/* * #%L * OME Bio-Formats package for reading and converting biological file formats. * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package loci.formats.in; import java.io.IOException; import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import loci.common.Constants; import loci.common.DateTools; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.meta.MetadataStore; import loci.formats.tiff.IFD; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffIFDEntry; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.PositiveFloat; import ome.xml.model.primitives.PositiveInteger; import ome.xml.model.primitives.Timestamp; import ome.units.quantity.Length; import ome.units.quantity.Time; import ome.units.UNITS; /** * SVSReader is the file format reader for Aperio SVS TIFF files. */ public class SVSReader extends BaseTiffReader { // -- Constants -- /** Logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(SVSReader.class); /** TIFF image description prefix for Aperio SVS files. */ public static final String APERIO_IMAGE_DESCRIPTION_PREFIX = "Aperio Image"; private static final String DATE_FORMAT = "MM/dd/yy HH:mm:ss"; // -- Fields -- private Length[] pixelSize; private String[] comments; private int[] ifdmap; private Double emissionWavelength, excitationWavelength; private Double exposureTime, exposureScale; private Double magnification; private String date, time; // -- Constructor -- /** Constructs a new SVS reader. */ public SVSReader() { super("Aperio SVS", new String[] {"svs"}); domains = new String[] {FormatTools.HISTOLOGY_DOMAIN}; suffixNecessary = true; noSubresolutions = true; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#fileGroupOption(String) */ @Override public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* (non-Javadoc) * @see loci.formats.FormatReader#isThisType(java.lang.String, boolean) */ @Override public boolean isThisType(String name, boolean open) { boolean isThisType = super.isThisType(name, open); if (!isThisType && open) { RandomAccessInputStream stream = null; try { stream = new RandomAccessInputStream(name); TiffParser tiffParser = new TiffParser(stream); tiffParser.setDoCaching(false); if (!tiffParser.isValidHeader()) { return false; } IFD ifd = tiffParser.getFirstIFD(); if (ifd == null) { return false; } Object description = ifd.get(IFD.IMAGE_DESCRIPTION); if (description != null) { String imageDescription = null; if (description instanceof TiffIFDEntry) { Object value = tiffParser.getIFDValue((TiffIFDEntry) description); if (value != null) { imageDescription = value.toString(); } } else if (description instanceof String) { imageDescription = (String) description; } if (imageDescription != null && imageDescription.startsWith(APERIO_IMAGE_DESCRIPTION_PREFIX)) { return true; } } return false; } catch (IOException e) { LOGGER.debug("I/O exception during isThisType() evaluation.", e); return false; } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) { LOGGER.debug("I/O exception during stream closure.", e); } } } return isThisType; } /** * @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 { if (core.size() == 1) { return super.openBytes(no, buf, x, y, w, h); } FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); if (tiffParser == null) { initTiffParser(); } int ifd = ifdmap[getCoreIndex()]; tiffParser.getSamples(ifds.get(ifd), buf, x, y, w, h); return buf; } /* @see loci.formats.IFormatReader#openThumbBytes(int) */ @Override public byte[] openThumbBytes(int no) throws FormatException, IOException { if (core.size() == 1 || getSeries() >= getSeriesCount() - 2) { return super.openThumbBytes(no); } int smallestSeries = getSeriesCount() - 3; if (smallestSeries >= 0) { int thisSeries = getSeries(); int resolution = getResolution(); setSeries(smallestSeries); if (!hasFlattenedResolutions()) { setResolution(1); } byte[] thumb = FormatTools.openThumbBytes(this, no); setSeries(thisSeries); setResolution(resolution); return thumb; } return super.openThumbBytes(no); } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { pixelSize = null; comments = null; ifdmap = null; emissionWavelength = null; excitationWavelength = null; exposureTime = null; exposureScale = null; magnification = null; date = null; time = null; } } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ @Override public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); try { int ifd = ifdmap[getCoreIndex()]; return (int) ifds.get(ifd).getTileWidth(); } catch (FormatException e) { LOGGER.debug("", e); } return super.getOptimalTileWidth(); } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ @Override public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); try { int ifd = ifdmap[getCoreIndex()]; return (int) ifds.get(ifd).getTileLength(); } catch (FormatException e) { LOGGER.debug("", e); } return super.getOptimalTileHeight(); } // -- Internal BaseTiffReader API methods -- /* @see loci.formats.BaseTiffReader#initStandardMetadata() */ @Override protected void initStandardMetadata() throws FormatException, IOException { super.initStandardMetadata(); ifds = tiffParser.getIFDs(); int seriesCount = ifds.size(); pixelSize = new Length[seriesCount]; comments = new String[seriesCount]; core.clear(); for (int i=0; i<seriesCount; i++) { core.add(new CoreMetadata()); } for (int i=0; i<seriesCount; i++) { setSeries(i); int index = getIFDIndex(i); tiffParser.fillInIFD(ifds.get(index)); if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { String comment = ifds.get(index).getComment(); if (comment == null) { continue; } String[] lines = comment.split("\n"); String[] tokens; String key, value; for (String line : lines) { tokens = line.split("[|]"); for (String t : tokens) { if (t.indexOf("=") == -1) { addGlobalMeta("Comment", t); comments[i] = t; } else { key = t.substring(0, t.indexOf("=")).trim(); value = t.substring(t.indexOf("=") + 1).trim(); addSeriesMeta(key, value); if (key.equals("MPP")) { pixelSize[i] = FormatTools.getPhysicalSizeX(Double.parseDouble(value)); } else if (key.equals("Date")) { date = value; } else if (key.equals("Time")) { time = value; } else if (key.equals("Emission Wavelength")) { emissionWavelength = new Double(value); } else if (key.equals("Excitation Wavelength")) { excitationWavelength = new Double(value); } else if (key.equals("Exposure Time")) { exposureTime = new Double(value); } else if (key.equals("Exposure Scale")) { exposureScale = new Double(value); } else if (key.equals("AppMag")) { magnification = new Double(value); } } } } } } setSeries(0); // repopulate core metadata for (int s=0; s<seriesCount; s++) { CoreMetadata ms = core.get(s); if (s == 0 && getSeriesCount() > 2) { ms.resolutionCount = getSeriesCount() - 2; } IFD ifd = ifds.get(getIFDIndex(s)); PhotoInterp p = ifd.getPhotometricInterpretation(); int samples = ifd.getSamplesPerPixel(); ms.rgb = samples > 1 || p == PhotoInterp.RGB; ms.sizeX = (int) ifd.getImageWidth(); ms.sizeY = (int) ifd.getImageLength(); ms.sizeZ = 1; ms.sizeT = 1; ms.sizeC = ms.rgb ? samples : 1; ms.littleEndian = ifd.isLittleEndian(); ms.indexed = p == PhotoInterp.RGB_PALETTE && (get8BitLookupTable() != null || get16BitLookupTable() != null); ms.imageCount = 1; ms.pixelType = ifd.getPixelType(); ms.metadataComplete = true; ms.interleaved = false; ms.falseColor = false; ms.dimensionOrder = "XYCZT"; ms.thumbnail = s != 0; } reorderResolutions(); } /* @see loci.formats.BaseTiffReader#initMetadataStore() */ @Override protected void initMetadataStore() throws FormatException { super.initMetadataStore(); MetadataStore store = makeFilterMetadata(); String instrument = MetadataTools.createLSID("Instrument", 0); String objective = MetadataTools.createLSID("Objective", 0, 0); store.setInstrumentID(instrument, 0); store.setObjectiveID(objective, 0, 0); store.setObjectiveNominalMagnification(magnification, 0, 0); for (int i=0; i<getSeriesCount(); i++) { store.setImageInstrumentRef(instrument, i); store.setObjectiveSettingsID(objective, i); store.setImageName("Series " + (i + 1), i); store.setImageDescription(comments[i], i); if (getDatestamp() != null) { store.setImageAcquisitionDate(getDatestamp(), i); } for (int c=0; c<getEffectiveSizeC(); c++) { if (getEmission() != null) { store.setChannelEmissionWavelength(getEmission(), i, c); } if (getExcitation() != null) { store.setChannelExcitationWavelength(getExcitation(), i, c); } } if (i < pixelSize.length && pixelSize[i] != null && pixelSize[i].value(UNITS.MICROM).doubleValue() - Constants.EPSILON > 0) { store.setPixelsPhysicalSizeX(pixelSize[i], i); store.setPixelsPhysicalSizeY(pixelSize[i], i); } } } private int getIFDIndex(int coreIndex) { int index = coreIndex; if (coreIndex > 0 && coreIndex < core.size() - 2) { index = core.size() - 2 - coreIndex; } return index; } /** * Validate the order of resolutions for the current series, in * decending order of size. If the order is wrong, reorder it. */ protected void reorderResolutions() { ifdmap = new int[core.size()]; for (int i = 0; i < core.size();) { int resolutions = core.get(i).resolutionCount; List<CoreMetadata> savedCore = new ArrayList<CoreMetadata>(); int savedIFDs[] = new int[resolutions]; HashMap<Integer,Integer> levels = new HashMap<Integer,Integer>(); for (int c = 0; c < resolutions; c++) { savedCore.add(core.get(i + c)); savedIFDs[c] = getIFDIndex(i+c); levels.put(savedCore.get(c).sizeX, c); } Integer[] keys = levels.keySet().toArray(new Integer[resolutions]); Arrays.sort(keys); for (int j = 0; j < resolutions; j++) { core.set(i + j, savedCore.get(levels.get(keys[resolutions - j - 1]))); ifdmap[i + j] = savedIFDs[levels.get(keys[resolutions - j - 1])]; if (j == 0) core.get(i + j).resolutionCount = resolutions; else core.get(i + j).resolutionCount = 1; } i += core.get(i).resolutionCount; } } protected Length getEmission() { if (emissionWavelength != null && emissionWavelength > 0) { return FormatTools.getEmissionWavelength(emissionWavelength); } return null; } protected Length getExcitation() { if (excitationWavelength != null && excitationWavelength > 0) { return FormatTools.getExcitationWavelength(excitationWavelength); } return null; } protected Double getExposureTime() { return exposureTime; } protected Timestamp getDatestamp() { if (date != null && time != null) { try { return new Timestamp( DateTools.formatDate(date + " " + time, DATE_FORMAT)); } catch (Exception e) { LOGGER.debug("Failed to parse '" + date + " " + time + "'", e); } } return null; } protected Length[] getPhysicalSizes() { return pixelSize; } protected double getMagnification() { return magnification; } }