// // FormatReader.java // /* LOCI Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman and Brian Loranger. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library 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; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.IOException; import java.util.*; /** * Abstract superclass of all biological file format readers. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatReader.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatReader.java">SVN</a></dd></dl> */ public abstract class FormatReader extends FormatHandler implements IFormatReader { // -- Constants -- /** Default thumbnail width and height. */ protected static final int THUMBNAIL_DIMENSION = 128; // -- Fields -- /** Current file. */ protected RandomAccessStream in; /** Hashtable containing metadata key/value pairs. */ protected Hashtable metadata; /** The number of the current series. */ protected int series = 0; /** Core metadata values. */ protected CoreMetadata core; /** Whether or not to normalize float data. */ protected boolean normalizeData; /** Whether or not to filter out invalid metadata. */ protected boolean filterMetadata; /** Whether or not to collect metadata. */ protected boolean collectMetadata = true; /** Whether or not to save proprietary metadata in the MetadataStore. */ protected boolean saveOriginalMetadata = false; /** Whether or not to group multi-file formats. */ protected boolean group = true; /** * Current metadata store. Should <b>never</b> be accessed directly as the * semantics of {@link #getMetadataStore()} prevent "null" access. */ protected MetadataStore metadataStore = new DummyMetadata(); // -- Constructors -- /** Constructs a format reader with the given name and default suffix. */ public FormatReader(String format, String suffix) { super(format, suffix); } /** Constructs a format reader with the given name and default suffixes. */ public FormatReader(String format, String[] suffixes) { super(format, suffixes); } // -- Internal FormatReader API methods -- /** * Initializes the given file (parsing header information, etc.). * Most subclasses should override this method to perform * initialization operations such as parsing metadata. */ protected void initFile(String id) throws FormatException, IOException { if (currentId != null) { String[] s = getUsedFiles(); for (int i=0; i<s.length; i++) { if (id.equals(s[i])) return; } } series = 0; close(); currentId = id; metadata = new Hashtable(); core = new CoreMetadata(1); Arrays.fill(core.orderCertain, true); // reinitialize the MetadataStore getMetadataStore().createRoot(); } /** * Opens the given file, reads in the first few KB and calls * isThisType(byte[]) to check whether it matches this format. */ protected boolean checkBytes(String name, int maxLen) { try { RandomAccessStream ras = new RandomAccessStream(name); long len = ras.length(); byte[] buf = new byte[len < maxLen ? (int) len : maxLen]; ras.readFully(buf); ras.close(); return isThisType(buf); } catch (IOException exc) { return false; } } /** Returns true if the given file name is in the used files list. */ protected boolean isUsedFile(String file) { String[] usedFiles = getUsedFiles(); for (int i=0; i<usedFiles.length; i++) { if (usedFiles[i].equals(file) || usedFiles[i].equals(new Location(file).getAbsolutePath())) { return true; } } return false; } /** Adds an entry to the metadata table. */ protected void addMeta(String key, Object value) { if (key == null || value == null || !collectMetadata) return; if (filterMetadata) { // verify key & value are not empty if (key.length() == 0) return; String val = value.toString(); if (val.length() == 0) return; // verify key & value are reasonable length // int maxLen = 8192; // if (key.length() > maxLen) return; // if (val.length() > maxLen) return; // verify key & value start with printable characters if (key.charAt(0) < 32) return; if (val.charAt(0) < 32) return; // verify key contains at least one alphabetic character if (!key.matches(".*[a-zA-Z].*")) return; } if (saveOriginalMetadata) { MetadataStore store = getMetadataStore(); if (MetadataTools.isOMEXMLMetadata(store)) { MetadataTools.populateOriginalMetadata(store, key, value.toString()); } } metadata.put(key, value); } /** Gets a value from the metadata table. */ protected Object getMeta(String key) { return metadata.get(key); } // -- IFormatReader API methods -- /* @see IFormatReader#getImageCount() */ public int getImageCount() { FormatTools.assertId(currentId, true, 1); return core.imageCount[series]; } /* @see IFormatReader#isRGB() */ public boolean isRGB() { FormatTools.assertId(currentId, true, 1); return core.rgb[series]; } /* @see IFormatReader#getSizeX() */ public int getSizeX() { FormatTools.assertId(currentId, true, 1); return core.sizeX[series]; } /* @see IFormatReader#getSizeY() */ public int getSizeY() { FormatTools.assertId(currentId, true, 1); return core.sizeY[series]; } /* @see IFormatReader#getSizeZ() */ public int getSizeZ() { FormatTools.assertId(currentId, true, 1); return core.sizeZ[series]; } /* @see IFormatReader#getSizeC() */ public int getSizeC() { FormatTools.assertId(currentId, true, 1); return core.sizeC[series]; } /* @see IFormatReader#getSizeT() */ public int getSizeT() { FormatTools.assertId(currentId, true, 1); return core.sizeT[series]; } /* @see IFormatReader#getPixelType() */ public int getPixelType() { FormatTools.assertId(currentId, true, 1); return core.pixelType[series]; } /* @see IFormatReader#getEffectiveSizeC() */ public int getEffectiveSizeC() { // NB: by definition, imageCount == effectiveSizeC * sizeZ * sizeT return getImageCount() / (getSizeZ() * getSizeT()); } /* @see IFormatReader#getRGBChannelCount() */ public int getRGBChannelCount() { return getSizeC() / getEffectiveSizeC(); } /* @see IFormatReader#isIndexed() */ public boolean isIndexed() { FormatTools.assertId(currentId, true, 1); return core.indexed[series]; } /* @see IFormatReader#isFalseColor() */ public boolean isFalseColor() { FormatTools.assertId(currentId, true, 1); return core.falseColor[series]; } /* @see IFormatReader#get8BitLookupTable() */ public byte[][] get8BitLookupTable() throws FormatException, IOException { return null; } /* @see IFormatReader#get16BitLookupTable() */ public short[][] get16BitLookupTable() throws FormatException, IOException { return null; } /* @see IFormatReader#getChannelDimLengths() */ public int[] getChannelDimLengths() { FormatTools.assertId(currentId, true, 1); if (core.cLengths[series] == null) { core.cLengths[series] = new int[] {core.sizeC[series]}; } return core.cLengths[series]; } /* @see IFormatReader#getChannelDimTypes() */ public String[] getChannelDimTypes() { FormatTools.assertId(currentId, true, 1); if (core.cTypes[series] == null) { core.cTypes[series] = new String[] {FormatTools.CHANNEL}; } return core.cTypes[series]; } /* @see IFormatReader#getThumbSizeX() */ public int getThumbSizeX() { FormatTools.assertId(currentId, true, 1); if (core.thumbSizeX[series] == 0) { int sx = getSizeX(); int sy = getSizeY(); core.thumbSizeX[series] = sx > sy ? THUMBNAIL_DIMENSION : sx * THUMBNAIL_DIMENSION / sy; } return core.thumbSizeX[series]; } /* @see IFormatReader#getThumbSizeY() */ public int getThumbSizeY() { FormatTools.assertId(currentId, true, 1); if (core.thumbSizeY[series] == 0) { int sx = getSizeX(); int sy = getSizeY(); core.thumbSizeY[series] = sy > sx ? THUMBNAIL_DIMENSION : sy * THUMBNAIL_DIMENSION / sx; } return core.thumbSizeY[series]; } /* @see IFormatReader.isLittleEndian() */ public boolean isLittleEndian() { FormatTools.assertId(currentId, true, 1); return core.littleEndian[series]; } /* @see IFormatReader#getDimensionOrder() */ public String getDimensionOrder() { FormatTools.assertId(currentId, true, 1); return core.currentOrder[series]; } /* @see IFormatReader#isOrderCertain() */ public boolean isOrderCertain() { FormatTools.assertId(currentId, true, 1); return core.orderCertain[series]; } /* @see IFormatReader#isInterleaved() */ public boolean isInterleaved() { return isInterleaved(0); } /* @see IFormatReader#isInterleaved(int) */ public boolean isInterleaved(int subC) { FormatTools.assertId(currentId, true, 1); return core.interleaved[series]; } /* @see IFormatReader#openBytes(int) */ public byte[] openBytes(int no) throws FormatException, IOException { byte[] buf = new byte[getSizeX() * getSizeY() * getRGBChannelCount() * FormatTools.getBytesPerPixel(getPixelType())]; return openBytes(no, buf); } /* @see IFormatReader#openImage(int) */ public BufferedImage openImage(int no) throws FormatException, IOException { byte[] buf = openBytes(no); if (getPixelType() == FormatTools.FLOAT) { float[] f = (float[]) DataTools.makeDataArray(buf, 4, true, isLittleEndian()); if (normalizeData) f = DataTools.normalizeFloats(f); return ImageTools.makeImage(f, core.sizeX[series], core.sizeY[series], getRGBChannelCount(), true); } BufferedImage b = ImageTools.makeImage(buf, core.sizeX[series], core.sizeY[series], isIndexed() ? 1 : getRGBChannelCount(), core.interleaved[0], FormatTools.getBytesPerPixel(core.pixelType[series]), core.littleEndian[series]); if (isIndexed()) { IndexedColorModel model = null; if (core.pixelType[series] == FormatTools.UINT8 || core.pixelType[series] == FormatTools.INT8) { byte[][] table = get8BitLookupTable(); model = new IndexedColorModel(8, table[0].length, table); } else if (core.pixelType[series] == FormatTools.UINT16 || core.pixelType[series] == FormatTools.INT16) { short[][] table = get16BitLookupTable(); model = new IndexedColorModel(16, table[0].length, table); } if (model != null) { WritableRaster raster = Raster.createWritableRaster(b.getSampleModel(), b.getData().getDataBuffer(), null); b = new BufferedImage(model, raster, false, null); } } return b; } /* @see IFormatReader#openThumbImage(int) */ public BufferedImage openThumbImage(int no) throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); return ImageTools.scale(openImage(no), getThumbSizeX(), getThumbSizeY(), false); } /* @see IFormatReader#openThumbBytes(int) */ public byte[] openThumbBytes(int no) throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); BufferedImage img = openThumbImage(no); byte[][] bytes = ImageTools.getPixelBytes(img, core.littleEndian[series]); if (bytes.length == 1) return bytes[0]; byte[] rtn = new byte[getRGBChannelCount() * bytes[0].length]; for (int i=0; i<getRGBChannelCount(); i++) { System.arraycopy(bytes[i], 0, rtn, bytes[0].length * i, bytes[i].length); } return rtn; } /* @see IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { if (fileOnly) { if (in != null) in.close(); } else close(); } /* @see IFormatReader#getSeriesCount() */ public int getSeriesCount() { FormatTools.assertId(currentId, true, 1); return core.sizeX.length; } /* @see IFormatReader#setSeries(int) */ public void setSeries(int no) { if (no < 0 || no >= getSeriesCount()) { throw new IllegalArgumentException("Invalid series: " + no); } series = no; } /* @see IFormatReader#getSeries() */ public int getSeries() { return series; } /* @see IFormatReader#setGroupFiles(boolean) */ public void setGroupFiles(boolean groupFiles) { FormatTools.assertId(currentId, false, 1); group = groupFiles; } /* @see IFormatReader#isGroupFiles() */ public boolean isGroupFiles() { return group; } /* @see IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.CANNOT_GROUP; } /* @see IFormatReader#isMetadataComplete() */ public boolean isMetadataComplete() { FormatTools.assertId(currentId, true, 1); return core.metadataComplete[series]; } /* @see IFormatReader#setNormalized(boolean) */ public void setNormalized(boolean normalize) { FormatTools.assertId(currentId, false, 1); normalizeData = normalize; } /* @see IFormatReader#isNormalized() */ public boolean isNormalized() { return normalizeData; } /* @see IFormatReader#setMetadataCollected(boolean) */ public void setMetadataCollected(boolean collect) { FormatTools.assertId(currentId, false, 1); collectMetadata = collect; } /* @see IFormatReader#isMetadataCollected() */ public boolean isMetadataCollected() { return collectMetadata; } /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */ public void setOriginalMetadataPopulated(boolean populate) { FormatTools.assertId(currentId, false, 1); saveOriginalMetadata = populate; } /* @see IFormatReader#isOriginalMetadataPopulated() */ public boolean isOriginalMetadataPopulated() { return saveOriginalMetadata; } /* @see IFormatReader#getUsedFiles() */ public String[] getUsedFiles() { FormatTools.assertId(currentId, true, 1); return new String[] {currentId}; } /* @see IFormatReader#getCurrentFile() */ public String getCurrentFile() { return currentId == null ? "" : currentId; } /* @see IFormatReader#getIndex(int, int, int) */ public int getIndex(int z, int c, int t) { FormatTools.assertId(currentId, true, 1); return FormatTools.getIndex(this, z, c, t); } /* @see IFormatReader#getZCTCoords(int) */ public int[] getZCTCoords(int index) { FormatTools.assertId(currentId, true, 1); return FormatTools.getZCTCoords(this, index); } /* @see IFormatReader#getMetadataValue(String) */ public Object getMetadataValue(String field) { FormatTools.assertId(currentId, true, 1); return getMeta(field); } /* @see IFormatReader#getMetadata() */ public Hashtable getMetadata() { FormatTools.assertId(currentId, true, 1); return metadata; } /* @see IFormatReader#getCoreMetadata() */ public CoreMetadata getCoreMetadata() { FormatTools.assertId(currentId, true, 1); return core; } /* @see IFormatReader#setMetadataFiltered(boolean) */ public void setMetadataFiltered(boolean filter) { FormatTools.assertId(currentId, false, 1); filterMetadata = filter; } /* @see IFormatReader#isMetadataFiltered() */ public boolean isMetadataFiltered() { return filterMetadata; } /* @see IFormatReader#setMetadataStore(MetadataStore) */ public void setMetadataStore(MetadataStore store) { FormatTools.assertId(currentId, false, 1); if (store == null) { throw new IllegalArgumentException("Metadata object is null"); } metadataStore = store; } /* @see IFormatReader#getMetadataStore() */ public MetadataStore getMetadataStore() { return metadataStore; } /* @see IFormatReader#getMetadataStoreRoot() */ public Object getMetadataStoreRoot() { FormatTools.assertId(currentId, true, 1); return getMetadataStore().getRoot(); } // -- IFormatHandler API methods -- /* @see IFormatHandler#setId(String, boolean) */ public void setId(String id, boolean force) throws FormatException, IOException { if (!id.equals(currentId) || force) initFile(id); } /* @see IFormatHandler#close() */ public void close() throws IOException { if (in != null) in.close(); in = null; currentId = null; } // -- Deprecated IFormatReader API methods -- /** @deprecated Replaced by {@link #getImageCount()} */ public int getImageCount(String id) throws FormatException, IOException { setId(id); return getImageCount(); } /** @deprecated Replaced by {@link #isRGB()} */ public boolean isRGB(String id) throws FormatException, IOException { return getRGBChannelCount(id) > 1; } /** @deprecated Replaced by {@link #getSizeX()} */ public int getSizeX(String id) throws FormatException, IOException { setId(id); return core.sizeX[series]; } /** @deprecated Replaced by {@link #getSizeY()} */ public int getSizeY(String id) throws FormatException, IOException { setId(id); return core.sizeY[series]; } /** @deprecated Replaced by {@link #getSizeZ()} */ public int getSizeZ(String id) throws FormatException, IOException { setId(id); return core.sizeZ[series]; } /** @deprecated Replaced by {@link #getSizeC()} */ public int getSizeC(String id) throws FormatException, IOException { setId(id); return core.sizeC[series]; } /** @deprecated Replaced by {@link #getSizeT()} */ public int getSizeT(String id) throws FormatException, IOException { setId(id); return core.sizeT[series]; } /** @deprecated Replaced by {@link #getPixelType()} */ public int getPixelType(String id) throws FormatException, IOException { setId(id); return core.pixelType[series]; } /** @deprecated Replaced by {@link #getEffectiveSizeC()} */ public int getEffectiveSizeC(String id) throws FormatException, IOException { // NB: by definition, imageCount == effectiveSizeC * sizeZ * sizeT return getImageCount(id) / (getSizeZ(id) * getSizeT(id)); } /** @deprecated Replaced by {@link #getRGBChannelCount()} */ public int getRGBChannelCount(String id) throws FormatException, IOException { return getSizeC(id) / getEffectiveSizeC(id); } /** @deprecated Replaced by {@link #getChannelDimLengths()} */ public int[] getChannelDimLengths(String id) throws FormatException, IOException { setId(id); if (core.cLengths[series] == null) { core.cLengths[series] = new int[] {core.sizeC[series]}; } return core.cLengths[series]; } /** @deprecated Replaced by {@link #getChannelDimTypes()} */ public String[] getChannelDimTypes(String id) throws FormatException, IOException { setId(id); if (core.cTypes[series] == null) { core.cTypes[series] = new String[] {FormatTools.CHANNEL}; } return core.cTypes[series]; } /** @deprecated Replaced by {@link #getThumbSizeX()} */ public int getThumbSizeX(String id) throws FormatException, IOException { int sx = getSizeX(id); int sy = getSizeY(id); return sx > sy ? THUMBNAIL_DIMENSION : sx * THUMBNAIL_DIMENSION / sy; } /** @deprecated Replaced by {@link #getThumbSizeY()} */ public int getThumbSizeY(String id) throws FormatException, IOException { int sx = getSizeX(id); int sy = getSizeY(id); return sy > sx ? THUMBNAIL_DIMENSION : sy * THUMBNAIL_DIMENSION / sx; } /** @deprecated Replaced by {@link #isLittleEndian()} */ public boolean isLittleEndian(String id) throws FormatException, IOException { setId(id); return isLittleEndian(); } /** @deprecated Replaced by {@link #getDimensionOrder()} */ public String getDimensionOrder(String id) throws FormatException, IOException { setId(id); return core.currentOrder[series]; } /** @deprecated Replaced by {@link #isOrderCertain()} */ public boolean isOrderCertain(String id) throws FormatException, IOException { setId(id); return core.orderCertain[series]; } /** @deprecated Replaced by {@link #isInterleaved()} */ public boolean isInterleaved(String id) throws FormatException, IOException { return isInterleaved(id, 0); } /** @deprecated Replaced by {@link #isInterleaved(int)} */ public boolean isInterleaved(String id, int subC) throws FormatException, IOException { setId(id); return isInterleaved(subC); } /** @deprecated Replaced by {@link #openImage(int)} */ public BufferedImage openImage(String id, int no) throws FormatException, IOException { setId(id); return openImage(no); } /** @deprecated Replaced by {@link #openBytes(int)} */ public byte[] openBytes(String id, int no) throws FormatException, IOException { setId(id); return openBytes(no); } /** @deprecated Replaced by {@link #openBytes(int, byte[])} */ public byte[] openBytes(String id, int no, byte[] buf) throws FormatException, IOException { return openBytes(id, no); } /** @deprecated Replaced by {@link #openThumbImage(int)} */ public BufferedImage openThumbImage(String id, int no) throws FormatException, IOException { return ImageTools.scale(openImage(id, no), getThumbSizeX(id), getThumbSizeY(id), false); } /** @deprecated Replaced by {@link #openThumbBytes(int)} */ public byte[] openThumbBytes(String id, int no) throws FormatException, IOException { BufferedImage img = openThumbImage(id, no); byte[][] bytes = ImageTools.getBytes(img); if (bytes.length == 1) return bytes[0]; byte[] rtn = new byte[getRGBChannelCount(id) * bytes[0].length]; for (int i=0; i<getRGBChannelCount(id); i++) { System.arraycopy(bytes[i], 0, rtn, bytes[0].length * i, bytes[i].length); } return rtn; } /** @deprecated Replaced by {@link #getSeriesCount()} */ public int getSeriesCount(String id) throws FormatException, IOException { setId(id); return 1; } /** @deprecated Replaced by {@link #setSeries(int)} */ public void setSeries(String id, int no) throws FormatException, IOException { if (no < 0 || no >= getSeriesCount(id)) { throw new FormatException("Invalid series: " + no); } series = no; } /** @deprecated Replaced by {@link #getSeries()} */ public int getSeries(String id) throws FormatException, IOException { setId(id); return series; } /** @deprecated Replaced by {@link #getUsedFiles()} */ public String[] getUsedFiles(String id) throws FormatException, IOException { setId(id); return new String[] {id}; } /** @deprecated Replaced by {@link #getIndex(int, int, int)} */ public int getIndex(String id, int z, int c, int t) throws FormatException, IOException { setId(id); return FormatTools.getIndex(this, z, c, t); } /** @deprecated Replaced by {@link #getZCTCoords(int)} */ public int[] getZCTCoords(String id, int index) throws FormatException, IOException { setId(id); return FormatTools.getZCTCoords(this, index); } /** @deprecated Replaced by {@link #getMetadataValue(String)} */ public Object getMetadataValue(String id, String field) throws FormatException, IOException { setId(id); return getMeta(field); } /** @deprecated Replaced by {@link #getMetadata()} */ public Hashtable getMetadata(String id) throws FormatException, IOException { setId(id); return metadata; } /** @deprecated Replaced by {@link #getCoreMetadata()} */ public CoreMetadata getCoreMetadata(String id) throws FormatException, IOException { setId(id); return core; } /** @deprecated Replaced by {@link #getMetadataStore()} */ public MetadataStore getMetadataStore(String id) throws FormatException, IOException { setId(id); return metadataStore; } /** @deprecated Replaced by {@link #getMetadataStoreRoot()} */ public Object getMetadataStoreRoot(String id) throws FormatException, IOException { setId(id); return getMetadataStore().getRoot(); } }