Begin versioning.
[fits.git] / src / nom / tam / image / ImageTiler.java
1 package nom.tam.image;
2
3 import nom.tam.util.*;
4 import java.lang.reflect.Array;
5 import java.io.IOException;
6
7 /** This class provides a subset of an N-dimensional image.
8  *  Modified May 2, 2000 by T. McGlynn to permit
9  *  tiles that go off the edge of the image.
10  */
11 public class ImageTiler {
12
13     RandomAccess f;
14     long fileOffset;
15     int[] dims;
16     Class base;
17
18     /** Create a tiler.
19      * @param f         The random access device from which image data may be read.
20      *                  This may be null if the tile information is available from
21      *                  memory.
22      * @param fileOffset  The file offset within the RandomAccess device at which
23      *                  the data begins.
24      * @param dims      The actual dimensions of the image.
25      * @param base      The base class (should be a primitive type) of the image.
26      */
27     public ImageTiler(RandomAccess f, long fileOffset, int[] dims,
28             Class base) {
29         this.f = f;
30         this.fileOffset = fileOffset;
31         this.dims = dims;
32         this.base = base;
33     }
34
35     /** See if we can get the image data from memory.
36      *  This may be overriden by other classes, notably
37      *  in nom.tam.fits.ImageData.
38      */
39     public Object getMemoryImage() {
40         return null;
41     }
42
43     /** Get a subset of the image.  An image tile is returned
44      *  as a one-dimensional array although the image will
45      *  normally be multi-dimensional.
46      *  @param The starting corner (using 0 as the start) for the image.
47      *  @param The length requested in each dimension.
48      */
49     public Object getTile(int[] corners, int[] lengths) throws IOException {
50
51         if (corners.length != dims.length || lengths.length != dims.length) {
52             throw new IOException("Inconsistent sub-image request");
53         }
54
55         int arraySize = 1;
56         for (int i = 0; i < dims.length; i += 1) {
57
58             if (corners[i] < 0 || lengths[i] < 0 || corners[i] + lengths[i] > dims[i]) {
59                 throw new IOException("Sub-image not within image");
60             }
61
62             arraySize *= lengths[i];
63         }
64
65         Object outArray = ArrayFuncs.newInstance(base, arraySize);
66
67         getTile(outArray, corners, lengths);
68         return outArray;
69     }
70
71     /** Get a tile, filling in a prespecified array.
72      *  This version does not check that the user hase
73      *  entered a valid set of corner and length arrays.
74      *  ensure that out matches the
75      *  length implied by the lengths array.
76      *
77      *  @param  outArray        The output tile array.  A one-dimensional
78      *                          array.
79      *                          Data not within the valid limits of the image will
80      *                          be left unchanged.  The length of this
81      *                          array should be the product of lengths.
82      *  @param  corners         The corners of the tile.
83      *  @param  lengths         The dimensions of the tile.
84      *
85      */
86     public void getTile(Object outArray, int[] corners, int[] lengths)
87             throws IOException {
88
89         Object data = getMemoryImage();
90
91         if (data == null && f == null) {
92             throw new IOException("No data source for tile subset");
93         }
94         fillTile(data, outArray, dims, corners, lengths);
95     }
96
97     /** Fill the subset.
98      *  @param          data    The memory-resident data image.
99      *                          This may be null if the image is to
100      *                          be read from a file.  This should
101      *                          be a multi-dimensional primitive array.
102      *  @param          o       The tile to be filled.  This is a
103      *                          simple primitive array.
104      *  @param          dims    The dimensions of the full image.
105      *  @param          corners The indices of the corner of the image.
106      *  @param          lengths The dimensions of the subset.
107      */
108     protected void fillTile(Object data, Object o, int[] dims, int[] corners, int[] lengths)
109             throws IOException {
110
111
112         int n = dims.length;
113         int[] posits = new int[n];
114         int baseLength = ArrayFuncs.getBaseLength(o);
115         int segment = lengths[n - 1];
116
117         System.arraycopy(corners, 0, posits, 0, n);
118         long currentOffset = 0;
119         if (data == null) {
120             currentOffset = f.getFilePointer();
121         }
122
123         int outputOffset = 0;
124
125
126         do {
127
128             // This implies there is some overlap
129             // in the last index (in conjunction
130             // with other tests)
131
132             int mx = dims.length - 1;
133             boolean validSegment =
134                     posits[mx] + lengths[mx] >= 0
135                     && posits[mx] < dims[mx];
136
137
138             // Don't do anything for the current
139             // segment if anything but the
140             // last index is out of range.
141
142             if (validSegment) {
143                 for (int i = 0; i < mx; i += 1) {
144                     if (posits[i] < 0 || posits[i] >= dims[i]) {
145                         validSegment = false;
146                         break;
147                     }
148                 }
149             }
150
151             if (validSegment) {
152                 if (data != null) {
153                     fillMemData(data, posits, segment, o, outputOffset, 0);
154                 } else {
155                     int offset = getOffset(dims, posits) * baseLength;
156
157                     // Point to offset at real beginning
158                     // of segment
159                     int actualLen = segment;
160                     int actualOffset = offset;
161                     int actualOutput = outputOffset;
162                     if (posits[mx] < 0) {
163                         actualOffset -= posits[mx] * baseLength;
164                         actualOutput -= posits[mx];
165                         actualLen += posits[mx];
166                     }
167                     if (posits[mx] + segment > dims[mx]) {
168                         actualLen -= posits[mx] + segment - dims[mx];
169                     }
170                     fillFileData(o, actualOffset, actualOutput, actualLen);
171                 }
172             }
173             outputOffset += segment;
174
175         } while (incrementPosition(corners, posits, lengths));
176         if (data == null) {
177             f.seek(currentOffset);
178         }
179     }
180
181     /** Fill a single segment from memory.
182      *  This routine is called recursively to handle multi-dimensional
183      *  arrays.  E.g., if data is three-dimensional, this will
184      *  recurse two levels until we get a call with a single dimensional
185      *  datum.  At that point the appropriate data will be copied
186      *  into the output.
187      *
188      *  @param data     The in-memory image data.
189      *  @param posits   The current position for which data is requested.
190      *  @param length   The size of the segments.
191      *  @param output   The output tile.
192      *  @param outputOffset The current offset into the output tile.
193      *  @param dim      The current dimension being
194      */
195     protected void fillMemData(Object data, int[] posits, int length,
196             Object output, int outputOffset, int dim) {
197
198
199         if (data instanceof Object[]) {
200
201             Object[] xo = (Object[]) data;
202             fillMemData(xo[posits[dim]], posits, length, output, outputOffset, dim + 1);
203
204         } else {
205
206             // Adjust the spacing for the actual copy.
207             int startFrom = posits[dim];
208             int startTo = outputOffset;
209             int copyLength = length;
210
211             if (posits[dim] < 0) {
212                 startFrom -= posits[dim];
213                 startTo -= posits[dim];
214                 copyLength += posits[dim];
215             }
216             if (posits[dim] + length > dims[dim]) {
217                 copyLength -= (posits[dim] + length - dims[dim]);
218             }
219
220             System.arraycopy(data, startFrom, output, startTo, copyLength);
221         }
222     }
223
224     /** File a tile segment from a file.
225      *  @param output           The output tile.
226      *  @param delta            The offset from the beginning of the image in bytes.
227      *  @param outputOffset     The index into the output array.
228      *  @param segment          The number of elements to be read for this segment.
229      */
230     protected void fillFileData(Object output, int delta, int outputOffset,
231             int segment) throws IOException {
232
233
234         f.seek(fileOffset + delta);
235
236         if (base == float.class) {
237             f.read((float[]) output, outputOffset, segment);
238         } else if (base == int.class) {
239             f.read((int[]) output, outputOffset, segment);
240         } else if (base == short.class) {
241             f.read((short[]) output, outputOffset, segment);
242         } else if (base == double.class) {
243             f.read((double[]) output, outputOffset, segment);
244         } else if (base == byte.class) {
245             f.read((byte[]) output, outputOffset, segment);
246         } else if (base == char.class) {
247             f.read((char[]) output, outputOffset, segment);
248         } else if (base == long.class) {
249             f.read((long[]) output, outputOffset, segment);
250         } else {
251             throw new IOException("Invalid type for tile array");
252         }
253     }
254
255     /** Increment the offset within the position array.
256      *  Note that we never look at the last index since
257      *  we copy data a block at a time and not byte by byte.
258      *  @param  start   The starting corner values.
259      *  @param  current The current offsets.
260      *  @param  lengths The desired dimensions of the subset.
261      */
262     protected static boolean incrementPosition(int[] start,
263             int[] current,
264             int[] lengths) {
265
266         for (int i = start.length - 2; i >= 0; i -= 1) {
267             if (current[i] - start[i] < lengths[i] - 1) {
268                 current[i] += 1;
269                 for (int j = i + 1; j < start.length - 1; j += 1) {
270                     current[j] = start[j];
271                 }
272                 return true;
273             }
274         }
275         return false;
276     }
277
278     /** Get the offset of a given position.
279      *  @param dims  The dimensions of the array.
280      *  @param pos   The index requested.
281      */
282     public static final int getOffset(int[] dims, int[] pos) {
283
284         int offset = 0;
285         for (int i = 0; i < dims.length; i += 1) {
286             if (i > 0) {
287                 offset *= dims[i];
288             }
289             offset += pos[i];
290         }
291         return offset;
292     }
293
294     /** Read the entire image into a multidimensional
295      * array.
296      */
297     public Object getCompleteImage() throws IOException {
298
299         if (f == null) {
300             throw new IOException("Attempt to read from null file");
301         }
302         long currentOffset = f.getFilePointer();
303         Object o = ArrayFuncs.newInstance(base, dims);
304         f.seek(fileOffset);
305         f.readLArray(o);
306         f.seek(currentOffset);
307         return o;
308     }
309 }
310