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