/**
* Copyright 2010-2013 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.element.impl;
import java.util.WeakHashMap;
import org.jplot2d.data.ImageDataBuffer;
import org.jplot2d.image.IntensityTransform;
/**
* The class calculate and cache z-scaled image band data.
*
* @author Jingjing Li
*
*/
public class ImageZscaleCache {
public static class Key {
private final ImageDataBuffer dbuf;
private final int w, h;
private final double[] limits;
private final IntensityTransform intensityTransform;
private final double bias, gain;
private final int outputBits;
private Key(ImageDataBuffer dbuf, int w, int h, double[] limits, IntensityTransform intensityTransform,
double bias, double gain, int outputBits) {
this.dbuf = dbuf;
this.w = w;
this.h = h;
this.limits = limits;
this.intensityTransform = intensityTransform;
this.bias = bias;
this.gain = gain;
this.outputBits = outputBits;
}
public boolean equals(Object obj) {
if (obj instanceof ImageZscaleCache) {
return false;
}
Key key = (Key) obj;
boolean limitsMatch = key.limits == limits
|| (key.limits != null && key != null && key.limits[0] == limits[0] && key.limits[1] == limits[1]);
return key.dbuf.equals(dbuf) && key.w == w && key.h == h && limitsMatch
&& key.intensityTransform == intensityTransform && key.bias == bias && key.gain == gain
&& key.outputBits == outputBits;
}
public int hashCode() {
/* we only use dbuf to produce hash code, since there is rare conflict in the cache map */
return dbuf.hashCode();
}
}
private static class ValueRef {
private Object v;
}
/**
* The max number of significant bits after applying limits. The max number is 16, for unsigned short data buffer.
*/
private static final int MAX_BITS = 16;
private static final WeakHashMap map = new WeakHashMap();
/**
* Create a cache entry for the given calculation arguments.
*
* @param dbuf
* @param w
* @param h
* @param limits
* @param intensityTransform
* @param bias
* @param gain
* @param outputBits
* @return the key for the given calculation arguments
*/
public static Key createCacheFor(ImageDataBuffer dbuf, int w, int h, double[] limits,
IntensityTransform intensityTransform, double bias, double gain, int outputBits) {
Key key = new Key(dbuf, w, h, limits, intensityTransform, bias, gain, outputBits);
synchronized (map) {
ValueRef vref = map.remove(key);
if (vref == null) {
vref = new ValueRef();
}
map.put(key, vref);
}
return key;
}
/**
* Returns the result for the given calculation arguments.
*
* @param dbuf
* @param w
* @param h
* @param limits
* @param intensityTransform
* @param bias
* @param gain
* @param outputBits
* @return
*/
public static Object getValue(ImageDataBuffer dbuf, int w, int h, double[] limits,
IntensityTransform intensityTransform, double bias, double gain, int outputBits) {
Key key = new Key(dbuf, w, h, limits, intensityTransform, bias, gain, outputBits);
ValueRef vref;
synchronized (map) {
vref = map.get(key);
}
if (vref == null) {
return zscaleLimits(key);
}
synchronized (vref) {
if (vref.v != null) {
return vref.v;
} else {
// calculate v and store it
vref.v = zscaleLimits(key);
return vref.v;
}
}
}
private static Object zscaleLimits(Key key) {
ImageDataBuffer idb = key.dbuf;
int w = key.w;
int h = key.h;
double[] limits = key.limits;
int lutInputBits = getILUTInputBits(key.intensityTransform, key.bias, key.gain, key.outputBits);
if (key.outputBits <= Byte.SIZE) {
byte[] lut = createByteILUT(key.intensityTransform, key.bias, key.gain, lutInputBits, key.outputBits);
return zscaleBytes(idb, 0, 0, w, h, limits, lut, lutInputBits);
} else {
short[] lut = createShortILUT(key.intensityTransform, key.bias, key.gain, lutInputBits, key.outputBits);
return zscaleShorts(idb, 0, 0, w, h, limits, lut, lutInputBits);
}
}
/**
* Returns the number of significant bits that the ILUT index should match. When applying the limits, the generated
* values should match the ILUT indexes.
*
* @return the number of significant bits
*/
private static int getILUTInputBits(IntensityTransform intensityTransform, double bias, double gain, int outputBits) {
int bits = outputBits;
if (intensityTransform != null || gain != 0.5 || bias != 0.5) {
bits += 2;
}
if (bits > MAX_BITS) {
bits = MAX_BITS;
}
return bits;
}
/**
* Returns the ILUT for processing data, for applying intensity transform and bias/gain.
*
* @return
*/
private static byte[] createByteILUT(IntensityTransform intensityTransform, double bias, double gain,
int inputBits, int outputBits) {
if (intensityTransform == null && gain == 0.5 && bias == 0.5) {
return null;
}
// the LUT index range is [0, lutIndexes], plus repeat the last value
int lutIndexes = 1 << inputBits;
// the output range is [0, outputRange - 1]
int outputRange = 1 << outputBits;
byte[] lut = new byte[lutIndexes + 2];
for (int i = 0; i <= lutIndexes; i++) {
// t range is [0, 1]
double t = (double) i / lutIndexes;
if (intensityTransform != null) {
t = intensityTransform.transform(t);
}
if (bias != 0.5) {
t = t / ((1.0 / bias - 2.0) * (1.0 - t) + 1.0);
}
if (gain != 0.5) {
double f = (1.0 / gain - 2.0) * (1.0 - 2 * t);
if (t < 0.5) {
t = t / (f + 1.0);
} else {
t = (f - t) / (f - 1.0);
}
}
int v = (int) (outputRange * t);
if (v < 0) {
v = 0;
} else if (v >= outputRange) {
v = outputRange - 1;
}
lut[i] = (byte) (v);
}
lut[lutIndexes + 1] = lut[lutIndexes];
return lut;
}
/**
* Apply the cuts and scale the data to a unsigned byte array
*
* @param xoff
* @param yoff
* @param w
* @param h
* @param limits
* @param lut
* @param lutInputBits
* @return
*/
private static byte[] zscaleBytes(ImageDataBuffer idb, int xoff, int yoff, int w, int h, double[] limits,
byte[] lut, int lutInputBits) {
byte[] result = new byte[w * h];
// limits is null means there is no valid data
if (limits == null) {
return result;
}
double lowCut = limits[0];
double highCut = limits[1];
int outputRange = 1 << lutInputBits;
double scale = outputRange / (highCut - lowCut);
int n = 0;
if (lut == null) {
for (int r = yoff; r < yoff + h; r++) {
for (int c = xoff; c < xoff + w; c++) {
double scaled = (idb.getDouble(c, r) - lowCut) * scale;
/*
* the scaled value may slightly larger than outputRange or slightly small than 0. the ilutIndex
* range is [0, outputRange]
*/
int ilutIndex = (int) scaled;
if (ilutIndex >= outputRange) {
ilutIndex = outputRange - 1;
}
result[n++] = (byte) ilutIndex;
}
}
} else {
for (int r = yoff; r < yoff + h; r++) {
for (int c = xoff; c < xoff + w; c++) {
double scaled = (idb.getDouble(c, r) - lowCut) * scale;
/*
* the scaled value may slightly larger than outputRange or slightly small than 0. the ilutIndex
* range is [0, outputRange]
*/
int ilutIndex = (int) scaled;
double idelta = ilutIndex - ilutIndex;
// the LUT output is unsigned byte, apply the 0xff bit mask.
int a = lut[ilutIndex] & 0xff;
int b = lut[ilutIndex + 1] & 0xff;
result[n++] = (byte) (a + idelta * (b - a));
}
}
}
return result;
}
/**
* Returns the ILUT for processing data, for applying intensity transform and bias/gain.
*
* @return
*/
private static short[] createShortILUT(IntensityTransform intensityTransform, double bias, double gain,
int inputBits, int outputBits) {
if (intensityTransform == null && gain == 0.5 && bias == 0.5) {
return null;
}
/*
* create a lookup table. The input bits is getInputDataBits(). The output bits is getOutputDataBits()
*/
// the LUT index range is [0, lutIndexes], plus repeat the last value
int lutIndexes = 1 << inputBits;
// the output range is [0, outputRange - 1]
int outputRange = 1 << outputBits;
short[] lut = new short[lutIndexes + 2];
for (int i = 0; i <= lutIndexes; i++) {
// t range is [0, 1]
double t = (double) i / lutIndexes;
if (intensityTransform != null) {
t = intensityTransform.transform(t);
}
if (bias != 0.5) {
t = t / ((1.0 / bias - 2.0) * (1.0 - t) + 1.0);
}
if (gain != 0.5) {
double f = (1.0 / gain - 2.0) * (1.0 - 2 * t);
if (t < 0.5) {
t = t / (f + 1.0);
} else {
t = (f - t) / (f - 1.0);
}
}
int v = (int) (outputRange * t);
if (v < 0) {
v = 0;
} else if (v >= outputRange) {
v = outputRange - 1;
}
lut[i] = (short) (v);
}
lut[lutIndexes + 1] = lut[lutIndexes];
return lut;
}
/**
* Apply the cuts and scale the data to a unsigned short array
*
* @param xoff
* @param yoff
* @param w
* @param h
* @param limits
* @param lut
* the LUT output bits is no more than 15
* @param lutInputBits
* @return
*/
private static short[] zscaleShorts(ImageDataBuffer idb, int xoff, int yoff, int w, int h, double[] limits,
short[] lut, int lutInputBits) {
short[] result = new short[w * h];
// limits is null means there is no valid data
if (limits == null) {
return result;
}
double lowCut = limits[0];
double highCut = limits[1];
int outputRange = 1 << lutInputBits;
double scale = outputRange / (highCut - lowCut);
int n = 0;
if (lut == null) {
for (int r = yoff; r < yoff + h; r++) {
for (int c = xoff; c < xoff + w; c++) {
double scaled = (idb.getDouble(c, r) - lowCut) * scale;
/*
* the scaled value may slightly larger than outputRange or slightly small than 0. the ilutIndex
* range is [0, outputRange]
*/
int ilutIndex = (int) scaled;
if (ilutIndex >= outputRange) {
ilutIndex = outputRange - 1;
}
result[n++] = (short) ilutIndex;
}
}
} else {
for (int r = yoff; r < yoff + h; r++) {
for (int c = xoff; c < xoff + w; c++) {
double scaled = (idb.getDouble(c, r) - lowCut) * scale;
/*
* the scaled value may slightly larger than outputRange or slightly small than 0. the ilutIndex
* range is [0, outputRange]
*/
int ilutIndex = (int) scaled;
double idelta = ilutIndex - ilutIndex;
// the LUT output is unsigned short, apply the 0xffff bit mask.
int a = lut[ilutIndex] & 0xffff;
int b = lut[ilutIndex + 1] & 0xffff;
result[n++] = (short) (a + idelta * (b - a));
}
}
}
return result;
}
}
Ads help maintain this website.