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

nom.tam.fits.ImageData Maven / Gradle / Ivy

Go to download

Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data.

There is a newer version: 1.21.1
Show newest version
package nom.tam.fits;

/*-
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 1996 - 2024 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.IOException;
import java.nio.Buffer;
import java.util.Arrays;

import nom.tam.fits.header.Bitpix;
import nom.tam.fits.header.NonStandard;
import nom.tam.fits.header.Standard;
import nom.tam.image.ImageTiler;
import nom.tam.image.StandardImageTiler;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.ArrayFuncs;
import nom.tam.util.ComplexValue;
import nom.tam.util.Cursor;
import nom.tam.util.FitsEncoder;
import nom.tam.util.Quantizer;
import nom.tam.util.RandomAccess;
import nom.tam.util.array.MultiArrayIterator;
import nom.tam.util.type.ElementType;

/**
 * 

* Image data. Essentially these data are a primitive multi-dimensional array, such as a double[], * float[][], or short[][][]. Or, as of version 1.20, they may also be {@link ComplexValue} * types also. *

*

* Starting in version 0.9 of the FITS library, this class allows users to defer the reading of images if the FITS data * is being read from a file. An {@link ImageTiler} object is supplied which can return an arbitrary subset of the image * as a one dimensional array -- suitable for manipulation by standard Java libraries. The image data may not be read * from the input until the user calls a method that requires the actual data (e.g. the {@link #getData()} / * {@link #getKernel()}, {@link #convertTo(Class)} or {@link #write(ArrayDataOutput)} methods). *

* * @see ImageHDU */ public class ImageData extends Data { private static final String COMPLEX_TYPE = "COMPLEX"; /** * This class describes an array */ protected static class ArrayDesc { private final Class type; private int[] dims; private Quantizer quant; private int complexAxis = -1; ArrayDesc(int[] dims, Class type) { this.dims = dims; this.type = type; if (ComplexValue.class.isAssignableFrom(type)) { complexAxis = dims.length; } } } /** * This inner class allows the ImageTiler to see if the user has read in the data. */ protected class ImageDataTiler extends StandardImageTiler { ImageDataTiler(RandomAccess o, long offset, ArrayDesc d) { super(o, offset, d.dims, d.type); } @Override protected Object getMemoryImage() { return dataArray; } } // private static final Logger LOG = getLogger(ImageData.class); /** The size of the data */ private long byteSize; /** * The actual array of data. This is normally a multi-dimensional primitive array. It may be null until the * getData() routine is invoked, or it may be filled by during the read call when a non-random access device is * used. */ private Object dataArray; /** A description of what the data should look like */ private ArrayDesc dataDescription; /** The image tiler associated with this image. */ private StandardImageTiler tiler; /** * Create the equivalent of a null data element. */ public ImageData() { this(new byte[0]); } /** * (for internal use) Create an array from a header description. This is typically how data will be created * when reading FITS data from a file where the header is read first. This creates an empty array. * * @param h header to be used as a template. * * @throws FitsException if there was a problem with the header description. */ public ImageData(Header h) throws FitsException { dataDescription = parseHeader(h); } /** * Create an ImageData object using the specified object to initialize the data array. * * @param x The initial data array. This should be a primitive array but this is not checked * currently. * * @throws IllegalArgumentException if x is not a suitable primitive array */ public ImageData(Object x) throws IllegalArgumentException { try { checkCompatible(x); } catch (FitsException e) { throw new IllegalArgumentException(e.getMessage(), e); } dataDescription = new ArrayDesc(ArrayFuncs.getDimensions(x), ArrayFuncs.getBaseClass(x)); dataArray = x; byteSize = FitsEncoder.computeSize(x); } @Override protected void loadData(ArrayDataInput in) throws IOException, FitsException { if (tiler != null) { dataArray = tiler.getCompleteImage(); } else { dataArray = ArrayFuncs.newInstance(getType(), getDimensions()); in.readImage(dataArray); } } @Override public void read(ArrayDataInput in) throws FitsException { tiler = (in instanceof RandomAccess) ? new ImageDataTiler((RandomAccess) in, ((RandomAccess) in).getFilePointer(), dataDescription) : null; super.read(in); } @Override protected Object getCurrentData() { return dataArray; } /** * Returns the class that can be used to divide this image into tiles that may be processed separately (and in * parallel). * * @return image tiler for this image instance. */ public StandardImageTiler getTiler() { return tiler; } /** * Sets the buffer that may hold a serialized version of the data for this image. * * @param data the buffer that may hold this image's data in serialized form. */ public void setBuffer(Buffer data) { ElementType elementType = ElementType.forClass(getType()); dataArray = ArrayFuncs.newInstance(getType(), getDimensions()); MultiArrayIterator iterator = new MultiArrayIterator<>(dataArray); Object array = iterator.next(); while (array != null) { elementType.getArray(data, array); array = iterator.next(); } tiler = new ImageDataTiler(null, 0, dataDescription); } @SuppressWarnings({"resource", "deprecation"}) @Override public void write(ArrayDataOutput o) throws FitsException { // Don't need to write null data (noted by Jens Knudstrup) if (byteSize == 0) { return; } if (o != getRandomAccessInput()) { ensureData(); } try { o.writeArray(dataArray); } catch (IOException e) { throw new FitsException("IO Error on image write" + e); } FitsUtil.pad(o, getTrueSize()); } @SuppressWarnings("deprecation") @Override protected void fillHeader(Header head) throws FitsException { if (dataArray == null) { head.nullImage(); return; } Standard.context(ImageData.class); // We'll assume it's a primary image, until we know better... // Just in case, we don't want an XTENSION key lingering around... head.deleteKey(Standard.XTENSION); Cursor c = head.iterator(); c.add(HeaderCard.create(Standard.SIMPLE, true)); Class base = getType(); int[] dims = getDimensions(); if (ComplexValue.class.isAssignableFrom(base)) { dims = Arrays.copyOf(dims, dims.length + 1); dims[dims.length - 1] = 2; base = ComplexValue.Float.class.isAssignableFrom(base) ? float.class : double.class; } c.add(HeaderCard.create(Standard.BITPIX, Bitpix.forPrimitiveType(base).getHeaderValue())); c.add(HeaderCard.create(Standard.NAXIS, dims.length)); for (int i = 1; i <= dims.length; i++) { c.add(HeaderCard.create(Standard.NAXISn.n(i), dims[dims.length - i])); } // Just in case! c.add(HeaderCard.create(Standard.PCOUNT, 0)); c.add(HeaderCard.create(Standard.GCOUNT, 1)); c.add(HeaderCard.create(Standard.EXTEND, true)); if (isComplexValued()) { c.add(HeaderCard.create(Standard.CTYPEn.n(dims.length - dataDescription.complexAxis), COMPLEX_TYPE)); } if (dataDescription.quant != null) { dataDescription.quant.editImageHeader(head); } Standard.context(null); } @Override protected long getTrueSize() { return byteSize; } /** * Returns the image specification based on its description in a FITS header. * * @param h the FITS header that describes this image with the standard keywords for an image HDU. * * @return an object that captures the description contained in the header for internal use. * * @throws FitsException If there was a problem accessing or interpreting the required header values. */ protected ArrayDesc parseHeader(Header h) throws FitsException { String ext = h.getStringValue(Standard.XTENSION, Standard.XTENSION_IMAGE); if (!ext.equalsIgnoreCase(Standard.XTENSION_IMAGE) && !ext.equalsIgnoreCase(NonStandard.XTENSION_IUEIMAGE)) { throw new FitsException("Not an image header (XTENSION = " + h.getStringValue(Standard.XTENSION) + ")"); } int gCount = h.getIntValue(Standard.GCOUNT, 1); int pCount = h.getIntValue(Standard.PCOUNT, 0); if (gCount > 1 || pCount != 0) { throw new FitsException("Group data treated as images"); } Bitpix bitpix = Bitpix.fromHeader(h); Class baseClass = bitpix.getPrimitiveType(); int ndim = h.getIntValue(Standard.NAXIS, 0); int[] dims = new int[ndim]; // Note that we have to invert the order of the axes // for the FITS file to get the order in the array we // are generating. byteSize = ndim > 0 ? 1 : 0; for (int i = 1; i <= ndim; i++) { int cdim = h.getIntValue(Standard.NAXISn.n(i), 0); if (cdim < 0) { throw new FitsException("Invalid array dimension:" + cdim); } byteSize *= cdim; dims[ndim - i] = cdim; } byteSize *= bitpix.byteSize(); ArrayDesc desc = new ArrayDesc(dims, baseClass); if (COMPLEX_TYPE.equals(h.getStringValue(Standard.CTYPEn.n(1))) && dims[ndim - 1] == 2) { desc.complexAxis = ndim - 1; } else if (COMPLEX_TYPE.equals(h.getStringValue(Standard.CTYPEn.n(ndim))) && dims[0] == 2) { desc.complexAxis = 0; } desc.quant = Quantizer.fromImageHeader(h); if (desc.quant.isDefault()) { desc.quant = null; } return desc; } void setTiler(StandardImageTiler tiler) { this.tiler = tiler; } /** * (for expert users) Overrides the image size description in the header to the specified Java array * dimensions. Typically users should not call this method, unless they want to define the image dimensions in the * absence of the actual complete image data. For example, to describe the dimensions when using low-level writes of * an image row-by-row, without ever storing the entire image in memory. * * @param header A FITS image header * @param sizes The array dimensions in Java order (fastest varying index last) * * @throws FitsException if the size has negative values, or the header is not that for an image * @throws IllegalArgumentException should not actually happen * * @since 1.18 * * @see #fillHeader(Header) */ public static void overrideHeaderAxes(Header header, int... sizes) throws FitsException, IllegalArgumentException { String extType = header.getStringValue(Standard.XTENSION, Standard.XTENSION_IMAGE); if (!extType.equals(Standard.XTENSION_IMAGE) && !extType.equals(NonStandard.XTENSION_IUEIMAGE)) { throw new FitsException("Not an image header (XTENSION = " + extType + ")"); } // Remove prior NAXISn values int n = header.getIntValue(Standard.NAXIS); for (int i = 1; i <= n; i++) { header.deleteKey(Standard.NAXISn.n(i)); } Cursor c = header.iterator(); c.setKey(Standard.NAXIS.key()); c.add(HeaderCard.create(Standard.NAXIS, sizes.length)); for (int i = 1; i <= sizes.length; i++) { int l = sizes[sizes.length - i]; if (l < 0) { throw new FitsException("Invalid size[ " + i + "] = " + l); } c.add(HeaderCard.create(Standard.NAXISn.n(i), l)); } } /** * Creates a new FITS image using the specified primitive numerical Java array containing data. * * @param data A regulatly shaped primitive numerical Java array, which can be * multi-dimensional. * * @return A new FITS image that encapsulates the specified array data. * * @throws IllegalArgumentException if the argument is not a primitive numerical Java array. * * @since 1.19 */ public static ImageData from(Object data) throws IllegalArgumentException { return new ImageData(data); } /** * Checks if a given data object may constitute the kernel for an image. To conform, the data must be a regularly * shaped primitive numerical array of ant dimensions, or null. * * @param data A regularly shaped primitive numerical array of ny dimension, or * null * * @throws IllegalArgumentException If the array is not regularly shaped. * @throws FitsException If the argument is not a primitive numerical array type * * @since 1.19 */ static void checkCompatible(Object data) throws IllegalArgumentException, FitsException { if (data != null) { Class base = ArrayFuncs.getBaseClass(data); if (ComplexValue.Float.class.isAssignableFrom(base)) { base = float.class; } else if (ComplexValue.class.isAssignableFrom(base)) { base = double.class; } Bitpix.forPrimitiveType(base); ArrayFuncs.checkRegularArray(data, false); } } @Override @SuppressWarnings("deprecation") public ImageHDU toHDU() throws FitsException { Header h = new Header(); fillHeader(h); return new ImageHDU(h, this); } /** * Sets the conversion between decimal and integer data representations. The quantizer for the image is set * automatically if the image was read from a FITS input, and if any of the associated BSCALE, BZERO, or BLANK * keywords were defined in the HDU's header. User may use this methods to set a different quantization or to use no * quantization at all when converting between floating-point and integer representations. * * @param quant the quantizer that converts between floating-point and integer data representations, or * null to not use quantization and instead rely on simple rounding for decimal-ineger conversions.. * * @see #getQuantizer() * @see #convertTo(Class) * * @since 1.20 */ public void setQuantizer(Quantizer quant) { dataDescription.quant = quant; } /** * Returns the conversion between decimal and integer data representations. * * @return the quantizer that converts between floating-point and integer data representations, which may be * null * * @see #setQuantizer(Quantizer) * @see #convertTo(Class) * * @since 1.20 */ public final Quantizer getQuantizer() { return dataDescription.quant; } /** * Returns the element type of this image in its current representation. * * @return The element type of this image, such as int.class, double.class or * {@link ComplexValue}.class. * * @see #getDimensions() * @see #isComplexValued() * @see #convertTo(Class) * * @since 1.20 */ public final Class getType() { return dataDescription.type; } /** * Returns the dimensions of this image. * * @return An array containing the sizes along each data dimension, in Java indexing order. The returned array is * not used internally, and therefore modifying it will not damage the integrity of the image data. * * @see #getType() * * @since 1.20 */ public final int[] getDimensions() { return Arrays.copyOf(dataDescription.dims, dataDescription.dims.length); } /** * Checks if the image data is explicitly designated as a complex-valued image. An image may be designated as * complex-valued either because it was created with {@link ComplexValue} type data, or because it was read from a * FITS file in which one image axis of dimension 2 was designated as an axis containing complex-valued components * with the corresponding CTYPEn header keyword set to 'COMPLEX'. The complex-valued deignation checked by this * method is not the same as {@link #getType()}, as it does not necesarily mean that the data itself is currently in * {@link ComplexValue} type representation. Rather it simply means that this data can be represented as * {@link ComplexValue} type, possibly after an appropriate conversion to a {@link ComplexValue} type. * * @return true if the data is complex valued or has been explicitly designated as complex valued. * Otherwise false. * * @see #convertTo(Class) * @see #getType() * * @since 1.20 */ public final boolean isComplexValued() { return dataDescription.complexAxis >= 0; } /** * Converts this image HDU to another image HDU of a different type, possibly using a qunatizer for the * integer-decimal conversion of the data elements. In all other respects, the returned image is identical to the * the original. If th conversion is th indetity, it will return itself and the data may remain in deferred mode. * * @param type The primitive numerical type (e.g. int.class or double.class), or * else a {@link ComplexValue} type in which data should be represented. Complex * representations are normally available for data whose first or last CTYPEn axis was * described as 'COMPLEX' by the FITS header with a dimensionality is 2 corresponfing to a * pair of real and imaginary data elements. Even without the CTYPEn designation, it is * always possible to convert to complex all arrays that have a trailing Java dimension * (NAXIS1 in FITS) equal to 2. * * @return An image HDU containing the same data in the chosen representation by another type. (It may * be the same as this HDU if the type is unchanged from the original). * * @throws FitsException if the data cannot be read from the input. * * @see #isComplexValued() * @see ArrayFuncs#convertArray(Object, Class, Quantizer) * * @since 1.20 */ public ImageData convertTo(Class type) throws FitsException { if (type.isAssignableFrom(getType())) { return this; } ensureData(); ImageData typed = null; boolean toComplex = ComplexValue.class.isAssignableFrom(type) && !ComplexValue.class.isAssignableFrom(getType()); if (toComplex && dataDescription.complexAxis == 0) { // Special case of converting separate re/im arrays to complex... // 1. Convert to intermediate floating-point class as necessary (with quantization if any) Class numType = ComplexValue.Float.class.isAssignableFrom(type) ? float.class : double.class; Object[] t = (Object[]) ArrayFuncs.convertArray(dataArray, numType, getQuantizer()); ImageData f = new ImageData(ArrayFuncs.decimalsToComplex(t[0], t[1])); f.dataDescription.quant = getQuantizer(); // 2. Assemble complex from separate re/im components. return f.convertTo(type); } typed = new ImageData(ArrayFuncs.convertArray(dataArray, type, getQuantizer())); typed.dataDescription.quant = getQuantizer(); return typed; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy