nom.tam.fits.Fits Maven / Gradle / Ivy
package nom.tam.fits;
/*
* #%L
* nom.tam FITS library
* %%
* Copyright (C) 2004 - 2015 nom-tam-fits
* %%
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* #L%
*/
import java.io.Closeable;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import nom.tam.fits.compress.CompressionManager;
import nom.tam.fits.utilities.FitsCheckSum;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.BufferedDataInputStream;
import nom.tam.util.BufferedDataOutputStream;
import nom.tam.util.BufferedFile;
import nom.tam.util.RandomAccess;
import nom.tam.util.SafeClose;
/**
* This class provides access to routines to allow users to read and write FITS
* files.
* Description of the Package
*
* This FITS package attempts to make using FITS files easy, but does not do
* exhaustive error checking. Users should not assume that just because a FITS
* file can be read and written that it is necessarily legal FITS. These classes
* try to make it easy to transform between arrays of Java primitives and their
* FITS encodings.
*
* - The Fits class provides capabilities to read and write data at the HDU
* level, and to add and delete HDU's from the current Fits object. A large
* number of constructors are provided which allow users to associate the Fits
* object with some form of external data. This external data may be in a
* compressed format.
*
* Note that this association is limited, it only specifies where the various
* read methods should read data from. It does not automatically read the data
* content and store the results. To ensure that the external content has been
* read and parsed the user may wish to invoke the read() method after creating
* the Fits object associated with external data. E.g.,
*
*
* File fl = ... ;
* Fits f = new Fits(fl); // Or we could have used one of the other constructors.
* // At this point the Fits object is empty.
* f.read(); // Read the external data into the Fits object
* // At this point the Fits object should have one or more HDUs depending
* // upon the external content.
*
*
* Users can choose to read only some of the HDUs in a given input, and may add
* HDU's that were either read from other files or generated by the program. See
* the various read and addHDU methods.
* - The FitsFactory class is a factory class which is used to create HDUs.
* HDU's can be of a number of types derived from the abstract class BasicHDU.
* The hierarchy of HDUs is:
*
* - BasicHDU
*
* - ImageHDU
*
- RandomGroupsHDU
*
- TableHDU
*
* - BinaryTableHDU
*
- AsciiTableHDU
*
* - UndefinedHDU
*
*
* - The Header class provides many functions to add, delete and read header
* keywords in a variety of formats.
*
- The HeaderCard class provides access to the structure of a FITS header
* card.
*
- The header package defines sets of enumerations that allow users to
* create and access header keywords in a controlled way.
*
- The Data class is an abstract class which provides the basic methods for
* reading and writing FITS data. It provides methods to get the the actual
* underlying arrays and detailed methods for manipulation specific to the
* different data types.
*
- The TableHDU class provides a large number of methods to access and
* modify information in tables.
*
- The utilities package includes simple tools to copy and list FITS files.
*
*
* @version 1.12
*/
public class Fits implements Closeable {
/**
* logger to log to.
*/
private static final Logger LOG = Logger.getLogger(Fits.class.getName());
/**
* The input stream associated with this Fits object.
*/
private ArrayDataInput dataStr;
/**
* A vector of HDUs that have been added to this Fits object.
*/
private final List> hduList = new ArrayList>();
/**
* Has the input stream reached the EOF?
*/
private boolean atEOF;
/**
* The last offset we reached. A -1 is used to indicate that we cannot use
* the offset.
*/
private long lastFileOffset = -1;
/**
* Create an empty Fits object which is not associated with an input stream.
*/
public Fits() {
}
/**
* Associate FITS object with a File. If the file is compressed a stream
* will be used, otherwise random access will be supported.
*
* @param myFile
* The File object. The content of this file will not be read
* into the Fits object until the user makes some explicit
* request. * @throws FitsException if the operation failed
* @throws FitsException
* if the operation failed
*/
public Fits(File myFile) throws FitsException {
this(myFile, CompressionManager.isCompressed(myFile));
}
/**
* Associate the Fits object with a File
*
* @param myFile
* The File object. The content of this file will not be read
* into the Fits object until the user makes some explicit
* request.
* @param compressed
* Is the data compressed?
* @throws FitsException
* if the operation failed
*/
public Fits(File myFile, boolean compressed) throws FitsException {
fileInit(myFile, compressed);
}
/**
* Create a Fits object associated with the given data stream. Compression
* is determined from the first few bytes of the stream.
*
* @param str
* The data stream. The content of this stream will not be read
* into the Fits object until the user makes some explicit
* request.
* @throws FitsException
* if the operation failed
*/
public Fits(InputStream str) throws FitsException {
streamInit(str);
}
/**
* Create a Fits object associated with a data stream.
*
* @param str
* The data stream. The content of this stream will not be read
* into the Fits object until the user makes some explicit
* request.
* @param compressed
* Is the stream compressed? This is currently ignored.
* Compression is determined from the first two bytes in the
* stream.
* @throws FitsException
* if the operation failed
* @deprecated use {@link #Fits(InputStream)} compression is auto detected.
*/
@Deprecated
public Fits(InputStream str, boolean compressed) throws FitsException {
this(str);
LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed);
}
/**
* Associate the FITS object with a file or URL. The string is assumed to be
* a URL if it begins one of the protocol strings. If the string ends in .gz
* it is assumed that the data is in a compressed format. All string
* comparisons are case insensitive.
*
* @param filename
* The name of the file or URL to be processed. The content of
* this file will not be read into the Fits object until the user
* makes some explicit request.
* @throws FitsException
* Thrown if unable to find or open a file or URL from the
* string given.
**/
public Fits(String filename) throws FitsException {
this(filename, CompressionManager.isCompressed(filename));
}
/**
* Associate the FITS object with a file or URL. The string is assumed to be
* a URL if it begins one of the protocol strings. If the string ends in .gz
* it is assumed that the data is in a compressed format. All string
* comparisons are case insensitive.
*
* @param filename
* The name of the file or URL to be processed. The content of
* this file will not be read into the Fits object until the user
* makes some explicit request.
* @param compressed
* is the file compressed?
* @throws FitsException
* Thrown if unable to find or open a file or URL from the
* string given.
**/
public Fits(String filename, boolean compressed) throws FitsException {
if (filename == null) {
throw new FitsException("Null FITS Identifier String");
}
try {
File fil = new File(filename);
if (fil.exists()) {
fileInit(fil, compressed);
return;
}
} catch (Exception e) {
LOG.log(Level.FINE, "not a file " + filename, e);
}
try {
InputStream str = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename);
if (str != null) {
streamInit(str);
return;
}
} catch (Exception e) {
LOG.log(Level.FINE, "not a resource " + filename, e);
}
try {
InputStream is = FitsUtil.getURLStream(new URL(filename), 0);
streamInit(is);
return;
} catch (Exception e) {
LOG.log(Level.FINE, "not a url " + filename, e);
}
throw new FitsException("could not detect type of " + filename);
}
/**
* Associate the FITS object with a given URL
*
* @param myURL
* The URL to be read. The content of this URL will not be read
* into the Fits object until the user makes some explicit
* request.
* @throws FitsException
* Thrown if unable to find or open a file or URL from the
* string given.
*/
public Fits(URL myURL) throws FitsException {
try {
streamInit(FitsUtil.getURLStream(myURL, 0));
} catch (IOException e) {
throw new FitsException("Unable to open input from URL:" + myURL, e);
}
}
/**
* Associate the FITS object with a given uncompressed URL
*
* @param myURL
* The URL to be associated with the FITS file. The content of
* this URL will not be read into the Fits object until the user
* makes some explicit request.
* @param compressed
* Compression flag, ignored.
* @throws FitsException
* Thrown if unable to use the specified URL.
* @deprecated use {@link #Fits(InputStream)} compression is auto detected.
*/
@Deprecated
public Fits(URL myURL, boolean compressed) throws FitsException {
this(myURL);
LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed);
}
/**
* @return a newly created HDU from the given Data.
* @param data
* The data to be described in this HDU.
* @param
* the class of the HDU
* @throws FitsException
* if the operation failed
*/
public static BasicHDU makeHDU(DataClass data) throws FitsException {
Header hdr = new Header();
data.fillHeader(hdr);
return FitsFactory.hduFactory(hdr, data);
}
/**
* @return a newly created HDU from the given header.
* @param h
* The header which describes the FITS extension
* @throws FitsException
* if the header could not be converted to a HDU.
*/
public static BasicHDU> makeHDU(Header h) throws FitsException {
Data d = FitsFactory.dataFactory(h);
return FitsFactory.hduFactory(h, d);
}
/**
* @return a newly created HDU from the given data kernel.
* @param o
* The data to be described in this HDU.
* @throws FitsException
* if the parameter could not be converted to a HDU.
*/
public static BasicHDU> makeHDU(Object o) throws FitsException {
return FitsFactory.hduFactory(o);
}
/**
* @return the version of the library.
*/
public static String version() {
Properties props = new Properties();
InputStream versionProperties = null;
try {
versionProperties = Fits.class.getResourceAsStream("/META-INF/maven/gov.nasa.gsfc.heasarc/nom-tam-fits/pom.properties");
props.load(versionProperties);
return props.getProperty("version");
} catch (IOException e) {
LOG.log(Level.INFO, "reading version failed, ignoring", e);
return "unknown";
} finally {
saveClose(versionProperties);
}
}
/**
* close the input stream, and ignore eventual errors.
*
* @param in
* the input stream to close.
*/
public static void saveClose(InputStream in) {
SafeClose.close(in);
}
/**
* Add an HDU to the Fits object. Users may intermix calls to functions
* which read HDUs from an associated input stream with the addHDU and
* insertHDU calls, but should be careful to understand the consequences.
*
* @param myHDU
* The HDU to be added to the end of the FITS object.
* @throws FitsException
* if the HDU could not be inserted.
*/
public void addHDU(BasicHDU> myHDU) throws FitsException {
insertHDU(myHDU, getNumberOfHDUs());
}
/**
* Get the current number of HDUs in the Fits object.
*
* @return The number of HDU's in the object.
* @deprecated use {@link #getNumberOfHDUs()} instead
*/
@Deprecated
public int currentSize() {
return getNumberOfHDUs();
}
/**
* Delete an HDU from the HDU list.
*
* @param n
* The index of the HDU to be deleted. If n is 0 and there is
* more than one HDU present, then the next HDU will be converted
* from an image to primary HDU if possible. If not a dummy
* header HDU will then be inserted.
* @throws FitsException
* if the HDU could not be deleted.
*/
public void deleteHDU(int n) throws FitsException {
int size = getNumberOfHDUs();
if (n < 0 || n >= size) {
throw new FitsException("Attempt to delete non-existent HDU:" + n);
}
this.hduList.remove(n);
if (n == 0 && size > 1) {
BasicHDU> newFirst = this.hduList.get(0);
if (newFirst.canBePrimary()) {
newFirst.setPrimaryHDU(true);
} else {
insertHDU(BasicHDU.getDummyHDU(), 0);
}
}
}
/**
* Get a stream from the file and then use the stream initialization.
*
* @param myFile
* The File to be associated.
* @param compressed
* Is the data compressed?
* @throws FitsException
* if the opening of the file failed.
*/
@SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "stream stays open, and will be read when nessesary.")
protected void fileInit(File myFile, boolean compressed) throws FitsException {
try {
if (compressed) {
streamInit(new FileInputStream(myFile));
} else {
randomInit(myFile);
}
} catch (IOException e) {
throw new FitsException("Unable to create Input Stream from File: " + myFile, e);
}
}
/**
* @return the n'th HDU. If the HDU is already read simply return a pointer
* to the cached data. Otherwise read the associated stream until
* the n'th HDU is read.
* @param n
* The index of the HDU to be read. The primary HDU is index 0.
* @return The n'th HDU or null if it could not be found.
* @throws FitsException
* if the header could not be read
* @throws IOException
* if the underlying buffer threw an error
*/
public BasicHDU> getHDU(int n) throws FitsException, IOException {
int size = getNumberOfHDUs();
for (int i = size; i <= n; i += 1) {
BasicHDU> hdu = readHDU();
if (hdu == null) {
return null;
}
}
return this.hduList.get(n);
}
/**
* Get the current number of HDUs in the Fits object.
*
* @return The number of HDU's in the object.
*/
public int getNumberOfHDUs() {
return this.hduList.size();
}
/**
* Get the data stream used for the Fits Data.
*
* @return The associated data stream. Users may wish to call this function
* after opening a Fits object when they wish detailed control for
* writing some part of the FITS file.
*/
public ArrayDataInput getStream() {
return this.dataStr;
}
/**
* Insert a FITS object into the list of HDUs.
*
* @param myHDU
* The HDU to be inserted into the list of HDUs.
* @param position
* The location at which the HDU is to be inserted.
* @throws FitsException
* if the HDU could not be inserted.
*/
public void insertHDU(BasicHDU> myHDU, int position) throws FitsException {
if (myHDU == null) {
return;
}
if (position < 0 || position > getNumberOfHDUs()) {
throw new FitsException("Attempt to insert HDU at invalid location: " + position);
}
try {
if (position == 0) {
// Note that the previous initial HDU is no longer the first.
// If we were to insert tables backwards from last to first,
// we could get a lot of extraneous DummyHDUs but we currently
// do not worry about that.
if (getNumberOfHDUs() > 0) {
this.hduList.get(0).setPrimaryHDU(false);
}
if (myHDU.canBePrimary()) {
myHDU.setPrimaryHDU(true);
this.hduList.add(0, myHDU);
} else {
insertHDU(BasicHDU.getDummyHDU(), 0);
myHDU.setPrimaryHDU(false);
this.hduList.add(1, myHDU);
}
} else {
myHDU.setPrimaryHDU(false);
this.hduList.add(position, myHDU);
}
} catch (NoSuchElementException e) {
throw new FitsException("hduList inconsistency in insertHDU", e);
}
}
/**
* Initialize using buffered random access. This implies that the data is
* uncompressed.
*
* @param file
* the file to open
* @throws FitsException
* if the file could not be read
*/
protected void randomInit(File file) throws FitsException {
String permissions = "r";
if (!file.exists() || !file.canRead()) {
throw new FitsException("Non-existent or unreadable file");
}
if (file.canWrite()) {
permissions += "w";
}
try {
this.dataStr = new BufferedFile(file, permissions);
((BufferedFile) this.dataStr).seek(0);
} catch (IOException e) {
throw new FitsException("Unable to open file " + file.getPath(), e);
}
}
/**
* Return all HDUs for the Fits object. If the FITS file is associated with
* an external stream make sure that we have exhausted the stream.
*
* @return an array of all HDUs in the Fits object. Returns null if there
* are no HDUs associated with this object.
* @throws FitsException
* if the reading failed.
*/
public BasicHDU>[] read() throws FitsException {
readToEnd();
int size = getNumberOfHDUs();
if (size == 0) {
return new BasicHDU>[0];
}
return this.hduList.toArray(new BasicHDU>[size]);
}
/**
* Read a FITS file from an InputStream object.
*
* @param is
* The InputStream stream whence the FITS information is found.
* @throws FitsException
* if the data read could not be interpreted
*/
public void read(InputStream is) throws FitsException {
if (is instanceof ArrayDataInput) {
this.dataStr = (ArrayDataInput) is;
} else {
this.dataStr = new BufferedDataInputStream(is);
}
read();
}
/**
* Read the next HDU on the default input stream.
*
* @return The HDU read, or null if an EOF was detected. Note that null is
* only returned when the EOF is detected immediately at the
* beginning of reading the HDU.
* @throws FitsException
* if the header could not be read
* @throws IOException
* if the underlying buffer threw an error
*/
public BasicHDU> readHDU() throws FitsException, IOException {
if (this.dataStr == null || this.atEOF) {
if (this.dataStr == null) {
LOG.warning("trying to read a hdu, without an input source!");
}
return null;
}
if (this.dataStr instanceof RandomAccess && this.lastFileOffset > 0) {
FitsUtil.reposition(this.dataStr, this.lastFileOffset);
}
Header hdr = Header.readHeader(this.dataStr);
if (hdr == null) {
this.atEOF = true;
return null;
}
Data data = hdr.makeData();
try {
data.read(this.dataStr);
} catch (PaddingException e) {
e.updateHeader(hdr);
throw e;
}
this.lastFileOffset = FitsUtil.findOffset(this.dataStr);
BasicHDU nextHDU = FitsFactory.hduFactory(hdr, data);
this.hduList.add(nextHDU);
return nextHDU;
}
/**
* Read to the end of the associated input stream
*
* @throws FitsException
* if the operation failed
*/
private void readToEnd() throws FitsException {
while (this.dataStr != null && !this.atEOF) {
try {
if (readHDU() == null) {
break;
}
} catch (EOFException e) {
if (FitsFactory.getAllowTerminalJunk() && //
e.getCause() instanceof TruncatedFileException && //
getNumberOfHDUs() > 0) {
this.atEOF = true;
return;
}
throw new FitsException("IO error: " + e);
} catch (IOException e) {
throw new FitsException("IO error: " + e);
}
}
}
/**
* Add or Modify the CHECKSUM keyword in all headers. by R J Mathar
*
* @throws FitsException
* if the operation failed
* @throws IOException
* if the underlying stream failed
*/
public void setChecksum() throws FitsException, IOException {
for (int i = 0; i < getNumberOfHDUs(); i += 1) {
setChecksum(getHDU(i));
}
}
/**
* Set the data stream to be used for future input.
*
* @param stream
* The data stream to be used.
*/
public void setStream(ArrayDataInput stream) {
this.dataStr = stream;
this.atEOF = false;
this.lastFileOffset = -1;
}
/**
* Return the number of HDUs in the Fits object. If the FITS file is
* associated with an external stream make sure that we have exhausted the
* stream.
*
* @return number of HDUs.
* @deprecated The meaning of size of ambiguous. Use
* {@link #getNumberOfHDUs()} instead. Note size() will read the
* input file/stream to the EOF before returning the number of
* HDUs which {@link #getNumberOfHDUs()} does not. If you wish
* to duplicate this behavior and ensure that the input has been
* exhausted before getting the number of HDUs then use the
* sequence:
* read();
* getNumberofHDUs();
*
* @throws FitsException
* if the file could not be read.
*/
@Deprecated
public int size() throws FitsException {
readToEnd();
return getNumberOfHDUs();
}
/**
* Skip the next HDU on the default input stream.
*
* @throws FitsException
* if the HDU could not be skipped
* @throws IOException
* if the underlying stream failed
*/
public void skipHDU() throws FitsException, IOException {
if (this.atEOF) {
return;
} else {
Header hdr = new Header(this.dataStr);
int dataSize = (int) hdr.getDataSize();
this.dataStr.skipAllBytes(dataSize);
if (this.dataStr instanceof RandomAccess) {
this.lastFileOffset = ((RandomAccess) this.dataStr).getFilePointer();
}
}
}
/**
* Skip HDUs on the associate input stream.
*
* @param n
* The number of HDUs to be skipped.
* @throws FitsException
* if the HDU could not be skipped
* @throws IOException
* if the underlying stream failed
*/
public void skipHDU(int n) throws FitsException, IOException {
for (int i = 0; i < n; i += 1) {
skipHDU();
}
}
/**
* Initialize the input stream. Mostly this checks to see if the stream is
* compressed and wraps the stream if necessary. Even if the stream is not
* compressed, it will likely be wrapped in a PushbackInputStream. So users
* should probably not supply a BufferedDataInputStream themselves, but
* should allow the Fits class to do the wrapping.
*
* @param inputStream
* stream to initialize
* @throws FitsException
* if the initialization failed
*/
protected void streamInit(InputStream inputStream) throws FitsException {
this.dataStr = new BufferedDataInputStream(CompressionManager.decompress(inputStream));
}
/**
* Write a Fits Object to an external Stream.
*
* @param os
* A DataOutput stream.
* @throws FitsException
* if the operation failed
*/
public void write(DataOutput os) throws FitsException {
ArrayDataOutput obs;
boolean newOS = false;
if (os instanceof ArrayDataOutput) {
obs = (ArrayDataOutput) os;
} else if (os instanceof DataOutputStream) {
newOS = true;
obs = new BufferedDataOutputStream((DataOutputStream) os);
} else {
throw new FitsException("Cannot create ArrayDataOutput from class " + os.getClass().getName());
}
for (BasicHDU> basicHDU : hduList) {
basicHDU.write(obs);
}
if (newOS) {
try {
obs.flush();
obs.close();
} catch (IOException e) {
throw new FitsException("Error flushing/closing the FITS output stream: " + e, e);
}
}
if (obs instanceof BufferedFile) {
try {
((BufferedFile) obs).setLength(((BufferedFile) obs).getFilePointer());
} catch (IOException e) {
throw new FitsException("Error resizing the FITS output stream: " + e, e);
}
}
}
/**
* Write the FITS to the specified file. This is a wrapper method provided
* for convenience, which calls the {@link #write(DataOutput)} method. It
* creates a suitable {@link nom.tam.util.BufferedFile}, to which the FITS
* is then written. Upon completion the underlying stream is closed.
*
* @param file
* a file to which the FITS is to be written.
* @throws FitsException
* if {@link #write(DataOutput)} failed
* @throws IOException
* if the underlying output stream could not be created or
* closed.
*/
public void write(File file) throws IOException, FitsException {
BufferedFile bf = null;
try {
bf = new BufferedFile(file, "rw");
write(bf);
} finally {
SafeClose.close(bf);
}
}
@Override
public void close() throws IOException {
if (dataStr != null) {
this.dataStr.close();
}
}
/**
* set the checksum of a HDU.
*
* @param hdu
* the HDU to add a checksum
* @throws FitsException
* the checksum could not be added to the header
* @deprecated use {@link FitsCheckSum#setChecksum(BasicHDU)}
*/
@Deprecated
public static void setChecksum(BasicHDU> hdu) throws FitsException {
FitsCheckSum.setChecksum(hdu);
}
/**
* calculate the checksum for the block of data
*
* @param data
* the data to create the checksum for
* @return the checksum
* @deprecated use {@link FitsCheckSum#checksum(byte[])}
*/
@Deprecated
public static long checksum(final byte[] data) {
return FitsCheckSum.checksum(data);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy