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