setSeries(series);
IFDList ifds = ifdsList.get(series);
IFD ifd = ifds.get(0);
in.close();
in = new RandomAccessInputStream(getLSMFileFromSeries(series));
in.order(isLittleEndian());
tiffParser = new TiffParser(in);
PhotoInterp photo = ifd.getPhotometricInterpretation();
int samples = ifd.getSamplesPerPixel();
CoreMetadata ms = core.get(series);
ms.sizeX = (int) ifd.getImageWidth();
ms.sizeY = (int) ifd.getImageLength();
ms.rgb = samples > 1 || photo == PhotoInterp.RGB;
ms.interleaved = false;
ms.sizeC = isRGB() ? samples : 1;
ms.pixelType = ifd.getPixelType();
ms.imageCount = ifds.size();
ms.sizeZ = getImageCount();
ms.sizeT = 1;
LOGGER.info("Reading LSM metadata for series #{}", series);
MetadataStore store = makeFilterMetadata();
int instrument = getEffectiveSeries(series);
String imageName = getLSMFileFromSeries(series);
if (imageName.indexOf(".") != -1) {
imageName = imageName.substring(0, imageName.lastIndexOf("."));
}
if (imageName.indexOf(File.separator) != -1) {
imageName =
imageName.substring(imageName.lastIndexOf(File.separator) + 1);
}
if (lsmFilenames.length != getSeriesCount()) {
imageName += " #" + (getPosition(series) + 1);
}
// link Instrument and Image
store.setImageID(MetadataTools.createLSID("Image", series), series);
String instrumentID = MetadataTools.createLSID("Instrument", instrument);
store.setInstrumentID(instrumentID, instrument);
store.setImageInstrumentRef(instrumentID, series);
RandomAccessInputStream ras = getCZTag(ifd);
if (ras == null) {
imageNames.add(imageName);
return;
}
ras.seek(16);
ms.sizeZ = ras.readInt();
ras.skipBytes(4);
ms.sizeT = ras.readInt();
int dataType = ras.readInt();
switch (dataType) {
case 2:
addSeriesMeta("DataType", "12 bit unsigned integer");
break;
case 5:
addSeriesMeta("DataType", "32 bit float");
break;
case 0:
addSeriesMeta("DataType", "varying data types");
break;
default:
addSeriesMeta("DataType", "8 bit unsigned integer");
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
ras.seek(0);
addSeriesMeta("MagicNumber ", ras.readInt());
addSeriesMeta("StructureSize", ras.readInt());
addSeriesMeta("DimensionX", ras.readInt());
addSeriesMeta("DimensionY", ras.readInt());
ras.seek(32);
addSeriesMeta("ThumbnailX", ras.readInt());
addSeriesMeta("ThumbnailY", ras.readInt());
// pixel sizes are stored in meters, we need them in microns
pixelSizeX = ras.readDouble() * 1000000;
pixelSizeY = ras.readDouble() * 1000000;
pixelSizeZ = ras.readDouble() * 1000000;
addSeriesMeta("VoxelSizeX", new Double(pixelSizeX));
addSeriesMeta("VoxelSizeY", new Double(pixelSizeY));
addSeriesMeta("VoxelSizeZ", new Double(pixelSizeZ));
originX = ras.readDouble() * 1000000;
originY = ras.readDouble() * 1000000;
originZ = ras.readDouble() * 1000000;
addSeriesMeta("OriginX", originX);
addSeriesMeta("OriginY", originY);
addSeriesMeta("OriginZ", originZ);
}
else ras.seek(88);
int scanType = ras.readShort();
switch (scanType) {
case 0:
addSeriesMeta("ScanType", "x-y-z scan");
ms.dimensionOrder = "XYZCT";
break;
case 1:
addSeriesMeta("ScanType", "z scan (x-z plane)");
ms.dimensionOrder = "XYZCT";
break;
case 2:
addSeriesMeta("ScanType", "line scan");
ms.dimensionOrder = "XYZCT";
break;
case 3:
addSeriesMeta("ScanType", "time series x-y");
ms.dimensionOrder = "XYTCZ";
break;
case 4:
addSeriesMeta("ScanType", "time series x-z");
ms.dimensionOrder = "XYZTC";
break;
case 5:
addSeriesMeta("ScanType", "time series 'Mean of ROIs'");
ms.dimensionOrder = "XYTCZ";
break;
case 6:
addSeriesMeta("ScanType", "time series x-y-z");
ms.dimensionOrder = "XYZTC";
break;
case 7:
addSeriesMeta("ScanType", "spline scan");
ms.dimensionOrder = "XYCTZ";
break;
case 8:
addSeriesMeta("ScanType", "spline scan x-z");
ms.dimensionOrder = "XYCZT";
break;
case 9:
addSeriesMeta("ScanType", "time series spline plane x-z");
ms.dimensionOrder = "XYTCZ";
break;
case 10:
addSeriesMeta("ScanType", "point mode");
ms.dimensionOrder = "XYZCT";
break;
default:
addSeriesMeta("ScanType", "x-y-z scan");
ms.dimensionOrder = "XYZCT";
}
ms.indexed = lut != null && lut[series] != null;
if (isIndexed()) {
ms.rgb = false;
}
if (getSizeC() == 0) ms.sizeC = 1;
if (isRGB()) {
// shuffle C to front of order string
ms.dimensionOrder = getDimensionOrder().replaceAll("C", "");
ms.dimensionOrder = getDimensionOrder().replaceAll("XY", "XYC");
}
if (getEffectiveSizeC() == 0) {
ms.imageCount = getSizeZ() * getSizeT();
}
else {
ms.imageCount = getSizeZ() * getSizeT() * getEffectiveSizeC();
}
if (getImageCount() != ifds.size()) {
int diff = getImageCount() - ifds.size();
ms.imageCount = ifds.size();
if (diff % getSizeZ() == 0) {
ms.sizeT -= (diff / getSizeZ());
}
else if (diff % getSizeT() == 0) {
ms.sizeZ -= (diff / getSizeT());
}
else if (getSizeZ() > 1) {
ms.sizeZ = ifds.size();
ms.sizeT = 1;
}
else if (getSizeT() > 1) {
ms.sizeT = ifds.size();
ms.sizeZ = 1;
}
}
if (getSizeZ() == 0) ms.sizeZ = getImageCount();
if (getSizeT() == 0) ms.sizeT = getImageCount() / getSizeZ();
long channelColorsOffset = 0;
long timeStampOffset = 0;
long eventListOffset = 0;
long scanInformationOffset = 0;
long channelWavelengthOffset = 0;
long applicationTagOffset = 0;
Color[] channelColor = new Color[getSizeC()];
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
int spectralScan = ras.readShort();
if (spectralScan != 1) {
addSeriesMeta("SpectralScan", "no spectral scan");
}
else addSeriesMeta("SpectralScan", "acquired with spectral scan");
int type = ras.readInt();
switch (type) {
case 1:
addSeriesMeta("DataType2", "calculated data");
break;
case 2:
addSeriesMeta("DataType2", "animation");
break;
default:
addSeriesMeta("DataType2", "original scan data");
}
long[] overlayOffsets = new long[9];
String[] overlayKeys = new String[] {"VectorOverlay", "InputLut",
"OutputLut", "ROI", "BleachROI", "MeanOfRoisOverlay",
"TopoIsolineOverlay", "TopoProfileOverlay", "LinescanOverlay"};
overlayOffsets[0] = ras.readInt();
overlayOffsets[1] = ras.readInt();
overlayOffsets[2] = ras.readInt();
channelColorsOffset = ras.readInt();
addSeriesMeta("TimeInterval", ras.readDouble());
ras.skipBytes(4);
scanInformationOffset = ras.readInt();
applicationTagOffset = ras.readInt();
timeStampOffset = ras.readInt();
eventListOffset = ras.readInt();
overlayOffsets[3] = ras.readInt();
overlayOffsets[4] = ras.readInt();
ras.skipBytes(4);
addSeriesMeta("DisplayAspectX", ras.readDouble());
addSeriesMeta("DisplayAspectY", ras.readDouble());
addSeriesMeta("DisplayAspectZ", ras.readDouble());
addSeriesMeta("DisplayAspectTime", ras.readDouble());
overlayOffsets[5] = ras.readInt();
overlayOffsets[6] = ras.readInt();
overlayOffsets[7] = ras.readInt();
overlayOffsets[8] = ras.readInt();
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.NO_OVERLAYS)
{
for (int i=0; i<overlayOffsets.length; i++) {
parseOverlays(series, overlayOffsets[i], overlayKeys[i], store);
}
}
totalROIs = 0;
addSeriesMeta("ToolbarFlags", ras.readInt());
channelWavelengthOffset = ras.readInt();
ras.skipBytes(64);
}
else ras.skipBytes(182);
if (getSizeC() > 1) {
if (!splitPlanes) splitPlanes = isRGB();
ms.rgb = false;
if (splitPlanes) ms.imageCount *= getSizeC();
}
for (int c=0; c<getEffectiveSizeC(); c++) {
String lsid = MetadataTools.createLSID("Channel", series, c);
store.setChannelID(lsid, series, c);
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
// NB: the Zeiss LSM 5.5 specification indicates that there should be
// 15 32-bit integers here; however, there are actually 16 32-bit
// integers before the tile position offset.
// We have confirmed with Zeiss that this is correct, and the 6.0
// specification was updated to contain the correct information.
ras.skipBytes(64);
int tilePositionOffset = ras.readInt();
ras.skipBytes(36);
int positionOffset = ras.readInt();
// read referenced structures
addSeriesMeta("DimensionZ", getSizeZ());
addSeriesMeta("DimensionChannels", getSizeC());
addSeriesMeta("DimensionM", dimensionM);
addSeriesMeta("DimensionP", dimensionP);
if (lsmFilenames.length == 1) {
xCoordinates.clear();
yCoordinates.clear();
zCoordinates.clear();
}
if (positionOffset != 0) {
in.seek(positionOffset);
int nPositions = in.readInt();
for (int i=0; i<nPositions; i++) {
double xPos = originX + in.readDouble() * 1000000;
double yPos = originY + in.readDouble() * 1000000;
double zPos = originZ + in.readDouble() * 1000000;
xCoordinates.add(xPos);
yCoordinates.add(yPos);
zCoordinates.add(zPos);
addGlobalMetaList("X position for position", xPos);
addGlobalMetaList("Y position for position", yPos);
addGlobalMetaList("Z position for position", zPos);
}
}
if (tilePositionOffset != 0) {
in.seek(tilePositionOffset);
int nTiles = in.readInt();
for (int i=0; i<nTiles; i++) {
double xPos = originX + in.readDouble() * 1000000;
double yPos = originY + in.readDouble() * 1000000;
double zPos = originZ + in.readDouble() * 1000000;
if (xCoordinates.size() > i) {
xPos += xCoordinates.get(i);
xCoordinates.setElementAt(xPos, i);
}
else if (xCoordinates.size() == i) {
xCoordinates.add(xPos);
}
if (yCoordinates.size() > i) {
yPos += yCoordinates.get(i);
yCoordinates.setElementAt(yPos, i);
}
else if (yCoordinates.size() == i) {
yCoordinates.add(yPos);
}
if (zCoordinates.size() > i) {
zPos += zCoordinates.get(i);
zCoordinates.setElementAt(zPos, i);
}
else if (zCoordinates.size() == i) {
zCoordinates.add(zPos);
}
addGlobalMetaList("X position for position", xPos);
addGlobalMetaList("Y position for position", yPos);
addGlobalMetaList("Z position for position", zPos);
}
}
if (channelColorsOffset != 0) {
in.seek(channelColorsOffset + 12);
int colorsOffset = in.readInt();
int namesOffset = in.readInt();
// read the color of each channel
if (colorsOffset > 0) {
in.seek(channelColorsOffset + colorsOffset);
lut[getSeries()] = new byte[getSizeC() * 3][256];
core.get(getSeries()).indexed = true;
for (int i=0; i<getSizeC(); i++) {
int color = in.readInt();
int red = color & 0xff;
int green = (color & 0xff00) >> 8;
int blue = (color & 0xff0000) >> 16;
channelColor[i] = new Color(red, green, blue, 255);
for (int j=0; j<256; j++) {
lut[getSeries()][i * 3][j] = (byte) ((red / 255.0) * j);
lut[getSeries()][i * 3 + 1][j] = (byte) ((green / 255.0) * j);
lut[getSeries()][i * 3 + 2][j] = (byte) ((blue / 255.0) * j);
}
}
}
// read the name of each channel
if (namesOffset > 0) {
in.seek(channelColorsOffset + namesOffset);
channelNames[series] = new String[getSizeC()];
for (int i=0; i<getSizeC(); i++) {
if (in.getFilePointer() >= in.length() - 1) break;
// we want to read until we find a null char
int length = in.readInt();
String name = in.readString(length);
while ((name.length() > 0) &&
(name.codePointAt(name.length()-1) == 0)) {
name = name.substring(0, name.length()-1);
}
if (name.length() <= 128) {
addSeriesMetaList("ChannelName", name);
}
channelNames[series][i] = name;
}
}
}
if (timeStampOffset != 0) {
in.seek(timeStampOffset + 4);
int nStamps = in.readInt();
for (int i=0; i<nStamps; i++) {
double stamp = in.readDouble();
addSeriesMetaList("TimeStamp", stamp);
timestamps.add(new Double(stamp));
}
}
if (eventListOffset != 0) {
in.seek(eventListOffset + 4);
int numEvents = in.readInt();
in.seek(in.getFilePointer() - 4);
in.order(!in.isLittleEndian());
int tmpEvents = in.readInt();
if (numEvents < 0) numEvents = tmpEvents;
else numEvents = (int) Math.min(numEvents, tmpEvents);
in.order(!in.isLittleEndian());
if (numEvents > 65535) numEvents = 0;
for (int i=0; i<numEvents; i++) {
if (in.getFilePointer() + 16 <= in.length()) {
int size = in.readInt();
double eventTime = in.readDouble();
int eventType = in.readInt();
addSeriesMetaList("Event Time", eventTime);
addSeriesMetaList("Event Type", eventType);
long fp = in.getFilePointer();
int len = size - 16;
if (len > 65536) len = 65536;
if (len < 0) len = 0;
addSeriesMetaList("Event Description", in.readString(len));
in.seek(fp + size - 16);
if (in.getFilePointer() < 0) break;
}
}
}
if (scanInformationOffset != 0) {
in.seek(scanInformationOffset);
nextLaser = nextDetector = 0;
nextFilter = nextDichroicChannel = nextDichroic = 0;
nextDetectChannel = nextIllumChannel = 0;
Vector<SubBlock> blocks = new Vector<SubBlock>();
while (in.getFilePointer() < in.length() - 12) {
if (in.getFilePointer() < 0) break;
int entry = in.readInt();
int blockType = in.readInt();
int dataSize = in.readInt();
if (blockType == TYPE_SUBBLOCK) {
SubBlock block = null;
switch (entry) {
case SUBBLOCK_RECORDING:
block = new Recording();
break;
case SUBBLOCK_LASER:
block = new Laser();
break;
case SUBBLOCK_TRACK:
block = new Track();
break;
case SUBBLOCK_DETECTION_CHANNEL:
block = new DetectionChannel();
break;
case SUBBLOCK_ILLUMINATION_CHANNEL:
block = new IlluminationChannel();
break;
case SUBBLOCK_BEAM_SPLITTER:
block = new BeamSplitter();
break;
case SUBBLOCK_DATA_CHANNEL:
block = new DataChannel();
break;
case SUBBLOCK_TIMER:
block = new Timer();
break;
case SUBBLOCK_MARKER:
block = new Marker();
break;
}
if (block != null) {
blocks.add(block);
}
}
else if (dataSize + in.getFilePointer() <= in.length() &&
dataSize > 0)
{
in.skipBytes(dataSize);
}
else break;
}
Vector<SubBlock> nonAcquiredBlocks = new Vector<SubBlock>();
SubBlock[] metadataBlocks = blocks.toArray(new SubBlock[0]);
for (SubBlock block : metadataBlocks) {
block.addToHashtable();
if (!block.acquire) {
nonAcquiredBlocks.add(block);
blocks.remove(block);
}
}
for (int i=0; i<blocks.size(); i++) {
SubBlock block = blocks.get(i);
// every valid IlluminationChannel must be immediately followed by
// a valid DataChannel or IlluminationChannel
if ((block instanceof IlluminationChannel) && i < blocks.size() - 1) {
SubBlock nextBlock = blocks.get(i + 1);
if (!(nextBlock instanceof DataChannel) &&
!(nextBlock instanceof IlluminationChannel))
{
((IlluminationChannel) block).wavelength = null;
}
}
// every valid DetectionChannel must be immediately preceded by
// a valid Track or DetectionChannel
else if ((block instanceof DetectionChannel) && i > 0) {
SubBlock prevBlock = blocks.get(i - 1);
if (!(prevBlock instanceof Track) &&
!(prevBlock instanceof DetectionChannel))
{
block.acquire = false;
nonAcquiredBlocks.add(block);
}
}
if (block.acquire) populateMetadataStore(block, store, series);
}
for (SubBlock block : nonAcquiredBlocks) {
populateMetadataStore(block, store, series);
}
}
if (applicationTagOffset != 0) {
in.seek(applicationTagOffset);
parseApplicationTags();
}
}
imageNames.add(imageName);
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
if (userName != null) {
String experimenterID = MetadataTools.createLSID("Experimenter", 0);
store.setExperimenterID(experimenterID, 0);
store.setExperimenterUserName(userName, 0);
}
PositiveFloat pixX = FormatTools.getPhysicalSizeX(new Double(pixelSizeX));
PositiveFloat pixY = FormatTools.getPhysicalSizeY(new Double(pixelSizeY));
PositiveFloat pixZ = FormatTools.getPhysicalSizeZ(new Double(pixelSizeZ));
if (pixX != null) {
store.setPixelsPhysicalSizeX(pixX, series);
}
if (pixY != null) {
store.setPixelsPhysicalSizeY(pixY, series);
}
if (pixZ != null) {
store.setPixelsPhysicalSizeZ(pixZ, series);
}
for (int i=0; i<getSizeC(); i++) {
store.setChannelColor(channelColor[i], series, i);
if (channelNames[series] != null) {
store.setChannelName(channelNames[series][i], series, i);
}
}
int stampIndex = 0;
for (int i=0; i<series; i++) {
stampIndex += core.get(i).sizeT;
}
double firstStamp = 0;
if (timestamps.size() > 0 && stampIndex < timestamps.size()) {
firstStamp = timestamps.get(stampIndex).doubleValue();
}
for (int i=0; i<getImageCount(); i++) {
int[] zct = FormatTools.getZCTCoords(this, i);
if (getSizeT() > 1 && zct[2] < timestamps.size() - stampIndex) {
double thisStamp = timestamps.get(stampIndex + zct[2]).doubleValue();
store.setPlaneDeltaT(thisStamp - firstStamp, series, i);
}
if (xCoordinates.size() > series) {
store.setPlanePositionX(xCoordinates.get(series), series, i);
store.setPlanePositionY(yCoordinates.get(series), series, i);
store.setPlanePositionZ(zCoordinates.get(series), series, i);
}
}
}
ras.close();
}