/* * #%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; import java.io.IOException; import java.util.Arrays; import loci.common.DataTools; import loci.formats.meta.IMinMaxStore; /** * Logic to compute minimum and maximum values for each channel. */ public class MinMaxCalculator extends ReaderWrapper { // -- Utility methods -- /** Converts the given reader into a MinMaxCalculator, wrapping if needed. */ public static MinMaxCalculator makeMinMaxCalculator(IFormatReader r) { if (r instanceof MinMaxCalculator) return (MinMaxCalculator) r; return new MinMaxCalculator(r); } // -- Fields -- /** Min values for each channel. */ protected double[][] chanMin; /** Max values for each channel. */ protected double[][] chanMax; /** Min values for each plane. */ protected double[][] planeMin; /** Max values for each plane. */ protected double[][] planeMax; /** Number of planes for which min/max computations have been completed. */ protected int[] minMaxDone; /** Consumer of channel global minima and maxima */ protected IMinMaxStore minMaxStore; // -- Constructors -- /** Constructs a MinMaxCalculator around a new image reader. */ public MinMaxCalculator() { super(); } /** Constructs a MinMaxCalculator with the given reader. */ public MinMaxCalculator(IFormatReader r) { super(r); } /** * Sets the active min-max store for the calculator. Whenever a channel's * global minimum and maximum calculation has been completed this store is * notified. * @param store See above. */ public void setMinMaxStore(IMinMaxStore store) { minMaxStore = store; } /** * Retrieves the current active min-max store for the calculator. * @return See above. */ public IMinMaxStore getMinMaxStore() { return minMaxStore; } // -- MinMaxCalculator API methods -- /** * Retrieves a specified channel's global minimum. * Returns null if some of the image planes have not been read. * * @throws IOException Not actually thrown. */ public Double getChannelGlobalMinimum(int theC) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); if (theC < 0 || theC >= getSizeC()) { throw new FormatException("Invalid channel index: " + theC); } int series = getSeries(); // check that all planes have been read if (minMaxDone == null || minMaxDone[series] < getImageCount()) { return null; } return new Double(chanMin[series][theC]); } /** * Retrieves a specified channel's global maximum. * Returns null if some of the image planes have not been read. * @throws IOException Not actually thrown. */ public Double getChannelGlobalMaximum(int theC) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); if (theC < 0 || theC >= getSizeC()) { throw new FormatException("Invalid channel index: " + theC); } int series = getSeries(); // check that all planes have been read if (minMaxDone == null || minMaxDone[series] < getImageCount()) { return null; } return new Double(chanMax[series][theC]); } /** * Retrieves the specified channel's minimum based on the images that have * been read. Returns null if no image planes have been read yet. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ public Double getChannelKnownMinimum(int theC) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return chanMin == null ? null : new Double(chanMin[getSeries()][theC]); } /** * Retrieves the specified channel's maximum based on the images that * have been read. Returns null if no image planes have been read yet. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ public Double getChannelKnownMaximum(int theC) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return chanMax == null ? null : new Double(chanMax[getSeries()][theC]); } /** * Retrieves the minimum pixel value for the specified plane. * If each image plane contains more than one channel (i.e., * {@link #getRGBChannelCount()} > 1), returns the maximum value for each * embedded channel. Returns null if the plane has not already been read. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ public Double[] getPlaneMinimum(int no) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); if (planeMin == null) return null; int numRGB = getRGBChannelCount(); int pBase = no * numRGB; int series = getSeries(); if (Double.isNaN(planeMin[series][pBase])) return null; Double[] min = new Double[numRGB]; for (int c=0; c<numRGB; c++) { min[c] = new Double(planeMin[series][pBase + c]); } return min; } /** * Retrieves the maximum pixel value for the specified plane. * If each image plane contains more than one channel (i.e., * {@link #getRGBChannelCount()} > 1), returns the maximum value for each * embedded channel. Returns null if the plane has not already been read. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ public Double[] getPlaneMaximum(int no) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); if (planeMax == null) return null; int numRGB = getRGBChannelCount(); int pBase = no * numRGB; int series = getSeries(); if (Double.isNaN(planeMax[series][pBase])) return null; Double[] max = new Double[numRGB]; for (int c=0; c<numRGB; c++) { max[c] = new Double(planeMax[series][pBase + c]); } return max; } /** * Returns true if the values returned by * getChannelGlobalMinimum/Maximum can be trusted. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ public boolean isMinMaxPopulated() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return minMaxDone != null && minMaxDone[getSeries()] == getImageCount(); } // -- IFormatReader API methods -- /* @see IFormatReader#openBytes(int) */ @Override public byte[] openBytes(int no) throws FormatException, IOException { return openBytes(no, 0, 0, getSizeX(), getSizeY()); } /* @see IFormatReader#openBytes(int, byte[]) */ @Override public byte[] openBytes(int no, byte[] buf) throws FormatException, IOException { return openBytes(no, buf, 0, 0, getSizeX(), getSizeY()); } /* @see IFormatReader#openBytes(int, int, int, int, int) */ @Override public byte[] openBytes(int no, int x, int y, int w, int h) throws FormatException, IOException { byte[] buf = new byte[w * h * getRGBChannelCount() * FormatTools.getBytesPerPixel(getPixelType())]; return openBytes(no, buf, x, y, w, h); } /* @see 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.assertId(getCurrentFile(), true, 2); super.openBytes(no, buf, x, y, w, h); updateMinMax(no, buf, FormatTools.getBytesPerPixel(getPixelType()) * w * h); return buf; } /* @see IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { reader.close(fileOnly); if (!fileOnly) { chanMin = null; chanMax = null; planeMin = null; planeMax = null; minMaxDone = null; } } // -- IFormatHandler API methods -- /* @see IFormatHandler#getNativeDataType() */ @Override public Class<?> getNativeDataType() { return byte[].class; } // -- Helper methods -- /** * Updates min/max values based on the given byte array. * @param no the image index within the file. * @param buf a pre-allocated buffer. * @param len as <code>buf</code> may be larger than the actual pixel count * having been written to it, the length (in bytes) of the those pixels. */ protected void updateMinMax(int no, byte[] buf, int len) throws FormatException, IOException { if (buf == null) return; initMinMax(); int numRGB = getRGBChannelCount(); int series = getSeries(); int pixelType = getPixelType(); int bpp = FormatTools.getBytesPerPixel(pixelType); int planeSize = getSizeX() * getSizeY() * bpp; // check whether min/max values have already been computed for this plane // and that the buffer requested is actually the entire plane if (len == planeSize && !Double.isNaN(planeMin[series][no * numRGB])) return; boolean little = isLittleEndian(); int pixels = len / (bpp * numRGB); boolean interleaved = isInterleaved(); int[] coords = getZCTCoords(no); int cBase = coords[1] * numRGB; int pBase = no * numRGB; for (int c=0; c<numRGB; c++) { planeMin[series][pBase + c] = Double.POSITIVE_INFINITY; planeMax[series][pBase + c] = Double.NEGATIVE_INFINITY; } boolean signed = FormatTools.isSigned(pixelType); long threshold = (long) Math.pow(2, bpp * 8 - 1); for (int i=0; i<pixels; i++) { for (int c=0; c<numRGB; c++) { int idx = bpp * (interleaved ? i * numRGB + c : c * pixels + i); long bits = DataTools.bytesToLong(buf, idx, bpp, little); if (signed) { if (bits >= threshold) bits -= 2*threshold; } double v = bits; if (pixelType == FormatTools.FLOAT) { v = Float.intBitsToFloat((int) bits); } else if (pixelType == FormatTools.DOUBLE) { v = Double.longBitsToDouble(bits); } if (v > chanMax[series][cBase + c]) { chanMax[series][cBase + c] = v; } if (v < chanMin[series][cBase + c]) { chanMin[series][cBase + c] = v; } } } for (int c=0; c<numRGB; c++) { if (chanMin[series][cBase + c] < planeMin[series][pBase + c]) { planeMin[series][pBase + c] = chanMin[series][cBase + c]; } if (chanMax[series][cBase + c] > planeMax[series][pBase + c]) { planeMax[series][pBase + c] = chanMax[series][cBase + c]; } } minMaxDone[series] = Math.max(minMaxDone[series], no + 1); if (minMaxDone[getSeries()] == getImageCount() && minMaxStore != null) { for (int c=0; c<getSizeC(); c++) { minMaxStore.setChannelGlobalMinMax(c, chanMin[getSeries()][c], chanMax[getSeries()][c], getSeries()); } } } /** * Ensures internal min/max variables are initialized properly. * * @throws FormatException Not actually thrown. * @throws IOException Not actually thrown. */ protected void initMinMax() throws FormatException, IOException { int seriesCount = getSeriesCount(); int oldSeries = getSeries(); if (chanMin == null) { chanMin = new double[seriesCount][]; for (int i=0; i<seriesCount; i++) { setSeries(i); chanMin[i] = new double[getSizeC()]; Arrays.fill(chanMin[i], Double.POSITIVE_INFINITY); } setSeries(oldSeries); } if (chanMax == null) { chanMax = new double[seriesCount][]; for (int i=0; i<seriesCount; i++) { setSeries(i); chanMax[i] = new double[getSizeC()]; Arrays.fill(chanMax[i], Double.NEGATIVE_INFINITY); } setSeries(oldSeries); } if (planeMin == null) { planeMin = new double[seriesCount][]; for (int i=0; i<seriesCount; i++) { setSeries(i); int numRGB = getRGBChannelCount(); planeMin[i] = new double[getImageCount() * numRGB]; Arrays.fill(planeMin[i], Double.NaN); } setSeries(oldSeries); } if (planeMax == null) { planeMax = new double[seriesCount][]; for (int i=0; i<seriesCount; i++) { setSeries(i); int numRGB = getRGBChannelCount(); planeMax[i] = new double[getImageCount() * numRGB]; Arrays.fill(planeMax[i], Double.NaN); } setSeries(oldSeries); } if (minMaxDone == null) minMaxDone = new int[seriesCount]; } }