Commit 449f3ccb authored by Ole Streicher's avatar Ole Streicher

Import Upstream version 0.1+2016.09.08

parents
jar.class.path=../tamfits/tamfits.jar \
../array/array.jar ../hdx/hdx.jar ../ndx/ndx.jar ../jniast/jniast.jar \
../table/table.jar
This diff is collapsed.
This diff is collapsed.
package uk.ac.starlink.fits;
import java.io.BufferedOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Logger;
import nom.tam.fits.FitsException;
import nom.tam.fits.Header;
import nom.tam.fits.HeaderCardException;
import uk.ac.starlink.table.MultiStarTableWriter;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.table.StarTableOutput;
import uk.ac.starlink.table.StreamStarTableWriter;
import uk.ac.starlink.table.TableSequence;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.util.IOUtils;
/**
* Abstract table writer superclass designed for writing FITS tables.
*
* <p>A couple of Auxiliary metadata items of the ColumnInfo metadata
* from written tables are respected:
* <ul>
* <li>{@link uk.ac.starlink.table.Tables#NULL_VALUE_INFO}:
* sets the value of <code>TNULLn</code> "magic" blank value for
* integer columns</li>
* <li>{@link uk.ac.starlink.table.Tables#UBYTE_FLAG_INFO}:
* if set to <code>Boolean.TRUE</code> and if the column has content class
* <code>Short</code> or <code>short[]</code>, the data will be written
* as unsigned bytes (<code>TFORMn='B'</code>)
* not 16-bit signed integers (<code>TFORMn='I'</code>).</li>
* </ul>
*
* @author Mark Taylor
* @since 27 Jun 2006
*/
public abstract class AbstractFitsTableWriter extends StreamStarTableWriter
implements MultiStarTableWriter {
private String formatName_;
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/** Hard limit for FITS table columns (TTYPE1000 has too many chars). */
private static final int MAX_FITS_COLUMNS = 999;
/**
* Constructor.
*
* @param formatName format name
*/
protected AbstractFitsTableWriter( String formatName ) {
setFormatName( formatName );
}
public String getFormatName() {
return formatName_;
}
/**
* Sets the declared format name.
*
* @param formatName format name
*/
public void setFormatName( String formatName ) {
formatName_ = formatName;
}
/**
* Returns "application/fits".
*
* @return MIME type
*/
public String getMimeType() {
return "application/fits";
}
/**
* Writes a single table.
* Invokes {@link #writeStarTables}.
*/
public void writeStarTable( StarTable table, OutputStream out )
throws IOException {
writeStarTables( Tables.singleTableSequence( table ), out );
}
/**
* Writes tables. Calls {@link #writePrimaryHDU(java.io.DataOutput)}
* to write the primary HDU.
* Subclasses which want to put something related to the input tables
* into the primary HDU will need to override this method
* (writeStarTables).
*/
public void writeStarTables( TableSequence tableSeq, OutputStream out )
throws IOException {
DataOutputStream ostrm = new DataOutputStream( out );
writePrimaryHDU( ostrm );
for ( StarTable table; ( table = tableSeq.nextTable() ) != null; ) {
writeTableHDU( table, createSerializer( table ), ostrm );
}
ostrm.flush();
}
/**
* Invokes {@link #writeStarTables(uk.ac.starlink.table.TableSequence,
java.io.OutputStream)}.
*/
public void writeStarTables( TableSequence tableSeq, String location,
StarTableOutput sto ) throws IOException {
OutputStream out = sto.getOutputStream( location );
try {
out = new BufferedOutputStream( out );
writeStarTables( tableSeq, out );
out.flush();
}
finally {
out.close();
}
}
/**
* Writes the primary HDU. This cannot contain a table since BINTABLE
* HDUs can only be extensions.
* The AbstractFitsTableWriter implementation writes a minimal, data-less
* HDU.
*
* @param out destination stream
*/
public void writePrimaryHDU( DataOutput out ) throws IOException {
FitsConstants.writeEmptyPrimary( out );
}
/**
* Writes a data HDU.
*
* @param table the table to be written into the HDU
* @param fitser fits serializer initalised from <code>table</code>
* @param out destination stream
*/
public void writeTableHDU( StarTable table, FitsTableSerializer fitser,
DataOutput out ) throws IOException {
int ncol = table.getColumnCount();
if ( ncol > MAX_FITS_COLUMNS ) {
throw new IOException( "Column count " + ncol
+ " exceeds FITS limit of "
+ MAX_FITS_COLUMNS );
}
try {
Header hdr = fitser.getHeader();
addMetadata( hdr );
FitsConstants.writeHeader( out, hdr );
}
catch ( FitsException e ) {
throw (IOException) new IOException( e.getMessage() )
.initCause( e );
}
fitser.writeData( out );
}
/**
* Provides a suitable serializer for a given table.
*
* @param table table to serialize
* @return FITS serializer
*/
protected abstract FitsTableSerializer createSerializer( StarTable table )
throws IOException;
/**
* Adds some standard metadata header cards to a FITS table header.
* This includes date stamp, STIL version, etc.
*
* @param hdr header to modify
*/
protected void addMetadata( Header hdr ) {
try {
hdr.addValue( "DATE-HDU", getCurrentDate(),
"Date of HDU creation (UTC)" );
hdr.addValue( "STILVERS",
IOUtils.getResourceContents( StarTable.class,
"stil.version", null ),
"Version of STIL software" );
hdr.addValue( "STILCLAS", getClass().getName(),
"Author class in STIL software" );
}
catch ( HeaderCardException e ) {
logger_.warning( "Trouble adding metadata header cards " + e );
}
}
/**
* Returns an ISO-8601 data string representing the time at which this
* method is called.
*
* @return date string
*/
public static String getCurrentDate() {
DateFormat fmt = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" );
TimeZone utc = TimeZone.getTimeZone( "UTC" );
fmt.setTimeZone( utc );
fmt.setCalendar( new GregorianCalendar( utc, Locale.UK ) );
return fmt.format( new Date() );
}
}
package uk.ac.starlink.fits;
import nom.tam.fits.Header;
import nom.tam.fits.HeaderCard;
/**
* Package-private class to get round a restriction on the nom.tam.fits.Header
* class - this one just subclasses it and publicises the addLine methods
* which are (I don't know why) protected in Header.
*/
class AddableHeader extends Header {
public void addLine( HeaderCard card ) {
super.addLine( card );
}
}
package uk.ac.starlink.fits;
import java.io.DataOutput;
import java.io.IOException;
/**
* Abstract superclass for objects which do the nuts and bolts of
* writing array data for column-oriented storage.
*
* @author Mark Taylor
* @since 21 Jun 2006
* @see ColumnStore
*/
abstract class ArrayStorage {
private final Class componentClass_;
private final char formatChar_;
private final int typeBytes_;
/**
* Constructor.
*
* @param componentClass element type of the arrays this will store
* @param formatChar FITS format identification character
* @param typeBytes number of bytes required for a single element
*/
protected ArrayStorage( Class componentClass, char formatChar,
int typeBytes ) {
componentClass_ = componentClass;
formatChar_ = formatChar;
typeBytes_ = typeBytes;
}
/**
* Returns the element type for arrays stored by this object.
*
* @return array element class
*/
public Class getComponentClass() {
return componentClass_;
}
/**
* Returns the FITS format identification character.
*
* @return FITS type char
*/
public char getFormatChar() {
return formatChar_;
}
/**
* Returns the number of bytes per element stored.
*
* @return number of bytes per element
*/
public int getTypeBytes() {
return typeBytes_;
}
/**
* Writes a given array to an output stream.
*
* @param array array of type compatible with this storage object
* @param out destination stream
*/
public abstract void writeArray( Object array, DataOutput out )
throws IOException;
/** Instance for storing boolean arrays. */
public static ArrayStorage BOOLEAN = new ArrayStorage( boolean.class, 'L',
1 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
boolean[] values = (boolean[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeByte( values[ i ] ? 'T' : 'F' );
}
}
};
/** Instance for storing <code>byte</code> arrays. */
public static ArrayStorage BYTE = new ArrayStorage( byte.class, 'B', 1 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
out.write( (byte[]) array );
}
};
/** Instance for storing <code>short</code> arrays. */
public static ArrayStorage SHORT = new ArrayStorage( short.class, 'I', 2 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
short[] values = (short[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeShort( values[ i ] );
}
}
};
/** Instance for storing <code>int</code> arrays. */
public static ArrayStorage INT = new ArrayStorage( int.class, 'J', 4 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
int[] values = (int[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeInt( values[ i ] );
}
}
};
/** Instance for storing <code>long</code> arrays. */
public static ArrayStorage LONG = new ArrayStorage( long.class, 'K', 8 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
long[] values = (long[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeLong( values[ i ] );
}
}
};
/** Instance for storing <code>float</code> arrays. */
public static ArrayStorage FLOAT = new ArrayStorage( float.class, 'E', 4 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
float[] values = (float[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeFloat( values[ i ] );
}
}
};
/** Instance for storing <code>double</code> arrays. */
public static ArrayStorage DOUBLE = new ArrayStorage( double.class,
'D', 8 ) {
public void writeArray( Object array, DataOutput out )
throws IOException {
double[] values = (double[]) array;
for ( int i = 0; i < values.length; i++ ) {
out.writeDouble( values[ i ] );
}
}
};
}
package uk.ac.starlink.fits;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.logging.Logger;
import uk.ac.starlink.table.ColumnInfo;
import uk.ac.starlink.table.Tables;
/**
* Object which knows how to write array data for a particular type
* to a FITS BINTABLE extension.
*
* @author Mark Taylor
* @since 10 Jul 2008
*/
abstract class ArrayWriter {
private final char formatChar_;
private final int nByte_;
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/**
* Constructor.
*
* @param formatChar data type-specific TFORM character
* @param nByte number of bytes for each element written
*/
protected ArrayWriter( char formatChar, int nByte ) {
formatChar_ = formatChar;
nByte_ = nByte;
}
/**
* Returns the type-specific TFORM format character for this writer.
*
* @return format character
*/
public char getFormatChar() {
return formatChar_;
}
/**
* Returns the number of bytes for each element written by this writer.
*
* @return byte count written
*/
public int getByteCount() {
return nByte_;
}
/**
* Writes an element of an array to an output stream.
*
* @param out output stream
* @param array array to take value from, of type appropriate for this
* writer
* @param index index of element to write
*/
public abstract void writeElement( DataOutput out, Object array,
int index )
throws IOException;
/**
* Writes a padding value to an output stream.
*
* @param out destination stream
*/
public abstract void writePad( DataOutput out ) throws IOException;
/**
* Returns offset value for this writer (normally 0).
*
* @return BZERO value
*/
public abstract double getZero();
/**
* Constructs a new ArrayWriter for a given array class.
*
* @param cinfo column metadata describing the data
* which this writer should be able to write
* @param allowSignedByte if true, bytes written as FITS signed bytes
* (TZERO=-128), if false bytes written as signed shorts
* @return new ArrayWriter, or null if <code>cinfo</code> can't be handled
*/
public static ArrayWriter createArrayWriter( ColumnInfo cinfo,
boolean allowSignedByte ) {
Class clazz = cinfo.getContentClass();
final boolean isUbyte =
Boolean.TRUE
.equals( cinfo.getAuxDatumValue( Tables.UBYTE_FLAG_INFO,
Boolean.class ) );
if ( isUbyte ) {
if ( clazz == short[].class ) {
return new NormalArrayWriter( 'B', 1,
new short[] { (short) 0 } ) {
public void writeElement( DataOutput out, Object array,
int ix )
throws IOException {
out.writeByte( ((short[]) array)[ ix ] );
}
};
}
else {
logger_.warning( "Ignoring " + Tables.UBYTE_FLAG_INFO
+ " on non-short[] column " + cinfo );
}
}
if ( clazz == boolean[].class ) {
return new ArrayWriter( 'L', 1 ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeByte( ((boolean[]) array)[ ix ] ? (byte) 'T'
: (byte) 'F' );
}
public void writePad( DataOutput out ) throws IOException {
out.writeByte( (byte) 0 );
}
public double getZero() {
return 0.0;
}
};
}
else if ( clazz == byte[].class ) {
if ( allowSignedByte ) {
return new NormalArrayWriter( 'B', 1,
new byte[] { (byte) 0 } ) {
public void writeElement( DataOutput out, Object array,
int ix )
throws IOException {
out.writeByte( ((byte[]) array)[ ix ] ^ (byte) 0x80 );
}
public double getZero() {
return -128.0;
}
};
}
else {
return new NormalArrayWriter( 'I', 2,
new byte[] { (byte) 0 } ) {
public void writeElement( DataOutput out, Object array,
int ix )
throws IOException {
out.writeShort( (short) ((byte[]) array)[ ix ] );
}
};
}
}
else if ( clazz == short[].class ) {
return new NormalArrayWriter( 'I', 2, new short[] { (short) 0 } ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeShort( ((short[]) array)[ ix ] );
}
};
}
else if ( clazz == int[].class ) {
return new NormalArrayWriter( 'J', 4, new int[] { 0 } ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeInt( ((int[]) array)[ ix ] );
}
};
}
else if ( clazz == long[].class ) {
return new NormalArrayWriter( 'K', 8, new long[] { 0L } ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeLong( ((long[]) array)[ ix ] );
}
};
}
else if ( clazz == float[].class ) {
return new NormalArrayWriter( 'E', 4, new float[] { Float.NaN } ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeFloat( ((float[]) array)[ ix ] );
}
};
}
else if ( clazz == double[].class ) {
return new NormalArrayWriter( 'D', 8,
new double[] { Double.NaN } ) {
public void writeElement( DataOutput out, Object array, int ix )
throws IOException {
out.writeDouble( ((double[]) array)[ ix ] );
}
};
}
/* Not an array. */
else {
return null;
}
}
/**
* ArrayWriter implmentation suitable for most cases.
*/
private static abstract class NormalArrayWriter extends ArrayWriter {
private final Object pad1_;
/**
* Constructor.
*
* @param formatChar format character
* @param nByte byte count
* @param pad1 1-element array containing a padding value
*/
protected NormalArrayWriter( char formatChar, int nByte, Object pad1 ) {
super( formatChar, nByte );
pad1_ = pad1;
if ( Array.getLength( pad1 ) != 1 ) {
throw new IllegalArgumentException();
}
}
public void writePad( DataOutput out ) throws IOException {
writeElement( out, pad1_, 0 );
}
public double getZero() {
return 0.0;
}
}
}
package uk.ac.starlink.fits;
import java.io.IOException;
/**
* Interface defining the basic data input operations required for
* the FITS reading classes. All the read operations operate at the
* current position of the assumed stream and advance the current
* position past the item they just read. Storage is FITS-like,
* which, happily, matches ByteBuffer conventions. Random access
* may or may not be supported.
*
* <p>This interface has some similarities to {@link java.io.DataInput},
* and that interface could have been used instead, but this one is
* explicitly used for the hand-coded FITS reader implementation to
* make clear which operations need to be efficient. At present
* no multi-byte (or multi-other-primitive-type) read operations are
* included, since it's not clear that these are required in practice
* for efficient table input, though for (uncommon?) tables that have
* columns with large array values that might not be true.
* If that turns out to be an important use case, such methods can
* be added to this interface, implemented in its implementations,
* and used in the clients of this interface.
*
* <p>Instances of this are <strong>not</strong> expected to be safe for
* use from multiple threads. Depending on the implementation,
* ignoring that fact may be a <em>very bad idea indeed</em>.
*
* @author Mark Taylor
* @since 1 Dec 2014
*/
public interface BasicInput {
/**
* Reads a byte from the stream.
* The current position is advanced.
*
* @return byte value
*/
byte readByte() throws IOException;
/**
* Reads a 2-byte integer from the stream.
* The current position is advanced.
*
* @return short value
*/
short readShort() throws IOException;
/**
* Reads a 4-byte integer from the stream.
* The current position is advanced.
*
* @return int value
*/
int readInt() throws IOException;
/**
* Reads an 8-byte integer from the stream.
* The current position is advanced.
*
* @return long value
*/
long readLong() throws IOException;
/**
* Reads a 4-byte floating point value from the stream.
* The current position is advanced.
*
* @return float value
*/
float readFloat() throws IOException;
/**
* Reads an 8-byte floating point value from the stream.
* The current position is advanced.
*
* @return double value
*/
double readDouble() throws IOException;
/**