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 if (algo.equals(Compression.ZCMPTYPE_GZIP_1) || algo.equals(Compression.ZCMPTYPE_GZIP_2)
307 || algo.equals(Compression.ZCMPTYPE_RICE_1) || algo.equals(Compression.ZCMPTYPE_PLIO_1)
308 || algo.equals(Compression.ZCMPTYPE_HCOMPRESS_1) || algo.equals(Compression.ZCMPTYPE_NOCOMPRESS)) {
309 compressAlgorithm = algo;
310 } else {
311 Logger.getLogger(HeaderCard.class.getName()).warning("Ignored invalid ZCMPTYPE value: " + algo);
312 }
313
314 return this;
315 }
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330 public synchronized TiledImageCompressionOperation setQuantAlgorithm(HeaderCard quantAlgorithmCard) {
331 quantAlgorithm = null;
332
333 if (quantAlgorithmCard == null) {
334 return this;
335 }
336
337 String algo = quantAlgorithmCard.getValue().toUpperCase();
338
339 if (algo.equals(Compression.ZQUANTIZ_NO_DITHER) || algo.equals(Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_1)
340 || algo.equals(Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_2)) {
341 quantAlgorithm = algo;
342 } else {
343 Logger.getLogger(HeaderCard.class.getName()).warning("Ignored invalid ZQUANTIZ value: " + algo);
344 }
345
346 return this;
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360
361 public synchronized String getQuantAlgorithm() {
362 return quantAlgorithm;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public String getCompressAlgorithm() {
378 return compressAlgorithm;
379 }
380
381 private <T> T getNullableColumn(Header header, Class<T> class1, String columnName) throws FitsException {
382 for (int i = 1; i <= binaryTable.getNCols(); i++) {
383 String val = header.getStringValue(TTYPEn.n(i));
384 if (val != null && val.trim().equals(columnName)) {
385 return class1.cast(binaryTable.getColumn(i - 1));
386 }
387 }
388 return null;
389 }
390
391 private void processAllTiles() {
392 compressOptions();
393 ExecutorService threadPool = FitsFactory.threadPool();
394 for (TileCompressionOperation tileOperation : getTileOperations()) {
395 tileOperation.execute(threadPool);
396 }
397 for (TileCompressionOperation tileOperation : getTileOperations()) {
398 tileOperation.waitForResult();
399 }
400 }
401
402 private void readAxis(Header header) throws FitsException {
403 if (hasAxes()) {
404 return;
405 }
406 int naxis = header.getIntValue(ZNAXIS);
407 int[] axes = new int[naxis];
408 for (int i = 1; i <= naxis; i++) {
409 int axisValue = header.getIntValue(ZNAXISn.n(i), -1);
410 if (axisValue == -1) {
411 throw new FitsException("Required ZNAXISn not found");
412 }
413 axes[naxis - i] = axisValue;
414 }
415 setAxes(axes);
416 }
417
418 private void readBaseType(Header header) {
419 if (getBaseType() == null) {
420 int zBitPix = header.getIntValue(ZBITPIX);
421 ElementType<Buffer> elementType = ElementType.forNearestBitpix(zBitPix);
422 if (elementType == ElementType.UNKNOWN) {
423 throw new IllegalArgumentException("Illegal value for ZBITPIX: " + zBitPix);
424 }
425 setBaseType(elementType);
426 }
427 }
428
429 private void readCompressionHeaders(Header header) {
430 compressOptions().getCompressionParameters().getValuesFromHeader(new HeaderAccess(header));
431 }
432
433 private void readTileAxis(Header header) throws FitsException {
434 if (hasTileAxes()) {
435 return;
436 }
437
438 int naxes = getNAxes();
439 int[] tileAxes = new int[naxes];
440
441
442
443 for (int i = 1; i <= naxes; i++) {
444 tileAxes[naxes - i] = header.getIntValue(ZTILEn.n(i), i == 1 ? header.getIntValue(ZNAXISn.n(1)) : 1);
445 }
446
447 setTileAxes(tileAxes);
448 }
449
450 private <T> Object setInColumn(Object column, boolean predicate, TileCompressionOperation tileOperation, Class<T> clazz,
451 T value) {
452 if (predicate) {
453 if (column == null) {
454 column = Array.newInstance(clazz, getNumberOfTileOperations());
455 }
456 Array.set(column, tileOperation.getTileIndex(), value);
457 }
458 return column;
459 }
460
461 private synchronized void writeColumns(BinaryTableHDU hdu) throws FitsException {
462 Object compressedColumn = null;
463 Object uncompressedColumn = null;
464 Object gzipColumn = null;
465 for (TileCompressionOperation tileOperation : getTileOperations()) {
466 TileCompressionType compression = tileOperation.getCompressionType();
467 byte[] compressedData = tileOperation.getCompressedData();
468
469 compressedColumn = setInColumn(compressedColumn, compression == COMPRESSED, tileOperation, byte[].class,
470 compressedData);
471 gzipColumn = setInColumn(gzipColumn, compression == GZIP_COMPRESSED, tileOperation, byte[].class,
472 compressedData);
473 uncompressedColumn = setInColumn(uncompressedColumn, compression == UNCOMPRESSED, tileOperation, byte[].class,
474 compressedData);
475 }
476 setNullEntries(compressedColumn, new byte[0]);
477 setNullEntries(gzipColumn, new byte[0]);
478 setNullEntries(uncompressedColumn, new byte[0]);
479 addColumnToTable(hdu, compressedColumn, COMPRESSED_DATA_COLUMN);
480 addColumnToTable(hdu, gzipColumn, GZIP_COMPRESSED_DATA_COLUMN);
481 addColumnToTable(hdu, uncompressedColumn, UNCOMPRESSED_DATA_COLUMN);
482 if (imageNullPixelMask != null) {
483 addColumnToTable(hdu, imageNullPixelMask.getColumn(), NULL_PIXEL_MASK_COLUMN);
484 }
485 compressOptions.getCompressionParameters().addColumnsToTable(hdu);
486 hdu.getData().fillHeader(hdu.getHeader());
487 }
488
489 private void writeHeader(Header header) throws FitsException {
490 HeaderCardBuilder cardBuilder = header
491 .card(ZBITPIX).value(getBaseType().bitPix())
492 .card(ZCMPTYPE).value(compressAlgorithm);
493 int[] tileAxes = getTileAxes();
494 int naxes = tileAxes.length;
495 for (int i = 1; i <= naxes; i++) {
496 cardBuilder.card(ZTILEn.n(i)).value(tileAxes[naxes - i]);
497 }
498 compressOptions().getCompressionParameters().setValuesInHeader(new HeaderAccess(header));
499 if (imageNullPixelMask != null) {
500 cardBuilder.card(ZMASKCMP).value(imageNullPixelMask.getCompressAlgorithm());
501 }
502 }
503
504 protected BinaryTable getBinaryTable() {
505 return binaryTable;
506 }
507
508 protected ImageNullPixelMask getImageNullPixelMask() {
509 return imageNullPixelMask;
510 }
511
512 }