001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.object.fits;
016
017import java.lang.reflect.Array;
018import java.util.Iterator;
019import java.util.List;
020import java.util.Vector;
021
022import hdf.object.Dataset;
023import hdf.object.Datatype;
024import hdf.object.FileFormat;
025import hdf.object.Group;
026import hdf.object.HObject;
027import hdf.object.MetaDataContainer;
028import hdf.object.ScalarDS;
029import hdf.object.fits.FitsAttribute;
030
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import nom.tam.fits.BasicHDU;
035import nom.tam.fits.Header;
036import nom.tam.fits.HeaderCard;
037
038/**
039 * FitsDataset describes an multi-dimension array of HDF5 scalar or atomic data types, such as byte, int,
040 * short, long, float, double and string, and operations performed on the scalar dataset
041 *
042 * The library predefines a modest number of datatypes. For details, read
043 * <a href="https://hdfgroup.github.io/hdf5/_h5_t__u_g.html#sec_datatype">HDF5 Datatypes in HDF5 User
044 * Guide</a>
045 *
046 * @version 1.1 9/4/2007
047 * @author Peter X. Cao
048 */
049public class FitsDataset extends ScalarDS implements MetaDataContainer {
050    private static final long serialVersionUID = 3944770379558335171L;
051
052    private static final Logger log = LoggerFactory.getLogger(FitsDataset.class);
053
054    /**
055     * The list of attributes of this data object. Members of the list are
056     * instance of Attribute.
057     */
058    private List attributeList;
059
060    /** the native dataset */
061    private BasicHDU nativeDataset;
062
063    /**
064     * Constructs an FitsDataset object with specific netcdf variable.
065     *
066     * @param fileFormat the netcdf file.
067     * @param hdu the BasicHDU.
068     * @param dName the name for this dataset.
069     * @param oid the unique identifier for this dataset.
070     */
071    public FitsDataset(FileFormat fileFormat, BasicHDU hdu, String dName, long[] oid)
072    {
073        super(fileFormat, dName, HObject.SEPARATOR, oid);
074        unsignedConverted = false;
075        nativeDataset     = hdu;
076    }
077
078    /**
079     * Check if the object has any attributes attached.
080     *
081     * @return true if it has any attributes, false otherwise.
082     */
083    @Override
084    public boolean hasAttribute()
085    {
086        return false;
087    }
088
089    // Implementing Dataset
090    @Override
091    public Dataset copy(Group pgroup, String dstName, long[] dims, Object buff) throws Exception
092    {
093        // not supported
094        throw new UnsupportedOperationException("copy operation unsupported for FITS.");
095    }
096
097    /*
098     * (non-Javadoc)
099     * @see hdf.object.Dataset#readBytes()
100     */
101    @Override
102    public byte[] readBytes() throws Exception
103    {
104        // not supported
105        throw new UnsupportedOperationException("readBytes operation unsupported for FITS.");
106    }
107
108    /**
109     * Reads the data from file.
110     *
111     * read() reads the data from file to a memory buffer and returns the memory
112     * buffer. The dataset object does not hold the memory buffer. To store the
113     * memory buffer in the dataset object, one must call getData().
114     *
115     * By default, the whole dataset is read into memory. Users can also select
116     * a subset to read. Subsetting is done in an implicit way.
117     *
118     * @return the data read from file.
119     *
120     * @see #getData()
121     *
122     * @throws Exception
123     *             if object can not be read
124     * @throws OutOfMemoryError
125     *             if memory is exhausted
126     */
127    @Override
128    public Object read() throws Exception
129    {
130        Object theData  = null;
131        Object fitsData = null;
132
133        if (nativeDataset == null)
134            return null;
135
136        try {
137            fitsData = nativeDataset.getData().getData();
138        }
139        catch (Exception ex) {
140            throw new UnsupportedOperationException(
141                "This implementation only supports integer and float dataset. "
142                + "It may not work for other datatypes. \n" + ex);
143        }
144
145        int n = get1DLength(fitsData);
146
147        theData = FitsDatatype.allocateArray(nativeDataset.getBitPix(), n);
148
149        to1Darray(fitsData, theData, 0);
150
151        return theData;
152    }
153
154    /**
155     * Writes a memory buffer to the object in the file.
156     *
157     * @param buf
158     *            the data to write
159     *
160     * @throws Exception
161     *             if data can not be written
162     */
163    @Override
164    public void write(Object buf) throws Exception
165    {
166        // not supported
167        throw new UnsupportedOperationException("write operation unsupported for FITS.");
168    }
169
170    /**
171     * Retrieves the object's metadata, such as attributes, from the file.
172     *
173     * Metadata, such as attributes, is stored in a List.
174     *
175     * @return the list of metadata objects.
176     *
177     * @throws Exception
178     *             if the metadata can not be retrieved
179     */
180    @SuppressWarnings("rawtypes")
181    public List getMetadata() throws Exception
182    {
183        if (attributeList != null)
184            return attributeList;
185
186        if (nativeDataset == null)
187            return null;
188
189        Header header = nativeDataset.getHeader();
190        if (header == null)
191            return null;
192
193        attributeList      = new Vector();
194        HeaderCard hc      = null;
195        Iterator it        = header.iterator();
196        FitsAttribute attr = null;
197        Datatype dtype     = new FitsDatatype(Datatype.CLASS_STRING, 80, 0, 0);
198        long[] dims        = {1};
199        String value       = null;
200        while (it.hasNext()) {
201            value         = "";
202            hc            = (HeaderCard)it.next();
203            attr          = new FitsAttribute(this, hc.getKey(), dtype, dims);
204            String tvalue = hc.getValue();
205            if (tvalue != null)
206                value += tvalue;
207            tvalue = hc.getComment();
208            if (tvalue != null)
209                value += " / " + tvalue;
210            attr.setAttributeData(value);
211            attributeList.add(attr);
212        }
213
214        return attributeList;
215    }
216
217    /**
218     * Writes a specific piece of metadata (such as an attribute) into the file.
219     *
220     * If an HDF(4&amp;5) attribute exists in the file, this method updates its
221     * value. If the attribute does not exist in the file, it creates the
222     * attribute in the file and attaches it to the object. It will fail to
223     * write a new attribute to the object where an attribute with the same name
224     * already exists. To update the value of an existing attribute in the file,
225     * one needs to get the instance of the attribute by getMetadata(), change
226     * its values, then use writeMetadata() to write the value.
227     *
228     * @param info
229     *            the metadata to write.
230     *
231     * @throws Exception
232     *             if the metadata can not be written
233     */
234    public void writeMetadata(Object info) throws Exception
235    {
236        // not supported
237        throw new UnsupportedOperationException("writeMetadata operation unsupported for FITS.");
238    }
239
240    /**
241     * Deletes an existing piece of metadata from this object.
242     *
243     * @param info
244     *            the metadata to delete.
245     *
246     * @throws Exception
247     *             if the metadata can not be removed
248     */
249    public void removeMetadata(Object info) throws Exception
250    {
251        // not supported
252        throw new UnsupportedOperationException("removeMetadata operation unsupported for FITS.");
253    }
254
255    /**
256     * Updates an existing piece of metadata attached to this object.
257     *
258     * @param info
259     *            the metadata to update.
260     *
261     * @throws Exception
262     *             if the metadata can not be updated
263     */
264    public void updateMetadata(Object info) throws Exception
265    {
266        // not supported
267        throw new UnsupportedOperationException("updateMetadata operation unsupported for FITS.");
268    }
269
270    /*
271     * (non-Javadoc)
272     * @see hdf.object.HObject#open()
273     */
274    @Override
275    public long open()
276    {
277        return -1;
278    }
279
280    /*
281     * (non-Javadoc)
282     * @see hdf.object.HObject#close(int)
283     */
284    @Override
285    public void close(long did)
286    {
287        // Nothing to implement
288    }
289
290    /*
291     * (non-Javadoc)
292     * @see hdf.object.Dataset#init()
293     */
294    @Override
295    public void init()
296    {
297        if (nativeDataset == null)
298            return;
299
300        if (inited)
301            return; // already called. Initialize only once
302
303        int[] axes = null;
304        try {
305            axes = nativeDataset.getAxes();
306        }
307        catch (Exception ex) {
308            log.debug("nativeDataset.getAxes():", ex);
309        }
310
311        if (axes == null)
312            return;
313
314        rank = axes.length;
315        if (rank == 0) {
316            // a scalar data point
317            isScalar = true;
318            rank     = 1;
319            dims     = new long[] {1};
320        }
321        else {
322            isScalar = false;
323            dims     = new long[rank];
324            for (int i = 0; i < rank; i++)
325                dims[i] = axes[i];
326        }
327
328        startDims    = new long[rank];
329        selectedDims = new long[rank];
330        for (int i = 0; i < rank; i++) {
331            startDims[i]    = 0;
332            selectedDims[i] = 1;
333        }
334
335        if (rank == 1) {
336            selectedIndex[0] = 0;
337            selectedDims[0]  = dims[0];
338        }
339        else if (rank == 2) {
340            selectedIndex[0] = 0;
341            selectedIndex[1] = 1;
342            selectedDims[0]  = dims[0];
343            selectedDims[1]  = dims[1];
344        }
345        else if (rank > 2) {
346            selectedIndex[0] = 0;
347            selectedIndex[1] = 1;
348            selectedIndex[2] = 2;
349            selectedDims[0]  = dims[0];
350            selectedDims[1]  = dims[1];
351        }
352
353        if ((rank > 1) && isText)
354            selectedDims[1] = 1;
355
356        inited = true;
357    }
358
359    /* Implement abstart ScalarDS */
360
361    /**
362     * Creates a new dataset.
363     *
364     * @param name the name of the dataset to create.
365     * @param pgroup the parent group of the new dataset.
366     * @param type the datatype of the dataset.
367     * @param dims the dimension size of the dataset.
368     * @param maxdims the max dimension size of the dataset.
369     * @param chunks the chunk size of the dataset.
370     * @param gzip the level of the gzip compression.
371     * @param data the array of data values.
372     *
373     * @return the new dataset if successful. Otherwise returns null.
374     *
375     * @throws Exception
376     *            if there is an error
377     */
378    public static FitsDataset create(String name, Group pgroup, Datatype type, long[] dims, long[] maxdims,
379                                     long[] chunks, int gzip, Object data) throws Exception
380    {
381        // not supported
382        throw new UnsupportedOperationException("Unsupported operation for FITS.");
383    }
384
385    /**
386     * Returns the datatype of the data object.
387     *
388     * @return the datatype of the data object.
389     */
390    @Override
391    public Datatype getDatatype()
392    {
393        if (datatype == null) {
394            try {
395                datatype = new FitsDatatype(nativeDataset.getBitPix());
396            }
397            catch (Exception ex) {
398                log.debug("getDatatype(): failed to create datatype: ", ex);
399                datatype = null;
400            }
401        }
402
403        return datatype;
404    }
405
406    /*
407     * (non-Javadoc)
408     * @see hdf.object.HObject#setName(java.lang.String)
409     */
410    @Override
411    public void setName(String newName) throws Exception
412    {
413        // not supported
414        throw new UnsupportedOperationException("Unsupported operation for FITS.");
415    }
416
417    private int get1DLength(Object data) throws Exception
418    {
419        if (!data.getClass().isArray())
420            return 1;
421
422        int len = Array.getLength(data);
423
424        int total = 0;
425        for (int i = 0; i < len; i++)
426            total += get1DLength(Array.get(data, i));
427
428        return total;
429    }
430
431    /** copy multi-dimension array of fits data into 1D array */
432    private int to1Darray(Object dataIn, Object dataOut, int offset) throws Exception
433    {
434        Class component = dataIn.getClass().getComponentType();
435        if (component == null)
436            return offset;
437
438        int size = Array.getLength(dataIn);
439        if (!component.isArray()) {
440            System.arraycopy(dataIn, 0, dataOut, offset, size);
441            return offset + size;
442        }
443
444        for (int i = size - 1; i >= 0; i--)
445            offset = to1Darray(Array.get(dataIn, i), dataOut, offset);
446
447        return offset;
448    }
449
450    // Implementing DataFormat
451    /* FITS does not support metadata */
452    /**
453     * Retrieves the object's metadata, such as attributes, from the file.
454     *
455     * Metadata, such as attributes, is stored in a List.
456     *
457     * @param attrPropList
458     *             the list of properties to get
459     *
460     * @return the list of metadata objects.
461     *
462     * @throws Exception
463     *             if the metadata can not be retrieved
464     */
465    public List getMetadata(int... attrPropList) throws Exception
466    {
467        throw new UnsupportedOperationException("getMetadata(int... attrPropList) is not supported");
468    }
469}