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 }