// // MinMaxCalculator.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.*; import java.io.IOException; import java.util.*; /** * Logic to compute minimum and maximum values for each channel. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/MinMaxCalculator.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/MinMaxCalculator.java">SVN</a></dd></dl> */ public class MinMaxCalculator extends ReaderWrapper { // -- 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; // -- 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); } // -- MinMaxCalculator API methods -- /** * Retrieves a specified channel's global minimum. * Returns null if some of the image planes have not been read. */ 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 reade 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. */ 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 reade 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. */ 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. */ 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(String)}), returns the maximum value for each * embedded channel. Returns null if the plane has not already been read. */ 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 (planeMin[series][pBase] != 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(String)}), returns the maximum value for each * embedded channel. Returns null if the plane has not already been read. */ 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 (planeMax[series][pBase] != 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. */ public boolean isMinMaxPopulated() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return minMaxDone != null && minMaxDone[getSeries()] == getImageCount(); } // -- IFormatReader API methods -- /* @see IFormatReader#openImage(int) */ public BufferedImage openImage(int no) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); BufferedImage b = super.openImage(no); updateMinMax(b, no); return b; } /* @see IFormatReader#openBytes(int) */ public byte[] openBytes(int no) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); byte[] b = super.openBytes(no); updateMinMax(b, no); return b; } /* @see IFormatReader#openBytes(int, byte[]) */ public byte[] openBytes(int no, byte[] buf) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); super.openBytes(no, buf); updateMinMax(buf, no); return buf; } /* @see IFormatReader#close() */ public void close() throws IOException { reader.close(); chanMin = null; chanMax = null; planeMin = null; planeMax = null; minMaxDone = null; } // -- Helper methods -- /** Updates min/max values based on the given BufferedImage. */ protected void updateMinMax(BufferedImage b, int ndx) throws FormatException, IOException { if (b == null) return; initMinMax(); int numRGB = getRGBChannelCount(); int series = getSeries(); // check whether min/max values have already been computed for this plane if (planeMin[series][ndx * numRGB] == planeMin[series][ndx * numRGB]) { return; } int[] coords = getZCTCoords(ndx); int cBase = coords[1] * numRGB; int pBase = ndx * numRGB; for (int c=0; c<numRGB; c++) { planeMin[series][pBase + c] = Double.POSITIVE_INFINITY; planeMax[series][pBase + c] = Double.NEGATIVE_INFINITY; } WritableRaster pixels = b.getRaster(); for (int x=0; x<b.getWidth(); x++) { for (int y=0; y<b.getHeight(); y++) { for (int c=0; c<numRGB; c++) { double v = pixels.getSampleDouble(x, y, c); if (v > chanMax[series][cBase + c]) { chanMax[series][cBase + c] = v; } if (v < chanMin[series][cBase + c]) { chanMin[series][cBase + c] = v; } if (v > planeMax[series][pBase + c]) { planeMax[series][pBase + c] = v; } if (v < planeMin[series][pBase + c]) { planeMin[series][pBase + c] = v; } } } } minMaxDone[series]++; if (minMaxDone[series] == getImageCount()) { MetadataStore store = getMetadataStore(); for (int c=0; c<getSizeC(); c++) { store.setChannelGlobalMinMax(c, new Double(chanMin[series][c]), new Double(chanMax[series][c]), new Integer(getSeries())); } } } /** Updates min/max values based on the given byte array. */ protected void updateMinMax(byte[] b, int ndx) throws FormatException, IOException { if (b == null) return; initMinMax(); int numRGB = getRGBChannelCount(); int series = getSeries(); // check whether min/max values have already been computed for this plane if (planeMin[series][ndx * numRGB] == planeMin[series][ndx * numRGB]) { return; } boolean little = isLittleEndian(); int bytes = FormatTools.getBytesPerPixel(getPixelType()); int pixels = getSizeX() * getSizeY(); boolean interleaved = isInterleaved(); int[] coords = getZCTCoords(ndx); int cBase = coords[1] * numRGB; int pBase = ndx * numRGB; for (int c=0; c<numRGB; c++) { planeMin[series][pBase + c] = Double.POSITIVE_INFINITY; planeMax[series][pBase + c] = Double.NEGATIVE_INFINITY; } boolean fp = getPixelType() == FormatTools.FLOAT || getPixelType() == FormatTools.DOUBLE; for (int i=0; i<pixels; i++) { for (int c=0; c<numRGB; c++) { int idx = bytes * (interleaved ? i * numRGB + c : c * pixels + i); long bits = DataTools.bytesToLong(b, idx, bytes, little); double v = fp ? Double.longBitsToDouble(bits) : (double) bits; if (v > chanMax[series][cBase + c]) { chanMax[series][cBase + c] = v; } if (v < chanMin[series][cBase + c]) { chanMin[series][cBase + c] = v; } if (v > planeMax[series][ndx]) { planeMax[series][ndx] = v; } if (v < planeMin[series][pBase + c]) { planeMin[series][pBase + c] = v; } } } minMaxDone[series]++; if (minMaxDone[getSeries()] == getImageCount()) { MetadataStore store = getMetadataStore(); for (int c=0; c<getSizeC(); c++) { store.setChannelGlobalMinMax(c, new Double(chanMin[getSeries()][c]), new Double(chanMax[getSeries()][c]), new Integer(getSeries())); } } } /** Ensures internal min/max variables are initialized properly. */ 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]; } // -- Deprecated IFormatReader API methods -- /** @deprecated Replaced by {@link #openImage(int)} */ public BufferedImage openImage(String id, int no) throws FormatException, IOException { setId(id); BufferedImage b = super.openImage(no); updateMinMax(b, no); return b; } /** @deprecated Replaced by {@link #openBytes(int)} */ public byte[] openBytes(String id, int no) throws FormatException, IOException { setId(id); byte[] b = super.openBytes(no); updateMinMax(b, no); return b; } /** @deprecated Replaced by {@link #openBytes(int, byte[])} */ public byte[] openBytes(String id, int no, byte[] buf) throws FormatException, IOException { setId(id); super.openBytes(no, buf); updateMinMax(buf, no); return buf; } }