//
// FakeReader.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.in;
import java.io.IOException;
import java.util.Random;
import loci.common.DataTools;
import loci.common.Location;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
/**
* FakeReader is the file format reader for faking input data.
* It is mainly useful for testing.
* <p>Examples:<ul>
* <li>showinf 'multi-series&series=11&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake' -series 9</li>
* <li>showinf '8bit-signed&pixelType=int8&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '8bit-unsigned&pixelType=uint8&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '16bit-signed&pixelType=int16&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '16bit-unsigned&pixelType=uint16&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '32bit-signed&pixelType=int32&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '32bit-unsigned&pixelType=uint32&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '32bit-floating&pixelType=float&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* <li>showinf '64bit-floating&pixelType=double&sizeZ=3&sizeC=5&sizeT=7&sizeY=50.fake'</li>
* </ul></p>
*
* <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/in/FakeReader.java">Trac</a>
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/FakeReader.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class FakeReader extends FormatReader {
// -- Constants --
public static final int BOX_SIZE = 10;
public static final int DEFAULT_SIZE_X = 512;
public static final int DEFAULT_SIZE_Y = 512;
public static final int DEFAULT_SIZE_Z = 1;
public static final int DEFAULT_SIZE_C = 1;
public static final int DEFAULT_SIZE_T = 1;
public static final int DEFAULT_PIXEL_TYPE = FormatTools.UINT8;
public static final int DEFAULT_RGB_CHANNEL_COUNT = 1;
public static final String DEFAULT_DIMENSION_ORDER = "XYZCT";
private static final String TOKEN_SEPARATOR = "&";
private static final long SEED = 0xcafebabe;
// -- Fields --
/** Scale factor for gradient, if any. */
private double scaleFactor = 1;
/** 8-bit lookup table, if indexed color, one per channel. */
private byte[][][] lut8 = null;
/** 16-bit lookup table, if indexed color, one per channel. */
private short[][][] lut16 = null;
/** For indexed color, mapping from indices to values and vice versa. */
private int[][] indexToValue = null, valueToIndex = null;
/** Channel of last opened image plane. */
private int ac = 0;
// -- Constructor --
/** Constructs a new fake reader. */
public FakeReader() { super("Simulated data", "fake"); }
// -- IFormatReader API methods --
/* @see IFormatReader#get8BitLookupTable() */
@Override
public byte[][] get8BitLookupTable() throws FormatException, IOException {
return ac < 0 || lut8 == null ? null : lut8[ac];
}
/* @see IFormatReader#get16BitLookupTable() */
@Override
public short[][] get16BitLookupTable() throws FormatException, IOException {
return ac < 0 || lut16 == null ? null : lut16[ac];
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
@Override
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
final int series = getSeries();
final int pixelType = getPixelType();
final int bpp = FormatTools.getBytesPerPixel(pixelType);
final boolean signed = FormatTools.isSigned(pixelType);
final boolean floating = FormatTools.isFloatingPoint(pixelType);
final int rgb = getRGBChannelCount();
final boolean indexed = isIndexed();
final boolean little = isLittleEndian();
final boolean interleaved = isInterleaved();
final int[] zct = getZCTCoords(no);
final int zIndex = zct[0], cIndex = zct[1], tIndex = zct[2];
ac = cIndex;
// integer types start gradient at the smallest value
long min = signed ? (long) -Math.pow(2, 8 * bpp - 1) : 0;
if (floating) min = 0; // floating point types always start at 0
for (int cOffset=0; cOffset<rgb; cOffset++) {
int channel = rgb * cIndex + cOffset;
for (int row=0; row<h; row++) {
int yy = y + row;
for (int col=0; col<w; col++) {
int xx = x + col;
long pixel = min + xx;
// encode various information into the image plane
boolean specialPixel = false;
if (yy < BOX_SIZE) {
int grid = xx / BOX_SIZE;
specialPixel = true;
switch (grid) {
case 0:
pixel = series;
break;
case 1:
pixel = no;
break;
case 2:
pixel = zIndex;
break;
case 3:
pixel = channel;
break;
case 4:
pixel = tIndex;
break;
default:
// just a normal pixel in the gradient
specialPixel = false;
}
}
// if indexed color with non-null LUT, convert value to index
if (indexed) {
if (lut8 != null) pixel = valueToIndex[ac][(int) (pixel % 256)];
if (lut16 != null) pixel = valueToIndex[ac][(int) (pixel % 65536)];
}
// scale pixel value by the scale factor
// if floating point, convert value to raw IEEE floating point bits
switch (pixelType) {
case FormatTools.FLOAT:
float floatPixel;
if (specialPixel) floatPixel = pixel;
else floatPixel = (float) (scaleFactor * pixel);
pixel = Float.floatToIntBits(floatPixel);
break;
case FormatTools.DOUBLE:
double doublePixel;
if (specialPixel) doublePixel = pixel;
else doublePixel = scaleFactor * pixel;
pixel = Double.doubleToLongBits(doublePixel);
break;
default:
if (!specialPixel) pixel = (long) (scaleFactor * pixel);
}
// unpack pixel into byte buffer
int index;
if (interleaved) index = w * rgb * row + rgb * col + cOffset; // CXY
else index = h * w * cOffset + w * row + col; // XYC
index *= bpp;
DataTools.unpackBytes(pixel, buf, index, bpp, little);
}
}
}
return buf;
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
String path = id;
if (new Location(id).exists()) {
path = new Location(id).getAbsoluteFile().getName();
}
String noExt = path.substring(0, path.lastIndexOf("."));
String[] tokens = noExt.split(TOKEN_SEPARATOR);
String name = null;
int sizeX = DEFAULT_SIZE_X;
int sizeY = DEFAULT_SIZE_Y;
int sizeZ = DEFAULT_SIZE_Z;
int sizeC = DEFAULT_SIZE_C;
int sizeT = DEFAULT_SIZE_T;
int thumbSizeX = 0; // default
int thumbSizeY = 0; // default
int pixelType = DEFAULT_PIXEL_TYPE;
int bitsPerPixel = 0; // default
int rgb = DEFAULT_RGB_CHANNEL_COUNT;
String dimOrder = DEFAULT_DIMENSION_ORDER;
boolean orderCertain = true;
boolean little = true;
boolean interleaved = false;
boolean indexed = false;
boolean falseColor = false;
boolean metadataComplete = true;
boolean thumbnail = false;
int seriesCount = 1;
int lutLength = 3;
// parse tokens from filename
for (String token : tokens) {
if (name == null) {
// first token is the image name
name = token;
continue;
}
int equals = token.indexOf("=");
if (equals < 0) {
LOGGER.warn("ignoring token: {}", token);
continue;
}
String key = token.substring(0, equals);
String value = token.substring(equals + 1);
boolean boolValue = value.equals("true");
double doubleValue = Double.NaN;
try {
doubleValue = Double.parseDouble(value);
}
catch (NumberFormatException exc) { }
int intValue = Double.isNaN(doubleValue) ? -1 : (int) doubleValue;
if (key.equals("sizeX")) sizeX = intValue;
else if (key.equals("sizeY")) sizeY = intValue;
else if (key.equals("sizeZ")) sizeZ = intValue;
else if (key.equals("sizeC")) sizeC = intValue;
else if (key.equals("sizeT")) sizeT = intValue;
else if (key.equals("thumbSizeX")) thumbSizeX = intValue;
else if (key.equals("thumbSizeY")) thumbSizeY = intValue;
else if (key.equals("pixelType")) {
pixelType = FormatTools.pixelTypeFromString(value);
}
else if (key.equals("bitsPerPixel")) bitsPerPixel = intValue;
else if (key.equals("rgb")) rgb = intValue;
else if (key.equals("dimOrder")) dimOrder = value.toUpperCase();
else if (key.equals("orderCertain")) orderCertain = boolValue;
else if (key.equals("little")) little = boolValue;
else if (key.equals("interleaved")) interleaved = boolValue;
else if (key.equals("indexed")) indexed = boolValue;
else if (key.equals("falseColor")) falseColor = boolValue;
else if (key.equals("metadataComplete")) metadataComplete = boolValue;
else if (key.equals("thumbnail")) thumbnail = boolValue;
else if (key.equals("series")) seriesCount = intValue;
else if (key.equals("lutLength")) lutLength = intValue;
else if (key.equals("scaleFactor")) scaleFactor = doubleValue;
}
// do some sanity checks
if (sizeX < 1) throw new FormatException("Invalid sizeX: " + sizeX);
if (sizeY < 1) throw new FormatException("Invalid sizeY: " + sizeY);
if (sizeZ < 1) throw new FormatException("Invalid sizeZ: " + sizeZ);
if (sizeC < 1) throw new FormatException("Invalid sizeC: " + sizeC);
if (sizeT < 1) throw new FormatException("Invalid sizeT: " + sizeT);
if (thumbSizeX < 0) {
throw new FormatException("Invalid thumbSizeX: " + thumbSizeX);
}
if (thumbSizeY < 0) {
throw new FormatException("Invalid thumbSizeY: " + thumbSizeY);
}
if (rgb < 1 || rgb > sizeC || sizeC % rgb != 0) {
throw new FormatException("Invalid sizeC/rgb combination: " +
sizeC + "/" + rgb);
}
getDimensionOrder(dimOrder);
if (falseColor && !indexed) {
throw new FormatException("False color images must be indexed");
}
if (seriesCount < 1) {
throw new FormatException("Invalid seriesCount: " + seriesCount);
}
if (lutLength < 1) {
throw new FormatException("Invalid lutLength: " + lutLength);
}
// populate core metadata
int effSizeC = sizeC / rgb;
core = new CoreMetadata[seriesCount];
for (int s=0; s<seriesCount; s++) {
core[s] = new CoreMetadata();
core[s].sizeX = sizeX;
core[s].sizeY = sizeY;
core[s].sizeZ = sizeZ;
core[s].sizeC = sizeC;
core[s].sizeT = sizeT;
core[s].thumbSizeX = thumbSizeX;
core[s].thumbSizeY = thumbSizeY;
core[s].pixelType = pixelType;
core[s].bitsPerPixel = bitsPerPixel;
core[s].imageCount = sizeZ * effSizeC * sizeT;
core[s].rgb = rgb > 1;
core[s].dimensionOrder = dimOrder;
core[s].orderCertain = orderCertain;
core[s].littleEndian = little;
core[s].interleaved = interleaved;
core[s].indexed = indexed;
core[s].falseColor = falseColor;
core[s].metadataComplete = metadataComplete;
core[s].thumbnail = thumbnail;
}
// populate OME metadata
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
for (int s=0; s<seriesCount; s++) {
String imageName = s > 0 ? name + " " + (s + 1) : name;
store.setImageName(imageName, s);
MetadataTools.setDefaultCreationDate(store, id, s);
}
// for indexed color images, create lookup tables
if (indexed) {
if (pixelType == FormatTools.UINT8) {
// create 8-bit LUTs
final int num = 256;
createIndexMap(num);
lut8 = new byte[sizeC][lutLength][num];
// linear ramp
for (int c=0; c<sizeC; c++) {
for (int i=0; i<lutLength; i++) {
for (int index=0; index<num; index++) {
lut8[c][i][index] = (byte) indexToValue[c][index];
}
}
}
}
else if (pixelType == FormatTools.UINT16) {
// create 16-bit LUTs
final int num = 65536;
createIndexMap(num);
lut16 = new short[sizeC][lutLength][num];
// linear ramp
for (int c=0; c<sizeC; c++) {
for (int i=0; i<lutLength; i++) {
for (int index=0; index<num; index++) {
lut16[c][i][index] = (short) indexToValue[c][index];
}
}
}
}
// NB: Other pixel types will have null LUTs.
}
}
// -- Helper methods --
/** Creates a mapping between indices and color values. */
private void createIndexMap(int num) {
int sizeC = core[0].sizeC;
// create random mapping from indices to values
indexToValue = new int[sizeC][num];
for (int c=0; c<sizeC; c++) {
for (int index=0; index<num; index++) indexToValue[c][index] = index;
shuffle(c, indexToValue[c]);
}
// create inverse mapping: values to indices
valueToIndex = new int[sizeC][num];
for (int c=0; c<sizeC; c++) {
for (int index=0; index<num; index++) {
int value = indexToValue[c][index];
valueToIndex[c][value] = index;
}
}
}
/** Fisher-Yates shuffle with constant seeds to ensure reproducibility. */
private static void shuffle(int c, int[] array) {
Random r = new Random(SEED + c);
for (int i = array.length; i > 1; i--) {
int j = r.nextInt(i);
int tmp = array[j];
array[j] = array[i - 1];
array[i - 1] = tmp;
}
}
}