View Javadoc

1   /*
2    $Id: FitsStreamReader.java,v 1.2 2004/10/06 21:41:01 mch Exp $
3   
4    Copyright (c) etc
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     //int dataOffset = -1; //how big is the header. -1 = not loaded yet.
41     //   Hashtable header = null; //header key/value pairs. null = not loaded yet
42     Log log = LogFactory.getLog(FitsReader.class);
43     
44     /* total number of bytes read */
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        //read a line (80 bytes) at a time until we reach END
89        byte[] block = new byte[80];
90        //for (int j=0;j<block.length;j++) block[j]=5;  //debug to check for nulls
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          //must read the complete 80 bytes - bear in mind we might be
101          //loading off a remote link
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          //debug
110          //String t="";
111          //for (int j=0;j<block.length;j++) t=t+"["+block[j]+"] ";
112          //Log.trace(t);
113          
114          //quick check for binary data, just in case there is something
115          //wrong with the header we don't want to have to read the whole
116          // image
117          //you get some nulls in some text lines, on the other hand you also get some complete 0 blocks...
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          //parse keyword
140          keyword = new FitsKeyword(block);
141 
142          //add keyword if not null - *do* want to add END so that we can check that
143          //the whole lot has been read
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       //first make sure we have read up to the header END keyword
163       if (hdu.getHeader().get("END") == null) {
164          
165          readHeaderKeywords(hdu.getHeader(), null);
166       }
167       
168       //then make up to nearest 2880 block
169       long r = totBytesRead % 2880;
170       skipTo(totBytesRead + r);
171       
172       //now read data
173       long size = hdu.getHeader().getDataSize();
174       log.trace("data size="+size);
175       
176 //    hdu.setData(new FitsData());
177       //skip data block.  NB sometimes one single 'skip' read is not sufficient
178       skipTo(totBytesRead+size);
179       
180       //then make up to nearest 2880 block
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  $Log: FitsStreamReader.java,v $
213  Revision 1.2  2004/10/06 21:41:01  mch
214  Removed string appending in loop
215 
216  Revision 1.1  2004/09/28 15:02:13  mch
217  Merged PAL and server packages
218 
219  Revision 1.6  2004/09/07 14:52:00  mch
220  Fixes etc for SEC
221 
222  Revision 1.5  2004/09/07 01:39:27  mch
223  Moved email keys from TargetIndicator to Slinger
224 
225  Revision 1.4  2004/07/12 23:25:58  mch
226  Better error reporting
227 
228  Revision 1.3  2004/03/12 04:45:26  mch
229  It05 MCH Refactor
230 
231  Revision 1.2  2004/01/13 00:33:14  nw
232  Merged in branch providing
233  * sql pass-through
234  * replace Certification by User
235  * Rename _query as Query
236 
237  Revision 1.1.10.1  2004/01/08 09:43:40  nw
238  replaced adql front end with a generalized front end that accepts
239  a range of query languages (pass-thru sql at the moment)
240 
241  Revision 1.1  2003/11/28 18:21:03  mch
242  Stream implementation of FitsReader
243 
244  Revision 1.2  2003/11/26 18:46:55  mch
245  First attempt to generate index from FITS files
246 
247  Revision 1.1  2003/11/25 11:04:11  mch
248  New FITS io package
249 
250  Revision 1.1.1.1  2003/08/25 18:36:27  mch
251  Reimported to fit It02 source structure
252 
253  Revision 1.1  2003/07/03 18:14:51  mch
254  Fits file handling
255 
256  */