/*
* omeis.providers.re.quantum.Quantization_32_bit
*
*------------------------------------------------------------------------------
* Copyright (C) 2014 University of Dundee. All rights reserved.
*
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package omeis.providers.re.quantum;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
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>
* @since OME5.1
*/
public class Quantization_32_bit extends QuantumStrategy {
/** The lowest pixel intensity value. */
private int min;
/** The uppest pixel intensity value. */
private int max;
/** 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;
/** The mapped values.*/
private LoadingCache<Double, Integer> values;
/**
* 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;
}
/** The input window size changed, re-map the values. */
@Override
protected void onWindowChange() {
values.invalidateAll();
}
/**
* Maps the value.
*
* @param value The value to handle.
* @return The mapped value.
* @throws QuantizationException Thrown if an error occurred during
* the mapping.
*/
private int _quantize(double value)
throws QuantizationException
{
double dStart = getWindowStart(), dEnd = getWindowEnd();
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();
if (value > Q1) {
if (value <= Q9) {
v = aDecile * normalize.transform(value, 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);
return ((byte) v) & 0xFF;
}
/**
* Creates a new strategy.
*
* @param qd
* Quantum definition object, contained mapping data.
* @param pixels
* The pixels
*/
public Quantization_32_bit(QuantumDef qd, Pixels pixels) {
super(qd, pixels);
values = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<Double, Integer>() {
public Integer load(Double key) throws Exception {
return _quantize(key);
}
});
}
/**
* Implemented as specified in {@link QuantumStrategy}.
*
* @see QuantumStrategy#quantize(double)
*/
@Override
public int quantize(double value) throws QuantizationException {
try {
return values.get(getMiddleRange(value));
} catch (ExecutionException e) {
throw new QuantizationException(e);
}
}
}