View Javadoc
1   package nom.tam.image.tile.operation;
2   
3   /*
4    * #%L
5    * nom.tam FITS library
6    * %%
7    * Copyright (C) 1996 - 2024 nom-tam-fits
8    * %%
9    * This is free and unencumbered software released into the public domain.
10   *
11   * Anyone is free to copy, modify, publish, use, compile, sell, or
12   * distribute this software, either in source code form or as a compiled
13   * binary, for any purpose, commercial or non-commercial, and by any
14   * means.
15   *
16   * In jurisdictions that recognize copyright laws, the author or authors
17   * of this software dedicate any and all copyright interest in the
18   * software to the public domain. We make this dedication for the benefit
19   * of the public at large and to the detriment of our heirs and
20   * successors. We intend this dedication to be an overt act of
21   * relinquishment in perpetuity of all present and future rights to this
22   * software under copyright law.
23   *
24   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
28   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
29   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30   * OTHER DEALINGS IN THE SOFTWARE.
31   * #L%
32   */
33  
34  import java.lang.reflect.Array;
35  import java.nio.Buffer;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  
39  import nom.tam.fits.FitsException;
40  import nom.tam.util.ArrayFuncs;
41  import nom.tam.util.type.ElementType;
42  
43  /**
44   * A base implementation of 2D image tile compression.
45   *
46   * @param <OPERATION> The generic type of tile operation that handles parallel processing
47   */
48  public abstract class AbstractTiledImageOperation<OPERATION extends ITileOperation> implements ITiledImageOperation {
49  
50      /** Image axes in Java array index order (is is last!). */
51      private int[] axes;
52  
53      /**
54       * Interprets the value of the BITPIX keyword in the uncompressed FITS image
55       */
56      private ElementType<Buffer> baseType;
57  
58      /** Tile dimensions in Java array index order (x is last!) */
59      private int[] tileAxes;
60  
61      private OPERATION[] tileOperations;
62  
63      private final Class<OPERATION> operationClass;
64  
65      /**
66       * Creates a new tiling foperation.
67       * 
68       * @param      operationClass the class of tile operation.
69       * 
70       * @deprecated                (<i>for internal use</i>) This constructor should have protected visibility.
71       */
72      public AbstractTiledImageOperation(Class<OPERATION> operationClass) {
73          this.operationClass = operationClass;
74      }
75  
76      @Override
77      public ElementType<Buffer> getBaseType() {
78          return baseType;
79      }
80  
81      /**
82       * Returns the number of elements that a buffer must have to store the entire image.
83       * 
84       * @return The number of points in the full image.
85       */
86      public int getBufferSize() {
87          int bufferSize = 1;
88          for (int axisValue : axes) {
89              bufferSize *= axisValue;
90          }
91          return bufferSize;
92      }
93  
94      @Override
95      public int getImageWidth() {
96          return axes[axes.length - 1];
97      }
98  
99      @Override
100     public OPERATION getTileOperation(int i) {
101         return tileOperations[i];
102     }
103 
104     /**
105      * Sets the image dimensions, in Java array index order.
106      *
107      * @param axes Image dimensions in Java array index order (x is last!).
108      */
109     public void setAxes(int[] axes) {
110         this.axes = Arrays.copyOf(axes, axes.length);
111     }
112 
113     /**
114      * <p>
115      * Sets the tile dimension. Here the dimensions are in Java array index order, that is the x-dimension (width of
116      * tile) is last!
117      * </p>
118      * <p>
119      * Note, that because tile compression is essentially 2D, the tile sizes in higher dimensions will be forced to 1,
120      * even if specified otherwise by the argument (see
121      * <a href="https://heasarc.gsfc.nasa.gov/docs/software/fitsio/compression.html">FITSIO convention</a>).
122      * </p>
123      *
124      * @param  value         The tile dimensions in Java array index order (x is last!). Only up to the last 2
125      *                           components are considered. The rest will be assumed to have values equals to 1.
126      *
127      * @throws FitsException If the leading dimensions (before the last 2) have sizes not equal to 1
128      */
129     public void setTileAxes(int[] value) throws FitsException {
130         for (int i = value.length - 2; --i >= 0;) {
131             if (value[i] != 1) {
132                 throw new FitsException("Tile sizes in higher dimensions (>2) must be 1 as per the FITSIO convention (" + i
133                         + ":" + value[i] + ")");
134             }
135         }
136         tileAxes = Arrays.copyOf(value, value.length);
137     }
138 
139     /**
140      * Checks if the image size has been defined.
141      * 
142      * @return <code>true</code> if the size of the image to be tiled has been set, otherwise <code>false</code>.
143      */
144     protected boolean hasAxes() {
145         return axes != null;
146     }
147 
148     /**
149      * Checks if the tiling has been defined and tile sizes are set.
150      * 
151      * @return <code>true</code> if the tile sizes have been defined already, otherwise <code>false</code>
152      */
153     protected boolean hasTileAxes() {
154         return tileAxes != null;
155     }
156 
157     private int getBufferOffset(int[] index) {
158         int l = 0;
159         int blockSize = 1;
160         for (int i = index.length; --i >= 0;) {
161             l += index[i] * blockSize;
162             blockSize *= axes[i];
163         }
164         return l;
165     }
166 
167     /**
168      * Creates a tiling pattern for an image.
169      * 
170      * @param  init          the parameters that determine the tiling pattern
171      * 
172      * @throws FitsException if the parameters are invalid.
173      */
174     @SuppressWarnings("unchecked")
175     protected void createTiles(ITileOperationInitialisation<OPERATION> init) throws FitsException {
176         int[] offset = new int[axes.length]; // Tile start in image (Java index order)
177         int[] tileSize = new int[2]; // {w, h}
178         int pos = 0;
179 
180         int imLength = 1;
181         for (int i = axes.length; --i >= 0;) {
182             imLength *= axes[i];
183         }
184 
185         tileSize[1] = 1;
186 
187         // If tile is not defined along all axes, pad with 1.
188         if (tileAxes.length < axes.length) {
189             int[] tile = new int[axes.length];
190             System.arraycopy(tileAxes, 0, tile, tile.length - tileAxes.length, tileAxes.length);
191             Arrays.fill(tile, 0, tile.length - tileAxes.length, 1);
192             tileAxes = tile;
193         }
194 
195         ArrayList<OPERATION> opList = new ArrayList<>();
196 
197         // Create 2D tiles to cover image (in N dimensions, where N need not be 2)
198         for (int tileIndex = 0; pos < imLength; tileIndex++) {
199             // Calculate the actual size of the current tile
200             for (int i = Math.min(tileSize.length, tileAxes.length); --i >= 0;) {
201                 int k = axes.length - 1 - i;
202                 tileSize[i] = (offset[k] + tileAxes[k] > axes[k]) ? axes[k] - offset[k] : tileAxes[k];
203             }
204 
205             // Create the tile at the current buffer offset and tile size
206             OPERATION op = init.createTileOperation(tileIndex, new TileArea().start(ArrayFuncs.getReversed(offset)));
207             op.setDimensions(pos, tileSize[0], tileSize[1]);
208             opList.add(op);
209 
210             // Calculate the image indices where the next tile starts.
211             for (int k = axes.length; --k >= 0;) {
212                 offset[k] += tileAxes[k]; // Try next tile along the current dimension...
213                 if (offset[k] < axes[k]) {
214                     break; // OK, tile is within image bounds
215                 }
216                 if (k > 0) {
217                     offset[k] = 0; // Otherwise reset the tile pos in the subarray dimensions
218                 }
219             }
220 
221             // Calculate the buffer position where the next tile starts.
222             pos = getBufferOffset(offset);
223         }
224 
225         tileOperations = (OPERATION[]) Array.newInstance(operationClass, opList.size());
226         opList.toArray(tileOperations);
227 
228         init.tileCount(tileOperations.length);
229 
230         for (OPERATION op : tileOperations) {
231             init.init(op);
232         }
233     }
234 
235     /**
236      * Returns the dimensionality of the image.
237      * 
238      * @return the dimensinality of the image, that is the number of cartesian axes it contains.
239      */
240     protected int getNAxes() {
241         return axes.length;
242     }
243 
244     /**
245      * Returns the number of tile operations that are needed to cover a tiled image.
246      * 
247      * @return the number of tiles in the image.
248      */
249     protected int getNumberOfTileOperations() {
250         return tileOperations.length;
251     }
252 
253     /**
254      * Returns the reference to the tile dimensions array. The dimensions are stored in Java array index order, i.e.,
255      * the x-dimension (width) is last.
256      *
257      * @return The tile dimensions in Java array index order (x is last!).
258      */
259     protected int[] getTileAxes() {
260         return tileAxes;
261     }
262 
263     /**
264      * Returns an array of parallel tile oprations, which cover the full image.
265      * 
266      * @return an array of parallel tile operations.
267      */
268     protected OPERATION[] getTileOperations() {
269         return tileOperations;
270     }
271 
272     /**
273      * Sets the FITS element type that is contained in the tiles for this operation.
274      * 
275      * @param baseType the FITS element type of data in the tile.
276      */
277     protected void setBaseType(ElementType<Buffer> baseType) {
278         this.baseType = baseType;
279     }
280 }