// // ZeissLSMReader.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.in; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Hashtable; import java.util.Vector; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; 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 ome.xml.model.primitives.PositiveFloat; 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; /** * ZeissLSMReader is the file format reader for Zeiss LSM files. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/ZeissLSMReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/ZeissLSMReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @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 Hashtable<Integer, String> METADATA_KEYS = createKeys(); // -- Fields -- private double pixelSizeX, pixelSizeY, pixelSizeZ; private byte[][][] lut = null; private Vector<Double> timestamps; private int validChannels; private String[] lsmFilenames; private Vector<IFDList> ifdsList; private TiffParser tiffParser; private int nextLaser = 0, nextDetector = 0; private int nextFilter = 0, nextDichroicChannel = 0, nextDichroic = 0; private int nextDataChannel = 0, nextIllumChannel = 0, nextDetectChannel = 0; private boolean splitPlanes = false; private double zoom; private Vector<String> imageNames; private String binning; private Vector<Double> xCoordinates, yCoordinates, zCoordinates; private int dimensionM, dimensionP; private Hashtable<String, Integer> seriesCounts; private String userName; 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 Hashtable<Integer, String> acquiredDate = new Hashtable<Integer, String>(); // -- 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; 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() */ 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) */ 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) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { pixelSizeX = pixelSizeY = pixelSizeZ = 0; lut = null; timestamps = null; validChannels = 0; lsmFilenames = null; ifdsList = null; tiffParser = null; nextLaser = nextDetector = 0; nextFilter = nextDichroicChannel = nextDichroic = 0; nextDataChannel = 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(); } } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 4; if (!FormatTools.validStream(stream, blockLen, false)) return false; TiffParser parser = new TiffParser(stream); return parser.isValidHeader() || stream.readShort() == 0x5374; } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ 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() */ 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() */ public short[][] get16BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); if (lut == null || lut[getSeries()] == null || getPixelType() != FormatTools.UINT16 || validChannels == 0) { return null; } short[][] s = new short[3][65536]; for (int i=2; i>=3-validChannels; i--) { for (int j=0; j<s[i].length; j++) { s[i][j] = (short) j; } } return s; } /* @see loci.formats.IFormatReader#setSeries(int) */ public void setSeries(int series) { if (series != getSeries()) { prevBuf = null; } super.setSeries(series); } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ 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); } 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) */ 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."); } timestamps = new Vector<Double>(); imageNames = new Vector<String>(); xCoordinates = new Vector<Double>(); yCoordinates = new Vector<Double>(); zCoordinates = new Vector<Double>(); seriesCounts = new Hashtable<String, Integer>(); int seriesCount = 0; Vector<String> validFiles = new Vector<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 = new CoreMetadata[seriesCount]; ifdsList = new Vector<IFDList>(); ifdsList.setSize(core.length); int realSeries = 0; for (int i=0; i<lsmFilenames.length; i++) { RandomAccessInputStream stream = new RandomAccessInputStream(lsmFilenames[i]); 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++) { core[realSeries] = new CoreMetadata(); core[realSeries].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.size() == 0) { 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); } stream.close(); } MetadataStore store = makeFilterMetadata(); lut = new byte[ifdsList.size()][][]; long[] previousStripOffsets = null; for (int series=0; series<ifdsList.size(); series++) { if (series > 0 && lsmFilenames.length > 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 = 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; } s.close(); initMetadata(series); } for (int i=0; i<getSeriesCount(); i++) { core[i].imageCount = core[i].sizeZ * core[i].sizeC * core[i].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.setImageAcquiredDate(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); 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(series)); core[getSeries()] = reader.getCoreMetadata()[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)); in.order(isLittleEndian()); tiffParser = new TiffParser(in); PhotoInterp photo = ifd.getPhotometricInterpretation(); int samples = ifd.getSamplesPerPixel(); core[series].sizeX = (int) ifd.getImageWidth(); core[series].sizeY = (int) ifd.getImageLength(); core[series].rgb = samples > 1 || photo == PhotoInterp.RGB; core[series].interleaved = false; core[series].sizeC = isRGB() ? samples : 1; core[series].pixelType = ifd.getPixelType(); core[series].imageCount = ifds.size(); core[series].sizeZ = getImageCount(); core[series].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); core[series].sizeZ = ras.readInt(); ras.skipBytes(4); core[series].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"); core[series].dimensionOrder = "XYZCT"; break; case 1: addSeriesMeta("ScanType", "z scan (x-z plane)"); core[series].dimensionOrder = "XYZCT"; break; case 2: addSeriesMeta("ScanType", "line scan"); core[series].dimensionOrder = "XYZCT"; break; case 3: addSeriesMeta("ScanType", "time series x-y"); core[series].dimensionOrder = "XYTCZ"; break; case 4: addSeriesMeta("ScanType", "time series x-z"); core[series].dimensionOrder = "XYZTC"; break; case 5: addSeriesMeta("ScanType", "time series 'Mean of ROIs'"); core[series].dimensionOrder = "XYTCZ"; break; case 6: addSeriesMeta("ScanType", "time series x-y-z"); core[series].dimensionOrder = "XYZTC"; break; case 7: addSeriesMeta("ScanType", "spline scan"); core[series].dimensionOrder = "XYCTZ"; break; case 8: addSeriesMeta("ScanType", "spline scan x-z"); core[series].dimensionOrder = "XYCZT"; break; case 9: addSeriesMeta("ScanType", "time series spline plane x-z"); core[series].dimensionOrder = "XYTCZ"; break; case 10: addSeriesMeta("ScanType", "point mode"); core[series].dimensionOrder = "XYZCT"; break; default: addSeriesMeta("ScanType", "x-y-z scan"); core[series].dimensionOrder = "XYZCT"; } core[series].indexed = lut != null && lut[series] != null; if (isIndexed()) { core[series].rgb = false; } if (getSizeC() == 0) core[series].sizeC = 1; if (isRGB()) { // shuffle C to front of order string core[series].dimensionOrder = getDimensionOrder().replaceAll("C", ""); core[series].dimensionOrder = getDimensionOrder().replaceAll("XY", "XYC"); } if (getEffectiveSizeC() == 0) { core[series].imageCount = getSizeZ() * getSizeT(); } else { core[series].imageCount = getSizeZ() * getSizeT() * getEffectiveSizeC(); } if (getImageCount() != ifds.size()) { int diff = getImageCount() - ifds.size(); core[series].imageCount = ifds.size(); if (diff % getSizeZ() == 0) { core[series].sizeT -= (diff / getSizeZ()); } else if (diff % getSizeT() == 0) { core[series].sizeZ -= (diff / getSizeT()); } else if (getSizeZ() > 1) { core[series].sizeZ = ifds.size(); core[series].sizeT = 1; } else if (getSizeT() > 1) { core[series].sizeT = ifds.size(); core[series].sizeZ = 1; } } if (getSizeZ() == 0) core[series].sizeZ = getImageCount(); if (getSizeT() == 0) core[series].sizeT = getImageCount() / getSizeZ(); long channelColorsOffset = 0; long timeStampOffset = 0; long eventListOffset = 0; long scanInformationOffset = 0; long channelWavelengthOffset = 0; long applicationTagOffset = 0; int[] channelColor = new int[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); MetadataTools.setDefaultCreationDate(store, getCurrentFile(), series); if (getSizeC() > 1) { if (!splitPlanes) splitPlanes = isRGB(); core[series].rgb = false; if (splitPlanes) core[series].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); addGlobalMeta("X position for position #" + (i + 1), xPos); addGlobalMeta("Y position for position #" + (i + 1), yPos); addGlobalMeta("Z position for position #" + (i + 1), 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); } addGlobalMeta("X position for position #" + (i + 1), xPos); addGlobalMeta("Y position for position #" + (i + 1), yPos); addGlobalMeta("Z position for position #" + (i + 1), 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[getSeries()].indexed = true; for (int i=0; i<getSizeC(); i++) { int color = in.readInt(); channelColor[i] = color; int red = color & 0xff; int green = (color & 0xff00) >> 8; int blue = (color & 0xff0000) >> 16; 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 + 4); for (int i=0; i<getSizeC(); i++) { if (in.getFilePointer() >= in.length() - 1) break; // we want to read until we find a null char String name = in.readCString(); if (name.length() <= 128) { addSeriesMeta("ChannelName" + i, name); } } } } if (timeStampOffset != 0) { in.seek(timeStampOffset + 4); int nStamps = in.readInt(); for (int i=0; i<nStamps; i++) { double stamp = in.readDouble(); addSeriesMeta("TimeStamp" + i, 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(); addSeriesMeta("Event" + i + " Time", eventTime); addSeriesMeta("Event" + i + " Type", eventType); long fp = in.getFilePointer(); int len = size - 16; if (len > 65536) len = 65536; if (len < 0) len = 0; addSeriesMeta("Event" + i + " 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; nextDataChannel = 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); store.setExperimenterDisplayName(userName, 0); } Double pixX = new Double(pixelSizeX); Double pixY = new Double(pixelSizeY); Double pixZ = new Double(pixelSizeZ); store.setPixelsPhysicalSizeX(new PositiveFloat(pixX), series); store.setPixelsPhysicalSizeY(new PositiveFloat(pixY), series); store.setPixelsPhysicalSizeZ(new PositiveFloat(pixZ), series); for (int i=0; i<getSizeC(); i++) { store.setChannelColor(channelColor[i], series, i); } double firstStamp = 0; if (timestamps.size() > 0) { firstStamp = timestamps.get(0).doubleValue(); } for (int i=0; i<getImageCount(); i++) { int[] zct = FormatTools.getZCTCoords(this, i); if (zct[2] < timestamps.size()) { double thisStamp = timestamps.get(zct[2]).doubleValue(); store.setPlaneDeltaT(thisStamp - firstStamp, series, i); int index = zct[2] + 1; double nextStamp = index < timestamps.size() ? timestamps.get(index).doubleValue() : thisStamp; if (i == getSizeT() - 1 && zct[2] > 0) { thisStamp = timestamps.get(zct[2] - 1).doubleValue(); } store.setPlaneExposureTime(nextStamp - thisStamp, 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(); } 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.setImageObjectiveSettingsID(objectiveID, series); binning = recording.binning; } store.setObjectiveCorrection( getCorrection(recording.correction), instrument, 0); store.setObjectiveImmersion( getImmersion(recording.immersion), instrument, 0); if (recording.magnification != null && recording.magnification > 0) { store.setObjectiveNominalMagnification( new PositiveInteger(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) { store.setPixelsTimeIncrement(track.timeIncrement, series); } } else if (block instanceof DataChannel) { DataChannel channel = (DataChannel) block; if (channel.name != null && nextDataChannel < getSizeC() && channel.acquire) { store.setChannelName(channel.name, series, nextDataChannel++); } } else if (block instanceof DetectionChannel) { DetectionChannel channel = (DetectionChannel) block; if (channel.pinhole != null && channel.pinhole.doubleValue() != 0f && nextDetectChannel < getSizeC() && channel.acquire) { store.setChannelPinholeSize(channel.pinhole, 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 { store.setTransmittanceRangeCutIn( PositiveInteger.valueOf(v[0].trim()), instrument, nextFilter); } catch (NumberFormatException e) { } if (v.length > 1) { try { store.setTransmittanceRangeCutOut( PositiveInteger.valueOf(v[1].trim()), 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) { store.setLaserWavelength( new PositiveInteger(channel.wavelength), 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); switch (type) { case TEXT: double x = in.readDouble(); double y = in.readDouble(); String text = DataTools.stripString(in.readCString()); store.setTextValue(text, i, 0); store.setTextFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setTextStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setTextID(shapeID, 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.setLineX1(startX, i, 0); store.setLineY1(startY, i, 0); store.setLineX2(endX, i, 0); store.setLineY2(endY, i, 0); store.setLineFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setLineStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setLineID(shapeID, i, 0); break; case SCALE_BAR: case OPEN_ARROW: case CLOSED_ARROW: case PALETTE: in.skipBytes(36); 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.setRectangleX(topX, i, 0); store.setRectangleY(topY, i, 0); store.setRectangleWidth(width, i, 0); store.setRectangleHeight(height, i, 0); store.setRectangleFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setRectangleStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setRectangleID(shapeID, 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; 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("rotate(" + theta + " " + centerX + " " + centerY + ")", i, 0); } store.setEllipseX(centerX, i, 0); store.setEllipseY(centerY, i, 0); store.setEllipseRadiusX(rx, i, 0); store.setEllipseRadiusY(ry, i, 0); store.setEllipseFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setEllipseStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setEllipseID(shapeID, i, 0); 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.setEllipseX(centerX, i, 0); store.setEllipseY(centerY, i, 0); store.setEllipseRadiusX(radius, i, 0); store.setEllipseRadiusY(radius, i, 0); store.setEllipseFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setEllipseStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setEllipseID(shapeID, i, 0); 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.setEllipseX(cx, i, 0); store.setEllipseY(cy, i, 0); store.setEllipseRadiusX(r, i, 0); store.setEllipseRadiusY(r, i, 0); store.setEllipseFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setEllipseStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setEllipseID(shapeID, i, 0); 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.setPolylinePoints(p.toString(), i, 0); store.setPolylineFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setPolylineStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setPolylineID(shapeID, i, 0); 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.setPolylinePoints(p.toString(), i, 0); store.setPolylineClosed(type == CLOSED_POLYLINE, i, 0); store.setPolylineFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setPolylineStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setPolylineID(shapeID, i, 0); 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.setPolylinePoints(p.toString(), i, 0); store.setPolylineClosed(type != OPEN_BEZIER, i, 0); store.setPolylineFontSize(new NonNegativeInteger(fontHeight), i, 0); store.setPolylineStrokeWidth(lineWidth, i, 0); store.setROIID(roiID, i); store.setImageROIRef(roiID, series, i); store.setPolylineID(shapeID, i, 0); 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(); Vector<String> referencedLSMs = new Vector<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] + " " + row; if (currentId != null) { addGlobalMeta(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++) { String absolutePath = new Location(parent, fileList[i]).getAbsolutePath(); if (checkSuffix(fileList[i], "mdb") && (!absolutePath.equals(mdbFile) && !fileList[i].equals(mdbFile))) { 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 Hashtable<Integer, String> createKeys() { Hashtable<Integer, String> h = new Hashtable<Integer, String>(); h.put(new Integer(0x10000001), "Name"); h.put(new Integer(0x4000000c), "Name"); h.put(new Integer(0x50000001), "Name"); h.put(new Integer(0x90000001), "Name"); h.put(new Integer(0x90000005), "Detection Channel Name"); h.put(new Integer(0xb0000003), "Name"); h.put(new Integer(0xd0000001), "Name"); h.put(new Integer(0x12000001), "Name"); h.put(new Integer(0x14000001), "Name"); h.put(new Integer(0x10000002), "Description"); h.put(new Integer(0x14000002), "Description"); h.put(new Integer(0x10000003), "Notes"); h.put(new Integer(0x10000004), "Objective"); h.put(new Integer(0x10000005), "Processing Summary"); h.put(new Integer(0x10000006), "Special Scan Mode"); h.put(new Integer(0x10000007), "Scan Type"); h.put(new Integer(0x10000008), "Scan Mode"); h.put(new Integer(0x10000009), "Number of Stacks"); h.put(new Integer(0x1000000a), "Lines Per Plane"); h.put(new Integer(0x1000000b), "Samples Per Line"); h.put(new Integer(0x1000000c), "Planes Per Volume"); h.put(new Integer(0x1000000d), "Images Width"); h.put(new Integer(0x1000000e), "Images Height"); h.put(new Integer(0x1000000f), "Number of Planes"); h.put(new Integer(0x10000010), "Number of Stacks"); h.put(new Integer(0x10000011), "Number of Channels"); h.put(new Integer(0x10000012), "Linescan XY Size"); h.put(new Integer(0x10000013), "Scan Direction"); h.put(new Integer(0x10000014), "Time Series"); h.put(new Integer(0x10000015), "Original Scan Data"); h.put(new Integer(0x10000016), "Zoom X"); h.put(new Integer(0x10000017), "Zoom Y"); h.put(new Integer(0x10000018), "Zoom Z"); h.put(new Integer(0x10000019), "Sample 0X"); h.put(new Integer(0x1000001a), "Sample 0Y"); h.put(new Integer(0x1000001b), "Sample 0Z"); h.put(new Integer(0x1000001c), "Sample Spacing"); h.put(new Integer(0x1000001d), "Line Spacing"); h.put(new Integer(0x1000001e), "Plane Spacing"); h.put(new Integer(0x1000001f), "Plane Width"); h.put(new Integer(0x10000020), "Plane Height"); h.put(new Integer(0x10000021), "Volume Depth"); h.put(new Integer(0x10000034), "Rotation"); h.put(new Integer(0x10000035), "Precession"); h.put(new Integer(0x10000036), "Sample 0Time"); h.put(new Integer(0x10000037), "Start Scan Trigger In"); h.put(new Integer(0x10000038), "Start Scan Trigger Out"); h.put(new Integer(0x10000039), "Start Scan Event"); h.put(new Integer(0x10000040), "Start Scan Time"); h.put(new Integer(0x10000041), "Stop Scan Trigger In"); h.put(new Integer(0x10000042), "Stop Scan Trigger Out"); h.put(new Integer(0x10000043), "Stop Scan Event"); h.put(new Integer(0x10000044), "Stop Scan Time"); h.put(new Integer(0x10000045), "Use ROIs"); h.put(new Integer(0x10000046), "Use Reduced Memory ROIs"); h.put(new Integer(0x10000047), "User"); h.put(new Integer(0x10000048), "Use B/C Correction"); h.put(new Integer(0x10000049), "Position B/C Contrast 1"); h.put(new Integer(0x10000050), "Position B/C Contrast 2"); h.put(new Integer(0x10000051), "Interpolation Y"); h.put(new Integer(0x10000052), "Camera Binning"); h.put(new Integer(0x10000053), "Camera Supersampling"); h.put(new Integer(0x10000054), "Camera Frame Width"); h.put(new Integer(0x10000055), "Camera Frame Height"); h.put(new Integer(0x10000056), "Camera Offset X"); h.put(new Integer(0x10000057), "Camera Offset Y"); h.put(new Integer(0x40000001), "Multiplex Type"); h.put(new Integer(0x40000002), "Multiplex Order"); h.put(new Integer(0x40000003), "Sampling Mode"); h.put(new Integer(0x40000004), "Sampling Method"); h.put(new Integer(0x40000005), "Sampling Number"); h.put(new Integer(0x40000006), "Acquire"); h.put(new Integer(0x50000002), "Acquire"); h.put(new Integer(0x7000000b), "Acquire"); h.put(new Integer(0x90000004), "Acquire"); h.put(new Integer(0xd0000017), "Acquire"); h.put(new Integer(0x40000007), "Sample Observation Time"); h.put(new Integer(0x40000008), "Time Between Stacks"); h.put(new Integer(0x4000000d), "Collimator 1 Name"); h.put(new Integer(0x4000000e), "Collimator 1 Position"); h.put(new Integer(0x4000000f), "Collimator 2 Name"); h.put(new Integer(0x40000010), "Collimator 2 Position"); h.put(new Integer(0x40000011), "Is Bleach Track"); h.put(new Integer(0x40000012), "Bleach After Scan Number"); h.put(new Integer(0x40000013), "Bleach Scan Number"); h.put(new Integer(0x40000014), "Trigger In"); h.put(new Integer(0x12000004), "Trigger In"); h.put(new Integer(0x14000003), "Trigger In"); h.put(new Integer(0x40000015), "Trigger Out"); h.put(new Integer(0x12000005), "Trigger Out"); h.put(new Integer(0x14000004), "Trigger Out"); h.put(new Integer(0x40000016), "Is Ratio Track"); h.put(new Integer(0x40000017), "Bleach Count"); h.put(new Integer(0x40000018), "SPI Center Wavelength"); h.put(new Integer(0x40000019), "Pixel Time"); h.put(new Integer(0x40000020), "ID Condensor Frontlens"); h.put(new Integer(0x40000021), "Condensor Frontlens"); h.put(new Integer(0x40000022), "ID Field Stop"); h.put(new Integer(0x40000023), "Field Stop Value"); h.put(new Integer(0x40000024), "ID Condensor Aperture"); h.put(new Integer(0x40000025), "Condensor Aperture"); h.put(new Integer(0x40000026), "ID Condensor Revolver"); h.put(new Integer(0x40000027), "Condensor Revolver"); h.put(new Integer(0x40000028), "ID Transmission Filter 1"); h.put(new Integer(0x40000029), "ID Transmission 1"); h.put(new Integer(0x40000030), "ID Transmission Filter 2"); h.put(new Integer(0x40000031), "ID Transmission 2"); h.put(new Integer(0x40000032), "Repeat Bleach"); h.put(new Integer(0x40000033), "Enable Spot Bleach Pos"); h.put(new Integer(0x40000034), "Spot Bleach Position X"); h.put(new Integer(0x40000035), "Spot Bleach Position Y"); h.put(new Integer(0x40000036), "Bleach Position Z"); h.put(new Integer(0x50000003), "Power"); h.put(new Integer(0x90000002), "Power"); h.put(new Integer(0x70000003), "Detector Gain"); h.put(new Integer(0x70000005), "Amplifier Gain"); h.put(new Integer(0x70000007), "Amplifier Offset"); h.put(new Integer(0x70000009), "Pinhole Diameter"); h.put(new Integer(0x7000000c), "Detector Name"); h.put(new Integer(0x7000000d), "Amplifier Name"); h.put(new Integer(0x7000000e), "Pinhole Name"); h.put(new Integer(0x7000000f), "Filter Set Name"); h.put(new Integer(0x70000010), "Filter Name"); h.put(new Integer(0x70000013), "Integrator Name"); h.put(new Integer(0x70000014), "Detection Channel Name"); h.put(new Integer(0x70000015), "Detector Gain B/C 1"); h.put(new Integer(0x70000016), "Detector Gain B/C 2"); h.put(new Integer(0x70000017), "Amplifier Gain B/C 1"); h.put(new Integer(0x70000018), "Amplifier Gain B/C 2"); h.put(new Integer(0x70000019), "Amplifier Offset B/C 1"); h.put(new Integer(0x70000020), "Amplifier Offset B/C 2"); h.put(new Integer(0x70000021), "Spectral Scan Channels"); h.put(new Integer(0x70000022), "SPI Wavelength Start"); h.put(new Integer(0x70000023), "SPI Wavelength End"); h.put(new Integer(0x70000026), "Dye Name"); h.put(new Integer(0xd0000014), "Dye Name"); h.put(new Integer(0x70000027), "Dye Folder"); h.put(new Integer(0xd0000015), "Dye Folder"); h.put(new Integer(0x90000003), "Wavelength"); h.put(new Integer(0x90000006), "Power B/C 1"); h.put(new Integer(0x90000007), "Power B/C 2"); h.put(new Integer(0xb0000001), "Filter Set"); h.put(new Integer(0xb0000002), "Filter"); h.put(new Integer(0xd0000004), "Color"); h.put(new Integer(0xd0000005), "Sample Type"); h.put(new Integer(0xd0000006), "Bits Per Sample"); h.put(new Integer(0xd0000007), "Ratio Type"); h.put(new Integer(0xd0000008), "Ratio Track 1"); h.put(new Integer(0xd0000009), "Ratio Track 2"); h.put(new Integer(0xd000000a), "Ratio Channel 1"); h.put(new Integer(0xd000000b), "Ratio Channel 2"); h.put(new Integer(0xd000000c), "Ratio Const. 1"); h.put(new Integer(0xd000000d), "Ratio Const. 2"); h.put(new Integer(0xd000000e), "Ratio Const. 3"); h.put(new Integer(0xd000000f), "Ratio Const. 4"); h.put(new Integer(0xd0000010), "Ratio Const. 5"); h.put(new Integer(0xd0000011), "Ratio Const. 6"); h.put(new Integer(0xd0000012), "Ratio First Images 1"); h.put(new Integer(0xd0000013), "Ratio First Images 2"); h.put(new Integer(0xd0000016), "Spectrum"); h.put(new Integer(0x12000003), "Interval"); return h; } private Integer readEntry() throws IOException { return new Integer(in.readInt()); } private Object readValue() throws IOException { int blockType = in.readInt(); int dataSize = in.readInt(); switch (blockType) { case TYPE_LONG: return new Long(in.readInt()); case TYPE_RATIONAL: return new Double(in.readDouble()); case TYPE_ASCII: String s = in.readString(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 = new Integer(in.readInt()); break; case TYPE_RATIONAL: data = new Double(in.readDouble()); break; case TYPE_DATE: data = new Long(in.readLong()); break; case TYPE_BOOLEAN: data = new Boolean(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 Hashtable<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(new Integer(key)); if (o == null) return -1; return !(o instanceof Number) ? -1 : ((Number) o).intValue(); } protected float getFloatValue(int key) { Object o = blockData.get(new Integer(key)); if (o == null) return -1f; return !(o instanceof Number) ? -1f : ((Number) o).floatValue(); } protected double getDoubleValue(int key) { Object o = blockData.get(new Integer(key)); if (o == null) return -1d; return !(o instanceof Number) ? -1d : ((Number) o).doubleValue(); } protected String getStringValue(int key) { Object o = blockData.get(new Integer(key)); return o == null ? null : o.toString(); } protected void read() throws IOException { blockData = new Hashtable<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() + " #"; int index = 1; while (getSeriesMeta(prefix + index + " Acquire") != null) index++; prefix += index; Integer[] keys = blockData.keySet().toArray(new Integer[0]); for (Integer key : keys) { if (METADATA_KEYS.get(key) != null) { addSeriesMeta(prefix + " " + METADATA_KEYS.get(key), blockData.get(key)); if (METADATA_KEYS.get(key).equals("Bits Per Sample")) { core[getSeries()].bitsPerPixel = Integer.parseInt(blockData.get(key).toString()); } else if (METADATA_KEYS.get(key).equals("User")) { userName = blockData.get(key).toString(); } } } addGlobalMeta(prefix + " Acquire", new Boolean(acquire)); } } class Recording extends SubBlock { public String description; public String name; public String binning; public String startTime; // Objective data public String correction, immersion; public Integer magnification; public Double lensNA; public Boolean iris; 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 Integer(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 = new Boolean(tokens[next++].trim().equalsIgnoreCase("iris")); } } } class Laser extends SubBlock { public String medium, type, model; public Double power; 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; 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; protected void read() throws IOException { super.read(); pinhole = new Double(getDoubleValue(CHANNEL_PINHOLE_DIAMETER)); gain = new Double(getDoubleValue(CHANNEL_DETECTOR_GAIN)); amplificationGain = new Double(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 Integer wavelength; public Double attenuation; public String name; protected void read() throws IOException { super.read(); wavelength = new Integer(getIntValue(ILLUM_CHANNEL_WAVELENGTH)); attenuation = new Double(getDoubleValue(ILLUM_CHANNEL_ATTENUATION)); acquire = getIntValue(ILLUM_CHANNEL_ACQUIRE) != 0; name = getStringValue(ILLUM_CHANNEL_NAME); try { wavelength = new Integer(name); } catch (NumberFormatException e) { } } } class DataChannel extends SubBlock { public String name; 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; 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 { } }