/*
* #%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.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import loci.common.DataTools;
import loci.common.DateTools;
import loci.common.RandomAccessInputStream;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.common.xml.XMLTools;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.ImageTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
import loci.formats.services.OMEXMLService;
import ome.xml.model.enums.DetectorType;
import ome.xml.model.enums.LaserMedium;
import ome.xml.model.enums.LaserType;
import ome.xml.model.primitives.Color;
import ome.xml.model.primitives.PercentFraction;
import ome.xml.model.primitives.PositiveFloat;
import ome.xml.model.primitives.Timestamp;
import ome.units.quantity.Length;
import ome.units.quantity.Time;
import ome.units.UNITS;
import org.xml.sax.SAXException;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
/**
* LIFReader is the file format reader for Leica LIF files.
*
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public class LIFReader extends FormatReader {
// -- Constants --
public static final byte LIF_MAGIC_BYTE = 0x70;
public static final byte LIF_MEMORY_BYTE = 0x2a;
/** The encoding used in this file.*/
private static final String ENCODING = "ISO-8859-1";
private static final ImmutableMap<String, Integer> CHANNEL_PRIORITIES =
createChannelPriorities();
private static ImmutableMap<String, Integer> createChannelPriorities() {
final Builder<String, Integer> h = ImmutableMap.builder();
h.put("red", 0);
h.put("green", 1);
h.put("blue", 2);
h.put("cyan", 3);
h.put("magenta", 4);
h.put("yellow", 5);
h.put("black", 6);
h.put("gray", 7);
h.put("", 8);
return h.build();
}
// -- Fields --
/** Offsets to memory blocks, paired with their corresponding description. */
private List<Long> offsets;
private int[][] realChannel;
private int lastChannel = 0;
private List<String> lutNames = new ArrayList<String>();
private List<Double> physicalSizeXs = new ArrayList<Double>();
private List<Double> physicalSizeYs = new ArrayList<Double>();
private List<Length> fieldPosX = new ArrayList<Length>();
private List<Length> fieldPosY = new ArrayList<Length>();
private String[] descriptions, microscopeModels, serialNumber;
private Double[] pinholes, zooms, zSteps, tSteps, lensNA;
private Double[][] expTimes, gains, detectorOffsets;
private String[][] channelNames;
private List[] detectorModels;
private Double[][] exWaves;
private List[] activeDetector;
private HashMap[] detectorIndexes;
private String[] immersions, corrections, objectiveModels;
private Double[] magnification;
private Length[] posX, posY, posZ;
private Double[] refractiveIndex;
private List[] cutIns, cutOuts, filterModels;
private double[][] timestamps;
private List[] laserWavelength, laserIntensity, laserActive, laserFrap;
private ROI[][] imageROIs;
private boolean alternateCenter = false;
private String[] imageNames;
private double[] acquiredDate;
private int[] tileCount;
private long endPointer;
// -- Constructor --
/** Constructs a new Leica LIF reader. */
public LIFReader() {
super("Leica Image File Format", "lif");
suffixNecessary = false;
domains = new String[] {FormatTools.LM_DOMAIN};
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#getOptimalTileHeight() */
@Override
public int getOptimalTileHeight() {
FormatTools.assertId(currentId, true, 1);
return getSizeY();
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 1;
if (!FormatTools.validStream(stream, blockLen, true)) return false;
return stream.read() == LIF_MAGIC_BYTE;
}
/* @see loci.formats.IFormatReader#get8BitLookupTable() */
@Override
public byte[][] get8BitLookupTable() {
FormatTools.assertId(currentId, true, 1);
if (getPixelType() != FormatTools.UINT8 || !isIndexed()) return null;
if (lastChannel < 0 || lastChannel >= 9) {
return null;
}
byte[][] lut = new byte[3][256];
for (int i=0; i<256; i++) {
switch (lastChannel) {
case 0:
// red
lut[0][i] = (byte) (i & 0xff);
break;
case 1:
// green
lut[1][i] = (byte) (i & 0xff);
break;
case 2:
// blue
lut[2][i] = (byte) (i & 0xff);
break;
case 3:
// cyan
lut[1][i] = (byte) (i & 0xff);
lut[2][i] = (byte) (i & 0xff);
break;
case 4:
// magenta
lut[0][i] = (byte) (i & 0xff);
lut[2][i] = (byte) (i & 0xff);
break;
case 5:
// yellow
lut[0][i] = (byte) (i & 0xff);
lut[1][i] = (byte) (i & 0xff);
break;
default:
// gray
lut[0][i] = (byte) (i & 0xff);
lut[1][i] = (byte) (i & 0xff);
lut[2][i] = (byte) (i & 0xff);
}
}
return lut;
}
/* @see loci.formats.IFormatReader#get16BitLookupTable() */
@Override
public short[][] get16BitLookupTable() {
FormatTools.assertId(currentId, true, 1);
if (getPixelType() != FormatTools.UINT16 || !isIndexed()) return null;
if (lastChannel < 0 || lastChannel >= 9) {
return null;
}
short[][] lut = new short[3][65536];
for (int i=0; i<65536; i++) {
switch (lastChannel) {
case 0:
// red
lut[0][i] = (short) (i & 0xffff);
break;
case 1:
// green
lut[1][i] = (short) (i & 0xffff);
break;
case 2:
// blue
lut[2][i] = (short) (i & 0xffff);
break;
case 3:
// cyan
lut[1][i] = (short) (i & 0xffff);
lut[2][i] = (short) (i & 0xffff);
break;
case 4:
// magenta
lut[0][i] = (short) (i & 0xffff);
lut[2][i] = (short) (i & 0xffff);
break;
case 5:
// yellow
lut[0][i] = (short) (i & 0xffff);
lut[1][i] = (short) (i & 0xffff);
break;
default:
// gray
lut[0][i] = (short) (i & 0xffff);
lut[1][i] = (short) (i & 0xffff);
lut[2][i] = (short) (i & 0xffff);
}
}
return lut;
}
/**
* @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);
if (!isRGB()) {
int[] pos = getZCTCoords(no);
lastChannel = realChannel[getTileIndex(series)][pos[1]];
}
int index = getTileIndex(series);
if (index >= offsets.size()) {
// truncated file; imitate LAS AF and return black planes
Arrays.fill(buf, (byte) 0);
return buf;
}
long offset = offsets.get(index).longValue();
int bytes = FormatTools.getBytesPerPixel(getPixelType());
int bpp = bytes * getRGBChannelCount();
long planeSize = (long) getSizeX() * getSizeY() * bpp;
long nextOffset = index + 1 < offsets.size() ?
offsets.get(index + 1).longValue() : endPointer;
int bytesToSkip = (int) (nextOffset - offset - planeSize * getImageCount());
bytesToSkip /= getSizeY();
if ((getSizeX() % 4) == 0) bytesToSkip = 0;
if (offset + (planeSize + bytesToSkip * getSizeY()) * no >= in.length()) {
// truncated file; imitate LAS AF and return black planes
Arrays.fill(buf, (byte) 0);
return buf;
}
in.seek(offset + planeSize * no);
int tile = series;
for (int i=0; i<index; i++) {
tile -= tileCount[i];
}
in.skipBytes((int) (tile * planeSize * getImageCount()));
in.skipBytes(bytesToSkip * getSizeY() * no);
if (bytesToSkip == 0) {
readPlane(in, x, y, w, h, buf);
}
else {
in.skipBytes(y * (getSizeX() * bpp + bytesToSkip));
for (int row=0; row<h; row++) {
in.skipBytes(x * bpp);
in.read(buf, row * w * bpp, w * bpp);
in.skipBytes(bpp * (getSizeX() - w - x) + bytesToSkip);
}
}
// color planes are stored in BGR order
if (getRGBChannelCount() == 3) {
ImageTools.bgrToRgb(buf, isInterleaved(), bytes, getRGBChannelCount());
}
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
offsets = null;
realChannel = null;
lastChannel = 0;
lutNames.clear();
physicalSizeXs.clear();
physicalSizeYs.clear();
descriptions = microscopeModels = serialNumber = null;
pinholes = zooms = lensNA = null;
zSteps = tSteps = null;
expTimes = gains = null;
detectorOffsets = null;
channelNames = null;
detectorModels = null;
exWaves = null;
activeDetector = null;
immersions = corrections = null;
magnification = null;
objectiveModels = null;
posX = posY = posZ = null;
refractiveIndex = null;
cutIns = cutOuts = filterModels = null;
timestamps = null;
laserWavelength = laserIntensity = laserActive = laserFrap = null;
imageROIs = null;
alternateCenter = false;
imageNames = null;
acquiredDate = null;
detectorIndexes = null;
tileCount = null;
fieldPosX.clear();
fieldPosY.clear();
endPointer = 0;
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
in = new RandomAccessInputStream(id);
in.setEncoding(ENCODING);
offsets = new ArrayList<Long>();
in.order(true);
// read the header
LOGGER.info("Reading header");
byte checkOne = in.readByte();
in.skipBytes(2);
byte checkTwo = in.readByte();
if (checkOne != LIF_MAGIC_BYTE && checkTwo != LIF_MAGIC_BYTE) {
throw new FormatException(id + " is not a valid Leica LIF file");
}
in.skipBytes(4);
// read and parse the XML description
if (in.read() != LIF_MEMORY_BYTE) {
throw new FormatException("Invalid XML description");
}
// number of Unicode characters in the XML block
int nc = in.readInt();
String xml = DataTools.stripString(in.readString(nc * 2));
LOGGER.info("Finding image offsets");
while (in.getFilePointer() < in.length()) {
LOGGER.debug("Looking for a block at {}; {} blocks read",
in.getFilePointer(), offsets.size());
int check = in.readInt();
if (check != LIF_MAGIC_BYTE) {
if (check == 0 && offsets.size() > 0) {
// newer .lif file; the remainder of the file is all 0s
endPointer = in.getFilePointer();
break;
}
throw new FormatException("Invalid Memory Block: found magic bytes " +
check + ", expected " + LIF_MAGIC_BYTE);
}
in.skipBytes(4);
check = in.read();
if (check != LIF_MEMORY_BYTE) {
throw new FormatException("Invalid Memory Description: found magic " +
"byte " + check + ", expected " + LIF_MEMORY_BYTE);
}
long blockLength = in.readInt();
if (in.read() != LIF_MEMORY_BYTE) {
in.seek(in.getFilePointer() - 5);
blockLength = in.readLong();
check = in.read();
if (check != LIF_MEMORY_BYTE) {
throw new FormatException("Invalid Memory Description: found magic " +
"byte " + check + ", expected " + LIF_MEMORY_BYTE);
}
}
int descrLength = in.readInt() * 2;
if (blockLength > 0) {
offsets.add(in.getFilePointer() + descrLength);
}
in.seek(in.getFilePointer() + descrLength + blockLength);
}
initMetadata(xml);
xml = null;
if (endPointer == 0) {
endPointer = in.length();
}
// correct offsets, if necessary
if (offsets.size() > getSeriesCount()) {
Long[] storedOffsets = offsets.toArray(new Long[offsets.size()]);
offsets.clear();
int index = 0;
for (int i=0; i<getSeriesCount(); i++) {
setSeries(i);
long nBytes = (long) FormatTools.getPlaneSize(this) * getImageCount();
long start = storedOffsets[index];
long end = index == storedOffsets.length - 1 ? in.length() :
storedOffsets[index + 1];
while (end - start < nBytes && ((end - start) / nBytes) != 1) {
index++;
start = storedOffsets[index];
end = index == storedOffsets.length - 1 ? in.length() :
storedOffsets[index + 1];
}
offsets.add(storedOffsets[index]);
index++;
}
setSeries(0);
}
}
// -- Helper methods --
/** Parses a string of XML and puts the values in a Hashtable. */
private void initMetadata(String xml) throws FormatException, IOException {
try {
ServiceFactory factory = new ServiceFactory();
OMEXMLService service = factory.getInstance(OMEXMLService.class);
service.createOMEXMLMetadata();
}
catch (DependencyException exc) {
throw new FormatException("Could not create OME-XML store.", exc);
}
catch (ServiceException exc) {
throw new FormatException("Could not create OME-XML store.", exc);
}
MetadataStore store = makeFilterMetadata();
// the XML blocks stored in a LIF file are invalid,
// because they don't have a root node
xml = "<?xml version=\"1.0\" encoding=\""+ENCODING+"\"?><LEICA>" + xml +
"</LEICA>";
xml = XMLTools.sanitizeXML(xml);
translateMetadata(getMetadataRoot(xml));
for (int i=0; i<imageNames.length; i++) {
setSeries(i);
addSeriesMeta("Image name", imageNames[i]);
}
setSeries(0);
// set up mapping to rearrange channels
// for instance, the green channel may be #0, and the red channel may be #1
realChannel = new int[tileCount.length][];
int nextLut = 0;
for (int i=0; i<core.size(); i++) {
int index = getTileIndex(i);
if (realChannel[index] != null) {
continue;
}
CoreMetadata ms = core.get(i);
realChannel[index] = new int[ms.sizeC];
for (int q=0; q<ms.sizeC; q++) {
String lut = "";
if (nextLut < lutNames.size()) {
lut = lutNames.get(nextLut++).toLowerCase();
}
if (!CHANNEL_PRIORITIES.containsKey(lut)) lut = "";
realChannel[index][q] = CHANNEL_PRIORITIES.get(lut).intValue();
}
int[] sorted = new int[ms.sizeC];
Arrays.fill(sorted, -1);
for (int q=0; q<sorted.length; q++) {
int min = Integer.MAX_VALUE;
int minIndex = -1;
for (int n=0; n<ms.sizeC; n++) {
if (realChannel[index][n] < min &&
!DataTools.containsValue(sorted, n))
{
min = realChannel[index][n];
minIndex = n;
}
}
sorted[q] = minIndex;
}
}
MetadataTools.populatePixels(store, this, true, false);
int roiCount = 0;
for (int i=0; i<getSeriesCount(); i++) {
setSeries(i);
String instrumentID = MetadataTools.createLSID("Instrument", i);
store.setInstrumentID(instrumentID, i);
int index = getTileIndex(i);
store.setMicroscopeModel(microscopeModels[index], i);
store.setMicroscopeType(getMicroscopeType("Other"), i);
String objectiveID = MetadataTools.createLSID("Objective", i, 0);
store.setObjectiveID(objectiveID, i, 0);
store.setObjectiveLensNA(lensNA[index], i, 0);
store.setObjectiveSerialNumber(serialNumber[index], i, 0);
if (magnification[index] != null) {
store.setObjectiveNominalMagnification(magnification[index], i, 0);
}
store.setObjectiveImmersion(getImmersion(immersions[index]), i, 0);
store.setObjectiveCorrection(getCorrection(corrections[index]), i, 0);
store.setObjectiveModel(objectiveModels[index], i, 0);
if (cutIns[index] != null && filterModels[index] != null) {
int channel = 0;
if (cutIns[index].size() >= filterModels[index].size() * 2) {
int diff = cutIns[index].size() - filterModels[index].size();
for (int q=0; q<diff; q++) {
cutIns[index].remove(filterModels[index].size());
}
}
for (int filter=0; filter<cutIns[index].size(); filter++) {
String filterID = MetadataTools.createLSID("Filter", i, filter);
store.setFilterID(filterID, i, filter);
if (filterModels[index] != null &&
filter < filterModels[index].size())
{
store.setFilterModel(
(String) filterModels[index].get(filter), i, filter);
}
store.setTransmittanceRangeCutIn(
(Length) cutIns[index].get(filter), i, filter);
store.setTransmittanceRangeCutOut(
(Length) cutOuts[index].get(filter), i, filter);
}
}
final List<Double> lasers = laserWavelength[index];
final List<Double> laserIntensities = laserIntensity[index];
final List<Boolean> active = laserActive[index];
final List<Boolean> frap = laserFrap[index];
int nextChannel = 0;
if (lasers != null) {
int laserIndex = 0;
while (laserIndex < lasers.size()) {
if ((Double) lasers.get(laserIndex) == 0) {
lasers.remove(laserIndex);
}
else {
laserIndex++;
}
}
for (int laser=0; laser<lasers.size(); laser++) {
String id = MetadataTools.createLSID("LightSource", i, laser);
store.setLaserID(id, i, laser);
store.setLaserType(LaserType.OTHER, i, laser);
store.setLaserLaserMedium(LaserMedium.OTHER, i, laser);
Double wavelength = (Double) lasers.get(laser);
Length wave = FormatTools.getWavelength(wavelength);
if (wave != null) {
store.setLaserWavelength(wave, i, laser);
}
}
Set<Integer> ignoredChannels = new HashSet<Integer>();
final List<Integer> validIntensities = new ArrayList<Integer>();
int size = lasers.size();
int channel = 0;
Set<Integer> channels = new HashSet<Integer>();
for (int laser=0; laser<laserIntensities.size(); laser++) {
double intensity = (Double) laserIntensities.get(laser);
channel = laser/size;
if (intensity < 100) {
validIntensities.add(laser);
channels.add(channel);
}
ignoredChannels.add(channel);
}
//remove channels w/o valid intensities
ignoredChannels.removeAll(channels);
//remove entries if channel has 2 wavelengths
//e.g. 30% 458 70% 633
int s = validIntensities.size();
int jj;
Set<Integer> toRemove = new HashSet<Integer>();
int as = active.size();
for (int j = 0; j < s; j++) {
if (j < as && !(Boolean) active.get(j)) {
toRemove.add(validIntensities.get(j));
}
jj = j+1;
if (jj < s) {
int v = validIntensities.get(j)/size;
int vv = validIntensities.get(jj)/size;
if (vv == v) {//do not consider that channel.
toRemove.add(validIntensities.get(j));
toRemove.add(validIntensities.get(jj));
ignoredChannels.add(j);
}
}
}
if (toRemove.size() > 0) {
validIntensities.removeAll(toRemove);
}
boolean noNames = true;
if (channelNames[index] != null) {
for (String name : channelNames[index]) {
if (name != null && !name.equals("")) {
noNames = false;
break;
}
}
}
if (!noNames && frap != null) { //only use name for frap.
for (int k = 0; k < frap.size(); k++) {
if (!frap.get(k)) {
noNames = true;
break;
}
}
}
int nextFilter = 0;
//int nextFilter = cutIns[i].size() - getEffectiveSizeC();
for (int k=0; k<validIntensities.size(); k++, nextChannel++) {
int laserArrayIndex = validIntensities.get(k);
double intensity = (Double) laserIntensities.get(laserArrayIndex);
int laser = laserArrayIndex % lasers.size();
Double wavelength = (Double) lasers.get(laser);
if (wavelength != 0) {
while (ignoredChannels.contains(nextChannel)) {
nextChannel++;
}
while (channelNames != null && nextChannel < getEffectiveSizeC() &&
channelNames[index] != null &&
((channelNames[index][nextChannel] == null ||
channelNames[index][nextChannel].equals("")) && !noNames))
{
nextChannel++;
}
if (nextChannel < getEffectiveSizeC()) {
String id = MetadataTools.createLSID("LightSource", i, laser);
store.setChannelLightSourceSettingsID(id, i, nextChannel);
store.setChannelLightSourceSettingsAttenuation(
new PercentFraction((float) intensity / 100f), i, nextChannel);
Length ex = FormatTools.getExcitationWavelength(wavelength);
if (ex != null) {
store.setChannelExcitationWavelength(ex, i, nextChannel);
}
if (wavelength > 0) {
if (cutIns[index] == null || nextFilter >= cutIns[index].size())
{
continue;
}
Double cutIn =
((Length) cutIns[index].get(nextFilter)).value(UNITS.NM).doubleValue();
while (cutIn - wavelength > 20) {
nextFilter++;
if (nextFilter < cutIns[index].size()) {
cutIn = ((Length)
cutIns[index].get(nextFilter)).value(UNITS.NM).doubleValue();
}
else {
break;
}
}
if (nextFilter < cutIns[index].size()) {
String fid =
MetadataTools.createLSID("Filter", i, nextFilter);
//store.setLightPathEmissionFilterRef(fid, i, nextChannel, 0);
nextFilter++;
}
}
}
}
}
}
store.setImageInstrumentRef(instrumentID, i);
store.setObjectiveSettingsID(objectiveID, i);
store.setObjectiveSettingsRefractiveIndex(refractiveIndex[index], i);
store.setImageDescription(descriptions[index], i);
if (acquiredDate[index] > 0) {
store.setImageAcquisitionDate(new Timestamp(DateTools.convertDate(
(long) (acquiredDate[index] * 1000), DateTools.COBOL,
DateTools.ISO8601_FORMAT, true)), i);
}
store.setImageName(imageNames[index].trim(), i);
Length sizeX =
FormatTools.getPhysicalSizeX(physicalSizeXs.get(index));
Length sizeY =
FormatTools.getPhysicalSizeY(physicalSizeYs.get(index));
Length sizeZ = FormatTools.getPhysicalSizeZ(zSteps[index]);
if (sizeX != null) {
store.setPixelsPhysicalSizeX(sizeX, i);
}
if (sizeY != null) {
store.setPixelsPhysicalSizeY(sizeY, i);
}
if (sizeZ != null) {
store.setPixelsPhysicalSizeZ(sizeZ, i);
}
if (tSteps[index] != null) {
store.setPixelsTimeIncrement(new Time(tSteps[index], UNITS.S), i);
}
final List<String> detectors = detectorModels[index];
if (detectors != null) {
nextChannel = 0;
int start = detectors.size() - getEffectiveSizeC();
if (start < 0) {
start = 0;
}
for (int detector=start; detector<detectors.size(); detector++) {
int dIndex = detector - start;
String detectorID = MetadataTools.createLSID("Detector", i, dIndex);
store.setDetectorID(detectorID, i, dIndex);
store.setDetectorModel((String) detectors.get(detector), i, dIndex);
store.setDetectorZoom(zooms[index], i, dIndex);
store.setDetectorType(DetectorType.PMT, i, dIndex);
if (activeDetector[index] != null) {
int detectorIndex =
activeDetector[index].size() - getEffectiveSizeC() + dIndex;
if (detectorIndex >= 0 &&
detectorIndex < activeDetector[index].size() &&
(Boolean) activeDetector[index].get(detectorIndex) &&
detectorOffsets[index] != null &&
nextChannel < detectorOffsets[index].length)
{
store.setDetectorOffset(
detectorOffsets[index][nextChannel++], i, dIndex);
}
}
}
}
final List<Boolean> activeDetectors = activeDetector[index];
int firstDetector = activeDetectors == null ? 0 :
activeDetectors.size() - getEffectiveSizeC();
int nextDetector = firstDetector;
int nextFilter = 0;
int nextFilterDetector = 0;
if (activeDetectors != null &&
activeDetectors.size() > cutIns[index].size() &&
(Boolean) activeDetectors.get(activeDetectors.size() - 1) &&
(Boolean) activeDetectors.get(activeDetectors.size() - 2))
{
nextFilterDetector = activeDetectors.size() - cutIns[index].size();
if (cutIns[index].size() > filterModels[index].size()) {
nextFilterDetector += filterModels[index].size();
nextFilter += filterModels[index].size();
}
}
for (int c=0; c<getEffectiveSizeC(); c++) {
if (activeDetectors != null) {
while (nextDetector >= 0 && nextDetector < activeDetectors.size() &&
!(Boolean) activeDetectors.get(nextDetector))
{
nextDetector++;
}
if (nextDetector < activeDetectors.size() && detectors != null &&
nextDetector - firstDetector < detectors.size())
{
String detectorID = MetadataTools.createLSID(
"Detector", i, nextDetector - firstDetector);
store.setDetectorSettingsID(detectorID, i, c);
nextDetector++;
if (detectorOffsets[index] != null &&
c < detectorOffsets[index].length)
{
store.setDetectorSettingsOffset(detectorOffsets[index][c], i, c);
}
if (gains[index] != null) {
store.setDetectorSettingsGain(gains[index][c], i, c);
}
}
}
if (channelNames[index] != null) {
store.setChannelName(channelNames[index][c], i, c);
}
if (pinholes[index] != null) {
store.setChannelPinholeSize(new Length(pinholes[index], UNITS.MICROM), i, c);
}
if (exWaves[index] != null) {
if (exWaves[index][c] != null && exWaves[index][c] > 1) {
Length ex =
FormatTools.getExcitationWavelength(exWaves[index][c]);
if (ex != null) {
store.setChannelExcitationWavelength(ex, i, c);
}
}
}
// channel coloring is implicit if the image is stored as RGB
Color channelColor = getChannelColor(realChannel[index][c]);
if (!isRGB()) {
store.setChannelColor(channelColor, i, c);
}
if (channelColor.getValue() != -1 && nextFilter >= 0) {
if (nextDetector - firstDetector != getSizeC() &&
cutIns[index] != null && nextDetector >= cutIns[index].size())
{
while (nextFilterDetector < firstDetector) {
String filterID =
MetadataTools.createLSID("Filter", i, nextFilter);
store.setFilterID(filterID, i, nextFilter);
nextFilterDetector++;
nextFilter++;
}
}
while (activeDetectors != null &&
nextFilterDetector < activeDetectors.size() &&
!(Boolean) activeDetectors.get(nextFilterDetector))
{
String filterID = MetadataTools.createLSID("Filter", i, nextFilter);
store.setFilterID(filterID, i, nextFilter);
nextFilterDetector++;
nextFilter++;
}
String filterID = MetadataTools.createLSID("Filter", i, nextFilter);
store.setFilterID(filterID, i, nextFilter);
store.setLightPathEmissionFilterRef(filterID, i, c, 0);
nextFilterDetector++;
nextFilter++;
}
}
for (int image=0; image<getImageCount(); image++) {
Length xPos = posX[index];
Length yPos = posY[index];
if (i < fieldPosX.size() && fieldPosX.get(i) != null) {
xPos = fieldPosX.get(i);
}
if (i < fieldPosY.size() && fieldPosY.get(i) != null) {
yPos = fieldPosY.get(i);
}
if (xPos != null) {
store.setPlanePositionX(xPos, i, image);
}
if (yPos != null) {
store.setPlanePositionY(yPos, i, image);
}
store.setPlanePositionZ(posZ[index], i, image);
if (timestamps[index] != null) {
double timestamp = timestamps[index][image];
if (timestamps[index][0] == acquiredDate[index]) {
timestamp -= acquiredDate[index];
}
else if (timestamp == acquiredDate[index] && image > 0) {
timestamp = timestamps[index][0];
}
store.setPlaneDeltaT(new Time(timestamp, UNITS.S), i, image);
}
if (expTimes[index] != null) {
int c = getZCTCoords(image)[1];
if (expTimes[index][c] != null)
{
store.setPlaneExposureTime(new Time(expTimes[index][c], UNITS.S), i, image);
}
}
}
if (imageROIs[index] != null) {
for (int roi=0; roi<imageROIs[index].length; roi++) {
if (imageROIs[index][roi] != null) {
imageROIs[index][roi].storeROI(store, i, roiCount++, roi);
}
}
}
}
}
private Element getMetadataRoot(String xml)
throws FormatException, IOException
{
ByteArrayInputStream s = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
s = new ByteArrayInputStream(xml.getBytes(ENCODING));
return parser.parse(s).getDocumentElement();
}
catch (ParserConfigurationException e) {
throw new FormatException(e);
}
catch (SAXException e) {
throw new FormatException(e);
} finally {
if (s != null) s.close();
}
}
private void translateMetadata(Element root) throws FormatException {
Element realRoot = (Element) root.getChildNodes().item(0);
NodeList toPrune = getNodes(realRoot, "LDM_Block_Sequential_Master");
if (toPrune != null) {
for (int i=0; i<toPrune.getLength(); i++) {
Element prune = (Element) toPrune.item(i);
Element parent = (Element) prune.getParentNode();
parent.removeChild(prune);
}
}
NodeList images = getNodes(realRoot, "Image");
List<Element> imageNodes = new ArrayList<Element>();
Long[] oldOffsets = null;
if (images.getLength() > offsets.size()) {
oldOffsets = offsets.toArray(new Long[offsets.size()]);
offsets.clear();
}
int nextOffset = 0;
for (int i=0; i<images.getLength(); i++) {
Element image = (Element) images.item(i);
Element grandparent = (Element) image.getParentNode();
if (grandparent == null) {
continue;
}
grandparent = (Element) grandparent.getParentNode();
if (grandparent == null) {
continue;
}
if (!"ProcessingHistory".equals(grandparent.getNodeName())) {
// image is being referenced from an event list
imageNodes.add(image);
if (oldOffsets != null && nextOffset < oldOffsets.length) {
offsets.add(oldOffsets[nextOffset]);
}
}
grandparent = (Element) grandparent.getParentNode();
if (grandparent == null) {
continue;
}
grandparent = (Element) grandparent.getParentNode();
if (grandparent != null) {
if (!"Image".equals(grandparent.getNodeName())) {
nextOffset++;
}
}
}
tileCount = new int[imageNodes.size()];
Arrays.fill(tileCount, 1);
core = new ArrayList<CoreMetadata>(imageNodes.size());
acquiredDate = new double[imageNodes.size()];
descriptions = new String[imageNodes.size()];
laserWavelength = new List[imageNodes.size()];
laserIntensity = new List[imageNodes.size()];
laserActive = new List[imageNodes.size()];
laserFrap = new List[imageNodes.size()];
timestamps = new double[imageNodes.size()][];
activeDetector = new List[imageNodes.size()];
serialNumber = new String[imageNodes.size()];
lensNA = new Double[imageNodes.size()];
magnification = new Double[imageNodes.size()];
immersions = new String[imageNodes.size()];
corrections = new String[imageNodes.size()];
objectiveModels = new String[imageNodes.size()];
posX = new Length[imageNodes.size()];
posY = new Length[imageNodes.size()];
posZ = new Length[imageNodes.size()];
refractiveIndex = new Double[imageNodes.size()];
cutIns = new List[imageNodes.size()];
cutOuts = new List[imageNodes.size()];
filterModels = new List[imageNodes.size()];
microscopeModels = new String[imageNodes.size()];
detectorModels = new List[imageNodes.size()];
detectorIndexes = new HashMap[imageNodes.size()];
zSteps = new Double[imageNodes.size()];
tSteps = new Double[imageNodes.size()];
pinholes = new Double[imageNodes.size()];
zooms = new Double[imageNodes.size()];
expTimes = new Double[imageNodes.size()][];
gains = new Double[imageNodes.size()][];
detectorOffsets = new Double[imageNodes.size()][];
channelNames = new String[imageNodes.size()][];
exWaves = new Double[imageNodes.size()][];
imageROIs = new ROI[imageNodes.size()][];
imageNames = new String[imageNodes.size()];
core.clear();
for (int i=0; i<imageNodes.size(); i++) {
Element image = imageNodes.get(i);
CoreMetadata ms = new CoreMetadata();
core.add(ms);
int index = core.size() - 1;
setSeries(index);
translateImageNames(image, index);
translateImageNodes(image, index);
translateAttachmentNodes(image, index);
translateScannerSettings(image, index);
translateFilterSettings(image, index);
translateTimestamps(image, index);
translateLaserLines(image, index);
translateROIs(image, index);
translateSingleROIs(image, index);
translateDetectors(image, index);
final Deque<String> nameStack = new ArrayDeque<String>();
populateOriginalMetadata(image, nameStack);
addUserCommentMeta(image, i);
}
setSeries(0);
int totalSeries = 0;
for (int count : tileCount) {
totalSeries += count;
}
ArrayList<CoreMetadata> newCore = new ArrayList<CoreMetadata>();
for (int i=0; i<core.size(); i++) {
for (int tile=0; tile<tileCount[i]; tile++) {
newCore.add(core.get(i));
}
}
core = newCore;
}
private void populateOriginalMetadata(Element root, Deque<String> nameStack) {
String name = root.getNodeName();
if (root.hasAttributes() && !name.equals("Element") &&
!name.equals("Attachment") && !name.equals("LMSDataContainerHeader"))
{
nameStack.push(name);
String suffix = root.getAttribute("Identifier");
String value = root.getAttribute("Variant");
if (suffix == null || suffix.trim().length() == 0) {
suffix = root.getAttribute("Description");
}
StringBuffer key = new StringBuffer();
final Iterator<String> nameStackIterator = nameStack.descendingIterator();
while (nameStackIterator.hasNext()) {
final String k = nameStackIterator.next();
key.append(k);
key.append("|");
}
if (suffix != null && value != null && suffix.length() > 0 &&
value.length() > 0 && !suffix.equals("HighInteger") &&
!suffix.equals("LowInteger"))
{
addSeriesMetaList(key.toString() + suffix, value);
}
else {
NamedNodeMap attributes = root.getAttributes();
for (int i=0; i<attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
if (!attr.getName().equals("HighInteger") &&
!attr.getName().equals("LowInteger"))
{
addSeriesMeta(key.toString() + attr.getName(), attr.getValue());
}
}
}
}
NodeList children = root.getChildNodes();
for (int i=0; i<children.getLength(); i++) {
Object child = children.item(i);
if (child instanceof Element) {
populateOriginalMetadata((Element) child, nameStack);
}
}
if (root.hasAttributes() && !name.equals("Element") &&
!name.equals("Attachment") && !name.equals("LMSDataContainerHeader"))
{
nameStack.pop();
}
}
private void translateImageNames(Element imageNode, int image) {
final List<String> names = new ArrayList<String>();
Element parent = imageNode;
while (true) {
parent = (Element) parent.getParentNode();
if (parent == null || parent.getNodeName().equals("LEICA")) {
break;
}
if (parent.getNodeName().equals("Element")) {
names.add(parent.getAttribute("Name"));
}
}
imageNames[image] = "";
for (int i=names.size() - 2; i>=0; i--) {
imageNames[image] += names.get(i);
if (i > 0) imageNames[image] += "/";
}
}
private void translateDetectors(Element imageNode, int image)
throws FormatException
{
NodeList definitions = getNodes(imageNode, "ATLConfocalSettingDefinition");
if (definitions == null) return;
final List<String> channels = new ArrayList<String>();
laserActive[image] = new ArrayList<Boolean>();
int nextChannel = 0;
for (int definition=0; definition<definitions.getLength(); definition++) {
Element definitionNode = (Element) definitions.item(definition);
String parentName = definitionNode.getParentNode().getNodeName();
boolean isMaster = parentName.endsWith("Master");
NodeList detectors = getNodes(definitionNode, "Detector");
if (detectors == null) return;
int count = 0;
for (int d=0; d<detectors.getLength(); d++) {
Element detector = (Element) detectors.item(d);
NodeList multibands = null;
if (!isMaster) {
multibands = getNodes(definitionNode, "MultiBand");
}
String v = detector.getAttribute("Gain");
Double gain =
v == null || v.trim().isEmpty() ? null : new Double(v.trim());
v = detector.getAttribute("Offset");
Double offset =
v == null || v.trim().isEmpty() ? null : new Double(v.trim());
boolean active = "1".equals(detector.getAttribute("IsActive"));
String c = detector.getAttribute("Channel");
int channel = (c == null || c.trim().length() == 0) ? 0 : Integer.parseInt(c);
if (active) {
if (detectorIndexes[image] != null && detectorModels[image] != null) {
detectorModels[image].add(detectorIndexes[image].get(channel));
}
Element multiband = null;
if (multibands != null) {
for (int i=0; i<multibands.getLength(); i++) {
Element mb = (Element) multibands.item(i);
if (channel == Integer.parseInt(mb.getAttribute("Channel"))) {
multiband = mb;
break;
}
}
}
if (multiband != null) {
String dye = multiband.getAttribute("DyeName");
if (!channels.contains(dye)) {
channels.add(dye);
}
double cutIn = new Double(multiband.getAttribute("LeftWorld"));
double cutOut = new Double(multiband.getAttribute("RightWorld"));
if ((int) cutIn > 0) {
if (cutIns[image] == null) {
cutIns[image] = new ArrayList<PositiveFloat>();
}
Length in =
FormatTools.getCutIn((double) Math.round(cutIn));
if (in != null) {
cutIns[image].add(in);
}
}
if ((int) cutOut > 0) {
if (cutOuts[image] == null) {
cutOuts[image] = new ArrayList<PositiveFloat>();
}
Length out =
FormatTools.getCutOut((double) Math.round(cutOut));
if (out != null) {
cutOuts[image].add(out);
}
}
}
else {
channels.add("");
}
if (!isMaster) {
if (channel < nextChannel) {
nextChannel = 0;
}
if (nextChannel < getEffectiveSizeC()) {
if (gains[image] != null) {
gains[image][nextChannel] = gain;
}
if (detectorOffsets[image] != null) {
detectorOffsets[image][nextChannel] = offset;
}
}
nextChannel++;
}
} else {
count++;
}
if (active && activeDetector[image] != null) {
activeDetector[image].add(active);
}
}
//Store values to check if actually it is active.
if (!isMaster) {
laserActive[image].add(count < detectors.getLength());
}
}
if (channels != null && channelNames[image] != null) {
for (int i=0; i<getEffectiveSizeC(); i++) {
int index = i + channels.size() - getEffectiveSizeC();
if (index >= 0 && index < channels.size()) {
if (channelNames[image][i] == null ||
channelNames[image][i].trim().isEmpty())
{
channelNames[image][i] = channels.get(index);
}
}
}
}
}
private void translateROIs(Element imageNode, int image)
throws FormatException
{
NodeList rois = getNodes(imageNode, "Annotation");
if (rois == null) return;
imageROIs[image] = new ROI[rois.getLength()];
for (int r=0; r<rois.getLength(); r++) {
Element roiNode = (Element) rois.item(r);
ROI roi = new ROI();
String type = roiNode.getAttribute("type");
if (type != null && !type.trim().isEmpty()) {
roi.type = Integer.parseInt(type.trim());
}
String color = roiNode.getAttribute("color");
if (color != null && !color.trim().isEmpty()) {
roi.color = Long.parseLong(color.trim());
}
roi.name = roiNode.getAttribute("name");
roi.fontName = roiNode.getAttribute("fontName");
roi.fontSize = roiNode.getAttribute("fontSize");
roi.transX = parseDouble(roiNode.getAttribute("transTransX"));
roi.transY = parseDouble(roiNode.getAttribute("transTransY"));
roi.scaleX = parseDouble(roiNode.getAttribute("transScalingX"));
roi.scaleY = parseDouble(roiNode.getAttribute("transScalingY"));
roi.rotation = parseDouble(roiNode.getAttribute("transRotation"));
String linewidth = roiNode.getAttribute("linewidth");
try {
if (linewidth != null && !linewidth.trim().isEmpty()) {
roi.linewidth = Integer.parseInt(linewidth.trim());
}
}
catch (NumberFormatException e) { }
roi.text = roiNode.getAttribute("text");
NodeList vertices = getNodes(roiNode, "Vertex");
if (vertices == null) {
continue;
}
for (int v=0; v<vertices.getLength(); v++) {
Element vertex = (Element) vertices.item(v);
String xx = vertex.getAttribute("x");
String yy = vertex.getAttribute("y");
if (xx != null && !xx.trim().isEmpty()) {
roi.x.add(parseDouble(xx.trim()));
}
if (yy != null && !yy.trim().isEmpty()) {
roi.y.add(parseDouble(yy.trim()));
}
}
imageROIs[image][r] = roi;
if (getNodes(imageNode, "ROI") != null) {
alternateCenter = true;
}
}
}
private void translateSingleROIs(Element imageNode, int image)
throws FormatException
{
if (imageROIs[image] != null) return;
NodeList children = getNodes(imageNode, "ROI");
if (children == null) return;
children = getNodes((Element) children.item(0), "Children");
if (children == null) return;
children = getNodes((Element) children.item(0), "Element");
if (children == null) return;
imageROIs[image] = new ROI[children.getLength()];
for (int r=0; r<children.getLength(); r++) {
NodeList rois = getNodes((Element) children.item(r), "ROISingle");
Element roiNode = (Element) rois.item(0);
ROI roi = new ROI();
String type = roiNode.getAttribute("RoiType");
if (type != null && !type.trim().isEmpty()) {
roi.type = Integer.parseInt(type.trim());
}
String color = roiNode.getAttribute("Color");
if (color != null && !color.trim().isEmpty()) {
roi.color = Long.parseLong(color.trim());
}
Element parent = (Element) roiNode.getParentNode();
parent = (Element) parent.getParentNode();
roi.name = parent.getAttribute("Name");
NodeList vertices = getNodes(roiNode, "P");
double sizeX = physicalSizeXs.get(image);
double sizeY = physicalSizeYs.get(image);
for (int v=0; v<vertices.getLength(); v++) {
Element vertex = (Element) vertices.item(v);
String xx = vertex.getAttribute("X");
String yy = vertex.getAttribute("Y");
if (xx != null && !xx.trim().isEmpty()) {
roi.x.add(parseDouble(xx.trim()) / sizeX);
}
if (yy != null && !yy.trim().isEmpty()) {
roi.y.add(parseDouble(yy.trim()) / sizeY);
}
}
Element transform = (Element) getNodes(roiNode, "Transformation").item(0);
roi.rotation = parseDouble(transform.getAttribute("Rotation"));
Element scaling = (Element) getNodes(transform, "Scaling").item(0);
roi.scaleX = parseDouble(scaling.getAttribute("XScale"));
roi.scaleY = parseDouble(scaling.getAttribute("YScale"));
Element translation =
(Element) getNodes(transform, "Translation").item(0);
roi.transX = parseDouble(translation.getAttribute("X")) / sizeX;
roi.transY = parseDouble(translation.getAttribute("Y")) / sizeY;
imageROIs[image][r] = roi;
}
}
private void translateLaserLines(Element imageNode, int image)
throws FormatException
{
NodeList aotfLists = getNodes(imageNode, "AotfList");
if (aotfLists == null || aotfLists.getLength() == 0) return;
laserWavelength[image] = new ArrayList<Double>();
laserIntensity[image] = new ArrayList<Double>();
laserFrap[image] = new ArrayList<Boolean>();
int baseIntensityIndex = 0;
for (int channel=0; channel<aotfLists.getLength(); channel++) {
Element aotf = (Element) aotfLists.item(channel);
NodeList laserLines = getNodes(aotf, "LaserLineSetting");
if (laserLines == null) return;
String gpName = aotf.getParentNode().getParentNode().getNodeName();
//might need parent for attachment
boolean isMaster = gpName.endsWith("Sequential_Master") ||
gpName.endsWith("Attachment");
laserFrap[image].add(gpName.endsWith("FRAP_Master"));
for (int laser=0; laser<laserLines.getLength(); laser++) {
Element laserLine = (Element) laserLines.item(laser);
if (isMaster) {
continue;
}
String lineIndex = laserLine.getAttribute("LineIndex");
String qual = laserLine.getAttribute("Qualifier");
int index =
lineIndex == null || lineIndex.trim().isEmpty() ? 0 :
Integer.parseInt(lineIndex.trim());
int qualifier =
qual == null || qual.trim().isEmpty() ? 0:
Integer.parseInt(qual.trim());
index += (2 - (qualifier / 10));
if (index < 0) {
continue;
//index = 0;
}
String v = laserLine.getAttribute("LaserLine");
Double wavelength = 0d;
if (v != null && !v.trim().isEmpty()) {
wavelength = new Double(v.trim());
}
if (index < laserWavelength[image].size()) {
laserWavelength[image].set(index, wavelength);
}
else {
for (int i=laserWavelength[image].size(); i<index; i++) {
laserWavelength[image].add(Double.valueOf(0));
}
laserWavelength[image].add(wavelength);
}
String intensity = laserLine.getAttribute("IntensityDev");
double realIntensity =
intensity == null || intensity.trim().isEmpty() ? 0d :
new Double(intensity.trim());
realIntensity = 100d - realIntensity;
int realIndex = baseIntensityIndex + index;
if (realIndex < laserIntensity[image].size()) {
laserIntensity[image].set(realIndex, realIntensity);
}
else {
while (realIndex < laserIntensity[image].size()) {
laserIntensity[image].add(100d);
}
laserIntensity[image].add(realIntensity);
}
}
baseIntensityIndex += laserWavelength[image].size();
}
}
private void translateTimestamps(Element imageNode, int image)
throws FormatException
{
NodeList timestampNodes = getNodes(imageNode, "TimeStamp");
if (timestampNodes == null) return;
timestamps[image] = new double[getImageCount()];
if (timestampNodes != null) {
for (int stamp=0; stamp<timestampNodes.getLength(); stamp++) {
if (stamp < getImageCount()) {
Element timestamp = (Element) timestampNodes.item(stamp);
String stampHigh = timestamp.getAttribute("HighInteger");
String stampLow = timestamp.getAttribute("LowInteger");
long high =
stampHigh == null || stampHigh.trim().isEmpty() ? 0 :
Long.parseLong(stampHigh.trim());
long low =
stampLow == null || stampHigh.trim().isEmpty() ? 0 :
Long.parseLong(stampLow.trim());
long ms = DateTools.getMillisFromTicks(high, low);
timestamps[image][stamp] = ms / 1000.0;
}
}
}
acquiredDate[image] = timestamps[image][0];
NodeList relTimestampNodes = getNodes(imageNode, "RelTimeStamp");
if (relTimestampNodes != null) {
for (int stamp=0; stamp<relTimestampNodes.getLength(); stamp++) {
if (stamp < getImageCount()) {
Element timestamp = (Element) relTimestampNodes.item(stamp);
timestamps[image][stamp] =
new Double(timestamp.getAttribute("Time"));
}
}
}
}
private void translateFilterSettings(Element imageNode, int image)
throws FormatException
{
NodeList filterSettings = getNodes(imageNode, "FilterSettingRecord");
if (filterSettings == null) return;
activeDetector[image] = new ArrayList<Boolean>();
cutIns[image] = new ArrayList<PositiveFloat>();
cutOuts[image] = new ArrayList<PositiveFloat>();
filterModels[image] = new ArrayList<String>();
detectorIndexes[image] = new HashMap<Integer, String>();
int nextChannel = 0;
for (int i=0; i<filterSettings.getLength(); i++) {
Element filterSetting = (Element) filterSettings.item(i);
String object = filterSetting.getAttribute("ObjectName");
String attribute = filterSetting.getAttribute("Attribute");
String objectClass = filterSetting.getAttribute("ClassName");
String variant = filterSetting.getAttribute("Variant");
String data = filterSetting.getAttribute("Data");
if (attribute.equals("NumericalAperture")) {
if (variant != null && !variant.trim().isEmpty()) {
lensNA[image] = new Double(variant.trim());
}
}
else if (attribute.equals("OrderNumber")) {
if (variant != null && !variant.trim().isEmpty()) {
serialNumber[image] = variant.trim();
}
}
else if (objectClass.equals("CDetectionUnit")) {
if (attribute.equals("State")) {
int channel = getChannelIndex(filterSetting);
if (channel < 0) continue;
detectorIndexes[image].put(new Integer(data), object);
activeDetector[image].add("Active".equals(variant.trim()));
}
}
else if (attribute.equals("Objective")) {
StringTokenizer tokens = new StringTokenizer(variant, " ");
boolean foundMag = false;
StringBuffer model = new StringBuffer();
while (!foundMag) {
String token = tokens.nextToken();
int x = token.indexOf("x");
if (x != -1) {
foundMag = true;
String na = token.substring(x + 1);
if (na != null && !na.trim().isEmpty()) {
lensNA[image] = new Double(na.trim());
}
na = token.substring(0, x);
if (na != null && !na.trim().isEmpty()) {
magnification[image] = new Double(na.trim());
}
}
else {
model.append(token);
model.append(" ");
}
}
String immersion = "Other";
if (tokens.hasMoreTokens()) {
immersion = tokens.nextToken();
if (immersion == null || immersion.trim().isEmpty()) {
immersion = "Other";
}
}
immersions[image] = immersion;
String correction = "Other";
if (tokens.hasMoreTokens()) {
correction = tokens.nextToken();
if (correction == null || correction.trim().isEmpty()) {
correction = "Other";
}
}
corrections[image] = correction;
objectiveModels[image] = model.toString().trim();
}
else if (attribute.equals("RefractionIndex")) {
if (variant != null && !variant.trim().isEmpty()) {
refractiveIndex[image] = new Double(variant.trim());
}
}
else if (attribute.equals("XPos")) {
if (variant != null && !variant.trim().isEmpty()) {
final Double number = Double.valueOf(variant.trim());
posX[image] = new Length(number, UNITS.REFERENCEFRAME);
}
}
else if (attribute.equals("YPos")) {
if (variant != null && !variant.trim().isEmpty()) {
final Double number = Double.valueOf(variant.trim());
posY[image] = new Length(number, UNITS.REFERENCEFRAME);
}
}
else if (attribute.equals("ZPos")) {
if (variant != null && !variant.trim().isEmpty()) {
final Double number = Double.valueOf(variant.trim());
posZ[image] = new Length(number, UNITS.REFERENCEFRAME);
}
}
else if (objectClass.equals("CSpectrophotometerUnit")) {
Double v = null;
try {
v = Double.parseDouble(variant);
}
catch (NumberFormatException e) { }
String description = filterSetting.getAttribute("Description");
if (description.endsWith("(left)")) {
filterModels[image].add(object);
if (v != null && v > 0) {
Length in = FormatTools.getCutIn(v);
if (in != null) {
cutIns[image].add(in);
}
}
}
else if (description.endsWith("(right)")) {
if (v != null && v > 0) {
Length out = FormatTools.getCutOut(v);
if (out != null) {
cutOuts[image].add(out);
}
}
}
else if (attribute.equals("Stain")) {
if (nextChannel < channelNames[image].length) {
channelNames[image][nextChannel++] = variant;
}
}
}
}
}
private void translateScannerSettings(Element imageNode, int image)
throws FormatException
{
NodeList scannerSettings = getNodes(imageNode, "ScannerSettingRecord");
if (scannerSettings == null) return;
expTimes[image] = new Double[getEffectiveSizeC()];
gains[image] = new Double[getEffectiveSizeC()];
detectorOffsets[image] = new Double[getEffectiveSizeC()];
channelNames[image] = new String[getEffectiveSizeC()];
exWaves[image] = new Double[getEffectiveSizeC()];
detectorModels[image] = new ArrayList<String>();
for (int i=0; i<scannerSettings.getLength(); i++) {
Element scannerSetting = (Element) scannerSettings.item(i);
String id = scannerSetting.getAttribute("Identifier");
if (id == null) id = "";
String suffix = scannerSetting.getAttribute("Identifier");
String value = scannerSetting.getAttribute("Variant");
if (id.equals("SystemType")) {
microscopeModels[image] = value;
}
else if (id.equals("dblPinhole")) {
if (value != null && !value.trim().isEmpty()) {
pinholes[image] = Double.parseDouble(value.trim()) * 1000000;
}
}
else if (id.equals("dblZoom")) {
if (value != null && !value.trim().isEmpty()) {
zooms[image] = new Double(value.trim());
}
}
else if (id.equals("dblStepSize")) {
if (value != null && !value.trim().isEmpty()) {
zSteps[image] = Double.parseDouble(value.trim()) * 1000000;
}
}
else if (id.equals("nDelayTime_s")) {
if (value != null && !value.trim().isEmpty()) {
tSteps[image] = new Double(value.trim());
}
}
else if (id.equals("CameraName")) {
detectorModels[image].add(value);
}
else if (id.equals("eDirectional")) {
addSeriesMeta("Reverse X orientation", "1".equals(value.trim()));
}
else if (id.equals("eDirectionalY")) {
addSeriesMeta("Reverse Y orientation", "1".equals(value.trim()));
}
else if (id.indexOf("WFC") == 1) {
int c = 0;
try {
c = Integer.parseInt(id.replaceAll("\\D", ""));
}
catch (NumberFormatException e) { }
if (c < 0 || c >= getEffectiveSizeC()) {
continue;
}
if (id.endsWith("ExposureTime")) {
if (value != null && !value.trim().isEmpty()) {
expTimes[image][c] = new Double(value.trim());
}
}
else if (id.endsWith("Gain")) {
if (value != null && !value.trim().isEmpty()) {
gains[image][c] = new Double(value.trim());
}
}
else if (id.endsWith("WaveLength")) {
if (value != null && !value.trim().isEmpty()) {
Double exWave = new Double(value.trim());
if (exWave > 0) {
exWaves[image][c] = exWave;
}
}
}
// NB: "UesrDefName" is not a typo.
else if ((id.endsWith("UesrDefName") || id.endsWith("UserDefName")) &&
!value.equals("None"))
{
if (channelNames[image][c] == null ||
channelNames[image][c].trim().isEmpty())
{
channelNames[image][c] = value;
}
}
}
}
}
private void translateAttachmentNodes(Element imageNode, int image)
throws FormatException
{
NodeList attachmentNodes = getNodes(imageNode, "Attachment");
if (attachmentNodes == null) return;
for (int i=0; i<attachmentNodes.getLength(); i++) {
Element attachment = (Element) attachmentNodes.item(i);
String attachmentName = attachment.getAttribute("Name");
if ("ContextDescription".equals(attachmentName)) {
descriptions[image] = attachment.getAttribute("Content");
}
else if ("TileScanInfo".equals(attachmentName)) {
NodeList tiles = getNodes(attachment, "Tile");
for (int tile=0; tile<tiles.getLength(); tile++) {
Element tileNode = (Element) tiles.item(tile);
String posX = tileNode.getAttribute("PosX");
String posY = tileNode.getAttribute("PosY");
if (posX != null) {
try {
final Double number = Double.valueOf(posX);
fieldPosX.add(new Length(number, UNITS.REFERENCEFRAME));
}
catch (NumberFormatException e) {
LOGGER.debug("", e);
fieldPosX.add(null);
}
}
if (posY != null) {
try {
final Double number = Double.valueOf(posY);
fieldPosY.add(new Length(number, UNITS.REFERENCEFRAME));
}
catch (NumberFormatException e) {
LOGGER.debug("", e);
fieldPosY.add(null);
}
}
}
}
}
}
private void addUserCommentMeta(Element imageNode, int image)
throws FormatException
{
NodeList attachmentNodes = getNodes(imageNode, "User-Comment");
if (attachmentNodes == null) return;
for (int i=0; i<attachmentNodes.getLength(); i++) {
Node attachment = attachmentNodes.item(i);
addSeriesMeta("User-Comment[" + i + "]", attachment.getTextContent());
if (i == 0 && descriptions[image] == null) {
descriptions[image] = attachment.getTextContent();
}
}
}
private void translateImageNodes(Element imageNode, int i)
throws FormatException
{
CoreMetadata ms = core.get(i);
ms.orderCertain = true;
ms.metadataComplete = true;
ms.littleEndian = true;
ms.falseColor = true;
NodeList channels = getChannelDescriptionNodes(imageNode);
NodeList dimensions = getDimensionDescriptionNodes(imageNode);
HashMap<Long, String> bytesPerAxis = new HashMap<Long, String>();
Double physicalSizeX = null;
Double physicalSizeY = null;
Double physicalSizeZ = null;
ms.sizeC = channels.getLength();
for (int ch=0; ch<channels.getLength(); ch++) {
Element channel = (Element) channels.item(ch);
lutNames.add(channel.getAttribute("LUTName"));
String bytesInc = channel.getAttribute("BytesInc");
long bytes =
bytesInc == null || bytesInc.trim().isEmpty() ? 0 :
Long.parseLong(bytesInc.trim());
if (bytes > 0) {
bytesPerAxis.put(bytes, "C");
}
}
int extras = 1;
for (int dim=0; dim<dimensions.getLength(); dim++) {
Element dimension = (Element) dimensions.item(dim);
String v = dimension.getAttribute("DimID");
int id = v == null || v.trim().isEmpty() ? 0 : Integer.parseInt(v.trim());
v = dimension.getAttribute("NumberOfElements");
int len = v == null || v.trim().isEmpty() ? 0 : Integer.parseInt(v.trim());
v = dimension.getAttribute("BytesInc");
long nBytes = v == null || v.trim().isEmpty() ? 0 : Long.parseLong(v.trim());
v = dimension.getAttribute("Length");
Double physicalLen;
if (StringUtils.isBlank(v)) {
physicalLen = 0d;
} else {
physicalLen = new Double(v.trim());
}
String unit = dimension.getAttribute("Unit");
physicalLen /= len;
if (unit.equals("Ks")) {
physicalLen /= 1000;
}
else if (unit.equals("m")) {
physicalLen *= 1000000;
}
switch (id) {
case 1: // X axis
ms.sizeX = len;
ms.rgb = (nBytes % 3) == 0;
if (ms.rgb) nBytes /= 3;
ms.pixelType =
FormatTools.pixelTypeFromBytes((int) nBytes, false, true);
physicalSizeX = physicalLen;
break;
case 2: // Y axis
if (ms.sizeY != 0) {
if (ms.sizeZ == 1) {
ms.sizeZ = len;
bytesPerAxis.put(nBytes, "Z");
physicalSizeZ = (physicalLen * len) / (len - 1);
}
else if (ms.sizeT == 1) {
ms.sizeT = len;
bytesPerAxis.put(nBytes, "T");
}
}
else {
ms.sizeY = len;
physicalSizeY = physicalLen;
}
break;
case 3: // Z axis
if (ms.sizeY == 0) {
// XZ scan - swap Y and Z
ms.sizeY = len;
ms.sizeZ = 1;
bytesPerAxis.put(nBytes, "Y");
physicalSizeY = physicalLen;
}
else {
ms.sizeZ = len;
bytesPerAxis.put(nBytes, "Z");
physicalSizeZ = (physicalLen * len) / (len - 1);
}
break;
case 4: // T axis
if (ms.sizeY == 0) {
// XT scan - swap Y and T
ms.sizeY = len;
ms.sizeT = 1;
bytesPerAxis.put(nBytes, "Y");
physicalSizeY = physicalLen;
}
else {
ms.sizeT = len;
bytesPerAxis.put(nBytes, "T");
}
break;
case 10: // tile axis
tileCount[i] *= len;
break;
default:
extras *= len;
}
}
physicalSizeXs.add(physicalSizeX);
physicalSizeYs.add(physicalSizeY);
if (zSteps[i] == null && physicalSizeZ != null) {
zSteps[i] = Math.abs(physicalSizeZ);
}
if (extras > 1) {
if (ms.sizeZ == 1) ms.sizeZ = extras;
else {
if (ms.sizeT == 0) ms.sizeT = extras;
else ms.sizeT *= extras;
}
}
if (ms.sizeC == 0) ms.sizeC = 1;
if (ms.sizeZ == 0) ms.sizeZ = 1;
if (ms.sizeT == 0) ms.sizeT = 1;
if (ms.sizeX == 0) ms.sizeX = 1;
if (ms.sizeY == 0) ms.sizeY = 1;
ms.interleaved = ms.rgb;
ms.indexed = !ms.rgb;
ms.imageCount = ms.sizeZ * ms.sizeT;
if (!ms.rgb) ms.imageCount *= ms.sizeC;
Long[] bytes = bytesPerAxis.keySet().toArray(new Long[0]);
Arrays.sort(bytes);
ms.dimensionOrder = "XY";
if (getSizeC() > 1 && getSizeT() > 1) {
ms.dimensionOrder += "C";
}
for (Long nBytes : bytes) {
String axis = bytesPerAxis.get(nBytes);
if (ms.dimensionOrder.indexOf(axis) == -1) {
ms.dimensionOrder += axis;
}
}
if (ms.dimensionOrder.indexOf("Z") == -1) {
ms.dimensionOrder += "Z";
}
if (ms.dimensionOrder.indexOf("C") == -1) {
ms.dimensionOrder += "C";
}
if (ms.dimensionOrder.indexOf("T") == -1) {
ms.dimensionOrder += "T";
}
}
private NodeList getNodes(Element root, String nodeName) {
NodeList nodes = root.getElementsByTagName(nodeName);
if (nodes.getLength() == 0) {
NodeList children = root.getChildNodes();
for (int i=0; i<children.getLength(); i++) {
Object child = children.item(i);
if (child instanceof Element) {
NodeList childNodes = getNodes((Element) child, nodeName);
if (childNodes != null) {
return childNodes;
}
}
}
return null;
}
else return nodes;
}
private Element getImageDescription(Element root) {
return (Element) root.getElementsByTagName("ImageDescription").item(0);
}
private NodeList getChannelDescriptionNodes(Element root) {
Element imageDescription = getImageDescription(root);
Element channels =
(Element) imageDescription.getElementsByTagName("Channels").item(0);
return channels.getElementsByTagName("ChannelDescription");
}
private NodeList getDimensionDescriptionNodes(Element root) {
Element imageDescription = getImageDescription(root);
Element channels =
(Element) imageDescription.getElementsByTagName("Dimensions").item(0);
return channels.getElementsByTagName("DimensionDescription");
}
private int getChannelIndex(Element filterSetting) {
String data = filterSetting.getAttribute("data");
if (data == null || data.equals("")) {
data = filterSetting.getAttribute("Data");
}
int channel = data == null || data.equals("") ? 0 : Integer.parseInt(data);
if (channel < 0) return -1;
return channel - 1;
}
// -- Helper class --
class ROI {
// -- Constants --
public static final int TEXT = 512;
public static final int SCALE_BAR = 8192;
public static final int POLYGON = 32;
public static final int RECTANGLE = 16;
public static final int LINE = 256;
public static final int ARROW = 2;
// -- Fields --
public int type;
public List<Double> x = new ArrayList<Double>();
public List<Double> y = new ArrayList<Double>();
// center point of the ROI
public double transX, transY;
// transformation parameters
public double scaleX, scaleY;
public double rotation;
public long color;
public int linewidth;
public String text;
public String fontName;
public String fontSize;
public String name;
private boolean normalized = false;
// -- ROI API methods --
public void storeROI(MetadataStore store, int series, int roi, int roiIndex)
{
MetadataLevel level = getMetadataOptions().getMetadataLevel();
if (level == MetadataLevel.NO_OVERLAYS || level == MetadataLevel.MINIMUM)
{
return;
}
// keep in mind that vertices are given relative to the center
// point of the ROI and the transX/transY values are relative to
// the center point of the image
String roiID = MetadataTools.createLSID("ROI", roi);
store.setImageROIRef(roiID, series, roiIndex);
store.setROIID(roiID, roi);
store.setLabelID(MetadataTools.createLSID("Shape", roi, 0), roi, 0);
if (text == null) {
text = name;
}
store.setLabelText(text, roi, 0);
if (fontSize != null) {
try {
int size = (int) Double.parseDouble(fontSize);
Length fontSize = FormatTools.getFontSize(size);
if (fontSize != null) {
store.setLabelFontSize(fontSize, roi, 0);
}
}
catch (NumberFormatException e) { }
}
Length l = new Length((double) linewidth, UNITS.PIXEL);
store.setLabelStrokeWidth(l, roi, 0);
if (!normalized) normalize();
double cornerX = x.get(0).doubleValue();
double cornerY = y.get(0).doubleValue();
store.setLabelX(cornerX, roi, 0);
store.setLabelY(cornerY, roi, 0);
int centerX = (core.get(series).sizeX / 2) - 1;
int centerY = (core.get(series).sizeY / 2) - 1;
double roiX = centerX + transX;
double roiY = centerY + transY;
if (alternateCenter) {
roiX = transX - 2 * cornerX;
roiY = transY - 2 * cornerY;
}
// TODO : rotation/scaling not populated
String shapeID = MetadataTools.createLSID("Shape", roi, 1);
switch (type) {
case POLYGON:
StringBuffer points = new StringBuffer();
for (int i=0; i<x.size(); i++) {
points.append(x.get(i).doubleValue() + roiX);
points.append(",");
points.append(y.get(i).doubleValue() + roiY);
if (i < x.size() - 1) points.append(" ");
}
store.setPolygonID(shapeID, roi, 1);
store.setPolygonPoints(points.toString(), roi, 1);
break;
case TEXT:
case RECTANGLE:
store.setRectangleID(shapeID, roi, 1);
store.setRectangleX(roiX - Math.abs(cornerX), roi, 1);
store.setRectangleY(roiY - Math.abs(cornerY), roi, 1);
double width = 2 * Math.abs(cornerX);
double height = 2 * Math.abs(cornerY);
store.setRectangleWidth(width, roi, 1);
store.setRectangleHeight(height, roi, 1);
break;
case SCALE_BAR:
case ARROW:
case LINE:
store.setLineID(shapeID, roi, 1);
store.setLineX1(roiX + x.get(0), roi, 1);
store.setLineY1(roiY + y.get(0), roi, 1);
store.setLineX2(roiX + x.get(1), roi, 1);
store.setLineY2(roiY + y.get(1), roi, 1);
break;
}
}
// -- Helper methods --
/**
* Vertices and transformation values are not stored in pixel coordinates.
* We need to convert them from physical coordinates to pixel coordinates
* so that they can be stored in a MetadataStore.
*/
private void normalize() {
if (normalized) return;
// coordinates are in meters
transX *= 1000000;
transY *= 1000000;
transX *= 1;
transY *= 1;
for (int i=0; i<x.size(); i++) {
double coordinate = x.get(i).doubleValue() * 1000000;
coordinate *= 1;
x.set(i, coordinate);
}
for (int i=0; i<y.size(); i++) {
double coordinate = y.get(i).doubleValue() * 1000000;
coordinate *= 1;
y.set(i, coordinate);
}
normalized = true;
}
}
private double parseDouble(String number) {
if (number != null) {
number = number.replaceAll(",", ".");
try {
return Double.parseDouble(number);
}
catch (NumberFormatException e) { }
}
return 0;
}
private Color getChannelColor(int colorCode) {
switch (colorCode) {
case 0: // red
return new Color(255, 0, 0, 255);
case 1: // green
return new Color(0, 255, 0, 255);
case 2: // blue
return new Color(0, 0, 255, 255);
case 3: // cyan
return new Color(0, 255, 255, 255);
case 4: // magenta
return new Color(255, 0, 255, 255);
case 5: // yellow
return new Color(255, 255, 0, 255);
}
return new Color(255, 255, 255, 255);
}
private int getTileIndex(int coreIndex) {
int count = 0;
for (int tile=0; tile<tileCount.length; tile++) {
if (coreIndex < count + tileCount[tile]) {
return tile;
}
count += tileCount[tile];
}
return -1;
}
}