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      /** For thread synchronization */
78      private Object lock = new Object();
79  
80      /**
81       * Creates a new empty compressed table data to be initialized at a later point
82       */
83      public CompressedTableData() {
84      }
85  
86      /**
87       * Creates a new compressed table data based on the prescription of the supplied header.
88       * 
89       * @param  header        The header that describes the compressed table
90       * 
91       * @throws FitsException If the header is invalid or could not be accessed.
92       */
93      public CompressedTableData(Header header) throws FitsException {
94          super(header);
95          rowsPerTile = header.getIntValue(Compression.ZTILELEN, header.getIntValue(Standard.NAXIS2));
96          setColumnCompressionAlgorithms(header);
97      }
98  
99      /**
100      * (<i>for internal use</i>) This should only be called by {@link CompressedTableHDU}, and should have reduced
101      * visibility accordingly.
102      * 
103      * @param  header        the compressed header
104      * 
105      * @throws FitsException if the table cannot be compressed.
106      */
107     public void compress(Header header) throws FitsException {
108         discardVLAs();
109 
110         // If table has only fixed-length data, we can compress in parallel, and defragment after.
111         for (BinaryTableTile tile : tiles) {
112             tile.execute(FitsFactory.threadPool());
113         }
114 
115         for (BinaryTableTile tile : tiles) {
116             tile.waitForResult();
117         }
118     }
119 
120     @Override
121     public long defragment() throws FitsException {
122         synchronized (lock) {
123             if (orig != null && orig.containsHeap()) {
124                 // Don't defragment if the original had VLAs, since these are stored on the heap
125                 // with a dual-set of descriptors, includeing compressed ones on the heap itself
126                 // which are not trivial to de-fragment.
127                 return 0L;
128             }
129             return super.defragment();
130         }
131     }
132 
133     @Override
134     public void fillHeader(Header h) throws FitsException {
135         super.fillHeader(h);
136 
137         h.setNaxis(2, getData().getNRows());
138         h.addValue(Compression.ZTABLE, true);
139         h.addValue(Compression.ZTILELEN, getRowsPerTile());
140 
141         for (int i = 0; i < getNCols(); i++) {
142             h.findCard(Compression.ZFORMn.n(i + 1));
143             h.addValue(Compression.ZCTYPn.n(i + 1), getAlgorithm(i));
144         }
145 
146         h.deleteKey(Compression.ZIMAGE);
147     }
148 
149     void prepareUncompressedData(BinaryTable fromTable) throws FitsException {
150         orig = fromTable;
151         prepareUncompressedData(orig.getData());
152     }
153 
154     /**
155      * @deprecated      (<i>for internal use</i>) This should only be called by {@link CompressedTableHDU}, and its
156      *                      visibility will be reduced accordingly in the future, not to mention that it should take a
157      *                      BinaryTable as its argument.
158      * 
159      * @param      data The original (uncompressed) table data.
160      */
161     @Deprecated
162     @SuppressWarnings("javadoc")
163     public void prepareUncompressedData(ColumnTable<?> data) throws FitsException {
164         tiles = new ArrayList<>();
165 
166         int nrows = data.getNRows();
167         int ncols = data.getNCols();
168 
169         if (!isPrepped) {
170             // Create compressed columns...
171             for (int column = 0; column < ncols; column++) {
172                 addColumn(BinaryTable.ColumnDesc.createForVariableSize(byte.class));
173                 getDescriptor(column).name(null);
174             }
175 
176             // Initialized compressed rows...
177             for (int rowStart = 0; rowStart < nrows; rowStart += getRowsPerTile()) {
178                 addRow(new byte[ncols][0]);
179             }
180         }
181 
182         // Changed tile-order in 1.19.1 to be in row-major table order.
183         for (int column = 0; column < ncols; column++) {
184             for (int tileIndex = 0, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += getRowsPerTile()) {
185 
186                 BinaryTableTileDescription td = tile()//
187                         .rowStart(rowStart)//
188                         .rowEnd(Math.min(nrows, rowStart + getRowsPerTile()))//
189                         .column(column)//
190                         .tileIndex(tileIndex + 1)//
191                         .compressionAlgorithm(getAlgorithm(column));
192 
193                 BinaryTableTileCompressor tile = (orig == null) ? new BinaryTableTileCompressor(this, data, td) :
194                         new BinaryTableTileCompressor(this, orig, td);
195 
196                 tiles.add(tile);
197             }
198         }
199 
200         isPrepped = true;
201     }
202 
203     /**
204      * (<i>for internal use</i>) No longer used, and it may be removed in the future.
205      */
206     @SuppressWarnings("javadoc")
207     protected BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader)
208             throws FitsException {
209         return asBinaryTable(toTable, compressedHeader, targetHeader, 0);
210     }
211 
212     BinaryTable asBinaryTable(BinaryTable toTable, Header compressedHeader, Header targetHeader, int fromTile)
213             throws FitsException {
214         int nrows = targetHeader.getIntValue(Standard.NAXIS2);
215         int ncols = compressedHeader.getIntValue(TFIELDS);
216         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nrows);
217 
218         ensureData();
219         setColumnCompressionAlgorithms(compressedHeader);
220 
221         BinaryTable.createColumnDataFor(toTable);
222 
223         List<BinaryTableTile> tileList = new ArrayList<>();
224 
225         for (int tileIndex = fromTile, rowStart = 0; rowStart < nrows; tileIndex++, rowStart += tileSize) {
226             for (int column = 0; column < ncols; column++) {
227                 BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, toTable, tile()//
228                         .rowStart(rowStart)//
229                         .rowEnd(Math.min(nrows, rowStart + tileSize))//
230                         .column(column)//
231                         .tileIndex(tileIndex + 1)//
232                         .compressionAlgorithm(getAlgorithm(column)));
233                 tileList.add(tile);
234 
235                 tile.execute(FitsFactory.threadPool());
236             }
237         }
238 
239         for (BinaryTableTile tile : tileList) {
240             tile.waitForResult();
241         }
242 
243         return toTable;
244     }
245 
246     Object getColumnData(int col, int fromTile, int toTile, Header compressedHeader, Header targetHeader)
247             throws FitsException {
248 
249         if (fromTile < 0 || fromTile >= getNRows()) {
250             throw new IllegalArgumentException("start tile " + fromTile + " is outof bounds for " + getNRows() + " tiles.");
251         }
252 
253         if (toTile > getNRows()) {
254             throw new IllegalArgumentException("end tile " + toTile + " is outof bounds for " + getNRows() + " tiles.");
255         }
256 
257         if (toTile <= fromTile) {
258             return null;
259         }
260 
261         setColumnCompressionAlgorithms(compressedHeader);
262 
263         int nr = targetHeader.getIntValue(Standard.NAXIS2);
264 
265         int tileSize = compressedHeader.getIntValue(Compression.ZTILELEN, nr);
266         int nRows = (toTile - fromTile) * tileSize;
267 
268         if (nRows > nr) {
269             nRows = nr;
270         }
271 
272         ColumnDesc c = getDescriptor(targetHeader, col);
273         class UncompressedTable extends BinaryTable {
274             @Override
275             public void createTable(int nRows) throws FitsException {
276                 super.createTable(nRows);
277             }
278         }
279 
280         UncompressedTable data = new UncompressedTable();
281         data.addColumn(c);
282         data.createTable(nRows);
283 
284         List<BinaryTableTile> tileList = new ArrayList<>();
285 
286         String algorithm = compressedHeader.getStringValue(Compression.ZCTYPn.n(col + 1));
287 
288         for (int tileIndex = fromTile, rowStart = 0; rowStart < nRows; tileIndex++, rowStart += tileSize) {
289             BinaryTableTileDecompressor tile = new BinaryTableTileDecompressor(this, data, tile()//
290                     .rowStart(rowStart)//
291                     .rowEnd(Math.min(nr, rowStart + tileSize))//
292                     .column(col)//
293                     .tileIndex(tileIndex + 1)//
294                     .compressionAlgorithm(algorithm));
295             tile.decompressToColumn(0);
296             tileList.add(tile);
297 
298             tile.execute(FitsFactory.threadPool());
299         }
300 
301         for (BinaryTableTile tile : tileList) {
302             tile.waitForResult();
303         }
304 
305         return data.getColumn(0);
306     }
307 
308     /**
309      * Returns the number of original (uncompressed) table rows that are compressed as a block into a single compressed
310      * table row.
311      * 
312      * @return the number of table rows compressed together as a block.
313      */
314     protected final int getRowsPerTile() {
315         synchronized (lock) {
316             return rowsPerTile;
317         }
318     }
319 
320     private String getAlgorithm(int column) {
321         if (colAlgorithm != null && column < colAlgorithm.length && colAlgorithm[column] != null) {
322             return colAlgorithm[column];
323         }
324         return Compression.ZCMPTYPE_GZIP_2;
325     }
326 
327     /**
328      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
329      * {@link CompressedTableHDU}.
330      */
331     @SuppressWarnings("javadoc")
332     protected void setColumnCompressionAlgorithms(String[] columnCompressionAlgorithms) {
333         for (String algo : columnCompressionAlgorithms) {
334             if (!ALLOWED_ALGORITHMS.contains(algo.toUpperCase(Locale.US))) {
335                 throw new IllegalArgumentException(algo + " cannot be used to compress tables.");
336             }
337         }
338 
339         this.colAlgorithm = columnCompressionAlgorithms;
340     }
341 
342     private void setColumnCompressionAlgorithms(Header header) {
343         int ncols = header.getIntValue(TFIELDS);
344 
345         // Default compression algorithm, unless specified...
346         colAlgorithm = new String[ncols];
347 
348         // Set the compression algorithms specified.
349         for (int column = 0; column < ncols; column++) {
350             colAlgorithm[column] = header.getStringValue(Compression.ZCTYPn.n(column + 1));
351         }
352     }
353 
354     /**
355      * (<i>for internal use</i>) Visibility may be reduced to the package level. This should only be called by
356      * {@link CompressedTableHDU}.
357      */
358     @SuppressWarnings("javadoc")
359     protected CompressedTableData setRowsPerTile(int value) {
360         synchronized (lock) {
361             rowsPerTile = value;
362             return this;
363         }
364     }
365 }