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