001    /* @(#)FitsHeader.java     $Revision: 1.10 $ $Date: 2003/04/11 08:41:15 $
002     *
003     * Copyright (C) 2002 European Southern Observatory 
004     * License:  GNU General Public License version 2 or later
005     */
006    package org.eso.fits;
007    
008    import java.util.*;
009    import java.io.*;
010    
011    /** FITS Header class which is an ordered set of FitsKeywords.
012     *
013     *  @version $Revision: 1.10 $ $Date: 2003/04/11 08:41:15 $
014     *  @author  P.Grosbol, DMD/ESO, <pgrosbol@eso.org>
015     */
016    public class FitsHeader {
017    
018        private Vector    keywords = null;
019        private Hashtable kwHash;
020        private Hashtable commentHash;
021        private int headerSpace = 0;
022    
023        /** Default constructor for empty FitsHeader class  */
024        public FitsHeader() {
025            keywords = new Vector(Fits.NOCARDS, Fits.NOCARDS);
026            kwHash = new Hashtable();
027            commentHash = new Hashtable();
028        }
029        
030        // John Talbot
031        private long getPosition(DataInput file) {
032            long position = 0;
033            try {
034                position = ((RandomAccessFile) file).getFilePointer();
035            } catch(Exception e) {
036                position = -1;
037            }
038            return position;
039        }
040    
041        /** Constructor for FitsHeader class given a DataInput file
042         *  positioned at the FITS header.
043         *
044         *  @param file    RandomAccess file positioned at the start of a
045         *                 FITS header
046         *  @exception FitsException */
047        public FitsHeader(DataInput file) throws FitsException {
048            byte record[] = new byte[Fits.RECORD];
049            byte line[] = new byte[Fits.CARD];
050            FitsKeyword  kw;
051    
052            try {                                       // check if FITS format
053    /*
054                try {  //KLUDGE: by John Talbot to support misplaced SDSS headers 
055                  RandomAccessFile raf = (RandomAccessFile) file;
056                  long pos = getPosition(file);
057                  byte b;
058                  do {
059                    b = file.readByte();
060                    pos++;
061                  } while (b != 'S' && b != 'X');  // Check first letter of 'S'IMPLE or 'X'TENSION keyword
062                  raf.seek(pos-1);
063                } catch(EOFException eof) {
064                  throw new FitsException("End of File", FitsException.HEADER);
065                }
066    */
067                file.readFully(record, 0, Fits.RECORD);
068                kw = new FitsKeyword(record);
069                if (!kw.getName().equals("SIMPLE") && !kw.getName().equals("XTENSION")) {
070                    throw new FitsException("Non standard record", FitsException.HEADER);
071                }
072            } catch (IOException e) {
073                throw new FitsException("No more data", FitsException.HEADER);
074            } catch (FitsException e) {
075                throw new FitsException("Unexpected error at position " + getPosition(file) + e, FitsException.HEADER);
076            }
077    
078            int  no_kw = 1;
079            keywords = new Vector(Fits.NOCARDS, Fits.NOCARDS);
080            no_kw = 0;
081            try {
082                int n = 0;
083                while (true) {
084                    for (int k=0; k<Fits.CARD; k++) {
085                        line[k] = record[n++];
086                    }
087                    kw = new FitsKeyword(line);
088                    keywords.setSize(no_kw++);
089                    keywords.addElement(kw);
090                    if (Fits.RECORD <= n) {
091                        file.readFully(record, 0, Fits.RECORD);
092                        n = 0;
093                    }
094                }
095            } catch (IOException e) {
096                throw new FitsException("Cannot read header", FitsException.HEADER);
097            } catch (FitsException e) {
098                if (e.getType() != FitsException.ENDCARD) {
099                    throw new FitsException("Bad FITS keyword (" + e + ")", FitsException.HEADER);
100                }
101            }
102    
103            int idx = keywords.size() - 1;   // remove trailing empty keywords
104            while (((FitsKeyword)keywords.elementAt(idx)).isEmpty()) {
105                keywords.removeElementAt(idx--);
106            }
107            keywords.trimToSize();
108    
109            headerSpace = (1+no_kw/Fits.NOCARDS)*Fits.NOCARDS;
110            kwHash = new Hashtable(keywords.size());
111            commentHash = new Hashtable();
112    
113            Enumeration  list = keywords.elements();
114            while (list.hasMoreElements()) {
115                kw = (FitsKeyword) list.nextElement();
116                hashKeyword(kw);
117            }
118        }
119    
120      /** Append FITS keyword to the end of the header.
121       *
122       *  @param kw  FitsKeyword to be appended */
123        public void addKeyword(FitsKeyword kw){
124            if (kw == null) return;
125            keywords.addElement(kw);
126            hashKeyword(kw);
127        }
128    
129        /** Insert FITS keyword at a given position in the header.
130         *
131         *  @param kw FitsKeyword to be appended
132         *  @param index place where the keyword should be inserted */
133        public void insertKeywordAt(FitsKeyword kw, int index){
134            if (kw == null) {
135                return;
136            }
137            keywords.insertElementAt(kw, index);
138            hashKeyword(kw);
139        }
140    
141        /** Remove FITS keyword at a given position in the header.
142         *
143         *  @param index location of keyword which should be removed */
144        public void removeKeywordAt(int index) {
145            FitsKeyword kw = (FitsKeyword) keywords.elementAt(index);
146            if (kw==null) {
147                return;
148            }
149            kwHash.remove(kw.getName());
150            keywords.removeElementAt(index);
151        }
152    
153        /** Add keyword to internal hash tables.
154         *
155         *  @param kw  FitsKeyword to be hashed */
156        private void hashKeyword(FitsKeyword kw){
157            if (kw == null) {
158                return;
159            }
160            if (kw.getType() == FitsKeyword.COMMENT) {
161                Vector vec = (Vector) commentHash.get(kw.getName());
162                if (vec == null) {
163                    vec = new Vector();
164                    commentHash.put(kw.getName(), vec);
165                }
166                vec.addElement(kw);
167            } else {
168                kwHash.put(kw.getName(), kw);
169            }
170        }
171    
172        /** Return the type of the FITS header e.g. Fits.IMAGE or Fits.BTABLE. */
173        final public int getType(){
174            if (keywords.size()<3) {
175                return Fits.FALSE;
176            }
177    
178            int type = Fits.FALSE;
179            FitsKeyword kw = (FitsKeyword) keywords.elementAt(0);
180            if (kw.getName().equals("SIMPLE") && kw.getBool()) {
181                kw = (FitsKeyword) kwHash.get("NAXIS1");
182                if ((kw != null) && (kw.getInt() == 0)) {
183                    kw = (FitsKeyword) kwHash.get("GROUPS");
184                    type = (((kw != null) && kw.getBool()))
185                        ? Fits.RGROUP : Fits.IMAGE;
186                } else {
187                    type = Fits.IMAGE;
188                }
189            } else if (kw.getName().equals("XTENSION")) {
190                if (kw.getString().startsWith("IMAGE")) {
191                    type = Fits.IMAGE;
192                } else if (kw.getString().startsWith("BINTABLE")) {
193                    type = Fits.BTABLE;
194                } else if (kw.getString().startsWith("TABLE")) {
195                    type = Fits.ATABLE;
196                } else {
197                    type = Fits.UNKNOWN;
198                }
199            }
200            return type;
201        }
202    
203        /** Compute size of FITS data matrix in bytes */
204        final public long getDataSize(){
205            if (kwHash == null) {
206                return 0;
207            }
208            int type = getType();
209            FitsKeyword kw;
210    
211            kw = (FitsKeyword) kwHash.get("NAXIS");  // get no. of axes
212            long naxis = kw.getInt();
213            if (naxis < 1) {
214                return 0;
215            }
216    
217            kw = (FitsKeyword) kwHash.get("BITPIX"); // get no. of bytes per value
218            long n_byte = Math.abs(kw.getInt())/8;
219    
220            long  d_size = 1;
221            for (int n=1; n<=naxis; n++) {
222                if (type==Fits.RGROUP && n==1) continue;
223                kw = (FitsKeyword) kwHash.get("NAXIS" + n);
224                d_size *= kw.getInt();
225            }
226    
227            kw = (FitsKeyword) kwHash.get("PCOUNT"); // add parameter block size
228            if (kw != null) {
229                d_size += kw.getInt();
230            }
231            kw = (FitsKeyword) kwHash.get("GCOUNT"); // multiple with group count
232            if (kw != null) {
233                d_size *= kw.getInt();
234            }
235            d_size *= n_byte;
236    
237            return d_size;
238        }
239    
240        /** Get name of FITS HUunit as given by the EXTNAME keyword.  If
241         *  this keyword is not present in the header, 'NONE' is returned.  */
242        final public String getName(){
243            FitsKeyword kw = (FitsKeyword) kwHash.get("EXTNAME");
244            if ((kw == null) || (kw.getType() != FitsKeyword.STRING)) {
245                return "NONE";
246            }
247          return kw.getString();
248      }
249    
250        /** Get version of FITS HUunit as given by the EXTVER keyword.  If
251         *  this keyword is not present in the header 1 is returned.  */
252        final public int getVersion(){
253            FitsKeyword kw = (FitsKeyword) kwHash.get("EXTVER");
254            if ((kw == null) || (kw.getType() != FitsKeyword.INTEGER)) {
255                return 1;
256            }
257            return (int) kw.getInt();
258        }
259    
260        /** Obtain the total number of keywords in the header.  */
261        final public int getNoKeywords(){
262            return keywords.size();
263        }
264    
265        /** Return a keyword giving its relative position.  If the position
266         *  is not in the valid range a NULL is returned.
267         *
268         *  @param  no  position of keyword in header (starting with 0) */
269        final public FitsKeyword getKeyword(int no){
270            if ((no < 0) || (keywords.size() <= no)) {
271                return (FitsKeyword) null;
272            }
273            return (FitsKeyword) keywords.elementAt(no);
274        }
275    
276        /** Return a keyword giving its name.  If there a multiple keywords,
277         *  the last is returned.  In cases where multiple keywords makes
278         *  sense (e.g. comments), the first of this set is given.  If none
279         *  is found, a NULL is returned.
280         *
281         *  @param  name  string with name of keyword */
282        final public FitsKeyword getKeyword(String name){
283            FitsKeyword kw = (FitsKeyword) kwHash.get(name);
284            if (kw == null) {
285                Vector vec = (Vector) commentHash.get(name);
286                if (vec != null) {
287                    kw = (FitsKeyword) vec.firstElement();
288                }
289            }
290            return kw;
291        }
292    
293        /** Return an array of keywords giving a name.  If none
294         *  is found, a NULL is returned.
295         *
296         *  @param  name  string with name of keyword */
297        final public FitsKeyword[] getKeywords(String name){
298            FitsKeyword kw = (FitsKeyword) kwHash.get(name);
299            if (kw == null) {
300                Vector vec = (Vector) commentHash.get(name);
301                if (vec == null) {
302                    return null;
303                }
304                FitsKeyword setKw[] = new FitsKeyword[vec.size()];
305                for (int n=0; n<vec.size(); n++) {
306                    setKw[n] = (FitsKeyword) vec.elementAt(n);
307                }
308                return setKw;     
309            }
310            FitsKeyword setKw[] = new FitsKeyword[1];
311            setKw[0] = kw;
312            return setKw;
313        }
314    
315        /** Obtain an Enumeration object for the keywords in the header.  */
316        final public Enumeration getKeywords(){
317            return keywords.elements();
318        }
319    
320        /** Generate a string with the FITS header.  The header string will
321         *  include the END-card and NOT be space filled with empty cards to
322         *  a full FITS 2880 char record. */
323        public String toString(){
324            int size = keywords.size();
325            StringBuffer  hd = new StringBuffer(Fits.CARD*size);
326    
327            Enumeration enumeration = keywords.elements();
328            while (enumeration.hasMoreElements()) {
329                FitsKeyword kw = (FitsKeyword) enumeration.nextElement();
330                hd.append(kw.toString());
331            }
332    
333            return hd.toString();
334        }
335    
336        /** Get space in FITS header read from file in terms of number of
337         *  keyword cards to can contain. */
338        public int getHeaderSpace(){
339            return headerSpace;
340        }
341    
342        /** Sets the first keyword in the header to XTENSION with the
343         *  type indicated.  Nothing will be done if a non-standard
344         *  extension is specified.
345         *
346         *  @param  type  Type of FITS extension
347         */
348        public void setExtension(int type) {
349            FitsKeyword kw;
350    
351            switch (type) {
352                case Fits.IMAGE :
353                    kw = new FitsKeyword("XTENSION", "IMAGE", "Image extension");
354                    break;
355                case Fits.BTABLE :
356                    kw = new FitsKeyword("XTENSION", "BINTABLE", "Binary table extension");
357                    break;
358                case Fits.ATABLE :
359                    kw = new FitsKeyword("XTENSION", "TABLE", "ASCII table extension");
360                    break;
361                default: return;
362            }
363            removeKeywordAt(0);
364            insertKeywordAt(kw, 0);
365        }
366    }