1 package nom.tam.fits;
2
3 import java.io.IOException;
4 import java.io.PrintStream;
5 import java.util.Date;
6 import java.util.logging.Level;
7 import java.util.logging.Logger;
8
9 import nom.tam.fits.header.Bitpix;
10 import nom.tam.fits.header.Checksum;
11 import nom.tam.fits.header.IFitsHeader;
12 import nom.tam.fits.header.Standard;
13 import nom.tam.fits.utilities.FitsCheckSum;
14 import nom.tam.util.ArrayDataInput;
15 import nom.tam.util.ArrayDataOutput;
16 import nom.tam.util.FitsOutput;
17 import nom.tam.util.RandomAccess;
18
19 /*
20 * #%L
21 * nom.tam FITS library
22 * %%
23 * Copyright (C) 2004 - 2024 nom-tam-fits
24 * %%
25 * This is free and unencumbered software released into the public domain.
26 *
27 * Anyone is free to copy, modify, publish, use, compile, sell, or
28 * distribute this software, either in source code form or as a compiled
29 * binary, for any purpose, commercial or non-commercial, and by any
30 * means.
31 *
32 * In jurisdictions that recognize copyright laws, the author or authors
33 * of this software dedicate any and all copyright interest in the
34 * software to the public domain. We make this dedication for the benefit
35 * of the public at large and to the detriment of our heirs and
36 * successors. We intend this dedication to be an overt act of
37 * relinquishment in perpetuity of all present and future rights to this
38 * software under copyright law.
39 *
40 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
41 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
42 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
43 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
44 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
45 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
46 * OTHER DEALINGS IN THE SOFTWARE.
47 * #L%
48 */
49
50 import static nom.tam.fits.header.Standard.AUTHOR;
51 import static nom.tam.fits.header.Standard.BLANK;
52 import static nom.tam.fits.header.Standard.BSCALE;
53 import static nom.tam.fits.header.Standard.BUNIT;
54 import static nom.tam.fits.header.Standard.BZERO;
55 import static nom.tam.fits.header.Standard.DATAMAX;
56 import static nom.tam.fits.header.Standard.DATAMIN;
57 import static nom.tam.fits.header.Standard.DATE;
58 import static nom.tam.fits.header.Standard.DATE_OBS;
59 import static nom.tam.fits.header.Standard.EPOCH;
60 import static nom.tam.fits.header.Standard.EQUINOX;
61 import static nom.tam.fits.header.Standard.GCOUNT;
62 import static nom.tam.fits.header.Standard.INSTRUME;
63 import static nom.tam.fits.header.Standard.NAXIS;
64 import static nom.tam.fits.header.Standard.NAXISn;
65 import static nom.tam.fits.header.Standard.OBJECT;
66 import static nom.tam.fits.header.Standard.OBSERVER;
67 import static nom.tam.fits.header.Standard.ORIGIN;
68 import static nom.tam.fits.header.Standard.PCOUNT;
69 import static nom.tam.fits.header.Standard.REFERENC;
70 import static nom.tam.fits.header.Standard.TELESCOP;
71 import static nom.tam.util.LoggerHelper.getLogger;
72
73 /**
74 * Abstract base class for all header-data unit (HDU) types. A HDU is a self-contained building block of the FITS files,
75 * which encapsulates information on a particular data object such as an image or table. As the name implies, HDUs
76 * constitute of a header and data entities, which can be accessed separately (via the {@link #getHeader()} and
77 * {@link #getData()} methods respectively). The {@link Header} class provides many functions to add, delete and read
78 * header keywords in HDUs in a variety of formats. The {@link Data} class, and its concrete subclassses provide access
79 * to the specific data object that the HDU encapsulates. It provides basic functionality for an HDU.
80 *
81 * @param <DataClass> the generic type of data contained in this HDU instance.
82 */
83 public abstract class BasicHDU<DataClass extends Data> implements FitsElement {
84
85 private static final int MAX_NAXIS_ALLOWED = 999;
86
87 private static final Logger LOG = getLogger(BasicHDU.class);
88
89 /**
90 * @deprecated Use {@link Bitpix#VALUE_FOR_BYTE} instead.
91 */
92 @Deprecated
93 public static final int BITPIX_BYTE = 8;
94
95 /**
96 * @deprecated Use {@link Bitpix#VALUE_FOR_SHORT} instead.
97 */
98 @Deprecated
99 public static final int BITPIX_SHORT = 16;
100
101 /**
102 * @deprecated Use {@link Bitpix#VALUE_FOR_INT} instead.
103 */
104 @Deprecated
105 public static final int BITPIX_INT = 32;
106
107 /**
108 * @deprecated Use {@link Bitpix#VALUE_FOR_LONG} instead.
109 */
110 @Deprecated
111 public static final int BITPIX_LONG = 64;
112
113 /**
114 * @deprecated Use {@link Bitpix#VALUE_FOR_FLOAT} instead.
115 */
116 @Deprecated
117 public static final int BITPIX_FLOAT = -32;
118
119 /**
120 * @deprecated Use {@link Bitpix#VALUE_FOR_DOUBLE} instead.
121 */
122 @Deprecated
123 public static final int BITPIX_DOUBLE = -64;
124
125 /** The associated header. */
126 protected Header myHeader = null;
127
128 /** The associated data unit. */
129 protected DataClass myData = null;
130
131 /**
132 * Creates a new HDU from the specified FITS header and associated data object.
133 *
134 * @deprecated intended for internal use. Its visibility should be reduced to package level in the future.
135 *
136 * @param myHeader the FITS header describing the data and any user-specific keywords
137 * @param myData the corresponding data object
138 */
139 protected BasicHDU(Header myHeader, DataClass myData) {
140 setHeader(myHeader);
141 this.myData = myData;
142 }
143
144 private void setHeader(Header header) {
145 this.myHeader = header;
146 if (header != null) {
147 this.myHeader.assignTo(this);
148 }
149 }
150
151 /**
152 * @deprecated Use {@link NullDataHDU} instead. Gets a HDU with no data, only header.
153 *
154 * @return an HDU without content
155 */
156 @Deprecated
157 public static NullDataHDU getDummyHDU() {
158 return new NullDataHDU();
159 }
160
161 /**
162 * Checks that this is a valid header for the HDU. This method is static but should be implemented by all
163 * subclasses.
164 *
165 * @deprecated (<i>for internal use</i>) Will be removed as it serves no purpose.
166 *
167 * @param header to validate.
168 *
169 * @return <CODE>true</CODE> if this is a valid header.
170 */
171 public static boolean isHeader(Header header) {
172 return false;
173 }
174
175 /**
176 * @deprecated (<i>for internal use</i>) Will be removed as it serves no purpose.
177 *
178 * @return if this object can be described as a FITS image. This method is static but should be implemented by
179 * all subclasses.
180 *
181 * @param o The Object being tested.
182 */
183 public static boolean isData(Object o) {
184 return false;
185 }
186
187 /**
188 * Add information to the header.
189 *
190 * @param key key to add to the header
191 * @param val value for the key to add
192 *
193 * @throws HeaderCardException if the card does not follow the specification
194 *
195 * @see #addValue(String, boolean, String)
196 * @see #addValue(IFitsHeader, int)
197 * @see #addValue(IFitsHeader, double)
198 * @see #addValue(IFitsHeader, String)
199 */
200 public void addValue(IFitsHeader key, boolean val) throws HeaderCardException {
201 myHeader.addValue(key.key(), val, key.comment());
202 }
203
204 /**
205 * Add information to the header.
206 *
207 * @param key key to add to the header
208 * @param val value for the key to add
209 *
210 * @throws HeaderCardException if the card does not follow the specification
211 *
212 * @see #addValue(String, boolean, String)
213 * @see #addValue(IFitsHeader, boolean)
214 * @see #addValue(IFitsHeader, int)
215 * @see #addValue(IFitsHeader, String)
216 */
217 public void addValue(IFitsHeader key, double val) throws HeaderCardException {
218 myHeader.addValue(key.key(), val, key.comment());
219 }
220
221 /**
222 * Add information to the header.
223 *
224 * @param key key to add to the header
225 * @param val value for the key to add
226 *
227 * @throws HeaderCardException if the card does not follow the specification
228 *
229 * @see #addValue(String, boolean, String)
230 * @see #addValue(IFitsHeader, boolean)
231 * @see #addValue(IFitsHeader, double)
232 * @see #addValue(IFitsHeader, String)
233 */
234 public void addValue(IFitsHeader key, int val) throws HeaderCardException {
235 myHeader.addValue(key.key(), val, key.comment());
236 }
237
238 /**
239 * Add information to the header.
240 *
241 * @param key key to add to the header
242 * @param val value for the key to add
243 *
244 * @throws HeaderCardException if the card does not follow the specification
245 *
246 * @see #addValue(String, boolean, String)
247 * @see #addValue(IFitsHeader, boolean)
248 * @see #addValue(IFitsHeader, int)
249 * @see #addValue(IFitsHeader, double)
250 */
251 public void addValue(IFitsHeader key, String val) throws HeaderCardException {
252 myHeader.addValue(key.key(), val, key.comment());
253 }
254
255 /**
256 * Add information to the header.
257 *
258 * @param key key to add to the header
259 * @param val value for the key to add
260 * @param comment comment for the key/value pair
261 *
262 * @throws HeaderCardException if the card does not follow the specification
263 *
264 * @see #addValue(IFitsHeader, boolean)
265 * @see #addValue(String, int, String)
266 * @see #addValue(String, double, String)
267 * @see #addValue(String, String, String)
268 */
269 public void addValue(String key, boolean val, String comment) throws HeaderCardException {
270 myHeader.addValue(key, val, comment);
271 }
272
273 /**
274 * Add information to the header.
275 *
276 * @param key key to add to the header
277 * @param val value for the key to add
278 * @param comment comment for the key/value pair
279 *
280 * @throws HeaderCardException if the card does not follow the specification
281 *
282 * @see #addValue(IFitsHeader, double)
283 * @see #addValue(String, boolean, String)
284 * @see #addValue(String, int, String)
285 * @see #addValue(String, String, String)
286 */
287 public void addValue(String key, double val, String comment) throws HeaderCardException {
288 myHeader.addValue(key, val, comment);
289 }
290
291 /**
292 * Add information to the header.
293 *
294 * @param key key to add to the header
295 * @param val value for the key to add
296 * @param comment comment for the key/value pair
297 *
298 * @throws HeaderCardException if the card does not follow the specification
299 *
300 * @see #addValue(IFitsHeader, int)
301 * @see #addValue(String, boolean, String)
302 * @see #addValue(String, double, String)
303 * @see #addValue(String, String, String)
304 */
305 public void addValue(String key, int val, String comment) throws HeaderCardException {
306 myHeader.addValue(key, val, comment);
307 }
308
309 /**
310 * Add information to the header.
311 *
312 * @param key key to add to the header
313 * @param val value for the key to add
314 * @param comment comment for the key/value pair
315 *
316 * @throws HeaderCardException if the card does not follow the specification
317 *
318 * @see #addValue(IFitsHeader, String)
319 * @see #addValue(String, boolean, String)
320 * @see #addValue(String, double, String)
321 * @see #addValue(String, int, String)
322 */
323 public void addValue(String key, String val, String comment) throws HeaderCardException {
324 myHeader.addValue(key, val, comment);
325 }
326
327 /**
328 * Checks if this HDU can be used as a primary HDU. For historical reasons FITS only allows certain HDU types to
329 * appear at the head of FITS files. Further HDU types can only be added as extensions after the first HDU. If this
330 * call returns <code>false</code> you may need to add e.g. a dummy {@link NullDataHDU} as the primary HDU at the
331 * beginning of the FITS before you can add this one.
332 *
333 * @return Indicate whether HDU can be primary HDU. This method must be overriden in HDU types which can appear at
334 * the beginning of a FITS file.
335 */
336 final boolean canBePrimary() {
337 return Standard.XTENSION_IMAGE.equals(getCanonicalXtension());
338 }
339
340 /**
341 * Return the name of the person who compiled the information in the data associated with this header.
342 *
343 * @return either <CODE>null</CODE> or a String object
344 */
345 public String getAuthor() {
346 return myHeader.getStringValue(AUTHOR);
347 }
348
349 /**
350 * In FITS files the index represented by NAXIS1 is the index that changes most rapidly. This reflectsf the behavior
351 * of Fortran where there are true multidimensional arrays. In Java in a multidimensional array is an array of
352 * arrays and the first index is the index that changes slowest. So at some point a client of the library is going
353 * to have to invert the order. E.g., if I have a FITS file will
354 *
355 * <pre>
356 * BITPIX=16
357 * NAXIS1=10
358 * NAXIS2=20
359 * NAXIS3=30
360 * </pre>
361 *
362 * this will be read into a Java array short[30][20][10] so it makes sense to me at least that the returned
363 * dimensions are 30,20,10
364 *
365 * @return the dimensions of the axis.
366 *
367 * @throws FitsException if the axis are configured wrong.
368 */
369 public int[] getAxes() throws FitsException {
370 int nAxis = myHeader.getIntValue(NAXIS, 0);
371 if (nAxis < 0) {
372 throw new FitsException("Negative NAXIS value " + nAxis);
373 }
374 if (nAxis > MAX_NAXIS_ALLOWED) {
375 throw new FitsException("NAXIS value " + nAxis + " too large");
376 }
377
378 if (nAxis == 0) {
379 return null;
380 }
381
382 int[] axes = new int[nAxis];
383 for (int i = 1; i <= nAxis; i++) {
384 axes[nAxis - i] = myHeader.getIntValue(NAXISn.n(i), 0);
385 }
386
387 return axes;
388 }
389
390 /**
391 * Return the Bitpix enum type for this HDU.
392 *
393 * @return The Bitpix enum object for this HDU.
394 *
395 * @throws FitsException if the BITPIX value in the header is absent or invalid.
396 *
397 * @since 1.16
398 *
399 * @see #getBitPix()
400 */
401 public Bitpix getBitpix() throws FitsException {
402 return Bitpix.fromHeader(myHeader);
403 }
404
405 /**
406 * Return the BITPIX integer value as stored in the FIS header.
407 *
408 * @return The BITPIX integer values for this HDU as it appears in the header.
409 *
410 * @throws FitsException if the BITPIX value in the header is absent or invalid.
411 *
412 * @deprecated (<i>for internal use</i>) Will reduce visibility or remove entirely in the future.
413 *
414 * @see #getBitpix()
415 */
416 public final int getBitPix() throws FitsException {
417 return getBitpix().getHeaderValue();
418 }
419
420 /**
421 * Returns the name of the physical unit in which images are represented.
422 *
423 * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} and not for other HDU or data
424 * types.
425 *
426 * @return the standard name of the physical unit in which the image is expressed, e.g.
427 * <code>"Jy beam^{-1}"</code>.
428 */
429 public String getBUnit() {
430 return myHeader.getStringValue(BUNIT);
431 }
432
433 /**
434 * Returns the integer value that signifies blank (missing or <code>null</code>) data in an integer image.
435 *
436 * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer
437 * type data and not for other HDU or data types.
438 *
439 * @return the integer value used for identifying blank / missing data in integer images.
440 *
441 * @throws FitsException if the header does not specify a blanking value.
442 */
443 public long getBlankValue() throws FitsException {
444 if (!myHeader.containsKey(BLANK.key())) {
445 throw new FitsException("BLANK undefined");
446 }
447 return myHeader.getLongValue(BLANK);
448 }
449
450 /**
451 * Returns the floating-point increment between adjacent integer values in the image.
452 *
453 * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer type data and not
454 * for other HDU or data types.
455 *
456 * @return the floating-point quantum that corresponds to the increment of 1 in the integer data representation.
457 *
458 * @see #getBZero()
459 */
460 @Deprecated
461 public double getBScale() {
462 return myHeader.getDoubleValue(BSCALE, 1.0);
463 }
464
465 /**
466 * Returns the floating-point value that corresponds to an 0 integer value in the image.
467 *
468 * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer type data and not
469 * for other HDU or data types.
470 *
471 * @return the floating point value that correspond to the integer 0 in the image data.
472 *
473 * @see #getBScale()
474 */
475 @Deprecated
476 public double getBZero() {
477 return myHeader.getDoubleValue(BZERO, 0.0);
478 }
479
480 /**
481 * Get the FITS file creation date as a <CODE>Date</CODE> object.
482 *
483 * @return either <CODE>null</CODE> or a Date object
484 */
485 public Date getCreationDate() {
486 try {
487 return new FitsDate(myHeader.getStringValue(DATE)).toDate();
488 } catch (FitsException e) {
489 LOG.log(Level.SEVERE, "Unable to convert string to FITS date", e);
490 return null;
491 }
492 }
493
494 /**
495 * Returns the data component of this HDU.
496 *
497 * @return the associated Data object
498 */
499 public DataClass getData() {
500 return myData;
501 }
502
503 /**
504 * Get the equinox in years for the celestial coordinate system in which positions given in either the header or
505 * data are expressed.
506 *
507 * @return either <CODE>null</CODE> or a String object
508 *
509 * @deprecated use {@link #getEquinox()} instead
510 */
511 @Deprecated
512 public double getEpoch() {
513 return myHeader.getDoubleValue(EPOCH, -1.0);
514 }
515
516 /**
517 * Get the equinox in years for the celestial coordinate system in which positions given in either the header or
518 * data are expressed.
519 *
520 * @return either <CODE>null</CODE> or a String object
521 */
522 public double getEquinox() {
523 return myHeader.getDoubleValue(EQUINOX, -1.0);
524 }
525
526 @Override
527 public long getFileOffset() {
528 return myHeader.getFileOffset();
529 }
530
531 /**
532 * Returns the number of data objects (of identical shape and size) that are group together in this HDUs data
533 * segment. For most data types this would be simply 1, except for {@link RandomGroupsData}, where other values are
534 * possible.
535 *
536 * @return the number of data objects (of identical shape and size) that are grouped together in the data
537 * segment.
538 *
539 * @deprecated Should not be exposed outside of {@link RandomGroupsHDU} -- will reduce visibility in the future/
540 *
541 * @see #getParameterCount()
542 */
543 public int getGroupCount() {
544 return myHeader.getIntValue(GCOUNT, 1);
545 }
546
547 /**
548 * Returns the decoded checksum that is stored in the header of this HDU under the <code>CHECKSUM</code> keyword. It
549 * does not have much use, and is not needed for integrity verification since the purpose of the CHECKSUM value is
550 * merely to ensure that the checksum of the HDU is always <code>(int) -1</code>.
551 *
552 * @deprecated Not very useful, since it has no meaning other than ensuring that the checksum of the
553 * HDU yields <code>(int) -1</code> (that is <code>0xffffffff</code>) after including
554 * this value for the CHECKSUM keyword in the header. It will be removed in the
555 * future. Use {@link #verifyIntegrity()} instead when appropriate.
556 *
557 * @return the decoded FITS checksum value recorded in the HDU
558 *
559 * @throws FitsException if the HDU's header does not contain a <code>CHECKSUM</code> keyword.
560 *
561 * @see #calcChecksum()
562 * @see Fits#calcChecksum(int)
563 * @see #getStoredDatasum()
564 * @see FitsCheckSum#getStoredDatasum(Header)
565 *
566 * @since 1.17
567 */
568 public long getStoredChecksum() throws FitsException {
569 return FitsCheckSum.getStoredChecksum(myHeader);
570 }
571
572 /**
573 * Returns the FITS checksum for the HDU's data that is stored in the header of this HDU under the
574 * <code>DATASUM</code> keyword. This may be useful to compare against the checksum calculated from data in memory
575 * (e.g. via {@link Data#calcChecksum()}) to check changes / corruption of the in-memory data vs what was stored in
576 * the file. Note however, that this type of checkum test will fail if the file used non-standard padding at the end
577 * of the data segment, even if the data themselves are identical. Hence, for verifying data contained in a file
578 * {@link #verifyDataIntegrity()} or {@link #verifyIntegrity()} should be preferred.
579 *
580 * @return the FITS <code>DATASUM</code> value recorded in the HDU
581 *
582 * @throws FitsException if the HDU's header does not contain a <code>DATASUM</code> keyword.
583 *
584 * @see #verifyDataIntegrity()
585 * @see Data#calcChecksum()
586 *
587 * @since 1.17
588 */
589 public long getStoredDatasum() throws FitsException {
590 return FitsCheckSum.getStoredDatasum(myHeader);
591 }
592
593 /**
594 * <p>
595 * Computes the checksums for this HDU and stores the <code>CHECKSUM</code> and <code>DATASUM</code> values in the
596 * header. This should be the last modification to the HDU before writing it.
597 * </p>
598 * <p>
599 * Note, that this method will always calculate the checksum in memory. As a result it will load data in deferred
600 * read mode into RAM for performaing the calculation. If you prefer to keep deferred read mode data unloaded, you
601 * should use {@link Fits#setChecksum(int)} instead.
602 *
603 * @throws FitsException if there was an error serializing the HDU for the checksum computation.
604 *
605 * @see Fits#setChecksum(int)
606 * @see FitsCheckSum#setChecksum(BasicHDU)
607 * @see #getStoredDatasum()
608 *
609 * @since 1.17
610 */
611 public void setChecksum() throws FitsException {
612 FitsCheckSum.setChecksum(this);
613 }
614
615 /**
616 * Checks the HDU's integrity, using the recorded CHECKSUM and/or DATASUM keywords if present. In addition of
617 * performing the same checks as {@link #verifyDataIntegrity()}, it also checks the overall checksum of the HDU if
618 * possible. When the header has a CHECKSUM keyword stored, the overall checksum of the HDU must be
619 * <code>0xffffffff</code>, that is -1 in 32-bit representation.
620 *
621 * @return <code>true</code> if the HDU has a CHECKSUM and/or DATASUM record to check against,
622 * otherwise <code>false</code>
623 *
624 * @throws FitsException if the HDU fails the integrity test.
625 * @throws IOException if there was an I/O error accessing the input.
626 *
627 * @see #verifyDataIntegrity()
628 * @see Fits#verifyIntegrity()
629 *
630 * @since 1.18.1
631 */
632 @SuppressWarnings("resource")
633 public boolean verifyIntegrity() throws FitsException, IOException {
634 boolean result = verifyDataIntegrity();
635
636 if (myHeader.getCard(Checksum.CHECKSUM) == null) {
637 return result;
638 }
639
640 long fsum = (myHeader.getStreamChecksum() < 0) ?
641 FitsCheckSum.checksum(myHeader.getRandomAccessInput(), getFileOffset(), getSize()) :
642 FitsCheckSum.sumOf(myHeader.getStreamChecksum(), myData.getStreamChecksum());
643
644 if (fsum != FitsCheckSum.HDU_CHECKSUM) {
645 throw new FitsIntegrityException("checksum", fsum, FitsCheckSum.HDU_CHECKSUM);
646 }
647 return true;
648 }
649
650 /**
651 * Checks that the HDUs data checksum is correct. The recorded DATASUM will be used, if available, to check the
652 * integrity of the data segment.
653 *
654 * @return <code>true</code> if the HDU has DATASUM record to check against, otherwise
655 * <code>false</code>
656 *
657 * @throws FitsException if the HDU fails the integrity test.
658 * @throws IOException if there was an I/O error accessing the input.
659 *
660 * @see #verifyIntegrity()
661 * @see Fits#verifyIntegrity()
662 *
663 * @since 1.18.1
664 */
665 @SuppressWarnings("resource")
666 public boolean verifyDataIntegrity() throws FitsException, IOException {
667 if (getHeader().getCard(Checksum.DATASUM) == null) {
668 return false;
669 }
670
671 Data d = getData();
672 RandomAccess rin = myData.getRandomAccessInput();
673 long fsum = (rin != null) ? FitsCheckSum.checksum(rin, d.getFileOffset(), d.getSize()) : d.getStreamChecksum();
674
675 if (fsum != getStoredDatasum()) {
676 throw new FitsIntegrityException("datasum", fsum, getStoredDatasum());
677 }
678 return true;
679 }
680
681 /**
682 * Computes and returns the FITS checksum for this HDU, e.g. to compare agains the stored <code>CHECKSUM</code> in
683 * the FITS header. This method always computes the checksum from data fully loaded in memory. As such it will load
684 * deferred read mode data into RAM to perform the calculation. If you prefer to leave the data in deferred read
685 * mode, you can use {@link Fits#calcChecksum(int)} instead.
686 *
687 * @deprecated Use {@link #verifyIntegrity()} instead when appropriate. It's not particularly useful
688 * since integrity checking does not use or require knowledge of this sum. May be
689 * removed from future releases.
690 *
691 * @return the computed HDU checksum (in memory).
692 *
693 * @throws FitsException if there was an error while calculating the checksum
694 *
695 * @see Data#calcChecksum()
696 * @see Fits#calcChecksum(int)
697 * @see FitsCheckSum#checksum(BasicHDU)
698 *
699 * @since 1.17
700 */
701 public long calcChecksum() throws FitsException {
702 return FitsCheckSum.checksum(this);
703 }
704
705 /**
706 * Returns the FITS header component of this HDU
707 *
708 * @return the associated header
709 *
710 * @see Fits#getPrimaryHeader()
711 * @see Fits#getCompleteHeader(int)
712 * @see Fits#getCompleteHeader(String)
713 * @see Fits#getCompleteHeader(String, int)
714 */
715 public Header getHeader() {
716 return myHeader;
717 }
718
719 /**
720 * Returns a header card builder for filling the header cards using the builder pattern.
721 *
722 * @param key the key for the first card.
723 *
724 * @return the builder for header cards.
725 */
726 public HeaderCardBuilder card(IFitsHeader key) {
727 return myHeader.card(key);
728 }
729
730 /**
731 * Get the name of the instrument which was used to acquire the data in this FITS file.
732 *
733 * @return either <CODE>null</CODE> or a String object
734 */
735 public String getInstrument() {
736 return myHeader.getStringValue(INSTRUME);
737 }
738
739 /**
740 * Returns the underlying Java object (usually an array of some type) that stores the data internally.
741 *
742 * @return the non-FITS data object. Same as {@link #getData()}.<code>getKernel()</code>.
743 */
744 public final Object getKernel() {
745 try {
746 return myData.getKernel();
747 } catch (FitsException e) {
748 LOG.log(Level.SEVERE, "Unable to get kernel data", e);
749 return null;
750 }
751 }
752
753 /**
754 * Return the minimum valid value in the array.
755 *
756 * @return minimum value.
757 */
758 public double getMaximumValue() {
759 return myHeader.getDoubleValue(DATAMAX);
760 }
761
762 /**
763 * Return the minimum valid value in the array.
764 *
765 * @return minimum value.
766 */
767 public double getMinimumValue() {
768 return myHeader.getDoubleValue(DATAMIN);
769 }
770
771 /**
772 * Get the name of the observed object in this FITS file.
773 *
774 * @return either <CODE>null</CODE> or a String object
775 */
776 public String getObject() {
777 return myHeader.getStringValue(OBJECT);
778 }
779
780 /**
781 * Get the FITS file observation date as a <CODE>Date</CODE> object.
782 *
783 * @return either <CODE>null</CODE> or a Date object
784 */
785 public Date getObservationDate() {
786 try {
787 return new FitsDate(myHeader.getStringValue(DATE_OBS)).toDate();
788 } catch (FitsException e) {
789 LOG.log(Level.SEVERE, "Unable to convert string to FITS observation date", e);
790 return null;
791 }
792 }
793
794 /**
795 * Get the name of the person who acquired the data in this FITS file.
796 *
797 * @return either <CODE>null</CODE> or a String object
798 */
799 public String getObserver() {
800 return myHeader.getStringValue(OBSERVER);
801 }
802
803 /**
804 * Get the name of the organization which created this FITS file.
805 *
806 * @return either <CODE>null</CODE> or a String object
807 */
808 public String getOrigin() {
809 return myHeader.getStringValue(ORIGIN);
810 }
811
812 /**
813 * Returns the number of parameter bytes (per data group) accompanying each data object in the group.
814 *
815 * @return the number of bytes used for arbitrary extra parameters accompanying each data object in the group.
816 *
817 * @deprecated Should not be exposed outside of {@link RandomGroupsHDU} -- will reduce visibility in the future.
818 *
819 * @see #getGroupCount()
820 */
821 public int getParameterCount() {
822 return myHeader.getIntValue(PCOUNT, 0);
823 }
824
825 /**
826 * Return the citation of a reference where the data associated with this header are published.
827 *
828 * @return either <CODE>null</CODE> or a String object
829 */
830 public String getReference() {
831 return myHeader.getStringValue(REFERENC);
832 }
833
834 @Override
835 public long getSize() {
836 long size = 0;
837
838 if (myHeader != null) {
839 size += myHeader.getSize();
840 }
841 if (myData != null) {
842 size += myData.getSize();
843 }
844 return size;
845 }
846
847 /**
848 * Get the name of the telescope which was used to acquire the data in this FITS file.
849 *
850 * @return either <CODE>null</CODE> or a String object
851 */
852 public String getTelescope() {
853 return myHeader.getStringValue(TELESCOP);
854 }
855
856 /**
857 * Get the String value associated with the header <CODE>keyword</CODE>. Trailing spaces are not significant in FITS
858 * headers and are automatically omitted during parsing. Leading spaces are however considered significant, and are
859 * retained otherwise.
860 *
861 * @param keyword the FITS keyword
862 *
863 * @deprecated (<i>for internal use</i>) Will reduced visibility in the future. Use
864 * {@link Header#getStringValue(IFitsHeader)} or similar instead followed by
865 * {@link String#trim()} if necessary.
866 *
867 * @return either <CODE>null</CODE> or a String with leading/trailing blanks stripped.
868 */
869 public String getTrimmedString(String keyword) {
870 String s = myHeader.getStringValue(keyword);
871 if (s != null) {
872 s = s.trim();
873 }
874 return s;
875 }
876
877 /**
878 * Get the String value associated with the header <CODE>keyword</CODE>.with leading spaces removed. Trailing spaces
879 * are not significant in FITS headers and are automatically omitted during parsing. Leading spaces are however
880 * considered significant, and are retained otherwise.
881 *
882 * @param keyword the FITS keyword
883 *
884 * @deprecated (<i>for internal use</i>) Will reduced visibility in the future. Use
885 * {@link Header#getStringValue(String)} or similar instead followed by
886 * {@link String#trim()} if necessary.
887 *
888 * @return either <CODE>null</CODE> or a String with leading/trailing blanks stripped.
889 */
890 public String getTrimmedString(IFitsHeader keyword) {
891 return getTrimmedString(keyword.key());
892 }
893
894 /**
895 * Print out some information about this HDU.
896 *
897 * @param stream the printstream to write the info on
898 *
899 * @throws FitsException if the HDU is malformed
900 */
901 public abstract void info(PrintStream stream) throws FitsException;
902
903 @Override
904 @SuppressWarnings({"unchecked", "deprecation"})
905 public void read(ArrayDataInput stream) throws FitsException, IOException {
906 setHeader(Header.readHeader(stream));
907 myData = (DataClass) FitsFactory.dataFactory(myHeader);
908 myData.read(stream);
909 }
910
911 @Override
912 public boolean reset() {
913 return myHeader.reset();
914 }
915
916 @Override
917 public void rewrite() throws FitsException, IOException {
918 if (!rewriteable()) {
919 throw new FitsException("Invalid attempt to rewrite HDU");
920 }
921 myHeader.rewrite();
922 if (!myData.isDeferred()) {
923 myData.rewrite();
924 }
925 }
926
927 @Override
928 public boolean rewriteable() {
929 return myHeader.rewriteable() && myData.rewriteable();
930 }
931
932 /**
933 * Indicate that an HDU is the first element of a FITS file.
934 *
935 * @param value value to set
936 *
937 * @throws FitsException if the operation failed
938 */
939 void setPrimaryHDU(boolean value) throws FitsException {
940 if (value && !canBePrimary()) {
941 throw new FitsException("Invalid attempt to make HDU of type:" + this.getClass().getName() + " primary.");
942 }
943
944 Header.KeywordCheck mode = myHeader.getKeywordChecking();
945 myHeader.setKeywordChecking(Header.KeywordCheck.DATA_TYPE);
946 myHeader.setRequiredKeys(value ? null : getCanonicalXtension());
947 myHeader.setKeywordChecking(mode);
948 }
949
950 /**
951 * Returns the canonical (expected) value for the XTENSION keywords for this type of HDU. Concrete HDU
952 * implementations should override this method as appropriate. As of FITS version 4, only the following XTENSION
953 * values are recognised: 'IMAGE', 'TABLE', and 'BINTABLE'.
954 *
955 * @return The value to use for the XTENSION keyword.
956 *
957 * @since 1.18
958 */
959 protected String getCanonicalXtension() {
960 // TODO this should become an abstract method for 2.0. Prior to that we provide a default
961 // implementation for API back-compatibility reasons for any 3rd-party HDU implementations.
962 // To warn that this should be ovewritten, we'll log a warning...
963 LOG.warning(getClass().getName() + " should override getCanonicalXtension() method as appropriate.");
964 return "UNKNOWN";
965 }
966
967 @Override
968 public void write(ArrayDataOutput stream) throws FitsException {
969 if (myHeader == null) {
970 setHeader(new Header());
971 }
972
973 if (stream instanceof FitsOutput) {
974 boolean isFirst = ((FitsOutput) stream).isAtStart();
975 setPrimaryHDU(canBePrimary() && isFirst);
976 }
977
978 myHeader.write(stream);
979
980 if (myData != null) {
981 myData.write(stream);
982 }
983 try {
984 stream.flush();
985 } catch (IOException e) {
986 throw new FitsException("Error flushing at end of HDU", e);
987 }
988 }
989 }