//
// 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.");
}
}
}