package nom.tam.fits; /* Copyright: Thomas McGlynn 1997-1998. * This code may be used for any purpose, non-commercial * or commercial so long as this copyright notice is retained * in the source code or included in or referred to in any * derived software. * * Many thanks to David Glowacki (U. Wisconsin) for substantial * improvements, enhancements and bug fixes. */ import nom.tam.util.ArrayFuncs; /** This class defines the methods for accessing FITS binary table header * information. */ public class BinaryTableHeaderParser { Header myHeader; /** Parse an existing header. * @param The existing header -- likely read from a file. */ public BinaryTableHeaderParser(Header myHeader) throws FitsException { int naxis1 = myHeader.getIntValue ("NAXIS1", 0); checkLT0(naxis1, "NAXIS1 < 0 for binary table"); int naxis2 = myHeader.getIntValue ("NAXIS2", 0); checkLT0(naxis2, "NAXIS2 < 0 for binary table"); int nfields = myHeader.getIntValue("TFIELDS",0); checkLT0(nfields, "NFIELDS < 0 for binary table"); this.myHeader = myHeader; } /** Create a model row for a binary table given a * describing header. * @return A model row for the table. */ public Object[] getModelRow() throws FitsException { int nfields = myHeader.getIntValue("TFIELDS"); Object[] row = new Object[nfields]; for (int i=0; i < nfields; i += 1) { Object column = getColumnDef(i+1); if (column == null) { throw new FitsException("Invalid TFORM for column "+(i+1)); } else { row[i] = column; } } return row; } /** Check if a value is less than 0 and throw an error if so. * @param i The value to be checked. * @param errmst The message to be associated with the FitsException thrown. */ protected void checkLT0(int i, String errmsg) throws FitsException { if (i < 0) { throw new FitsException(errmsg); } } /** Get the format for a given column * @param col the column being examined. * @return an object of the type described in the column * header info. Note that only the TFORM and TDIM keywords * are examined. */ protected Object getColumnDef(int col) throws FitsException{ int i; Class baseType; int arrsiz; String format = myHeader.getStringValue("TFORM"+col); if (format == null) { throw new FitsException("No TFORM for column "+col); } // Skip initial white space for (i=0; i<format.length(); i += 1) { if (!Character.isSpaceChar(format.charAt(i))){ break; } } // Skip numbers for ( ;i<format.length(); i += 1) { if (!Character.isDigit(format.charAt(i))) { break; } } boolean complex = false; boolean bitData = false; boolean varData = false; if (i >= format.length() ) { throw new FitsException("Invalid TFORM value for column "+col); } if (i > 0) { arrsiz = Integer.parseInt(format.substring(0,i)); } else { arrsiz = 1; } switch (format.charAt(i)){ case 'X': baseType = Byte.TYPE; bitData = true; break; case 'B': case 'A': case 'L': baseType = Byte.TYPE; break; case 'I': baseType = Short.TYPE; break; case 'J': baseType = Integer.TYPE; break; case 'K': baseType = Long.TYPE; break; case 'E': baseType = Float.TYPE; break; case 'D': baseType = Double.TYPE; break; case 'C': baseType = Float.TYPE; complex = true; break; case 'M': baseType = Double.TYPE; complex = true; break; case 'P': baseType = Integer.TYPE; varData = true; if (arrsiz > 0) { arrsiz = 2; } else { arrsiz = 0; } break; default: throw new FitsException("Invalid TFORM code '"+format.charAt(i)+"' for column "+col); } String tdims = myHeader.getStringValue("TDIM"+col); int[] dims; if (tdims != null && !varData && !bitData) { dims = getTDims(tdims, arrsiz); } else { if (bitData) arrsiz /= 8; dims = new int[1]; dims[0] = arrsiz; } // Add in a dimension for complex data. if (complex) { int[] ndims = new int[dims.length+1]; ndims[0] = 2; for (i=1; i<=dims.length; i += 1) { ndims[i] = dims[i-1]; } dims = ndims; } try { return java.lang.reflect.Array.newInstance(baseType, dims); } catch (IllegalArgumentException e) { throw new FitsException("Invalid datatype"); } catch (NegativeArraySizeException e) { throw new FitsException("Negative dimensions"); } } /** Parse the TDIMS value. * * If the TDIMS value cannot be deciphered a one-d * array with the size given in arrsiz is returned. * * @param tdims The value of the TDIMSn card. * @param arrsiz The size field found on the TFORMn card. * @return An int array of the desired dimensions. * Note that the order of the tdims is the inverser * of the order in the TDIMS key. */ public static int[] getTDims(String tdims, int arrsiz) { // The TDIMS value should be of the form: "(iiii,jjjj,kkk,...)" int[] backup = {arrsiz}; // Count the commas in the tdims field. int ncomma = 0; for (int i=0; i<tdims.length(); i += 1) { if (tdims.charAt(i) == ',') { ncomma += 1; } } int[] dims = new int[ncomma+1]; int starter = tdims.indexOf('(') + 1; if (starter < 0) { return backup; } int ender; for (int i=0; i < ncomma; i += 1) { ender= tdims.indexOf(',', starter); if (ender < 0) { return backup; } dims[i] = Integer.parseInt(tdims.substring(starter,ender)); starter = ender + 1; } ender = tdims.indexOf(')', starter); if (ender < 0) { return backup; } dims[ncomma] = Integer.parseInt(tdims.substring(starter,ender)); // Now invert the order of the tdims. int[] newdims = new int[dims.length]; for (int i=0; i<dims.length; i += 1) { newdims[i] = dims[dims.length-i-1]; } return newdims; } /** Make the header describe the a table where we give only. * a single row of the table and the number of rows. * * @exception FitsException if the table was not valid. */ public static Header pointToTable(BinaryTable table) throws FitsException { if (table == null) { throw new FitsException("Cannot create header for null table"); } Header myHeader = new Header(); return pointToTable(table, myHeader); } /** Make the header describe a specified table and included * existing header information. * @param table The binary table data. * @param myHeader An existing header for this data. It will be modified * as needed, but excess keywords will not be pruned. */ public static Header pointToTable(BinaryTable table, Header myHeader) throws FitsException { myHeader.setXtension("BINTABLE"); myHeader.setBitpix(8); myHeader.setNaxes(2); myHeader.setNaxis(1, 0); // This is just a place holder myHeader.setNaxis(2, table.getNrow()); myHeader.setPcount(0); myHeader.setGcount(1); int[][] dimens = table.getDimens(); int[] sizes = table.getSizes(); char[] types = table.getTypes(); myHeader.addIntValue("TFIELDS", dimens.length, "Number of fields in table"); int mark = myHeader.getMark(); String card = myHeader.getCard(mark); if (card != null && (card.substring(0,8).equals("COMMENT ") || card.substring(0,8).equals(" ") )) { while(card.substring(0,8).equals("COMMENT ") || card.substring(0,8).equals(" ")) { mark += 1; card = myHeader.getCard(mark); } } else { myHeader.insertCommentStyle("",""); myHeader.insertComment("End of required structural keywords"); myHeader.insertCommentStyle("",""); } int rowsize = 0; for (int col=0; col < dimens.length; col += 1) { pointToCol(myHeader, col, sizes[col], dimens[col], types[col]); int colsiz = sizes[col]; if (types[col] == 'S') { colsiz *= 2; } else if (types[col] == 'I' || types[col] == 'F') { colsiz *= 4; } else if (types[col] == 'L' || types[col] == 'D') { colsiz *= 8; } rowsize += colsiz; } // Overwrite previous values. myHeader.addIntValue("NAXIS1", rowsize, "Number of bytes in row"); return myHeader; } /** Add a column to the header information. * * @param column The column index for the new column. * @param col The column data. * @param myHeader The existing header for the table. */ public static void addColumn(int column, Object[] col, Header myHeader) throws FitsException { int size; int[] dimens = ArrayFuncs.getDimensions(col[0]); if (dimens.length == 0) { size = 1; dimens = new int[1]; dimens[0] = 1; } else { size = 1; for (int i=0; i<dimens.length; i += 1) { size *= dimens[i]; } } char type; int bsize; Class base = ArrayFuncs.getBaseClass(col[0]); if (base == Boolean.TYPE) { bsize = 1; type = 'Z'; } else if (base == Byte.TYPE) { bsize = 1; type = 'B'; } else if (base == Short.TYPE || base == Character.TYPE) { type = 'S'; bsize = 2; } else if (base == Integer.TYPE) { type = 'I'; bsize=4; } else if (base == Long.TYPE) { type = 'J'; bsize=8; } else if (base == Float.TYPE) { type = 'F'; bsize=4; } else if (base == Double.TYPE) { type = 'D'; bsize = 8; } else { throw new FitsException("Invalid Column type"); } pointToCol(myHeader, column, size, dimens, type); myHeader.addIntValue("TFIELDS", myHeader.getIntValue("TFIELDS")+1, "Number of columns"); myHeader.addIntValue("NAXIS1", myHeader.getIntValue("NAXIS1")+bsize*size, "Bytes per row"); if (column == 0) { myHeader.addIntValue("NAXIS2", col.length, "Number of rows"); } } /** Add information in the header to describe a single column. * * @param myHeader The header to be updated. * @param col The column index. * @param size The number of elements in the column per row * @param dimens The dimensions of the column. * @param type A character indicating the type of data. */ static void pointToCol(Header myHeader, int col, int size, int[] dimens, char type) throws FitsException { char desc; switch(type) { case 'Z': desc = 'L'; break; case 'B': desc = 'B'; break; case 'I': desc = 'J'; break; case 'J': desc = 'K'; break; case 'S': desc = 'I'; break; case 'F': desc = 'E'; break; case 'D': desc = 'D'; break; default: throw new FitsException("Invalid data type at column:"+col); } StringBuffer tdim = new StringBuffer("("); for (int i=0; i < dimens.length; i += 1) { int dim = dimens[dimens.length-i-1]; if (i > 0) { tdim.append(","); } tdim.append(dim); } tdim.append(")"); // Don't overwrite a variable length column... if (size == 2 && desc == 'J') { String colTform = myHeader.getStringValue("TFORM"+(col+1)); if (colTform != null && ( (colTform.length() > 1 && colTform.charAt(0) == 'P') || (colTform.length() > 2 && colTform.substring(0,2).equals("1P") ) ) ){ return; } } myHeader.addStringValue("TFORM"+(col+1), ""+size+desc, null); myHeader.addStringValue("TDIM"+(col+1), new String(tdim), null); } }