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

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

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 - 2024 Weber Informatics LLC | Privacy Policy