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 }