All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.fits.Header 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 static nom.tam.fits.header.Standard.BITPIX;
import static nom.tam.fits.header.Standard.COMMENT;
import static nom.tam.fits.header.Standard.END;
import static nom.tam.fits.header.Standard.EXTEND;
import static nom.tam.fits.header.Standard.GCOUNT;
import static nom.tam.fits.header.Standard.GROUPS;
import static nom.tam.fits.header.Standard.HISTORY;
import static nom.tam.fits.header.Standard.NAXIS;
import static nom.tam.fits.header.Standard.NAXISn;
import static nom.tam.fits.header.Standard.PCOUNT;
import static nom.tam.fits.header.Standard.SIMPLE;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.XTENSION;
import static nom.tam.fits.header.Standard.XTENSION_BINTABLE;
import static nom.tam.fits.header.extra.CXCExt.LONGSTRN;

import java.io.EOFException;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import nom.tam.fits.FitsFactory.FitsSettings;
import nom.tam.fits.header.IFitsHeader;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.AsciiFuncs;
import nom.tam.util.Cursor;
import nom.tam.util.FitsIO;
import nom.tam.util.HashedList;
import nom.tam.util.RandomAccess;

/**
 * This class describes methods to access and manipulate the header for a FITS
 * HDU. This class does not include code specific to particular types of HDU. As
 * of version 1.1 this class supports the long keyword convention which allows
 * long string keyword values to be split among multiple keywords
 *
 * 
 *    KEY        = 'ABC&'   /A comment
 *    CONTINUE      'DEF&'  / Another comment
 *    CONTINUE      'GHIJKL '
 * 
* * The methods getStringValue(key), addValue(key,value,comment) and * deleteCard(key) will get, create/update and delete long string values if the * longStringsEnabled flag is set. This flag is set automatically when a FITS * header with a LONGSTRN card is found. The value is not checked. It may also * be set/unset using the static method setLongStringsEnabled(boolean). [So if a * user wishes to ensure that it is not set, it should be unset after any header * is read] When long strings are found in the FITS header users should be * careful not to interpose new header cards within a long value sequence. When * writing long strings, the comment is included in the last card. If a user is * writing long strings, a the keyword LONGSTRN = 'OGIP 1.0' should be added to * the FITS header, but this is not done automatically for the user. */ public class Header implements FitsElement { private static final int MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER = 4; private static final int MAX_CARDS_PER_HEADER = FitsFactory.FITS_BLOCK_SIZE / HeaderCard.FITS_HEADER_CARD_SIZE; private static final Logger LOG = Logger.getLogger(Header.class.getName()); /** * The actual header data stored as a HashedList of HeaderCard's. */ private final HashedList cards = new HashedList(); /** * This iterator allows one to run through the list. */ private Cursor iter = this.cards.iterator(0); /** Offset of this Header in the FITS file */ private long fileOffset = -1; private List duplicates; /** Input descriptor last time header was read */ private ArrayDataInput input; /** * Number of cards in header before duplicates were removed. A user may want * to know how large the actual FITS header was on input. Since the keyword * hash removes duplicate keys the internal size may be smaller. Added by * Booth Hartley (IPAC/Caltech). */ private int originalCardCount = 0; // RBH ADDED /** * the sorter used to sort the header cards defore writing the header. */ private Comparator headerSorter = new HeaderOrder(); /** * Create a header by reading the information from the input stream. * * @param dis * The input stream to read the data from. * @return null if there was a problem with the header; * otherwise return the header read from the input stream. * @throws TruncatedFileException * if the stream ended prematurely * @throws IOException * if the header could not be read. */ public static Header readHeader(ArrayDataInput dis) throws TruncatedFileException, IOException { Header myHeader = new Header(); try { myHeader.read(dis); } catch (EOFException e) { if (e.getCause() instanceof TruncatedFileException) { throw e; } // An EOF exception is thrown only if the EOF was detected // when reading the first card. In this case we want // to return a null. return null; } return myHeader; } /** * please use {@link FitsFactory#setLongStringsEnabled(boolean)} instead. * * @param flag * the new value for long-string enabling. */ @Deprecated public static void setLongStringsEnabled(boolean flag) { FitsFactory.setLongStringsEnabled(flag); } /** Create an empty header */ public Header() { super(); } /** * Create a header and populate it from the input stream * * @param is * The input stream where header information is expected. * @throws IOException * if the header could not be read. * @throws TruncatedFileException * if the stream ended prematurely */ public Header(ArrayDataInput is) throws TruncatedFileException, IOException { read(is); } /** * Create a header which points to the given data object. * * @param o * The data object to be described. * @throws FitsException * if the data was not valid for this header. */ public Header(Data o) throws FitsException { o.fillHeader(this); } /** * Create a header and initialize it with a vector of strings. * * @param newCards * Card images to be placed in the header. */ public Header(String[] newCards) { for (String newCard : newCards) { this.cards.add(HeaderCard.create(newCard)); } } /** * Add a card image to the header. * * @param fcard * The card to be added. */ public void addLine(HeaderCard fcard) { if (fcard != null) { this.iter.add(fcard); } } /** * Add or replace a key with the given boolean value and comment. * * @param key * The header key. * @param val * The boolean value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, boolean val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, double val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, int val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, long val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given string value and comment. * * @param key * The header key. * @param val * The string value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, String val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given bigdecimal value and comment. Note * that float values will be promoted to doubles. * * @param key * The header key. * @param val * The bigDecimal value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, BigDecimal val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given BigInteger value and comment. Note * that float values will be promoted to doubles. * * @param key * The header key. * @param val * The BigInteger value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, BigInteger val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given boolean value and comment. * * @param key * The header key. * @param val * The boolean value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, boolean val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @param precision * The fixed number of decimal places to show. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, double val, int precision, String comment) throws HeaderCardException { this.iter.add(new HeaderCard(key, val, precision, comment)); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, double val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, long val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given string value and comment. * * @param key * The header key. * @param val * The string value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, String val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * get a builder for filling the header cards using the builder pattern. * * @param key * the key for the first card. * @return the builder for header cards. */ public HeaderCardBuilder card(IFitsHeader key) { return new HeaderCardBuilder(this, key); } /** * Tests if the specified keyword is present in this table. * * @param key * the keyword to be found. * @return true if the specified keyword is present in this * table; false otherwise. */ public final boolean containsKey(IFitsHeader key) { return this.cards.containsKey(key.key()); } /** * Tests if the specified keyword is present in this table. * * @param key * the keyword to be found. * @return true if the specified keyword is present in this * table; false otherwise. */ public final boolean containsKey(String key) { return this.cards.containsKey(key); } /** * Delete the card associated with the given key. Nothing occurs if the key * is not found. * * @param key * The header key. */ public void deleteKey(IFitsHeader key) { deleteKey(key.key()); } /** * Delete the card associated with the given key. Nothing occurs if the key * is not found. * * @param key * The header key. */ public void deleteKey(String key) { if (containsKey(key)) { this.iter.setKey(key); if (this.iter.hasNext()) { this.iter.next(); this.iter.remove(); } } } /** * Print the header to a given stream. * * @param ps * the stream to which the card images are dumped. */ public void dumpHeader(PrintStream ps) { this.iter = iterator(); while (this.iter.hasNext()) { ps.println(this.iter.next()); } } /** * Find the card associated with a given key. If found this sets the mark to * the card, otherwise it unsets the mark. * * @param key * The header key. * @return null if the keyword could not be found; return the * HeaderCard object otherwise. */ public HeaderCard findCard(IFitsHeader key) { return this.findCard(key.key()); } /** * Find the card associated with a given key. If found this sets the mark to * the card, otherwise it unsets the mark. * * @param key * The header key. * @return null if the keyword could not be found; return the * HeaderCard object otherwise. */ public HeaderCard findCard(String key) { HeaderCard card = this.cards.get(key); if (card != null) { this.iter.setKey(key); } return card; } /** * Find the card associated with a given key. * * @param key * The header key. * @return null if the keyword could not be found; return the * card image otherwise. */ public String findKey(String key) { HeaderCard card = findCard(key); if (card == null) { return null; } else { return card.toString(); } } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public BigDecimal getBigDecimalValue(IFitsHeader key) { return getBigDecimalValue(key.key()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public BigDecimal getBigDecimalValue(String key) { return getBigDecimalValue(key, BigDecimal.ZERO); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public BigDecimal getBigDecimalValue(String key, BigDecimal dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigDecimal.class, dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public BigInteger getBigIntegerValue(IFitsHeader key, BigInteger dft) { return getBigIntegerValue(key.key(), dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public BigInteger getBigIntegerValue(String key) { return getBigIntegerValue(key, BigInteger.ZERO); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public BigInteger getBigIntegerValue(String key, BigInteger dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigInteger.class, dft); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @return The value found, or false if not found or if the keyword is not a * logical keyword. */ public boolean getBooleanValue(IFitsHeader key) { return getBooleanValue(key.key()); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @param dft * The value to be returned if the key cannot be found or if the * parameter does not seem to be a boolean. * @return the associated value. */ public boolean getBooleanValue(IFitsHeader key, boolean dft) { return getBooleanValue(key.key(), dft); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @return The value found, or false if not found or if the keyword is not a * logical keyword. */ public boolean getBooleanValue(String key) { return getBooleanValue(key, false); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @param dft * The value to be returned if the key cannot be found or if the * parameter does not seem to be a boolean. * @return the associated value. */ public boolean getBooleanValue(String key, boolean dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Boolean.class, dft).booleanValue(); } /** * Get the n'th card image in the header * * @param n * the card index to get * @return the card image; return null if the n'th card does * not exist. * @deprecated An iterator from {@link #iterator(int)} or * {@link #iterator()} should be used for sequential access to * the header. */ @Deprecated public String getCard(int n) { if (n >= 0 && n < this.cards.size()) { return this.cards.get(n).toString(); } return null; } /** * Return the size of the data including any needed padding. * * @return the data segment size including any needed padding. */ public long getDataSize() { return FitsUtil.addPadding(trueDataSize()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public double getDoubleValue(IFitsHeader key) { return getDoubleValue(key.key()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public double getDoubleValue(IFitsHeader key, double dft) { return getDoubleValue(key.key(), dft); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public double getDoubleValue(String key) { return getDoubleValue(key, 0.); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public double getDoubleValue(String key, double dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Double.class, dft).doubleValue(); } /** * @return the list of duplicate cards. Note that when the header is read * in, only the last entry for a given keyword is retained in the * active header. This method returns earlier cards that have been * discarded in the order in which they were encountered in the * header. It is possible for there to be many cards with the same * keyword in this list. */ public List getDuplicates() { return this.duplicates; } /** * @return Get the offset of this header */ @Override public long getFileOffset() { return this.fileOffset; } /** * Get the float value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public float getFloatValue(IFitsHeader key) { return getFloatValue(key.key()); } /** * @return the float value associated with the given key. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public float getFloatValue(IFitsHeader key, float dft) { return (float) getDoubleValue(key, dft); } /** * Get the float value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public float getFloatValue(String key) { return (float) getDoubleValue(key); } /** * @return the float value associated with the given key. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public float getFloatValue(String key, float dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Float.class, dft).floatValue(); } /** * Get the int value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public int getIntValue(IFitsHeader key) { return (int) getLongValue(key); } /** * @return the value associated with the key as an int. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public int getIntValue(IFitsHeader key, int dft) { return (int) getLongValue(key, dft); } /** * Get the int value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public int getIntValue(String key) { return (int) getLongValue(key); } /** * @return the value associated with the key as an int. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public int getIntValue(String key, int dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Integer.class, dft).intValue(); } /** * Get the n'th key in the header. * * @param n * the index of the key * @return the card image; return null if the n'th key does not * exist. * @deprecated An iterator from {@link #iterator(int)} or * {@link #iterator()} should be used for sequential access to * the header. */ @Deprecated public String getKey(int n) { if (n >= 0 && n < this.cards.size()) { return this.cards.get(n).getKey(); } return null; } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public long getLongValue(IFitsHeader key) { return getLongValue(key.key()); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public long getLongValue(IFitsHeader key, long dft) { return getLongValue(key.key(), dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public long getLongValue(String key) { return getLongValue(key, 0L); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public long getLongValue(String key, long dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Long.class, dft).longValue(); } /** * @return the number of cards in the header */ public int getNumberOfCards() { return this.cards.size(); } /** * @return the number of physical cards in the header. */ public int getNumberOfPhysicalCards() { int count = 0; for (HeaderCard card : this.cards) { count += card.cardSize(); } return count; } /** * @return the size of the original header in bytes. */ public long getOriginalSize() { return FitsUtil.addPadding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * @return the size of the header in bytes */ @Override public long getSize() { return headerSize(); } public String getStringValue(IFitsHeader header) { return getStringValue(header.key()); } /** * Get the String value associated with the given key. * * @param key * The header key. * @return The associated value or null if not found or if the value is not * a string. */ public String getStringValue(String key) { HeaderCard fcard = findCard(key); if (fcard == null || !fcard.isStringValue()) { return null; } return fcard.getValue(); } /** * @return Were duplicate header keys found when this record was read in? */ public boolean hadDuplicates() { return this.duplicates != null; } /** * Add a COMMENT line. * * @param value * The comment. * @exception HeaderCardException * If the parameter is not a valid FITS comment. */ public void insertComment(String value) throws HeaderCardException { insertCommentStyle(COMMENT.key(), value); } /** * Add a line to the header using the COMMENT style, i.e., no '=' in column * 9. * * @param header * The comment style header. * @param value * A string to follow the header. */ public void insertCommentStyle(String header, String value) { this.iter.add(HeaderCard.saveNewHeaderCard(header, value, false)); } /** * Add a HISTORY line. * * @param value * The history record. * @exception HeaderCardException * If the parameter is not a valid FITS comment. */ public void insertHistory(String value) throws HeaderCardException { insertCommentStyle(HISTORY.key(), value); } /** @return an iterator over the header cards */ public Cursor iterator() { return this.cards.iterator(0); } /** * @return an iterator over the header cards starting at an index * @param index * the card index to start the iterator */ public Cursor iterator(int index) { return this.cards.iterator(index); } /** * @return Create the data element corresponding to the current header * @throws FitsException * if the header did not contain enough information to detect * the type of the data */ public Data makeData() throws FitsException { return FitsFactory.dataFactory(this); } /** * @return the next card in the Header using the current iterator */ public HeaderCard nextCard() { if (this.iter.hasNext()) { return this.iter.next(); } else { return null; } } /** * Create a header which points to the given data object. * * @param o * The data object to be described. * @throws FitsException * if the data was not valid for this header. * @deprecated Use the appropriate Header constructor. */ @Deprecated public void pointToData(Data o) throws FitsException { o.fillHeader(this); } /** * Read a stream for header data. * * @param dis * The input stream to read the data from. * @throws TruncatedFileException * the the stream ended prematurely * @throws IOException * if the operation failed */ @Override public void read(ArrayDataInput dis) throws TruncatedFileException, IOException { if (dis instanceof RandomAccess) { this.fileOffset = FitsUtil.findOffset(dis); } else { this.fileOffset = -1; } boolean firstCard = true; HeaderCardCountingArrayDataInput cardCountingArray = new HeaderCardCountingArrayDataInput(dis); try { while (true) { HeaderCard fcard = new HeaderCard(cardCountingArray); String key = fcard.getKey(); if (firstCard) { checkFirstCard(key); firstCard = false; } if (key != null && this.cards.containsKey(key)) { addDuplicate(this.cards.get(key)); } // We don't check the value here. If the user // wants to be sure that long strings are disabled, // they can call setLongStringsEnabled(false) after // reading the header. if (LONGSTRN.key().equals(key)) { FitsFactory.setLongStringsEnabled(true); } // save card addLine(fcard); if (END.key().equals(key)) { break; // Out of reading the header. } } } catch (EOFException e) { if (!firstCard) { throw new IOException("Invalid FITS Header:", new TruncatedFileException(e.getMessage())); } throw e; } catch (TruncatedFileException e) { if (firstCard && FitsFactory.getAllowTerminalJunk()) { EOFException eofException = new EOFException("First card truncated"); eofException.initCause(e); throw eofException; } throw new IOException("Invalid FITS Header:", new TruncatedFileException(e.getMessage())); } catch (Exception e) { throw new IOException("Invalid FITS Header", e); } if (this.fileOffset >= 0) { this.input = dis; } this.originalCardCount = cardCountingArray.getPhysicalCardsRead(); // Read to the end of the current FITS block. // try { dis.skipAllBytes(FitsUtil.padding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE)); } catch (IOException e) { throw new TruncatedFileException("Failed to skip " + FitsUtil.padding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE) + " bytes", e); } } /** * Delete a key. * * @param key * The header key. * @throws HeaderCardException * if the operation failed * @deprecated see {@link #deleteKey(String)} */ @Deprecated public void removeCard(String key) throws HeaderCardException { deleteKey(key); } /** Reset the file pointer to the beginning of the header */ @Override public boolean reset() { try { FitsUtil.reposition(this.input, this.fileOffset); return true; } catch (Exception e) { LOG.log(Level.WARNING, "Exception while repositioning " + this.input, e); return false; } } /** * Indicate that we can use the current internal size of the Header as the * 'original' size (e.g., perhaps we've rewritten the header to disk). Note * that affects the results of rewriteable(), so users should not call this * method unless the underlying data has actually been updated. */ public void resetOriginalSize() { this.originalCardCount = getNumberOfPhysicalCards(); } /** Rewrite the header. */ @Override public void rewrite() throws FitsException, IOException { ArrayDataOutput dos = (ArrayDataOutput) this.input; if (rewriteable()) { FitsUtil.reposition(dos, this.fileOffset); write(dos); dos.flush(); } else { throw new FitsException("Invalid attempt to rewrite Header."); } } @Override public boolean rewriteable() { return this.fileOffset >= 0 && this.input instanceof ArrayDataOutput && // (getNumberOfPhysicalCards() + MAX_CARDS_PER_HEADER - 1) / MAX_CARDS_PER_HEADER == // (this.originalCardCount + MAX_CARDS_PER_HEADER - 1) / MAX_CARDS_PER_HEADER; } /** * Set the BITPIX value for the header. The following values are permitted * by FITS conventions: *
    *
  • 8 -- signed byte data. Also used for tables.
  • *
  • 16 -- signed short data.
  • *
  • 32 -- signed int data.
  • *
  • 64 -- signed long data.
  • *
  • -32 -- IEEE 32 bit floating point numbers.
  • *
  • -64 -- IEEE 64 bit floating point numbers.
  • *
* * @param val * The value set by the user. */ public void setBitpix(int val) { this.iter = iterator(); this.iter.next(); this.iter.add(HeaderCard.saveNewHeaderCard(BITPIX.key(), BITPIX.comment(), false).setValue(val)); } /** * Overwite the default header card sorter. * * @param headerSorter * the sorter tu use or null to disable sorting */ public void setHeaderSorter(Comparator headerSorter) { this.headerSorter = headerSorter; } /** * Set the value of the NAXIS keyword * * @param val * The dimensionality of the data. */ public void setNaxes(int val) { this.iter.setKey(BITPIX.key()); if (this.iter.hasNext()) { this.iter.next(); } this.iter.add(HeaderCard.saveNewHeaderCard(NAXIS.key(), NAXIS.comment(), false).setValue(val)); } /** * Set the dimension for a given axis. * * @param axis * The axis being set. * @param dim * The dimension */ public void setNaxis(int axis, int dim) { if (axis <= 0) { LOG.warning("setNaxis ignored because axis less than 0"); return; } if (axis == 1) { this.iter.setKey(NAXIS.key()); } else if (axis > 1) { this.iter.setKey(NAXISn.n(axis - 1).key()); } if (this.iter.hasNext()) { this.iter.next(); } IFitsHeader naxisKey = NAXISn.n(axis); this.iter.add(HeaderCard.saveNewHeaderCard(naxisKey.key(), naxisKey.comment(), false).setValue(dim)); } /** * Set the SIMPLE keyword to the given value. * * @param val * The boolean value -- Should be true for FITS data. */ public void setSimple(boolean val) { deleteKey(SIMPLE); deleteKey(XTENSION); // If we're flipping back to and from the primary header // we need to add in the EXTEND keyword whenever we become // a primary, because it's not permitted in the extensions // (at least not where it needs to be in the primary array). if (findCard(NAXIS) != null) { int nax = getIntValue(NAXIS); this.iter = iterator(); if (findCard(NAXISn.n(nax)) != null) { this.iter.next(); deleteKey(EXTEND); this.iter.add(HeaderCard.saveNewHeaderCard(EXTEND.key(), EXTEND.comment(), false).setValue(true)); } } this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(SIMPLE.key(), SIMPLE.comment(), false).setValue(val)); } /** * Set the XTENSION keyword to the given value. * * @param val * The name of the extension. */ public void setXtension(String val) { deleteKey(SIMPLE); deleteKey(XTENSION); deleteKey(EXTEND); this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(XTENSION.key(), XTENSION.comment(), true).setValue(val)); } /** * @return the number of cards in the header * @deprecated use {@link #getNumberOfCards()}. The units of the size of the * header may be unclear. */ @Deprecated public int size() { return this.cards.size(); } /** * Update a line in the header * * @param key * The key of the card to be replaced. * @param card * A new card * @throws HeaderCardException * if the operation failed */ public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException { deleteKey(key); this.iter.add(card); } /** * Update a line in the header * * @param key * The key of the card to be replaced. * @param card * A new card * @throws HeaderCardException * if the operation failed */ public void updateLine(String key, HeaderCard card) throws HeaderCardException { addHeaderCard(key, card); } /** * Overwrite the lines in the header. Add the new PHDU header to the current * one. If keywords appear twice, the new value and comment overwrite the * current contents. By Richard J Mathar. * * @param newHdr * the list of new header data lines to replace the current ones. * @throws HeaderCardException * if the operation failed */ public void updateLines(final Header newHdr) throws HeaderCardException { Cursor j = newHdr.iterator(); while (j.hasNext()) { HeaderCard nextHCard = j.next(); // updateLine() doesn't work with COMMENT and HISTORYs because // this would allow only one COMMENT in total in each header if (nextHCard.getKey().equals(COMMENT.key())) { insertComment(nextHCard.getComment()); } else if (nextHCard.getKey().equals(HISTORY.key())) { insertHistory(nextHCard.getComment()); } else { updateLine(nextHCard.getKey(), nextHCard); } } } /** * Write the current header (including any needed padding) to the output * stream. * * @param dos * The output stream to which the data is to be written. * @throws FitsException * if the header could not be written. */ @Override public void write(ArrayDataOutput dos) throws FitsException { FitsSettings settings = FitsFactory.current(); this.fileOffset = FitsUtil.findOffset(dos); // Ensure that all cards are in the proper order. if (this.headerSorter != null) { this.cards.sort(this.headerSorter); } checkBeginning(); checkEnd(); Cursor writeIterator = this.cards.iterator(0); try { while (writeIterator.hasNext()) { HeaderCard card = writeIterator.next(); byte[] b = AsciiFuncs.getBytes(card.toString(settings)); dos.write(b); } FitsUtil.pad(dos, getNumberOfPhysicalCards() * HeaderCard.FITS_HEADER_CARD_SIZE, (byte) ' '); dos.flush(); } catch (IOException e) { throw new FitsException("IO Error writing header: " + e); } } private void addDuplicate(HeaderCard dup) { if (!COMMENT.key().equals(dup.getKey()) && !HISTORY.key().equals(dup.getKey()) && !dup.getKey().trim().isEmpty()) { LOG.log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey()); if (this.duplicates == null) { this.duplicates = new ArrayList(); } this.duplicates.add(dup); } } private void addHeaderCard(String key, HeaderCard card) { deleteKey(key); this.iter.add(card); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(IFitsHeader key) throws FitsException { cardCheck(key.key()); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(String key) throws FitsException { if (!this.iter.hasNext()) { throw new FitsException("Header terminates before " + key); } HeaderCard card = this.iter.next(); if (!card.getKey().equals(key)) { throw new FitsException("Key " + key + " not found where expected." + "Found " + card.getKey()); } } private void checkFirstCard(String key) throws IOException { if (key == null || !key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { if (this.fileOffset > 0 && FitsFactory.getAllowTerminalJunk()) { throw new EOFException("Not FITS format at " + this.fileOffset + ":" + key); } else { throw new IOException("Not FITS format at " + this.fileOffset + ":" + key); } } } private void doCardChecks(boolean isTable, boolean isExtension) throws FitsException { cardCheck(BITPIX); cardCheck(NAXIS); int nax = getIntValue(NAXIS); this.iter.next(); for (int i = 1; i <= nax; i += 1) { cardCheck(NAXISn.n(i)); } if (isExtension) { cardCheck(PCOUNT); cardCheck(GCOUNT); if (isTable) { cardCheck(TFIELDS); } } // This does not check for the EXTEND keyword which // if present in the primary array must immediately follow // the NAXISn. } /** * Move after the EXTEND keyword in images. Used in bug fix noted by V. * Forchi */ void afterExtend() { if (findCard(EXTEND) != null) { nextCard(); } } /** * Ensure that the header begins with a valid set of keywords. Note that we * do not check the values of these keywords. */ void checkBeginning() throws FitsException { this.iter = iterator(); if (!this.iter.hasNext()) { throw new FitsException("Empty Header"); } HeaderCard card = this.iter.next(); String key = card.getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { throw new FitsException("No SIMPLE or XTENSION at beginning of Header"); } boolean isTable = false; boolean isExtension = false; if (key.equals(XTENSION.key())) { String value = card.getValue(); if (value == null || value.isEmpty()) { throw new FitsException("Empty XTENSION keyword"); } isExtension = true; if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) { isTable = true; } } doCardChecks(isTable, isExtension); } /** * Ensure that the header has exactly one END keyword in the appropriate * location. */ void checkEnd() { // Ensure we have an END card only at the end of the // header. // this.iter = iterator(); HeaderCard card; while (this.iter.hasNext()) { card = this.iter.next(); if (!card.isKeyValuePair() && card.getKey().equals(END.key())) { this.iter.remove(); } } // End cannot have a comment this.iter.add(HeaderCard.saveNewHeaderCard(END.key(), null, false)); } /** * Return the size of the header data including padding. * * @return the header size including any needed padding. */ int headerSize() { if (!isValidHeader()) { return 0; } return FitsUtil.addPadding(getNumberOfPhysicalCards() * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * Is this a valid header. * * @return true for a valid header, false * otherwise. */ boolean isValidHeader() { if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) { return false; } this.iter = iterator(); String key = this.iter.next().getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { return false; } key = this.iter.next().getKey(); if (!key.equals(BITPIX.key())) { return false; } key = this.iter.next().getKey(); if (!key.equals(NAXIS.key())) { return false; } while (this.iter.hasNext()) { key = this.iter.next().getKey(); } return key.equals(END.key()); } /** * Create a header for a null image. */ void nullImage() { this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(SIMPLE.key(), SIMPLE.comment(), false).setValue(true)); this.iter.add(HeaderCard.saveNewHeaderCard(BITPIX.key(), BITPIX.comment(), false).setValue(BasicHDU.BITPIX_BYTE)); this.iter.add(HeaderCard.saveNewHeaderCard(NAXIS.key(), NAXIS.comment(), false).setValue(0)); this.iter.add(HeaderCard.saveNewHeaderCard(EXTEND.key(), EXTEND.comment(), false).setValue(true)); } /** * Find the end of a set of keywords describing a column or axis (or * anything else terminated by an index. This routine leaves the header * ready to add keywords after any existing keywords with the index * specified. The user should specify a prefix to a keyword that is * guaranteed to be present. */ Cursor positionAfterIndex(IFitsHeader prefix, int col) { String colnum = String.valueOf(col); this.iter.setKey(prefix.n(col).key()); if (this.iter.hasNext()) { // Bug fix (references to forward) here by Laurent Borges boolean toFar = false; while (this.iter.hasNext()) { String key = this.iter.next().getKey().trim(); if (key == null || key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) { toFar = true; break; } } if (toFar) { this.iter.prev(); // Gone one too far, so skip back an element. } } return this.iter; } /** * Replace the key with a new key. Typically this is used when deleting or * inserting columns so that TFORMx -> TFORMx-1 * * @param oldKey * The old header keyword. * @param newKey * the new header keyword. * @return true if the card was replaced. * @exception HeaderCardException * If newKey is not a valid FITS keyword. */ boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException { return replaceKey(oldKey.key(), newKey.key()); } /** * Replace the key with a new key. Typically this is used when deleting or * inserting columns so that TFORMx -> TFORMx-1 * * @param oldKey * The old header keyword. * @param newKey * the new header keyword. * @return true if the card was replaced. * @exception HeaderCardException * If newKey is not a valid FITS keyword. */ boolean replaceKey(String oldKey, String newKey) throws HeaderCardException { HeaderCard oldCard = findCard(oldKey); if (oldCard == null) { return false; } if (!this.cards.replaceKey(oldKey, newKey)) { throw new HeaderCardException("Duplicate key in replace"); } oldCard.setKey(newKey); return true; } /** * Calculate the unpadded size of the data segment from the header * information. * * @return the unpadded data segment size. */ long trueDataSize() { if (!isValidHeader()) { return 0L; } int naxis = getIntValue(NAXIS, 0); // If there are no axes then there is no data. if (naxis == 0) { return 0L; } getIntValue(BITPIX); int[] axes = new int[naxis]; for (int axis = 1; axis <= naxis; axis += 1) { axes[axis - 1] = getIntValue(NAXISn.n(axis), 0); } boolean isGroup = getBooleanValue(GROUPS, false); int pcount = getIntValue(PCOUNT, 0); int gcount = getIntValue(GCOUNT, 1); int startAxis = 0; if (isGroup && naxis > 1 && axes[0] == 0) { startAxis = 1; } long size = 1; for (int i = startAxis; i < naxis; i += 1) { size *= axes[i]; } size += pcount; size *= gcount; // Now multiply by the number of bits per pixel and // convert to bytes. size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE; return size; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy