/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package loci.formats.in;
import static ome.xml.model.Pixels.getPhysicalSizeXUnitXsdDefault;
import static ome.xml.model.Pixels.getPhysicalSizeYUnitXsdDefault;
import static ome.xml.model.Pixels.getPhysicalSizeZUnitXsdDefault;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import loci.common.DataTools;
import loci.common.DateTools;
import loci.common.IniList;
import loci.common.IniParser;
import loci.common.IniTable;
import loci.common.Location;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.ResourceNamer;
import loci.formats.meta.MetadataStore;
import loci.formats.ome.OMEXMLMetadata;
import loci.formats.services.OMEXMLService;
import ome.specification.XMLMockObjects;
import ome.xml.meta.OMEXMLMetadataRoot;
import ome.xml.model.MapPair;
import ome.xml.model.OME;
import ome.xml.model.enums.EnumerationException;
import ome.xml.model.enums.UnitsLength;
import ome.xml.model.enums.handlers.UnitsLengthEnumHandler;
import ome.xml.model.primitives.Color;
import ome.xml.model.primitives.Timestamp;
import ome.units.quantity.Length;
import ome.units.quantity.Time;
import ome.units.unit.Unit;
import ome.units.UNITS;
/**
* 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>
* <li>showinf 'SPW&plates=2&plateRows=3&plateCols=3&fields=8&plateAcqs=5.fake'</li>
* </ul></p>
*/
public class FakeReader extends FormatReader {
// -- Constants --
private static final long ANN_LONG_VALUE = 365;
private static final Double ANN_DOUBLE_VALUE = 0.111;
private static final String ANNOTATION_PREFIX = "Annotation:";
private static final String ANNOTATION_NAMESPACE = "fake-reader";
private static final String ANN_TERM_VALUE = "Term:";
private static final String ANN_TAG_VALUE = "Tag:";
private static final Timestamp ANN_TIME_VALUE = new Timestamp("1970-01-01T00:00:00");
private static final boolean ANN_BOOLEAN_VALUE = true;
private static final String ANN_COMMENT_VALUE = "Comment:";
private static final String ANN_XML_VALUE_START = "<dummyXml>";
private static final String ANN_XML_VALUE_END = "</dummyXml>";
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 --
/** exposure time per plane info */
private Time exposureTime = null;
/* physical sizes */
private Length physicalSizeX, physicalSizeY, physicalSizeZ;
/** 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;
/** Properties companion file which can be associated with this fake file */
private String iniFile;
/** List of used files if the fake is a SPW structure */
private List<String> fakeSeries = new ArrayList<String>();
private OMEXMLMetadata omeXmlMetadata;
private OMEXMLService omeXmlService;
// -- Constructor --
/** Constructs a new fake reader. */
public FakeReader() {
super("Simulated data", "fake");
hasCompanionFiles = true;
}
// -- IFormatReader API methods --
@Override
public byte[][] get8BitLookupTable() throws FormatException, IOException {
return ac < 0 || lut8 == null ? null : lut8[ac];
}
@Override
public short[][] get16BitLookupTable() throws FormatException, IOException {
return ac < 0 || lut16 == null ? null : lut16[ac];
}
@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 s = 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 = s;
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 --
@Override
public boolean isSingleFile(String id) throws FormatException, IOException {
if (new Location(id).isDirectory() && checkSuffix(id, "fake")) {
fakeSeries.clear();
return listFakeSeries(id).size() <= 1;
}
if (checkSuffix(id, "fake" + ".ini")) {
return ! new Location(id).exists();
}
return ! new Location(id + ".ini").exists();
}
@Override
public boolean isThisType(String name, boolean open) {
if (checkSuffix(name, "fake.ini"))
{
return true;
}
fakeSeries.clear();
if (name.endsWith(".fake") && listFakeSeries(name).size() > 0) {
return true;
}
return super.isThisType(name, open);
}
@Override
public String[] getSeriesUsedFiles(boolean noPixels) {
FormatTools.assertId(currentId, true, 1);
List<String> files = new ArrayList<String>();
fakeSeries.clear();
if (!noPixels) files.addAll(listFakeSeries(currentId));
if (iniFile != null) files.add(iniFile);
return files.toArray(new String[files.size()]);
}
private void findLogFiles() {
iniFile = null;
Location loc = new Location(getCurrentFile() + ".ini");
if (loc.exists()) {
iniFile = loc.getAbsolutePath();
}
}
@Override
public void close(boolean fileOnly) throws IOException {
iniFile = null;
super.close(fileOnly);
}
public OMEXMLMetadata getOmeXmlMetadata() {
if (omeXmlMetadata == null) {
try {
omeXmlMetadata = getOmeXmlService().createOMEXMLMetadata();
} catch (ServiceException exc) {
LOGGER.error("Could not create OME-XML metadata", exc);
}
}
return omeXmlMetadata;
}
public OMEXMLService getOmeXmlService() {
if (omeXmlService == null) {
try {
omeXmlService = new ServiceFactory().getInstance(OMEXMLService.class);
} catch (DependencyException exc) {
LOGGER.error("Could not create OME-XML service", exc);
}
}
return omeXmlService;
}
@Override
protected void initFile(String id) throws FormatException, IOException {
if (!checkSuffix(id, "fake")) {
if (checkSuffix(id, "fake.ini")) {
id = id.substring(0, id.lastIndexOf("."));
}
Location file = new Location(id).getAbsoluteFile();
if (!file.exists()) {
Location dir = file.getParentFile();
String[] list = dir.list(true);
String name = file.getName();
name = name.substring(0, name.lastIndexOf("."));
for (String f : list) {
if (checkSuffix(f, "fake") && f.startsWith(name)) {
id = new Location(dir, f).getAbsolutePath();
break;
}
}
}
}
// Logic copied from deltavision. This should probably be refactored into
// a helper method "replaceBySuffix" or something.
super.initFile(id);
findLogFiles();
String path = id;
Location location = new Location(id);
String[] tokens = null;
if (location.exists()) {
path = location.getAbsoluteFile().getName();
if (path.startsWith("Field")) {
Location root = location.getAbsoluteFile().getParentFile();
if (root != null) {
root = root.getParentFile();
if (root != null) {
root = root.getParentFile();
if (root != null) {
root = root.getParentFile();
if (isSPWStructure(root.getAbsolutePath())) {
tokens = extractTokensFromFakeSeries(root.getAbsolutePath());
// makes sure that getSeriesUsedFiles returns correctly
currentId = root.getAbsolutePath();
}
}
}
}
}
}
if (location.isDirectory() && isSPWStructure(location.getAbsolutePath())) {
tokens = extractTokensFromFakeSeries(location.getAbsolutePath());
} else if (tokens == null) {
String noExt = path.substring(0, path.lastIndexOf("."));
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;
String acquisitionDate = null;
int plates = 0;
int plateRows = 0;
int plateCols = 0;
int fields = 0;
int plateAcqs = 0;
/*
* Other annotation types that could be added
* int annFile = 0; // FileAnnotation
* int annList = 0; // ListAnnotation
*/
int annLong = 0;
int annDouble = 0;
int annComment = 0;
int annBool = 0;
int annTime = 0;
int annTag = 0;
int annTerm = 0;
int annXml = 0;
int annMap = 0;
Integer defaultColor = null;
ArrayList<Integer> color = new ArrayList<Integer>();
// add properties file values to list of tokens.
if (iniFile != null) {
IniParser parser = new IniParser();
IniList list = parser.parseINI(new File(iniFile));
List<String> newTokens = new ArrayList<String>();
// Unclear what to do with other headers...
IniTable table = list.getTable(IniTable.DEFAULT_HEADER);
if (table != null) {
for (Map.Entry<String, String> entry : table.entrySet()) {
newTokens.add(entry.getKey() + "=" + entry.getValue());
}
}
table = list.getTable("GlobalMetadata");
if (table != null) {
for (Map.Entry<String, String> entry : table.entrySet()) {
addGlobalMeta(entry.getKey(), entry.getValue());
}
}
String[] newTokArr = newTokens.toArray(new String[0]);
String[] oldTokArr = tokens;
tokens = new String[newTokArr.length + oldTokArr.length];
System.arraycopy(oldTokArr, 0, tokens, 0, oldTokArr.length);
System.arraycopy(newTokArr, 0, tokens, oldTokArr.length, newTokArr.length);
// Properties overrides file name values
}
// 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;
try {
doubleValue = Double.parseDouble(value);
}
catch (NumberFormatException exc) {
doubleValue = Double.NaN;
}
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;
else if (key.equals("exposureTime")) exposureTime = new Time((float) doubleValue, UNITS.S);
else if (key.equals("acquisitionDate")) acquisitionDate = value;
else if (key.equals("plates")) plates = intValue;
else if (key.equals("plateRows")) plateRows = intValue;
else if (key.equals("plateCols")) plateCols = intValue;
else if (key.equals("fields")) fields = intValue;
else if (key.equals("plateAcqs")) plateAcqs = intValue;
else if (key.equals("annLong")) annLong = intValue;
else if (key.equals("annDouble")) annDouble = intValue;
else if (key.equals("annMap")) annMap = intValue;
else if (key.equals("annComment")) annComment = intValue;
else if (key.equals("annBool")) annBool = intValue;
else if (key.equals("annTime")) annTime = intValue;
else if (key.equals("annTag")) annTag = intValue;
else if (key.equals("annTerm")) annTerm = intValue;
else if (key.equals("annXml")) annXml = intValue;
else if (key.equals("physicalSizeX")) physicalSizeX = parseLength(value, getPhysicalSizeXUnitXsdDefault());
else if (key.equals("physicalSizeY")) physicalSizeY = parseLength(value, getPhysicalSizeYUnitXsdDefault());
else if (key.equals("physicalSizeZ")) physicalSizeZ = parseLength(value, getPhysicalSizeZUnitXsdDefault());
else if (key.equals("color")) {
defaultColor = parseColor(value);
}
else if (key.startsWith("color_")) {
// 'color' and 'color_x' can be used together, but 'color_x' takes
// precedence. 'color' will in that case be used for any missing
// or invalid 'color_x' values.
int index = Integer.parseInt(key.substring(key.indexOf("_") + 1));
while (index >= color.size()) {
color.add(null);
}
color.set(index, parseColor(value));
}
}
// 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 SPW metadata
MetadataStore store = makeFilterMetadata();
boolean hasSPW = plates > 0 && plateRows > 0 &&
plateCols > 0 && fields > 0 && plateAcqs > 0;
if (hasSPW) {
// generate SPW metadata and override series count to match
int imageCount =
populateSPW(store, plates, plateRows, plateCols, fields, plateAcqs);
if (imageCount > 0) seriesCount = imageCount;
else hasSPW = false; // failed to generate SPW metadata
}
// populate core metadata
int effSizeC = sizeC / rgb;
core.clear();
for (int s=0; s<seriesCount; s++) {
CoreMetadata ms = new CoreMetadata();
core.add(ms);
ms.sizeX = sizeX;
ms.sizeY = sizeY;
ms.sizeZ = sizeZ;
ms.sizeC = sizeC;
ms.sizeT = sizeT;
ms.thumbSizeX = thumbSizeX;
ms.thumbSizeY = thumbSizeY;
ms.pixelType = pixelType;
ms.bitsPerPixel = bitsPerPixel;
ms.imageCount = sizeZ * effSizeC * sizeT;
ms.rgb = rgb > 1;
ms.dimensionOrder = dimOrder;
ms.orderCertain = orderCertain;
ms.littleEndian = little;
ms.interleaved = interleaved;
ms.indexed = indexed;
ms.falseColor = falseColor;
ms.metadataComplete = metadataComplete;
ms.thumbnail = thumbnail;
}
// populate OME metadata
boolean planeInfo = (exposureTime != null);
// per file counts
int annotationCount = 0;
int annotationDoubleCount = 0;
int annotationLongCount = 0;
int annotationBoolCount = 0;
int annotationCommentCount = 0;
int annotationTagCount = 0;
int annotationTermCount = 0;
int annotationTimeCount = 0;
int annotationXmlCount = 0;
int annotationMapCount = 0;
// per image count
int annotationRefCount = 0;
String nextAnnotationID;
MetadataTools.populatePixels(store, this, planeInfo);
fillExposureTime(store);
fillPhysicalSizes(store);
for (int currentImageIndex=0; currentImageIndex<seriesCount; currentImageIndex++) {
String imageName = currentImageIndex > 0 ? name + " " + (currentImageIndex + 1) : name;
store.setImageName(imageName, currentImageIndex);
if (acquisitionDate != null) {
if(DateTools.getTime(acquisitionDate, DateTools.FILENAME_FORMAT) != -1) {
store.setImageAcquisitionDate(new Timestamp(DateTools.formatDate(acquisitionDate, DateTools.FILENAME_FORMAT)), currentImageIndex);
}
}
for (int c=0; c<getEffectiveSizeC(); c++) {
Color channel = defaultColor == null ? null: new Color(defaultColor);
if (c < color.size() && color.get(c) != null) {
channel = new Color(color.get(c));
}
if (channel != null) {
store.setChannelColor(channel, currentImageIndex, c);
}
}
// new image so reset annotationRefCount
annotationRefCount = 0;
for (int currentAnnotation=0; currentAnnotation<annLong; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setLongAnnotationID(nextAnnotationID, annotationLongCount);
store.setLongAnnotationNamespace(ANNOTATION_NAMESPACE, annotationLongCount);
store.setLongAnnotationValue(ANN_LONG_VALUE+annotationCount, annotationLongCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationLongCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annDouble; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setDoubleAnnotationID(nextAnnotationID, annotationDoubleCount);
store.setDoubleAnnotationNamespace(ANNOTATION_NAMESPACE, annotationDoubleCount);
store.setDoubleAnnotationValue(ANN_DOUBLE_VALUE*(annotationCount+1), annotationDoubleCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationDoubleCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annMap; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setMapAnnotationID(nextAnnotationID, annotationMapCount);
store.setMapAnnotationNamespace(ANNOTATION_NAMESPACE, annotationMapCount);
List<MapPair> mapValue = new ArrayList<MapPair>();
for (int keyNum=0; keyNum<10; keyNum++) {
mapValue.add(new MapPair("keyS" + currentImageIndex + "N" + keyNum, "val" + (keyNum+1)*(annotationCount+1)));
}
store.setMapAnnotationValue(mapValue, annotationMapCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationMapCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annComment; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setCommentAnnotationID(nextAnnotationID, annotationCommentCount);
store.setCommentAnnotationNamespace(ANNOTATION_NAMESPACE, annotationCommentCount);
store.setCommentAnnotationValue(ANN_COMMENT_VALUE + (annotationCount+1), annotationCommentCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationCommentCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annBool; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setBooleanAnnotationID(nextAnnotationID, annotationBoolCount);
store.setBooleanAnnotationNamespace(ANNOTATION_NAMESPACE, annotationBoolCount);
store.setBooleanAnnotationValue(ANN_BOOLEAN_VALUE, annotationBoolCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationBoolCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annTime; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setTimestampAnnotationID(nextAnnotationID, annotationTimeCount);
store.setTimestampAnnotationNamespace(ANNOTATION_NAMESPACE, annotationTimeCount);
store.setTimestampAnnotationValue(ANN_TIME_VALUE, annotationTimeCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationTimeCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annTag; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setTagAnnotationID(nextAnnotationID, annotationTagCount);
store.setTagAnnotationNamespace(ANNOTATION_NAMESPACE, annotationTagCount);
store.setTagAnnotationValue(ANN_TAG_VALUE + (annotationCount+1), annotationTagCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationTagCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annTerm; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setTermAnnotationID(nextAnnotationID, annotationTermCount);
store.setTermAnnotationNamespace(ANNOTATION_NAMESPACE, annotationTermCount);
store.setTermAnnotationValue(ANN_TERM_VALUE + (annotationCount+1), annotationTermCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationTermCount++;
annotationCount++;
annotationRefCount++;
}
for (int currentAnnotation=0; currentAnnotation<annXml; currentAnnotation++) {
nextAnnotationID = ANNOTATION_PREFIX + annotationCount;
store.setXMLAnnotationID(nextAnnotationID, annotationXmlCount);
store.setXMLAnnotationNamespace(ANNOTATION_NAMESPACE, annotationXmlCount);
store.setXMLAnnotationValue(ANN_XML_VALUE_START + (annotationCount+1) + ANN_XML_VALUE_END, annotationXmlCount);
store.setImageAnnotationRef(nextAnnotationID, currentImageIndex, annotationRefCount);
annotationXmlCount++;
annotationCount++;
annotationRefCount++;
}
}
// 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.
}
}
private void fillPhysicalSizes(MetadataStore store) {
if (physicalSizeX == null && physicalSizeY == null && physicalSizeZ == null) return;
for (int s=0; s<getSeriesCount(); s++) {
store.setPixelsPhysicalSizeX(physicalSizeX, s);
store.setPixelsPhysicalSizeY(physicalSizeY, s);
store.setPixelsPhysicalSizeZ(physicalSizeZ, s);
}
}
private void fillExposureTime(MetadataStore store) {
if (exposureTime == null) return;
int oldSeries = getSeries();
for (int s=0; s<getSeriesCount(); s++) {
setSeries(s);
for (int i=0; i<getImageCount(); i++) {
store.setPlaneExposureTime(exposureTime, s, i);
}
}
setSeries(oldSeries);
}
// -- Helper methods --
private String[] extractTokensFromFakeSeries(String path) {
List<String> tokens = new ArrayList<String>();
int plates = 0, plateAcqs = 0, rows = 0, cols = 0, fields = 0;
String currentPlate = "";
String regExFileSeparator = File.separatorChar == '\\' ?
"\\\\" : File.separator;
// This is a sub-optimal approach, based on the assumption
// that the last fakeSeries[] element has the fakeImage with biggest indices
// in its name.
for (String fakeImage : fakeSeries) {
for (String pathToken : fakeImage.split(regExFileSeparator)) {
if (pathToken.startsWith(ResourceNamer.PLATE)) {
if (!pathToken.equals(currentPlate)) {
currentPlate = pathToken;
plates++;
}
}
}
}
for (String pathToken : fakeSeries.get(fakeSeries.size() - 1)
.split(regExFileSeparator)) {
if (pathToken.startsWith(ResourceNamer.RUN)) {
plateAcqs = Integer.parseInt(pathToken.substring(pathToken.lastIndexOf(
ResourceNamer.RUN) + ResourceNamer.RUN.length(),
pathToken.length())) + 1;
} else if (pathToken.startsWith(ResourceNamer.WELL)) {
String wellId = pathToken.substring(pathToken.lastIndexOf(
ResourceNamer.WELL) + ResourceNamer.WELL.length(),
pathToken.length());
String[] elements = wellId.split("(?<=\\p{L})(?=\\d)");
rows = ResourceNamer.alphabeticIndexCount(elements[0]);
cols = Integer.parseInt(elements[1]) + 1;
} else if (pathToken.startsWith(ResourceNamer.FIELD)) {
String fieldName = pathToken.substring(0, pathToken.lastIndexOf("."));
fields = Integer.parseInt(fieldName.substring(fieldName.lastIndexOf(
ResourceNamer.FIELD) + ResourceNamer.FIELD.length(),
fieldName.length())) + 1;
}
}
tokens.add(path);
tokens.add("plates="+plates);
tokens.add("plateRows="+rows);
tokens.add("plateCols="+cols);
tokens.add("fields="+fields);
tokens.add("plateAcqs="+plateAcqs);
return tokens.toArray(new String[tokens.size()]);
}
private boolean isSPWStructure(String path) {
fakeSeries.clear();
return !listFakeSeries(path).get(0).equals(path);
}
private int populateSPW(MetadataStore store, int plates, int rows, int cols,
int fields, int acqs)
{
final XMLMockObjects xml = new XMLMockObjects();
final OME ome =
xml.createPopulatedScreen(plates, rows, cols, fields, acqs);
getOmeXmlMetadata().setRoot(new OMEXMLMetadataRoot(ome));
// copy populated SPW metadata into destination MetadataStore
getOmeXmlService().convertMetadata(omeXmlMetadata, store);
domains = new String[] {FormatTools.HCS_DOMAIN};
return ome.sizeOfImageList();
}
/** Creates a mapping between indices and color values. */
private void createIndexMap(int num) {
int sizeC = core.get(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;
}
}
}
/** Traverses a fake file folder structure indicated by traversedDirectory */
private List<String> listFakeSeries(String traversedDirectory) {
File parent = new File(traversedDirectory);
if (parent.isDirectory()) {
File[] children = parent.listFiles();
Arrays.sort(children);
if (children != null) {
for (File child : children) {
listFakeSeries(child.getAbsolutePath());
}
}
} else {
String path = parent.getAbsolutePath();
// explicitly check suffixes, otherwise any other files that were put
// in the directory will be picked up (e.g. .DS_Store)
if (checkSuffix(path, "fake") || checkSuffix(path, "fake.ini")) {
fakeSeries.add(path);
}
}
return fakeSeries;
}
/** 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;
}
}
private int parseColor(String value) {
// parse colors as longs so that unsigned values can be specified,
// e.g. 0xff0000ff for red with opaque alpha
int base = 10;
if (value.startsWith("0x") || value.startsWith("0X")) {
value = value.substring(2);
base = 16;
}
try {
return (int) Long.parseLong(value, base);
}
catch (NumberFormatException e) { }
return 0;
}
private Length parseLength(String value, String defaultUnit) {
Matcher m = Pattern.compile("\\s*([\\d.]+)\\s*([\\D\\S]*)\\s*").matcher(value);
if (!m.matches()) {
throw new RuntimeException(String.format(
"%s does not match a physical size!", value));
}
String number = m.group(1);
String unit = m.group(2);
if (unit == null || unit.trim().length() == 0) {
unit = defaultUnit;
}
double d = Double.valueOf(number);
Unit<Length> l = null;
try {
l = UnitsLengthEnumHandler.getBaseUnit(UnitsLength.fromString(unit));
} catch (EnumerationException e) {
throw new RuntimeException(String.format(
"%s does not match a length unit!", unit));
}
return new Length(d, l);
}
}