// // FormatTools.java // /* LOCI Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman and Brian Loranger. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library 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; /** * A utility class for format reader and writer implementations. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatTools.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatTools.java">SVN</a></dd></dl> */ public final class FormatTools { // -- Constants -- /** 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 String[] pixelTypes; static { 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"; } /** * 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"; /** File grouping options. */ public static final int MUST_GROUP = 0; public static final int CAN_GROUP = 1; public static final int CANNOT_GROUP = 2; // -- 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. */ 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. */ 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 indices from the given dimension order to the native one. */ public static int getReorderedIndex(IFormatReader r, String order, int no) throws FormatException { int[] zct = getZCTCoords(order, r.getSizeZ(), r.getSizeC(), r.getSizeT(), r.getImageCount(), no); return getIndex(r.getDimensionOrder(), r.getSizeZ(), r.getSizeC(), r.getSizeT(), r.getImageCount(), 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 RuntimeException("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(String) */ 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); } // -- Utility methods - metadata /** * Populates the 'pixels' element of the given metadata store, using core * metadata from the given reader. */ public static void populatePixels(MetadataStore store, IFormatReader r) { int oldSeries = r.getSeries(); for (int i=0; i<r.getSeriesCount(); i++) { Integer ii = new Integer(i); r.setSeries(i); store.setPixels(new Integer(r.getSizeX()), new Integer(r.getSizeY()), new Integer(r.getSizeZ()), new Integer(r.getSizeC()), new Integer(r.getSizeT()), new Integer(r.getPixelType()), new Boolean(!r.isLittleEndian()), r.getDimensionOrder(), ii, null); } r.setSeries(oldSeries); } // -- 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); } /** Checks that the given plane number is valid for the given reader. */ public static void checkPlaneNumber(IFormatReader r, int no) throws FormatException { if (no < 0 || no >= r.getImageCount()) { throw new FormatException("Invalid image number: " + no); } } public static void checkBufferSize(IFormatReader r, int len) throws FormatException { int size = r.getSizeX() * r.getSizeY() * (r.isIndexed() ? 1 : r.getRGBChannelCount()) * getBytesPerPixel(r.getPixelType()); if (size > len) { throw new FormatException("Buffer too small."); } } }