/* * #%L * BSD implementations of Bio-Formats readers and writers * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package loci.formats.in; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import loci.common.DataTools; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.IFormatReader; import loci.formats.MetadataTools; import loci.formats.MissingLibraryException; import loci.formats.Modulo; import loci.formats.meta.IMetadata; import loci.formats.meta.MetadataStore; import loci.formats.ome.OMEXMLMetadata; import loci.formats.services.OMEXMLService; import loci.formats.services.OMEXMLServiceImpl; import loci.formats.tiff.IFD; import loci.formats.tiff.IFDList; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffIFDEntry; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; import ome.xml.model.primitives.Timestamp; /** * OMETiffReader is the file format reader for * <a href="http://www.openmicroscopy.org/site/support/ome-model/ome-tiff/">OME-TIFF</a> * files. */ public class OMETiffReader extends FormatReader { // -- Fields -- /** Mapping from series and plane numbers to files and IFD entries. */ protected OMETiffPlane[][] info; // dimensioned [numSeries][numPlanes] /** List of used files. */ protected String[] used; private int lastPlane = 0; private boolean hasSPW; private int[] tileWidth; private int[] tileHeight; private OMEXMLService service; private transient OMEXMLMetadata meta; private String metaFile; private String metadataFile; // -- Constructor -- /** Constructs a new OME-TIFF reader. */ public OMETiffReader() { super("OME-TIFF", new String[] {"ome.tif", "ome.tiff", "companion.ome"}); suffixNecessary = false; suffixSufficient = false; domains = FormatTools.NON_GRAPHICS_DOMAINS; hasCompanionFiles = true; datasetDescription = "One or more .ome.tiff files"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isSingleFile(String) */ @Override public boolean isSingleFile(String id) throws FormatException, IOException { // companion files in a binary-only dataset should always have additional files if (checkSuffix(id, "companion.ome")) { return false; } // parse and populate OME-XML metadata String fileName = new Location(id).getAbsoluteFile().getAbsolutePath(); RandomAccessInputStream ras = new RandomAccessInputStream(fileName, 16); TiffParser tp = new TiffParser(ras); IFD ifd = tp.getFirstIFD(); long[] ifdOffsets = tp.getIFDOffsets(); ras.close(); String xml = ifd.getComment(); if (service == null) setupService(); OMEXMLMetadata meta; try { meta = service.createOMEXMLMetadata(xml); metaFile = new Location(id).getAbsolutePath(); } catch (ServiceException se) { throw new FormatException(se); } if (meta.getRoot() == null) { throw new FormatException("Could not parse OME-XML from TIFF comment"); } int nImages = 0; for (int i=0; i<meta.getImageCount(); i++) { int nChannels = meta.getChannelCount(i); if (nChannels == 0) nChannels = 1; int z = meta.getPixelsSizeZ(i).getValue().intValue(); int t = meta.getPixelsSizeT(i).getValue().intValue(); nImages += z * t * nChannels; } return nImages > 0 && nImages <= ifdOffsets.length; } /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ @Override public boolean isThisType(String name, boolean open) { if (checkSuffix(name, "companion.ome")) { // force the reader to pick up binary-only companion files return true; } metaFile = new Location(name).getAbsolutePath(); boolean valid = super.isThisType(name, open); if (valid && !isGroupFiles()) { try { return isSingleFile(metaFile); } catch (Exception e) { LOGGER.debug("", e); return false; } } return valid; } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ @Override public boolean isThisType(RandomAccessInputStream stream) throws IOException { TiffParser tp = new TiffParser(stream); tp.setDoCaching(false); boolean validHeader = tp.isValidHeader(); if (!validHeader) return false; // look for OME-XML in first IFD's comment IFD ifd = tp.getFirstIFD(); if (ifd == null) return false; Object description = ifd.get(IFD.IMAGE_DESCRIPTION); if (description == null) { return false; } String comment = null; if (description instanceof TiffIFDEntry) { Object value = tp.getIFDValue((TiffIFDEntry) description); if (value != null) { comment = value.toString(); } } else if (description instanceof String) { comment = (String) description; } if (comment == null || comment.trim().length() == 0) return false; comment = comment.trim(); // do a basic sanity check before attempting to parse the comment as XML // the parsing step is a bit slow, so there is no sense in trying unless // we are reasonably sure that the comment contains XML if (!comment.startsWith("<") || !comment.endsWith(">")) { return false; } try { if (service == null) setupService(); meta = service.createOMEXMLMetadata(comment); try { String metadataFile = meta.getBinaryOnlyMetadataFile(); if (metadataFile != null) { return true; } } catch (NullPointerException e) { } for (int i=0; i<meta.getImageCount(); i++) { meta.setPixelsBinDataBigEndian(Boolean.TRUE, i, 0); MetadataTools.verifyMinimumPopulated(meta, i); } return meta.getImageCount() > 0; } catch (ServiceException se) { LOGGER.debug("OME-XML parsing failed", se); } catch (NullPointerException e) { LOGGER.debug("OME-XML parsing failed", e); } catch (FormatException e) { LOGGER.debug("OME-XML parsing failed", e); } catch (IndexOutOfBoundsException e) { LOGGER.debug("OME-XML parsing failed", e); } return false; } /* @see loci.formats.IFormatReader#getDomains() */ @Override public String[] getDomains() { FormatTools.assertId(currentId, true, 1); return hasSPW ? new String[] {FormatTools.HCS_DOMAIN} : FormatTools.NON_SPECIAL_DOMAINS; } /* @see loci.formats.IFormatReader#get8BitLookupTable() */ @Override public byte[][] get8BitLookupTable() throws FormatException, IOException { int series = getSeries(); if (info[series][lastPlane] == null || info[series][lastPlane].reader == null || info[series][lastPlane].id == null) { return null; } info[series][lastPlane].reader.setId(info[series][lastPlane].id); return info[series][lastPlane].reader.get8BitLookupTable(); } /* @see loci.formats.IFormatReader#get16BitLookupTable() */ @Override public short[][] get16BitLookupTable() throws FormatException, IOException { int series = getSeries(); if (info[series][lastPlane] == null || info[series][lastPlane].reader == null || info[series][lastPlane].id == null) { return null; } info[series][lastPlane].reader.setId(info[series][lastPlane].id); return info[series][lastPlane].reader.get16BitLookupTable(); } /* * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ @Override public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); int series = getSeries(); lastPlane = no; int i = info[series][no].ifd; if (!info[series][no].exists) { Arrays.fill(buf, (byte) 0); return buf; } MinimalTiffReader r = (MinimalTiffReader) info[series][no].reader; if (r.getCurrentFile() == null) { r.setId(info[series][no].id); } r.lastPlane = i; IFDList ifdList = r.getIFDs(); if (i >= ifdList.size()) { LOGGER.warn("Error untangling IFDs; the OME-TIFF file may be malformed (IFD #{} missing).", i); return buf; } IFD ifd = ifdList.get(i); RandomAccessInputStream s = new RandomAccessInputStream(info[series][no].id); TiffParser p = new TiffParser(s); p.getSamples(ifd, buf, x, y, w, h); s.close(); // reasonably safe to close the reader if the entire plane or // lower-right-most tile from a single plane file has been read if (r.getImageCount() == 1 && w + x == getSizeX() && h + y == getSizeY()) { r.close(); } return buf; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ @Override public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); int series = getSeries(); if (noPixels) return null; Vector<String> usedFiles = new Vector<String>(); if (metadataFile != null) { usedFiles.add(metadataFile); } if (info != null && info[series] != null) { for (int i=0; i<info[series].length; i++) { if (!usedFiles.contains(info[series][i].id)) { usedFiles.add(info[series][i].id); } } } return usedFiles.toArray(new String[usedFiles.size()]); } /* @see loci.formats.IFormatReader#fileGroupOption() */ @Override public int fileGroupOption(String id) { try { boolean single = isSingleFile(id); return single ? FormatTools.CAN_GROUP : FormatTools.MUST_GROUP; } catch (FormatException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } return FormatTools.CAN_GROUP; } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (info != null) { for (OMETiffPlane[] dimension : info) { for (OMETiffPlane plane : dimension) { if (plane.reader != null) { try { plane.reader.close(); } catch (Exception e) { LOGGER.error("Plane closure failure!", e); } } } } } if (!fileOnly) { info = null; used = null; lastPlane = 0; tileWidth = null; tileHeight = null; metadataFile = null; } } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ @Override public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); return tileWidth[getSeries()]; } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ @Override public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); return tileHeight[getSeries()]; } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { // normalize file name super.initFile(normalizeFilename(null, id)); id = currentId; String dir = new File(id).getParent(); // parse and populate OME-XML metadata String fileName = new Location(id).getAbsoluteFile().getAbsolutePath(); if (!new File(fileName).exists()) { fileName = currentId; } String xml; IFD firstIFD = null; boolean companion = false; if (checkSuffix(fileName, "companion.ome")) { xml = DataTools.readFile(fileName); companion = true; } else { RandomAccessInputStream ras = new RandomAccessInputStream(fileName, 16); try { TiffParser tp = new TiffParser(ras); firstIFD = tp.getFirstIFD(); xml = firstIFD.getComment(); } finally { ras.close(); } } if (service == null) setupService(); try { if (meta == null || !metaFile.equals(currentId)) { meta = service.createOMEXMLMetadata(xml); metaFile = currentId; } if (companion) { String firstTIFF = meta.getUUIDFileName(0, 0); initFile(new Location(dir, firstTIFF).getAbsolutePath()); return; } } catch (ServiceException se) { throw new FormatException(se); } String metadataPath = null; try { metadataPath = meta.getBinaryOnlyMetadataFile(); } catch (NullPointerException e) { } if (metadataPath != null) { // this is a binary-only file // overwrite XML with what is in the companion OME-XML file Location path = new Location(dir, metadataPath); if (path.exists()) { metadataFile = path.getAbsolutePath(); xml = readMetadataFile(); try { meta = service.createOMEXMLMetadata(xml); } catch (ServiceException se) { throw new FormatException(se); } catch (NullPointerException e) { metadataFile = null; metadataPath = null; } } } hasSPW = meta.getPlateCount() > 0; for (int i=0; i<meta.getImageCount(); i++) { int sizeC = meta.getPixelsSizeC(i).getValue().intValue(); service.removeChannels(meta, i, sizeC); } Hashtable originalMetadata = service.getOriginalMetadata(meta); if (originalMetadata != null) metadata = originalMetadata; LOGGER.trace(xml); if (meta.getRoot() == null) { throw new FormatException("Could not parse OME-XML from TIFF comment"); } String[] acquiredDates = new String[meta.getImageCount()]; for (int i=0; i<acquiredDates.length; i++) { Timestamp acquisitionDate = meta.getImageAcquisitionDate(i); if (acquisitionDate != null) { acquiredDates[i] = acquisitionDate.getValue(); } } String currentUUID = meta.getUUID(); service.convertMetadata(meta, metadataStore); // determine series count from Image and Pixels elements int seriesCount = meta.getImageCount(); core.clear(); for (int i=0; i<seriesCount; i++) { core.add(new CoreMetadata()); } info = new OMETiffPlane[seriesCount][]; tileWidth = new int[seriesCount]; tileHeight = new int[seriesCount]; // compile list of file/UUID mappings Hashtable<String, String> files = new Hashtable<String, String>(); boolean needSearch = false; for (int i=0; i<seriesCount; i++) { int tiffDataCount = meta.getTiffDataCount(i); for (int td=0; td<tiffDataCount; td++) { String uuid = null; try { uuid = meta.getUUIDValue(i, td); } catch (NullPointerException e) { } String filename = null; if (uuid == null) { // no UUID means that TiffData element refers to this file uuid = ""; filename = id; } else { filename = meta.getUUIDFileName(i, td); if (!new Location(dir, filename).exists()) filename = null; if (filename == null) { if (uuid.equals(currentUUID) || currentUUID == null) { // UUID references this file filename = id; } else { // will need to search for this UUID filename = ""; needSearch = true; } } else filename = normalizeFilename(dir, filename); } String existing = files.get(uuid); if (existing == null) files.put(uuid, filename); else if (!existing.equals(filename)) { throw new FormatException("Inconsistent UUID filenames"); } } } // search for missing filenames if (needSearch) { Enumeration en = files.keys(); while (en.hasMoreElements()) { String uuid = (String) en.nextElement(); String filename = files.get(uuid); if (filename.equals("")) { // TODO search... // should scan only other .ome.tif files // to make this work with OME server may be a little tricky? throw new FormatException("Unmatched UUID: " + uuid); } } } // build list of used files Enumeration en = files.keys(); int numUUIDs = files.size(); HashSet fileSet = new HashSet(); // ensure no duplicate filenames for (int i=0; i<numUUIDs; i++) { String uuid = (String) en.nextElement(); String filename = files.get(uuid); fileSet.add(filename); } used = new String[fileSet.size()]; Iterator iter = fileSet.iterator(); for (int i=0; i<used.length; i++) used[i] = (String) iter.next(); // process TiffData elements Hashtable<String, IFormatReader> readers = new Hashtable<String, IFormatReader>(); boolean adjustedSamples = false; for (int i=0; i<seriesCount; i++) { int s = i; LOGGER.debug("Image[{}] {", i); LOGGER.debug(" id = {}", meta.getImageID(i)); String order = meta.getPixelsDimensionOrder(i).toString(); PositiveInteger samplesPerPixel = null; if (meta.getChannelCount(i) > 0) { samplesPerPixel = meta.getChannelSamplesPerPixel(i, 0); } int samples = samplesPerPixel == null ? -1 : samplesPerPixel.getValue(); int tiffSamples = firstIFD.getSamplesPerPixel(); if (adjustedSamples || (samples != tiffSamples && (i == 0 || samples < 0))) { LOGGER.warn("SamplesPerPixel mismatch: OME={}, TIFF={}", samples, tiffSamples); samples = tiffSamples; adjustedSamples = true; } else { adjustedSamples = false; } if (adjustedSamples && meta.getChannelCount(i) <= 1) { adjustedSamples = false; } int effSizeC = meta.getPixelsSizeC(i).getValue().intValue(); if (!adjustedSamples) { effSizeC /= samples; } if (effSizeC == 0) effSizeC = 1; if (effSizeC * samples != meta.getPixelsSizeC(i).getValue().intValue()) { effSizeC = meta.getPixelsSizeC(i).getValue().intValue(); } int sizeT = meta.getPixelsSizeT(i).getValue().intValue(); int sizeZ = meta.getPixelsSizeZ(i).getValue().intValue(); int num = effSizeC * sizeT * sizeZ; OMETiffPlane[] planes = new OMETiffPlane[num]; for (int no=0; no<num; no++) planes[no] = new OMETiffPlane(); int tiffDataCount = meta.getTiffDataCount(i); Boolean zOneIndexed = null; Boolean cOneIndexed = null; Boolean tOneIndexed = null; // pre-scan TiffData indices to see if any of them are indexed from 1 for (int td=0; td<tiffDataCount; td++) { NonNegativeInteger firstC = meta.getTiffDataFirstC(i, td); NonNegativeInteger firstT = meta.getTiffDataFirstT(i, td); NonNegativeInteger firstZ = meta.getTiffDataFirstZ(i, td); int c = firstC == null ? 0 : firstC.getValue(); int t = firstT == null ? 0 : firstT.getValue(); int z = firstZ == null ? 0 : firstZ.getValue(); if (c >= effSizeC && cOneIndexed == null) { cOneIndexed = true; } else if (c == 0) { cOneIndexed = false; } if (z >= sizeZ && zOneIndexed == null) { zOneIndexed = true; } else if (z == 0) { zOneIndexed = false; } if (t >= sizeT && tOneIndexed == null) { tOneIndexed = true; } else if (t == 0) { tOneIndexed = false; } } for (int td=0; td<tiffDataCount; td++) { LOGGER.debug(" TiffData[{}] {", td); // extract TiffData parameters String filename = null; String uuid = null; try { filename = meta.getUUIDFileName(i, td); } catch (NullPointerException e) { LOGGER.debug("Ignoring null UUID object when retrieving filename."); } try { uuid = meta.getUUIDValue(i, td); } catch (NullPointerException e) { LOGGER.debug("Ignoring null UUID object when retrieving value."); } NonNegativeInteger tdIFD = meta.getTiffDataIFD(i, td); int ifd = tdIFD == null ? 0 : tdIFD.getValue(); NonNegativeInteger numPlanes = meta.getTiffDataPlaneCount(i, td); NonNegativeInteger firstC = meta.getTiffDataFirstC(i, td); NonNegativeInteger firstT = meta.getTiffDataFirstT(i, td); NonNegativeInteger firstZ = meta.getTiffDataFirstZ(i, td); int c = firstC == null ? 0 : firstC.getValue(); int t = firstT == null ? 0 : firstT.getValue(); int z = firstZ == null ? 0 : firstZ.getValue(); // NB: some writers index FirstC, FirstZ and FirstT from 1 if (cOneIndexed != null && cOneIndexed) c--; if (zOneIndexed != null && zOneIndexed) z--; if (tOneIndexed != null && tOneIndexed) t--; if (z >= sizeZ || c >= effSizeC || t >= sizeT) { LOGGER.warn("Found invalid TiffData: Z={}, C={}, T={}", new Object[] {z, c, t}); break; } int index = FormatTools.getIndex(order, sizeZ, effSizeC, sizeT, num, z, c, t); int count = numPlanes == null ? 1 : numPlanes.getValue(); if (count == 0) { core.set(s, null); break; } // get reader object for this filename if (filename == null) { if (uuid == null) filename = id; else filename = files.get(uuid); } else filename = normalizeFilename(dir, filename); IFormatReader r = readers.get(filename); if (r == null) { r = new MinimalTiffReader(); readers.put(filename, r); } Location file = new Location(filename); boolean exists = true; if (!file.exists()) { // if this is an absolute file name, try using a relative name // old versions of OMETiffWriter wrote an absolute path to // UUID.FileName, which causes problems if the file is moved to // a different directory filename = filename.substring(filename.lastIndexOf(File.separator) + 1); filename = dir + File.separator + filename; if (!new Location(filename).exists()) { filename = currentId; // if only one file is defined, we have to assume that it // corresponds to the current file exists = fileSet.size() == 1; } } // populate plane index -> IFD mapping for (int q=0; q<count; q++) { int no = index + q; planes[no].reader = r; planes[no].id = filename; planes[no].ifd = ifd + q; planes[no].certain = true; planes[no].exists = exists; LOGGER.debug(" Plane[{}]: file={}, IFD={}", new Object[] {no, planes[no].id, planes[no].ifd}); } if (numPlanes == null) { // unknown number of planes; fill down for (int no=index+1; no<num; no++) { if (planes[no].certain) break; planes[no].reader = r; planes[no].id = filename; planes[no].ifd = planes[no - 1].ifd + 1; planes[no].exists = exists; LOGGER.debug(" Plane[{}]: FILLED", no); } } else { // known number of planes; clear anything subsequently filled for (int no=index+count; no<num; no++) { if (planes[no].certain) break; planes[no].reader = null; planes[no].id = null; planes[no].ifd = -1; LOGGER.debug(" Plane[{}]: CLEARED", no); } } LOGGER.debug(" }"); } if (core.get(s) == null) continue; // verify that all planes are available LOGGER.debug(" --------------------------------"); for (int no=0; no<num; no++) { LOGGER.debug(" Plane[{}]: file={}, IFD={}", new Object[] {no, planes[no].id, planes[no].ifd}); if (planes[no].reader == null) { LOGGER.warn("Image ID '{}': missing plane #{}. " + "Using TiffReader to determine the number of planes.", meta.getImageID(i), no); TiffReader r = new TiffReader(); r.setId(currentId); try { planes = new OMETiffPlane[r.getImageCount()]; for (int plane=0; plane<planes.length; plane++) { planes[plane] = new OMETiffPlane(); planes[plane].id = currentId; planes[plane].reader = r; planes[plane].ifd = plane; } num = planes.length; } finally { r.close(); } } } LOGGER.debug(" }"); // populate core metadata CoreMetadata m = core.get(s); info[s] = planes; try { RandomAccessInputStream testFile = new RandomAccessInputStream(info[s][0].id); String firstFile = info[s][0].id; if (!info[s][0].reader.isThisType(testFile)) { LOGGER.warn("{} is not a valid OME-TIFF", info[s][0].id); info[s][0].id = currentId; info[s][0].exists = false; } testFile.close(); for (int plane=1; plane<info[s].length; plane++) { if (info[s][plane].id.equals(firstFile)) { // don't repeat slow type checking if the files are the same if (!info[s][0].exists) { info[s][plane].id = info[s][0].id; info[s][plane].exists = false; } continue; } testFile = new RandomAccessInputStream(info[s][plane].id); if (!info[s][plane].reader.isThisType(testFile)) { LOGGER.warn("{} is not a valid OME-TIFF", info[s][plane].id); info[s][plane].id = info[s][0].id; info[s][plane].exists = false; } testFile.close(); } info[s][0].reader.setId(info[s][0].id); tileWidth[s] = info[s][0].reader.getOptimalTileWidth(); tileHeight[s] = info[s][0].reader.getOptimalTileHeight(); m.sizeX = meta.getPixelsSizeX(i).getValue().intValue(); int tiffWidth = (int) firstIFD.getImageWidth(); if (m.sizeX != tiffWidth && s == 0) { LOGGER.warn("SizeX mismatch: OME={}, TIFF={}", m.sizeX, tiffWidth); } m.sizeY = meta.getPixelsSizeY(i).getValue().intValue(); int tiffHeight = (int) firstIFD.getImageLength(); if (m.sizeY != tiffHeight && s == 0) { LOGGER.warn("SizeY mismatch: OME={}, TIFF={}", m.sizeY, tiffHeight); } m.sizeZ = meta.getPixelsSizeZ(i).getValue().intValue(); m.sizeC = meta.getPixelsSizeC(i).getValue().intValue(); m.sizeT = meta.getPixelsSizeT(i).getValue().intValue(); m.pixelType = FormatTools.pixelTypeFromString( meta.getPixelsType(i).toString()); int tiffPixelType = firstIFD.getPixelType(); if (m.pixelType != tiffPixelType && (s == 0 || adjustedSamples)) { LOGGER.warn("PixelType mismatch: OME={}, TIFF={}", m.pixelType, tiffPixelType); m.pixelType = tiffPixelType; } m.imageCount = num; m.dimensionOrder = meta.getPixelsDimensionOrder(i).toString(); // hackish workaround for files exported by OMERO that have an // incorrect dimension order String uuidFileName = ""; try { if (meta.getTiffDataCount(i) > 0) { uuidFileName = meta.getUUIDFileName(i, 0); } } catch (NullPointerException e) { } if (meta.getChannelCount(i) > 0 && meta.getChannelName(i, 0) == null && meta.getTiffDataCount(i) > 0 && uuidFileName.indexOf("__omero_export") != -1) { m.dimensionOrder = "XYZCT"; } m.orderCertain = true; PhotoInterp photo = firstIFD.getPhotometricInterpretation(); m.rgb = samples > 1 || photo == PhotoInterp.RGB; if ((samples != m.sizeC && (samples % m.sizeC) != 0 && (m.sizeC % samples) != 0) || m.sizeC == 1 || adjustedSamples) { m.sizeC *= samples; } if (m.sizeZ * m.sizeT * m.sizeC > m.imageCount && !m.rgb) { if (m.sizeZ == m.imageCount) { m.sizeT = 1; m.sizeC = 1; } else if (m.sizeT == m.imageCount) { m.sizeZ = 1; m.sizeC = 1; } else if (m.sizeC == m.imageCount) { m.sizeT = 1; m.sizeZ = 1; } } if (meta.getPixelsBinDataCount(i) > 1) { LOGGER.warn("OME-TIFF Pixels element contains BinData elements! " + "Ignoring."); } m.littleEndian = firstIFD.isLittleEndian(); m.interleaved = false; m.indexed = photo == PhotoInterp.RGB_PALETTE && firstIFD.getIFDValue(IFD.COLOR_MAP) != null; if (m.indexed) { m.rgb = false; } m.falseColor = true; m.metadataComplete = true; if (meta.getPixelsSignificantBits(i) != null) { m.bitsPerPixel = meta.getPixelsSignificantBits(i).getValue(); } } catch (NullPointerException exc) { throw new FormatException("Incomplete Pixels metadata", exc); } } // remove null CoreMetadata entries ArrayList<CoreMetadata> series = new ArrayList<CoreMetadata>(); Vector<OMETiffPlane[]> planeInfo = new Vector<OMETiffPlane[]>(); for (int i=0; i<core.size(); i++) { if (core.get(i) != null) { series.add(core.get(i)); planeInfo.add(info[i]); } } core = series; info = planeInfo.toArray(new OMETiffPlane[0][0]); if (getImageCount() == 1) { CoreMetadata ms0 = core.get(0); ms0.sizeZ = 1; if (!ms0.rgb) { ms0.sizeC = 1; } ms0.sizeT = 1; } for (int i=0; i<core.size(); i++) { CoreMetadata m = core.get(i); Modulo z = service.getModuloAlongZ(meta, i); if (z != null) { m.moduloZ = z; } Modulo c = service.getModuloAlongC(meta, i); if (c != null) { m.moduloC = c; } Modulo t = service.getModuloAlongT(meta, i); if (t != null) { m.moduloT = t; } } MetadataTools.populatePixels(metadataStore, this, false, false); for (int i=0; i<acquiredDates.length; i++) { if (acquiredDates[i] != null) { metadataStore.setImageAcquisitionDate( new Timestamp(acquiredDates[i]), i); } } metadataStore = getMetadataStoreForConversion(); } // -- OMETiffReader API methods -- /** * Returns a MetadataStore that is populated in such a way as to * produce valid OME-XML. The returned MetadataStore cannot be used * by an IFormatWriter, as it will not contain the required * BinData.BigEndian attributes. */ public MetadataStore getMetadataStoreForDisplay() { MetadataStore store = getMetadataStore(); if (service.isOMEXMLMetadata(store)) { service.removeBinData((OMEXMLMetadata) store); for (int i=0; i<getSeriesCount(); i++) { if (((OMEXMLMetadata) store).getTiffDataCount(i) == 0) { service.addMetadataOnly((OMEXMLMetadata) store, i); } } } return store; } /** * Returns a MetadataStore that is populated in such a way as to be * usable by an IFormatWriter. Any OME-XML generated from this * MetadataStore is <em>very unlikely</em> to be valid, as more than * likely both BinData and TiffData element will be present. */ public MetadataStore getMetadataStoreForConversion() { MetadataStore store = getMetadataStore(); int realSeries = getSeries(); for (int i=0; i<getSeriesCount(); i++) { setSeries(i); store.setPixelsBinDataBigEndian(new Boolean(!isLittleEndian()), i, 0); } setSeries(realSeries); return store; } // -- Helper methods -- private String normalizeFilename(String dir, String name) { File file = new File(dir, name); if (file.exists()) return file.getAbsolutePath(); return name; } private void setupService() throws FormatException { try { ServiceFactory factory = new ServiceFactory(); service = factory.getInstance(OMEXMLService.class); } catch (DependencyException de) { throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de); } } /** Extracts the OME-XML from the current {@link #metadataFile}. */ private String readMetadataFile() throws IOException { if (checkSuffix(metadataFile, "tif") || checkSuffix(metadataFile, "tiff")) { // metadata file is an OME-TIFF file; extract OME-XML comment return new TiffParser(metadataFile).getComment(); } // assume metadata file is an XML file return DataTools.readFile(metadataFile); } // -- Helper classes -- /** Structure containing details on where to find a particular image plane. */ private class OMETiffPlane { /** Reader to use for accessing this plane. */ public IFormatReader reader; /** File containing this plane. */ public String id; /** IFD number of this plane. */ public int ifd = -1; /** Certainty flag, for dealing with unspecified NumPlanes. */ public boolean certain = false; /** * Whether or not the file meant to contain this plane exists. * The value of 'id' may be changed to allow the tile and image dimensions * to be populated; this flag indicates whether the originally recorded file * for this plane exists. */ public boolean exists = true; } }