// // LeicaReader.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.StringTokenizer; import java.util.Vector; import loci.common.DataTools; import loci.common.DateTools; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.formats.AxisGuesser; import loci.formats.CoreMetadata; import loci.formats.FilePattern; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.meta.MetadataStore; import ome.xml.model.primitives.PositiveFloat; import loci.formats.tiff.IFD; import loci.formats.tiff.IFDList; import loci.formats.tiff.TiffConstants; import loci.formats.tiff.TiffParser; import ome.xml.model.enums.Correction; import ome.xml.model.enums.Immersion; import ome.xml.model.primitives.PositiveInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LeicaReader is the file format reader for Leica 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/LeicaReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/LeicaReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class LeicaReader extends FormatReader { // -- Constants - private static final Logger LOGGER = LoggerFactory.getLogger(LeicaReader.class); public static final String[] LEI_SUFFIX = {"lei"}; /** All Leica TIFFs have this tag. */ private static final int LEICA_MAGIC_TAG = 33923; /** Format for dates. */ private static final String DATE_FORMAT = "yyyy:MM:dd,HH:mm:ss:SSS"; /** IFD tags. */ private static final Integer SERIES = new Integer(10); private static final Integer IMAGES = new Integer(15); private static final Integer DIMDESCR = new Integer(20); private static final Integer FILTERSET = new Integer(30); private static final Integer TIMEINFO = new Integer(40); private static final Integer SCANNERSET = new Integer(50); private static final Integer EXPERIMENT = new Integer(60); private static final Integer LUTDESC = new Integer(70); private static final Integer CHANDESC = new Integer(80); private static final Integer SEQUENTIALSET = new Integer(90); private static final Integer SEQ_SCANNERSET = new Integer(200); private static final Integer SEQ_FILTERSET = new Integer(700); private static final int SEQ_SCANNERSET_END = 300; private static final int SEQ_FILTERSET_END = 800; private static final Hashtable<Integer, String> DIMENSION_NAMES = makeDimensionTable(); // -- Fields -- protected IFDList ifds; /** Array of IFD-like structures containing metadata. */ protected IFDList headerIFDs; /** Helper readers. */ protected MinimalTiffReader tiff; /** Array of image file names. */ protected Vector[] files; /** Number of series in the file. */ private int numSeries; /** Name of current LEI file */ private String leiFilename; /** Length of each file name. */ private int fileLength; private boolean[] valid; private String[][] timestamps; private Vector<String> seriesNames; private Vector<String> seriesDescriptions; private int lastPlane = 0, nameLength = 0; private double[][] physicalSizes; private double[] pinhole, exposureTime; private int nextDetector = 0, nextChannel = 0; private Vector<Integer> activeChannelIndices = new Vector<Integer>(); private boolean sequential = false; private Vector[] channelNames; private Vector[] emWaves; private Vector[] exWaves; private boolean[][] cutInPopulated; private boolean[][] cutOutPopulated; private boolean[][] filterRefPopulated; private Double detectorOffset, detectorVoltage; private int[] tileWidth, tileHeight; private int[][] channelColor; // -- Constructor -- /** Constructs a new Leica reader. */ public LeicaReader() { super("Leica", new String[] {"lei", "tif", "tiff", "raw"}); domains = new String[] {FormatTools.LM_DOMAIN}; hasCompanionFiles = true; datasetDescription = "One .lei file with at least one .tif/.tiff file " + "and an optional .txt file"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isSingleFile(String) */ public boolean isSingleFile(String id) throws FormatException, IOException { return false; } /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ public boolean isThisType(String name, boolean open) { if (checkSuffix(name, LEI_SUFFIX)) return true; if (!checkSuffix(name, TiffReader.TIFF_SUFFIXES) && !checkSuffix(name, "raw")) { return false; } if (!open) return false; // not allowed to touch the file system // check for that there is an .lei file in the same directory String prefix = name; if (prefix.indexOf(".") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf(".")); } Location lei = new Location(prefix + ".lei"); if (!lei.exists()) { lei = new Location(prefix + ".LEI"); while (!lei.exists() && prefix.indexOf("_") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf("_")); lei = new Location(prefix + ".lei"); if (!lei.exists()) lei = new Location(prefix + ".LEI"); } } return lei.exists(); } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { TiffParser tp = new TiffParser(stream); IFD ifd = tp.getFirstIFD(); if (ifd == null) return false; return ifd.containsKey(new Integer(LEICA_MAGIC_TAG)); } /* @see loci.formats.IFormatReader#get8BitLookupTable() */ public byte[][] get8BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); try { int index = (int) Math.min(lastPlane, files[series].size() - 1); tiff.setId((String) files[series].get(index)); return tiff.get8BitLookupTable(); } catch (FormatException e) { LOGGER.debug("Failed to retrieve lookup table", e); } catch (IOException e) { LOGGER.debug("Failed to retrieve lookup table", e); } return null; } /* @see loci.formats.IFormatReader#get16BitLookupTable() */ public short[][] get16BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); try { int index = (int) Math.min(lastPlane, files[series].size() - 1); tiff.setId((String) files[series].get(index)); return tiff.get16BitLookupTable(); } catch (FormatException e) { LOGGER.debug("Failed to retrieve lookup table", e); } catch (IOException e) { LOGGER.debug("Failed to retrieve lookup table", e); } return null; } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /** * @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); lastPlane = no; int fileIndex = no < files[series].size() ? no : 0; int planeIndex = no < files[series].size() ? 0 : no; String filename = (String) files[series].get(fileIndex); if (new Location(filename).exists()) { if (checkSuffix(filename, TiffReader.TIFF_SUFFIXES)) { tiff.setId(filename); return tiff.openBytes(planeIndex, buf, x, y, w, h); } else { RandomAccessInputStream s = new RandomAccessInputStream(filename); s.seek(planeIndex * FormatTools.getPlaneSize(this)); readPlane(s, x, y, w, h, buf); s.close(); } } // imitate Leica's software and return a blank plane if the // appropriate TIFF file is missing return buf; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); Vector<String> v = new Vector<String>(); if (leiFilename != null) v.add(leiFilename); if (!noPixels && files != null) { for (Object file : files[getSeries()]) { if (file != null && new Location((String) file).exists()) { v.add((String) file); } } } return v.toArray(new String[v.size()]); } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (tiff != null) tiff.close(fileOnly); if (!fileOnly) { leiFilename = null; files = null; ifds = headerIFDs = null; tiff = null; seriesNames = null; numSeries = 0; lastPlane = 0; physicalSizes = null; seriesDescriptions = null; pinhole = exposureTime = null; nextDetector = 0; nextChannel = 0; sequential = false; activeChannelIndices.clear(); channelNames = null; emWaves = null; exWaves = null; cutInPopulated = null; cutOutPopulated = null; filterRefPopulated = null; } } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); if (tileWidth[getSeries()] != 0) { return tileWidth[getSeries()]; } return super.getOptimalTileWidth(); } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); if (tileHeight[getSeries()] != 0) { return tileHeight[getSeries()]; } return super.getOptimalTileHeight(); } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { close(); String leiFile = findLEIFile(id); if (leiFile == null || leiFile.trim().length() == 0 || new Location(leiFile).isDirectory()) { if (checkSuffix(id, TiffReader.TIFF_SUFFIXES)) { super.initFile(id); TiffReader r = new TiffReader(); r.setMetadataStore(getMetadataStore()); r.setId(id); core = r.getCoreMetadata(); metadataStore = r.getMetadataStore(); Hashtable globalMetadata = r.getGlobalMetadata(); for (Object key : globalMetadata.keySet()) { addGlobalMeta(key.toString(), globalMetadata.get(key)); } r.close(); files = new Vector[] {new Vector()}; files[0].add(id); tiff = new MinimalTiffReader(); return; } else { throw new FormatException("LEI file not found."); } } // parse the LEI file super.initFile(leiFile); leiFilename = new File(leiFile).exists()? new Location(leiFile).getAbsolutePath() : id; in = new RandomAccessInputStream(leiFile); MetadataLevel metadataLevel = metadataOptions.getMetadataLevel(); seriesNames = new Vector<String>(); byte[] fourBytes = new byte[4]; in.read(fourBytes); core[0].littleEndian = (fourBytes[0] == TiffConstants.LITTLE && fourBytes[1] == TiffConstants.LITTLE && fourBytes[2] == TiffConstants.LITTLE && fourBytes[3] == TiffConstants.LITTLE); boolean realLittleEndian = isLittleEndian(); in.order(isLittleEndian()); LOGGER.info("Reading metadata blocks"); in.skipBytes(8); int addr = in.readInt(); headerIFDs = new IFDList(); while (addr != 0) { IFD ifd = new IFD(); headerIFDs.add(ifd); in.seek(addr + 4); int tag = in.readInt(); while (tag != 0) { // create the IFD structure int offset = in.readInt(); long pos = in.getFilePointer(); in.seek(offset + 12); int size = in.readInt(); ifd.putIFDValue(tag, in.getFilePointer()); in.seek(pos); tag = in.readInt(); } addr = in.readInt(); } numSeries = headerIFDs.size(); tileWidth = new int[numSeries]; tileHeight = new int[numSeries]; core = new CoreMetadata[numSeries]; for (int i=0; i<numSeries; i++) { core[i] = new CoreMetadata(); } files = new Vector[numSeries]; channelNames = new Vector[getSeriesCount()]; emWaves = new Vector[getSeriesCount()]; exWaves = new Vector[getSeriesCount()]; cutInPopulated = new boolean[getSeriesCount()][]; cutOutPopulated = new boolean[getSeriesCount()][]; filterRefPopulated = new boolean[getSeriesCount()][]; for (int i=0; i<getSeriesCount(); i++) { channelNames[i] = new Vector(); emWaves[i] = new Vector(); exWaves[i] = new Vector(); } // determine the length of a filename LOGGER.info("Parsing metadata blocks"); core[0].littleEndian = !isLittleEndian(); int seriesIndex = 0; int invalidCount = 0; valid = new boolean[numSeries]; timestamps = new String[headerIFDs.size()][]; for (int i=0; i<headerIFDs.size(); i++) { IFD ifd = headerIFDs.get(i); valid[i] = true; if (ifd.get(SERIES) != null) { long offset = ((Long) ifd.get(SERIES)).longValue(); in.seek(offset + 8); nameLength = in.readInt() * 2; } in.seek(((Long) ifd.get(IMAGES)).longValue()); parseFilenames(i); if (!valid[i]) invalidCount++; } numSeries -= invalidCount; int[] count = new int[getSeriesCount()]; for (int i=0; i<getSeriesCount(); i++) { count[i] = core[i].imageCount; } Vector[] tempFiles = files; IFDList tempIFDs = headerIFDs; core = new CoreMetadata[numSeries]; files = new Vector[numSeries]; headerIFDs = new IFDList(); int index = 0; for (int i=0; i<numSeries; i++) { core[i] = new CoreMetadata(); while (!valid[index]) index++; core[i].imageCount = count[index]; files[i] = tempFiles[index]; Object[] sorted = files[i].toArray(); Arrays.sort(sorted); files[i].clear(); files[i].addAll(Arrays.asList(sorted)); headerIFDs.add(tempIFDs.get(index)); index++; } tiff = new MinimalTiffReader(); LOGGER.info("Populating metadata"); if (headerIFDs == null) headerIFDs = ifds; seriesDescriptions = new Vector<String>(); physicalSizes = new double[headerIFDs.size()][5]; pinhole = new double[headerIFDs.size()]; exposureTime = new double[headerIFDs.size()]; channelColor = new int[headerIFDs.size()][]; for (int i=0; i<headerIFDs.size(); i++) { IFD ifd = headerIFDs.get(i); core[i].littleEndian = isLittleEndian(); setSeries(i); Integer[] keys = ifd.keySet().toArray(new Integer[ifd.size()]); Arrays.sort(keys); for (Integer key : keys) { long offset = ((Long) ifd.get(key)).longValue(); in.seek(offset); if (key.equals(SERIES)) { parseSeriesTag(); } else if (key.equals(IMAGES)) { parseImageTag(i); } else if (key.equals(DIMDESCR)) { parseDimensionTag(i); } else if (key.equals(TIMEINFO) && metadataLevel != MetadataLevel.MINIMUM) { parseTimeTag(i); } else if (key.equals(EXPERIMENT) && metadataLevel != MetadataLevel.MINIMUM) { parseExperimentTag(); } else if (key.equals(LUTDESC)) { parseLUT(i); } else if (key.equals(CHANDESC) && metadataLevel != MetadataLevel.MINIMUM) { parseChannelTag(); } } core[i].orderCertain = true; core[i].littleEndian = isLittleEndian(); core[i].falseColor = true; core[i].metadataComplete = true; core[i].interleaved = false; String filename = (String) files[i].get(0); if (checkSuffix(filename, TiffReader.TIFF_SUFFIXES)) { tiff.setId(filename); core[i].sizeX = tiff.getSizeX(); core[i].sizeY = tiff.getSizeY(); tileWidth[i] = tiff.getOptimalTileWidth(); tileHeight[i] = tiff.getOptimalTileHeight(); } } for (int i=0; i<numSeries; i++) { setSeries(i); if (getSizeZ() == 0) core[i].sizeZ = 1; if (getSizeT() == 0) core[i].sizeT = 1; if (getSizeC() == 0) core[i].sizeC = 1; if (getImageCount() == 0) core[i].imageCount = 1; if (getImageCount() == 1 && getSizeZ() * getSizeT() > 1) { core[i].sizeZ = 1; core[i].sizeT = 1; } if (getSizeY() == 1 || getSizeY() == getSizeZ() || getSizeY() == getSizeT()) { // XZ or XT scan if (getSizeZ() > 1 && getImageCount() == getSizeC() * getSizeT()) { core[i].sizeY = getSizeZ(); core[i].sizeZ = 1; } else if (getSizeT() > 1 && getImageCount() == getSizeC() * getSizeZ()) { core[i].sizeY = getSizeT(); core[i].sizeT = 1; } } if (isRGB()) core[i].indexed = false; core[i].dimensionOrder = MetadataTools.makeSaneDimensionOrder(getDimensionOrder()); core[i].littleEndian = realLittleEndian; } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this, true); // Ensure we populate Image names before returning due to a possible // minimum metadata level. for (int i=0; i<numSeries;i ++) { store.setImageName(seriesNames.get(i), i); } if (metadataLevel == MetadataLevel.MINIMUM) return; for (int i=0; i<numSeries; i++) { IFD ifd = headerIFDs.get(i); long firstPlane = 0; if (i < timestamps.length && timestamps[i] != null && timestamps[i].length > 0) { firstPlane = DateTools.getTime(timestamps[i][0], DATE_FORMAT); store.setImageAcquiredDate( DateTools.formatDate(timestamps[i][0], DATE_FORMAT), i); } else { MetadataTools.setDefaultCreationDate(store, id, i); } store.setImageDescription(seriesDescriptions.get(i), i); String instrumentID = MetadataTools.createLSID("Instrument", i); store.setInstrumentID(instrumentID, i); // parse instrument data nextDetector = 0; nextChannel = 0; cutInPopulated[i] = new boolean[core[i].sizeC]; cutOutPopulated[i] = new boolean[core[i].sizeC]; filterRefPopulated[i] = new boolean[core[i].sizeC]; Integer[] keys = ifd.keySet().toArray(new Integer[ifd.size()]); Arrays.sort(keys); int nextInstrumentBlock = 1; sequential = DataTools.indexOf(keys, SEQ_SCANNERSET) != -1; for (Integer key : keys) { if (key.equals(FILTERSET) || key.equals(SCANNERSET) || key.equals(SEQ_SCANNERSET) || key.equals(SEQ_FILTERSET) || (key > SEQ_SCANNERSET && key < SEQ_SCANNERSET_END) || (key > SEQ_FILTERSET && key < SEQ_FILTERSET_END)) { if (sequential && (key.equals(FILTERSET) || key.equals(SCANNERSET))) { continue; } long offset = ((Long) ifd.get(key)).longValue(); in.seek(offset); setSeries(i); parseInstrumentData(store, nextInstrumentBlock++); } } activeChannelIndices.clear(); // link Instrument and Image store.setImageInstrumentRef(instrumentID, i); if (physicalSizes[i][0] > 0) { store.setPixelsPhysicalSizeX(new PositiveFloat(physicalSizes[i][0]), i); } if (physicalSizes[i][1] > 0) { store.setPixelsPhysicalSizeY(new PositiveFloat(physicalSizes[i][1]), i); } if (physicalSizes[i][2] > 0) { store.setPixelsPhysicalSizeZ(new PositiveFloat(physicalSizes[i][2]), i); } if ((int) physicalSizes[i][4] > 0) { store.setPixelsTimeIncrement(physicalSizes[i][4], i); } for (int j=0; j<core[i].imageCount; j++) { if (timestamps[i] != null && j < timestamps[i].length) { long time = DateTools.getTime(timestamps[i][j], DATE_FORMAT); double elapsedTime = (double) (time - firstPlane) / 1000; store.setPlaneDeltaT(elapsedTime, i, j); store.setPlaneExposureTime(exposureTime[i], i, j); } } } setSeries(0); } // -- Helper methods -- /** Find the .lei file that belongs to the same dataset as the given file. */ private String findLEIFile(String baseFile) throws FormatException, IOException { if (checkSuffix(baseFile, LEI_SUFFIX)) { return baseFile; } else if (checkSuffix(baseFile, TiffReader.TIFF_SUFFIXES) && isGroupFiles()) { // need to find the associated .lei file if (ifds == null) super.initFile(baseFile); in = new RandomAccessInputStream(baseFile); TiffParser tp = new TiffParser(in); in.order(tp.checkHeader().booleanValue()); in.seek(0); LOGGER.info("Finding companion file name"); // open the TIFF file and look for the "Image Description" field ifds = tp.getIFDs(); if (ifds == null) throw new FormatException("No IFDs found"); String descr = ifds.get(0).getComment(); // remove anything of the form "[blah]" descr = descr.replaceAll("\\[.*.\\]\n", ""); // each remaining line in descr is a (key, value) pair, // where '=' separates the key from the value String lei = baseFile.substring(0, baseFile.lastIndexOf(File.separator) + 1); StringTokenizer lines = new StringTokenizer(descr, "\n"); String line = null, key = null, value = null; while (lines.hasMoreTokens()) { line = lines.nextToken(); if (line.indexOf("=") == -1) continue; key = line.substring(0, line.indexOf("=")).trim(); value = line.substring(line.indexOf("=") + 1).trim(); addGlobalMeta(key, value); if (key.startsWith("Series Name")) lei += value; } // now open the LEI file Location l = new Location(lei).getAbsoluteFile(); if (l.exists()) { return lei; } else { if (!lei.endsWith("lei") && !lei.endsWith("LEI")) { lei = lei.substring(0, lei.lastIndexOf(".") + 1); String test = lei + "lei"; if (new Location(test).exists()) { return test; } test = lei + "LEI"; if (new Location(test).exists()) { return test; } } l = l.getParentFile(); String[] list = l.list(); for (int i=0; i<list.length; i++) { if (checkSuffix(list[i], LEI_SUFFIX)) { return new Location(l.getAbsolutePath(), list[i]).getAbsolutePath(); } } } } else if (checkSuffix(baseFile, "raw") && isGroupFiles()) { // check for that there is an .lei file in the same directory String prefix = baseFile; if (prefix.indexOf(".") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf(".")); } Location lei = new Location(prefix + ".lei"); if (!lei.exists()) { lei = new Location(prefix + ".LEI"); while (!lei.exists() && prefix.indexOf("_") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf("_")); lei = new Location(prefix + ".lei"); if (!lei.exists()) lei = new Location(prefix + ".LEI"); } } if (lei.exists()) return lei.getAbsolutePath(); } return null; } private void parseFilenames(int seriesIndex) throws IOException { int maxPlanes = 0; Vector<String> f = new Vector<String>(); int tempImages = in.readInt(); if (((long) tempImages * nameLength) > in.length()) { in.order(!isLittleEndian()); tempImages = in.readInt(); in.order(isLittleEndian()); } core[seriesIndex].sizeX = in.readInt(); core[seriesIndex].sizeY = in.readInt(); in.skipBytes(4); int samplesPerPixel = in.readInt(); core[seriesIndex].rgb = samplesPerPixel > 1; core[seriesIndex].sizeC = samplesPerPixel; File dirFile = new File(currentId).getAbsoluteFile(); String[] listing = null; String dirPrefix = ""; if (dirFile.exists()) { listing = dirFile.getParentFile().list(); dirPrefix = dirFile.getParent(); if (!dirPrefix.endsWith(File.separator)) dirPrefix += File.separator; } else { listing = Location.getIdMap().keySet().toArray(new String[0]); } Vector<String> list = new Vector<String>(); for (int k=0; k<listing.length; k++) { if (checkSuffix(listing[k], TiffReader.TIFF_SUFFIXES)) { list.add(listing[k]); } } boolean tiffsExist = false; String prefix = ""; for (int j=0; j<tempImages; j++) { // read in each filename prefix = getString(nameLength); f.add(dirPrefix + prefix); // test to make sure the path is valid Location test = new Location(f.get(f.size() - 1)).getAbsoluteFile(); LOGGER.debug("Expected to find TIFF file {}", test.getAbsolutePath()); if (!test.exists()) { LOGGER.debug(" file does not exist"); } if (test.exists()) list.remove(prefix); if (!tiffsExist) tiffsExist = test.exists(); } // all of the TIFF files were renamed if (!tiffsExist) { // Strategy for handling renamed files: // 1) Assume that files for each series follow a pattern. // 2) Assign each file group to the first series with the correct count. LOGGER.info("Handling renamed TIFF files"); listing = list.toArray(new String[list.size()]); // grab the file patterns Vector<String> filePatterns = new Vector<String>(); for (String q : listing) { Location l = new Location(dirPrefix, q).getAbsoluteFile(); FilePattern pattern = new FilePattern(l); if (!pattern.isValid()) continue; AxisGuesser guess = new AxisGuesser(pattern, "XYZCT", 1, 1, 1, false); String fp = pattern.getPattern(); if (guess.getAxisCountS() >= 1) { String pre = pattern.getPrefix(guess.getAxisCountS()); Vector<String> fileList = new Vector<String>(); for (int n=0; n<listing.length; n++) { Location p = new Location(dirPrefix, listing[n]); if (p.getAbsolutePath().startsWith(pre)) { fileList.add(listing[n]); } } fp = FilePattern.findPattern(l.getAbsolutePath(), dirPrefix, fileList.toArray(new String[fileList.size()])); } if (fp != null && !filePatterns.contains(fp)) { filePatterns.add(fp); } } for (String q : filePatterns) { String[] pattern = new FilePattern(q).getFiles(); if (pattern.length == tempImages) { // make sure that this pattern hasn't already been used boolean validPattern = true; for (int n=0; n<seriesIndex; n++) { if (files[n] == null) continue; if (files[n].contains(pattern[0])) { validPattern = false; break; } } if (validPattern) { files[seriesIndex] = new Vector<String>(); files[seriesIndex].addAll(Arrays.asList(pattern)); } } } } else files[seriesIndex] = f; if (files[seriesIndex] == null) valid[seriesIndex] = false; else { core[seriesIndex].imageCount = files[seriesIndex].size(); maxPlanes = (int) Math.max(maxPlanes, core[seriesIndex].imageCount); } } private void parseSeriesTag() throws IOException { addSeriesMeta("Version", in.readInt()); addSeriesMeta("Number of Series", in.readInt()); fileLength = in.readInt(); addSeriesMeta("Length of filename", fileLength); int extLen = in.readInt(); if (extLen > fileLength) { in.seek(8); core[0].littleEndian = !isLittleEndian(); in.order(isLittleEndian()); fileLength = in.readInt(); extLen = in.readInt(); } addSeriesMeta("Length of file extension", extLen); addSeriesMeta("Image file extension", getString(extLen)); } private void parseImageTag(int seriesIndex) throws IOException { core[seriesIndex].imageCount = in.readInt(); core[seriesIndex].sizeX = in.readInt(); core[seriesIndex].sizeY = in.readInt(); addSeriesMeta("Number of images", getImageCount()); addSeriesMeta("Image width", getSizeX()); addSeriesMeta("Image height", getSizeY()); addSeriesMeta("Bits per Sample", in.readInt()); addSeriesMeta("Samples per pixel", in.readInt()); String name = getString(fileLength * 2); if (name.indexOf(".") != -1) { name = name.substring(0, name.lastIndexOf(".")); } String[] tokens = name.split("_"); StringBuffer buf = new StringBuffer(); for (int p=1; p<tokens.length; p++) { String lcase = tokens[p].toLowerCase(); if (!lcase.startsWith("ch0") && !lcase.startsWith("c0") && !lcase.startsWith("z0") && !lcase.startsWith("t0")) { if (buf.length() > 0) buf.append("_"); buf.append(tokens[p]); } } seriesNames.add(buf.toString()); } private void parseDimensionTag(int seriesIndex) throws FormatException, IOException { addSeriesMeta("Voxel Version", in.readInt()); core[seriesIndex].rgb = in.readInt() == 20; addSeriesMeta("VoxelType", isRGB() ? "RGB" : "gray"); int bpp = in.readInt(); addSeriesMeta("Bytes per pixel", bpp); if (bpp % 3 == 0) { core[seriesIndex].sizeC = 3; core[seriesIndex].rgb = true; bpp /= 3; } core[seriesIndex].pixelType = FormatTools.pixelTypeFromBytes(bpp, false, false); core[seriesIndex].dimensionOrder = "XY"; int resolution = in.readInt(); core[seriesIndex].bitsPerPixel = resolution; addSeriesMeta("Real world resolution", resolution); addSeriesMeta("Maximum voxel intensity", getString(true)); addSeriesMeta("Minimum voxel intensity", getString(true)); int len = in.readInt(); in.skipBytes(len * 2 + 4); len = in.readInt(); for (int j=0; j<len; j++) { int dimId = in.readInt(); String dimType = DIMENSION_NAMES.get(new Integer(dimId)); if (dimType == null) dimType = ""; int size = in.readInt(); int distance = in.readInt(); int strlen = in.readInt() * 2; String[] sizeData = getString(strlen).split(" "); String physicalSize = sizeData[0]; String unit = ""; if (sizeData.length > 1) unit = sizeData[1]; double physical = Double.parseDouble(physicalSize) / size; if (unit.equals("m")) { physical *= 1000000; } if (dimType.equals("x")) { core[seriesIndex].sizeX = size; physicalSizes[seriesIndex][0] = physical; } else if (dimType.equals("y")) { core[seriesIndex].sizeY = size; physicalSizes[seriesIndex][1] = physical; } else if (dimType.equals("channel")) { if (getSizeC() == 0) core[seriesIndex].sizeC = 1; core[seriesIndex].sizeC *= size; if (getDimensionOrder().indexOf("C") == -1) { core[seriesIndex].dimensionOrder += "C"; } physicalSizes[seriesIndex][3] = physical; } else if (dimType.equals("z")) { core[seriesIndex].sizeZ = size; if (getDimensionOrder().indexOf("Z") == -1) { core[seriesIndex].dimensionOrder += "Z"; } physicalSizes[seriesIndex][2] = physical; } else { core[seriesIndex].sizeT = size; if (getDimensionOrder().indexOf("T") == -1) { core[seriesIndex].dimensionOrder += "T"; } physicalSizes[seriesIndex][4] = physical; } String dimPrefix = "Dim" + j; addSeriesMeta(dimPrefix + " type", dimType); addSeriesMeta(dimPrefix + " size", size); addSeriesMeta(dimPrefix + " distance between sub-dimensions", distance); addSeriesMeta(dimPrefix + " physical length", physicalSize + " " + unit); addSeriesMeta(dimPrefix + " physical origin", getString(true)); } addSeriesMeta("Series name", getString(false)); String description = getString(false); seriesDescriptions.add(description); addSeriesMeta("Series description", description); } private void parseTimeTag(int seriesIndex) throws IOException { int nDims = in.readInt(); addSeriesMeta("Number of time-stamped dimensions", nDims); addSeriesMeta("Time-stamped dimension", in.readInt()); for (int j=0; j<nDims; j++) { String dimPrefix = "Dimension " + j; addSeriesMeta(dimPrefix + " ID", in.readInt()); addSeriesMeta(dimPrefix + " size", in.readInt()); addSeriesMeta(dimPrefix + " distance", in.readInt()); } int numStamps = in.readInt(); addSeriesMeta("Number of time-stamps", numStamps); timestamps[seriesIndex] = new String[numStamps]; for (int j=0; j<numStamps; j++) { timestamps[seriesIndex][j] = getString(64); addSeriesMeta("Timestamp " + j, timestamps[seriesIndex][j]); } if (in.getFilePointer() < in.length()) { int numTMs = in.readInt(); addSeriesMeta("Number of time-markers", numTMs); for (int j=0; j<numTMs; j++) { if (in.getFilePointer() + 4 >= in.length()) break; int numDims = in.readInt(); String time = "Time-marker " + j + " Dimension "; for (int k=0; k<numDims; k++) { if (in.getFilePointer() + 4 < in.length()) { addSeriesMeta(time + k + " coordinate", in.readInt()); } else break; } if (in.getFilePointer() >= in.length()) break; addSeriesMeta("Time-marker " + j, getString(64)); } } } private void parseExperimentTag() throws IOException { in.skipBytes(8); String description = getString(true); addSeriesMeta("Image Description", description); addSeriesMeta("Main file extension", getString(true)); addSeriesMeta("Image format identifier", getString(true)); addSeriesMeta("Single image extension", getString(true)); } private void parseLUT(int seriesIndex) throws IOException { int nChannels = in.readInt(); if (nChannels > 0) core[seriesIndex].indexed = true; addSeriesMeta("Number of LUT channels", nChannels); addSeriesMeta("ID of colored dimension", in.readInt()); channelColor[seriesIndex] = new int[nChannels]; for (int j=0; j<nChannels; j++) { String p = "LUT Channel " + j; addSeriesMeta(p + " version", in.readInt()); addSeriesMeta(p + " inverted?", in.read() == 1); addSeriesMeta(p + " description", getString(false)); addSeriesMeta(p + " filename", getString(false)); String lut = getString(false); addSeriesMeta(p + " name", lut); channelColor[seriesIndex][j] = 0xffffff; if (lut.equalsIgnoreCase("red")) { channelColor[seriesIndex][j] = 0xff0000; } else if (lut.equalsIgnoreCase("green")) { channelColor[seriesIndex][j] = 0xff00; } else if (lut.equalsIgnoreCase("blue")) { channelColor[seriesIndex][j] = 0xff; } else if (lut.equalsIgnoreCase("yellow")) { channelColor[seriesIndex][j] = 0xffff00; } else if (lut.equalsIgnoreCase("cyan")) { channelColor[seriesIndex][j] = 0xffff; } else if (lut.equalsIgnoreCase("magenta")) { channelColor[seriesIndex][j] = 0xff00ff; } in.skipBytes(8); } } private void parseChannelTag() throws IOException { int nBands = in.readInt(); for (int band=0; band<nBands; band++) { String p = "Band #" + (band + 1) + " "; addSeriesMeta(p + "Lower wavelength", in.readDouble()); in.skipBytes(4); addSeriesMeta(p + "Higher wavelength", in.readDouble()); in.skipBytes(4); addSeriesMeta(p + "Gain", in.readDouble()); addSeriesMeta(p + "Offset", in.readDouble()); } } private void parseInstrumentData(MetadataStore store, int blockNum) throws FormatException, IOException { int series = getSeries(); // read 24 byte SAFEARRAY in.skipBytes(4); int cbElements = in.readInt(); in.skipBytes(8); int nElements = in.readInt(); in.skipBytes(4); long initialOffset = in.getFilePointer(); long elementOffset = 0; LOGGER.trace("Element LOOP; series {} at offset", series, initialOffset); for (int j=0; j<nElements; j++) { elementOffset = initialOffset + j * cbElements; LOGGER.trace("Seeking to: {}", elementOffset); in.seek(elementOffset); String contentID = getString(128); LOGGER.trace("contentID: {}", contentID); String description = getString(64); LOGGER.trace("description: {}", description); String data = getString(64); int dataType = in.readShort(); LOGGER.trace("dataType: {}", dataType); in.skipBytes(6); // read data switch (dataType) { case 2: data = String.valueOf(in.readShort()); break; case 3: data = String.valueOf(in.readInt()); break; case 4: data = String.valueOf(in.readFloat()); break; case 5: data = String.valueOf(in.readDouble()); break; case 7: case 11: data = in.read() == 0 ? "false" : "true"; break; case 17: data = in.readString(1); break; } LOGGER.trace("data: {}", data); if (data.trim().length() == 0) { LOGGER.trace("Zero length dat string, continuing..."); continue; } String[] tokens = contentID.split("\\|"); LOGGER.trace("Parsing tokens: {}", tokens); if (tokens[0].startsWith("CDetectionUnit")) { // detector information if (tokens[1].startsWith("PMT")) { try { if (tokens[2].equals("VideoOffset")) { detectorOffset = new Double(data); } else if (tokens[2].equals("HighVoltage")) { detectorVoltage = new Double(data); nextDetector++; } else if (tokens[2].equals("State")) { // link Detector to Image, if the detector was actually used if (data.equals("Active")) { store.setDetectorOffset(detectorOffset, series, nextDetector); store.setDetectorVoltage(detectorVoltage, series, nextDetector); store.setDetectorType( getDetectorType("PMT"), series, nextDetector); String index = tokens[1].substring(tokens[1].indexOf(" ") + 1); int channelIndex = -1; try { channelIndex = Integer.parseInt(index) - 1; } catch (NumberFormatException e) { } if (channelIndex >= 0) { activeChannelIndices.add(new Integer(channelIndex)); } String detectorID = MetadataTools.createLSID("Detector", series, nextDetector); store.setDetectorID(detectorID, series, nextDetector); if (nextDetector == 0) { // link every channel to the first detector in the beginning // if additional detectors are found, the links will be // overwritten for (int c=0; c<getEffectiveSizeC(); c++) { store.setDetectorSettingsID(detectorID, series, c); } } if (nextChannel < getEffectiveSizeC()) { store.setDetectorSettingsID( detectorID, series, nextChannel++); } } } } catch (NumberFormatException e) { LOGGER.debug("Failed to parse detector metadata", e); } } } else if (tokens[0].startsWith("CTurret")) { // objective information int objective = Integer.parseInt(tokens[3]); if (tokens[2].equals("NumericalAperture")) { store.setObjectiveLensNA(new Double(data), series, objective); } else if (tokens[2].equals("Objective")) { String[] objectiveData = data.split(" "); StringBuffer model = new StringBuffer(); String mag = null, na = null; String immersion = null, correction = null; for (int i=0; i<objectiveData.length; i++) { if (objectiveData[i].indexOf("x") != -1 && mag == null && na == null) { int xIndex = objectiveData[i].indexOf("x"); mag = objectiveData[i].substring(0, xIndex).trim(); na = objectiveData[i].substring(xIndex + 1).trim(); } else if (mag == null && na == null) { model.append(objectiveData[i]); model.append(" "); } else if (correction == null) { correction = objectiveData[i]; } else if (immersion == null) { immersion = objectiveData[i]; } } if (immersion != null) immersion = immersion.trim(); if (correction != null) correction = correction.trim(); Correction realCorrection = getCorrection(correction); Correction testCorrection = getCorrection(immersion); Immersion realImmersion = getImmersion(immersion); Immersion testImmersion = getImmersion(correction); // Correction and Immersion are reversed if ((testCorrection != Correction.OTHER && realCorrection == Correction.OTHER) || (testImmersion != Immersion.OTHER && realImmersion == Immersion.OTHER)) { String tmp = correction; correction = immersion; immersion = tmp; } store.setObjectiveImmersion( getImmersion(immersion), series, objective); store.setObjectiveCorrection( getCorrection(correction), series, objective); store.setObjectiveModel(model.toString().trim(), series, objective); store.setObjectiveLensNA(new Double(na), series, objective); store.setObjectiveNominalMagnification(new PositiveInteger((int) Double.parseDouble(mag)), series, objective); } else if (tokens[2].equals("OrderNumber")) { store.setObjectiveSerialNumber(data, series, objective); } else if (tokens[2].equals("RefractionIndex")) { store.setImageObjectiveSettingsRefractiveIndex( new Double(data), series); } // link Objective to Image String objectiveID = MetadataTools.createLSID("Objective", series, objective); store.setObjectiveID(objectiveID, series, objective); if (objective == 0) { store.setImageObjectiveSettingsID(objectiveID, series); } } else if (tokens[0].startsWith("CSpectrophotometerUnit")) { int ndx = tokens[1].lastIndexOf(" "); int channel = Integer.parseInt(tokens[1].substring(ndx + 1)) - 1; if (tokens[2].equals("Wavelength")) { Integer wavelength = new Integer((int) Double.parseDouble(data)); store.setFilterModel(tokens[1], series, channel); String filterID = MetadataTools.createLSID("Filter", series, channel); store.setFilterID(filterID, series, channel); int index = activeChannelIndices.indexOf(new Integer(channel)); if (index >= 0 && index < core[series].sizeC) { if (!filterRefPopulated[series][index]) { store.setLightPathEmissionFilterRef(filterID, series, index, 0); filterRefPopulated[series][index] = true; } if (tokens[3].equals("0") && !cutInPopulated[series][index]) { store.setTransmittanceRangeCutIn( new PositiveInteger(wavelength), series, channel); cutInPopulated[series][index] = true; } else if (tokens[3].equals("1") && !cutOutPopulated[series][index]) { store.setTransmittanceRangeCutOut( new PositiveInteger(wavelength), series, channel); cutOutPopulated[series][index] = true; } } } else if (tokens[2].equals("Stain")) { if (activeChannelIndices.contains(new Integer(channel))) { int nNames = channelNames[series].size(); String prevValue = nNames == 0 ? "" : (String) channelNames[series].get(nNames - 1); if (!prevValue.equals(data)) { channelNames[series].add(data); } } } } else if (tokens[0].startsWith("CXYZStage")) { // NB: there is only one stage position specified for each series if (tokens[2].equals("XPos")) { for (int q=0; q<core[series].imageCount; q++) { store.setPlanePositionX(new Double(data), series, q); if (q == 0) { addGlobalMeta("X position for position #" + (series + 1), data); } } } else if (tokens[2].equals("YPos")) { for (int q=0; q<core[series].imageCount; q++) { store.setPlanePositionY(new Double(data), series, q); if (q == 0) { addGlobalMeta("Y position for position #" + (series + 1), data); } } } else if (tokens[2].equals("ZPos")) { for (int q=0; q<core[series].imageCount; q++) { store.setPlanePositionZ(new Double(data), series, q); if (q == 0) { addGlobalMeta("Z position for position #" + (series + 1), data); } } } } else if (tokens[0].equals("CScanActuator") && tokens[1].equals("Z Scan Actuator") && tokens[2].equals("Position")) { double pos = Double.parseDouble(data) * 1000000; for (int q=0; q<core[series].imageCount; q++) { store.setPlanePositionZ(pos, series, q); } } if (contentID.equals("dblVoxelX")) { physicalSizes[series][0] = Double.parseDouble(data); } else if (contentID.equals("dblVoxelY")) { physicalSizes[series][1] = Double.parseDouble(data); } else if (contentID.equals("dblStepSize")) { double size = Double.parseDouble(data); if (size > 0) { physicalSizes[series][2] = size; } } else if (contentID.equals("dblPinhole")) { // pinhole is stored in meters pinhole[series] = Double.parseDouble(data) * 1000000; } else if (contentID.startsWith("nDelayTime")) { exposureTime[series] = Double.parseDouble(data); if (contentID.endsWith("_ms")) { exposureTime[series] /= 1000; } } addSeriesMeta("Block " + blockNum + " " + contentID, data); } // populate saved LogicalChannel data for (int i=0; i<getSeriesCount(); i++) { setSeries(i); for (int channel=0; channel<getEffectiveSizeC(); channel++) { if (channel < channelNames[i].size()) { String name = (String) channelNames[i].get(channel); if (name != null && !name.trim().equals("") && !name.equals("None")) { store.setChannelName(name, i, channel); } } if (channel < emWaves[i].size()) { Integer wave = new Integer(emWaves[i].get(channel).toString()); store.setChannelEmissionWavelength( new PositiveInteger(wave), i, channel); } if (channel < exWaves[i].size()) { Integer wave = new Integer(exWaves[i].get(channel).toString()); store.setChannelExcitationWavelength( new PositiveInteger(wave), i, channel); } if (i < pinhole.length) { store.setChannelPinholeSize(new Double(pinhole[i]), i, channel); } if (channel < channelColor[i].length) { store.setChannelColor(channelColor[i][channel], i, channel); } } } setSeries(0); } private boolean usedFile(String s) { if (files == null) return false; for (int i=0; i<files.length; i++) { if (files[i] == null) continue; for (int j=0; j<files[i].size(); j++) { if (((String) files[i].get(j)).endsWith(s)) return true; } } return false; } private String getString(int len) throws IOException { return DataTools.stripString(in.readString(len)); } private String getString(boolean doubleLength) throws IOException { int len = in.readInt(); if (doubleLength) len *= 2; return getString(len); } private static Hashtable<Integer, String> makeDimensionTable() { Hashtable<Integer, String> table = new Hashtable<Integer, String>(); table.put(new Integer(0), "undefined"); table.put(new Integer(120), "x"); table.put(new Integer(121), "y"); table.put(new Integer(122), "z"); table.put(new Integer(116), "t"); table.put(new Integer(6815843), "channel"); table.put(new Integer(6357100), "wave length"); table.put(new Integer(7602290), "rotation"); table.put(new Integer(7798904), "x-wide for the motorized xy-stage"); table.put(new Integer(7798905), "y-wide for the motorized xy-stage"); table.put(new Integer(7798906), "z-wide for the z-stage-drive"); table.put(new Integer(4259957), "user1 - unspecified"); table.put(new Integer(4325493), "user2 - unspecified"); table.put(new Integer(4391029), "user3 - unspecified"); table.put(new Integer(6357095), "graylevel"); table.put(new Integer(6422631), "graylevel1"); table.put(new Integer(6488167), "graylevel2"); table.put(new Integer(6553703), "graylevel3"); table.put(new Integer(7864398), "logical x"); table.put(new Integer(7929934), "logical y"); table.put(new Integer(7995470), "logical z"); table.put(new Integer(7602254), "logical t"); table.put(new Integer(7077966), "logical lambda"); table.put(new Integer(7471182), "logical rotation"); table.put(new Integer(5767246), "logical x-wide"); table.put(new Integer(5832782), "logical y-wide"); table.put(new Integer(5898318), "logical z-wide"); return table; } }