1 package nom.tam.fits.compression.algorithm.quant;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import java.nio.ByteBuffer;
35 import java.nio.DoubleBuffer;
36 import java.nio.FloatBuffer;
37 import java.nio.IntBuffer;
38
39 import nom.tam.fits.compression.algorithm.api.ICompressor;
40
41
42
43
44 @SuppressWarnings({"javadoc", "deprecation"})
45 public class QuantizeProcessor {
46
47 public static class DoubleQuantCompressor extends QuantizeProcessor implements ICompressor<DoubleBuffer> {
48
49 private final ICompressor<IntBuffer> postCompressor;
50
51 public DoubleQuantCompressor(QuantizeOption quantizeOption, ICompressor<IntBuffer> compressor) {
52 super(quantizeOption);
53 postCompressor = compressor;
54 }
55
56 @Override
57 public boolean compress(DoubleBuffer buffer, ByteBuffer compressed) {
58 IntBuffer intData = IntBuffer.wrap(new int[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()]);
59 double[] doubles = new double[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()];
60 buffer.get(doubles);
61 if (!this.quantize(doubles, intData)) {
62 return false;
63 }
64 intData.rewind();
65 postCompressor.compress(intData, compressed);
66 return true;
67 }
68
69 @Override
70 public void decompress(ByteBuffer compressed, DoubleBuffer buffer) {
71 IntBuffer intData = IntBuffer.wrap(new int[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()]);
72 postCompressor.decompress(compressed, intData);
73 intData.rewind();
74 unquantize(intData, buffer);
75 }
76 }
77
78
79
80
81 public static class FloatQuantCompressor extends QuantizeProcessor implements ICompressor<FloatBuffer> {
82
83 private final ICompressor<IntBuffer> postCompressor;
84
85 public FloatQuantCompressor(QuantizeOption quantizeOption, ICompressor<IntBuffer> postCompressor) {
86 super(quantizeOption);
87 this.postCompressor = postCompressor;
88 }
89
90 @Override
91 public boolean compress(FloatBuffer buffer, ByteBuffer compressed) {
92 float[] floats = new float[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()];
93 double[] doubles = new double[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()];
94 buffer.get(floats);
95 for (int index = 0; index < doubles.length; index++) {
96 doubles[index] = floats[index];
97 }
98 IntBuffer intData = IntBuffer.wrap(new int[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()]);
99 if (!this.quantize(doubles, intData)) {
100 return false;
101 }
102 intData.rewind();
103 postCompressor.compress(intData, compressed);
104 return true;
105 }
106
107 @Override
108 public void decompress(ByteBuffer compressed, FloatBuffer buffer) {
109 IntBuffer intData = IntBuffer.wrap(new int[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()]);
110 postCompressor.decompress(compressed, intData);
111 intData.rewind();
112 double[] doubles = new double[quantizeOption.getTileHeight() * quantizeOption.getTileWidth()];
113 DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);
114 unquantize(intData, doubleBuffer);
115 for (double d : doubles) {
116 buffer.put((float) d);
117 }
118 }
119 }
120
121 private class BaseFilter extends PixelFilter {
122
123 BaseFilter() {
124 super(null);
125 }
126
127 @Override
128 protected void nextPixel() {
129 }
130
131 @Override
132 protected double toDouble(int pixel) {
133 return (pixel + ROUNDING_HALF) * bScale + bZero;
134 }
135
136 @Override
137 protected int toInt(double pixel) {
138 return nint((pixel - bZero) / bScale + ROUNDING_HALF);
139 }
140 }
141
142 private class DitherFilter extends PixelFilter {
143
144 private static final int RANDOM_MULTIPLICATOR = 500;
145
146 private int iseed = 0;
147
148 private int nextRandom = 0;
149
150 DitherFilter(long seed) {
151 super(null);
152 initialize(seed);
153 }
154
155 public void initialize(long ditherSeed) {
156 iseed = (int) ((ditherSeed - 1) % RandomSequence.length());
157 initI1();
158 }
159
160 private void initI1() {
161 nextRandom = (int) (RandomSequence.get(iseed) * RANDOM_MULTIPLICATOR);
162 }
163
164 public double nextRandom() {
165 return RandomSequence.get(nextRandom);
166 }
167
168 @Override
169 protected void nextPixel() {
170 nextRandom++;
171 if (nextRandom >= RandomSequence.length()) {
172 iseed++;
173 if (iseed >= RandomSequence.length()) {
174 iseed = 0;
175 }
176 initI1();
177 }
178 }
179
180 @Override
181 protected double toDouble(int pixel) {
182 return (pixel - nextRandom() + ROUNDING_HALF) * bScale + bZero;
183 }
184
185 @Override
186 protected int toInt(double pixel) {
187 return nint((pixel - bZero) / bScale + nextRandom() - ROUNDING_HALF);
188 }
189 }
190
191 private class NullFilter extends PixelFilter {
192
193 private final double nullValue;
194
195 private final boolean isNaN;
196
197 private final int nullValueIndicator;
198
199 NullFilter(double nullValue, int nullValueIndicator, PixelFilter next) {
200 super(next);
201 this.nullValue = nullValue;
202 isNaN = Double.isNaN(this.nullValue);
203 this.nullValueIndicator = nullValueIndicator;
204 }
205
206 public final boolean isNull(double pixel) {
207 return isNaN ? Double.isNaN(pixel) : nullValue == pixel;
208 }
209
210 @Override
211 protected double toDouble(int pixel) {
212 if (pixel == nullValueIndicator) {
213 return nullValue;
214 }
215 return super.toDouble(pixel);
216 }
217
218 @Override
219 protected int toInt(double pixel) {
220 if (isNull(pixel)) {
221 return nullValueIndicator;
222 }
223 return super.toInt(pixel);
224 }
225 }
226
227 private class PixelFilter {
228
229 private final PixelFilter next;
230
231 protected PixelFilter(PixelFilter next) {
232 this.next = next;
233 }
234
235 protected void nextPixel() {
236 next.nextPixel();
237 }
238
239 protected double toDouble(int pixel) {
240 return next.toDouble(pixel);
241 }
242
243 protected int toInt(double pixel) {
244 return next.toInt(pixel);
245 }
246 }
247
248 private class ZeroFilter extends PixelFilter {
249
250 ZeroFilter(PixelFilter next) {
251 super(next);
252 }
253
254 @Override
255 protected double toDouble(int pixel) {
256 if (pixel == ZERO_VALUE) {
257 return 0.0;
258 }
259 return super.toDouble(pixel);
260 }
261
262 @Override
263 protected int toInt(double pixel) {
264 if (pixel == 0.0) {
265 return ZERO_VALUE;
266 }
267 return super.toInt(pixel);
268 }
269 }
270
271 private static final double MAX_INT_AS_DOUBLE = Integer.MAX_VALUE;
272
273
274
275
276 private static final long N_RESERVED_VALUES = 10;
277
278 private static final double ROUNDING_HALF = 0.5;
279
280
281
282
283 private static final int ZERO_VALUE = Integer.MIN_VALUE + 2;
284
285 private final boolean centerOnZero;
286
287 private final PixelFilter pixelFilter;
288
289 private double bScale;
290
291 private double bZero;
292
293 private Quantize quantize;
294
295 protected final QuantizeOption quantizeOption;
296
297 public QuantizeProcessor(QuantizeOption quantizeOption) {
298 this.quantizeOption = quantizeOption;
299 bScale = quantizeOption.getBScale();
300 bZero = quantizeOption.getBZero();
301 PixelFilter filter = null;
302 boolean localCenterOnZero = quantizeOption.isCenterOnZero();
303 if (quantizeOption.isDither2()) {
304 filter = new DitherFilter(quantizeOption.getSeed() + quantizeOption.getTileIndex());
305 localCenterOnZero = true;
306 quantizeOption.setCheckZero(true);
307 } else if (quantizeOption.isDither()) {
308 filter = new DitherFilter(quantizeOption.getSeed() + quantizeOption.getTileIndex());
309 } else {
310 filter = new BaseFilter();
311 }
312 if (quantizeOption.isCheckZero()) {
313 filter = new ZeroFilter(filter);
314 }
315 if (quantizeOption.isCheckNull()) {
316 final NullFilter nullFilter = new NullFilter(quantizeOption.getNullValue(), quantizeOption.getBNull(), filter);
317 filter = nullFilter;
318 quantize = new Quantize(quantizeOption) {
319
320 @Override
321 protected int findNextValidPixelWithNullCheck(int nx, DoubleArrayPointer rowpix, int ii) {
322 while (ii < nx && nullFilter.isNull(rowpix.get(ii))) {
323 ii++;
324 }
325 return ii;
326 }
327
328 @Override
329 protected boolean isNull(double d) {
330 return nullFilter.isNull(d);
331 }
332 };
333 } else {
334 quantize = new Quantize(quantizeOption);
335 }
336 pixelFilter = filter;
337 centerOnZero = localCenterOnZero;
338 }
339
340 public Quantize getQuantize() {
341 return quantize;
342 }
343
344 public boolean quantize(double[] doubles, IntBuffer quants) {
345 boolean success = quantize.quantize(doubles, quantizeOption.getTileWidth(), quantizeOption.getTileHeight());
346 if (success) {
347 calculateBZeroAndBscale();
348 quantize(DoubleBuffer.wrap(doubles, 0, quantizeOption.getTileWidth() * quantizeOption.getTileHeight()), quants);
349 }
350 return success;
351 }
352
353 public void quantize(final DoubleBuffer fdata, final IntBuffer intData) {
354 while (fdata.hasRemaining()) {
355 intData.put(pixelFilter.toInt(fdata.get()));
356 pixelFilter.nextPixel();
357 }
358 }
359
360 public void unquantize(final IntBuffer intData, final DoubleBuffer fdata) {
361 while (fdata.hasRemaining()) {
362 fdata.put(pixelFilter.toDouble(intData.get()));
363 pixelFilter.nextPixel();
364 }
365 }
366
367 private void calculateBZeroAndBscale() {
368 bScale = quantizeOption.getBScale();
369 bZero = zeroCenter();
370 quantizeOption.setIntMinValue(nint((quantizeOption.getMinValue() - bZero) / bScale));
371 quantizeOption.setIntMaxValue(nint((quantizeOption.getMaxValue() - bZero) / bScale));
372 quantizeOption.setBZero(bZero);
373 }
374
375 private int nint(double x) {
376 return x >= 0. ? (int) (x + ROUNDING_HALF) : (int) (x - ROUNDING_HALF);
377 }
378
379 private double zeroCenter() {
380 final double minValue = quantizeOption.getMinValue();
381 final double maxValue = quantizeOption.getMaxValue();
382 double evaluatedBZero;
383 if (!quantizeOption.isCheckNull() && !centerOnZero) {
384
385
386
387
388 if ((maxValue - minValue) / bScale < MAX_INT_AS_DOUBLE - N_RESERVED_VALUES) {
389 evaluatedBZero = minValue;
390
391
392
393 long iqfactor = (long) (evaluatedBZero / bScale + ROUNDING_HALF);
394 evaluatedBZero = iqfactor * bScale;
395 } else {
396
397 evaluatedBZero = (minValue + maxValue) / 2.;
398 }
399 } else {
400
401
402
403 evaluatedBZero = minValue - bScale * (Integer.MIN_VALUE + N_RESERVED_VALUES + 1);
404 }
405 return evaluatedBZero;
406 }
407 }