1
2
3
4
5
6
7 package org.astrogrid.datacenter.fits;
8
9 import java.io.BufferedInputStream;
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.net.URL;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.astrogrid.datacenter.fits.FitsReader;
18
19 /***
20 * A set of routines for reading a fits image
21 * <p>
22 * FITS images are binary representations of tables or pixel images, and are
23 * well known in the astronomical community (in 2003!). They consist of
24 * a number of 'lines' of 80 characters of ASCII keyword/value pairs, then
25 * the binary data itself.
26 * <p>
27 * (Should say more or give reference)
28 * <p>
29 * This particular class handles streamable fits images for reading purposes.
30 * Therefore you can pass it a remote url and call methods to process that url.
31 * However you can't do random access or writes on a url referenced file...
32 * <p>
33 * This is a 'lazy loader' - it only reads when required, but caches everything
34 * as it does.
35 */
36 public class FitsStreamReader implements FitsReader
37 {
38 String fitsName = null;
39 InputStream in = null;
40
41
42 Log log = LogFactory.getLog(FitsReader.class);
43
44
45 long totBytesRead = 0;
46
47 public FitsStreamReader(URL url) throws IOException
48 {
49 fitsName = url.toString();
50 InputStream urlIn = url.openStream();
51 assert urlIn != null : "Stream to URL "+url+" returned null";
52 in = new BufferedInputStream(urlIn);
53 }
54
55 public FitsStreamReader(File file) throws IOException
56 {
57 fitsName = file.toString();
58 in = new BufferedInputStream(new FileInputStream(file));
59 }
60
61 public FitsStreamReader(InputStream inStream)
62 {
63 if (inStream == null)
64 {
65 throw new IllegalArgumentException("InputStream cannot be null");
66 }
67
68 this.in = inStream;
69 }
70
71 /***
72 * Loads the header into a set of key/value pairs. NB this reads up to the
73 * END marker, or the optionally given stop keyword. This means that, if
74 * we know something of the file and only want to read useful information, we
75 * can stop before having to read the whole thing.
76 * <p>
77 * For example, some of the JBO fits files have huge HISTORY blocks, so we
78 * can specify "HISTORY AIP" as an 'endWord' and when they are encountered,
79 * the reader will stop reading.
80 * <p>
81 * To read the whole thing, right up to the end of the 2880 block boundary (ie
82 * ready to read the data block) see readHeader
83 */
84 public void readHeaderKeywords(FitsHeader header, String endWord) throws IOException
85 {
86 log.trace("Loading image header...");
87
88
89 byte[] block = new byte[80];
90
91 int lineNum = 0;
92 FitsKeyword keyword = null;
93
94 do {
95 lineNum++;
96
97 int bytesRead = in.read(block);
98 int lineBytesRead = bytesRead;
99
100
101
102 while ((lineBytesRead <80) && (bytesRead != -1))
103 {
104 bytesRead = in.read(block, lineBytesRead, 80-lineBytesRead);
105 lineBytesRead = lineBytesRead + bytesRead;
106 }
107 totBytesRead = totBytesRead + lineBytesRead;
108
109
110
111
112
113
114
115
116
117
118 for (int i=0;i<block.length;i++)
119 {
120 if ( ((block[i] >128) || (block[i] <32)) && (block[i] != 0) )
121 {
122 StringBuffer msg= new StringBuffer(
123 "Binary data encountered before header END in image "+fitsName+"\n"+
124 new String(block)+"\n");
125 for (int j=0;j<block.length;j++) msg.append("["+block[j]+"] ");
126
127 throw new FitsFormatException(msg.toString());
128 }
129 }
130 boolean isNuls = true;
131 for (int i=0;i<block.length;i++) {
132 if (block[i] != 0) {
133 isNuls = false;
134 break;
135 }
136 }
137 if (isNuls) throw new FitsFormatException("All-Nuls block in header of "+fitsName+", line "+lineNum);
138
139
140 keyword = new FitsKeyword(block);
141
142
143
144 if ( (keyword.getKey() != null) && (keyword.getKey().length() >0))
145 {
146 header.add(keyword);
147 log.trace("Line="+new String(block)+", Key="+keyword.getKey()+", Value='"+keyword.getValue()+"'");
148 }
149
150 } while ((keyword.getKey() == null) || !(keyword.getKey().toUpperCase().startsWith("END") || ((endWord != null) && (keyword.getKey().toUpperCase().startsWith(endWord.toUpperCase())))));
151 }
152
153
154 /***
155 * Read data block (actually just skips it for the moment)
156 * @todo there's a bug about - I've added a bit to skip an extra 28 cards
157 * because that's what seems to need to be done, but I have no idea why.
158 */
159 public void readData(FitsHdu hdu) throws IOException
160 {
161 log.trace("Skipping data (pos="+totBytesRead+")...");
162
163 if (hdu.getHeader().get("END") == null) {
164
165 readHeaderKeywords(hdu.getHeader(), null);
166 }
167
168
169 long r = totBytesRead % 2880;
170 skipTo(totBytesRead + r);
171
172
173 long size = hdu.getHeader().getDataSize();
174 log.trace("data size="+size);
175
176
177
178 skipTo(totBytesRead+size);
179
180
181 r = totBytesRead % 2880;
182 skipTo(totBytesRead + r);
183
184 if (size >0) skipTo(totBytesRead + 2880);
185
186 log.trace("read to pos "+totBytesRead);
187
188
189 }
190
191 /***
192 * Skips bytes up to given position in stream. A single 'skip()' call does
193 * not always skip that number of bytes, presumably depending on
194 */
195 public void skipTo(long targetPos) throws IOException
196 {
197 while (totBytesRead <targetPos)
198 {
199 long bytesRead = in.skip(targetPos - totBytesRead);
200
201 if (bytesRead == -1) {
202 throw new IOException("End of file");
203 }
204
205 totBytesRead = totBytesRead + bytesRead;
206 }
207 }
208
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256