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     @SuppressWarnings("javadoc")
157     public void prepareUncompressedData(ColumnTable<?> data) throws FitsException {
158         tiles = new ArrayList<>();
159 
160         int nrows = data.getNRows();
161         int ncols = data.getNCols();
162 
163         if (!isPrepped) {
164             // Create compressed columns...
165             for (int column = 0; column < ncols; column++) {
166                 addColumn(BinaryTable.ColumnDesc.createForVariableSize(byte.class));
167                 getDescriptor(column).name(null);
168             }
169 
170             // Initialized compressed rows...
171             for (int rowStart = 0; rowStart < nrows; rowStart += getRowsPerTile()) {
172                 addRow(new byte[ncols][0]);
173             }
174         }
175 
176         // Changed tile-order in 1.19.1 to be in row-major table order.
177         for (int column = 0; column < ncols; column++) {
178             for (int tileIndex = 0, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += getRowsPerTile()) {
179 
180                 BinaryTableTileDescription td = tile()//
181                         .rowStart(rowStart)//
182                         .rowEnd(Math.min(nrows, rowStart + getRowsPerTile()))//
183                         .column(column)//
184                         .tileIndex(tileIndex + 1)//
185                         .compressionAlgorithm(getAlgorithm(column));
186 
187                 BinaryTableTileCompressor tile = (orig == null) ? new BinaryTableTileCompressor(this, data, td) :
188                         new BinaryTableTileCompressor(this, orig, td);
189 
190                 tiles.add(tile);
191             }
192         }
193 
194         isPrepped = true;
195     }
196 
197     /**
198      * (<i>for internal use</i>) No longer used, and it may be removed in the future.
199      */
200     @SuppressWarnings("javadoc")
201     protected BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader)
202             throws FitsException {
203         return asBinaryTable(toTable, compressedHeader, targetHeader, 0);
204     }
205 
206     BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader, int fromTile)
207             throws FitsException {
208         int nrows = targetHeader.getIntValue(Standard.NAXIS2);
209         int ncols = compressedHeader.getIntValue(TFIELDS);
210         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nrows);
211 
212         ensureData();
213         setColumnCompressionAlgorithms(compressedHeader);
214 
215         BinaryTable.createColumnDataFor(toTable);
216 
217         List<BinaryTableTile> tileList = new ArrayList<>();
218 
219         for (int tileIndex = fromTile, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += tileSize) {
220             for (int column = 0; column < ncols; column++) {
221                 BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, toTable, tile()//
222                         .rowStart(rowStart)//
223                         .rowEnd(Math.min(nrows, rowStart + tileSize))//
224                         .column(column)//
225                         .tileIndex(tileIndex + 1)//
226                         .compressionAlgorithm(getAlgorithm(column)));
227                 tileList.add(tile);
228 
229                 tile.execute(FitsFactory.threadPool());
230             }
231         }
232 
233         for (BinaryTableTile tile : tileList) {
234             tile.waitForResult();
235         }
236 
237         return toTable;
238     }
239 
240     Object getColumnData(int col, int fromTile, int toTile, Header compressedHeader, Header targetHeader)
241             throws FitsException {
242 
243         if (fromTile < 0 || fromTile >= getNRows()) {
244             throw new IllegalArgumentException("start tile " + fromTile + " is outof bounds for " + getNRows() + " tiles.");
245         }
246 
247         if (toTile > getNRows()) {
248             throw new IllegalArgumentException("end tile " + toTile + " is outof bounds for " + getNRows() + " tiles.");
249         }
250 
251         if (toTile <= fromTile) {
252             return null;
253         }
254 
255         setColumnCompressionAlgorithms(compressedHeader);
256 
257         int nr = targetHeader.getIntValue(Standard.NAXIS2);
258 
259         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nr);
260         int nRows = (toTile - fromTile) * tileSize;
261 
262         if (nRows > nr) {
263             nRows = nr;
264         }
265 
266         ColumnDesc c = getDescriptor(targetHeader, col);
267         class UncompressedTable extends BinaryTable {
268             @Override
269             public void createTable(int nRows) throws FitsException {
270                 super.createTable(nRows);
271             }
272         }
273 
274         UncompressedTable data = new UncompressedTable();
275         data.addColumn(c);
276         data.createTable(nRows);
277 
278         List<BinaryTableTile> tileList = new ArrayList<>();
279 
280         String algorithm = compressedHeader.getStringValue(Compression.ZCTYPn.n(col + 1));
281 
282         for (int tileIndex = fromTile, rowStart = 0; rowStart < nRows; tileIndex++, rowStart += tileSize) {
283             BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, data, tile()//
284                     .rowStart(rowStart)//
285                     .rowEnd(Math.min(nr, rowStart + tileSize))//
286                     .column(col)//
287                     .tileIndex(tileIndex + 1)//
288                     .compressionAlgorithm(algorithm));
289             tile.decompressToColumn(0);
290             tileList.add(tile);
291 
292             tile.execute(FitsFactory.threadPool());
293         }
294 
295         for (BinaryTableTile tile : tileList) {
296             tile.waitForResult();
297         }
298 
299         return data.getColumn(0);
300     }
301 
302     /**
303      * Returns the number of original (uncompressed) table rows that are compressed as a block into a single compressed
304      * table row.
305      * 
306      * @return the number of table rows compressed together as a block.
307      */
308     protected final synchronized int getRowsPerTile() {
309         return rowsPerTile;
310     }
311 
312     private String getAlgorithm(int column) {
313         if (colAlgorithm != null && column < colAlgorithm.length && colAlgorithm[column] != null) {
314             return colAlgorithm[column];
315         }
316         return Compression.ZCMPTYPE_GZIP_2;
317     }
318 
319     /**
320      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
321      * {@link CompressedTableHDU}.
322      */
323     @SuppressWarnings("javadoc")
324     protected void setColumnCompressionAlgorithms(String[] columnCompressionAlgorithms) {
325         for (String algo : columnCompressionAlgorithms) {
326             if (!ALLOWED_ALGORITHMS.contains(algo.toUpperCase(Locale.US))) {
327                 throw new IllegalArgumentException(algo + " cannot be used to compress tables.");
328             }
329         }
330 
331         this.colAlgorithm = columnCompressionAlgorithms;
332     }
333 
334     private void setColumnCompressionAlgorithms(Header header) {
335         int ncols = header.getIntValue(TFIELDS);
336 
337         // Default compression algorithm, unless specified...
338         colAlgorithm = new String[ncols];
339 
340         // Set the compression algorithms specified.
341         for (int column = 0; column < ncols; column++) {
342             colAlgorithm[column] = header.getStringValue(Compression.ZCTYPn.n(column + 1));
343         }
344     }
345 
346     /**
347      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
348      * {@link CompressedTableHDU}.
349      */
350     @SuppressWarnings("javadoc")
351     protected synchronized CompressedTableData setRowsPerTile(int value) {
352         rowsPerTile = value;
353         return this;
354     }
355 }