/*
* #%L
* OME Bio-Formats package for reading and converting biological file formats.
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* 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, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package loci.formats.in;
import java.io.IOException;
import loci.common.DateTools;
import loci.common.RandomAccessInputStream;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
import loci.formats.tiff.IFD;
import loci.formats.tiff.TiffParser;
import loci.formats.tiff.TiffRational;
import ome.xml.model.primitives.PositiveFloat;
import ome.xml.model.primitives.Timestamp;
import ome.units.quantity.ElectricPotential;
import ome.units.quantity.Frequency;
import ome.units.quantity.Length;
import ome.units.quantity.Temperature;
import ome.units.quantity.Time;
import ome.units.UNITS;
/**
* FluoviewReader is the file format reader for
* Olympus Fluoview TIFF files AND Andor Bio-imaging Division (ABD) TIFF files.
*
* @author Eric Kjellman egkjellman at wisc.edu
* @author Melissa Linkert melissa at glencoesoftware.com
* @author Curtis Rueden ctrueden at wisc.edu
*/
public class FluoviewReader extends BaseTiffReader {
// -- Constants --
/** String identifying a Fluoview file. */
private static final String FLUOVIEW_MAGIC_STRING = "FLUOVIEW";
private static final String ANDOR_MAGIC_STRING = "Andor";
/** Private TIFF tags */
private static final int MMHEADER = 34361;
private static final int MMSTAMP = 34362;
private static final int TEMPERATURE = 4869;
private static final int EXPOSURE_TIME = 4876;
private static final int KINETIC_CYCLE_TIME = 4878;
private static final int N_ACCUMULATIONS = 4879;
private static final int ACQUISITION_CYCLE_TIME = 4881;
private static final int READOUT_TIME = 4882;
private static final int EM_DAC = 4885;
private static final int N_FRAMES = 4890;
private static final int HORIZONTAL_FLIP = 4896;
private static final int VERTICAL_FLIP = 4897;
private static final int CLOCKWISE = 4898;
private static final int COUNTER_CLOCKWISE = 4899;
private static final int VERTICAL_CLOCK_VOLTAGE = 4904;
private static final int VERTICAL_SHIFT_SPEED = 4905;
private static final int PRE_AMP_SETTING = 4907;
private static final int CAMERA_SERIAL_SETTING = 4908;
private static final int ACTUAL_TEMPERATURE = 4911;
private static final int BASELINE_CLAMP = 4912;
private static final int PRESCANS = 4913;
private static final int MODEL = 4914;
private static final int CHIP_SIZE_X = 4915;
private static final int CHIP_SIZE_Y = 4916;
private static final int BASELINE_OFFSET = 4944;
/** Date format */
private static final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss.SSS";
// -- Fields --
/** Pixel dimensions for this file. */
private double voxelX = 1, voxelY = 1, voxelZ = 1, voxelC = 1, voxelT = 1;
private String dimensionOrder;
private String date = null;
private int timeIndex = -1;
private int fieldIndex = -1;
private int montageIndex = -1;
/** Timestamps for each plane, in seconds. */
private double[][] stamps = null;
// hardware settings
private String[] gains, voltages, offsets, channelNames, lensNA;
private String mag, detectorManufacturer, objectiveManufacturer, comment;
private Float temperature, exposureTime, readoutTime;
private String model;
private double[][] montageOffsets;
private double[][] fieldOffsets;
// -- Constructor --
/** Constructs a new Fluoview TIFF reader. */
public FluoviewReader() {
super("Olympus Fluoview/ABD TIFF", new String[] {"tif", "tiff"});
suffixSufficient = false;
domains = new String[] {FormatTools.LM_DOMAIN};
datasetDescription =
"One or more .tif/.tiff files, and an optional .txt file";
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
TiffParser tp = new TiffParser(stream);
IFD ifd = tp.getFirstIFD();
if (ifd == null) return false;
String com = ifd.getComment();
if (com == null) com = "";
return (com.indexOf(FLUOVIEW_MAGIC_STRING) != -1 &&
ifd.containsKey(new Integer(MMHEADER)) ||
ifd.containsKey(new Integer(MMSTAMP))) ||
com.startsWith(ANDOR_MAGIC_STRING);
}
/**
* @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
{
int image = getImageIndex(no);
if (tiffParser == null) {
initTiffParser();
}
if (getSizeY() == ifds.get(0).getImageLength()) {
tiffParser.getSamples(ifds.get(image), buf, x, y, w, h);
}
else {
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
tiffParser.getSamples(ifds.get(0), buf, x, image, w, 1);
}
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
voxelX = voxelY = voxelZ = voxelC = voxelT = 1;
dimensionOrder = null;
gains = voltages = offsets = channelNames = lensNA = null;
mag = detectorManufacturer = objectiveManufacturer = comment = null;
date = null;
timeIndex = -1;
stamps = null;
fieldIndex = -1;
montageIndex = -1;
fieldOffsets = null;
montageOffsets = null;
model = null;
temperature = null;
exposureTime = null;
readoutTime = null;
}
}
// -- Internal BaseTiffReader API methods --
/* @see loci.formats.BaseTiffReader#initStandardMetadata() */
@Override
protected void initStandardMetadata() throws FormatException, IOException {
super.initStandardMetadata();
// First, we want to determine whether this file is a Fluoview TIFF.
// Originally, Andor TIFF had its own reader; however, the two formats are
// very similar, so it made more sense to merge the two formats into one
// reader.
short[] s = ifds.get(0).getIFDShortArray(MMHEADER);
if (s == null) {
initAlternateMetadata();
return;
}
byte[] mmheader = shortArrayToBytes(s);
RandomAccessInputStream ras = new RandomAccessInputStream(mmheader);
ras.order(isLittleEndian());
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
put("Header Flag", ras.readShort());
put("Image Type", ras.read());
String name = ras.readString(257);
name = name.substring(0, name.indexOf("\0"));
put("Image name", name);
ras.skipBytes(4); // skip pointer to data field
put("Number of colors", ras.readInt());
ras.skipBytes(4); // skip pointer to palette field
ras.skipBytes(4); // skip pointer to other palette field
put("Comment size", ras.readInt());
ras.skipBytes(4); // skip pointer to comment field
}
else ras.skipBytes(284);
// read dimension information
String[] names = new String[10];
int[] sizes = new int[10];
double[] resolutions = new double[10];
for (int i=0; i<10; i++) {
names[i] = ras.readString(16);
sizes[i] = ras.readInt();
double origin = ras.readDouble();
resolutions[i] = ras.readDouble();
put("Dimension " + (i + 1) + " Name", names[i]);
put("Dimension " + (i + 1) + " Size", sizes[i]);
put("Dimension " + (i + 1) + " Origin", origin);
put("Dimension " + (i + 1) + " Resolution", resolutions[i]);
put("Dimension " + (i + 1) + " Units", ras.readString(64));
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
ras.skipBytes(4); // skip pointer to spatial position data
put("Map type", ras.readShort());
put("Map min", ras.readDouble());
put("Map max", ras.readDouble());
put("Min value", ras.readDouble());
put("Max value", ras.readDouble());
ras.skipBytes(4); // skip pointer to map data
put("Gamma", ras.readDouble());
put("Offset", ras.readDouble());
// read gray channel data
put("Gray Channel Name", ras.readString(16));
put("Gray Channel Size", ras.readInt());
put("Gray Channel Origin", ras.readDouble());
put("Gray Channel Resolution", ras.readDouble());
put("Gray Channel Units", ras.readString(64));
ras.skipBytes(4); // skip pointer to thumbnail data
put("Voice field", ras.readInt());
ras.skipBytes(4); // skip pointer to voice field
// now we need to read the MMSTAMP data to determine dimension order
readStamps();
}
ras.close();
// calculate the dimension order and axis sizes
CoreMetadata m = core.get(0);
dimensionOrder = "XY";
int seriesCount = 1;
m.sizeZ = m.sizeC = m.sizeT = 1;
for (int i=0; i<10; i++) {
String name = names[i];
int size = sizes[i];
double voxel = resolutions[i];
if (name == null || size == 0) continue;
name = name.toLowerCase().trim();
if (name.length() == 0) continue;
if (name.equals("x")) {
voxelX = voxel;
}
else if (name.equals("y")) {
voxelY = voxel;
}
else if (name.equals("z") || name.equals("event")) {
m.sizeZ *= size;
if (dimensionOrder.indexOf("Z") == -1) {
dimensionOrder += "Z";
}
voxelZ = voxel;
}
else if (name.equals("ch") || name.equals("wavelength")) {
m.sizeC *= size;
if (dimensionOrder.indexOf("C") == -1) {
dimensionOrder += "C";
}
voxelC = voxel;
}
else if (name.equals("time") || name.equals("t") ||
name.equals("animation"))
{
m.sizeT *= size;
if (dimensionOrder.indexOf("T") == -1) {
dimensionOrder += "T";
}
voxelT = voxel;
timeIndex = i - 2;
}
else {
if (dimensionOrder.indexOf("S") == -1) dimensionOrder += "S";
seriesCount *= size;
if (name.equals("montage")) montageIndex = i - 2;
else if (name.equals("xy")) fieldIndex = i - 2;
}
}
if (dimensionOrder.indexOf("Z") == -1) dimensionOrder += "Z";
if (dimensionOrder.indexOf("T") == -1) dimensionOrder += "T";
if (dimensionOrder.indexOf("C") == -1) dimensionOrder += "C";
if (dimensionOrder.indexOf("S") == -1) dimensionOrder += "S";
m.imageCount = ifds.size() / seriesCount;
if (getSizeZ() > getImageCount()) m.sizeZ = getImageCount();
if (getSizeT() > getImageCount()) m.sizeT = getImageCount();
if (getSizeZ() * getSizeC() * getSizeT() > getImageCount()) {
int diff = getSizeZ() * getSizeC() * getSizeT() - getImageCount();
if (diff == getSizeC()) {
if (getSizeZ() > 1) m.sizeZ--;
else if (getSizeT() > 1) m.sizeT--;
else m.sizeC /= getSizeC();
}
}
if (getImageCount() == 1 && (getSizeT() == getSizeY() ||
getSizeZ() == getSizeY()) && (getSizeT() > getImageCount() ||
getSizeZ() > getImageCount()))
{
m.sizeY = 1;
m.imageCount = getSizeZ() * getSizeC() * getSizeT();
}
m.dimensionOrder = dimensionOrder.replaceAll("S", "");
if (getPixelType() == FormatTools.UINT32) {
m.pixelType = FormatTools.FLOAT;
}
if (seriesCount > 1) {
core.clear();
for (int i=0; i<seriesCount; i++) {
core.add(m);
}
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
// cut up the comment, if necessary
comment = ifds.get(0).getComment();
gains = new String[getSizeC()];
offsets = new String[getSizeC()];
voltages = new String[getSizeC()];
channelNames = new String[getSizeC()];
lensNA = new String[getSizeC()];
parsePageName();
parseComment();
addGlobalMeta("Comment", comment);
}
}
/* @see loci.formats.in.BaseTiffReader#initMetadataStore() */
@Override
protected void initMetadataStore() throws FormatException {
super.initMetadataStore();
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this, true);
if (date != null) {
store.setImageAcquisitionDate(new Timestamp(date), 0);
}
if (getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM) {
return;
}
if (ifds.get(0).get(MMHEADER) == null) {
initAlternateMetadataStore();
return;
}
store.setImageDescription(comment, 0);
// link Instrument and Image
String instrumentID = MetadataTools.createLSID("Instrument", 0);
store.setInstrumentID(instrumentID, 0);
store.setImageInstrumentRef(instrumentID, 0);
// populate timing data
if (timeIndex >= 0) {
for (int s=0; s<getSeriesCount(); s++) {
setSeries(s);
for (int i=0; i<getImageCount(); i++) {
int index = getImageIndex(i);
store.setPlaneDeltaT(new Time(stamps[timeIndex][index], UNITS.S), s, i);
}
}
setSeries(0);
}
// populate Dimensions
for (int i=0; i<getSeriesCount(); i++) {
Length sizeX = FormatTools.getPhysicalSizeX(voxelX);
Length sizeY = FormatTools.getPhysicalSizeY(voxelY);
Length sizeZ = FormatTools.getPhysicalSizeZ(voxelZ);
if (sizeX != null) {
store.setPixelsPhysicalSizeX(sizeX, i);
}
if (sizeY != null) {
store.setPixelsPhysicalSizeY(sizeY, i);
}
if (sizeZ != null) {
store.setPixelsPhysicalSizeZ(sizeZ, i);
}
store.setPixelsTimeIncrement(new Time(voxelT, UNITS.S), i);
int montage = getMontage(i);
int field = getField(i);
double posX = 0d, posY = 0d, posZ = 0d;
if (montageOffsets != null && montage < montageOffsets.length) {
if (montageOffsets[montage].length > 0) {
posX += montageOffsets[montage][0];
}
if (montageOffsets[montage].length > 1) {
posY += montageOffsets[montage][1];
}
if (montageOffsets[montage].length > 2) {
posZ += montageOffsets[montage][2];
}
}
if (fieldOffsets != null && field < fieldOffsets.length) {
if (fieldOffsets[field].length > 0) {
posX += fieldOffsets[field][0];
}
if (fieldOffsets[field].length > 1) {
posY += fieldOffsets[field][1];
}
if (fieldOffsets[field].length > 2) {
posZ += fieldOffsets[field][2];
}
}
for (int image=0; image<getImageCount(); image++) {
final Length xl = new Length(posX, UNITS.REFERENCEFRAME);
final Length yl = new Length(posY, UNITS.REFERENCEFRAME);
final Length zl = new Length(posZ, UNITS.REFERENCEFRAME);
store.setPlanePositionX(xl, i, image);
store.setPlanePositionY(yl, i, image);
store.setPlanePositionZ(zl, i, image);
}
}
// populate LogicalChannel data
if (channelNames != null) {
for (int i=0; i<getSizeC(); i++) {
if (channelNames[i] != null) {
store.setChannelName(channelNames[i].trim(), 0, i);
}
}
}
// populate Detector data
for (int i=0; i<getSizeC(); i++) {
if (voltages != null && voltages[i] != null) {
store.setDetectorSettingsVoltage(
new ElectricPotential(new Double(voltages[i]), UNITS.V), 0, i);
}
if (gains != null && gains[i] != null) {
store.setDetectorSettingsGain(new Double(gains[i]), 0, i);
}
if (offsets != null && offsets[i] != null) {
store.setDetectorSettingsOffset(new Double(offsets[i]), 0, i);
}
store.setDetectorType(getDetectorType("Other"), 0, i);
if (detectorManufacturer != null) {
store.setDetectorManufacturer(detectorManufacturer, 0, i);
}
// link DetectorSettings to an actual Detector
String detectorID = MetadataTools.createLSID("Detector", 0, i);
store.setDetectorID(detectorID, 0, i);
store.setDetectorSettingsID(detectorID, 0, i);
}
// populate Objective data
if (mag != null && mag.toLowerCase().endsWith("x")) {
mag = mag.substring(0, mag.length() - 1);
}
else if (mag == null) mag = "1";
store.setObjectiveCorrection(getCorrection("Other"), 0, 0);
store.setObjectiveImmersion(getImmersion("Other"), 0, 0);
if (objectiveManufacturer != null) {
String[] objectiveData = objectiveManufacturer.split(" ");
store.setObjectiveModel(objectiveData[0], 0, 0);
if (objectiveData.length > 2) {
store.setObjectiveImmersion(getImmersion(objectiveData[2]), 0, 0);
}
}
if (mag != null) {
store.setObjectiveCalibratedMagnification(new Double(mag), 0, 0);
}
if (lensNA != null) {
for (int i=0; i<getSizeC(); i++) {
if (lensNA[i] != null) {
store.setObjectiveLensNA(new Double(lensNA[i]), 0, i);
}
}
}
// link Objective to Image using ObjectiveSettings
String objectiveID = MetadataTools.createLSID("Objective", 0, 0);
store.setObjectiveID(objectiveID, 0, 0);
store.setObjectiveSettingsID(objectiveID, 0);
}
// -- Helper methods --
private void initAlternateMetadata() throws FormatException, IOException {
IFD firstIFD = ifds.get(0);
temperature = (Float) firstIFD.getIFDValue(TEMPERATURE);
exposureTime = (Float) firstIFD.getIFDValue(EXPOSURE_TIME);
Float kineticCycleTime = (Float) firstIFD.getIFDValue(KINETIC_CYCLE_TIME);
Double nAccumulations = (Double) firstIFD.getIFDValue(N_ACCUMULATIONS);
Float acquisitionCycleTime =
(Float) firstIFD.getIFDValue(ACQUISITION_CYCLE_TIME);
readoutTime = (Float) firstIFD.getIFDValue(READOUT_TIME);
Double emDAC = (Double) firstIFD.getIFDValue(EM_DAC);
Double nFrames = (Double) firstIFD.getIFDValue(N_FRAMES);
Double horizontalFlip = (Double) firstIFD.getIFDValue(HORIZONTAL_FLIP);
Double verticalFlip = (Double) firstIFD.getIFDValue(VERTICAL_FLIP);
Double clockwise = (Double) firstIFD.getIFDValue(CLOCKWISE);
Double counterClockwise = (Double) firstIFD.getIFDValue(COUNTER_CLOCKWISE);
Double verticalClockVoltage =
(Double) firstIFD.getIFDValue(VERTICAL_CLOCK_VOLTAGE);
Float verticalShiftSpeed =
(Float) firstIFD.getIFDValue(VERTICAL_SHIFT_SPEED);
Float preamp = (Float) firstIFD.getIFDValue(PRE_AMP_SETTING);
Double serialSetting = (Double) firstIFD.getIFDValue(CAMERA_SERIAL_SETTING);
Float actualTemperature = (Float) firstIFD.getIFDValue(ACTUAL_TEMPERATURE);
Double baselineClamp = (Double) firstIFD.getIFDValue(BASELINE_CLAMP);
Double prescans = (Double) firstIFD.getIFDValue(PRESCANS);
model = firstIFD.getIFDTextValue(MODEL);
Double chipSizeX = (Double) firstIFD.getIFDValue(CHIP_SIZE_X);
Double chipSizeY = (Double) firstIFD.getIFDValue(CHIP_SIZE_Y);
Double baselineOffset = (Double) firstIFD.getIFDValue(BASELINE_OFFSET);
addGlobalMeta("Temperature", temperature);
addGlobalMeta("Exposure time (in seconds)", exposureTime);
addGlobalMeta("Kinetic cycle time", kineticCycleTime);
addGlobalMeta("Number of accumulations", nAccumulations);
addGlobalMeta("Acquisition cycle time", acquisitionCycleTime);
addGlobalMeta("Readout time", readoutTime);
addGlobalMeta("EM DAC", emDAC);
addGlobalMeta("Number of frames", nFrames);
addGlobalMeta("Horizontal flip", horizontalFlip);
addGlobalMeta("Vertical flip", verticalFlip);
addGlobalMeta("Clockwise rotation", clockwise);
addGlobalMeta("Counter-clockwise rotation", counterClockwise);
addGlobalMeta("Vertical clock voltage", verticalClockVoltage);
addGlobalMeta("Vertical shift speed", verticalShiftSpeed);
addGlobalMeta("Pre-amp", preamp);
addGlobalMeta("Camera serial setting", serialSetting);
addGlobalMeta("Actual temperature", actualTemperature);
addGlobalMeta("Baseline clamp", baselineClamp);
addGlobalMeta("Prescans", prescans);
addGlobalMeta("Camera model", model);
addGlobalMeta("Chip size X", chipSizeX);
addGlobalMeta("Chip size Y", chipSizeY);
addGlobalMeta("Baseline offset", baselineOffset);
}
private void initAlternateMetadataStore() throws FormatException {
MetadataStore store = makeFilterMetadata();
store.setImagingEnvironmentTemperature(
new Temperature(new Double(temperature.floatValue()), UNITS.DEGREEC), 0);
String instrumentID = MetadataTools.createLSID("Instrument", 0);
String detectorID = MetadataTools.createLSID("Detector", 0, 0);
store.setInstrumentID(instrumentID, 0);
store.setDetectorID(detectorID, 0, 0);
store.setDetectorModel(model, 0, 0);
store.setImageInstrumentRef(instrumentID, 0);
if (exposureTime != null) {
for (int i=0; i<getImageCount(); i++) {
store.setPlaneExposureTime(new Time(new Double(exposureTime.floatValue()), UNITS.S), 0, i);
}
}
for (int i=0; i<getEffectiveSizeC(); i++) {
store.setDetectorSettingsID(detectorID, 0, i);
store.setDetectorSettingsReadOutRate(
new Frequency(new Double(readoutTime.floatValue()), UNITS.HZ), 0, i);
}
}
private int getImageIndex(int no) {
// the 'series' axis can be in any position relative to Z, C and T
// we need to convert the plane number within the series into an IFD number
if (getSeriesCount() == 1) return no;
int[] lengths = new int[4];
int[] pos = getZCTCoords(no);
int[] realPos = new int[4];
for (int i=2; i<dimensionOrder.length(); i++) {
char axis = dimensionOrder.charAt(i);
if (axis == 'Z') {
lengths[i - 2] = getSizeZ();
realPos[i - 2] = pos[0];
}
else if (axis == 'C') {
lengths[i - 2] = getEffectiveSizeC();
realPos[i - 2] = pos[1];
}
else if (axis == 'T') {
lengths[i - 2] = getSizeT();
realPos[i - 2] = pos[2];
}
else if (axis == 'S') {
lengths[i - 2] = getSeriesCount();
realPos[i - 2] = getSeries();
}
}
return FormatTools.positionToRaster(lengths, realPos);
}
private void readStamps() throws FormatException, IOException {
stamps = new double[8][ifds.size()];
for (int i=0; i<ifds.size(); i++) {
byte[] stamp = shortArrayToBytes(ifds.get(i).getIFDShortArray(MMSTAMP));
RandomAccessInputStream ras = new RandomAccessInputStream(stamp);
ras.order(isLittleEndian());
// each stamp is 8 doubles, representing the position on dimensions 3-10
for (int j=0; j<8; j++) {
stamps[j][i] = ras.readDouble() / 1000;
}
ras.close();
}
}
private byte[] shortArrayToBytes(short[] s) {
byte[] b = new byte[s.length];
for (int i=0; i<s.length; i++) {
b[i] = (byte) s[i];
}
return b;
}
private void parsePageName() {
String pageName = ifds.get(0).getIFDTextValue(IFD.PAGE_NAME);
if (pageName == null) return;
String[] lines = pageName.split("\n");
for (String line : lines) {
if (line.startsWith("Resolution")) {
String[] resolutions = line.split("\t");
if (resolutions.length > 1) {
voxelX = Double.parseDouble(resolutions[1].trim());
}
if (resolutions.length > 2) {
voxelY = Double.parseDouble(resolutions[2].trim());
}
break;
}
}
}
private void parseComment() {
if (comment != null) {
// this is an INI-style comment, with one key/value pair per line
String[] lines = comment.split("\n");
for (String token : lines) {
token = token.trim();
int eq = token.indexOf("=");
if (eq != -1) {
String key = token.substring(0, eq);
String value = token.substring(eq + 1);
addGlobalMeta(key, value);
if (key.startsWith("Gain Ch")) {
int index = Integer.parseInt(key.substring(7).trim());
if (index > 0 && index <= gains.length) {
gains[index - 1] = value;
}
}
else if (key.startsWith("PMT Voltage Ch")) {
int index = Integer.parseInt(key.substring(14).trim());
if (index > 0 && index <= voltages.length) {
voltages[index - 1] = value;
}
}
else if (key.startsWith("Offset Ch")) {
int index = Integer.parseInt(key.substring(9).trim());
if (index > 0 && index <= offsets.length) {
offsets[index - 1] = value;
}
}
else if (key.equals("Magnification")) mag = value;
else if (key.equals("System Configuration")) {
detectorManufacturer = value;
}
else if (key.equals("Objective Lens")) objectiveManufacturer = value;
else if (key.startsWith("Channel ") && key.endsWith("Dye")) {
for (int i=0; i<channelNames.length; i++) {
if (channelNames[i] == null) {
channelNames[i] = value;
break;
}
}
}
else if (key.startsWith("Confocal Aperture-Ch")) {
int index = Integer.parseInt(key.substring(20).trim());
if (index > 0 && index <= lensNA.length) {
lensNA[index - 1] = value.substring(0, value.length() - 2);
}
}
else if (key.equals("Date")) {
date = value;
}
else if (key.equals("Time")) {
date += " " + value;
}
else if (key.equals("MontageOffsets")) {
String[] offsets = value.split("\t");
montageOffsets = new double[offsets.length - 1][3];
for (int i=1; i<offsets.length; i++) {
String[] v = offsets[i].trim().split(",");
for (int j=0; j<v.length; j++) {
montageOffsets[i - 1][j] = Double.parseDouble(v[j].trim());
}
}
}
else if (key.equals("XYFields")) {
String[] offsets = value.split("\t");
fieldOffsets = new double[offsets.length - 1][3];
for (int i=1; i<offsets.length; i++) {
String[] v = offsets[i].trim().split(",");
for (int j=0; j<v.length; j++) {
try {
fieldOffsets[i - 1][j] = Double.parseDouble(v[j].trim());
}
catch (NumberFormatException e) { }
}
}
}
}
else if (token.startsWith("Z") && token.indexOf(" um ") != -1) {
// looking for "Z - x um in y planes"
String z = token.substring(token.indexOf("-") + 1);
z = z.replaceAll("\\p{Alpha}", "").trim();
int firstSpace = z.indexOf(" ");
double size = Double.parseDouble(z.substring(0, firstSpace));
double nPlanes = Double.parseDouble(z.substring(firstSpace).trim());
voxelZ = size / nPlanes;
}
}
if (date != null) {
date = DateTools.formatDate(date.trim(),
new String[] {"MM/dd/yyyy hh:mm:ss a", "MM-dd-yyyy hh:mm:ss","MM/dd/yyyy H:mm:ss"}, true);
Timestamp timestamp = Timestamp.valueOf(date);
if (timeIndex >= 0 && timestamp != null) {
long ms = timestamp.asInstant().getMillis();
int nChars = String.valueOf(getImageCount()).length();
for (int i=0; i<getImageCount(); i++) {
int[] zct = getZCTCoords(i);
String key = String.format(
"Timestamp for Z=%2s, C=%2s, T=%2s", zct[0], zct[1], zct[2]);
long stamp = ms + (long) (stamps[timeIndex][i] * 1000);
addGlobalMeta(key,
DateTools.convertDate(stamp, DateTools.UNIX, DATE_FORMAT));
}
}
}
int start = comment.indexOf("[Version Info]");
int end = comment.indexOf("[Version Info End]");
if (start != -1 && end != -1 && end > start) {
comment = comment.substring(start + 14, end).trim();
start = comment.indexOf("=") + 1;
end = comment.indexOf("\n");
if (end > start) comment = comment.substring(start, end).trim();
else comment = comment.substring(start).trim();
}
else comment = "";
}
}
private int getMontage(int seriesIndex) {
if (montageOffsets == null && fieldOffsets == null) return 0;
int[] pos = getPos(seriesIndex);
return montageIndex < fieldIndex ? pos[0] : pos[1];
}
private int getField(int seriesIndex) {
if (montageOffsets == null && fieldOffsets == null) return 0;
int[] pos = getPos(seriesIndex);
return montageIndex < fieldIndex ? pos[1] : pos[0];
}
private int[] getPos(int seriesIndex) {
int[] lengths = new int[2];
if (montageIndex < fieldIndex) {
lengths[0] = montageOffsets == null ? 1 : montageOffsets.length;
lengths[1] = fieldOffsets == null ? 1 : fieldOffsets.length;
}
else {
lengths[1] = montageOffsets == null ? 1 : montageOffsets.length;
lengths[0] = fieldOffsets == null ? 1 : fieldOffsets.length;
}
if (lengths[0] == 0) lengths[0] = 1;
if (lengths[1] == 0) lengths[1] = 1;
return FormatTools.rasterToPosition(lengths, seriesIndex);
}
}