/*
* omeis.providers.re.metadata.StatsFactory
*
* Copyright 2006-2015 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package omeis.providers.re.metadata;
import loci.formats.FormatTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ome.io.nio.PixelBuffer;
import ome.model.core.Channel;
import ome.model.core.Pixels;
import ome.model.enums.PixelsType;
import ome.model.stats.StatsInfo;
import omeis.providers.re.data.Plane2D;
import omeis.providers.re.data.PlaneDef;
import omeis.providers.re.quantum.QuantumStrategy;
/**
* Computes two types of statistics: The PixelsStats and the location stats. The
* location stats determine the location of the pixels' values in order to set
* the inputWindow and the noiseReduction flag. This flag will then be used when
* we map the pixels intensity values onto the device space.
*
* @author Jean-Marie Burel <a
* href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author <br>
* Andrea Falconi <a
* href="mailto:a.falconi@dundee.ac.uk"> a.falconi@dundee.ac.uk</a>
* @since OME2.2
*/
public class StatsFactory {
/** The logger for this particular class */
private static Logger log = LoggerFactory.getLogger(StatsFactory.class);
/** The minimum range. */
private static final int RANGE_RGB = 255;
/** The number of bins. */
private static final int NB_BIN = 2 * QuantumStrategy.DECILE;
/** The default size of a bin. */
private static final int BIN = 2;
/** The error factor. */
private static final int EPSILON = 4;
/** The threshold value. */
private static final double THRESHOLD = 0.99;
/** The noise reduction threshold value. */
private static final double NR_THRESHOLD = 0.95;
/** The location statistics. */
private double[] locationStats;
/** Boolean flag to determine the mapping algorithm. */
private boolean noiseReduction;
/** Value determined according to the location of the pixels' value. */
private double inputStart;
/** Value determined according to the location of the pixels' value. */
private double inputEnd;
/** The size of the bin.*/
private double sizeBin;
/** The epsilon value.*/
private double epsilon;
/**
* For the specified {@link Plane2D}, computes the bins, determines the
* inputWindow and the noiseReduction flag.
*
* @param p2D The selected plane2d.
* @param gMin The global minimum.
* @param sizeX2 The size along one axis.
* @param sizeX1 The size along the other axis.
*/
private void computeBins(Plane2D p2D, double gMin, int sizeX2,
int sizeX1) {
int[] totals = new int[NB_BIN];
/*
* Segment[] segments = new Segment[NB_BIN]; for (int i = 0; i < NB_BIN;
* i++) { segments[i] = new Segment( gMin + i * sizeBin, 0, gMin + (i +
* 1) * sizeBin, 0); }
*/
BasicSegment[] segments = new BasicSegment[NB_BIN];
for (int i = 0; i < NB_BIN; i++) {
segments[i] = new BasicSegment(gMin + i * sizeBin, gMin + (i + 1)
* sizeBin);
}
// check segment [o,e[
double v;
BasicSegment segment;
if (p2D.isXYPlanar()) {
// modified code
int size = sizeX1 * sizeX2;
for (int j = 0; j < size; j++) {
v = p2D.getPixelValue(j);
for (int i = 0; i < segments.length; i++) {
segment = segments[i];
if (v >= segment.x1 && v < segment.x2) {
totals[i]++;
break;
}
} // end i
}
} else {
for (int x2 = 0; x2 < sizeX2; ++x2) {
for (int x1 = 0; x1 < sizeX1; ++x1) {
v = p2D.getPixelValue(x1, x2);
for (int i = 0; i < segments.length; i++) {
segment = segments[i];
if (v >= segment.x1 && v < segment.x2) {
totals[i]++;
break;
}
/*
* if (!segments[i].equals(1, pointX1, pointX2) &&
* segments[i].lies(pointX1, pointX2)) { totals[i]++;
* break; }
*/
} // end i
} // end x1
}// end x2
}
double total = sizeX2 * sizeX1;
for (int i = 0; i < totals.length; i++) {
locationStats[i] += totals[i] / total;
}
double s = segments[0].x2;
double end = segments[NB_BIN - 1].x2;
total = total - totals[0] - totals[NB_BIN - 1];
if (totals[0] >= totals[NB_BIN - 1]) {
end = accumulateCloseToMin(totals, segments, total, epsilon);
} else {
s = accumulateCloseToMax(totals, segments, total, epsilon);
}
if (s < inputStart) inputStart = s;
if (end > inputEnd) inputEnd = end;
noiseReduction = noiseReduction();
}
/** Determines the value of the noiseReduction flag. */
private boolean noiseReduction() {
double sumMin = 0, sumMax = 0;
for (int i = 0; i < locationStats.length; i++) {
if (i < BIN) {
sumMin += locationStats[i];
}
if (i >= locationStats.length - BIN) {
sumMax += locationStats[i];
}
}
if (sumMin >= NR_THRESHOLD || sumMax >= NR_THRESHOLD) {
return false;
}
return true;
}
/**
* Determines the value of inputEnd when the pixels' values accumulated
* closed to the minimum.
*
* @param totals The accumulated values.
* @param segments The segments to analyze.
* @param total The total value.
* @param epsilon The error value.
*/
private double accumulateCloseToMin(int[] totals, BasicSegment[] segments,
double total, double epsilon) {
double e = segments[NB_BIN - 1].x2, sum = 0;
for (int i = 1; i < totals.length - 1; i++) {
sum += totals[i];
if (sum / total > THRESHOLD) {
e = segments[i].x1 + epsilon;
break;
}
}
return e;
}
/**
* Determines the value of inputStart when the pixels' values accumulated
* closed to the max.
*
* @param totals The accumulated values.
* @param segments The segments to analyze.
* @param total The total value.
* @param epsilon The error value.
*/
private double accumulateCloseToMax(int[] totals, BasicSegment[] segments,
double total, double epsilon) {
double s = segments[0].x2, sum = 0;
for (int i = totals.length - 2; i > 0; i--) {
sum += totals[i];
if (sum / total > THRESHOLD) {
s = segments[i].x2 - epsilon;
break;
}
}
return s;
}
/**
* Determines the minimum and maximum corresponding to the passed
* pixels.
*
* @param metadata The pixels to handle.
*/
public double[] initPixelsRange(Pixels metadata)
{
PixelsType type = metadata.getPixelsType();
double[] minmax = new double[] {0, 1};
if (type == null) return minmax;
String typeAsString = type.getValue();
int bfPixelsType = FormatTools.pixelTypeFromString(typeAsString);
// Handle floating point types first
if (FormatTools.isFloatingPoint(bfPixelsType)) {
long[] values = FormatTools.defaultMinMax(bfPixelsType);
minmax[0] = values[0];
minmax[1] = values[1];
return minmax;
}
// Use significant bits if they are available for integral pixel types.
Integer significantBits = metadata.getSignificantBits();
if (significantBits == null) {
significantBits = type.getBitSize();
}
significantBits = Math.min(significantBits, type.getBitSize());
if (FormatTools.isSigned(bfPixelsType)) {
minmax[0] = -Math.pow(2, significantBits - 1);
minmax[1] = Math.pow(2, significantBits - 1) - 1;
} else {
minmax[0] = 0;
minmax[1] = Math.pow(2, significantBits) - 1;
}
return minmax;
}
/**
* Helper object to determine the location of the pixels' values, the
* inputWindow i.e. <code>inputStart</code> and <code>inputEnd</code>
* and to initialize the <code>noiseReduction</code> flag.
*
* @param metadata The pixels to parse.
* @param pixelsData The buffer.
* @param pd The plane to handle.
* @param index The channel index.
* @throws PixMetadataException
*/
public void computeLocationStats(final Pixels metadata,
final PixelBuffer pixelsData, final PlaneDef pd, final int index) {
log.debug("Computing location stats for Pixels:" + metadata.getId());
Channel channel = metadata.getChannel(index);
final StatsInfo stats = channel.getStatsInfo();
inputStart = 0;
inputEnd = 1;
if (stats == null) {
double[] values = initPixelsRange(metadata);
inputStart = values[0];
inputEnd = values[1];
} else {
inputStart = stats.getGlobalMin().doubleValue();
inputEnd = stats.getGlobalMax().doubleValue();
}
}
/**
* Returns the statistics.
*
* @return See above.
*/
public double[] getLocationStats() {
return locationStats;
}
/**
* Returns <code>true</code> if the flag is on, <code>false</code>
* otherwise.
*
* @return See above.
*/
public boolean isNoiseReduction() {
return noiseReduction;
}
/**
* Returns the input start.
*
* @return See above.
*/
public double getInputStart() {
return inputStart;
}
/**
* Returns the input end.
*
* @return See above.
*/
public double getInputEnd() {
return inputEnd;
}
// inner class
class BasicSegment {
/** Left bound of the segment. */
double x1;
/** Right bound of the segment. */
double x2;
/**
* Creates a new instance.
*
* @param x1
* The left bound of the segment.
* @param x2
* The right bound of the segment.
*/
BasicSegment(double x1, double x2) {
if (x2 < x1) {
throw new IllegalArgumentException("Segment not valid.");
}
this.x2 = x2;
this.x1 = x1;
}
}
}