//
// MinMaxCalculator.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;
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.
*
* <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/MinMaxCalculator.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/MinMaxCalculator.java;hb=HEAD">Gitweb</a></dd></dl>
*/
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) */
public byte[] openBytes(int no) throws FormatException, IOException {
return openBytes(no, 0, 0, getSizeX(), getSizeY());
}
/* @see IFormatReader#openBytes(int, byte[]) */
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) */
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) */
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) */
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() */
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];
}
}