// // NDPIReader.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.IOException; import loci.common.DateTools; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.codec.JPEGTileDecoder; import loci.formats.meta.MetadataStore; import loci.formats.tiff.IFD; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.PositiveFloat; /** * NDPIReader is the file format reader for Hamamatsu .ndpi 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/NDPIReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/NDPIReader.java;hb=HEAD">Gitweb</a></dd></dl> */ public class NDPIReader extends BaseTiffReader { // -- Constants -- private static final int MAX_SIZE = 8192; private static final int THUMB_TAG_1 = 65426; private static final int THUMB_TAG_2 = 65439; private static final int METADATA_TAG = 65449; // -- Fields -- private JPEGTileDecoder decoder; private int initializedSeries = -1; private int initializedPlane = -1; private int sizeZ = 1; private int pyramidHeight = 1; // -- Constructor -- /** Constructs a new NDPI reader. */ public NDPIReader() { super("Hamamatsu NDPI", new String[] {"ndpi"}); domains = new String[] {FormatTools.HISTOLOGY_DOMAIN}; suffixNecessary = true; } // -- IFormatReader API methods -- /** @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return 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); if (x == 0 && y == 0 && w == 1 && h == 1) { return buf; } else if (getSizeX() <= MAX_SIZE && getSizeY() <= MAX_SIZE) { int ifdIndex = getIFDIndex(getSeries(), no); in = new RandomAccessInputStream(currentId); tiffParser = new TiffParser(in); tiffParser.setUse64BitOffsets(true); return tiffParser.getSamples(ifds.get(ifdIndex), buf, x, y, w, h); } if (initializedSeries != getSeries() || initializedPlane != no) { if (x == 0 && y == 0 && w == getOptimalTileWidth() && h == getOptimalTileHeight()) { // it looks like we'll be reading lots of tiles setupService(0, 0, no); } else { // it looks like we'll only read one tile setupService(y, h, no); } initializedSeries = getSeries(); initializedPlane = no; } else if (decoder.getScanline(y) == null) { setupService(y, h, no); } int c = getRGBChannelCount(); int bytes = FormatTools.getBytesPerPixel(getPixelType()); int row = w * c * bytes; for (int yy=y; yy<y + h; yy++) { byte[] scanline = decoder.getScanline(yy); if (scanline != null) { int copy = (int) Math.min(row, buf.length - (yy - y) * row - 1); if (copy < 0) break; System.arraycopy(scanline, x * c * bytes, buf, (yy - y) * row, copy); } } return buf; } /* @see loci.formats.IFormatReader#openThumbBytes(int) */ public byte[] openThumbBytes(int no) throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); int currentSeries = getSeries(); if (currentSeries >= pyramidHeight) { return super.openThumbBytes(no); } int thumbX = getThumbSizeX(); int thumbY = getThumbSizeY(); int rgbCount = getRGBChannelCount(); setSeries(pyramidHeight - 1); byte[] thumb = null; if (thumbX == getThumbSizeX() && thumbY == getThumbSizeY() && rgbCount == getRGBChannelCount()) { thumb = FormatTools.openThumbBytes(this, no); setSeries(currentSeries); } else { // find the smallest series with the same aspect ratio for (int s=getSeriesCount()-1; s>=0; s--) { setSeries(s); if (thumbX == getThumbSizeX() && thumbY == getThumbSizeY() && s != currentSeries && rgbCount == getRGBChannelCount()) { thumb = FormatTools.openThumbBytes(this, no); break; } } setSeries(currentSeries); if (thumb == null) { thumb = FormatTools.openThumbBytes(this, no); } } return thumb; } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { if (decoder != null) { decoder.close(); } decoder = null; initializedSeries = -1; initializedPlane = -1; sizeZ = 1; pyramidHeight = 1; } } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); int bpp = FormatTools.getBytesPerPixel(getPixelType()); int maxHeight = (1024 * 1024) / (getSizeX() * getRGBChannelCount() * bpp); return (int) Math.min(maxHeight, getSizeY()); } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { RandomAccessInputStream s = new RandomAccessInputStream(id); use64Bit = s.length() >= Math.pow(2, 32); s.close(); super.initFile(id); } // -- Internal BaseTiffReader API methods -- /* @see loci.formats.BaseTiffReader#initStandardMetadata() */ protected void initStandardMetadata() throws FormatException, IOException { super.initStandardMetadata(); decoder = new JPEGTileDecoder(); ifds = tiffParser.getIFDs(); // fix the offsets for > 4 GB files RandomAccessInputStream stream = new RandomAccessInputStream(currentId); for (int i=0; i<ifds.size(); i++) { long[] stripOffsets = ifds.get(i).getStripOffsets(); boolean neededAdjustment = false; for (int j=0; j<stripOffsets.length; j++) { long newOffset = stripOffsets[j] + 0x100000000L; if (newOffset < stream.length()) { stripOffsets[j] = newOffset; neededAdjustment = true; } } if (neededAdjustment) { ifds.get(i).putIFDValue(IFD.STRIP_OFFSETS, stripOffsets); } } stream.close(); for (int i=1; i<ifds.size(); i++) { IFD ifd = ifds.get(i); if (ifd.getImageWidth() == ifds.get(0).getImageWidth() && ifd.getImageLength() == ifds.get(0).getImageLength()) { sizeZ++; } else if (sizeZ == 1) { pyramidHeight++; } } int seriesCount = pyramidHeight + (ifds.size() - pyramidHeight * sizeZ); core = new CoreMetadata[seriesCount]; // repopulate core metadata for (int i=0; i<ifds.size(); i++) { IFD ifd = ifds.get(i); ifd.remove(THUMB_TAG_1); ifd.remove(THUMB_TAG_2); ifds.set(i, ifd); tiffParser.fillInIFD(ifds.get(i)); } for (int s=0; s<core.length; s++) { setSeries(s); core[s] = new CoreMetadata(); IFD ifd = ifds.get(getIFDIndex(s, 0)); PhotoInterp p = ifd.getPhotometricInterpretation(); int samples = ifd.getSamplesPerPixel(); core[s].rgb = samples > 1 || p == PhotoInterp.RGB; core[s].sizeX = (int) ifd.getImageWidth(); core[s].sizeY = (int) ifd.getImageLength(); core[s].sizeZ = s < pyramidHeight ? sizeZ : 1; core[s].sizeT = 1; core[s].sizeC = core[s].rgb ? samples : 1; core[s].littleEndian = ifd.isLittleEndian(); core[s].indexed = p == PhotoInterp.RGB_PALETTE && (get8BitLookupTable() != null || get16BitLookupTable() != null); core[s].imageCount = getSizeZ() * getSizeT(); core[s].pixelType = ifd.getPixelType(); core[s].metadataComplete = true; core[s].interleaved = getSizeX() > MAX_SIZE || getSizeY() > MAX_SIZE; core[s].falseColor = false; core[s].dimensionOrder = "XYCZT"; core[s].thumbnail = s != 0; } setSeries(0); } /* @see loci.formats.BaseTiffReader#initMetadataStore() */ protected void initMetadataStore() throws FormatException { super.initMetadataStore(); MetadataStore store = makeFilterMetadata(); for (int i=0; i<getSeriesCount(); i++) { store.setImageName("Series " + (i + 1), i); if (i > 0) { int ifdIndex = getIFDIndex(i, 0); String creationDate = ifds.get(ifdIndex).getIFDTextValue(IFD.DATE_TIME); creationDate = DateTools.formatDate(creationDate, DATE_FORMATS); store.setImageAcquiredDate(creationDate, i); double xResolution = ifds.get(ifdIndex).getXResolution(); double yResolution = ifds.get(ifdIndex).getYResolution(); if (xResolution > 0) { store.setPixelsPhysicalSizeX(new PositiveFloat(xResolution), i); } if (yResolution > 0) { store.setPixelsPhysicalSizeY(new PositiveFloat(yResolution), i); } store.setPixelsPhysicalSizeZ(null, i); } } } // -- Helper methods -- private void setupService(int y, int h, int z) throws FormatException, IOException { decoder.close(); IFD ifd = ifds.get(getIFDIndex(getSeries(), z)); long offset = ifd.getStripOffsets()[0]; int byteCount = (int) ifd.getStripByteCounts()[0]; if (in != null) { in.close(); } in = new RandomAccessInputStream(currentId); in.seek(offset); in.setLength(offset + byteCount); decoder.initialize(in, y, h, getSizeX()); } private int getIFDIndex(int seriesIndex, int zIndex) { if (seriesIndex < pyramidHeight) { return zIndex * pyramidHeight + seriesIndex; } return sizeZ * pyramidHeight + (seriesIndex - pyramidHeight); } }