Begin versioning.
[fits.git] / src / nom / tam / fits / ImageData.java
1 package nom.tam.fits;
2
3 import java.lang.reflect.Array;
4 import nom.tam.image.ImageTiler;
5 import nom.tam.util.*;
6 import java.io.*;
7
8
9 /* Copyright: Thomas McGlynn 1997-1999.
10  * This code may be used for any purpose, non-commercial
11  * or commercial so long as this copyright notice is retained
12  * in the source code or included in or referred to in any
13  * derived software.
14  *
15  * Many thanks to David Glowacki (U. Wisconsin) for substantial
16  * improvements, enhancements and bug fixes.
17  */
18 /** This class instantiates FITS primary HDU and IMAGE extension data.
19  * Essentially these data are a primitive multi-dimensional array.
20  * <p>
21  * Starting in version 0.9 of the FITS library, this routine
22  * allows users to defer the reading of images if the FITS
23  * data is being read from a file.  An ImageTiler object is
24  * supplied which can return an arbitrary subset of the image
25  * as a one dimensional array -- suitable for manipulation by
26  * standard Java libraries.  A call to the getData() method
27  * will still return a multi-dimensional array, but the
28  * image data will not be read until the user explicitly requests.
29  * it.
30  */
31 public class ImageData extends Data {
32
33     /** The size of the data */
34     long byteSize;
35     /** The actual array of data.  This
36      *  is normally a multi-dimensional primitive array.
37      *  It may be null until the getData() routine is
38      *  invoked, or it may be filled by during the read
39      *  call when a non-random access device is used.
40      */
41     Object dataArray;
42
43     /** This class describes an array */
44     protected class ArrayDesc {
45
46         int[] dims;
47         Class type;
48
49         ArrayDesc(int[] dims, Class type) {
50             this.dims = dims;
51             this.type = type;
52         }
53     }
54     /** A description of what the data should look like */
55     ArrayDesc dataDescription;
56
57     /** This inner class allows the ImageTiler
58      *  to see if the user has read in the data.
59      */
60     protected class ImageDataTiler extends nom.tam.image.ImageTiler {
61
62         ImageDataTiler(RandomAccess o, long offset, ArrayDesc d) {
63             super(o, offset, d.dims, d.type);
64         }
65
66         public Object getMemoryImage() {
67             return dataArray;
68         }
69     }
70     /** The image tiler associated with this image. */
71     private ImageTiler tiler;
72
73     /** Create an array from a header description.
74      * This is typically how data will be created when reading
75      * FITS data from a file where the header is read first.
76      * This creates an empty array.
77      * @param h header to be used as a template.
78      * @exception FitsException if there was a problem with the header description.
79      */
80     public ImageData(Header h) throws FitsException {
81
82         dataDescription = parseHeader(h);
83     }
84
85     protected ArrayDesc parseHeader(Header h) throws FitsException {
86
87         int bitpix;
88         int type;
89         int ndim;
90         int[] dims;
91
92         int i;
93
94         Object dataArray;
95
96         Class baseClass;
97
98
99         int gCount = h.getIntValue("GCOUNT", 1);
100         int pCount = h.getIntValue("PCOUNT", 0);
101         if (gCount > 1 || pCount != 0) {
102             throw new FitsException("Group data treated as images");
103         }
104
105         bitpix = h.getIntValue("BITPIX", 0);
106
107         if (bitpix == 8) {
108             baseClass = Byte.TYPE;
109         } else if (bitpix == 16) {
110             baseClass = Short.TYPE;
111         } else if (bitpix == 32) {
112             baseClass = Integer.TYPE;
113         } else if (bitpix == 64) {
114             baseClass = Long.TYPE;
115         } else if (bitpix == -32) {
116             baseClass = Float.TYPE;
117         } else if (bitpix == -64) {
118             baseClass = Double.TYPE;
119         } else {
120             throw new FitsException("Invalid BITPIX:" + bitpix);
121         }
122
123         ndim = h.getIntValue("NAXIS", 0);
124         dims = new int[ndim];
125
126
127         // Note that we have to invert the order of the axes
128         // for the FITS file to get the order in the array we
129         // are generating.
130
131         byteSize = 1;
132         for (i = 0; i < ndim; i += 1) {
133             int cdim = h.getIntValue("NAXIS" + (i + 1), 0);
134             if (cdim < 0) {
135                 throw new FitsException("Invalid array dimension:" + cdim);
136             }
137             byteSize *= cdim;
138             dims[ndim - i - 1] = cdim;
139         }
140         byteSize *= Math.abs(bitpix) / 8;
141         if (ndim == 0) {
142             byteSize = 0;
143         }
144         return new ArrayDesc(dims, baseClass);
145     }
146
147     /** Create the equivalent of a null data element.
148      */
149     public ImageData() {
150         dataArray = new byte[0];
151         byteSize = 0;
152     }
153
154     /** Create an ImageData object using the specified object to
155      * initialize the data array.
156      * @param x The initial data array.  This should be a primitive
157      *          array but this is not checked currently.
158      */
159     public ImageData(Object x) {
160         dataArray = x;
161         byteSize = ArrayFuncs.computeLSize(x);
162     }
163
164     /** Fill header with keywords that describe
165      * image data.
166      * @param head The FITS header
167      * @exception FitsException if the object does not contain
168      *          valid image data.
169      */
170     protected void fillHeader(Header head) throws FitsException {
171
172
173         if (dataArray == null) {
174             head.nullImage();
175             return;
176         }
177
178         String classname = dataArray.getClass().getName();
179
180         int[] dimens = ArrayFuncs.getDimensions(dataArray);
181
182         if (dimens == null || dimens.length == 0) {
183             throw new FitsException("Image data object not array");
184         }
185
186
187         int bitpix;
188         switch (classname.charAt(dimens.length)) {
189             case 'B':
190                 bitpix = 8;
191                 break;
192             case 'S':
193                 bitpix = 16;
194                 break;
195             case 'I':
196                 bitpix = 32;
197                 break;
198             case 'J':
199                 bitpix = 64;
200                 break;
201             case 'F':
202                 bitpix = -32;
203                 break;
204             case 'D':
205                 bitpix = -64;
206                 break;
207             default:
208                 throw new FitsException("Invalid Object Type for FITS data:"
209                         + classname.charAt(dimens.length));
210         }
211
212         // if this is neither a primary header nor an image extension,
213         // make it a primary header
214         head.setSimple(true);
215         head.setBitpix(bitpix);
216         head.setNaxes(dimens.length);
217
218         for (int i = 1; i <= dimens.length; i += 1) {
219             if (dimens[i - 1] == -1) {
220                 throw new FitsException("Unfilled array for dimension: " + i);
221             }
222             head.setNaxis(i, dimens[dimens.length - i]);
223         }
224         head.addValue("EXTEND", true,"ntf::imagedata:extend:1");  // Just in case!
225         head.addValue("PCOUNT", 0, "ntf::imagedata:pcount:1");
226         head.addValue("GCOUNT", 1, "ntf::imagedata:gcount:1");
227
228     }
229
230     public void read(ArrayDataInput i) throws FitsException {
231
232         // Don't need to read null data (noted by Jens Knudstrup)
233         if (byteSize == 0) {
234             return;
235         }
236         setFileOffset(i);
237
238
239         if (i instanceof RandomAccess) {
240             tiler = new ImageDataTiler((RandomAccess) i,
241                     ((RandomAccess) i).getFilePointer(),
242                     dataDescription);
243             try {
244                 // Handle long skips.
245                 i.skipBytes(byteSize);
246             } catch (IOException e) {
247                 throw new FitsException("Unable to skip over image:" + e);
248             }
249
250         } else {
251             dataArray = ArrayFuncs.newInstance(dataDescription.type,
252                     dataDescription.dims);
253             try {
254                 i.readLArray(dataArray);
255             } catch (IOException e) {
256                 throw new FitsException("Unable to read image data:" + e);
257             }
258
259             tiler = new ImageDataTiler(null, 0, dataDescription);
260         }
261
262         int pad = FitsUtil.padding(getTrueSize());
263         try {
264             i.skipBytes(pad);
265         } catch (EOFException e) {
266             throw new PaddingException("Error skipping padding after image", this);
267         } catch (IOException e) {
268             throw new FitsException("Error skipping padding after image");
269         }
270     }
271
272     public void write(ArrayDataOutput o) throws FitsException {
273
274         // Don't need to write null data (noted by Jens Knudstrup)
275         if (byteSize == 0) {
276             return;
277         }
278
279         if (dataArray == null) {
280             if (tiler != null) {
281
282                 // Need to read in the whole image first.
283                 try {
284                     dataArray = tiler.getCompleteImage();
285                 } catch (IOException e) {
286                     throw new FitsException("Error attempting to fill image");
287                 }
288
289             } else if (dataArray == null && dataDescription != null) {
290                 // Need to create an array to match a specified header.
291                 dataArray = ArrayFuncs.newInstance(dataDescription.type,
292                         dataDescription.dims);
293
294             } else {
295                 // This image isn't ready to be written!
296                 throw new FitsException("Null image data");
297             }
298         }
299
300         try {
301             o.writeArray(dataArray);
302         } catch (IOException e) {
303             throw new FitsException("IO Error on image write" + e);
304         }
305
306         FitsUtil.pad(o, getTrueSize());
307     }
308
309     /** Get the size in bytes of the data */
310     protected long getTrueSize() {
311         return byteSize;
312     }
313
314     /** Return the actual data.
315      *  Note that this may return a null when
316      *  the data is not readable.  It might be better
317      *  to throw a FitsException, but this is
318      *  a very commonly called method and we prefered
319      *  not to change how users must invoke it.
320      */
321     public Object getData() {
322
323         if (dataArray == null && tiler != null) {
324             try {
325                 dataArray = tiler.getCompleteImage();
326             } catch (Exception e) {
327                 return null;
328             }
329         }
330
331         return dataArray;
332     }
333
334     void setTiler(ImageTiler tiler) {
335         this.tiler = tiler;
336     }
337
338     public ImageTiler getTiler() {
339         return tiler;
340     }
341 }