// // FormatTools.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats; import java.io.IOException; import java.util.Vector; import loci.common.DateTools; import loci.common.RandomAccessInputStream; import loci.common.ReflectException; import loci.common.ReflectedUniverse; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.meta.DummyMetadata; import loci.formats.meta.MetadataRetrieve; import loci.formats.meta.MetadataStore; import loci.formats.services.OMEXMLService; import loci.formats.services.OMEXMLServiceImpl; /** * A utility class for format reader and writer implementations. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/FormatTools.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/FormatTools.java;hb=HEAD">Gitweb</a></dd></dl> */ public final class FormatTools { // -- Constants - pixel types -- /** Identifies the <i>INT8</i> data type used to store pixel values. */ public static final int INT8 = 0; /** Identifies the <i>UINT8</i> data type used to store pixel values. */ public static final int UINT8 = 1; /** Identifies the <i>INT16</i> data type used to store pixel values. */ public static final int INT16 = 2; /** Identifies the <i>UINT16</i> data type used to store pixel values. */ public static final int UINT16 = 3; /** Identifies the <i>INT32</i> data type used to store pixel values. */ public static final int INT32 = 4; /** Identifies the <i>UINT32</i> data type used to store pixel values. */ public static final int UINT32 = 5; /** Identifies the <i>FLOAT</i> data type used to store pixel values. */ public static final int FLOAT = 6; /** Identifies the <i>DOUBLE</i> data type used to store pixel values. */ public static final int DOUBLE = 7; /** Human readable pixel type. */ private static final String[] pixelTypes = makePixelTypes(); static String[] makePixelTypes() { String[] pixelTypes = new String[8]; pixelTypes[INT8] = "int8"; pixelTypes[UINT8] = "uint8"; pixelTypes[INT16] = "int16"; pixelTypes[UINT16] = "uint16"; pixelTypes[INT32] = "int32"; pixelTypes[UINT32] = "uint32"; pixelTypes[FLOAT] = "float"; pixelTypes[DOUBLE] = "double"; return pixelTypes; } // -- Constants - dimensional labels -- /** * Identifies the <i>Channel</i> dimensional type, * representing a generic channel dimension. */ public static final String CHANNEL = "Channel"; /** * Identifies the <i>Spectra</i> dimensional type, * representing a dimension consisting of spectral channels. */ public static final String SPECTRA = "Spectra"; /** * Identifies the <i>Lifetime</i> dimensional type, * representing a dimension consisting of a lifetime histogram. */ public static final String LIFETIME = "Lifetime"; /** * Identifies the <i>Polarization</i> dimensional type, * representing a dimension consisting of polarization states. */ public static final String POLARIZATION = "Polarization"; /** * Identifies the <i>Phase</i> dimensional type, * representing a dimension consisting of phases. */ public static final String PHASE = "Phase"; /** * Identifies the <i>Frequency</i> dimensional type, * representing a dimension consisting of frequencies. */ public static final String FREQUENCY = "Frequency"; // -- Constants - miscellaneous -- /** File grouping options. */ public static final int MUST_GROUP = 0; public static final int CAN_GROUP = 1; public static final int CANNOT_GROUP = 2; /** Patterns to be used when constructing a pattern for output filenames. */ public static final String SERIES_NUM = "%s"; public static final String SERIES_NAME = "%n"; public static final String CHANNEL_NUM = "%c"; public static final String CHANNEL_NAME = "%w"; public static final String Z_NUM = "%z"; public static final String T_NUM = "%t"; public static final String TIMESTAMP = "%A"; // -- Constants - versioning -- /** * Current SVN revision. * @deprecated After Git move, deprecated in favour of {@link #VCS_REVISION}. */ @Deprecated public static final String SVN_REVISION = "@vcs.revision@"; /** Current VCS revision. */ public static final String VCS_REVISION = "@vcs.revision@"; /** Date on which this release was built. */ public static final String DATE = "@date@"; /** Version number of this release. */ public static final String VERSION = "4.4-DEV"; // -- Constants - domains -- /** Identifies the high content screening domain. */ public static final String HCS_DOMAIN = "High-Content Screening (HCS)"; /** Identifies the light microscopy domain. */ public static final String LM_DOMAIN = "Light Microscopy"; /** Identifies the electron microscopy domain. */ public static final String EM_DOMAIN = "Electron Microscopy (EM)"; /** Identifies the scanning probe microscopy domain. */ public static final String SPM_DOMAIN = "Scanning Probe Microscopy (SPM)"; /** Identifies the scanning electron microscopy domain. */ public static final String SEM_DOMAIN = "Scanning Electron Microscopy (SEM)"; /** Identifies the fluorescence-lifetime domain. */ public static final String FLIM_DOMAIN = "Fluorescence-Lifetime Imaging"; /** Identifies the medical imaging domain. */ public static final String MEDICAL_DOMAIN = "Medical Imaging"; /** Identifies the histology domain. */ public static final String HISTOLOGY_DOMAIN = "Histology"; /** Identifies the gel and blot imaging domain. */ public static final String GEL_DOMAIN = "Gel/Blot Imaging"; /** Identifies the astronomy domain. */ public static final String ASTRONOMY_DOMAIN = "Astronomy"; /** * Identifies the graphics domain. * This includes formats used exclusively by analysis software. */ public static final String GRAPHICS_DOMAIN = "Graphics"; /** Identifies an unknown domain. */ public static final String UNKNOWN_DOMAIN = "Unknown"; /** List of non-graphics domains. */ public static final String[] NON_GRAPHICS_DOMAINS = new String[] { LM_DOMAIN, EM_DOMAIN, SPM_DOMAIN, SEM_DOMAIN, FLIM_DOMAIN, MEDICAL_DOMAIN, HISTOLOGY_DOMAIN, GEL_DOMAIN, ASTRONOMY_DOMAIN, HCS_DOMAIN, UNKNOWN_DOMAIN }; /** List of non-HCS domains. */ public static final String[] NON_HCS_DOMAINS = new String[] { LM_DOMAIN, EM_DOMAIN, SPM_DOMAIN, SEM_DOMAIN, FLIM_DOMAIN, MEDICAL_DOMAIN, HISTOLOGY_DOMAIN, GEL_DOMAIN, ASTRONOMY_DOMAIN, UNKNOWN_DOMAIN }; /** * List of domains that do not require special handling. Domains that * require special handling are {@link #GRAPHICS_DOMAIN} and * {@link #HCS_DOMAIN}. */ public static final String[] NON_SPECIAL_DOMAINS = new String[] { LM_DOMAIN, EM_DOMAIN, SPM_DOMAIN, SEM_DOMAIN, FLIM_DOMAIN, MEDICAL_DOMAIN, HISTOLOGY_DOMAIN, GEL_DOMAIN, ASTRONOMY_DOMAIN, UNKNOWN_DOMAIN }; /** List of all supported domains. */ public static final String[] ALL_DOMAINS = new String[] { HCS_DOMAIN, LM_DOMAIN, EM_DOMAIN, SPM_DOMAIN, SEM_DOMAIN, FLIM_DOMAIN, MEDICAL_DOMAIN, HISTOLOGY_DOMAIN, GEL_DOMAIN, ASTRONOMY_DOMAIN, GRAPHICS_DOMAIN, UNKNOWN_DOMAIN }; // -- Constants - web pages -- /** URL of Bio-Formats web page. */ public static final String URL_BIO_FORMATS = "http://www.loci.wisc.edu/software/bio-formats"; /** URL of 'Bio-Formats as a Java Library' web page. */ public static final String URL_BIO_FORMATS_LIBRARIES = "http://www.loci.wisc.edu/bio-formats/bio-formats-java-library"; /** URL of OME-TIFF web page. */ public static final String URL_OME_TIFF = "http://ome-xml.org/wiki/OmeTiff"; // -- Constructor -- private FormatTools() { } // -- Utility methods - dimensional positions -- /** * Gets the rasterized index corresponding * to the given Z, C and T coordinates. */ public static int getIndex(IFormatReader reader, int z, int c, int t) { String order = reader.getDimensionOrder(); int zSize = reader.getSizeZ(); int cSize = reader.getEffectiveSizeC(); int tSize = reader.getSizeT(); int num = reader.getImageCount(); return getIndex(order, zSize, cSize, tSize, num, z, c, t); } /** * Gets the rasterized index corresponding * to the given Z, C and T coordinates. * * @param order Dimension order. * @param zSize Total number of focal planes. * @param cSize Total number of channels. * @param tSize Total number of time points. * @param num Total number of image planes (zSize * cSize * tSize), * specified as a consistency check. * @param z Z coordinate of ZCT coordinate triple to convert to 1D index. * @param c C coordinate of ZCT coordinate triple to convert to 1D index. * @param t T coordinate of ZCT coordinate triple to convert to 1D index. */ public static int getIndex(String order, int zSize, int cSize, int tSize, int num, int z, int c, int t) { // check DimensionOrder if (order == null) { throw new IllegalArgumentException("Dimension order is null"); } if (!order.startsWith("XY") && !order.startsWith("YX")) { throw new IllegalArgumentException("Invalid dimension order: " + order); } int iz = order.indexOf("Z") - 2; int ic = order.indexOf("C") - 2; int it = order.indexOf("T") - 2; if (iz < 0 || iz > 2 || ic < 0 || ic > 2 || it < 0 || it > 2) { throw new IllegalArgumentException("Invalid dimension order: " + order); } // check SizeZ if (zSize <= 0) { throw new IllegalArgumentException("Invalid Z size: " + zSize); } if (z < 0 || z >= zSize) { throw new IllegalArgumentException("Invalid Z index: " + z + "/" + zSize); } // check SizeC if (cSize <= 0) { throw new IllegalArgumentException("Invalid C size: " + cSize); } if (c < 0 || c >= cSize) { throw new IllegalArgumentException("Invalid C index: " + c + "/" + cSize); } // check SizeT if (tSize <= 0) { throw new IllegalArgumentException("Invalid T size: " + tSize); } if (t < 0 || t >= tSize) { throw new IllegalArgumentException("Invalid T index: " + t + "/" + tSize); } // check image count if (num <= 0) { throw new IllegalArgumentException("Invalid image count: " + num); } if (num != zSize * cSize * tSize) { // if this happens, there is probably a bug in metadata population -- // either one of the ZCT sizes, or the total number of images -- // or else the input file is invalid throw new IllegalArgumentException("ZCT size vs image count mismatch " + "(sizeZ=" + zSize + ", sizeC=" + cSize + ", sizeT=" + tSize + ", total=" + num + ")"); } // assign rasterization order int v0 = iz == 0 ? z : (ic == 0 ? c : t); int v1 = iz == 1 ? z : (ic == 1 ? c : t); int v2 = iz == 2 ? z : (ic == 2 ? c : t); int len0 = iz == 0 ? zSize : (ic == 0 ? cSize : tSize); int len1 = iz == 1 ? zSize : (ic == 1 ? cSize : tSize); return v0 + v1 * len0 + v2 * len0 * len1; } /** * Gets the Z, C and T coordinates corresponding * to the given rasterized index value. */ public static int[] getZCTCoords(IFormatReader reader, int index) { String order = reader.getDimensionOrder(); int zSize = reader.getSizeZ(); int cSize = reader.getEffectiveSizeC(); int tSize = reader.getSizeT(); int num = reader.getImageCount(); return getZCTCoords(order, zSize, cSize, tSize, num, index); } /** * Gets the Z, C and T coordinates corresponding to the given rasterized * index value. * * @param order Dimension order. * @param zSize Total number of focal planes. * @param cSize Total number of channels. * @param tSize Total number of time points. * @param num Total number of image planes (zSize * cSize * tSize), * specified as a consistency check. * @param index 1D (rasterized) index to convert to ZCT coordinate triple. */ public static int[] getZCTCoords(String order, int zSize, int cSize, int tSize, int num, int index) { // check DimensionOrder if (order == null) { throw new IllegalArgumentException("Dimension order is null"); } if (!order.startsWith("XY") && !order.startsWith("YX")) { throw new IllegalArgumentException("Invalid dimension order: " + order); } int iz = order.indexOf("Z") - 2; int ic = order.indexOf("C") - 2; int it = order.indexOf("T") - 2; if (iz < 0 || iz > 2 || ic < 0 || ic > 2 || it < 0 || it > 2) { throw new IllegalArgumentException("Invalid dimension order: " + order); } // check SizeZ if (zSize <= 0) { throw new IllegalArgumentException("Invalid Z size: " + zSize); } // check SizeC if (cSize <= 0) { throw new IllegalArgumentException("Invalid C size: " + cSize); } // check SizeT if (tSize <= 0) { throw new IllegalArgumentException("Invalid T size: " + tSize); } // check image count if (num <= 0) { throw new IllegalArgumentException("Invalid image count: " + num); } if (num != zSize * cSize * tSize) { // if this happens, there is probably a bug in metadata population -- // either one of the ZCT sizes, or the total number of images -- // or else the input file is invalid throw new IllegalArgumentException("ZCT size vs image count mismatch " + "(sizeZ=" + zSize + ", sizeC=" + cSize + ", sizeT=" + tSize + ", total=" + num + ")"); } if (index < 0 || index >= num) { throw new IllegalArgumentException("Invalid image index: " + index + "/" + num); } // assign rasterization order int len0 = iz == 0 ? zSize : (ic == 0 ? cSize : tSize); int len1 = iz == 1 ? zSize : (ic == 1 ? cSize : tSize); //int len2 = iz == 2 ? sizeZ : (ic == 2 ? sizeC : sizeT); int v0 = index % len0; int v1 = index / len0 % len1; int v2 = index / len0 / len1; int z = iz == 0 ? v0 : (iz == 1 ? v1 : v2); int c = ic == 0 ? v0 : (ic == 1 ? v1 : v2); int t = it == 0 ? v0 : (it == 1 ? v1 : v2); return new int[] {z, c, t}; } /** * Converts index from the given dimension order to the reader's native one. * This method is useful for shuffling the planar order around * (rather than eassigning ZCT sizes as {@link DimensionSwapper} does). * * @throws FormatException Never actually thrown. */ public static int getReorderedIndex(IFormatReader reader, String newOrder, int newIndex) throws FormatException { String origOrder = reader.getDimensionOrder(); int zSize = reader.getSizeZ(); int cSize = reader.getEffectiveSizeC(); int tSize = reader.getSizeT(); int num = reader.getImageCount(); return getReorderedIndex(origOrder, newOrder, zSize, cSize, tSize, num, newIndex); } /** * Converts index from one dimension order to another. * This method is useful for shuffling the planar order around * (rather than eassigning ZCT sizes as {@link DimensionSwapper} does). * * @param origOrder Original dimension order. * @param newOrder New dimension order. * @param zSize Total number of focal planes. * @param cSize Total number of channels. * @param tSize Total number of time points. * @param num Total number of image planes (zSize * cSize * tSize), * specified as a consistency check. * @param newIndex 1D (rasterized) index according to new dimension order. * @return rasterized index according to original dimension order. */ public static int getReorderedIndex(String origOrder, String newOrder, int zSize, int cSize, int tSize, int num, int newIndex) { int[] zct = getZCTCoords(newOrder, zSize, cSize, tSize, num, newIndex); return getIndex(origOrder, zSize, cSize, tSize, num, zct[0], zct[1], zct[2]); } /** * Computes a unique 1-D index corresponding * to the given multidimensional position. * @param lengths the maximum value for each positional dimension * @param pos position along each dimensional axis * @return rasterized index value */ public static int positionToRaster(int[] lengths, int[] pos) { int offset = 1; int raster = 0; for (int i=0; i<pos.length; i++) { raster += offset * pos[i]; offset *= lengths[i]; } return raster; } /** * Computes a unique N-D position corresponding * to the given rasterized index value. * @param lengths the maximum value at each positional dimension * @param raster rasterized index value * @return position along each dimensional axis */ public static int[] rasterToPosition(int[] lengths, int raster) { return rasterToPosition(lengths, raster, new int[lengths.length]); } /** * Computes a unique N-D position corresponding * to the given rasterized index value. * @param lengths the maximum value at each positional dimension * @param raster rasterized index value * @param pos preallocated position array to populate with the result * @return position along each dimensional axis */ public static int[] rasterToPosition(int[] lengths, int raster, int[] pos) { int offset = 1; for (int i=0; i<pos.length; i++) { int offset1 = offset * lengths[i]; int q = i < pos.length - 1 ? raster % offset1 : raster; pos[i] = q / offset; raster -= q; offset = offset1; } return pos; } /** * Computes the number of raster values for a positional array * with the given lengths. */ public static int getRasterLength(int[] lengths) { int len = 1; for (int i=0; i<lengths.length; i++) len *= lengths[i]; return len; } // -- Utility methods - pixel types -- /** * Takes a string value and maps it to one of the pixel type enumerations. * @param pixelTypeAsString the pixel type as a string. * @return type enumeration value for use with class constants. */ public static int pixelTypeFromString(String pixelTypeAsString) { String lowercaseTypeAsString = pixelTypeAsString.toLowerCase(); for (int i = 0; i < pixelTypes.length; i++) { if (pixelTypes[i].equals(lowercaseTypeAsString)) return i; } throw new IllegalArgumentException("Unknown type: '" + pixelTypeAsString + "'"); } /** * Takes a pixel type value and gets a corresponding string representation. * @param pixelType the pixel type. * @return string value for human-readable output. */ public static String getPixelTypeString(int pixelType) { if (pixelType < 0 || pixelType >= pixelTypes.length) { throw new IllegalArgumentException("Unknown pixel type: " + pixelType); } return pixelTypes[pixelType]; } /** * Retrieves how many bytes per pixel the current plane or section has. * @param pixelType the pixel type as retrieved from * {@link IFormatReader#getPixelType()}. * @return the number of bytes per pixel. * @see IFormatReader#getPixelType() */ public static int getBytesPerPixel(int pixelType) { switch (pixelType) { case INT8: case UINT8: return 1; case INT16: case UINT16: return 2; case INT32: case UINT32: case FLOAT: return 4; case DOUBLE: return 8; } throw new IllegalArgumentException("Unknown pixel type: " + pixelType); } /** * Retrieves the number of bytes per pixel in the current plane. * @param pixelType the pixel type, as a String. * @return the number of bytes per pixel. * @see #pixelTypeFromString(String) * @see #getBytesPerPixel(int) */ public static int getBytesPerPixel(String pixelType) { return getBytesPerPixel(pixelTypeFromString(pixelType)); } /** * Determines whether the given pixel type is floating point or integer. * @param pixelType the pixel type as retrieved from * {@link IFormatReader#getPixelType()}. * @return true if the pixel type is floating point. * @see IFormatReader#getPixelType() */ public static boolean isFloatingPoint(int pixelType) { switch (pixelType) { case INT8: case UINT8: case INT16: case UINT16: case INT32: case UINT32: return false; case FLOAT: case DOUBLE: return true; } throw new IllegalArgumentException("Unknown pixel type: " + pixelType); } /** * Determines whether the given pixel type is signed or unsigned. * @param pixelType the pixel type as retrieved from * {@link IFormatReader#getPixelType()}. * @return true if the pixel type is signed. * @see IFormatReader#getPixelType() */ public static boolean isSigned(int pixelType) { switch (pixelType) { case INT8: case INT16: case INT32: case FLOAT: case DOUBLE: return true; case UINT8: case UINT16: case UINT32: return false; } throw new IllegalArgumentException("Unknown pixel type: " + pixelType); } /** * Returns an appropriate pixel type given the number of bytes per pixel. * * @param bytes number of bytes per pixel. * @param signed whether or not the pixel type should be signed. * @param fp whether or not these are floating point pixels. */ public static int pixelTypeFromBytes(int bytes, boolean signed, boolean fp) throws FormatException { switch (bytes) { case 1: return signed ? INT8 : UINT8; case 2: return signed ? INT16: UINT16; case 4: return fp ? FLOAT : signed ? INT32: UINT32; case 8: return DOUBLE; default: throw new FormatException("Unsupported byte depth: " + bytes); } } // -- Utility methods - sanity checking /** * Asserts that the current file is either null, or not, according to the * given flag. If the assertion fails, an IllegalStateException is thrown. * @param currentId File name to test. * @param notNull True iff id should be non-null. * @param depth How far back in the stack the calling method is; this name * is reported as part of the exception message, if available. Use zero * to suppress output of the calling method name. */ public static void assertId(String currentId, boolean notNull, int depth) { String msg = null; if (currentId == null && notNull) { msg = "Current file should not be null; call setId(String) first"; } else if (currentId != null && !notNull) { msg = "Current file should be null, but is '" + currentId + "'; call close() first"; } if (msg == null) return; StackTraceElement[] ste = new Exception().getStackTrace(); String header; if (depth > 0 && ste.length > depth) { String c = ste[depth].getClassName(); if (c.startsWith("loci.formats.")) { c = c.substring(c.lastIndexOf(".") + 1); } header = c + "." + ste[depth].getMethodName() + ": "; } else header = ""; throw new IllegalStateException(header + msg); } /** * Convenience method for checking that the plane number, tile size and * buffer sizes are all valid for the given reader. * If 'bufLength' is less than 0, then the buffer length check is not * performed. */ public static void checkPlaneParameters(IFormatReader r, int no, int bufLength, int x, int y, int w, int h) throws FormatException { assertId(r.getCurrentFile(), true, 2); checkPlaneNumber(r, no); checkTileSize(r, x, y, w, h); if (bufLength >= 0) checkBufferSize(r, bufLength, w, h); } /** Checks that the given plane number is valid for the given reader. */ public static void checkPlaneNumber(IFormatReader r, int no) throws FormatException { int imageCount = r.getImageCount(); if (no < 0 || no >= imageCount) { throw new FormatException("Invalid image number: " + no + " (series=" + r.getSeries() + ", imageCount=" + imageCount + ")"); } } /** Checks that the given tile size is valid for the given reader. */ public static void checkTileSize(IFormatReader r, int x, int y, int w, int h) throws FormatException { int width = r.getSizeX(); int height = r.getSizeY(); if (x < 0 || y < 0 || w < 0 || h < 0 || (x + w) > width || (y + h) > height) { throw new FormatException("Invalid tile size: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h); } } public static void checkBufferSize(IFormatReader r, int len) throws FormatException { checkBufferSize(r, len, r.getSizeX(), r.getSizeY()); } /** * Checks that the given buffer size is large enough to hold a w * h * image as returned by the given reader. * @throws FormatException if the buffer is too small */ public static void checkBufferSize(IFormatReader r, int len, int w, int h) throws FormatException { int size = getPlaneSize(r, w, h); if (size > len) { throw new FormatException("Buffer too small (got " + len + ", expected " + size + ")."); } } /** * Returns true if the given RandomAccessInputStream conatins at least * 'len' bytes. */ public static boolean validStream(RandomAccessInputStream stream, int len, boolean littleEndian) throws IOException { stream.seek(0); stream.order(littleEndian); return stream.length() >= len; } /** Returns the size in bytes of a single plane. */ public static int getPlaneSize(IFormatReader r) { return getPlaneSize(r, r.getSizeX(), r.getSizeY()); } /** Returns the size in bytes of a w * h tile. */ public static int getPlaneSize(IFormatReader r, int w, int h) { return w * h * r.getRGBChannelCount() * getBytesPerPixel(r.getPixelType()); } // -- Utility methods -- export /** * @throws FormatException Never actually thrown. * @throws IOException Never actually thrown. */ public static String getFilename(int series, int image, IFormatReader r, String pattern) throws FormatException, IOException { MetadataStore store = r.getMetadataStore(); MetadataRetrieve retrieve = store instanceof MetadataRetrieve ? (MetadataRetrieve) store : new DummyMetadata(); String filename = pattern.replaceAll(SERIES_NUM, String.valueOf(series)); String imageName = retrieve.getImageName(series); if (imageName == null) imageName = "Series" + series; imageName = imageName.replaceAll("/", "_"); imageName = imageName.replaceAll("\\\\", "_"); filename = filename.replaceAll(SERIES_NAME, imageName); r.setSeries(series); int[] coordinates = r.getZCTCoords(image); filename = filename.replaceAll(Z_NUM, String.valueOf(coordinates[0])); filename = filename.replaceAll(T_NUM, String.valueOf(coordinates[2])); filename = filename.replaceAll(CHANNEL_NUM, String.valueOf(coordinates[1])); String channelName = retrieve.getChannelName(series, coordinates[1]); if (channelName == null) channelName = String.valueOf(coordinates[1]); channelName = channelName.replaceAll("/", "_"); channelName = channelName.replaceAll("\\\\", "_"); filename = filename.replaceAll(CHANNEL_NAME, channelName); String date = retrieve.getImageAcquiredDate(series); long stamp = 0; if (retrieve.getPlaneCount(series) > image) { Double deltaT = retrieve.getPlaneDeltaT(series, image); if (deltaT != null) { stamp = (long) (deltaT * 1000); } } stamp += DateTools.getTime(date, DateTools.ISO8601_FORMAT); date = DateTools.convertDate(stamp, (int) DateTools.UNIX_EPOCH); filename = filename.replaceAll(TIMESTAMP, date); return filename; } public static String[] getFilenames(String pattern, IFormatReader r) throws FormatException, IOException { Vector<String> filenames = new Vector<String>(); String filename = null; for (int series=0; series<r.getSeriesCount(); series++) { r.setSeries(series); for (int image=0; image<r.getImageCount(); image++) { filename = getFilename(series, image, r, pattern); if (!filenames.contains(filename)) filenames.add(filename); } } return filenames.toArray(new String[0]); } public static int getImagesPerFile(String pattern, IFormatReader r) throws FormatException, IOException { String[] filenames = getFilenames(pattern, r); int totalPlanes = 0; for (int series=0; series<r.getSeriesCount(); series++) { r.setSeries(series); totalPlanes += r.getImageCount(); } return totalPlanes / filenames.length; } // -- Utility methods -- other /** * Recursively look for the first underlying reader that is an * instance of the given class. */ public static IFormatReader getReader(IFormatReader r, Class<? extends IFormatReader> c) { IFormatReader[] underlying = r.getUnderlyingReaders(); if (underlying != null) { for (int i=0; i<underlying.length; i++) { if (underlying[i].getClass().isInstance(c)) return underlying[i]; } for (int i=0; i<underlying.length; i++) { IFormatReader t = getReader(underlying[i], c); if (t != null) return t; } } return null; } /** * Default implementation for {@link IFormatReader#openThumbBytes}. * * At the moment, it uses {@link java.awt.image.BufferedImage} objects * to resize thumbnails, so it is not safe for use in headless contexts. * In the future, we may reimplement the image scaling logic purely with * byte arrays, but handling every case would be substantial effort, so * doing so is currently a low priority item. */ public static byte[] openThumbBytes(IFormatReader reader, int no) throws FormatException, IOException { // NB: Dependency on AWT here is unfortunate, but very difficult to // eliminate in general. We use reflection to limit class loading // problems with AWT on Mac OS X. ReflectedUniverse r = new ReflectedUniverse(); byte[][] bytes = null; try { r.exec("import loci.formats.gui.AWTImageTools"); int planeSize = getPlaneSize(reader); byte[] plane = null; if (planeSize < 0) { int width = reader.getThumbSizeX() * 4; int height = reader.getThumbSizeY() * 4; int x = (reader.getSizeX() - width) / 2; int y = (reader.getSizeY() - height) / 2; plane = reader.openBytes(no, x, y, width, height); } else { plane = reader.openBytes(no); } r.setVar("plane", plane); r.setVar("reader", reader); r.setVar("sizeX", reader.getSizeX()); r.setVar("sizeY", reader.getSizeY()); r.setVar("thumbSizeX", reader.getThumbSizeX()); r.setVar("thumbSizeY", reader.getThumbSizeY()); r.setVar("little", reader.isLittleEndian()); r.exec("img = AWTImageTools.openImage(plane, reader, sizeX, sizeY)"); r.exec("img = AWTImageTools.makeUnsigned(img)"); r.exec("thumb = AWTImageTools.scale(img, thumbSizeX, thumbSizeY, false)"); bytes = (byte[][]) r.exec("AWTImageTools.getPixelBytes(thumb, little)"); } catch (ReflectException exc) { throw new FormatException(exc); } if (bytes.length == 1) return bytes[0]; int rgbChannelCount = reader.getRGBChannelCount(); byte[] rtn = new byte[rgbChannelCount * bytes[0].length]; for (int i=0; i<rgbChannelCount; i++) { System.arraycopy(bytes[i], 0, rtn, bytes[0].length * i, bytes[i].length); } return rtn; } // -- Conversion convenience methods -- /** * Convenience method for converting the specified input file to the * specified output file. The ImageReader and ImageWriter classes are used * for input and output, respectively. To use other IFormatReader or * IFormatWriter implementation, * @see convert(IFormatReader, IFormatWriter, String). * * @param input the full path name of the existing input file * @param output the full path name of the output file to be created * @throws FormatException if there is a general problem reading from or * writing to one of the files. * @throws IOException if there is an I/O-related error. */ public static void convert(String input, String output) throws FormatException, IOException { IFormatReader reader = new ImageReader(); try { ServiceFactory factory = new ServiceFactory(); OMEXMLService service = factory.getInstance(OMEXMLService.class); reader.setMetadataStore(service.createOMEXMLMetadata()); } catch (DependencyException de) { throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de); } catch (ServiceException se) { throw new FormatException(se); } reader.setId(input); IFormatWriter writer = new ImageWriter(); convert(reader, writer, output); } /** * Convenience method for writing all of the images and metadata obtained * from the specified IFormatReader into the specified IFormatWriter. * * It is required that setId(String) be called on the IFormatReader * object before it is passed to convert(...). setMetadataStore(...) * should also have been called with an appropriate instance of IMetadata. * * The setId(String) method must not be called on the IFormatWriter * object; this is taken care of internally. Additionally, the * setMetadataRetrieve(...) method in IFormatWriter should not be called. * * @param input the pre-initialized IFormatReader used for reading data. * @param output the uninitialized IFormatWriter used for writing data. * @param outputFile the full path name of the output file to be created. * @throws FormatException if there is a general problem reading from or * writing to one of the files. * @throws IOException if there is an I/O-related error. */ public static void convert(IFormatReader input, IFormatWriter output, String outputFile) throws FormatException, IOException { MetadataStore store = input.getMetadataStore(); MetadataRetrieve meta = null; try { ServiceFactory factory = new ServiceFactory(); OMEXMLService service = factory.getInstance(OMEXMLService.class); meta = service.asRetrieve(store); } catch (DependencyException de) { throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de); } output.setMetadataRetrieve(meta); output.setId(outputFile); for (int series=0; series<input.getSeriesCount(); series++) { input.setSeries(series); output.setSeries(series); byte[] buf = new byte[getPlaneSize(input)]; for (int image=0; image<input.getImageCount(); image++) { input.openBytes(image, buf); output.saveBytes(image, buf); } } input.close(); output.close(); } }