/*
* omeis.providers.re.quantum.Quantization_8_16_bit
*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package omeis.providers.re.quantum;
import ome.model.core.Pixels;
import ome.model.display.QuantumDef;
/**
* Quantization process. In charge of building a look-up table for each active
* wavelength. The mapping process is done in three mapping steps, for some
* computer reasons, we cannot compose (in the mathematical sense) the three
* maps directly. Each wavelength initializes a strategy, in order to preserve
* the 5D-notion of OME image, we first compute the normalized parameters. We
* determine a pseudo-decile (not decile in maths terms) interval and compute
* the associated parameters to reduce the irrelevant values (noiseReduction).
*
*
* @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>
* @version 2.2 <small> (<b>Internal version:</b> $Revision$ $Date:
* 2005/06/20 14:12:20 $) </small>
* @since OME2.2
*/
public class Quantization_8_16_bit extends QuantumStrategy {
/** The look-up table. */
private byte[] LUT;
/** The lowest pixel intensity value. */
private int min;
/** The uppest pixel intensity value. */
private int max;
/** The lower bound of the table. */
private int lutMin;
/** The upper bound of the table. */
private int lutMax;
/** The input start normalized value. */
private double ysNormalized;
/** The input end normalized value. */
private double yeNormalized;
/** The slope of the normalized map. */
private double aNormalized;
/** The lower bound of the decile interval. */
private double Q1;
/** The upper bound of the decile interval. */
private double Q9;
/**
* The mapping parameters from the sub-interval of [Q1, Q9] to the device
* space.
*/
private double aDecile, bDecile;
/**
* The device space sub-interval. The values aren't the ones stored in
* {@link QuantumDef} if the noise reduction flag is <code>true</code>.
*/
private int cdStart, cdEnd;
/**
* Initializes the LUT. Comparable getGlobalMin and getGlobalMax assumed to
* be Integer, QuantumStrategy enforces min < max. QuantumFactory makes
* sure 0 < max-min < 2^N where N = 8 or N = 16. LUT size is at most
* 256 bytes if N = 8 or 2^16 bytes = 2^6Kb = 64Kb if N = 16.
*
* @param s The lower bound.
* @param e The upper bound.
*/
private void initLUT(int s, int e)
{
min = (int) getGlobalMin();
max = (int) getGlobalMax();
lutMin = (int) getPixelsTypeMin();
lutMax = (int) getPixelsTypeMax();
if (lutMax == 0) { //couldn't initialize the value
if (s < min) lutMin = s;
else lutMin = min;
if (e > max) lutMax = e;
else lutMax = max;
}
int range = lutMax - lutMin;
if (range > MAX_SIZE_LUT)
{
// We want to avoid *huge* memory allocations below so if we
// couldn't initialize the value above and our lutMax and lutMin
// have been assigned out of range values we want to choke, not
// cause the server to throw a java.lang.OutOfMemory exception.
// *** Ticket #1353 -- Chris Allan <callan@blackcat.ca> ***
//This should not happen since we have now strategy for float
//and 32bit.
throw new IllegalArgumentException(String.format(
"Lookup table of size %d greater than supported size %f",
range, MAX_SIZE_LUT));
}
LUT = new byte[lutMax-lutMin+1];
}
/**
* Resets the LUT. We rebuild the LUT if and only if the
* pixels type range was not determined at init time.
*
* @param s The lower bound.
* @param e The upper bound.
*/
private void resetLUT(int s, int e)
{
int pMax = (int) getPixelsTypeMax();
if (pMax != 0) return;
if (s < lutMin && e > lutMax) {
lutMin = s;
lutMax = e;
LUT = new byte[lutMax-lutMin+1];
} else if (s < lutMin && e <= lutMax) {
lutMin = s;
LUT = new byte[lutMax-lutMin+1];
} else if (s >= lutMin && e > lutMax) {
lutMax = e;
LUT = new byte[lutMax-lutMin+1];
}
}
/**
* Initializes the coefficient of the normalize mapping operation.
*
* @param k
* The coefficient of the selected curve.
*/
private void initNormalizedMap(double k) {
ysNormalized = valueMapper.transform(MIN, k);
yeNormalized = valueMapper.transform(MAX, k);
aNormalized = qDef.getBitResolution().intValue()
/ (yeNormalized - ysNormalized);
}
/**
* Initializes the parameter to map the pixels intensities to the device
* space and returned the default initial depending on the value of the
* noise reduction flag.
*
* @param dStart
* The input window start.
* @param dEnd
* The input window end.
* @return See above.
*/
private double initDecileMap(double dStart, double dEnd) {
cdStart = qDef.getCdStart().intValue();
cdEnd = qDef.getCdEnd().intValue();
double denum = dEnd - dStart, num = MAX;
double v = 0, b = dStart;
int e = 0;
double startMin = min;
double startMax = max;
Q1 = min;
Q9 = max;
if (dStart <= startMin) {
Q1 = dStart;
}
if (dEnd >= startMax) Q9 = dEnd;
if (startMin == startMax) v = 1;
double decile = (startMax - startMin) / DECILE;
if (getNoiseReduction()) {
Q1 += decile;
Q9 -= decile;
denum = Q9 - Q1;
v = DECILE;
e = DECILE;
num = MAX - 2 * DECILE;
b = Q1;
if (dStart >= Q1 && dEnd > Q9) {
denum = Q9 - dStart;
b = dStart;
} else if (dStart >= Q1 && dEnd <= Q9) {
denum = dEnd - dStart;
b = dStart;
} else if (dStart < Q1 && dEnd <= Q9) {
denum = dEnd - Q1;
}
if (cdStart < DECILE) {
cdStart = DECILE;
}
if (cdEnd > MAX - DECILE) {
cdEnd = MAX - DECILE;
}
}
aDecile = num / denum;
bDecile = aDecile * b - e;
return v;
}
/**
* Maps the input interval onto the codomain [cdStart, cdEnd] sub-interval
* of [0, 255]. Since the user can select the bitResolution 2^n-1 where n =
* 1..8, 2 steps. The mapping is the composition of 2 transformations: The
* first one <code>f</code> is one of the map selected by the user
* f:[inputStart, inputEnd]-<[0, 2^n-1]. The second one <code>g</code>
* is a linear map: y = a1*x+b1 where b1 = codomainStart and a1 =
* (qDef.cdEnd-qDef.cdStart)/((double) qDef.bitResolution); g: [0,
* 2^n-1]-<[cdStart, cdEnd]. For some reasons, we cannot compute directly
* gof.
*/
private void buildLUT() {
double dStart = getWindowStart(), dEnd = getWindowEnd();
if (LUT == null) {
initLUT((int) dStart, (int) dEnd);
} else {
resetLUT((int) dStart, (int) dEnd);
}
// Comparable assumed to be Integer
// domain
double k = getCurveCoefficient();
double a1 = (qDef.getCdEnd().intValue() - qDef.getCdStart().intValue())
/ qDef.getBitResolution().doubleValue();
// Initializes the normalized map.
initNormalizedMap(k);
// Initializes the decile map.
double v = initDecileMap(dStart, dEnd);
QuantumMap normalize = new PolynomialMap();
// Build the LUT
int x = lutMin;
for (; x < dStart; ++x) {
LUT[x - lutMin] = (byte) cdStart;
}
for (; x < dEnd; ++x) {
if (x > Q1) {
if (x <= Q9) {
v = aDecile * normalize.transform(x, 1) - bDecile;
} else {
v = cdEnd;
}
} else {
v = cdStart;
}
v = aNormalized * (valueMapper.transform(v, k) - ysNormalized);
v = Math.round(v);
v = Math.round(a1 * v + cdStart);
LUT[x - lutMin] = (byte) v;
}
for (; x <= lutMax; ++x) {
LUT[x - lutMin] = (byte) cdEnd;
}
}
/** The input window size changed, rebuild the LUT. */
@Override
protected void onWindowChange() {
buildLUT();
}
/**
* Creates a new strategy.
*
* @param qd
* Quantum definition object, contained mapping data.
* @param pixels
* The pixels
*/
public Quantization_8_16_bit(QuantumDef qd, Pixels pixels) {
super(qd, pixels);
}
/**
* Implemented as specified in {@link QuantumStrategy}.
*
* @see QuantumStrategy#quantize(double)
*/
@Override
public int quantize(double value) throws QuantizationException {
int x = (int) value;
if (x < lutMin) {
double r = getOriginalGlobalMax()-getOriginalGlobalMin();
if (r != 0) {
double f = (lutMax-lutMin)/r;
x = (int) (f*(x-lutMin));
if (x < lutMin) x = lutMin;
} else x = lutMin;
}
if (x > lutMax) {
double r = getOriginalGlobalMax()-getOriginalGlobalMin();
if (r != 0) {
double f = (lutMax-lutMin)/r;
x = (int) (f*(x-lutMin));
if (x > lutMax) x = lutMax;
} else x = lutMax;
}
int i = LUT[x - lutMin];
return i & 0xFF;
}
}