1 package nom.tam.image.compression.tile;
2
3 import java.lang.reflect.Array;
4 import java.nio.Buffer;
5 import java.nio.ByteBuffer;
6 import java.util.Locale;
7 import java.util.concurrent.ExecutorService;
8 import java.util.logging.Logger;
9
10 import nom.tam.fits.BinaryTable;
11 import nom.tam.fits.BinaryTableHDU;
12 import nom.tam.fits.FitsException;
13 import nom.tam.fits.FitsFactory;
14 import nom.tam.fits.Header;
15 import nom.tam.fits.HeaderCard;
16 import nom.tam.fits.HeaderCardBuilder;
17 import nom.tam.fits.compression.algorithm.api.ICompressOption;
18 import nom.tam.fits.compression.algorithm.api.ICompressorControl;
19 import nom.tam.fits.compression.provider.CompressorProvider;
20 import nom.tam.fits.compression.provider.param.api.HeaderAccess;
21 import nom.tam.fits.header.Compression;
22 import nom.tam.image.compression.tile.mask.ImageNullPixelMask;
23 import nom.tam.image.tile.operation.AbstractTiledImageOperation;
24 import nom.tam.image.tile.operation.TileArea;
25 import nom.tam.util.type.ElementType;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 import static nom.tam.fits.header.Compression.COMPRESSED_DATA_COLUMN;
59 import static nom.tam.fits.header.Compression.GZIP_COMPRESSED_DATA_COLUMN;
60 import static nom.tam.fits.header.Compression.NULL_PIXEL_MASK_COLUMN;
61 import static nom.tam.fits.header.Compression.UNCOMPRESSED_DATA_COLUMN;
62 import static nom.tam.fits.header.Compression.ZBITPIX;
63 import static nom.tam.fits.header.Compression.ZCMPTYPE;
64 import static nom.tam.fits.header.Compression.ZCMPTYPE_GZIP_1;
65 import static nom.tam.fits.header.Compression.ZMASKCMP;
66 import static nom.tam.fits.header.Compression.ZNAXIS;
67 import static nom.tam.fits.header.Compression.ZNAXISn;
68 import static nom.tam.fits.header.Compression.ZQUANTIZ;
69 import static nom.tam.fits.header.Compression.ZSCALE_COLUMN;
70 import static nom.tam.fits.header.Compression.ZTILEn;
71 import static nom.tam.fits.header.Compression.ZZERO_COLUMN;
72 import static nom.tam.fits.header.Standard.TFIELDS;
73 import static nom.tam.fits.header.Standard.TTYPEn;
74 import static nom.tam.image.compression.tile.TileCompressionType.COMPRESSED;
75 import static nom.tam.image.compression.tile.TileCompressionType.GZIP_COMPRESSED;
76 import static nom.tam.image.compression.tile.TileCompressionType.UNCOMPRESSED;
77
78
79
80
81
82
83 @SuppressWarnings({"javadoc", "deprecation"})
84 public class TiledImageCompressionOperation extends AbstractTiledImageOperation<TileCompressionOperation> {
85
86
87
88
89 private String compressAlgorithm;
90
91 private final BinaryTable binaryTable;
92
93 private ByteBuffer compressedWholeArea;
94
95
96 private ICompressorControl compressorControl;
97
98
99 private ICompressOption compressOptions;
100
101
102
103
104 private String quantAlgorithm;
105
106
107 private ICompressorControl gzipCompressorControl;
108
109 private ImageNullPixelMask imageNullPixelMask;
110
111
112 private Object lock = new Object();
113
114 private static void addColumnToTable(BinaryTableHDU hdu, Object column, String columnName) throws FitsException {
115 if (column != null) {
116 hdu.setColumnName(hdu.addColumn(column) - 1, columnName, null);
117 }
118 }
119
120 private static void setNullEntries(Object column, Object defaultValue) {
121 if (column != null) {
122 for (int index = 0; index < Array.getLength(column); index++) {
123 if (Array.get(column, index) == null) {
124 Array.set(column, index, defaultValue);
125 }
126 }
127 }
128 }
129
130
131
132
133
134
135 public TiledImageCompressionOperation(BinaryTable binaryTable) {
136 super(TileCompressionOperation.class);
137 this.binaryTable = binaryTable;
138 }
139
140 public void compress(BinaryTableHDU hdu) throws FitsException {
141 processAllTiles();
142 writeColumns(hdu);
143 writeHeader(hdu.getHeader());
144 }
145
146 @Override
147 public ICompressOption compressOptions() {
148 synchronized (lock) {
149 if (compressorControl == null) {
150 getCompressorControl();
151 compressOptions = compressorControl.option();
152 if (quantAlgorithm != null) {
153 Header h = new Header();
154 h.addLine(HeaderCard.create(ZQUANTIZ, quantAlgorithm));
155 compressOptions.getCompressionParameters().getValuesFromHeader(h);
156 }
157 }
158 return compressOptions;
159 }
160 }
161
162 @Override
163 public void ensureColumnParameters() {
164 compressOptions.getCompressionParameters().initializeColumns(getNumberOfTileOperations());
165 }
166
167 public Buffer decompress() {
168 Buffer decompressedWholeArea = getBaseType().newBuffer(getBufferSize());
169 for (TileCompressionOperation tileOperation : getTileOperations()) {
170 tileOperation.setWholeImageBuffer(decompressedWholeArea);
171 }
172 processAllTiles();
173 decompressedWholeArea.rewind();
174 return decompressedWholeArea;
175 }
176
177 public void forceNoLoss(int x, int y, int width, int heigth) {
178 TileArea tileArea = new TileArea().start(x, y).end(x + width, y + heigth);
179 for (TileCompressionOperation operation : getTileOperations()) {
180 if (operation.getArea().intersects(tileArea)) {
181 operation.forceNoLoss(true);
182 }
183 }
184 }
185
186 @Override
187 public ByteBuffer getCompressedWholeArea() {
188 return compressedWholeArea;
189 }
190
191 @Override
192 public ICompressorControl getCompressorControl() {
193 synchronized (lock) {
194 if (compressorControl == null) {
195 compressorControl = CompressorProvider.findCompressorControl(quantAlgorithm, compressAlgorithm,
196 getBaseType().primitiveClass());
197 if (compressorControl == null) {
198 throw new IllegalStateException(
199 "Found no compressor control for compression algorithm:" + compressAlgorithm +
200 " (quantize algorithm = " + quantAlgorithm + ", base type = "
201 + getBaseType().primitiveClass() + ")");
202 }
203 }
204 return compressorControl;
205 }
206 }
207
208 @Override
209 public ICompressorControl getGzipCompressorControl() {
210 synchronized (lock) {
211 if (gzipCompressorControl == null) {
212 gzipCompressorControl = CompressorProvider.findCompressorControl(null, ZCMPTYPE_GZIP_1,
213 getBaseType().primitiveClass());
214 }
215 return gzipCompressorControl;
216 }
217 }
218
219 public TiledImageCompressionOperation prepareUncompressedData(final Buffer buffer) throws FitsException {
220 compressedWholeArea = ByteBuffer.wrap(new byte[getBaseType().size() * getBufferSize()]);
221 createTiles(new TileCompressorInitialisation(this, buffer));
222 compressedWholeArea.rewind();
223 return this;
224 }
225
226
227
228
229
230
231
232
233
234
235 public ImageNullPixelMask preserveNulls(long nullValue, String compressionAlgorithm) {
236 imageNullPixelMask = new ImageNullPixelMask(getTileOperations().length, nullValue, compressionAlgorithm);
237 for (TileCompressionOperation tileOperation : getTileOperations()) {
238 tileOperation.createImageNullPixelMask(getImageNullPixelMask());
239 }
240 return imageNullPixelMask;
241 }
242
243 private void setQuantAlgorithm(final Header header) {
244 synchronized (lock) {
245
246
247
248
249
250
251
252 boolean hasScale = false;
253 boolean hasZero = false;
254
255 int nFields = header.getIntValue(TFIELDS);
256
257
258 quantAlgorithm = null;
259
260 for (int i = 1; i <= nFields; i++) {
261 String type = header.getStringValue(TTYPEn.n(i));
262
263 if (ZSCALE_COLUMN.equals(type)) {
264 hasScale = true;
265 } else if (ZZERO_COLUMN.equals(type)) {
266 hasZero = true;
267 }
268
269 if (hasScale && hasZero) {
270
271
272 quantAlgorithm = Compression.ZQUANTIZ_NO_DITHER;
273 setQuantAlgorithm(header.getCard(ZQUANTIZ));
274 return;
275 }
276 }
277 }
278 }
279
280 public TiledImageCompressionOperation read(final Header header) throws FitsException {
281 readPrimaryHeaders(header);
282 setCompressAlgorithm(header.getCard(ZCMPTYPE));
283 setQuantAlgorithm(header);
284
285 createTiles(new TileDecompressorInitialisation(this,
286 getNullableColumn(header, Object[].class, UNCOMPRESSED_DATA_COLUMN),
287 getNullableColumn(header, Object[].class, COMPRESSED_DATA_COLUMN),
288 getNullableColumn(header, Object[].class, GZIP_COMPRESSED_DATA_COLUMN),
289 header));
290 byte[][] nullPixels = getNullableColumn(header, byte[][].class, NULL_PIXEL_MASK_COLUMN);
291 if (nullPixels != null) {
292 preserveNulls(0L, header.getStringValue(ZMASKCMP)).setColumn(nullPixels);
293 }
294 readCompressionHeaders(header);
295 return this;
296 }
297
298 public void readPrimaryHeaders(Header header) throws FitsException {
299 readBaseType(header);
300 readAxis(header);
301 readTileAxis(header);
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 public TiledImageCompressionOperation setCompressAlgorithm(HeaderCard compressAlgorithmCard) {
318 compressAlgorithm = null;
319
320 if (compressAlgorithmCard == null) {
321 return this;
322 }
323
324 String algo = compressAlgorithmCard.getValue().toUpperCase(Locale.US);
325 compressAlgorithm = algo;
326
327 if (algo.equals(Compression.ZCMPTYPE_RICE_ONE) && FitsFactory.isAllowHeaderRepairs()) {
328 compressAlgorithm = Compression.ZCMPTYPE_RICE_1;
329 Logger.getLogger(HeaderCard.class.getName()).warning("Repaired non-standard ZCMPTYPE value: "
330 + Compression.ZCMPTYPE_RICE_ONE + " to " + Compression.ZCMPTYPE_RICE_1);
331 return this;
332 }
333
334 if (algo.equals(Compression.ZCMPTYPE_GZIP_1) || algo.equals(Compression.ZCMPTYPE_GZIP_2)
335 || algo.equals(Compression.ZCMPTYPE_RICE_1) || algo.equals(Compression.ZCMPTYPE_PLIO_1)
336 || algo.equals(Compression.ZCMPTYPE_HCOMPRESS_1) || algo.equals(Compression.ZCMPTYPE_NOCOMPRESS)) {
337 return this;
338 }
339
340 throw new FitsException("Invalid ZCMPTYPE value: " + algo);
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356 public TiledImageCompressionOperation setQuantAlgorithm(HeaderCard quantAlgorithmCard) {
357 synchronized (lock) {
358 quantAlgorithm = null;
359
360 if (quantAlgorithmCard == null) {
361 return this;
362 }
363
364 String algo = quantAlgorithmCard.getValue().toUpperCase();
365
366 if (algo.equals(Compression.ZQUANTIZ_NO_DITHER) || algo.equals(Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_1)
367 || algo.equals(Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_2)) {
368 quantAlgorithm = algo;
369 } else {
370 Logger.getLogger(HeaderCard.class.getName()).warning("Ignored invalid ZQUANTIZ value: " + algo);
371 }
372
373 return this;
374 }
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389 public String getQuantAlgorithm() {
390 synchronized (lock) {
391 return quantAlgorithm;
392 }
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407 public String getCompressAlgorithm() {
408 return compressAlgorithm;
409 }
410
411 private <T> T getNullableColumn(Header header, Class<T> class1, String columnName) throws FitsException {
412 for (int i = 1; i <= binaryTable.getNCols(); i++) {
413 String val = header.getStringValue(TTYPEn.n(i));
414 if (val != null && val.trim().equals(columnName)) {
415 return class1.cast(binaryTable.getColumn(i - 1));
416 }
417 }
418 return null;
419 }
420
421 private void processAllTiles() {
422 compressOptions();
423 ExecutorService threadPool = FitsFactory.threadPool();
424 for (TileCompressionOperation tileOperation : getTileOperations()) {
425 tileOperation.execute(threadPool);
426 }
427 for (TileCompressionOperation tileOperation : getTileOperations()) {
428 tileOperation.waitForResult();
429 }
430 }
431
432 private void readAxis(Header header) throws FitsException {
433 if (hasAxes()) {
434 return;
435 }
436 int naxis = header.getIntValue(ZNAXIS);
437 int[] axes = new int[naxis];
438 for (int i = 1; i <= naxis; i++) {
439 int axisValue = header.getIntValue(ZNAXISn.n(i), -1);
440 if (axisValue == -1) {
441 throw new FitsException("Required ZNAXISn not found");
442 }
443 axes[naxis - i] = axisValue;
444 }
445 setAxes(axes);
446 }
447
448 private void readBaseType(Header header) {
449 if (getBaseType() == null) {
450 int zBitPix = header.getIntValue(ZBITPIX);
451 ElementType<Buffer> elementType = ElementType.forNearestBitpix(zBitPix);
452 if (elementType == ElementType.UNKNOWN) {
453 throw new IllegalArgumentException("Illegal value for ZBITPIX: " + zBitPix);
454 }
455 setBaseType(elementType);
456 }
457 }
458
459 private void readCompressionHeaders(Header header) {
460 compressOptions().getCompressionParameters().getValuesFromHeader(new HeaderAccess(header));
461 }
462
463 private void readTileAxis(Header header) throws FitsException {
464 if (hasTileAxes()) {
465 return;
466 }
467
468 int naxes = getNAxes();
469 int[] tileAxes = new int[naxes];
470
471
472
473 for (int i = 1; i <= naxes; i++) {
474 tileAxes[naxes - i] = header.getIntValue(ZTILEn.n(i), i == 1 ? header.getIntValue(ZNAXISn.n(1)) : 1);
475 }
476
477 setTileAxes(tileAxes);
478 }
479
480 private <T> Object setInColumn(Object column, boolean predicate, TileCompressionOperation tileOperation, Class<T> clazz,
481 T value) {
482 if (predicate) {
483 if (column == null) {
484 column = Array.newInstance(clazz, getNumberOfTileOperations());
485 }
486 Array.set(column, tileOperation.getTileIndex(), value);
487 }
488 return column;
489 }
490
491 private void writeColumns(BinaryTableHDU hdu) throws FitsException {
492 Object compressedColumn = null;
493 Object uncompressedColumn = null;
494 Object gzipColumn = null;
495
496 synchronized (lock) {
497 for (TileCompressionOperation tileOperation : getTileOperations()) {
498 TileCompressionType compression = tileOperation.getCompressionType();
499 byte[] compressedData = tileOperation.getCompressedData();
500
501 compressedColumn = setInColumn(compressedColumn, compression == COMPRESSED, tileOperation, byte[].class,
502 compressedData);
503 gzipColumn = setInColumn(gzipColumn, compression == GZIP_COMPRESSED, tileOperation, byte[].class,
504 compressedData);
505 uncompressedColumn = setInColumn(uncompressedColumn, compression == UNCOMPRESSED, tileOperation,
506 byte[].class, compressedData);
507 }
508 setNullEntries(compressedColumn, new byte[0]);
509 setNullEntries(gzipColumn, new byte[0]);
510 setNullEntries(uncompressedColumn, new byte[0]);
511 addColumnToTable(hdu, compressedColumn, COMPRESSED_DATA_COLUMN);
512 addColumnToTable(hdu, gzipColumn, GZIP_COMPRESSED_DATA_COLUMN);
513 addColumnToTable(hdu, uncompressedColumn, UNCOMPRESSED_DATA_COLUMN);
514
515 if (imageNullPixelMask != null) {
516 addColumnToTable(hdu, imageNullPixelMask.getColumn(), NULL_PIXEL_MASK_COLUMN);
517 }
518 compressOptions.getCompressionParameters().addColumnsToTable(hdu);
519
520 hdu.getData().fillHeader(hdu.getHeader());
521 }
522 }
523
524 private void writeHeader(Header header) throws FitsException {
525 HeaderCardBuilder cardBuilder = header
526 .card(ZBITPIX).value(getBaseType().bitPix())
527 .card(ZCMPTYPE).value(compressAlgorithm);
528 int[] tileAxes = getTileAxes();
529 int naxes = tileAxes.length;
530 for (int i = 1; i <= naxes; i++) {
531 cardBuilder.card(ZTILEn.n(i)).value(tileAxes[naxes - i]);
532 }
533 compressOptions().getCompressionParameters().setValuesInHeader(new HeaderAccess(header));
534 if (imageNullPixelMask != null) {
535 cardBuilder.card(ZMASKCMP).value(imageNullPixelMask.getCompressAlgorithm());
536 }
537 }
538
539 protected BinaryTable getBinaryTable() {
540 return binaryTable;
541 }
542
543 protected ImageNullPixelMask getImageNullPixelMask() {
544 return imageNullPixelMask;
545 }
546
547 }