View Javadoc
1   package nom.tam.image.compression.hdu;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.List;
6   import java.util.Locale;
7   
8   import nom.tam.fits.BinaryTable;
9   import nom.tam.fits.FitsException;
10  import nom.tam.fits.FitsFactory;
11  import nom.tam.fits.Header;
12  import nom.tam.fits.header.Compression;
13  import nom.tam.fits.header.Standard;
14  import nom.tam.image.compression.bintable.BinaryTableTile;
15  import nom.tam.image.compression.bintable.BinaryTableTileCompressor;
16  import nom.tam.image.compression.bintable.BinaryTableTileDecompressor;
17  import nom.tam.image.compression.bintable.BinaryTableTileDescription;
18  import nom.tam.util.ColumnTable;
19  
20  /*
21   * #%L
22   * nom.tam FITS library
23   * %%
24   * Copyright (C) 1996 - 2024 nom-tam-fits
25   * %%
26   * This is free and unencumbered software released into the public domain.
27   *
28   * Anyone is free to copy, modify, publish, use, compile, sell, or
29   * distribute this software, either in source code form or as a compiled
30   * binary, for any purpose, commercial or non-commercial, and by any
31   * means.
32   *
33   * In jurisdictions that recognize copyright laws, the author or authors
34   * of this software dedicate any and all copyright interest in the
35   * software to the public domain. We make this dedication for the benefit
36   * of the public at large and to the detriment of our heirs and
37   * successors. We intend this dedication to be an overt act of
38   * relinquishment in perpetuity of all present and future rights to this
39   * software under copyright law.
40   *
41   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
44   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
45   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
46   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
47   * OTHER DEALINGS IN THE SOFTWARE.
48   * #L%
49   */
50  
51  import static nom.tam.fits.header.Standard.TFIELDS;
52  import static nom.tam.image.compression.bintable.BinaryTableTileDescription.tile;
53  
54  /**
55   * FITS representation of a compressed binary table. It itself is a binary table, but one in which each row represents
56   * the compressed image of one or more rows of the original table.
57   * 
58   * @see CompressedTableHDU
59   */
60  @SuppressWarnings("deprecation")
61  public class CompressedTableData extends BinaryTable {
62  
63      private static final List<String> ALLOWED_ALGORITHMS = Arrays.asList(Compression.ZCMPTYPE_GZIP_1,
64              Compression.ZCMPTYPE_GZIP_2, Compression.ZCMPTYPE_RICE_1, Compression.ZCMPTYPE_NOCOMPRESS);
65  
66      private int rowsPerTile;
67  
68      private List<BinaryTableTile> tiles;
69  
70      private BinaryTable orig;
71  
72      /** Only add new var-length column in the preparation step once */
73      private boolean isPrepped;
74  
75      private String[] colAlgorithm;
76  
77      /**
78       * Creates a new empty compressed table data to be initialized at a later point
79       */
80      public CompressedTableData() {
81      }
82  
83      /**
84       * Creates a new compressed table data based on the prescription of the supplied header.
85       * 
86       * @param  header        The header that describes the compressed table
87       * 
88       * @throws FitsException If the header is invalid or could not be accessed.
89       */
90      public CompressedTableData(Header header) throws FitsException {
91          super(header);
92          rowsPerTile = header.getIntValue(Compression.ZTILELEN, header.getIntValue(Standard.NAXIS2));
93          setColumnCompressionAlgorithms(header);
94      }
95  
96      /**
97       * (<i>for internal use</i>) This should only be called by {@link CompressedTableHDU}, and should have reduced
98       * visibility accordingly.
99       * 
100      * @param  header        the compressed header
101      * 
102      * @throws FitsException if the table cannot be compressed.
103      */
104     public void compress(Header header) throws FitsException {
105         discardVLAs();
106 
107         // If table has only fixed-length data, we can compress in parallel, and defragment after.
108         for (BinaryTableTile tile : tiles) {
109             tile.execute(FitsFactory.threadPool());
110         }
111 
112         for (BinaryTableTile tile : tiles) {
113             tile.waitForResult();
114         }
115     }
116 
117     @Override
118     public synchronized long defragment() throws FitsException {
119         if (orig != null && orig.containsHeap()) {
120             // Don't defragment if the original had VLAs, since these are stored on the heap
121             // with a dual-set of descriptors, includeing compressed ones on the heap itself
122             // which are not trivial to de-fragment.
123             return 0L;
124         }
125         return super.defragment();
126     }
127 
128     @Override
129     public void fillHeader(Header h) throws FitsException {
130         super.fillHeader(h);
131 
132         h.setNaxis(2, getData().getNRows());
133         h.addValue(Compression.ZTABLE, true);
134         h.addValue(Compression.ZTILELEN, getRowsPerTile());
135 
136         for (int i = 0; i < getNCols(); i++) {
137             h.findCard(Compression.ZFORMn.n(i + 1));
138             h.addValue(Compression.ZCTYPn.n(i + 1), getAlgorithm(i));
139         }
140 
141         h.deleteKey(Compression.ZIMAGE);
142     }
143 
144     void prepareUncompressedData(BinaryTable fromTable) throws FitsException {
145         orig = fromTable;
146         prepareUncompressedData(orig.getData());
147     }
148 
149     /**
150      * @deprecated      (<i>for internal use</i>) This should only be called by {@link CompressedTableHDU}, and its
151      *                      visibility will be reduced accordingly in the future, not to mention that it should take a
152      *                      BinaryTable as its argument.
153      * 
154      * @param      data The original (uncompressed) table data.
155      */
156     @Deprecated
157     @SuppressWarnings("javadoc")
158     public void prepareUncompressedData(ColumnTable<?> data) throws FitsException {
159         tiles = new ArrayList<>();
160 
161         int nrows = data.getNRows();
162         int ncols = data.getNCols();
163 
164         if (!isPrepped) {
165             // Create compressed columns...
166             for (int column = 0; column < ncols; column++) {
167                 addColumn(BinaryTable.ColumnDesc.createForVariableSize(byte.class));
168                 getDescriptor(column).name(null);
169             }
170 
171             // Initialized compressed rows...
172             for (int rowStart = 0; rowStart < nrows; rowStart += getRowsPerTile()) {
173                 addRow(new byte[ncols][0]);
174             }
175         }
176 
177         // Changed tile-order in 1.19.1 to be in row-major table order.
178         for (int column = 0; column < ncols; column++) {
179             for (int tileIndex = 0, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += getRowsPerTile()) {
180 
181                 BinaryTableTileDescription td = tile()//
182                         .rowStart(rowStart)//
183                         .rowEnd(Math.min(nrows, rowStart + getRowsPerTile()))//
184                         .column(column)//
185                         .tileIndex(tileIndex + 1)//
186                         .compressionAlgorithm(getAlgorithm(column));
187 
188                 BinaryTableTileCompressor tile = (orig == null) ? new BinaryTableTileCompressor(this, data, td) :
189                         new BinaryTableTileCompressor(this, orig, td);
190 
191                 tiles.add(tile);
192             }
193         }
194 
195         isPrepped = true;
196     }
197 
198     /**
199      * (<i>for internal use</i>) No longer used, and it may be removed in the future.
200      */
201     @SuppressWarnings("javadoc")
202     protected BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader)
203             throws FitsException {
204         return asBinaryTable(toTable, compressedHeader, targetHeader, 0);
205     }
206 
207     BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader, int fromTile)
208             throws FitsException {
209         int nrows = targetHeader.getIntValue(Standard.NAXIS2);
210         int ncols = compressedHeader.getIntValue(TFIELDS);
211         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nrows);
212 
213         ensureData();
214         setColumnCompressionAlgorithms(compressedHeader);
215 
216         BinaryTable.createColumnDataFor(toTable);
217 
218         List<BinaryTableTile> tileList = new ArrayList<>();
219 
220         for (int tileIndex = fromTile, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += tileSize) {
221             for (int column = 0; column < ncols; column++) {
222                 BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, toTable, tile()//
223                         .rowStart(rowStart)//
224                         .rowEnd(Math.min(nrows, rowStart + tileSize))//
225                         .column(column)//
226                         .tileIndex(tileIndex + 1)//
227                         .compressionAlgorithm(getAlgorithm(column)));
228                 tileList.add(tile);
229 
230                 tile.execute(FitsFactory.threadPool());
231             }
232         }
233 
234         for (BinaryTableTile tile : tileList) {
235             tile.waitForResult();
236         }
237 
238         return toTable;
239     }
240 
241     Object getColumnData(int col, int fromTile, int toTile, Header compressedHeader, Header targetHeader)
242             throws FitsException {
243 
244         if (fromTile < 0 || fromTile >= getNRows()) {
245             throw new IllegalArgumentException("start tile " + fromTile + " is outof bounds for " + getNRows() + " tiles.");
246         }
247 
248         if (toTile > getNRows()) {
249             throw new IllegalArgumentException("end tile " + toTile + " is outof bounds for " + getNRows() + " tiles.");
250         }
251 
252         if (toTile <= fromTile) {
253             return null;
254         }
255 
256         setColumnCompressionAlgorithms(compressedHeader);
257 
258         int nr = targetHeader.getIntValue(Standard.NAXIS2);
259 
260         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nr);
261         int nRows = (toTile - fromTile) * tileSize;
262 
263         if (nRows > nr) {
264             nRows = nr;
265         }
266 
267         ColumnDesc c = getDescriptor(targetHeader, col);
268         class UncompressedTable extends BinaryTable {
269             @Override
270             public void createTable(int nRows) throws FitsException {
271                 super.createTable(nRows);
272             }
273         }
274 
275         UncompressedTable data = new UncompressedTable();
276         data.addColumn(c);
277         data.createTable(nRows);
278 
279         List<BinaryTableTile> tileList = new ArrayList<>();
280 
281         String algorithm = compressedHeader.getStringValue(Compression.ZCTYPn.n(col + 1));
282 
283         for (int tileIndex = fromTile, rowStart = 0; rowStart < nRows; tileIndex++, rowStart += tileSize) {
284             BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, data, tile()//
285                     .rowStart(rowStart)//
286                     .rowEnd(Math.min(nr, rowStart + tileSize))//
287                     .column(col)//
288                     .tileIndex(tileIndex + 1)//
289                     .compressionAlgorithm(algorithm));
290             tile.decompressToColumn(0);
291             tileList.add(tile);
292 
293             tile.execute(FitsFactory.threadPool());
294         }
295 
296         for (BinaryTableTile tile : tileList) {
297             tile.waitForResult();
298         }
299 
300         return data.getColumn(0);
301     }
302 
303     /**
304      * Returns the number of original (uncompressed) table rows that are compressed as a block into a single compressed
305      * table row.
306      * 
307      * @return the number of table rows compressed together as a block.
308      */
309     protected final synchronized int getRowsPerTile() {
310         return rowsPerTile;
311     }
312 
313     private String getAlgorithm(int column) {
314         if (colAlgorithm != null && column < colAlgorithm.length && colAlgorithm[column] != null) {
315             return colAlgorithm[column];
316         }
317         return Compression.ZCMPTYPE_GZIP_2;
318     }
319 
320     /**
321      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
322      * {@link CompressedTableHDU}.
323      */
324     @SuppressWarnings("javadoc")
325     protected void setColumnCompressionAlgorithms(String[] columnCompressionAlgorithms) {
326         for (String algo : columnCompressionAlgorithms) {
327             if (!ALLOWED_ALGORITHMS.contains(algo.toUpperCase(Locale.US))) {
328                 throw new IllegalArgumentException(algo + " cannot be used to compress tables.");
329             }
330         }
331 
332         this.colAlgorithm = columnCompressionAlgorithms;
333     }
334 
335     private void setColumnCompressionAlgorithms(Header header) {
336         int ncols = header.getIntValue(TFIELDS);
337 
338         // Default compression algorithm, unless specified...
339         colAlgorithm = new String[ncols];
340 
341         // Set the compression algorithms specified.
342         for (int column = 0; column < ncols; column++) {
343             colAlgorithm[column] = header.getStringValue(Compression.ZCTYPn.n(column + 1));
344         }
345     }
346 
347     /**
348      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
349      * {@link CompressedTableHDU}.
350      */
351     @SuppressWarnings("javadoc")
352     protected synchronized CompressedTableData setRowsPerTile(int value) {
353         rowsPerTile = value;
354         return this;
355     }
356 }