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 }