/*
* #%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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import loci.common.DataTools;
import loci.common.DateTools;
import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.common.Region;
import loci.common.services.DependencyException;
import loci.common.services.ServiceFactory;
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.MDBService;
import loci.formats.tiff.IFD;
import loci.formats.tiff.IFDList;
import loci.formats.tiff.PhotoInterp;
import loci.formats.tiff.TiffCompression;
import loci.formats.tiff.TiffConstants;
import loci.formats.tiff.TiffParser;
import ome.xml.model.primitives.Color;
import ome.xml.model.primitives.Timestamp;
import ome.units.quantity.Length;
import ome.units.quantity.Time;
import ome.units.UNITS;
/**
* ZeissLSMReader is the file format reader for Zeiss LSM files.
*
* @author Eric Kjellman egkjellman at wisc.edu
* @author Melissa Linkert melissa at glencoesoftware.com
* @author Curtis Rueden ctrueden at wisc.edu
*/
public class ZeissLSMReader extends FormatReader {
// -- Constants --
public static final String[] MDB_SUFFIX = {"mdb"};
/** Tag identifying a Zeiss LSM file. */
private static final int ZEISS_ID = 34412;
/** Data types. */
private static final int TYPE_SUBBLOCK = 0;
private static final int TYPE_ASCII = 2;
private static final int TYPE_LONG = 4;
private static final int TYPE_RATIONAL = 5;
private static final int TYPE_DATE = 6;
private static final int TYPE_BOOLEAN = 7;
/** Subblock types. */
private static final int SUBBLOCK_RECORDING = 0x10000000;
private static final int SUBBLOCK_LASER = 0x50000000;
private static final int SUBBLOCK_TRACK = 0x40000000;
private static final int SUBBLOCK_DETECTION_CHANNEL = 0x70000000;
private static final int SUBBLOCK_ILLUMINATION_CHANNEL = 0x90000000;
private static final int SUBBLOCK_BEAM_SPLITTER = 0xb0000000;
private static final int SUBBLOCK_DATA_CHANNEL = 0xd0000000;
private static final int SUBBLOCK_TIMER = 0x12000000;
private static final int SUBBLOCK_MARKER = 0x14000000;
private static final int SUBBLOCK_END = (int) 0xffffffff;
/** Data types. */
private static final int RECORDING_NAME = 0x10000001;
private static final int RECORDING_DESCRIPTION = 0x10000002;
private static final int RECORDING_OBJECTIVE = 0x10000004;
private static final int RECORDING_ZOOM = 0x10000016;
private static final int RECORDING_SAMPLE_0TIME = 0x10000036;
private static final int RECORDING_CAMERA_BINNING = 0x10000052;
private static final int TRACK_ACQUIRE = 0x40000006;
private static final int TRACK_TIME_BETWEEN_STACKS = 0x4000000b;
private static final int LASER_NAME = 0x50000001;
private static final int LASER_ACQUIRE = 0x50000002;
private static final int LASER_POWER = 0x50000003;
private static final int CHANNEL_DETECTOR_GAIN = 0x70000003;
private static final int CHANNEL_PINHOLE_DIAMETER = 0x70000009;
private static final int CHANNEL_AMPLIFIER_GAIN = 0x70000005;
private static final int CHANNEL_FILTER_SET = 0x7000000f;
private static final int CHANNEL_FILTER = 0x70000010;
private static final int CHANNEL_ACQUIRE = 0x7000000b;
private static final int CHANNEL_NAME = 0x70000014;
private static final int ILLUM_CHANNEL_NAME = 0x90000001;
private static final int ILLUM_CHANNEL_ATTENUATION = 0x90000002;
private static final int ILLUM_CHANNEL_WAVELENGTH = 0x90000003;
private static final int ILLUM_CHANNEL_ACQUIRE = 0x90000004;
private static final int START_TIME = 0x10000036;
private static final int DATA_CHANNEL_NAME = 0xd0000001;
private static final int DATA_CHANNEL_ACQUIRE = 0xd0000017;
private static final int BEAM_SPLITTER_FILTER = 0xb0000002;
private static final int BEAM_SPLITTER_FILTER_SET = 0xb0000003;
/** Drawing element types. */
private static final int TEXT = 13;
private static final int LINE = 14;
private static final int SCALE_BAR = 15;
private static final int OPEN_ARROW = 16;
private static final int CLOSED_ARROW = 17;
private static final int RECTANGLE = 18;
private static final int ELLIPSE = 19;
private static final int CLOSED_POLYLINE = 20;
private static final int OPEN_POLYLINE = 21;
private static final int CLOSED_BEZIER = 22;
private static final int OPEN_BEZIER = 23;
private static final int CIRCLE = 24;
private static final int PALETTE = 25;
private static final int POLYLINE_ARROW = 26;
private static final int BEZIER_WITH_ARROW = 27;
private static final int ANGLE = 28;
private static final int CIRCLE_3POINT = 29;
// -- Static fields --
private static final ImmutableMap<Integer, String> METADATA_KEYS =
createKeys();
// -- Fields --
private double pixelSizeX, pixelSizeY, pixelSizeZ;
private byte[][][] lut = null;
private List<Double> timestamps;
private String[] lsmFilenames;
private List<IFDList> ifdsList;
private transient TiffParser tiffParser;
private int nextLaser = 0, nextDetector = 0;
private int nextFilter = 0, nextDichroicChannel = 0, nextDichroic = 0;
private int nextIllumChannel = 0, nextDetectChannel = 0;
private boolean splitPlanes = false;
private double zoom;
private List<String> imageNames;
private String binning;
private List<Double> xCoordinates, yCoordinates, zCoordinates;
private int dimensionM, dimensionP;
private Map<String, Integer> seriesCounts;
private String userName;
private String[][] channelNames;
private double originX, originY, originZ;
private int totalROIs = 0;
private int prevPlane = -1;
private int prevChannel = 0;
private byte[] prevBuf = null;
private Region prevRegion = null;
private Map<Integer, String> acquiredDate =
new HashMap<Integer, String>();
private Color[] channelColor;
// -- Constructor --
/** Constructs a new Zeiss LSM reader. */
public ZeissLSMReader() {
super("Zeiss Laser-Scanning Microscopy", new String[] {"lsm", "mdb"});
domains = new String[] {FormatTools.LM_DOMAIN};
hasCompanionFiles = true;
suffixSufficient = false;
datasetDescription = "One or more .lsm files; if multiple .lsm files " +
"are present, an .mdb file should also be present";
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#getOptimalTileWidth() */
public int getOptimalWidth() {
FormatTools.assertId(currentId, true, 1);
try {
return (int) ifdsList.get(getSeries()).get(0).getTileWidth();
}
catch (FormatException e) {
LOGGER.debug("Could not retrieve tile width", e);
}
return super.getOptimalTileWidth();
}
/* @see loci.formats.IFormatReader#getOptimalTileHeight() */
@Override
public int getOptimalTileHeight() {
FormatTools.assertId(currentId, true, 1);
try {
return (int) ifdsList.get(getSeries()).get(0).getTileLength();
}
catch (FormatException e) {
LOGGER.debug("Could not retrieve tile height", e);
}
return super.getOptimalTileHeight();
}
/* @see loci.formats.IFormatReader#isSingleFile(String) */
@Override
public boolean isSingleFile(String id) throws FormatException, IOException {
if (checkSuffix(id, MDB_SUFFIX)) return false;
return isGroupFiles() ? getMDBFile(id) != null : true;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
pixelSizeX = pixelSizeY = pixelSizeZ = 0;
lut = null;
timestamps = null;
lsmFilenames = null;
ifdsList = null;
tiffParser = null;
nextLaser = nextDetector = 0;
nextFilter = nextDichroicChannel = nextDichroic = 0;
nextIllumChannel = nextDetectChannel = 0;
splitPlanes = false;
zoom = 0;
imageNames = null;
binning = null;
totalROIs = 0;
prevPlane = -1;
prevChannel = 0;
prevBuf = null;
prevRegion = null;
xCoordinates = null;
yCoordinates = null;
zCoordinates = null;
dimensionM = 0;
dimensionP = 0;
seriesCounts = null;
originX = originY = originZ = 0d;
userName = null;
acquiredDate.clear();
channelNames = null;
channelColor = null;
}
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 4096;
if (!FormatTools.validStream(stream, blockLen, false)) return false;
TiffParser parser = new TiffParser(stream);
if (parser.isValidHeader()) {
return true;
}
stream.seek(4);
if (stream.readShort() == 0x5374) {
String check =
stream.readString((int) (blockLen - stream.getFilePointer()));
return check.indexOf("ID") > 0;
}
return false;
}
/* @see loci.formats.IFormatReader#fileGroupOption(String) */
@Override
public int fileGroupOption(String id) throws FormatException, IOException {
return checkSuffix(id, MDB_SUFFIX) ||
!new Location(id).getName().startsWith("spim_") ? FormatTools.MUST_GROUP :
FormatTools.CAN_GROUP;
}
/* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */
@Override
public String[] getSeriesUsedFiles(boolean noPixels) {
FormatTools.assertId(currentId, true, 1);
if (noPixels) {
if (checkSuffix(currentId, MDB_SUFFIX)) return new String[] {currentId};
return null;
}
if (lsmFilenames == null) return new String[] {currentId};
if (lsmFilenames.length == 1 && currentId.equals(lsmFilenames[0])) {
return lsmFilenames;
}
return new String[] {currentId, getLSMFileFromSeries(getSeries())};
}
/* @see loci.formats.IFormatReader#get8BitLookupTable() */
@Override
public byte[][] get8BitLookupTable() throws FormatException, IOException {
FormatTools.assertId(currentId, true, 1);
if (lut == null || lut[getSeries()] == null ||
getPixelType() != FormatTools.UINT8)
{
return null;
}
byte[][] b = new byte[3][];
b[0] = lut[getSeries()][prevChannel * 3];
b[1] = lut[getSeries()][prevChannel * 3 + 1];
b[2] = lut[getSeries()][prevChannel * 3 + 2];
return b;
}
/* @see loci.formats.IFormatReader#get16BitLookupTable() */
@Override
public short[][] get16BitLookupTable() throws FormatException, IOException {
FormatTools.assertId(currentId, true, 1);
if (lut == null || lut[getSeries()] == null ||
getPixelType() != FormatTools.UINT16 || channelColor == null)
{
return null;
}
short[][] s = new short[3][65536];
Color color = channelColor[prevChannel];
for (int j=0; j<s[0].length; j++) {
s[0][j] = (short) ((color.getRed() / 255.0) * j);
s[1][j] = (short) ((color.getGreen() / 255.0) * j);
s[2][j] = (short) ((color.getBlue() / 255.0) * j);
}
return s;
}
/* @see loci.formats.IFormatReader#setSeries(int) */
@Override
public void setSeries(int series) {
if (series != getSeries()) {
prevBuf = null;
}
super.setSeries(series);
}
/**
* @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 (getSeriesCount() > 1) {
in.close();
in = new RandomAccessInputStream(getLSMFileFromSeries(getSeries()));
in.order(!isLittleEndian());
tiffParser = new TiffParser(in);
}
else if (tiffParser == null) {
tiffParser = new TiffParser(in);
}
IFDList ifds = ifdsList.get(getSeries());
if (splitPlanes && getSizeC() > 1 && ifds.size() == getSizeZ() * getSizeT())
{
int bpp = FormatTools.getBytesPerPixel(getPixelType());
int plane = no / getSizeC();
int c = no % getSizeC();
Region region = new Region(x, y, w, h);
if (prevPlane != plane || prevBuf == null ||
prevBuf.length < w * h * bpp * getSizeC() || !region.equals(prevRegion))
{
prevBuf = new byte[w * h * bpp * getSizeC()];
tiffParser.getSamples(ifds.get(plane), prevBuf, x, y, w, h);
prevPlane = plane;
prevRegion = region;
}
ImageTools.splitChannels(
prevBuf, buf, c, getSizeC(), bpp, false, false, w * h * bpp);
prevChannel = c;
}
else {
tiffParser.getSamples(ifds.get(no), buf, x, y, w, h);
prevChannel = getZCTCoords(no)[1];
}
if (getSeriesCount() > 1) in.close();
return buf;
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
if (checkSuffix(id, MDB_SUFFIX)) {
lsmFilenames = parseMDB(id);
}
else lsmFilenames = new String[] {id};
if (lsmFilenames == null || lsmFilenames.length == 0) {
throw new FormatException("LSM files were not found.");
}
totalROIs = 0;
timestamps = new ArrayList<Double>();
imageNames = new ArrayList<String>();
xCoordinates = new ArrayList<Double>();
yCoordinates = new ArrayList<Double>();
zCoordinates = new ArrayList<Double>();
seriesCounts = new HashMap<String, Integer>();
int seriesCount = 0;
final List<String> validFiles = new ArrayList<String>();
for (String filename : lsmFilenames) {
try {
int extraSeries = getExtraSeries(filename);
seriesCounts.put(filename, extraSeries);
seriesCount += extraSeries;
validFiles.add(filename);
}
catch (IOException e) {
LOGGER.debug("Failed to parse " + filename, e);
}
}
lsmFilenames = validFiles.toArray(new String[validFiles.size()]);
core.clear();
for (int c=0; c<seriesCount; c++) {
CoreMetadata ms = new CoreMetadata();
core.add(ms);
}
channelNames = new String[seriesCount][];
ifdsList = new ArrayList<IFDList>();
for (int series = 0; series < seriesCount; series++) {
ifdsList.add(null);
}
int realSeries = 0;
for (int i=0; i<lsmFilenames.length; i++) {
RandomAccessInputStream stream = null;
try {
stream = new RandomAccessInputStream(lsmFilenames[i], 16);
int count = seriesCounts.get(lsmFilenames[i]);
TiffParser tp = new TiffParser(stream);
Boolean littleEndian = tp.checkHeader();
long[] ifdOffsets = tp.getIFDOffsets();
int ifdsPerSeries = (ifdOffsets.length / 2) / count;
int offset = 0;
Object zeissTag = null;
for (int s=0; s<count; s++, realSeries++) {
CoreMetadata ms = core.get(realSeries);
ms.littleEndian = littleEndian;
IFDList ifds = new IFDList();
while (ifds.size() < ifdsPerSeries) {
tp.setDoCaching(offset == 0);
IFD ifd = tp.getIFD(ifdOffsets[offset]);
if (offset == 0) zeissTag = ifd.get(ZEISS_ID);
if (offset > 0 && ifds.isEmpty()) {
ifd.putIFDValue(ZEISS_ID, zeissTag);
}
ifds.add(ifd);
if (zeissTag != null) offset += 2;
else offset++;
}
for (IFD ifd : ifds) {
tp.fillInIFD(ifd);
}
ifdsList.set(realSeries, ifds);
}
} catch (IOException e) {
throw e;
} finally {
if (stream != null) stream.close();
}
}
MetadataStore store = makeFilterMetadata();
lut = new byte[ifdsList.size()][][];
long[] previousStripOffsets = null;
for (int series=0; series<ifdsList.size(); series++) {
// IFD ordering is ZPT, so reset state if we have multiple timepoints
// this prevents offsets from being confused when the first offset in
// the next series is legitimately smaller than the last offset in
// the previous series
if (series > 0 && getSizeT() > 1) {
previousStripOffsets = null;
}
IFDList ifds = ifdsList.get(series);
for (IFD ifd : ifds) {
// check that predictor is set to 1 if anything other
// than LZW compression is used
if (ifd.getCompression() != TiffCompression.LZW) {
ifd.putIFDValue(IFD.PREDICTOR, 1);
}
}
// fix the offsets for > 4 GB files
RandomAccessInputStream s = null;
try {
s = new RandomAccessInputStream(getLSMFileFromSeries(series));
for (int i=0; i<ifds.size(); i++) {
long[] stripOffsets = ifds.get(i).getStripOffsets();
if (stripOffsets == null || (i != 0 &&
previousStripOffsets == null)) {
throw new FormatException(
"Strip offsets are missing; this is an invalid file.");
}
else if (i == 0 && previousStripOffsets == null) {
previousStripOffsets = stripOffsets;
continue;
}
boolean neededAdjustment = false;
for (int j=0; j<stripOffsets.length; j++) {
if (j >= previousStripOffsets.length) break;
if (stripOffsets[j] < previousStripOffsets[j]) {
stripOffsets[j] = (previousStripOffsets[j] & ~0xffffffffL) |
(stripOffsets[j] & 0xffffffffL);
if (stripOffsets[j] < previousStripOffsets[j]) {
long newOffset = stripOffsets[j] + 0x100000000L;
if (newOffset < s.length()) {
stripOffsets[j] = newOffset;
}
}
neededAdjustment = true;
}
if (neededAdjustment) {
ifds.get(i).putIFDValue(IFD.STRIP_OFFSETS, stripOffsets);
}
}
previousStripOffsets = stripOffsets;
}
initMetadata(series);
} catch (IOException e) {
throw e;
} finally {
if (s != null) s.close();
}
}
for (int i=0; i<getSeriesCount(); i++) {
CoreMetadata ms = core.get(i);
ms.imageCount = ms.sizeZ * ms.sizeC * ms.sizeT;
}
MetadataTools.populatePixels(store, this, true);
for (int series=0; series<ifdsList.size(); series++) {
setSeries(series);
if (series < imageNames.size()) {
store.setImageName(imageNames.get(series), series);
}
if (acquiredDate.containsKey(series)) {
store.setImageAcquisitionDate(new Timestamp(
acquiredDate.get(series)), series);
}
store.setPixelsBinDataBigEndian(!isLittleEndian(), series, 0);
}
setSeries(0);
}
// -- Helper methods --
private String getMDBFile(String id) throws FormatException, IOException {
Location parentFile = new Location(id).getAbsoluteFile().getParentFile();
String[] fileList = parentFile.list();
for (int i=0; i<fileList.length; i++) {
if (fileList[i].startsWith(".")) continue;
if (checkSuffix(fileList[i], MDB_SUFFIX)) {
Location file =
new Location(parentFile, fileList[i]).getAbsoluteFile();
if (file.isDirectory()) continue;
// make sure that the .mdb references this .lsm
String[] lsms = parseMDB(file.getAbsolutePath());
if (lsms == null) return null;
for (String lsm : lsms) {
if (id.endsWith(lsm) || lsm.endsWith(id)) {
return file.getAbsolutePath();
}
}
}
}
return null;
}
private int getEffectiveSeries(int currentSeries) {
int seriesCount = 0;
for (int i=0; i<lsmFilenames.length; i++) {
Integer count = seriesCounts.get(lsmFilenames[i]);
if (count == null) count = 1;
seriesCount += count;
if (seriesCount > currentSeries) return i;
}
return -1;
}
private String getLSMFileFromSeries(int currentSeries) {
int effectiveSeries = getEffectiveSeries(currentSeries);
return effectiveSeries < 0 ? null : lsmFilenames[effectiveSeries];
}
private int getExtraSeries(String file) throws FormatException, IOException {
if (in != null) in.close();
in = new RandomAccessInputStream(file, 16);
boolean littleEndian = in.read() == TiffConstants.LITTLE;
in.order(littleEndian);
tiffParser = new TiffParser(in);
IFD ifd = tiffParser.getFirstIFD();
RandomAccessInputStream ras = getCZTag(ifd);
if (ras == null) return 1;
ras.order(littleEndian);
ras.seek(264);
dimensionP = ras.readInt();
dimensionM = ras.readInt();
ras.close();
int nSeries = dimensionM * dimensionP;
return nSeries <= 0 ? 1 : nSeries;
}
private int getPosition(int currentSeries) {
int effectiveSeries = getEffectiveSeries(currentSeries);
int firstPosition = 0;
for (int i=0; i<effectiveSeries; i++) {
firstPosition += seriesCounts.get(lsmFilenames[i]);
}
return currentSeries - firstPosition;
}
private RandomAccessInputStream getCZTag(IFD ifd)
throws FormatException, IOException
{
// get TIF_CZ_LSMINFO structure
short[] s = ifd.getIFDShortArray(ZEISS_ID);
if (s == null) {
LOGGER.warn("Invalid Zeiss LSM file. Tag {} not found.", ZEISS_ID);
TiffReader reader = new TiffReader();
reader.setId(getLSMFileFromSeries(getSeries()));
core.set(getSeries(), reader.getCoreMetadataList().get(0));
reader.close();
return null;
}
byte[] cz = new byte[s.length];
for (int i=0; i<s.length; i++) {
cz[i] = (byte) s[i];
}
RandomAccessInputStream ras = new RandomAccessInputStream(cz);
ras.order(isLittleEndian());
return ras;
}
protected void initMetadata(int series) throws FormatException, IOException {
setSeries(series);
IFDList ifds = ifdsList.get(series);
IFD ifd = ifds.get(0);
in.close();
in = new RandomAccessInputStream(getLSMFileFromSeries(series), 16);
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", pixelSizeX);
addSeriesMeta("VoxelSizeY", pixelSizeY);
addSeriesMeta("VoxelSizeZ", 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;
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);
}
}
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.set(i, xPos);
}
else if (xCoordinates.size() == i) {
xCoordinates.add(xPos);
}
if (yCoordinates.size() > i) {
yPos += yCoordinates.get(i);
yCoordinates.set(i, yPos);
}
else if (yCoordinates.size() == i) {
yCoordinates.add(yPos);
}
if (zCoordinates.size() > i) {
zPos += zCoordinates.get(i);
zCoordinates.set(i, zPos);
}
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(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;
final List<SubBlock> blocks = new ArrayList<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;
}
final List<SubBlock> nonAcquiredBlocks = new ArrayList<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);
}
Length pixX = FormatTools.getPhysicalSizeX(pixelSizeX);
Length pixY = FormatTools.getPhysicalSizeY(pixelSizeY);
Length pixZ = FormatTools.getPhysicalSizeZ(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(new Time(thisStamp - firstStamp, UNITS.S), series, i);
}
if (xCoordinates.size() > series) {
final Double xCoord = xCoordinates.get(series);
final Double yCoord = yCoordinates.get(series);
final Double zCoord = zCoordinates.get(series);
if (xCoord == null) {
store.setPlanePositionX(null, series, i);
} else {
final Length x = new Length(xCoord, UNITS.REFERENCEFRAME);
store.setPlanePositionX(x, series, i);
}
if (yCoord == null) {
store.setPlanePositionY(null, series, i);
} else {
final Length y = new Length(yCoord, UNITS.REFERENCEFRAME);
store.setPlanePositionY(y, series, i);
}
if (zCoord == null) {
store.setPlanePositionZ(null, series, i);
} else {
final Length z = new Length(zCoord, UNITS.REFERENCEFRAME);
store.setPlanePositionZ(z, series, i);
}
}
}
}
ras.close();
}
protected void populateMetadataStore(SubBlock block, MetadataStore store,
int series)
throws FormatException
{
if (getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM) {
return;
}
int instrument = getEffectiveSeries(series);
// NB: block.acquire can be false. If that is the case, Instrument data
// is the only thing that should be populated.
if (block instanceof Recording) {
Recording recording = (Recording) block;
String objectiveID = MetadataTools.createLSID("Objective", instrument, 0);
if (recording.acquire) {
store.setImageDescription(recording.description, series);
if (recording.startTime != null) {
acquiredDate.put(series, recording.startTime);
}
store.setObjectiveSettingsID(objectiveID, series);
binning = recording.binning;
}
store.setObjectiveCorrection(
getCorrection(recording.correction), instrument, 0);
store.setObjectiveImmersion(
getImmersion(recording.immersion), instrument, 0);
if (recording.magnification != null) {
store.setObjectiveNominalMagnification(
recording.magnification, instrument, 0);
}
store.setObjectiveLensNA(recording.lensNA, instrument, 0);
store.setObjectiveIris(recording.iris, instrument, 0);
store.setObjectiveID(objectiveID, instrument, 0);
}
else if (block instanceof Laser) {
Laser laser = (Laser) block;
if (laser.medium != null) {
store.setLaserLaserMedium(getLaserMedium(laser.medium),
instrument, nextLaser);
}
if (laser.type != null) {
store.setLaserType(getLaserType(laser.type), instrument, nextLaser);
}
if (laser.model != null) {
store.setLaserModel(laser.model, instrument, nextLaser);
}
String lightSourceID =
MetadataTools.createLSID("LightSource", instrument, nextLaser);
store.setLaserID(lightSourceID, instrument, nextLaser);
nextLaser++;
}
else if (block instanceof Track) {
Track track = (Track) block;
if (track.acquire) {
if (track.timeIncrement != null)
{
store.setPixelsTimeIncrement(new Time(track.timeIncrement, UNITS.S), series);
}
}
}
else if (block instanceof DetectionChannel) {
DetectionChannel channel = (DetectionChannel) block;
if (channel.pinhole != null && channel.pinhole.doubleValue() != 0f &&
nextDetectChannel < getSizeC() && channel.acquire)
{
store.setChannelPinholeSize(new Length(channel.pinhole, UNITS.MICROM), series, nextDetectChannel);
}
if (channel.filter != null) {
String id = MetadataTools.createLSID("Filter", instrument, nextFilter);
if (channel.acquire && nextDetectChannel < getSizeC()) {
store.setLightPathEmissionFilterRef(
id, instrument, nextDetectChannel, 0);
}
store.setFilterID(id, instrument, nextFilter);
store.setFilterModel(channel.filter, instrument, nextFilter);
int space = channel.filter.indexOf(" ");
if (space != -1) {
String type = channel.filter.substring(0, space).trim();
if (type.equals("BP")) type = "BandPass";
else if (type.equals("LP")) type = "LongPass";
store.setFilterType(getFilterType(type), instrument, nextFilter);
String transmittance = channel.filter.substring(space + 1).trim();
String[] v = transmittance.split("-");
try {
final Double cutIn = new Double(v[0].trim());
Length in = FormatTools.getCutIn(cutIn);
if (in != null) {
store.setTransmittanceRangeCutIn(in, instrument, nextFilter);
}
}
catch (NumberFormatException e) { }
if (v.length > 1) {
try {
final Double cutOut = new Double(v[1].trim());
Length out = FormatTools.getCutOut(cutOut);
if (out != null) {
store.setTransmittanceRangeCutOut(out, instrument, nextFilter);
}
}
catch (NumberFormatException e) { }
}
}
nextFilter++;
}
if (channel.channelName != null) {
String detectorID =
MetadataTools.createLSID("Detector", instrument, nextDetector);
store.setDetectorID(detectorID, instrument, nextDetector);
if (channel.acquire && nextDetector < getSizeC()) {
store.setDetectorSettingsID(detectorID, series, nextDetector);
//store.setDetectorSettingsBinning(
// getBinning(binning), series, nextDetector);
}
}
if (channel.amplificationGain != null) {
store.setDetectorAmplificationGain(
channel.amplificationGain, instrument, nextDetector);
}
if (channel.gain != null) {
store.setDetectorGain(channel.gain, instrument, nextDetector);
}
store.setDetectorType(getDetectorType("PMT"), instrument, nextDetector);
store.setDetectorZoom(zoom, instrument, nextDetector);
nextDetectChannel++;
nextDetector++;
}
else if (block instanceof BeamSplitter) {
BeamSplitter beamSplitter = (BeamSplitter) block;
if (beamSplitter.filterSet != null) {
if (beamSplitter.filter != null) {
String id = MetadataTools.createLSID(
"Dichroic", instrument, nextDichroic);
store.setDichroicID(id, instrument, nextDichroic);
store.setDichroicModel(beamSplitter.filter, instrument, nextDichroic);
if (nextDichroicChannel < getEffectiveSizeC()) {
//store.setLightPathDichroicRef(id, series, nextDichroicChannel);
}
nextDichroic++;
}
nextDichroicChannel++;
}
}
else if (block instanceof IlluminationChannel) {
IlluminationChannel channel = (IlluminationChannel) block;
if (channel.acquire && channel.wavelength != null &&
channel.wavelength > 0)
{
Length wave = FormatTools.getWavelength(channel.wavelength);
if (wave != null) {
store.setLaserWavelength(wave, instrument, nextIllumChannel);
}
if (nextIllumChannel >= nextLaser) {
String lightSourceID = MetadataTools.createLSID(
"LightSource", instrument, nextIllumChannel);
store.setLaserID(lightSourceID, instrument, nextIllumChannel);
}
nextIllumChannel++;
}
}
}
/** Parses overlay-related fields. */
protected void parseOverlays(int series, long data, String suffix,
MetadataStore store) throws IOException
{
if (data == 0) return;
String prefix = "Series " + series + " ";
in.seek(data);
int numberOfShapes = in.readInt();
int size = in.readInt();
if (size <= 194) return;
in.skipBytes(20);
boolean valid = in.readInt() == 1;
in.skipBytes(164);
for (int i=totalROIs; i<totalROIs+numberOfShapes; i++) {
long offset = in.getFilePointer();
int type = in.readInt();
int blockLength = in.readInt();
double lineWidth = in.readInt();
int measurements = in.readInt();
double textOffsetX = in.readDouble();
double textOffsetY = in.readDouble();
int color = in.readInt();
boolean validShape = in.readInt() != 0;
int knotWidth = in.readInt();
int catchArea = in.readInt();
int fontHeight = in.readInt();
int fontWidth = in.readInt();
int fontEscapement = in.readInt();
int fontOrientation = in.readInt();
int fontWeight = in.readInt();
boolean fontItalic = in.readInt() != 0;
boolean fontUnderlined = in.readInt() != 0;
boolean fontStrikeout = in.readInt() != 0;
int fontCharSet = in.readInt();
int fontOutputPrecision = in.readInt();
int fontClipPrecision = in.readInt();
int fontQuality = in.readInt();
int fontPitchAndFamily = in.readInt();
String fontName = DataTools.stripString(in.readString(64));
boolean enabled = in.readShort() == 0;
boolean moveable = in.readInt() == 0;
in.skipBytes(34);
String roiID = MetadataTools.createLSID("ROI", i);
String shapeID = MetadataTools.createLSID("Shape", i, 0);
Length fontSize = FormatTools.getFontSize(fontHeight);
Length line = new Length(lineWidth, UNITS.PIXEL);
switch (type) {
case TEXT:
double x = in.readDouble();
double y = in.readDouble();
String text = DataTools.stripString(in.readCString());
store.setROIID(roiID, i);
store.setLabelID(shapeID, i, 0);
store.setLabelX(x, i, 0);
store.setLabelY(y, i, 0);
store.setLabelText(text, i, 0);
if (fontSize != null) {
store.setLabelFontSize(fontSize, i, 0);
}
store.setLabelStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case LINE:
in.skipBytes(4);
double startX = in.readDouble();
double startY = in.readDouble();
double endX = in.readDouble();
double endY = in.readDouble();
store.setROIID(roiID, i);
store.setLineID(shapeID, i, 0);
store.setLineX1(startX, i, 0);
store.setLineY1(startY, i, 0);
store.setLineX2(endX, i, 0);
store.setLineY2(endY, i, 0);
if (fontSize != null) {
store.setLineFontSize(fontSize, i, 0);
}
store.setLineStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case SCALE_BAR:
case OPEN_ARROW:
case CLOSED_ARROW:
case PALETTE:
in.skipBytes(36);
i--;
numberOfShapes--;
break;
case RECTANGLE:
in.skipBytes(4);
double topX = in.readDouble();
double topY = in.readDouble();
double bottomX = in.readDouble();
double bottomY = in.readDouble();
double width = Math.abs(bottomX - topX);
double height = Math.abs(bottomY - topY);
topX = Math.min(topX, bottomX);
topY = Math.min(topY, bottomY);
store.setROIID(roiID, i);
store.setRectangleID(shapeID, i, 0);
store.setRectangleX(topX, i, 0);
store.setRectangleY(topY, i, 0);
store.setRectangleWidth(width, i, 0);
store.setRectangleHeight(height, i, 0);
if (fontSize != null) {
store.setRectangleFontSize(fontSize, i, 0);
}
store.setRectangleStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case ELLIPSE:
int knots = in.readInt();
double[] xs = new double[knots];
double[] ys = new double[knots];
for (int j=0; j<xs.length; j++) {
xs[j] = in.readDouble();
ys[j] = in.readDouble();
}
double rx = 0, ry = 0, centerX = 0, centerY = 0;
store.setROIID(roiID, i);
store.setEllipseID(shapeID, i, 0);
if (knots == 4) {
double r1x = Math.abs(xs[2] - xs[0]) / 2;
double r1y = Math.abs(ys[2] - ys[0]) / 2;
double r2x = Math.abs(xs[3] - xs[1]) / 2;
double r2y = Math.abs(ys[3] - ys[1]) / 2;
if (r1x > r2x) {
ry = r1y;
rx = r2x;
centerX = Math.min(xs[3], xs[1]) + rx;
centerY = Math.min(ys[2], ys[0]) + ry;
}
else {
ry = r2y;
rx = r1x;
centerX = Math.min(xs[2], xs[0]) + rx;
centerY = Math.min(ys[3], ys[1]) + ry;
}
}
else if (knots == 3) {
// we are given the center point and one cut point for each axis
centerX = xs[0];
centerY = ys[0];
rx = Math.sqrt(Math.pow(xs[1] - xs[0], 2) +
Math.pow(ys[1] - ys[0], 2));
ry = Math.sqrt(Math.pow(xs[2] - xs[0], 2) +
Math.pow(ys[2] - ys[0], 2));
// calculate rotation angle
double slope = (ys[2] - centerY) / (xs[2] - centerX);
double theta = Math.toDegrees(Math.atan(slope));
store.setEllipseTransform(getRotationTransform(theta), i, 0);
}
store.setEllipseX(centerX, i, 0);
store.setEllipseY(centerY, i, 0);
store.setEllipseRadiusX(rx, i, 0);
store.setEllipseRadiusY(ry, i, 0);
if (fontSize != null) {
store.setEllipseFontSize(fontSize, i, 0);
}
store.setEllipseStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case CIRCLE:
in.skipBytes(4);
centerX = in.readDouble();
centerY = in.readDouble();
double curveX = in.readDouble();
double curveY = in.readDouble();
double radius = Math.sqrt(Math.pow(curveX - centerX, 2) +
Math.pow(curveY - centerY, 2));
store.setROIID(roiID, i);
store.setEllipseID(shapeID, i, 0);
store.setEllipseX(centerX, i, 0);
store.setEllipseY(centerY, i, 0);
store.setEllipseRadiusX(radius, i, 0);
store.setEllipseRadiusY(radius, i, 0);
if (fontSize != null) {
store.setEllipseFontSize(fontSize, i, 0);
}
store.setEllipseStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case CIRCLE_3POINT:
in.skipBytes(4);
// given 3 points on the perimeter of the circle, we need to
// calculate the center and radius
double[][] points = new double[3][2];
for (int j=0; j<points.length; j++) {
for (int k=0; k<points[j].length; k++) {
points[j][k] = in.readDouble();
}
}
double s = 0.5 * ((points[1][0] - points[2][0]) *
(points[0][0] - points[2][0]) - (points[1][1] - points[2][1]) *
(points[2][1] - points[0][1]));
double div = (points[0][0] - points[1][0]) *
(points[2][1] - points[0][1]) - (points[1][1] - points[0][1]) *
(points[0][0] - points[2][0]);
s /= div;
double cx = 0.5 * (points[0][0] + points[1][0]) +
s * (points[1][1] - points[0][1]);
double cy = 0.5 * (points[0][1] + points[1][1]) +
s * (points[0][0] - points[1][0]);
double r = Math.sqrt(Math.pow(points[0][0] - cx, 2) +
Math.pow(points[0][1] - cy, 2));
store.setROIID(roiID, i);
store.setEllipseID(shapeID, i, 0);
store.setEllipseX(cx, i, 0);
store.setEllipseY(cy, i, 0);
store.setEllipseRadiusX(r, i, 0);
store.setEllipseRadiusY(r, i, 0);
if (fontSize != null) {
store.setEllipseFontSize(fontSize, i, 0);
}
store.setEllipseStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case ANGLE:
in.skipBytes(4);
points = new double[3][2];
for (int j=0; j<points.length; j++) {
for (int k=0; k<points[j].length; k++) {
points[j][k] = in.readDouble();
}
}
StringBuffer p = new StringBuffer();
for (int j=0; j<points.length; j++) {
p.append(points[j][0]);
p.append(",");
p.append(points[j][1]);
if (j < points.length - 1) p.append(" ");
}
store.setROIID(roiID, i);
store.setPolylineID(shapeID, i, 0);
store.setPolylinePoints(p.toString(), i, 0);
if (fontSize != null) {
store.setPolylineFontSize(fontSize, i, 0);
}
store.setPolylineStrokeWidth(line, i, 0);
store.setImageROIRef(roiID, series, i);
break;
case CLOSED_POLYLINE:
case OPEN_POLYLINE:
case POLYLINE_ARROW:
int nKnots = in.readInt();
points = new double[nKnots][2];
for (int j=0; j<points.length; j++) {
for (int k=0; k<points[j].length; k++) {
points[j][k] = in.readDouble();
}
}
p = new StringBuffer();
for (int j=0; j<points.length; j++) {
p.append(points[j][0]);
p.append(",");
p.append(points[j][1]);
if (j < points.length - 1) p.append(" ");
}
store.setROIID(roiID, i);
if (type != CLOSED_POLYLINE) {
store.setPolylinePoints(p.toString(), i, 0);
if (fontSize != null) {
store.setPolylineFontSize(fontSize, i, 0);
}
store.setPolylineStrokeWidth(line, i, 0);
store.setPolylineID(shapeID, i, 0);
}
else {
store.setPolygonPoints(p.toString(), i, 0);
if (fontSize != null) {
store.setPolygonFontSize(fontSize, i, 0);
}
store.setPolygonStrokeWidth(line, i, 0);
store.setPolygonID(shapeID, i, 0);
}
store.setImageROIRef(roiID, series, i);
break;
case CLOSED_BEZIER:
case OPEN_BEZIER:
case BEZIER_WITH_ARROW:
nKnots = in.readInt();
points = new double[nKnots][2];
for (int j=0; j<points.length; j++) {
for (int k=0; k<points[j].length; k++) {
points[j][k] = in.readDouble();
}
}
p = new StringBuffer();
for (int j=0; j<points.length; j++) {
p.append(points[j][0]);
p.append(",");
p.append(points[j][1]);
if (j < points.length - 1) p.append(" ");
}
store.setROIID(roiID, i);
if (type == OPEN_BEZIER) {
store.setPolylineID(shapeID, i, 0);
store.setPolylinePoints(p.toString(), i, 0);
if (fontSize != null) {
store.setPolylineFontSize(fontSize, i, 0);
}
store.setPolylineStrokeWidth(line, i, 0);
}
else {
store.setPolygonID(shapeID, i, 0);
store.setPolygonPoints(p.toString(), i, 0);
if (fontSize != null) {
store.setPolygonFontSize(fontSize, i, 0);
}
store.setPolygonStrokeWidth(line, i, 0);
}
store.setImageROIRef(roiID, series, i);
break;
default:
i--;
numberOfShapes--;
continue;
}
// populate shape attributes
in.seek(offset + blockLength);
}
totalROIs += numberOfShapes;
}
/** Parse a .mdb file and return a list of referenced .lsm files. */
private String[] parseMDB(String mdbFile) throws FormatException, IOException
{
Location mdb = new Location(mdbFile).getAbsoluteFile();
Location parent = mdb.getParentFile();
MDBService mdbService = null;
try {
ServiceFactory factory = new ServiceFactory();
mdbService = factory.getInstance(MDBService.class);
}
catch (DependencyException de) {
throw new FormatException("MDB Tools Java library not found", de);
}
try {
mdbService.initialize(mdbFile);
}
catch (Exception e) {
return null;
}
Vector<Vector<String[]>> tables = mdbService.parseDatabase();
mdbService.close();
final List<String> referencedLSMs = new ArrayList<String>();
int referenceCount = 0;
for (Vector<String[]> table : tables) {
String[] columnNames = table.get(0);
String tableName = columnNames[0];
for (int row=1; row<table.size(); row++) {
String[] tableRow = table.get(row);
for (int col=0; col<tableRow.length; col++) {
String key = tableName + " " + columnNames[col + 1];
if (currentId != null) {
addGlobalMetaList(key, tableRow[col]);
}
if (tableName.equals("Recordings") && columnNames[col + 1] != null &&
columnNames[col + 1].equals("SampleData"))
{
String filename = tableRow[col].trim();
filename = filename.replace('\\', File.separatorChar);
filename = filename.replace('/', File.separatorChar);
filename =
filename.substring(filename.lastIndexOf(File.separator) + 1);
if (filename.length() > 0) {
Location file = new Location(parent, filename);
if (file.exists()) {
referencedLSMs.add(file.getAbsolutePath());
}
}
referenceCount++;
}
}
}
}
if (referencedLSMs.size() == referenceCount) {
return referencedLSMs.toArray(new String[0]);
}
String[] fileList = parent.list(true);
Arrays.sort(fileList);
for (int i=0; i<fileList.length; i++) {
Location f = new Location(fileList[i]);
if (!f.exists()) {
f = new Location(parent, fileList[i]);
}
String absolutePath = f.getAbsolutePath();
if (checkSuffix(fileList[i], "mdb") &&
(!absolutePath.equals(mdbFile) && !fileList[i].equals(mdbFile) &&
!absolutePath.equals(new Location(mdbFile).getAbsolutePath())))
{
if (referencedLSMs.size() > 0) {
return referencedLSMs.toArray(new String[0]);
}
break;
}
}
referencedLSMs.clear();
int mdbCount = 0;
for (int i=0; i<fileList.length; i++) {
String absolutePath = new Location(parent, fileList[i]).getAbsolutePath();
if (checkSuffix(fileList[i], "lsm")) {
referencedLSMs.add(absolutePath);
}
else if (checkSuffix(fileList[i], "mdb")) {
mdbCount++;
}
}
if (mdbCount > 1 || ((referencedLSMs.size() > referenceCount) &&
mdbCount > 1))
{
for (int i=0; i<fileList.length; i++) {
String absolutePath =
new Location(parent, fileList[i]).getAbsolutePath();
if (checkSuffix(fileList[i], "mdb") && !absolutePath.endsWith(mdbFile))
{
String[] files = parseMDB(absolutePath);
for (String f : files) {
referencedLSMs.remove(f);
}
}
}
}
return referencedLSMs.toArray(new String[0]);
}
private static ImmutableMap<Integer, String> createKeys() {
final Builder<Integer, String> h = ImmutableMap.builder();
h.put(0x10000001, "Name");
h.put(0x4000000c, "Name");
h.put(0x50000001, "Name");
h.put(0x90000001, "Name");
h.put(0x90000005, "Detection Channel Name");
h.put(0xb0000003, "Name");
h.put(0xd0000001, "Name");
h.put(0x12000001, "Name");
h.put(0x14000001, "Name");
h.put(0x10000002, "Description");
h.put(0x14000002, "Description");
h.put(0x10000003, "Notes");
h.put(0x10000004, "Objective");
h.put(0x10000005, "Processing Summary");
h.put(0x10000006, "Special Scan Mode");
h.put(0x10000007, "Scan Type");
h.put(0x10000008, "Scan Mode");
h.put(0x10000009, "Number of Stacks");
h.put(0x1000000a, "Lines Per Plane");
h.put(0x1000000b, "Samples Per Line");
h.put(0x1000000c, "Planes Per Volume");
h.put(0x1000000d, "Images Width");
h.put(0x1000000e, "Images Height");
h.put(0x1000000f, "Number of Planes");
h.put(0x10000010, "Number of Stacks");
h.put(0x10000011, "Number of Channels");
h.put(0x10000012, "Linescan XY Size");
h.put(0x10000013, "Scan Direction");
h.put(0x10000014, "Time Series");
h.put(0x10000015, "Original Scan Data");
h.put(0x10000016, "Zoom X");
h.put(0x10000017, "Zoom Y");
h.put(0x10000018, "Zoom Z");
h.put(0x10000019, "Sample 0X");
h.put(0x1000001a, "Sample 0Y");
h.put(0x1000001b, "Sample 0Z");
h.put(0x1000001c, "Sample Spacing");
h.put(0x1000001d, "Line Spacing");
h.put(0x1000001e, "Plane Spacing");
h.put(0x1000001f, "Plane Width");
h.put(0x10000020, "Plane Height");
h.put(0x10000021, "Volume Depth");
h.put(0x10000034, "Rotation");
h.put(0x10000035, "Precession");
h.put(0x10000036, "Sample 0Time");
h.put(0x10000037, "Start Scan Trigger In");
h.put(0x10000038, "Start Scan Trigger Out");
h.put(0x10000039, "Start Scan Event");
h.put(0x10000040, "Start Scan Time");
h.put(0x10000041, "Stop Scan Trigger In");
h.put(0x10000042, "Stop Scan Trigger Out");
h.put(0x10000043, "Stop Scan Event");
h.put(0x10000044, "Stop Scan Time");
h.put(0x10000045, "Use ROIs");
h.put(0x10000046, "Use Reduced Memory ROIs");
h.put(0x10000047, "User");
h.put(0x10000048, "Use B/C Correction");
h.put(0x10000049, "Position B/C Contrast 1");
h.put(0x10000050, "Position B/C Contrast 2");
h.put(0x10000051, "Interpolation Y");
h.put(0x10000052, "Camera Binning");
h.put(0x10000053, "Camera Supersampling");
h.put(0x10000054, "Camera Frame Width");
h.put(0x10000055, "Camera Frame Height");
h.put(0x10000056, "Camera Offset X");
h.put(0x10000057, "Camera Offset Y");
h.put(0x40000001, "Multiplex Type");
h.put(0x40000002, "Multiplex Order");
h.put(0x40000003, "Sampling Mode");
h.put(0x40000004, "Sampling Method");
h.put(0x40000005, "Sampling Number");
h.put(0x40000006, "Acquire");
h.put(0x50000002, "Acquire");
h.put(0x7000000b, "Acquire");
h.put(0x90000004, "Acquire");
h.put(0xd0000017, "Acquire");
h.put(0x40000007, "Sample Observation Time");
h.put(0x40000008, "Time Between Stacks");
h.put(0x4000000d, "Collimator 1 Name");
h.put(0x4000000e, "Collimator 1 Position");
h.put(0x4000000f, "Collimator 2 Name");
h.put(0x40000010, "Collimator 2 Position");
h.put(0x40000011, "Is Bleach Track");
h.put(0x40000012, "Bleach After Scan Number");
h.put(0x40000013, "Bleach Scan Number");
h.put(0x40000014, "Trigger In");
h.put(0x12000004, "Trigger In");
h.put(0x14000003, "Trigger In");
h.put(0x40000015, "Trigger Out");
h.put(0x12000005, "Trigger Out");
h.put(0x14000004, "Trigger Out");
h.put(0x40000016, "Is Ratio Track");
h.put(0x40000017, "Bleach Count");
h.put(0x40000018, "SPI Center Wavelength");
h.put(0x40000019, "Pixel Time");
h.put(0x40000020, "ID Condensor Frontlens");
h.put(0x40000021, "Condensor Frontlens");
h.put(0x40000022, "ID Field Stop");
h.put(0x40000023, "Field Stop Value");
h.put(0x40000024, "ID Condensor Aperture");
h.put(0x40000025, "Condensor Aperture");
h.put(0x40000026, "ID Condensor Revolver");
h.put(0x40000027, "Condensor Revolver");
h.put(0x40000028, "ID Transmission Filter 1");
h.put(0x40000029, "ID Transmission 1");
h.put(0x40000030, "ID Transmission Filter 2");
h.put(0x40000031, "ID Transmission 2");
h.put(0x40000032, "Repeat Bleach");
h.put(0x40000033, "Enable Spot Bleach Pos");
h.put(0x40000034, "Spot Bleach Position X");
h.put(0x40000035, "Spot Bleach Position Y");
h.put(0x40000036, "Bleach Position Z");
h.put(0x50000003, "Power");
h.put(0x90000002, "Power");
h.put(0x70000003, "Detector Gain");
h.put(0x70000005, "Amplifier Gain");
h.put(0x70000007, "Amplifier Offset");
h.put(0x70000009, "Pinhole Diameter");
h.put(0x7000000c, "Detector Name");
h.put(0x7000000d, "Amplifier Name");
h.put(0x7000000e, "Pinhole Name");
h.put(0x7000000f, "Filter Set Name");
h.put(0x70000010, "Filter Name");
h.put(0x70000013, "Integrator Name");
h.put(0x70000014, "Detection Channel Name");
h.put(0x70000015, "Detector Gain B/C 1");
h.put(0x70000016, "Detector Gain B/C 2");
h.put(0x70000017, "Amplifier Gain B/C 1");
h.put(0x70000018, "Amplifier Gain B/C 2");
h.put(0x70000019, "Amplifier Offset B/C 1");
h.put(0x70000020, "Amplifier Offset B/C 2");
h.put(0x70000021, "Spectral Scan Channels");
h.put(0x70000022, "SPI Wavelength Start");
h.put(0x70000023, "SPI Wavelength End");
h.put(0x70000026, "Dye Name");
h.put(0xd0000014, "Dye Name");
h.put(0x70000027, "Dye Folder");
h.put(0xd0000015, "Dye Folder");
h.put(0x90000003, "Wavelength");
h.put(0x90000006, "Power B/C 1");
h.put(0x90000007, "Power B/C 2");
h.put(0xb0000001, "Filter Set");
h.put(0xb0000002, "Filter");
h.put(0xd0000004, "Color");
h.put(0xd0000005, "Sample Type");
h.put(0xd0000006, "Bits Per Sample");
h.put(0xd0000007, "Ratio Type");
h.put(0xd0000008, "Ratio Track 1");
h.put(0xd0000009, "Ratio Track 2");
h.put(0xd000000a, "Ratio Channel 1");
h.put(0xd000000b, "Ratio Channel 2");
h.put(0xd000000c, "Ratio Const. 1");
h.put(0xd000000d, "Ratio Const. 2");
h.put(0xd000000e, "Ratio Const. 3");
h.put(0xd000000f, "Ratio Const. 4");
h.put(0xd0000010, "Ratio Const. 5");
h.put(0xd0000011, "Ratio Const. 6");
h.put(0xd0000012, "Ratio First Images 1");
h.put(0xd0000013, "Ratio First Images 2");
h.put(0xd0000016, "Spectrum");
h.put(0x12000003, "Interval");
return h.build();
}
private Integer readEntry() throws IOException {
return in.readInt();
}
private Object readValue() throws IOException {
int blockType = in.readInt();
int dataSize = in.readInt();
switch (blockType) {
case TYPE_LONG:
return in.readInt();
case TYPE_RATIONAL:
return in.readDouble();
case TYPE_ASCII:
String s = in.readByteToString(dataSize).trim();
StringBuffer sb = new StringBuffer();
for (int i=0; i<s.length(); i++) {
if (s.charAt(i) >= 10) sb.append(s.charAt(i));
else break;
}
return sb.toString();
case TYPE_SUBBLOCK:
return null;
}
in.skipBytes(dataSize);
return "";
}
private void parseApplicationTags() throws IOException {
int blockSize = in.readInt();
int numEntries = in.readInt();
for (int i=0; i<numEntries; i++) {
long fp = in.getFilePointer();
int entrySize = in.readInt();
int entryNameLength = in.readInt();
String entryName = in.readString(entryNameLength);
int dataType = in.readInt();
int dataSize = in.readInt();
Object data = null;
switch (dataType) {
case TYPE_ASCII:
data = in.readString(dataSize);
break;
case TYPE_LONG:
data = in.readInt();
break;
case TYPE_RATIONAL:
data = in.readDouble();
break;
case TYPE_DATE:
data = in.readLong();
break;
case TYPE_BOOLEAN:
data = in.readInt() == 0;
break;
}
addGlobalMeta(entryName, data);
if (in.getFilePointer() == fp + entrySize) {
continue;
}
int nDimensions = in.readInt();
int[] coordinate = new int[nDimensions];
for (int n=0; n<nDimensions; n++) {
coordinate[n] = in.readInt();
}
}
}
// -- Helper classes --
class SubBlock {
public Map<Integer, Object> blockData;
public boolean acquire = true;
public SubBlock() {
try {
read();
}
catch (IOException e) {
LOGGER.debug("Failed to read sub-block data", e);
}
}
protected int getIntValue(int key) {
Object o = blockData.get(key);
if (o == null) return -1;
return !(o instanceof Number) ? -1 : ((Number) o).intValue();
}
protected float getFloatValue(int key) {
Object o = blockData.get(key);
if (o == null) return -1f;
return !(o instanceof Number) ? -1f : ((Number) o).floatValue();
}
protected double getDoubleValue(int key) {
Object o = blockData.get(key);
if (o == null) return -1d;
return !(o instanceof Number) ? -1d : ((Number) o).doubleValue();
}
protected String getStringValue(int key) {
Object o = blockData.get(key);
return o == null ? null : o.toString();
}
protected void read() throws IOException {
blockData = new HashMap<Integer, Object>();
Integer entry = readEntry();
Object value = readValue();
while (value != null && in.getFilePointer() < in.length()) {
if (!blockData.containsKey(entry)) blockData.put(entry, value);
entry = readEntry();
value = readValue();
}
}
public void addToHashtable() {
String prefix = this.getClass().getSimpleName();
Integer[] keys = blockData.keySet().toArray(new Integer[0]);
for (Integer key : keys) {
if (METADATA_KEYS.get(key) != null) {
addSeriesMetaList(prefix + " " + METADATA_KEYS.get(key),
blockData.get(key));
if (METADATA_KEYS.get(key).equals("Bits Per Sample")) {
core.get(getSeries()).bitsPerPixel =
Integer.parseInt(blockData.get(key).toString());
}
else if (METADATA_KEYS.get(key).equals("User")) {
userName = blockData.get(key).toString();
}
}
}
addGlobalMetaList(prefix + " Acquire", acquire);
}
}
class Recording extends SubBlock {
public String description;
public String name;
public String binning;
public String startTime;
// Objective data
public String correction, immersion;
public Double magnification;
public Double lensNA;
public Boolean iris;
@Override
protected void read() throws IOException {
super.read();
description = getStringValue(RECORDING_DESCRIPTION);
name = getStringValue(RECORDING_NAME);
binning = getStringValue(RECORDING_CAMERA_BINNING);
if (binning != null && binning.indexOf("x") == -1) {
if (binning.equals("0")) binning = null;
else binning += "x" + binning;
}
// start time in days since Dec 30 1899
long stamp = (long) (getDoubleValue(RECORDING_SAMPLE_0TIME) * 86400000);
if (stamp > 0) {
startTime = DateTools.convertDate(stamp, DateTools.MICROSOFT);
}
zoom = getDoubleValue(RECORDING_ZOOM);
String objective = getStringValue(RECORDING_OBJECTIVE);
correction = "";
if (objective == null) objective = "";
String[] tokens = objective.split(" ");
int next = 0;
for (; next<tokens.length; next++) {
if (tokens[next].indexOf("/") != -1) break;
correction += tokens[next];
}
if (next < tokens.length) {
String p = tokens[next++];
int slash = p.indexOf("/");
if (slash > 0) {
try {
magnification = new Double(p.substring(0, slash - 1));
}
catch (NumberFormatException e) { }
}
if (slash >= 0 && slash < p.length() - 1) {
try {
lensNA = new Double(p.substring(slash + 1));
}
catch (NumberFormatException e) { }
}
}
immersion = next < tokens.length ? tokens[next++] : "Unknown";
iris = Boolean.FALSE;
if (next < tokens.length) {
iris = tokens[next++].trim().equalsIgnoreCase("iris");
}
}
}
class Laser extends SubBlock {
public String medium, type, model;
public Double power;
@Override
protected void read() throws IOException {
super.read();
model = getStringValue(LASER_NAME);
type = getStringValue(LASER_NAME);
if (type == null) type = "";
medium = "";
if (type.startsWith("HeNe")) {
medium = "HeNe";
type = "Gas";
}
else if (type.startsWith("Argon")) {
medium = "Ar";
type = "Gas";
}
else if (type.equals("Titanium:Sapphire") || type.equals("Mai Tai")) {
medium = "TiSapphire";
type = "SolidState";
}
else if (type.equals("YAG")) {
medium = "";
type = "SolidState";
}
else if (type.equals("Ar/Kr")) {
medium = "";
type = "Gas";
}
acquire = getIntValue(LASER_ACQUIRE) != 0;
power = getDoubleValue(LASER_POWER);
}
}
class Track extends SubBlock {
public Double timeIncrement;
@Override
protected void read() throws IOException {
super.read();
timeIncrement = getDoubleValue(TRACK_TIME_BETWEEN_STACKS);
acquire = getIntValue(TRACK_ACQUIRE) != 0;
}
}
class DetectionChannel extends SubBlock {
public Double pinhole;
public Double gain, amplificationGain;
public String filter, filterSet;
public String channelName;
@Override
protected void read() throws IOException {
super.read();
pinhole = getDoubleValue(CHANNEL_PINHOLE_DIAMETER);
gain = getDoubleValue(CHANNEL_DETECTOR_GAIN);
amplificationGain = getDoubleValue(CHANNEL_AMPLIFIER_GAIN);
filter = getStringValue(CHANNEL_FILTER);
if (filter != null) {
filter = filter.trim();
if (filter.length() == 0 || filter.equals("None")) {
filter = null;
}
}
filterSet = getStringValue(CHANNEL_FILTER_SET);
channelName = getStringValue(CHANNEL_NAME);
acquire = getIntValue(CHANNEL_ACQUIRE) != 0;
}
}
class IlluminationChannel extends SubBlock {
public Double wavelength;
public Double attenuation;
public String name;
@Override
protected void read() throws IOException {
super.read();
wavelength = getDoubleValue(ILLUM_CHANNEL_WAVELENGTH);
attenuation = getDoubleValue(ILLUM_CHANNEL_ATTENUATION);
acquire = getIntValue(ILLUM_CHANNEL_ACQUIRE) != 0;
name = getStringValue(ILLUM_CHANNEL_NAME);
try {
wavelength = new Double(name);
}
catch (NumberFormatException e) { }
}
}
class DataChannel extends SubBlock {
public String name;
@Override
protected void read() throws IOException {
super.read();
name = getStringValue(DATA_CHANNEL_NAME);
for (int i=0; i<name.length(); i++) {
if (name.charAt(i) < 10) {
name = name.substring(0, i);
break;
}
}
acquire = getIntValue(DATA_CHANNEL_ACQUIRE) != 0;
}
}
class BeamSplitter extends SubBlock {
public String filter, filterSet;
@Override
protected void read() throws IOException {
super.read();
filter = getStringValue(BEAM_SPLITTER_FILTER);
if (filter != null) {
filter = filter.trim();
if (filter.length() == 0 || filter.equals("None")) {
filter = null;
}
}
filterSet = getStringValue(BEAM_SPLITTER_FILTER_SET);
}
}
class Timer extends SubBlock { }
class Marker extends SubBlock { }
}