pax_global_header00006660000000000000000000000064131006365050014511gustar00rootroot0000000000000052 comment=0fa0023689db1bf261f1b9e0e9493453f5667afa nom-tam-fits-nom-tam-fits-1.15.2/000077500000000000000000000000001310063650500164235ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/.gitignore000066400000000000000000000004711310063650500204150ustar00rootroot00000000000000target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties /.classpath /.project /LICENSE.txt /.settings/ /jtest.fil /.travis.yml~ /target/ /.metadata/ /RemoteSystemsTempFiles/ /.apt_generated/ /.factorypath /target/ /nom-tam-fits.sonargraph/ /target/ .idea nom-tam-fits.iml nom-tam-fits-nom-tam-fits-1.15.2/.travis.yml000066400000000000000000000022101310063650500205270ustar00rootroot00000000000000language: java jdk: - oraclejdk7 branches: except: - blackbox-images install: - git clone --depth=50 --branch=master https://github.com/nom-tam-fits/blackbox-images.git ../blackbox-images - mvn install -Dmaven.test.skip=true -P!createHeasarcZip script: - mvn test ; export MAVEN_RESULT=$? - if [ "$MAVEN_RESULT" -ne 0 ]; then exit 1; fi - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then mvn clean deploy --quiet --settings settings.xml -Dmaven.test.skip=true -P!createHeasarcZip; fi after_success: - mvn clean test jacoco:report coveralls:report notifications: email: - richard.vannieuwenhoven@adesso.at - Tom.McGlynn@nasa.gov - erik.koerber@gmail.com env: global: - MAVEN_OPTS="-Xmx=2G" - TERM=dumb - secure: "bKh2my2DsryV1AgANX4DvhgttoTIeGs/QlEdRg5zAGQ8+4s7v4qw90iONMa/HSWuZCha8zzZdiplyXevY/CqICVOp8YtJ6SwfUSZpwNnKBVjwWKWI4uIRpDONYiMuVwSMgusREphiJY2A1raohXl48cP2JexEY+ftCSY11P2rLg=" - secure: "HoAeUJC7l85KrP47PN9jcDvyLpssmX/n9dj7l0WkjQKu+yH6rnHw7P8KxAwWgntjj1fdefotSbUd82FNziQPyNKOr/PTm71eyWjkoeMpRWabmP6Jw6aosmrtrfkHj2jWb83gXK3p8LX/TLNgcHjP4vv0Gr/GJOEVT1PPOtQiZuo=" nom-tam-fits-nom-tam-fits-1.15.2/README.md000066400000000000000000000032771310063650500177130ustar00rootroot00000000000000# nom-tam-fits current status [![Build Status](https://travis-ci.org/nom-tam-fits/nom-tam-fits.png)](https://travis-ci.org/nom-tam-fits/nom-tam-fits) [![Coverage Status](https://coveralls.io/repos/nom-tam-fits/nom-tam-fits/badge.svg?branch=master)](https://coveralls.io/r/nom-tam-fits/nom-tam-fits?branch=master) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/gov.nasa.gsfc.heasarc/nom-tam-fits/badge.svg)](https://maven-badges.herokuapp.com/maven-central/gov.nasa.gsfc.heasarc/nom-tam-fits) Pure java Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data. This is the original library from http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/java/ ported to maven and published to the central repo by the original developers! Please feel free to fork us and report any found issues at our github pages (Pull request are very welcom): [nom-tam-fits git repository](https://github.com/nom-tam-fits/nom-tam-fits "nom-tam-fits git repository") visit our documentation at: [nom-tam-fits project site](http://nom-tam-fits.github.io/nom-tam-fits/ "nom-tam-fits project site") If you want to use the bleeding edge version of nom-tam-fits, you can get it from sonatype: gov.nasa.gsfc.heasarc nom-tam-fits xxxxx-SNAPSHOT ... sonatype-snapshots https://oss.sonatype.org/content/repositories/snapshots true nom-tam-fits-nom-tam-fits-1.15.2/nom-tam-fits release-perform.launch000066400000000000000000000024011310063650500252000ustar00rootroot00000000000000 nom-tam-fits-nom-tam-fits-1.15.2/nom-tam-fits release-prepare.launch000066400000000000000000000022461310063650500251730ustar00rootroot00000000000000 nom-tam-fits-nom-tam-fits-1.15.2/pom.xml000066400000000000000000000474541310063650500177560ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 gov.nasa.gsfc.heasarc 1.15.2 jar nom-tam-fits nom.tam FITS library Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data. 1996 nom-tam-fits http://nom-tam-fits.github.io/nom-tam-fits tmcglynn Thomas A Mcglynn Thomas.A.McGlynn [at] NASA [dot] gov Developer Admin http://science.gsfc.nasa.gov/sed/index.cfm?fuseAction=people.jumpBio&&iPhonebookId=849 http://science.gsfc.nasa.gov/sed/images/Photos/038982816/McGlynn,_Tom.jpg ritchieGitHub Richard van Nieuwenhoven ritchie [at] gmx [dot] at Developer Admin http://www.gravatar.com/avatar/9e2c2e7aa94335b72952a4b2d56bfc89.png erikfk Erik Koerber erik.koerber [at] gmail [dot] com Developer Admin http://www.gravatar.com/avatar/6f82a608d349cc24f05604242422b1b8.png whcleveland William H. Cleveland Jr. whclevelandjr [at] gmail [dot] com Developer David Glowacki R.J. Mathar Laurent Michel Guillaume Belanger Laurent Bourges Rose Early Jorgo Baker Attila Kovacs attila [at] submm [dot] caltech [dot] edu http://www.submm.caltech.edu/~attila V. Forchi J.C. Segovia Booth Hartley Jason Weiss Martin Vrábel Jonathan Cook scm:git:https://github.com/nom-tam-fits/nom-tam-fits.git scm:git:https://github.com/nom-tam-fits/nom-tam-fits.git https://github.com/nom-tam-fits/nom-tam-fits HEAD sonatype-nexus-staging i4j sonytype Maven 2 repository https://oss.sonatype.org/service/local/staging/deploy/maven2 sonatype-nexus-staging i4j sonytype Snapshot Maven 2 repository https://oss.sonatype.org/content/repositories/snapshots github-project-site GitHub Maven 2 Project Site gitsite:git@github.com/nom-tam-fits/nom-tam-fits.git GitHub https://github.com/nom-tam-fits/nom-tam-fits/issues/ Public Domain file://${project.basedir}/src/license/publicdomain/license.txt repo org.apache.maven.wagon wagon-ssh 2.7 org.apache.maven.scm maven-scm-provider-gitexe 1.3 org.apache.maven.scm maven-scm-manager-plexus 1.3 com.github.stephenc.wagon wagon-gitsite 0.5 org.apache.maven.plugins maven-source-plugin 2.4 attach-sources jar-no-fork attach-test-sources test-jar-no-fork false org.apache.maven.plugins maven-deploy-plugin 2.8.2 org.apache.maven.plugins maven-gpg-plugin 1.6 gpg org.apache.maven.plugins maven-compiler-plugin 3.1 1.6 1.6 org.apache.maven.plugins maven-javadoc-plugin 2.10.1 false true attach-javadocs jar org.apache.maven.plugins maven-jar-plugin 2.5 true nom.tam.fits.utilities.Main org.apache.maven.plugins maven-release-plugin 2.5.1 forked-path false org.tinyjee.dim doxia-include-macro 1.1 initialize-doxia-include-macro pre-site initialize org.apache.maven.plugins maven-site-plugin 3.4 false org.apache.maven.wagon wagon-ssh 2.7 org.tinyjee.dim doxia-include-macro 1.1 org.apache.maven.doxia doxia-module-markdown 1.6 org.codehaus.mojo license-maven-plugin 1.7 false publicdomain https://raw.githubusercontent.com/nom-tam-fits/nom-tam-fits/master/src/license first update-file-header update-project-license process-sources src/main/java src/test com.googlecode.maven-java-formatter-plugin maven-java-formatter-plugin 0.4 format ${project.basedir}/src/main/eclipse/formatter.xml org.apache.maven.plugins maven-checkstyle-plugin 2.17 com.puppycrawl.tools checkstyle 6.14.1 checkstyle process-sources ${project.basedir}/src/main/checkstyle/nom-tam-fits-style.xml ${project.basedir}/src/main/checkstyle/checkstyle-suppressions.xml checkstyle.suppressions.file UTF-8 true true org.jacoco jacoco-maven-plugin 0.7.6.201602180812 default-prepare-agent prepare-agent default-report prepare-package report default-check check BUNDLE LINE COVEREDRATIO 0.98 org.eluder.coveralls coveralls-maven-plugin 4.1.0 org.codehaus.mojo findbugs-maven-plugin 3.0.3 check package org.apache.maven.plugins maven-pdf-plugin 1.3 pdf site pdf ${project.reporting.outputDirectory} false org.apache.maven.doxia doxia-module-markdown 1.6 org.apache.maven.plugins maven-surefire-plugin 2.19.1 1 -Xmx2G @{argLine} src/test/resources/logging.properties ${skip.backbox.images} default-test nom.tam.fits.test.CompressWithoutDependenciesTest classpath-test test test nom.tam.fits.test.CompressWithoutDependenciesTest org.apache.commons:commons-compress org.apache.maven.plugins maven-project-info-reports-plugin 2.8 false false index dependencies project-team mailing-list cim issue-tracking license scm org.apache.maven.plugins maven-checkstyle-plugin 2.17 ${project.basedir}/src/main/checkstyle/nom-tam-fits-style.xml ${project.basedir}/src/main/checkstyle/checkstyle-suppressions.xml checkstyle.suppressions.file UTF-8 true true true checkstyle org.apache.maven.plugins maven-javadoc-plugin 2.10.1 false false javadoc-no-fork org.apache.maven.plugins maven-jxr-plugin 2.4 true org.apache.maven.plugins maven-changes-plugin 2.11 changes-report org.apache.maven.plugins maven-pmd-plugin 3.6 true UTF-8 1.7 false 50 org.codehaus.mojo taglist-maven-plugin 2.4 org.codehaus.mojo findbugs-maven-plugin 3.0.3 org.jacoco jacoco-maven-plugin 0.7.6.201602180812 org.apache.commons commons-compress 1.11 true com.google.code.findbugs annotations 3.0.0 provided junit junit 4.11 test com.nanohttpd nanohttpd-webserver 2.1.1 test org.openjdk.jmh jmh-core 1.11.2 test org.openjdk.jmh jmh-generator-annprocess 1.11.2 test UTF-8 nom-tam-fits-deploy release-profile org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign createHeasarcZip pom.xml org.codehaus.mojo exec-maven-plugin 1.4.0 java test test nom.tam.fits.doc.GenerateReleaseNote maven-assembly-plugin 2.5.5 false src/assembly/heasarc.xml make-assembly package single skipBlackBoxImages true nom-tam-fits-nom-tam-fits-1.15.2/settings.xml000066400000000000000000000004221310063650500210030ustar00rootroot00000000000000 sonatype-nexus-staging ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD} nom-tam-fits-nom-tam-fits-1.15.2/src/000077500000000000000000000000001310063650500172125ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/assembly/000077500000000000000000000000001310063650500210315ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/assembly/BUILD.txt000066400000000000000000000100231310063650500224250ustar00rootroot00000000000000Starting with version 1.14 the FITS library uses a number of features that make Maven the recommended method for building the FITS library. Look at http://github.com/nom.tam.fits/nom.tam.fits to download the latest release. However users who still wish to build the library directly from the source jar can do so if they follow procedures similar to the instructions below. Perhaps you wish to compile for an older version of Java or maybe you want to try some tweaks to the library. It's not hard. These instructions are given in a Unix/Linux context and should be suitably adapted to work in Windows and other architectures. (Architecture independence is one reason using tools like Maven in nice!) 1. Extract the source files and configuration into a suitable directory. E.g. suppose you have downloaded the source jar file into the directory 'downloads' and wish to create a local version of the library wget -O ~/downloads/fits_src.jar http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/java/v1.0/fits_src.jar mkdir ~/myfits cd ~/myfits jar xf ~/downloads/fits_src.jar 2. A couple of external Jar files are needed to compile the library (but generally are not required to run it). These libraries are available at: http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/java/v1.0/dependencies Download these Jar files into some location on your machine. The commons-compress Jar is used for bzip decompression if you do not use an executable command. If you want to use this library for bzip decompression, then you will need to have it in the classpath during runtime too, but otherwise not. The Annotation jar is only needed for compilation. If you delete all lines with SuppressFBWarnings from the source code you can dispense with it altogether. It is used to suppress warning messages in some of the diagnostic tools. So, mkdir ~/dependencies cd ~/dependencies wget -O annotations.jar http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/java/v1.0/dependencies/annotations-2.0.1.jar wget -O commons.compress.jar http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/java/v1.0/dependencies/commons-compress-1.11.jar 3. Now compile the code. In the example we'll assume you are interested in building a Java 1.6 compatible jar file. First we need to get the list of all of the Java files we are going to compile, then we set the classpath to include our dependencies and then we do the compile. cd ~/myfits find . -name '*.java' > ../src.list setenv CLASSPATH .:../dependencies/annotations.jar:../dependencies/commons.compress.jar javac -source 1.6 -target 1.6 -encoding utf8 @../src.list There are a few source files that have UTF characters in them so you will need to use the '-encoding utf8' flag. The UTF characters are only in the JavaDoc comments so you can simply delete them if you like. The CLASSPATH (which you can set in a variety of ways) must include both the tree we are compiling and the jars we just downloaded. The -source 1.6 and -target 1.6 arguments allow you to use a more modern Java compiler to create 1.6 compatible outputs. If you are using JDK 1.6, then they are superfluous. We are endeavoring to ensure that all 1.xx releases will remain compatible with Java 1.6. 4. Finally generate a new Jar file with the compiled classes. You can get rid of the .java files if you want, but it's OK to leave them in. find . -name '*.java' -exec rm {} \; # Delete the Java source files jar cf ../myfits.jar . You will need to package things up in a jar to run the library. It now uses some resource files that it will look for in particular locations. Putting them in the jar and then putting that Jar in your CLASSPATH will ensure that the resource files are found. 5. Clean up (if you like): cd rm -rf dependencies # But not if using bzip decompression. rm -rf myfits rm src.list 6. Use the library cd ~/mycode setenv CLASSPATH ../myfits.jar javac MyClass.java # Where this is some class you write. java MyClass Good luck! nom-tam-fits-nom-tam-fits-1.15.2/src/assembly/heasarc.xml000066400000000000000000000045611310063650500231670ustar00rootroot00000000000000 heasarc zip false src/assembly/BUILD.txt BUILD java/v1.0 target/${artifact.artifactId}-${artifact.version}.jar fits.jar java target/${artifact.artifactId}-${artifact.version}-sources.jar fits_src.jar java target/${artifact.artifactId}-${artifact.version}.jar fits.jar java/v1.0 target/${artifact.artifactId}-${artifact.version}-sources.jar fits_src.jar java/v1.0 target/${artifact.artifactId}-${artifact.version}.jar fits.jar java/v1.0/v${artifact.version} target/${artifact.artifactId}-${artifact.version}-sources.jar fits_src.jar java/v1.0/v${artifact.version} target/${artifact.artifactId}-${artifact.version}-test-sources.jar fits_test_src.jar java/v1.0/v${artifact.version} java/v1.0/javadoc target/apidocs java/v1.0 target NOTE.* false java/v1.0/dependencies compile nom-tam-fits-nom-tam-fits-1.15.2/src/changes/000077500000000000000000000000001310063650500206225ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/changes/changes.xml000066400000000000000000001413311310063650500227570ustar00rootroot00000000000000 Maintenance release with bug fixes. Header can be controlled to specify the header card order. ImageHDU tiler corrupts values after 2GB worth of data bug fixed. Non standard BITPIX allowed during de/compression. Add tiler support for ImageHDU from uncompressing a CompressedImageHDU? Remove redundant spaces in HIERARCH keys . Fix integer overflow in case of negative values in combination with a defined blank value of Integer.MIN_VANUE. make the worker threads deamons so they do not hold of a shutdown enhancement. Update Outdated documentation in introduction enhancement, thanks to MaxNoe. Fix Reading boolean arrays with getColumn bug. Maintenance release with bug fixes. Comment type header cards where not protected against to long comments, that can result in corrupted headers. Introduction document verified and corrected kind thanks to Maximilian Nöthe. Binary table compression and tiling are now fully supported by nom-tam-fits. An API for easy handling of compressed tables is now provided. Binary table compression now fully supported. The dummy compression algorithm NOCOMPRESS is now supported. Multiple code quality fixes, provided by various developers. Maintenance release with bug fixes. Removal of redundent attribute "rowSize". Attention here the public api has a minor change, the deletColumns in the ColumnTable does not return an int anymore. Fix for a bug in encurling the multim arrays of the BinaryTable with variable length columns. Maintenance release with important bug fixes and the restoration of java 6 support. Fits does not handle comments that start with 8 blank characters correctly when reading/writing/reading bug . Restored Java 6 compatibility. Maintenance release with minor bug fixes and enhancements. Important note for all users, since 1.13.0 a bug is fixed in the table behavior. This can cause problems for users expecting the "buggy" result. See the issue on github for more details. Since a approximately 1.12.0 nom-tam-fits uses java.util.logging for all logs, the details what and where to log to can therefore be configured freely. In case of long strings the difference between a null comment and an empty string was not detected correctly. Image compression support for the null pixel mask, this allows correct NaN with the use of lossy compression's. Image compression and tiling are now fully supported by nom-tam-fits. A 100% Java implementation of the compression libraries available in cfitsio was implemented. An API for easy handling of compressed images is now provided. Support for binary table compression and the NULL_PIXEL_MASK features is anticipated in the next release. When [de]compressing all available CPU's are automatically utilized. Internal compression allows FITS files to be created where the data are efficiently stored, but the metadata is still easily accessible. The tiling of images is particularly critical for supporting efficient access to subsets of very large images. A user can easily access only the tiles that overlap the region of interest and can skip data not of interest. While some skipping might be possible with uncompressed FITS files (i.e., read only the rows overlapping the desired subset), internal tiles can be much more efficient when the image is substantially larger than the subset. Most compression algorithms interfere with the ability to skip uninteresting data, but tiles are compressed independently, so users can benefit both from the compression and the selection of only a subset of the image. Added a [de]compression API supporting all compression methods in the proposed updates to the FITS standard. Wrong checksum calculation corrected. Some problems with data segments that are bigger than 2GB corrected. Header parsing performance optimization. Comment style cards with a empty key value can now be used multiple times. Alignment of hierarch headercard values deactivated. The formatting of hierarch card keys can mow be be controlled. Two formats are provided. Maintenance release with fixes for hierarch/longstring and rewrite bugs. After the correction of #44 the padding calculation of the fits header was wrong, now the calculation is consistent. Improved the calculation of the number of cards to be used for a longstring in case of a hierarch cards because it was wrong when some special string lengths where used. Now rewriting is useing the new calculation to see if the header fits in place. More variants of the hierarch keywords as in #16. More variants of the hierarch keywords allowed (lowercase and dot), but writing will convert the keywords back to the standard. Major updates to the util package including a set of routines for efficient copyying of arrays of all types. Some of the special FITS values are collected in the FitsIO class. New buffering utility classes have also been created. Some methods that were public in the util package but which were not used in the FITS library have been removed. The logging is now using java.util.logging, no standard out or stanard error is used anymore. Added utilty class for updating checksums Added examples in utilities package for how to use new Header enumerations. Builder pattern for the creation of cards introduced. Fixed handling of binary tables built from an empty state row by row. Fixed coupling of binary tables and the FITS heap to allow copying of binary tables using the internal ColumnTable representation. Reorganized compression and added internal compression package. Tile compression, will be implemented from scratch in 2.0 and is not yet available in 1.13.0 Unit tests extended to cover 92% of the library . Longstring support was improved and longer comments are now supported. Compression dependecy to apache compression is now optional again. When reading/writing the same card the comment moved one blank to the right. All javadoc's are now java-8 compatible and produce no warnings. Support for biginteger and bigdecimal. Generic detection of the value type of a card. Insert a header card at a specific position. All internally used keyword references are now enumerations. Comment mapping is now moved to the standard keyword enumeration. The settings of FitsFactory are now changeable to thread local specific settings. Enumerations where added for ~1000 more or less Standard fits headers are now included (making compile references to headers possible). Moved the sources to github and the artifacts to the central repo. Moved the license to maven as build system, incl reorganisation of the project. tests are no included in the published jars any more. Moved the license to the official unlicensed. Creation of a project site on github with issue management. Java formatting/licence updating is now done by the maven plug ins. Moved the code to Java 1.6 and @Override is now consistent. Richard van Nieuwenhoven joined the team. Debuggging statements inadverently left in V111.0 were removed. Fixed error in handling of strings with non-terminated quotes so that these don't crash program (thanks to Kevin McAbee) Deferred allocation of memory when creating FitsHeap's (thanks to Vincenzo Forchi, ESO). Fixed error in getting size of associated data for dummy headers in Header.getDataSize(). This could return 1 rather than 0 if NAXIS=0 was used to signal a dummy. This doesn't affect data when read normally (as full HDUs) since the ImageData class does the computation correctly. (Thanks to Pat Dowler, CADC) The source code JAR (fits_src.jar) includes a number of new classes for which the corresponding class files are not included in fits.jar. These classes are pre-alpha versions of support for tile compressed data that is being developed. Interested Users may take a look at these, but they definitely are not expected to work today. Support for Rice, Gzip and HCompress compression is expected. No functional changes to the FITS code are included in this release. All internal documentation has been updated to reflect that this library is now available in the public domain. Fixed error in the writing of ASCII table columns where all of the elements of the table were null or 0 length strings. Previously we would write a column with TFORM A0. This is not supported by CFITSIO and since it is not valid Fortran is of dubious legality for FITS. Such columns are now written with A1 (issue noted by Jason Weiss, UCLA). AsciiTable did not check if columns were of a valid type (if the FitsFactory methods were used, then a BinaryTable would be written, but a user can explicitly instantiate an AsciiTable). A FitsException is now returned if a column other than a String, double, int or long array is used Added boolean hadDuplicates() and List getDuplicates() methods to Header to allow users to track if there were duplicate keywords in a header. This release contains some new features suggested by Booth Hartley (IPAC) who supplied modified code including: Allow a FITS file to have invalid data after a valid FITS HDU. User can call FitsFactory.setAllowTerminalJunk(true) to enable this. The Fits object will return all valid HDUs. Note that whatever follows the valid FITS data must start with something that is clearly not FITs. This includes modifications to FitsFactory and Header. Allow users to find original size of headers as they were read from some source file. The library throws away duplicate key values, so that the number of header cards in the header as read may be smaller than the original data. The getOriginalSize() gets the original size of the header in bytes. A resetOriginalSize() allows the user to tell the library that this header has been updated on disk and now has the same number of records as internally. Fixed the order of the EXTEND keyword to follow the NAXISn keywords when it is specified. This constraint on the EXTEND keyword is no longer required in the latest version of the standard, but it doesn't hurt anything and may make the file more acceptable to some readers. Fixed JavaDoc errors for a number of files. Fixed a bug in Header.rewriteable() related to the same issue as the original size. Updated Fits.setChecksum so that it will now set both the CHECKSUM and DATASUM keywords. Added tests for the new capabilities above and updated the checksum test. Substantial reworking of compression to accommodate BZIP2 compression. The Apache Bzip library is used or since this is very slow, the user can specify a local command to do the decompression using the BZIP_DECOMPRESSOR environment variable. This is assumed to require a '-' argument which is added if not supplied by the user. The decompressor should act as a filter between standard input and output. User compression flags are now completely ignored and the compression and the compression is determined entirely by the content of the stream. The Apache library will be needed in the classpath to accommodate BZIP2 inputs if the user does not supply the BZIP_DECOMPRESSOR. Adding additional compression methods should be much easier and may only involve adding a couple of lines in the FitsUtil.decompress function if a decompressor class is available. One subtle consequence of how compression is now handled is that there is no advantage for users to create their own BufferedDataInputStream's. Users should just provide a standard input stream and allow the FITS library to wrap it in a BufferedDataInputStream. A bug in the UndefinedData class was detected Vincenzo Forchi and has been corrected. The nom.tam.util.AsciiFuncs class now handles ASCII encoding to more cleanly separate this functionality from the FITS library and to enable Java 1.5 compatibitity. (Suggested by changes of L.Bourges) Other V1.5 incompatiblities removed. The HeaderCommentsMap class is now provided to enable users to control the comments that are generated in system generated header cards. The map is initialized to values that should be the same as the current defaults. This should allow users to emulate the comments of other packages. All Java code has been passed through NetBeans formatter so that it should have a more uniform appearance. The implementation of long string values was incorrect, using COMMENT rather than CONTINUE cards.noted originally by V. Forchi. The placement of the header cursor after opening a primary array was such that unless the user took explicit action to move the cursor, new header records would be written before the EXTEND keyword which is a violation of the FITS standard (although it would not affect the operations of this library). The library now leaves the cursor just after the EXTEND keyword where new keywords are legal. It's still possible for users to write an illegal header but now it requires at least a little effort on their part.noted originally by V. Forchi. This build procedure for FITS library has been changed. The library is now stored as a NetBeans project and the standard NetBeans build script has been modified to generate the fits.jar and fits_src.jar. The names of a number of the test procedures have been slightly modified (XXXTester -> XXXTest) and test data are included in the class jar file. Adding methods to allow finer control of the placement of metadata records for columns of FITS tables. This could previously be done using Cursors, but the TableHDU.setTableMeta() methods now allow these to be specified more directly. This involves changes only to TableHDU. Usage is illustrated in the test method BinaryTableTest.columnMetaTest.by Laurent Bourges Adding more rigor to the transformation between bytes and strings and fixing a bug in the handling of strings with embedded nuls. According to the standard an embedded null should terminate an string in a binary table.by Laurent Bourges The standard also precludes other non-printing characters from strings. This has been ignored previously, but there is now a method FitsFactory.setCheckAsciiString(boolean flag) which can be called to turn on checking. A warning will be issued and non-printing characters will be converted to spaces if this flag is set. Only a single warning will be issued regardless of the number of invalid characters are seen. There are changes in a number of classes where the conversions occur to ensure that the ASCII charset is used. How these changes work is illustrated in BinaryTableTest.specialStringsTest.by Laurent Bourges Handling fixed and variable length, single and double precision complex data. The library uses a float[2] or double[2] for a complex scalar. This is mostly bug fixes to existing code and changes are only in BinaryTable and BinaryTableHDU. The method BinaryTableHDU.setComplexColumn() allows the user to tell the FITS writer that a field which otherwise would be treated as a float or double array (with most rapidly varying dimensionality of 2) should be treated as complex. The internal data representations are identical. Variable length complex data will be found automatically if number of elements actually varies. A variable length complex column is a 3-D float or double array where the last dimension (for Java) is always 2, the first dimension is the number of rows in the table and the middle dimension is the number of complex numbers in the row and may vary from row to row. Other variable length columns are represented as 2-D arrays, where the first index points to the row, and the second index enumerates the elements in the row. Use of complex columns is illustrated in BinaryTableTest in the routines testSimpleComplex (fixed columns), testVar (variable length columns), and buildByColumn and buildByRow where columns and rows containing complex numbers are added to existing tables.by Laurent Bourges Changing the null HDU created when a table is to be written without a prior image to use a vector with dimensionality 0 rather than a one-dimensional vector with a dimension of 0. I.e, use NAXIS=0 rather than NAXIS=1, NAXIS1=0.by Laurent Bourges Consolidating the writing of padding at the end of FITS elements into the FitsUtil.pad methods. Adding the reset() method to the FitsElement interface. This attempts to reset the Fits input stream pointer to the beginning of the element. It does not throw exceptions but will return false if not successful. This is intended to make it easier for user who wish to use low-level I/O to read FITS data, by allowing them to position the stream to the beginning of the data they are interested in. Changed FitsUtil.HDUFactory(Object x) to accept a Header object as well as the various kinds of data inputs. Provided a method in BinaryTable to get back the ModelRow array. This makes is easier for users to do low level I/O in binary tables. An ArrayDataInput object will read a row of the table, given the result of getModelRow(). Added a getColumns() method to TableHDU. This returns an Object[] array where each entry is the result of getColumn(n) Support for the HEASARC long strings convention has been added. This affects only the Header class. Two new public static methods have been added. setLongStringsEnabled(boolean) allows the use to enable/disable the handling of long strings. getLongStringsEnabled() returns the current setting. By default long strings are disabled. The convention is enabled automatically whenever a header is read which has the LONGSTRN keyword is read. It is not disabled if subsequent headers are read which do not have this keyword. The addValue(String,String,String), getStringValue(String) and removeCard(String) methods are affected, allowing the user to set, read and delete long string values. The library does NOT ensure that users do not interpolate new keywords or comments inside the card sequence that is used to store the long string value. A bug in the processing of keyword values with embedded apostrophes was fixed. Apostrophe's were properly doubled in encoding but the doubling was left when the values were read. A potential bug in the processing of headers discovered by Mark Taylor was fixed. The implementation of the FitsUtil.byteArrayToStrings method was changed so that trimmed space from strings can be cleaned up more efficiently. Change suggested by J.C. Segovia (ESA). There should be no effect -- other than memory usage -- on external programs. Users might want to note that when reading string values in binary tables, both leading and trailing spaces are trimmed from the string values. This release adds further support for large datasets where the size of an HDU may exceed 2GB. In ArrayDataInput (and the BufferedFile and BufferedDataInputStream that implement it) int skipBytes(int) method of java.io.DataInput is now overloaded with long skipBytes(long). In ArrayFuncs int computeSize(Object) method is augmented with long computeLSize(Object) It was not possible to use the same name here since the argument type is the same. Similarly int nElements(Object) is now matched with long nLElements(Object) These changes should not affect current usage of the existing methods. References to skipBytes and computeSize in the FITS classes now take advantage of these new methods. While these changes increase the support of the library for large datasets, there are still a number of restictions that arise from Java's limit that array indices must be ints. E.g., no single dimension can exceed 2 GB, and the total size of the heap for a given binary table HDU cannot exceed 2 GB. ASCII tables may also be limited to 2 GB in some circumstances. Files which exceed these limits may be readable using line by line approaches, but users will need to use the library at a much lower level. The Header.read() method may now throw an IOException in circumstances where it would previously throw an Error. It probably should throw a FitsException, but that was not in the signature and might have broken existing programs. A bug in the new PaddingException was fixed which allows use of Tilers with truncated Image HDUs. Some obsolete comments indicating that BITPIX=64 was an extension of FITS were deleted. FITS has officially supported longs for a fair number of years now. The regression tests have been augmented to test the new features, but users should note that the new BigFileTester test takes a very long time to run. E.g., on the primary development machine this takes 240 seconds while all of the other tests finish in just a few seconds. The time is simply the time it takes to write a file of known content that is more than 2 GB in size. ASCII tables with data fields that were blank filled were not being handled properly. According to the FITS standards, numeric fields where the FITS table has blanks are to be treated as containing 0. A parsing error was being returned. The getInt, getLong, and getDouble methods in ByteParser were changed to acoommodate this case (getFloat simply calls getDouble).suggested by L. Michel A new exception, PaddingException, which inherits from FitsException has been added. This exception is thrown when an otherwise valid HDU is not properly padded to the next 2880 byte boundary. The exception class has a getTruncatedHDU method which allows the user to get the information in the truncated HDU. In addition to the new class changes were made in BinaryTable, AsciiTable, UndefinedData, ImageData and RandomGroupsData to throw the exception at the appropriate time. The main Fits method was also updated so that when its readHDU() method is being used, the notional header that is given to the truncated HDU in the Data classes is replaced by the actual header. If a user wishes to ignore padding exceptions, then a FITS file may be read using the following idiom in the new nom.tam.fits.test.PaddingTester to see a complete example of this idiom. A number of changes were implemented to handle large FITS files more gracefully and to correct bugs associated with large files. This includes a change to the method Data.getTrueSize(); This method was public only for the BinaryTable data type and previously returned an int. It now returns a long. User programs which called this method will need to be recompiled. Specific bugs were noted by Javier Diaz and Juan Carlos Segovia. Note that the program may still fail on very large files but it should give more informative error messages when it does so A bug noted by Thomas Granzer in the handling of HIERARCH keyword values was also corrected. Fixed bug where reading a table by rows caused reading a subsequent HDU to fail. Added tests to BinaryTableTester and HeaderCardTester. Fixed bug where exponential notation in FITS header keywords used 'e' rather than 'E' (noted by Javier Diaz) The major chage to this release is support for .Z compressed images. A problem reading past the end of files in normal FITS processing was included in the 1.0 release and was fixed (7/11/08). The earlier jars were overwritten. The problem shows up in the regression test suite. The major chage to this release is support for .Z compressed images. This is implemented by using the uncompress command which must be in the user's execution path. There is much more dynamic checking of the magic number of inputs to determine whether the input to the FITS constructor is compressed or not, and if compressed what the compression type is. This can still be confused but in many cases it will get the compression right regardless of what the user specifies. Future versions may completely ignore the user specified compression flag. Bug fix to BinaryTable by A. Kovacs Binary table handling of 1 character strings. Added Support HTTPS, FTP and FILE URLs Added Fits(String,compressed) constructor Made some of the methods in FitsFactory public. Added getRawElement method to Binary table. Changed handling of double values in header so that they all fit into the fixed format. In rare circumstances this may result in a loss of precision. Additional fixes for zero length and null strings Fix to handling of Strings in Binary tables (A. Kovacs) Additional changes to handle null and zero length strings. Moved code to use subversion repository and Ant compile scripts. Major transformations of all test code to use Junit and automated checking rather than comparing print outs. nom.tam.fits.utilities package created and FitsCopy and FitsReader classes were moved there. A few test classes, e.g., BigImage and RMFUpdTest were deleted and their functions subsumed into the other tests. Test routines now considered standard part of library. There are not separate JARs for the test routines. Note that the test routines use Annotations and may not compile with versions of Java prior to 1.5. ArrayFuncs: Added arrayEquals() methods which allow comparison of arrays of arbitrary dimensionality. Used extensively in the updated test classes. AsciiTable: Setting a row, column or element de-nulls any elements that were set to null. Fixed offsets in columns after column was deleted. FitsUtil: Fixed bug in maxLength which looked for nulls in the array pointer rather than the individual strings. Added check for nulls in stringsToByteArray HeaderCard: Truncated String in one argument constructor to a maximum of 80 characters. BinaryTable: Fixed handling of columns with 0 width (e.g., 0 length strings, or arrays of 0 length. ColumnTable: Fixed handling of columns with 0 width. Added new methods to delete rows and columns from both binary and ASCII tables. There are changes to many of the table classes including util/ColumnTable. These changes were suggested by row deletion code written by R. Mathar, but the actual implementation is entirely independent and errors are handled somewhat differently than in his code. There are deleteColumns and deleteRows methods in TableHDU that delete either a specified range or all tables or columns after (and including) the one specified. The util.HashedList implementation has been completely revised. It now uses a HashedMap for keyed access and an ArrayList for sequential access. It no longer implements a simple but custom list structure. The public interface was not significantly changed. Header now sorts keywords before a header is written to ensure that required keywords come where they need to be. Previously users needed to work to make sure that they wrote required keywords in the right location in the header. A new class, HeaderOrder, is used. A number of errors in the handling of variable length arrays were fixed. These were pointed out by Guillame Belanger. This included changes to util.ColumnTable but mostly BinaryTable and FitsHeap. A number of changes mostly to BinaryTable or documentation in other routines suggested by R. MAthar. The three packages, nom.tam.fits, nom.tam.util and nom.tam.image have been combined into a single JAR file for the convenience of the user. Added support for Checksums. Use the setChecksum methods in the FITS class to add checksums to FITS HDUs. The static method setChecksum(HDU) adds a checksum to a given HDU. The instance method setChecksum() adds checksums to all HDUs in the file. Note that setting the checksum should be the last step before writing the file since any manipulation of the file is likely to invalidate the checksum. (This code was contributed by R.J. Mathar, Leiden University). Changed handling of 1-d arrays with a single element so that they can be distinguished from scalar values. No TDIM will be be created for scalar columns, and a TDIMn = '(1)' will give an array rather than a scalar value. (Suggested by Jorgo Bakker. ESA) For data written using the previous version of the FITS library, this may cause problems when the data is read with the new version, since the type of the returned column will be different. When checking if a file is compressed, the actual content of the file will be used if possible rather than the name (Suggested by Laurent Michel, CDS) The code used to support TFORMn = 'xNNN' where the array dimension followed rather than preceded the format type. This has been deleted (Suggested by Laurent Michel, CDS) Zero-length string values should now be allowed as header keyword values (Bug noted by Fred Romelfanger, ST ScI and Jorgo Bakker, ESA). The addLine methods in Header are now public rather than protected. If the Fits.write() method is called using a BufferedFile, then the size of the file is truncated at the end of the write. Otherwise if the FITS data was being written into a previously existing file of greater length, there would be extra bytes at the end of the file. This is still possible if the user uses the write methods for individual constituents of the FITS object. The ArrayFuncs.newInstance method now accepts an dimension array of length 0 and returns a 1-d array of length 1 to emulate a scalar. Corrected bug in writing a binary table when the read of that table had been deferred. Version 0.97 corrects several bugs in the handling header keywords and ASCII tables and Images. The HeaderCard class now has constructor with the signature (String,String,boolean) which may be used to generate either a comment style card with the keyword and value given, or a card with a null value The handling of the EXTEND keyword has been made consistent with FITS standards ASCII tables are first read to an intermediate byte buffer and then parsed as needed. Bugs where this buffer was being deleted at inappropriate times, or left undeleted when it was invalid were fixed. This should fix errors when AsciiTables are read from non-seekable sources. This should slightly speed up most access to ASCII tables In certain circumstances an Image would not be properly initialized before it was to be written The routines Header, HeaderCard, ImageData and AsciiTableData where modified in this release The getChannel method was added to BufferedFile The handling of PCOUNT, GCOUNT and EXTEND keywords was changed in images so that the first two are only generated for extensions and the first only for primary HDU's. A bug in the creation of ASCII Table Headers was fixed. Some of the header cards in the header were being inserted as if they were comments, allowing multiple copies to be generated. This was also possible when a Header was created from an array of strings. The HeaderCard class has been modified to handle The HIERARCH keyword convention. The FitsFactory now has methods set/getUseHierarch to enable/disable this processing A new interface FitsElement has been added which is implemented by the BasicHDU, Header, Data and FitsHeap classes. It enables users to more easily deal with FITS data at the byte level. There is also a public method getDataSize in Header to get the size in bytes of the associated data element including padding. The FitsHeap class has been made public Several bugs relating to null images were corrected. (Thanks to Jens Knudstrup) (ImageData) The handling of EOF conditions in array reads in the BufferedFile and BufferedDataInputStream classes was made consistent with the behavior of java.io classes reading byte arrays Several bug fixes implemented by Alan Brighton (and already fixed in the Jsky distribution) were incorporated All references to the java.lang.reflect.Array.newInstance() methods were modified to use new methods with the same signature in ArrayFuncs. These new methods throw an OutOfMemory exception when an array cannot be created. The JVM methods seem -- in contradiction to the documentation -- to simply return null. Previously the program could mysteriously crash when used to read large files, when the null in a dynamic allocation was eventually dereferenced BinaryTable Fixed bug initializing BinaryTable's read from streams (rather than files) FitsDate: added getFitsDateString Header: FitsDate: made several methods public added checking for initial keywords before write BinaryTable: removed TDIM keywords for variable length columns BinaryTable: fixed bug that made BinaryTable(Object[][]) constructor unusable BinaryTableHDU: fixed usage of THEAP keyword AsciiTable: use blanks for data filler rather than nulls BasicHDU made getDummyHDU public HeaderCard fixed padding of string values which sometimes had one too many spaces image.ImageTiler allow requests for tiles that are not fully within the original image util.ByteFormatter: changed formatter to use 'E' (rather than 'e') for exponents since 'e' not legal for FITS ASCII tables Support for ASCII tables Deferred input for images and tables (data is read only when user actually requests it) Image subsetting without reading the entire image Reading individual rows and elements of tables without reading the entire table Support for in-place rewriting of headers and data Transparent support for Strings in ASCII and Binary tables Transparent support for booleans in binary tables, including varying length columns Efficient buffered random access methods More flexible support for I/O of primitive arrays nom-tam-fits-nom-tam-fits-1.15.2/src/license/000077500000000000000000000000001310063650500206345ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/license/licenses.properties000066400000000000000000000000321310063650500245520ustar00rootroot00000000000000publicdomain=Public Domainnom-tam-fits-nom-tam-fits-1.15.2/src/license/publicdomain/000077500000000000000000000000001310063650500233025ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/license/publicdomain/header.txt000066400000000000000000000021731310063650500252760ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.nom-tam-fits-nom-tam-fits-1.15.2/src/license/publicdomain/license.txt000066400000000000000000000023111310063650500254620ustar00rootroot00000000000000The Unlicense This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to nom-tam-fits-nom-tam-fits-1.15.2/src/main/000077500000000000000000000000001310063650500201365ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/checkstyle/000077500000000000000000000000001310063650500222745ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/checkstyle/checkstyle-suppressions.xml000066400000000000000000000010511310063650500277240ustar00rootroot00000000000000 nom-tam-fits-nom-tam-fits-1.15.2/src/main/checkstyle/nom-tam-fits-style.xml000066400000000000000000000133331310063650500264720ustar00rootroot00000000000000 nom-tam-fits-nom-tam-fits-1.15.2/src/main/eclipse/000077500000000000000000000000001310063650500215625ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/eclipse/formatter.xml000066400000000000000000000750121310063650500243140ustar00rootroot00000000000000 nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/000077500000000000000000000000001310063650500212225ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/diff/000077500000000000000000000000001310063650500221325ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/diff/hcompress32.c000066400000000000000000000366471310063650500244660ustar00rootroot00000000000000int fits_hcompress(int *a, int ny, int nx, int scale, char *output, long *nbytes, int *status) { /* compress the input image using the H-compress algorithm a - input image array nx - size of X axis of image ny - size of Y axis of image scale - quantization scale factor. Larger values results in more (lossy) compression scale = 0 does lossless compression output - pre-allocated array to hold the output compressed stream of bytes nbyts - input value = size of the output buffer; returned value = size of the compressed byte stream, in bytes NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat; if (*status > 0) return(*status); /* H-transform */ stat = htrans(a, nx, ny); if (stat) { *status = stat; return(*status); } /* digitize */ digitize(a, nx, ny, scale); /* encode and write to output array */ FFLOCK; noutmax = *nbytes; /* input value is the allocated size of the array */ *nbytes = 0; /* reset */ stat = encode(output, nbytes, a, nx, ny, scale); FFUNLOCK; *status = stat; return(*status); } static int htrans(int a[],int nx,int ny) { int nmax, log2n, h0, hx, hy, hc, nxtop, nytop, i, j, k; int oddx, oddy; int shift, mask, mask2, prnd, prnd2, nrnd2; int s10, s00; int *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> shift; hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift; hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift; hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift; /* * Throw away the 2 bottom bits of h0, bottom bit of hx,hy. * To get rounding to be same for positive and negative * numbers, nrnd2 = prnd2 - 1. */ a[s10+1] = hc; a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00+1] = ( (hy>=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = (a[s10] + a[s00]) << (1-shift); hx = (a[s10] - a[s00]) << (1-shift); a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 1; s10 += 1; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = i*ny; for (j = 0; j=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00] << (2-shift); a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; } } /* * now shuffle in each dimension to group coefficients by order */ for (i = 0; i>1; nytop = (nytop+1)>>1; /* * divisor doubles after first reduction */ shift = 1; /* * masks, rounding values double after each iteration */ mask = mask2; prnd = prnd2; mask2 = mask2 << 1; prnd2 = prnd2 << 1; nrnd2 = prnd2 - 1; } free(tmp); return(0); } static void shuffle(int a[], int n, int n2, int tmp[]) { /* int a[]; array to shuffle int n; number of elements to shuffle int n2; second dimension int tmp[]; scratch storage */ int i; int *p1, *p2, *pt; /* * copy odd elements to tmp */ pt = tmp; p1 = &a[n2]; for (i=1; i < n; i += 2) { *pt = *p1; pt += 1; p1 += (n2+n2); } /* * compress even elements into first half of A */ p1 = &a[n2]; p2 = &a[n2+n2]; for (i=2; i0) ? (*p+d) : (*p-d))/scale; } static int encode(char *outfile, long *nlength, int a[], int nx, int ny, int scale) { /* FILE *outfile; - change outfile to a char array */ /* long * nlength returned length (in bytes) of the encoded array) int a[]; input H-transform array (nx,ny) int nx,ny; size of H-transform array int scale; scale factor for digitization */ int nel, nx2, ny2, i, j, k, q, vmax[3], nsign, bits_to_go; unsigned char nbitplanes[3]; unsigned char *signbits; int stat; noutchar = 0; /* initialize the number of compressed bytes that have been written */ nel = nx*ny; /* * write magic value */ qwrite(outfile, code_magic, sizeof(code_magic)); writeint(outfile, nx); /* size of image */ writeint(outfile, ny); writeint(outfile, scale); /* scale factor for digitization */ /* * write first value of A (sum of all pixels -- the only value * which does not compress well) */ writelonglong(outfile, (LONGLONG) a[0]); a[0] = 0; /* * allocate array for sign bits and save values, 8 per byte */ signbits = (unsigned char *) malloc((nel+7)/8); if (signbits == (unsigned char *) NULL) { ffpmsg("encode: insufficient memory"); return(DATA_COMPRESSION_ERR); } nsign = 0; bits_to_go = 8; signbits[0] = 0; for (i=0; i 0) { /* * positive element, put zero at end of buffer */ signbits[nsign] <<= 1; bits_to_go -= 1; } else if (a[i] < 0) { /* * negative element, shift in a one */ signbits[nsign] <<= 1; signbits[nsign] |= 1; bits_to_go -= 1; /* * replace a by absolute value */ a[i] = -a[i]; } if (bits_to_go == 0) { /* * filled up this byte, go to the next one */ bits_to_go = 8; nsign += 1; signbits[nsign] = 0; } } if (bits_to_go != 8) { /* * some bits in last element * move bits in last byte to bottom and increment nsign */ signbits[nsign] <<= bits_to_go; nsign += 1; } /* * calculate number of bit planes for 3 quadrants * * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, */ for (q=0; q<3; q++) { vmax[q] = 0; } /* * get maximum absolute value in each quadrant */ nx2 = (nx+1)/2; ny2 = (ny+1)/2; j=0; /* column counter */ k=0; /* row counter */ for (i=0; i=ny2) + (k>=nx2); if (vmax[q] < a[i]) vmax[q] = a[i]; if (++j >= ny) { j = 0; k += 1; } } /* * now calculate number of bits for each quadrant */ /* this is a more efficient way to do this, */ for (q = 0; q < 3; q++) { for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; } /* for (q = 0; q < 3; q++) { nbitplanes[q] = (int) (log((float) (vmax[q]+1))/log(2.0)+0.5); if ( (vmax[q]+1) > (1< 0) { if ( 0 == qwrite(outfile, (char *) signbits, nsign)) { free(signbits); *nlength = noutchar; ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } } free(signbits); *nlength = noutchar; if (noutchar >= noutmax) { ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } return(stat); } static int doencode(char *outfile, int a[], int nx, int ny, unsigned char nbitplanes[3]) { /* char *outfile; output data stream int a[]; Array of values to encode int nx,ny; Array dimensions [nx][ny] unsigned char nbitplanes[3]; Number of bit planes in quadrants */ int nx2, ny2, stat; nx2 = (nx+1)/2; ny2 = (ny+1)/2; /* * Initialize bit output */ start_outputing_bits(); /* * write out the bit planes for each quadrant */ stat = qtree_encode(outfile, &a[0], ny, nx2, ny2, nbitplanes[0]); if (!stat) stat = qtree_encode(outfile, &a[ny2], ny, nx2, ny/2, nbitplanes[1]); if (!stat) stat = qtree_encode(outfile, &a[ny*nx2], ny, nx/2, ny2, nbitplanes[1]); if (!stat) stat = qtree_encode(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]); /* * Add zero as an EOF symbol */ output_nybble(outfile, 0); done_outputing_bits(outfile); return(stat); } static int qtree_encode(char *outfile, int a[], int n, int nqx, int nqy, int nbitplanes) { /* int a[]; int n; physical dimension of row in a int nqx; length of row int nqy; length of column (<=n) int nbitplanes; number of bit planes to output */ int log2n, i, k, bit, b, bmax, nqmax, nqx2, nqy2, nx, ny; unsigned char *scratch, *buffer; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ nqmax = (nqx>nqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * initial bit buffer */ b = 0; bitbuffer = 0; bits_to_go3 = 0; /* * on first pass copy A to scratch array */ qtree_onebit(a,n,nqx,nqy,scratch,bit); nx = (nqx+1)>>1; ny = (nqy+1)>>1; /* * copy non-zero values to output buffer, which will be written * in reverse order */ if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { /* * quadtree is expanding data, * change warning code and just fill buffer with bit-map */ write_bdirect(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } /* * do log2n reductions */ for (k = 1; k>1; ny = (ny+1)>>1; if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { write_bdirect(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } } /* * OK, we've got the code in buffer * Write quadtree warning code, then write buffer in reverse order */ output_nybble(outfile,0xF); if (b==0) { if (bits_to_go3>0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<=0; i--) { output_nbits(outfile,buffer[i],8); } } bitplane_done: ; } free(buffer); free(scratch); return(0); } static void qtree_onebit(int a[], int n, int nx, int ny, unsigned char b[], int bit) { int i, j, k; int b0, b1, b2, b3; int s10, s00; /* * use selected bit to get amount to shift */ b0 = 1<> bit; k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row * s00+1,s10+1 are off edge */ b[k] = ( ((a[s10 ]<<1) & b1) | ((a[s00 ]<<3) & b3) ) >> bit; k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10,s10+1 are off edge */ s00 = n*i; for (j = 0; j> bit; k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ b[k] = ( ((a[s00 ]<<3) & b3) ) >> bit; k += 1; } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/diff/hcompress64.c000066400000000000000000000343111310063650500244550ustar00rootroot00000000000000int fits_hcompress64(LONGLONG *a, int ny, int nx, int scale, char *output, long *nbytes, int *status) { /* compress the input image using the H-compress algorithm a - input image array nx - size of X axis of image ny - size of Y axis of image scale - quantization scale factor. Larger values results in more (lossy) compression scale = 0 does lossless compression output - pre-allocated array to hold the output compressed stream of bytes nbyts - size of the compressed byte stream, in bytes NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat; if (*status > 0) return(*status); /* H-transform */ stat = htrans64(a, nx, ny); if (stat) { *status = stat; return(*status); } /* digitize */ digitize64(a, nx, ny, scale); /* encode and write to output array */ FFLOCK; noutmax = *nbytes; /* input value is the allocated size of the array */ *nbytes = 0; /* reset */ stat = encode64(output, nbytes, a, nx, ny, scale); FFUNLOCK; *status = stat; return(*status); } static int htrans64(LONGLONG a[],int nx,int ny) { int nmax, log2n, nxtop, nytop, i, j, k; int oddx, oddy; int shift; int s10, s00; LONGLONG h0, hx, hy, hc, prnd, prnd2, nrnd2, mask, mask2; LONGLONG *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> shift; hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift; hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift; hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift; /* * Throw away the 2 bottom bits of h0, bottom bit of hx,hy. * To get rounding to be same for positive and negative * numbers, nrnd2 = prnd2 - 1. */ a[s10+1] = hc; a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00+1] = ( (hy>=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = (a[s10] + a[s00]) << (1-shift); hx = (a[s10] - a[s00]) << (1-shift); a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 1; s10 += 1; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = i*ny; for (j = 0; j=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00] << (2-shift); a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; } } /* * now shuffle in each dimension to group coefficients by order */ for (i = 0; i>1; nytop = (nytop+1)>>1; /* * divisor doubles after first reduction */ shift = 1; /* * masks, rounding values double after each iteration */ mask = mask2; prnd = prnd2; mask2 = mask2 << 1; prnd2 = prnd2 << 1; nrnd2 = prnd2 - 1; } free(tmp); return(0); } static void shuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[]) { /* LONGLONG a[]; array to shuffle int n; number of elements to shuffle int n2; second dimension LONGLONG tmp[]; scratch storage */ int i; LONGLONG *p1, *p2, *pt; /* * copy odd elements to tmp */ pt = tmp; p1 = &a[n2]; for (i=1; i < n; i += 2) { *pt = *p1; pt += 1; p1 += (n2+n2); } /* * compress even elements into first half of A */ p1 = &a[n2]; p2 = &a[n2+n2]; for (i=2; i0) ? (*p+d) : (*p-d))/scale64; } static int encode64(char *outfile, long *nlength, LONGLONG a[], int nx, int ny, int scale) { /* FILE *outfile; - change outfile to a char array */ /* long * nlength returned length (in bytes) of the encoded array) LONGLONG a[]; input H-transform array (nx,ny) int nx,ny; size of H-transform array int scale; scale factor for digitization */ int nel, nx2, ny2, i, j, k, q, nsign, bits_to_go; LONGLONG vmax[3]; unsigned char nbitplanes[3]; unsigned char *signbits; int stat; noutchar = 0; /* initialize the number of compressed bytes that have been written */ nel = nx*ny; /* * write magic value */ qwrite(outfile, code_magic, sizeof(code_magic)); writeint(outfile, nx); /* size of image */ writeint(outfile, ny); writeint(outfile, scale); /* scale factor for digitization */ /* * write first value of A (sum of all pixels -- the only value * which does not compress well) */ writelonglong(outfile, a[0]); a[0] = 0; /* * allocate array for sign bits and save values, 8 per byte */ signbits = (unsigned char *) malloc((nel+7)/8); if (signbits == (unsigned char *) NULL) { ffpmsg("encode64: insufficient memory"); return(DATA_COMPRESSION_ERR); } nsign = 0; bits_to_go = 8; signbits[0] = 0; for (i=0; i 0) { /* * positive element, put zero at end of buffer */ signbits[nsign] <<= 1; bits_to_go -= 1; } else if (a[i] < 0) { /* * negative element, shift in a one */ signbits[nsign] <<= 1; signbits[nsign] |= 1; bits_to_go -= 1; /* * replace a by absolute value */ a[i] = -a[i]; } if (bits_to_go == 0) { /* * filled up this byte, go to the next one */ bits_to_go = 8; nsign += 1; signbits[nsign] = 0; } } if (bits_to_go != 8) { /* * some bits in last element * move bits in last byte to bottom and increment nsign */ signbits[nsign] <<= bits_to_go; nsign += 1; } /* * calculate number of bit planes for 3 quadrants * * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, */ for (q=0; q<3; q++) { vmax[q] = 0; } /* * get maximum absolute value in each quadrant */ nx2 = (nx+1)/2; ny2 = (ny+1)/2; j=0; /* column counter */ k=0; /* row counter */ for (i=0; i=ny2) + (k>=nx2); if (vmax[q] < a[i]) vmax[q] = a[i]; if (++j >= ny) { j = 0; k += 1; } } /* * now calculate number of bits for each quadrant */ /* this is a more efficient way to do this, */ for (q = 0; q < 3; q++) { for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; } /* for (q = 0; q < 3; q++) { nbitplanes[q] = log((float) (vmax[q]+1))/log(2.0)+0.5; if ( (vmax[q]+1) > (((LONGLONG) 1)< 0) { if ( 0 == qwrite(outfile, (char *) signbits, nsign)) { free(signbits); *nlength = noutchar; ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } } free(signbits); *nlength = noutchar; if (noutchar >= noutmax) { ffpmsg("encode64: output buffer too small"); return(DATA_COMPRESSION_ERR); } return(stat); } static int doencode64(char *outfile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]) { /* char *outfile; output data stream LONGLONG a[]; Array of values to encode int nx,ny; Array dimensions [nx][ny] unsigned char nbitplanes[3]; Number of bit planes in quadrants */ int nx2, ny2, stat; nx2 = (nx+1)/2; ny2 = (ny+1)/2; /* * Initialize bit output */ start_outputing_bits(); /* * write out the bit planes for each quadrant */ stat = qtree_encode64(outfile, &a[0], ny, nx2, ny2, nbitplanes[0]); if (!stat) stat = qtree_encode64(outfile, &a[ny2], ny, nx2, ny/2, nbitplanes[1]); if (!stat) stat = qtree_encode64(outfile, &a[ny*nx2], ny, nx/2, ny2, nbitplanes[1]); if (!stat) stat = qtree_encode64(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]); /* * Add zero as an EOF symbol */ output_nybble(outfile, 0); done_outputing_bits(outfile); return(stat); } static int qtree_encode64(char *outfile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes) { /* LONGLONG a[]; int n; physical dimension of row in a int nqx; length of row int nqy; length of column (<=n) int nbitplanes; number of bit planes to output */ int log2n, i, k, bit, b, nqmax, nqx2, nqy2, nx, ny; int bmax; /* this potentially needs to be made a 64-bit int to support large arrays */ unsigned char *scratch, *buffer; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ nqmax = (nqx>nqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * initial bit buffer */ b = 0; bitbuffer = 0; bits_to_go3 = 0; /* * on first pass copy A to scratch array */ qtree_onebit64(a,n,nqx,nqy,scratch,bit); nx = (nqx+1)>>1; ny = (nqy+1)>>1; /* * copy non-zero values to output buffer, which will be written * in reverse order */ if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { /* * quadtree is expanding data, * change warning code and just fill buffer with bit-map */ write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } /* * do log2n reductions */ for (k = 1; k>1; ny = (ny+1)>>1; if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } } /* * OK, we've got the code in buffer * Write quadtree warning code, then write buffer in reverse order */ output_nybble(outfile,0xF); if (b==0) { if (bits_to_go3>0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<=0; i--) { output_nbits(outfile,buffer[i],8); } } bitplane_done: ; } free(buffer); free(scratch); return(0); } static void qtree_onebit64(LONGLONG a[], int n, int nx, int ny, unsigned char b[], int bit) { int i, j, k; LONGLONG b0, b1, b2, b3; int s10, s00; /* * use selected bit to get amount to shift */ b0 = ((LONGLONG) 1)<> bit); k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row * s00+1,s10+1 are off edge */ b[k] = (unsigned char) (( ((a[s10 ]<<1) & b1) | ((a[s00 ]<<3) & b3) ) >> bit); k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10,s10+1 are off edge */ s00 = n*i; for (j = 0; j> bit); k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ b[k] = (unsigned char) (( ((a[s00 ]<<3) & b3) ) >> bit); k += 1; } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/fits_hcompress.c000066400000000000000000001336501310063650500244260ustar00rootroot00000000000000/* ######################################################################### These routines to apply the H-compress compression algorithm to a 2-D Fits image were written by R. White at the STScI and were obtained from the STScI at http://www.stsci.edu/software/hcompress.html This source file is a concatination of the following sources files in the original distribution htrans.c digitize.c encode.c qwrite.c doencode.c bit_output.c qtree_encode.c The following modifications have been made to the original code: - commented out redundant "include" statements - added the noutchar global variable - changed all the 'extern' declarations to 'static', since all the routines are in the same source file - changed the first parameter in encode (and in lower level routines from a file stream to a char array - modifid the encode routine to return the size of the compressed array of bytes - changed calls to printf and perror to call the CFITSIO ffpmsg routine - modified the mywrite routine, and lower level byte writing routines, to copy the output bytes to a char array, instead of writing them to a file stream - replace "exit" statements with "return" statements - changed the function declarations to the more modern ANSI C style ############################################################################ */ #include #include #include #include #include "fitsio2.h" static long noutchar; static long noutmax; static int htrans(int a[],int nx,int ny); static void digitize(int a[], int nx, int ny, int scale); static int encode(char *outfile, long *nlen, int a[], int nx, int ny, int scale); static void shuffle(int a[], int n, int n2, int tmp[]); static int htrans64(LONGLONG a[],int nx,int ny); static void digitize64(LONGLONG a[], int nx, int ny, int scale); static int encode64(char *outfile, long *nlen, LONGLONG a[], int nx, int ny, int scale); static void shuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[]); static void writeint(char *outfile, int a); static void writelonglong(char *outfile, LONGLONG a); static int doencode(char *outfile, int a[], int nx, int ny, unsigned char nbitplanes[3]); static int doencode64(char *outfile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]); static int qwrite(char *file, char buffer[], int n); static int qtree_encode(char *outfile, int a[], int n, int nqx, int nqy, int nbitplanes); static int qtree_encode64(char *outfile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes); static void start_outputing_bits(void); static void done_outputing_bits(char *outfile); static void output_nbits(char *outfile, int bits, int n); static void qtree_onebit(int a[], int n, int nx, int ny, unsigned char b[], int bit); static void qtree_onebit64(LONGLONG a[], int n, int nx, int ny, unsigned char b[], int bit); static void qtree_reduce(unsigned char a[], int n, int nx, int ny, unsigned char b[]); static int bufcopy(unsigned char a[], int n, unsigned char buffer[], int *b, int bmax); static void write_bdirect(char *outfile, int a[], int n,int nqx, int nqy, unsigned char scratch[], int bit); static void write_bdirect64(char *outfile, LONGLONG a[], int n,int nqx, int nqy, unsigned char scratch[], int bit); /* #define output_nybble(outfile,c) output_nbits(outfile,c,4) */ static void output_nybble(char *outfile, int bits); static void output_nnybble(char *outfile, int n, unsigned char array[]); #define output_huffman(outfile,c) output_nbits(outfile,code[c],ncode[c]) /* ---------------------------------------------------------------------- */ int fits_hcompress(int *a, int ny, int nx, int scale, char *output, long *nbytes, int *status) { /* compress the input image using the H-compress algorithm a - input image array nx - size of X axis of image ny - size of Y axis of image scale - quantization scale factor. Larger values results in more (lossy) compression scale = 0 does lossless compression output - pre-allocated array to hold the output compressed stream of bytes nbyts - input value = size of the output buffer; returned value = size of the compressed byte stream, in bytes NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat; if (*status > 0) return(*status); /* H-transform */ stat = htrans(a, nx, ny); if (stat) { *status = stat; return(*status); } /* digitize */ digitize(a, nx, ny, scale); /* encode and write to output array */ FFLOCK; noutmax = *nbytes; /* input value is the allocated size of the array */ *nbytes = 0; /* reset */ stat = encode(output, nbytes, a, nx, ny, scale); FFUNLOCK; *status = stat; return(*status); } /* ---------------------------------------------------------------------- */ int fits_hcompress64(LONGLONG *a, int ny, int nx, int scale, char *output, long *nbytes, int *status) { /* compress the input image using the H-compress algorithm a - input image array nx - size of X axis of image ny - size of Y axis of image scale - quantization scale factor. Larger values results in more (lossy) compression scale = 0 does lossless compression output - pre-allocated array to hold the output compressed stream of bytes nbyts - size of the compressed byte stream, in bytes NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat; if (*status > 0) return(*status); /* H-transform */ stat = htrans64(a, nx, ny); if (stat) { *status = stat; return(*status); } /* digitize */ digitize64(a, nx, ny, scale); /* encode and write to output array */ FFLOCK; noutmax = *nbytes; /* input value is the allocated size of the array */ *nbytes = 0; /* reset */ stat = encode64(output, nbytes, a, nx, ny, scale); FFUNLOCK; *status = stat; return(*status); } /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* htrans.c H-transform of NX x NY integer image * * Programmer: R. White Date: 11 May 1992 */ /* ######################################################################### */ static int htrans(int a[],int nx,int ny) { int nmax, log2n, h0, hx, hy, hc, nxtop, nytop, i, j, k; int oddx, oddy; int shift, mask, mask2, prnd, prnd2, nrnd2; int s10, s00; int *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> shift; hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift; hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift; hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift; /* * Throw away the 2 bottom bits of h0, bottom bit of hx,hy. * To get rounding to be same for positive and negative * numbers, nrnd2 = prnd2 - 1. */ a[s10+1] = hc; a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00+1] = ( (hy>=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = (a[s10] + a[s00]) << (1-shift); hx = (a[s10] - a[s00]) << (1-shift); a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 1; s10 += 1; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = i*ny; for (j = 0; j=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00] << (2-shift); a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; } } /* * now shuffle in each dimension to group coefficients by order */ for (i = 0; i>1; nytop = (nytop+1)>>1; /* * divisor doubles after first reduction */ shift = 1; /* * masks, rounding values double after each iteration */ mask = mask2; prnd = prnd2; mask2 = mask2 << 1; prnd2 = prnd2 << 1; nrnd2 = prnd2 - 1; } free(tmp); return(0); } /* ######################################################################### */ static int htrans64(LONGLONG a[],int nx,int ny) { int nmax, log2n, nxtop, nytop, i, j, k; int oddx, oddy; int shift; int s10, s00; LONGLONG h0, hx, hy, hc, prnd, prnd2, nrnd2, mask, mask2; LONGLONG *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> shift; hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift; hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift; hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift; /* * Throw away the 2 bottom bits of h0, bottom bit of hx,hy. * To get rounding to be same for positive and negative * numbers, nrnd2 = prnd2 - 1. */ a[s10+1] = hc; a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00+1] = ( (hy>=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = (a[s10] + a[s00]) << (1-shift); hx = (a[s10] - a[s00]) << (1-shift); a[s10 ] = ( (hx>=0) ? (hx+prnd) : hx ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 1; s10 += 1; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = i*ny; for (j = 0; j=0) ? (hy+prnd) : hy ) & mask ; a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00] << (2-shift); a[s00 ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2; } } /* * now shuffle in each dimension to group coefficients by order */ for (i = 0; i>1; nytop = (nytop+1)>>1; /* * divisor doubles after first reduction */ shift = 1; /* * masks, rounding values double after each iteration */ mask = mask2; prnd = prnd2; mask2 = mask2 << 1; prnd2 = prnd2 << 1; nrnd2 = prnd2 - 1; } free(tmp); return(0); } /* ######################################################################### */ static void shuffle(int a[], int n, int n2, int tmp[]) { /* int a[]; array to shuffle int n; number of elements to shuffle int n2; second dimension int tmp[]; scratch storage */ int i; int *p1, *p2, *pt; /* * copy odd elements to tmp */ pt = tmp; p1 = &a[n2]; for (i=1; i < n; i += 2) { *pt = *p1; pt += 1; p1 += (n2+n2); } /* * compress even elements into first half of A */ p1 = &a[n2]; p2 = &a[n2+n2]; for (i=2; i0) ? (*p+d) : (*p-d))/scale; } /* ######################################################################### */ static void digitize64(LONGLONG a[], int nx, int ny, int scale) { LONGLONG d, *p, scale64; /* * round to multiple of scale */ if (scale <= 1) return; d=(scale+1)/2-1; scale64 = scale; /* use a 64-bit int for efficiency in the big loop */ for (p=a; p <= &a[nx*ny-1]; p++) *p = ((*p>0) ? (*p+d) : (*p-d))/scale64; } /* ######################################################################### */ /* ######################################################################### */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* encode.c encode H-transform and write to outfile * * Programmer: R. White Date: 2 February 1994 */ static char code_magic[2] = { (char)0xDD, (char)0x99 }; /* ######################################################################### */ static int encode(char *outfile, long *nlength, int a[], int nx, int ny, int scale) { /* FILE *outfile; - change outfile to a char array */ /* long * nlength returned length (in bytes) of the encoded array) int a[]; input H-transform array (nx,ny) int nx,ny; size of H-transform array int scale; scale factor for digitization */ int nel, nx2, ny2, i, j, k, q, vmax[3], nsign, bits_to_go; unsigned char nbitplanes[3]; unsigned char *signbits; int stat; noutchar = 0; /* initialize the number of compressed bytes that have been written */ nel = nx*ny; /* * write magic value */ qwrite(outfile, code_magic, sizeof(code_magic)); writeint(outfile, nx); /* size of image */ writeint(outfile, ny); writeint(outfile, scale); /* scale factor for digitization */ /* * write first value of A (sum of all pixels -- the only value * which does not compress well) */ writelonglong(outfile, (LONGLONG) a[0]); a[0] = 0; /* * allocate array for sign bits and save values, 8 per byte */ signbits = (unsigned char *) malloc((nel+7)/8); if (signbits == (unsigned char *) NULL) { ffpmsg("encode: insufficient memory"); return(DATA_COMPRESSION_ERR); } nsign = 0; bits_to_go = 8; signbits[0] = 0; for (i=0; i 0) { /* * positive element, put zero at end of buffer */ signbits[nsign] <<= 1; bits_to_go -= 1; } else if (a[i] < 0) { /* * negative element, shift in a one */ signbits[nsign] <<= 1; signbits[nsign] |= 1; bits_to_go -= 1; /* * replace a by absolute value */ a[i] = -a[i]; } if (bits_to_go == 0) { /* * filled up this byte, go to the next one */ bits_to_go = 8; nsign += 1; signbits[nsign] = 0; } } if (bits_to_go != 8) { /* * some bits in last element * move bits in last byte to bottom and increment nsign */ signbits[nsign] <<= bits_to_go; nsign += 1; } /* * calculate number of bit planes for 3 quadrants * * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, */ for (q=0; q<3; q++) { vmax[q] = 0; } /* * get maximum absolute value in each quadrant */ nx2 = (nx+1)/2; ny2 = (ny+1)/2; j=0; /* column counter */ k=0; /* row counter */ for (i=0; i=ny2) + (k>=nx2); if (vmax[q] < a[i]) vmax[q] = a[i]; if (++j >= ny) { j = 0; k += 1; } } /* * now calculate number of bits for each quadrant */ /* this is a more efficient way to do this, */ for (q = 0; q < 3; q++) { for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; } /* for (q = 0; q < 3; q++) { nbitplanes[q] = (int) (log((float) (vmax[q]+1))/log(2.0)+0.5); if ( (vmax[q]+1) > (1< 0) { if ( 0 == qwrite(outfile, (char *) signbits, nsign)) { free(signbits); *nlength = noutchar; ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } } free(signbits); *nlength = noutchar; if (noutchar >= noutmax) { ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } return(stat); } /* ######################################################################### */ static int encode64(char *outfile, long *nlength, LONGLONG a[], int nx, int ny, int scale) { /* FILE *outfile; - change outfile to a char array */ /* long * nlength returned length (in bytes) of the encoded array) LONGLONG a[]; input H-transform array (nx,ny) int nx,ny; size of H-transform array int scale; scale factor for digitization */ int nel, nx2, ny2, i, j, k, q, nsign, bits_to_go; LONGLONG vmax[3]; unsigned char nbitplanes[3]; unsigned char *signbits; int stat; noutchar = 0; /* initialize the number of compressed bytes that have been written */ nel = nx*ny; /* * write magic value */ qwrite(outfile, code_magic, sizeof(code_magic)); writeint(outfile, nx); /* size of image */ writeint(outfile, ny); writeint(outfile, scale); /* scale factor for digitization */ /* * write first value of A (sum of all pixels -- the only value * which does not compress well) */ writelonglong(outfile, a[0]); a[0] = 0; /* * allocate array for sign bits and save values, 8 per byte */ signbits = (unsigned char *) malloc((nel+7)/8); if (signbits == (unsigned char *) NULL) { ffpmsg("encode64: insufficient memory"); return(DATA_COMPRESSION_ERR); } nsign = 0; bits_to_go = 8; signbits[0] = 0; for (i=0; i 0) { /* * positive element, put zero at end of buffer */ signbits[nsign] <<= 1; bits_to_go -= 1; } else if (a[i] < 0) { /* * negative element, shift in a one */ signbits[nsign] <<= 1; signbits[nsign] |= 1; bits_to_go -= 1; /* * replace a by absolute value */ a[i] = -a[i]; } if (bits_to_go == 0) { /* * filled up this byte, go to the next one */ bits_to_go = 8; nsign += 1; signbits[nsign] = 0; } } if (bits_to_go != 8) { /* * some bits in last element * move bits in last byte to bottom and increment nsign */ signbits[nsign] <<= bits_to_go; nsign += 1; } /* * calculate number of bit planes for 3 quadrants * * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, */ for (q=0; q<3; q++) { vmax[q] = 0; } /* * get maximum absolute value in each quadrant */ nx2 = (nx+1)/2; ny2 = (ny+1)/2; j=0; /* column counter */ k=0; /* row counter */ for (i=0; i=ny2) + (k>=nx2); if (vmax[q] < a[i]) vmax[q] = a[i]; if (++j >= ny) { j = 0; k += 1; } } /* * now calculate number of bits for each quadrant */ /* this is a more efficient way to do this, */ for (q = 0; q < 3; q++) { for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; } /* for (q = 0; q < 3; q++) { nbitplanes[q] = log((float) (vmax[q]+1))/log(2.0)+0.5; if ( (vmax[q]+1) > (((LONGLONG) 1)< 0) { if ( 0 == qwrite(outfile, (char *) signbits, nsign)) { free(signbits); *nlength = noutchar; ffpmsg("encode: output buffer too small"); return(DATA_COMPRESSION_ERR); } } free(signbits); *nlength = noutchar; if (noutchar >= noutmax) { ffpmsg("encode64: output buffer too small"); return(DATA_COMPRESSION_ERR); } return(stat); } /* ######################################################################### */ /* ######################################################################### */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* qwrite.c Write binary data * * Programmer: R. White Date: 11 March 1991 */ /* ######################################################################### */ static void writeint(char *outfile, int a) { int i; unsigned char b[4]; /* Write integer A one byte at a time to outfile. * * This is portable from Vax to Sun since it eliminates the * need for byte-swapping. */ for (i=3; i>=0; i--) { b[i] = a & 0x000000ff; a >>= 8; } for (i=0; i<4; i++) qwrite(outfile, (char *) &b[i],1); } /* ######################################################################### */ static void writelonglong(char *outfile, LONGLONG a) { int i; unsigned char b[8]; /* Write integer A one byte at a time to outfile. * * This is portable from Vax to Sun since it eliminates the * need for byte-swapping. */ for (i=7; i>=0; i--) { b[i] = (unsigned char) (a & 0x000000ff); a >>= 8; } for (i=0; i<8; i++) qwrite(outfile, (char *) &b[i],1); } /* ######################################################################### */ static int qwrite(char *file, char buffer[], int n){ /* * write n bytes from buffer into file * returns number of bytes read (=n) if successful, <=0 if not */ if (noutchar + n > noutmax) return(0); /* buffer overflow */ memcpy(&file[noutchar], buffer, n); noutchar += n; return(n); } /* ######################################################################### */ /* ######################################################################### */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* doencode.c Encode 2-D array and write stream of characters on outfile * * This version assumes that A is positive. * * Programmer: R. White Date: 7 May 1991 */ /* ######################################################################### */ static int doencode(char *outfile, int a[], int nx, int ny, unsigned char nbitplanes[3]) { /* char *outfile; output data stream int a[]; Array of values to encode int nx,ny; Array dimensions [nx][ny] unsigned char nbitplanes[3]; Number of bit planes in quadrants */ int nx2, ny2, stat; nx2 = (nx+1)/2; ny2 = (ny+1)/2; /* * Initialize bit output */ start_outputing_bits(); /* * write out the bit planes for each quadrant */ stat = qtree_encode(outfile, &a[0], ny, nx2, ny2, nbitplanes[0]); if (!stat) stat = qtree_encode(outfile, &a[ny2], ny, nx2, ny/2, nbitplanes[1]); if (!stat) stat = qtree_encode(outfile, &a[ny*nx2], ny, nx/2, ny2, nbitplanes[1]); if (!stat) stat = qtree_encode(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]); /* * Add zero as an EOF symbol */ output_nybble(outfile, 0); done_outputing_bits(outfile); return(stat); } /* ######################################################################### */ static int doencode64(char *outfile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]) { /* char *outfile; output data stream LONGLONG a[]; Array of values to encode int nx,ny; Array dimensions [nx][ny] unsigned char nbitplanes[3]; Number of bit planes in quadrants */ int nx2, ny2, stat; nx2 = (nx+1)/2; ny2 = (ny+1)/2; /* * Initialize bit output */ start_outputing_bits(); /* * write out the bit planes for each quadrant */ stat = qtree_encode64(outfile, &a[0], ny, nx2, ny2, nbitplanes[0]); if (!stat) stat = qtree_encode64(outfile, &a[ny2], ny, nx2, ny/2, nbitplanes[1]); if (!stat) stat = qtree_encode64(outfile, &a[ny*nx2], ny, nx/2, ny2, nbitplanes[1]); if (!stat) stat = qtree_encode64(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]); /* * Add zero as an EOF symbol */ output_nybble(outfile, 0); done_outputing_bits(outfile); return(stat); } /* ######################################################################### */ /* ######################################################################### */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* BIT OUTPUT ROUTINES */ static LONGLONG bitcount; /* THE BIT BUFFER */ static int buffer2; /* Bits buffered for output */ static int bits_to_go2; /* Number of bits free in buffer */ /* ######################################################################### */ /* INITIALIZE FOR BIT OUTPUT */ static void start_outputing_bits(void) { buffer2 = 0; /* Buffer is empty to start */ bits_to_go2 = 8; /* with */ bitcount = 0; } /* ######################################################################### */ /* OUTPUT N BITS (N must be <= 8) */ static void output_nbits(char *outfile, int bits, int n) { /* AND mask for the right-most n bits */ static int mask[9] = {0, 1, 3, 7, 15, 31, 63, 127, 255}; /* * insert bits at end of buffer */ buffer2 <<= n; /* buffer2 |= ( bits & ((1<>(-bits_to_go2)) & 0xff); if (noutchar < noutmax) noutchar++; bits_to_go2 += 8; } bitcount += n; } /* ######################################################################### */ /* OUTPUT a 4 bit nybble */ static void output_nybble(char *outfile, int bits) { /* * insert 4 bits at end of buffer */ buffer2 = (buffer2<<4) | ( bits & 15 ); bits_to_go2 -= 4; if (bits_to_go2 <= 0) { /* * buffer2 full, put out top 8 bits */ outfile[noutchar] = ((buffer2>>(-bits_to_go2)) & 0xff); if (noutchar < noutmax) noutchar++; bits_to_go2 += 8; } bitcount += 4; } /* ############################################################################ */ /* OUTPUT array of 4 BITS */ static void output_nnybble(char *outfile, int n, unsigned char array[]) { /* pack the 4 lower bits in each element of the array into the outfile array */ int ii, jj, kk = 0, shift; if (n == 1) { output_nybble(outfile, (int) array[0]); return; } /* forcing byte alignment doesn;t help, and even makes it go slightly slower if (bits_to_go2 != 8) output_nbits(outfile, kk, bits_to_go2); */ if (bits_to_go2 <= 4) { /* just room for 1 nybble; write it out separately */ output_nybble(outfile, array[0]); kk++; /* index to next array element */ if (n == 2) /* only 1 more nybble to write out */ { output_nybble(outfile, (int) array[1]); return; } } /* bits_to_go2 is now in the range 5 - 8 */ shift = 8 - bits_to_go2; /* now write out pairs of nybbles; this does not affect value of bits_to_go2 */ jj = (n - kk) / 2; if (bits_to_go2 == 8) { /* special case if nybbles are aligned on byte boundary */ /* this actually seems to make very little differnece in speed */ buffer2 = 0; for (ii = 0; ii < jj; ii++) { outfile[noutchar] = ((array[kk] & 15)<<4) | (array[kk+1] & 15); kk += 2; noutchar++; } } else { for (ii = 0; ii < jj; ii++) { buffer2 = (buffer2<<8) | ((array[kk] & 15)<<4) | (array[kk+1] & 15); kk += 2; /* buffer2 full, put out top 8 bits */ outfile[noutchar] = ((buffer2>>shift) & 0xff); noutchar++; } } bitcount += (8 * (ii - 1)); /* write out last odd nybble, if present */ if (kk != n) output_nybble(outfile, (int) array[n - 1]); return; } /* ######################################################################### */ /* FLUSH OUT THE LAST BITS */ static void done_outputing_bits(char *outfile) { if(bits_to_go2 < 8) { /* putc(buffer2<nqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * initial bit buffer */ b = 0; bitbuffer = 0; bits_to_go3 = 0; /* * on first pass copy A to scratch array */ qtree_onebit(a,n,nqx,nqy,scratch,bit); nx = (nqx+1)>>1; ny = (nqy+1)>>1; /* * copy non-zero values to output buffer, which will be written * in reverse order */ if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { /* * quadtree is expanding data, * change warning code and just fill buffer with bit-map */ write_bdirect(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } /* * do log2n reductions */ for (k = 1; k>1; ny = (ny+1)>>1; if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { write_bdirect(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } } /* * OK, we've got the code in buffer * Write quadtree warning code, then write buffer in reverse order */ output_nybble(outfile,0xF); if (b==0) { if (bits_to_go3>0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<=0; i--) { output_nbits(outfile,buffer[i],8); } } bitplane_done: ; } free(buffer); free(scratch); return(0); } /* ######################################################################### */ static int qtree_encode64(char *outfile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes) { /* LONGLONG a[]; int n; physical dimension of row in a int nqx; length of row int nqy; length of column (<=n) int nbitplanes; number of bit planes to output */ int log2n, i, k, bit, b, nqmax, nqx2, nqy2, nx, ny; int bmax; /* this potentially needs to be made a 64-bit int to support large arrays */ unsigned char *scratch, *buffer; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ nqmax = (nqx>nqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * initial bit buffer */ b = 0; bitbuffer = 0; bits_to_go3 = 0; /* * on first pass copy A to scratch array */ qtree_onebit64(a,n,nqx,nqy,scratch,bit); nx = (nqx+1)>>1; ny = (nqy+1)>>1; /* * copy non-zero values to output buffer, which will be written * in reverse order */ if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { /* * quadtree is expanding data, * change warning code and just fill buffer with bit-map */ write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } /* * do log2n reductions */ for (k = 1; k>1; ny = (ny+1)>>1; if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) { write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit); goto bitplane_done; } } /* * OK, we've got the code in buffer * Write quadtree warning code, then write buffer in reverse order */ output_nybble(outfile,0xF); if (b==0) { if (bits_to_go3>0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<0) { /* * put out the last few bits */ output_nbits(outfile, bitbuffer & ((1<=0; i--) { output_nbits(outfile,buffer[i],8); } } bitplane_done: ; } free(buffer); free(scratch); return(0); } /* ######################################################################### */ /* * copy non-zero codes from array to buffer */ static int bufcopy(unsigned char a[], int n, unsigned char buffer[], int *b, int bmax) { int i; for (i = 0; i < n; i++) { if (a[i] != 0) { /* * add Huffman code for a[i] to buffer */ bitbuffer |= code[a[i]] << bits_to_go3; bits_to_go3 += ncode[a[i]]; if (bits_to_go3 >= 8) { buffer[*b] = bitbuffer & 0xFF; *b += 1; /* * return warning code if we fill buffer */ if (*b >= bmax) return(1); bitbuffer >>= 8; bits_to_go3 -= 8; } } } return(0); } /* ######################################################################### */ /* * Do first quadtree reduction step on bit BIT of array A. * Results put into B. * */ static void qtree_onebit(int a[], int n, int nx, int ny, unsigned char b[], int bit) { int i, j, k; int b0, b1, b2, b3; int s10, s00; /* * use selected bit to get amount to shift */ b0 = 1<> bit; k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row * s00+1,s10+1 are off edge */ b[k] = ( ((a[s10 ]<<1) & b1) | ((a[s00 ]<<3) & b3) ) >> bit; k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10,s10+1 are off edge */ s00 = n*i; for (j = 0; j> bit; k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ b[k] = ( ((a[s00 ]<<3) & b3) ) >> bit; k += 1; } } } /* ######################################################################### */ /* * Do first quadtree reduction step on bit BIT of array A. * Results put into B. * */ static void qtree_onebit64(LONGLONG a[], int n, int nx, int ny, unsigned char b[], int bit) { int i, j, k; LONGLONG b0, b1, b2, b3; int s10, s00; /* * use selected bit to get amount to shift */ b0 = ((LONGLONG) 1)<> bit); k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row * s00+1,s10+1 are off edge */ b[k] = (unsigned char) (( ((a[s10 ]<<1) & b1) | ((a[s00 ]<<3) & b3) ) >> bit); k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10,s10+1 are off edge */ s00 = n*i; for (j = 0; j> bit); k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ b[k] = (unsigned char) (( ((a[s00 ]<<3) & b3) ) >> bit); k += 1; } } } /* ######################################################################### */ /* * do one quadtree reduction step on array a * results put into b (which may be the same as a) */ static void qtree_reduce(unsigned char a[], int n, int nx, int ny, unsigned char b[]) { int i, j, k; int s10, s00; k = 0; /* k is index of b[i/2,j/2] */ for (i = 0; i #include #include #include #include "fitsio2.h" /* WDP added test to see if min and max are already defined */ #ifndef min #define min(a,b) (((a)<(b))?(a):(b)) #endif #ifndef max #define max(a,b) (((a)>(b))?(a):(b)) #endif static long nextchar; static int decode(unsigned char *infile, int *a, int *nx, int *ny, int *scale); static int decode64(unsigned char *infile, LONGLONG *a, int *nx, int *ny, int *scale); static int hinv(int a[], int nx, int ny, int smooth ,int scale); static int hinv64(LONGLONG a[], int nx, int ny, int smooth ,int scale); static void undigitize(int a[], int nx, int ny, int scale); static void undigitize64(LONGLONG a[], int nx, int ny, int scale); static void unshuffle(int a[], int n, int n2, int tmp[]); static void unshuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[]); static void hsmooth(int a[], int nxtop, int nytop, int ny, int scale); static void hsmooth64(LONGLONG a[], int nxtop, int nytop, int ny, int scale); static void qread(unsigned char *infile,char *a, int n); static int readint(unsigned char *infile); static LONGLONG readlonglong(unsigned char *infile); static int dodecode(unsigned char *infile, int a[], int nx, int ny, unsigned char nbitplanes[3]); static int dodecode64(unsigned char *infile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]); static int qtree_decode(unsigned char *infile, int a[], int n, int nqx, int nqy, int nbitplanes); static int qtree_decode64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes); static void start_inputing_bits(void); static int input_bit(unsigned char *infile); static int input_nbits(unsigned char *infile, int n); /* make input_nybble a separate routine, for added effiency */ /* #define input_nybble(infile) input_nbits(infile,4) */ static int input_nybble(unsigned char *infile); static int input_nnybble(unsigned char *infile, int n, unsigned char *array); static void qtree_expand(unsigned char *infile, unsigned char a[], int nx, int ny, unsigned char b[]); static void qtree_bitins(unsigned char a[], int nx, int ny, int b[], int n, int bit); static void qtree_bitins64(unsigned char a[], int nx, int ny, LONGLONG b[], int n, int bit); static void qtree_copy(unsigned char a[], int nx, int ny, unsigned char b[], int n); static void read_bdirect(unsigned char *infile, int a[], int n, int nqx, int nqy, unsigned char scratch[], int bit); static void read_bdirect64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, unsigned char scratch[], int bit); static int input_huffman(unsigned char *infile); /* ---------------------------------------------------------------------- */ int fits_hdecompress(unsigned char *input, int smooth, int *a, int *ny, int *nx, int *scale, int *status) { /* decompress the input byte stream using the H-compress algorithm input - input array of compressed bytes a - pre-allocated array to hold the output uncompressed image nx - returned X axis size ny - returned Y axis size NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat; if (*status > 0) return(*status); /* decode the input array */ FFLOCK; /* decode uses the nextchar global variable */ stat = decode(input, a, nx, ny, scale); FFUNLOCK; *status = stat; if (stat) return(*status); /* * Un-Digitize */ undigitize(a, *nx, *ny, *scale); /* * Inverse H-transform */ stat = hinv(a, *nx, *ny, smooth, *scale); *status = stat; return(*status); } /* ---------------------------------------------------------------------- */ int fits_hdecompress64(unsigned char *input, int smooth, LONGLONG *a, int *ny, int *nx, int *scale, int *status) { /* decompress the input byte stream using the H-compress algorithm input - input array of compressed bytes a - pre-allocated array to hold the output uncompressed image nx - returned X axis size ny - returned Y axis size NOTE: the nx and ny dimensions as defined within this code are reversed from the usual FITS notation. ny is the fastest varying dimension, which is usually considered the X axis in the FITS image display */ int stat, *iarray, ii, nval; if (*status > 0) return(*status); /* decode the input array */ FFLOCK; /* decode uses the nextchar global variable */ stat = decode64(input, a, nx, ny, scale); FFUNLOCK; *status = stat; if (stat) return(*status); /* * Un-Digitize */ undigitize64(a, *nx, *ny, *scale); /* * Inverse H-transform */ stat = hinv64(a, *nx, *ny, smooth, *scale); *status = stat; /* pack the I*8 values back into an I*4 array */ iarray = (int *) a; nval = (*nx) * (*ny); for (ii = 0; ii < nval; ii++) iarray[ii] = (int) a[ii]; return(*status); } /* ############################################################################ */ /* ############################################################################ */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* hinv.c Inverse H-transform of NX x NY integer image * * Programmer: R. White Date: 23 July 1993 */ /* ############################################################################ */ static int hinv(int a[], int nx, int ny, int smooth ,int scale) /* int smooth; 0 for no smoothing, else smooth during inversion int scale; used if smoothing is specified */ { int nmax, log2n, i, j, k; int nxtop,nytop,nxf,nyf,c; int oddx,oddy; int shift, bit0, bit1, bit2, mask0, mask1, mask2, prnd0, prnd1, prnd2, nrnd0, nrnd1, nrnd2, lowbit0, lowbit1; int h0, hx, hy, hc; int s10, s00; int *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> 1; prnd1 = bit1 >> 1; prnd2 = bit2 >> 1; nrnd0 = prnd0 - 1; nrnd1 = prnd1 - 1; nrnd2 = prnd2 - 1; /* * round h0 to multiple of bit2 */ a[0] = (a[0] + ((a[0] >= 0) ? prnd2 : nrnd2)) & mask2; /* * do log2n expansions * * We're indexing a as a 2-D array with dimensions (nx,ny). */ nxtop = 1; nytop = 1; nxf = nx; nyf = ny; c = 1<=0; k--) { /* * this somewhat cryptic code generates the sequence * ntop[k-1] = (ntop[k]+1)/2, where ntop[log2n] = n */ c = c>>1; nxtop = nxtop<<1; nytop = nytop<<1; if (nxf <= c) { nxtop -= 1; } else { nxf -= c; } if (nyf <= c) { nytop -= 1; } else { nyf -= c; } /* * double shift and fix nrnd0 (because prnd0=0) on last pass */ if (k == 0) { nrnd0 = 0; shift = 2; } /* * unshuffle in each dimension to interleave coefficients */ for (i = 0; i= 0) ? prnd1 : nrnd1)) & mask1; hy = (hy + ((hy >= 0) ? prnd1 : nrnd1)) & mask1; hc = (hc + ((hc >= 0) ? prnd0 : nrnd0)) & mask0; /* * propagate bit0 of hc to hx,hy */ lowbit0 = hc & bit0; hx = (hx >= 0) ? (hx - lowbit0) : (hx + lowbit0); hy = (hy >= 0) ? (hy - lowbit0) : (hy + lowbit0); /* * Propagate bits 0 and 1 of hc,hx,hy to h0. * This could be simplified if we assume h0>0, but then * the inversion would not be lossless for images with * negative pixels. */ lowbit1 = (hc ^ hx ^ hy) & bit1; h0 = (h0 >= 0) ? (h0 + lowbit0 - lowbit1) : (h0 + ((lowbit0 == 0) ? lowbit1 : (lowbit0-lowbit1))); /* * Divide sums by 2 (4 last time) */ a[s10+1] = (h0 + hx + hy + hc) >> shift; a[s10 ] = (h0 + hx - hy - hc) >> shift; a[s00+1] = (h0 - hx + hy - hc) >> shift; a[s00 ] = (h0 - hx - hy + hc) >> shift; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = a[s00 ]; hx = a[s10 ]; hx = ((hx >= 0) ? (hx+prnd1) : (hx+nrnd1)) & mask1; lowbit1 = hx & bit1; h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1); a[s10 ] = (h0 + hx) >> shift; a[s00 ] = (h0 - hx) >> shift; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = ny*i; for (j = 0; j= 0) ? (hy+prnd1) : (hy+nrnd1)) & mask1; lowbit1 = hy & bit1; h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1); a[s00+1] = (h0 + hy) >> shift; a[s00 ] = (h0 - hy) >> shift; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00 ]; a[s00 ] = h0 >> shift; } } /* * divide all the masks and rounding values by 2 */ bit2 = bit1; bit1 = bit0; bit0 = bit0 >> 1; mask1 = mask0; mask0 = mask0 >> 1; prnd1 = prnd0; prnd0 = prnd0 >> 1; nrnd1 = nrnd0; nrnd0 = prnd0 - 1; } free(tmp); return(0); } /* ############################################################################ */ static int hinv64(LONGLONG a[], int nx, int ny, int smooth ,int scale) /* int smooth; 0 for no smoothing, else smooth during inversion int scale; used if smoothing is specified */ { int nmax, log2n, i, j, k; int nxtop,nytop,nxf,nyf,c; int oddx,oddy; int shift; LONGLONG mask0, mask1, mask2, prnd0, prnd1, prnd2, bit0, bit1, bit2; LONGLONG nrnd0, nrnd1, nrnd2, lowbit0, lowbit1; LONGLONG h0, hx, hy, hc; int s10, s00; LONGLONG *tmp; /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ nmax = (nx>ny) ? nx : ny; log2n = (int) (log((float) nmax)/log(2.0)+0.5); if ( nmax > (1<> 1; prnd1 = bit1 >> 1; prnd2 = bit2 >> 1; nrnd0 = prnd0 - 1; nrnd1 = prnd1 - 1; nrnd2 = prnd2 - 1; /* * round h0 to multiple of bit2 */ a[0] = (a[0] + ((a[0] >= 0) ? prnd2 : nrnd2)) & mask2; /* * do log2n expansions * * We're indexing a as a 2-D array with dimensions (nx,ny). */ nxtop = 1; nytop = 1; nxf = nx; nyf = ny; c = 1<=0; k--) { /* * this somewhat cryptic code generates the sequence * ntop[k-1] = (ntop[k]+1)/2, where ntop[log2n] = n */ c = c>>1; nxtop = nxtop<<1; nytop = nytop<<1; if (nxf <= c) { nxtop -= 1; } else { nxf -= c; } if (nyf <= c) { nytop -= 1; } else { nyf -= c; } /* * double shift and fix nrnd0 (because prnd0=0) on last pass */ if (k == 0) { nrnd0 = 0; shift = 2; } /* * unshuffle in each dimension to interleave coefficients */ for (i = 0; i= 0) ? prnd1 : nrnd1)) & mask1; hy = (hy + ((hy >= 0) ? prnd1 : nrnd1)) & mask1; hc = (hc + ((hc >= 0) ? prnd0 : nrnd0)) & mask0; /* * propagate bit0 of hc to hx,hy */ lowbit0 = hc & bit0; hx = (hx >= 0) ? (hx - lowbit0) : (hx + lowbit0); hy = (hy >= 0) ? (hy - lowbit0) : (hy + lowbit0); /* * Propagate bits 0 and 1 of hc,hx,hy to h0. * This could be simplified if we assume h0>0, but then * the inversion would not be lossless for images with * negative pixels. */ lowbit1 = (hc ^ hx ^ hy) & bit1; h0 = (h0 >= 0) ? (h0 + lowbit0 - lowbit1) : (h0 + ((lowbit0 == 0) ? lowbit1 : (lowbit0-lowbit1))); /* * Divide sums by 2 (4 last time) */ a[s10+1] = (h0 + hx + hy + hc) >> shift; a[s10 ] = (h0 + hx - hy - hc) >> shift; a[s00+1] = (h0 - hx + hy - hc) >> shift; a[s00 ] = (h0 - hx - hy + hc) >> shift; s00 += 2; s10 += 2; } if (oddy) { /* * do last element in row if row length is odd * s00+1, s10+1 are off edge */ h0 = a[s00 ]; hx = a[s10 ]; hx = ((hx >= 0) ? (hx+prnd1) : (hx+nrnd1)) & mask1; lowbit1 = hx & bit1; h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1); a[s10 ] = (h0 + hx) >> shift; a[s00 ] = (h0 - hx) >> shift; } } if (oddx) { /* * do last row if column length is odd * s10, s10+1 are off edge */ s00 = ny*i; for (j = 0; j= 0) ? (hy+prnd1) : (hy+nrnd1)) & mask1; lowbit1 = hy & bit1; h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1); a[s00+1] = (h0 + hy) >> shift; a[s00 ] = (h0 - hy) >> shift; s00 += 2; } if (oddy) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ h0 = a[s00 ]; a[s00 ] = h0 >> shift; } } /* * divide all the masks and rounding values by 2 */ bit2 = bit1; bit1 = bit0; bit0 = bit0 >> 1; mask1 = mask0; mask0 = mask0 >> 1; prnd1 = prnd0; prnd0 = prnd0 >> 1; nrnd1 = nrnd0; nrnd0 = prnd0 - 1; } free(tmp); return(0); } /* ############################################################################ */ static void unshuffle(int a[], int n, int n2, int tmp[]) /* int a[]; array to shuffle int n; number of elements to shuffle int n2; second dimension int tmp[]; scratch storage */ { int i; int nhalf; int *p1, *p2, *pt; /* * copy 2nd half of array to tmp */ nhalf = (n+1)>>1; pt = tmp; p1 = &a[n2*nhalf]; /* pointer to a[i] */ for (i=nhalf; i= 0; i--) { *p1 = *p2; p2 -= n2; p1 -= (n2+n2); } /* * now distribute 2nd half of array (in tmp) to odd elements */ pt = tmp; p1 = &a[n2]; /* pointer to a[i] */ for (i=1; i>1; pt = tmp; p1 = &a[n2*nhalf]; /* pointer to a[i] */ for (i=nhalf; i= 0; i--) { *p1 = *p2; p2 -= n2; p1 -= (n2+n2); } /* * now distribute 2nd half of array (in tmp) to odd elements */ pt = tmp; p1 = &a[n2]; /* pointer to a[i] */ for (i=1; i> 1); if (smax <= 0) return; ny2 = ny << 1; /* * We're indexing a as a 2-D array with dimensions (nxtop,ny) of which * only (nxtop,nytop) are used. The coefficients on the edge of the * array are not adjusted (which is why the loops below start at 2 * instead of 0 and end at nxtop-2 instead of nxtop.) */ /* * Adjust x difference hx */ for (i = 2; i=0, dmin<=0. */ if (dmin < dmax) { diff = max( min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using * shift for divide by 8. */ s = diff-(a[s10]<<3); s = (s>=0) ? (s>>3) : ((s+7)>>3) ; s = max( min(s, smax), -smax); a[s10] = a[s10]+s; } s00 += 2; s10 += 2; } } /* * Adjust y difference hy */ for (i = 0; i=0) ? (s>>3) : ((s+7)>>3) ; s = max( min(s, smax), -smax); a[s00+1] = a[s00+1]+s; } s00 += 2; s10 += 2; } } /* * Adjust curvature difference hc */ for (i = 2; i=0, dmin<=0. */ if (dmin < dmax) { diff = max( min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using * shift for divide by 64. */ s = diff-(a[s10+1]<<6); s = (s>=0) ? (s>>6) : ((s+63)>>6) ; s = max( min(s, smax), -smax); a[s10+1] = a[s10+1]+s; } s00 += 2; s10 += 2; } } } /* ############################################################################ */ static void hsmooth64(LONGLONG a[], int nxtop, int nytop, int ny, int scale) /* LONGLONG a[]; array of H-transform coefficients int nxtop,nytop; size of coefficient block to use int ny; actual 1st dimension of array int scale; truncation scale factor that was used */ { int i, j; int ny2, s10, s00; LONGLONG hm, h0, hp, hmm, hpm, hmp, hpp, hx2, hy2, diff, dmax, dmin, s, smax, m1, m2; /* * Maximum change in coefficients is determined by scale factor. * Since we rounded during division (see digitize.c), the biggest * permitted change is scale/2. */ smax = (scale >> 1); if (smax <= 0) return; ny2 = ny << 1; /* * We're indexing a as a 2-D array with dimensions (nxtop,ny) of which * only (nxtop,nytop) are used. The coefficients on the edge of the * array are not adjusted (which is why the loops below start at 2 * instead of 0 and end at nxtop-2 instead of nxtop.) */ /* * Adjust x difference hx */ for (i = 2; i=0, dmin<=0. */ if (dmin < dmax) { diff = max( min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using * shift for divide by 8. */ s = diff-(a[s10]<<3); s = (s>=0) ? (s>>3) : ((s+7)>>3) ; s = max( min(s, smax), -smax); a[s10] = a[s10]+s; } s00 += 2; s10 += 2; } } /* * Adjust y difference hy */ for (i = 0; i=0) ? (s>>3) : ((s+7)>>3) ; s = max( min(s, smax), -smax); a[s00+1] = a[s00+1]+s; } s00 += 2; s10 += 2; } } /* * Adjust curvature difference hc */ for (i = 2; i=0, dmin<=0. */ if (dmin < dmax) { diff = max( min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using * shift for divide by 64. */ s = diff-(a[s10+1]<<6); s = (s>=0) ? (s>>6) : ((s+63)>>6) ; s = max( min(s, smax), -smax); a[s10+1] = a[s10+1]+s; } s00 += 2; s10 += 2; } } } /* ############################################################################ */ /* ############################################################################ */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* undigitize.c undigitize H-transform * * Programmer: R. White Date: 9 May 1991 */ /* ############################################################################ */ static void undigitize(int a[], int nx, int ny, int scale) { int *p; /* * multiply by scale */ if (scale <= 1) return; for (p=a; p <= &a[nx*ny-1]; p++) *p = (*p)*scale; } /* ############################################################################ */ static void undigitize64(LONGLONG a[], int nx, int ny, int scale) { LONGLONG *p, scale64; /* * multiply by scale */ if (scale <= 1) return; scale64 = (LONGLONG) scale; /* use a 64-bit int for efficiency in the big loop */ for (p=a; p <= &a[nx*ny-1]; p++) *p = (*p)*scale64; } /* ############################################################################ */ /* ############################################################################ */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* decode.c read codes from infile and construct array * * Programmer: R. White Date: 2 February 1994 */ static char code_magic[2] = { (char)0xDD, (char)0x99 }; /* ############################################################################ */ static int decode(unsigned char *infile, int *a, int *nx, int *ny, int *scale) /* char *infile; input file int *a; address of output array [nx][ny] int *nx,*ny; size of output array int *scale; scale factor for digitization */ { LONGLONG sumall; int nel, stat; unsigned char nbitplanes[3]; char tmagic[2]; /* initialize the byte read position to the beginning of the array */; nextchar = 0; /* * File starts either with special 2-byte magic code or with * FITS keyword "SIMPLE =" */ qread(infile, tmagic, sizeof(tmagic)); /* * check for correct magic code value */ if (memcmp(tmagic,code_magic,sizeof(code_magic)) != 0) { ffpmsg("bad file format"); return(DATA_DECOMPRESSION_ERR); } *nx =readint(infile); /* x size of image */ *ny =readint(infile); /* y size of image */ *scale=readint(infile); /* scale factor for digitization */ nel = (*nx) * (*ny); /* sum of all pixels */ sumall=readlonglong(infile); /* # bits in quadrants */ qread(infile, (char *) nbitplanes, sizeof(nbitplanes)); stat = dodecode(infile, a, *nx, *ny, nbitplanes); /* * put sum of all pixels back into pixel 0 */ a[0] = (int) sumall; return(stat); } /* ############################################################################ */ static int decode64(unsigned char *infile, LONGLONG *a, int *nx, int *ny, int *scale) /* char *infile; input file LONGLONG *a; address of output array [nx][ny] int *nx,*ny; size of output array int *scale; scale factor for digitization */ { int nel, stat; LONGLONG sumall; unsigned char nbitplanes[3]; char tmagic[2]; /* initialize the byte read position to the beginning of the array */; nextchar = 0; /* * File starts either with special 2-byte magic code or with * FITS keyword "SIMPLE =" */ qread(infile, tmagic, sizeof(tmagic)); /* * check for correct magic code value */ if (memcmp(tmagic,code_magic,sizeof(code_magic)) != 0) { ffpmsg("bad file format"); return(DATA_DECOMPRESSION_ERR); } *nx =readint(infile); /* x size of image */ *ny =readint(infile); /* y size of image */ *scale=readint(infile); /* scale factor for digitization */ nel = (*nx) * (*ny); /* sum of all pixels */ sumall=readlonglong(infile); /* # bits in quadrants */ qread(infile, (char *) nbitplanes, sizeof(nbitplanes)); stat = dodecode64(infile, a, *nx, *ny, nbitplanes); /* * put sum of all pixels back into pixel 0 */ a[0] = sumall; return(stat); } /* ############################################################################ */ /* ############################################################################ */ /* Copyright (c) 1993 Association of Universities for Research * in Astronomy. All rights reserved. Produced under National * Aeronautics and Space Administration Contract No. NAS5-26555. */ /* dodecode.c Decode stream of characters on infile and return array * * This version encodes the different quadrants separately * * Programmer: R. White Date: 9 May 1991 */ /* ############################################################################ */ static int dodecode(unsigned char *infile, int a[], int nx, int ny, unsigned char nbitplanes[3]) /* int a[]; int nx,ny; Array dimensions are [nx][ny] unsigned char nbitplanes[3]; Number of bit planes in quadrants */ { int i, nel, nx2, ny2, stat; nel = nx*ny; nx2 = (nx+1)/2; ny2 = (ny+1)/2; /* * initialize a to zero */ for (i=0; inqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * Was bitplane was quadtree-coded or written directly? */ b = input_nybble(infile); if(b == 0) { /* * bit map was written directly */ read_bdirect(infile,a,n,nqx,nqy,scratch,bit); } else if (b != 0xf) { ffpmsg("qtree_decode: bad format code"); return(DATA_DECOMPRESSION_ERR); } else { /* * bitmap was quadtree-coded, do log2n expansions * * read first code */ scratch[0] = input_huffman(infile); /* * now do log2n expansions, reading codes from file as necessary */ nx = 1; ny = 1; nfx = nqx; nfy = nqy; c = 1<>1; nx = nx<<1; ny = ny<<1; if (nfx <= c) { nx -= 1; } else { nfx -= c; } if (nfy <= c) { ny -= 1; } else { nfy -= c; } qtree_expand(infile,scratch,nx,ny,scratch); } /* * now copy last set of 4-bit codes to bitplane bit of array a */ qtree_bitins(scratch,nqx,nqy,a,n,bit); } } free(scratch); return(0); } /* ############################################################################ */ static int qtree_decode64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes) /* char *infile; LONGLONG a[]; a is 2-D array with dimensions (n,n) int n; length of full row in a int nqx; partial length of row to decode int nqy; partial length of column (<=n) int nbitplanes; number of bitplanes to decode */ { int log2n, k, bit, b, nqmax; int nx,ny,nfx,nfy,c; int nqx2, nqy2; unsigned char *scratch; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ nqmax = (nqx>nqy) ? nqx : nqy; log2n = (int) (log((float) nqmax)/log(2.0)+0.5); if (nqmax > (1<= 0; bit--) { /* * Was bitplane was quadtree-coded or written directly? */ b = input_nybble(infile); if(b == 0) { /* * bit map was written directly */ read_bdirect64(infile,a,n,nqx,nqy,scratch,bit); } else if (b != 0xf) { ffpmsg("qtree_decode64: bad format code"); return(DATA_DECOMPRESSION_ERR); } else { /* * bitmap was quadtree-coded, do log2n expansions * * read first code */ scratch[0] = input_huffman(infile); /* * now do log2n expansions, reading codes from file as necessary */ nx = 1; ny = 1; nfx = nqx; nfy = nqy; c = 1<>1; nx = nx<<1; ny = ny<<1; if (nfx <= c) { nx -= 1; } else { nfx -= c; } if (nfy <= c) { ny -= 1; } else { nfy -= c; } qtree_expand(infile,scratch,nx,ny,scratch); } /* * now copy last set of 4-bit codes to bitplane bit of array a */ qtree_bitins64(scratch,nqx,nqy,a,n,bit); } } free(scratch); return(0); } /* ############################################################################ */ /* * do one quadtree expansion step on array a[(nqx+1)/2,(nqy+1)/2] * results put into b[nqx,nqy] (which may be the same as a) */ static void qtree_expand(unsigned char *infile, unsigned char a[], int nx, int ny, unsigned char b[]) { int i; /* * first copy a to b, expanding each 4-bit value */ qtree_copy(a,nx,ny,b,ny); /* * now read new 4-bit values into b for each non-zero element */ for (i = nx*ny-1; i >= 0; i--) { if (b[i]) b[i] = input_huffman(infile); } } /* ############################################################################ */ /* * copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding * each value to 2x2 pixels * a,b may be same array */ static void qtree_copy(unsigned char a[], int nx, int ny, unsigned char b[], int n) /* int n; declared y dimension of b */ { int i, j, k, nx2, ny2; int s00, s10; /* * first copy 4-bit values to b * start at end in case a,b are same array */ nx2 = (nx+1)/2; ny2 = (ny+1)/2; k = ny2*(nx2-1)+ny2-1; /* k is index of a[i,j] */ for (i = nx2-1; i >= 0; i--) { s00 = 2*(n*i+ny2-1); /* s00 is index of b[2*i,2*j] */ for (j = ny2-1; j >= 0; j--) { b[s00] = a[k]; k -= 1; s00 -= 2; } } /* * now expand each 2x2 block */ for (i = 0; i>1) & 1; b[s00+1] = (b[s00]>>2) & 1; b[s00 ] = (b[s00]>>3) & 1; */ s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row * s00+1, s10+1 are off edge */ /* not worth converting this to use 16 case statements */ b[s10 ] = (b[s00]>>1) & 1; b[s00 ] = (b[s00]>>3) & 1; } } if (i < nx) { /* * column size is odd, do last row * s10, s10+1 are off edge */ s00 = n*i; for (j = 0; j>2) & 1; b[s00 ] = (b[s00]>>3) & 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ /* not worth converting this to use 16 case statements */ b[s00 ] = (b[s00]>>3) & 1; } } } /* ############################################################################ */ /* * Copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding * each value to 2x2 pixels and inserting into bitplane BIT of B. * A,B may NOT be same array (it wouldn't make sense to be inserting * bits into the same array anyway.) */ static void qtree_bitins(unsigned char a[], int nx, int ny, int b[], int n, int bit) /* int n; declared y dimension of b */ { int i, j, k; int s00; int plane_val; plane_val = 1 << bit; /* * expand each 2x2 block */ k = 0; /* k is index of a[i/2,j/2] */ for (i = 0; i>1) & 1) << bit; b[s00+1] |= ((a[k]>>2) & 1) << bit; b[s00 ] |= ((a[k]>>3) & 1) << bit; */ s00 += 2; /* s10 += 2; */ k += 1; } if (j < ny) { /* * row size is odd, do last element in row * s00+1, s10+1 are off edge */ switch (a[k]) { case(0): break; case(1): break; case(2): b[s00+n ] |= plane_val; break; case(3): b[s00+n ] |= plane_val; break; case(4): break; case(5): break; case(6): b[s00+n ] |= plane_val; break; case(7): b[s00+n ] |= plane_val; break; case(8): b[s00 ] |= plane_val; break; case(9): b[s00 ] |= plane_val; break; case(10): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(11): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(12): b[s00 ] |= plane_val; break; case(13): b[s00 ] |= plane_val; break; case(14): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(15): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; } /* b[s10 ] |= ((a[k]>>1) & 1) << bit; b[s00 ] |= ((a[k]>>3) & 1) << bit; */ k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10, s10+1 are off edge */ s00 = n*i; for (j = 0; j>2) & 1) << bit; b[s00 ] |= ((a[k]>>3) & 1) << bit; */ s00 += 2; k += 1; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ switch (a[k]) { case(0): break; case(1): break; case(2): break; case(3): break; case(4): break; case(5): break; case(6): break; case(7): break; case(8): b[s00 ] |= plane_val; break; case(9): b[s00 ] |= plane_val; break; case(10): b[s00 ] |= plane_val; break; case(11): b[s00 ] |= plane_val; break; case(12): b[s00 ] |= plane_val; break; case(13): b[s00 ] |= plane_val; break; case(14): b[s00 ] |= plane_val; break; case(15): b[s00 ] |= plane_val; break; } /* b[s00 ] |= ((a[k]>>3) & 1) << bit; */ k += 1; } } } /* ############################################################################ */ /* * Copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding * each value to 2x2 pixels and inserting into bitplane BIT of B. * A,B may NOT be same array (it wouldn't make sense to be inserting * bits into the same array anyway.) */ static void qtree_bitins64(unsigned char a[], int nx, int ny, LONGLONG b[], int n, int bit) /* int n; declared y dimension of b */ { int i, j, k; int s00; int plane_val; plane_val = 1 << bit; /* * expand each 2x2 block */ k = 0; /* k is index of a[i/2,j/2] */ for (i = 0; i>1) & 1) << bit; b[s00+1] |= ((((LONGLONG)a[k])>>2) & 1) << bit; b[s00 ] |= ((((LONGLONG)a[k])>>3) & 1) << bit; */ s00 += 2; /* s10 += 2; */ k += 1; } if (j < ny) { /* * row size is odd, do last element in row * s00+1, s10+1 are off edge */ switch (a[k]) { case(0): break; case(1): break; case(2): b[s00+n ] |= plane_val; break; case(3): b[s00+n ] |= plane_val; break; case(4): break; case(5): break; case(6): b[s00+n ] |= plane_val; break; case(7): b[s00+n ] |= plane_val; break; case(8): b[s00 ] |= plane_val; break; case(9): b[s00 ] |= plane_val; break; case(10): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(11): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(12): b[s00 ] |= plane_val; break; case(13): b[s00 ] |= plane_val; break; case(14): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; case(15): b[s00+n ] |= plane_val; b[s00 ] |= plane_val; break; } /* b[s10 ] |= ((((LONGLONG)a[k])>>1) & 1) << bit; b[s00 ] |= ((((LONGLONG)a[k])>>3) & 1) << bit; */ k += 1; } } if (i < nx) { /* * column size is odd, do last row * s10, s10+1 are off edge */ s00 = n*i; for (j = 0; j>2) & 1) << bit; b[s00 ] |= ((((LONGLONG)a[k])>>3) & 1) << bit; */ s00 += 2; k += 1; } if (j < ny) { /* * both row and column size are odd, do corner element * s00+1, s10, s10+1 are off edge */ switch (a[k]) { case(0): break; case(1): break; case(2): break; case(3): break; case(4): break; case(5): break; case(6): break; case(7): break; case(8): b[s00 ] |= plane_val; break; case(9): b[s00 ] |= plane_val; break; case(10): b[s00 ] |= plane_val; break; case(11): b[s00 ] |= plane_val; break; case(12): b[s00 ] |= plane_val; break; case(13): b[s00 ] |= plane_val; break; case(14): b[s00 ] |= plane_val; break; case(15): b[s00 ] |= plane_val; break; } /* b[s00 ] |= ((((LONGLONG)a[k])>>3) & 1) << bit; */ k += 1; } } } /* ############################################################################ */ static void read_bdirect(unsigned char *infile, int a[], int n, int nqx, int nqy, unsigned char scratch[], int bit) { /* * read bit image packed 4 pixels/nybble */ /* int i; for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) { scratch[i] = input_nybble(infile); } */ input_nnybble(infile, ((nqx+1)/2) * ((nqy+1)/2), scratch); /* * insert in bitplane BIT of image A */ qtree_bitins(scratch,nqx,nqy,a,n,bit); } /* ############################################################################ */ static void read_bdirect64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, unsigned char scratch[], int bit) { /* * read bit image packed 4 pixels/nybble */ /* int i; for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) { scratch[i] = input_nybble(infile); } */ input_nnybble(infile, ((nqx+1)/2) * ((nqy+1)/2), scratch); /* * insert in bitplane BIT of image A */ qtree_bitins64(scratch,nqx,nqy,a,n,bit); } /* ############################################################################ */ /* * Huffman decoding for fixed codes * * Coded values range from 0-15 * * Huffman code values (hex): * * 3e, 00, 01, 08, 02, 09, 1a, 1b, * 03, 1c, 0a, 1d, 0b, 1e, 3f, 0c * * and number of bits in each code: * * 6, 3, 3, 4, 3, 4, 5, 5, * 3, 5, 4, 5, 4, 5, 6, 4 */ static int input_huffman(unsigned char *infile) { int c; /* * get first 3 bits to start */ c = input_nbits(infile,3); if (c < 4) { /* * this is all we need * return 1,2,4,8 for c=0,1,2,3 */ return(1<>bits_to_go) & 1); } /* ############################################################################ */ /* INPUT N BITS (N must be <= 8) */ static int input_nbits(unsigned char *infile, int n) { /* AND mask for retreiving the right-most n bits */ static int mask[9] = {0, 1, 3, 7, 15, 31, 63, 127, 255}; if (bits_to_go < n) { /* * need another byte's worth of bits */ buffer2 = (buffer2<<8) | (int) infile[nextchar]; nextchar++; bits_to_go += 8; } /* * now pick off the first n bits */ bits_to_go -= n; /* there was a slight gain in speed by replacing the following line */ /* return( (buffer2>>bits_to_go) & ((1<>bits_to_go) & (*(mask+n)) ); } /* ############################################################################ */ /* INPUT 4 BITS */ static int input_nybble(unsigned char *infile) { if (bits_to_go < 4) { /* * need another byte's worth of bits */ buffer2 = (buffer2<<8) | (int) infile[nextchar]; nextchar++; bits_to_go += 8; } /* * now pick off the first 4 bits */ bits_to_go -= 4; return( (buffer2>>bits_to_go) & 15 ); } /* ############################################################################ */ /* INPUT array of 4 BITS */ static int input_nnybble(unsigned char *infile, int n, unsigned char array[]) { /* copy n 4-bit nybbles from infile to the lower 4 bits of array */ int ii, kk, shift1, shift2; /* forcing byte alignment doesn;t help, and even makes it go slightly slower if (bits_to_go != 8) input_nbits(infile, bits_to_go); */ if (n == 1) { array[0] = input_nybble(infile); return(0); } if (bits_to_go == 8) { /* already have 2 full nybbles in buffer2, so backspace the infile array to reuse last char */ nextchar--; bits_to_go = 0; } /* bits_to_go now has a value in the range 0 - 7. After adding */ /* another byte, bits_to_go effectively will be in range 8 - 15 */ shift1 = bits_to_go + 4; /* shift1 will be in range 4 - 11 */ shift2 = bits_to_go; /* shift2 will be in range 0 - 7 */ kk = 0; /* special case */ if (bits_to_go == 0) { for (ii = 0; ii < n/2; ii++) { /* * refill the buffer with next byte */ buffer2 = (buffer2<<8) | (int) infile[nextchar]; nextchar++; array[kk] = (int) ((buffer2>>4) & 15); array[kk + 1] = (int) ((buffer2) & 15); /* no shift required */ kk += 2; } } else { for (ii = 0; ii < n/2; ii++) { /* * refill the buffer with next byte */ buffer2 = (buffer2<<8) | (int) infile[nextchar]; nextchar++; array[kk] = (int) ((buffer2>>shift1) & 15); array[kk + 1] = (int) ((buffer2>>shift2) & 15); kk += 2; } } if (ii * 2 != n) { /* have to read last odd byte */ array[n-1] = input_nybble(infile); } return( (buffer2>>bits_to_go) & 15 ); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/fpack.c000066400000000000000000000232251310063650500224560ustar00rootroot00000000000000/* FPACK * R. Seaman, NOAO, with a few enhancements by W. Pence, HEASARC * * Calls fits_img_compress in the CFITSIO library by W. Pence, HEASARC */ #include #include #include "fitsio.h" #include "fpack.h" /* ================================================================== */ int main(int argc, char *argv[]) { fpstate fpvar; if (argc <= 1) { fp_usage (); fp_hint (); exit (-1); } fp_init (&fpvar); fp_get_param (argc, argv, &fpvar); if (fpvar.listonly) { fp_list (argc, argv, fpvar); } else { fp_preflight (argc, argv, FPACK, &fpvar); fp_loop (argc, argv, FPACK, fpvar); } exit (0); } /* ================================================================== */ int fp_get_param (int argc, char *argv[], fpstate *fpptr) { int gottype=0, gottile=0, wholetile=0, iarg, len, ndim, ii, doffset; char tmp[SZ_STR], tile[SZ_STR]; if (fpptr->initialized != FP_INIT_MAGIC) { fp_msg ("Error: internal initialization error\n"); exit (-1); } tile[0] = (char) NULL; /* flags must come first and be separately specified */ for (iarg = 1; iarg < argc; iarg++) { if ((argv[iarg][0] == '-' && strlen (argv[iarg]) == 2) || !strncmp(argv[iarg], "-q", 2) ) /* 1 special case */ { /* Rice is the default, so -r is superfluous */ if ( argv[iarg][1] == 'r') { fpptr->comptype = RICE_1; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; } else if (argv[iarg][1] == 'p') { fpptr->comptype = PLIO_1; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; } else if (argv[iarg][1] == 'g') { fpptr->comptype = GZIP_1; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; /* } else if (argv[iarg][1] == 'b') { fpptr->comptype = BZIP2_1; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; */ } else if (argv[iarg][1] == 'h') { fpptr->comptype = HCOMPRESS_1; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; } else if (argv[iarg][1] == 'd') { fpptr->comptype = NOCOMPRESS; if (gottype) { fp_msg ("Error: multiple compression flags\n"); fp_usage (); exit (-1); } else gottype++; } else if (argv[iarg][1] == 'q') { /* test for modifiers following the 'q' */ if (argv[iarg][2] == 't') { fpptr->dither_offset = -1; /* dither based on tile checksum */ } else if (isdigit(argv[iarg][2])) { /* is a number appended to q? */ doffset = atoi(argv[iarg]+2); if (doffset == 0) { fpptr->no_dither = 1; /* don't dither the quantized values */ } else if (doffset > 0 && doffset <= 10000) { fpptr->dither_offset = doffset; } else { fp_msg ("Error: invalid q suffix\n"); fp_usage (); exit (-1); } } if (++iarg >= argc) { fp_usage (); exit (-1); } else { fpptr->quantize_level = (float) atof (argv[iarg]); } } else if (argv[iarg][1] == 'n') { if (++iarg >= argc) { fp_usage (); exit (-1); } else { fpptr->rescale_noise = (float) atof (argv[iarg]); } } else if (argv[iarg][1] == 's') { if (++iarg >= argc) { fp_usage (); exit (-1); } else { fpptr->scale = (float) atof (argv[iarg]); } } else if (argv[iarg][1] == 't') { if (gottile) { fp_msg ("Error: multiple tile specifications\n"); fp_usage (); exit (-1); } else gottile++; if (++iarg >= argc) { fp_usage (); exit (-1); } else strncpy (tile, argv[iarg], SZ_STR); /* checked below */ } else if (argv[iarg][1] == 'v') { fpptr->verbose = 1; } else if (argv[iarg][1] == 'w') { wholetile++; if (gottile) { fp_msg ("Error: multiple tile specifications\n"); fp_usage (); exit (-1); } else gottile++; } else if (argv[iarg][1] == 'F') { fpptr->clobber++; /* overwrite existing file */ } else if (argv[iarg][1] == 'D') { fpptr->delete_input++; } else if (argv[iarg][1] == 'Y') { fpptr->do_not_prompt++; } else if (argv[iarg][1] == 'S') { fpptr->to_stdout++; } else if (argv[iarg][1] == 'L') { fpptr->listonly++; } else if (argv[iarg][1] == 'C') { fpptr->do_checksums = 0; } else if (argv[iarg][1] == 'T') { fpptr->test_all = 1; } else if (argv[iarg][1] == 'R') { if (++iarg >= argc) { fp_usage (); fp_hint (); exit (-1); } else strncpy (fpptr->outfile, argv[iarg], SZ_STR); } else if (argv[iarg][1] == 'H') { fp_help (); exit (0); } else if (argv[iarg][1] == 'V') { fp_version (); exit (0); } else { fp_msg ("Error: unknown command line flag `"); fp_msg (argv[iarg]); fp_msg ("'\n"); fp_usage (); fp_hint (); exit (-1); } } else break; } if (fpptr->scale != 0. && fpptr->comptype != HCOMPRESS_1 && fpptr->test_all != 1) { fp_msg ("Error: `-s' requires `-h or -T'\n"); exit (-1); } if (fpptr->quantize_level == 0. && fpptr->comptype != GZIP_1 ) { fp_msg ("Error: `-q 0' only allowed with GZIP\n"); exit (-1); } if (wholetile) { for (ndim=0; ndim < MAX_COMPRESS_DIM; ndim++) fpptr->ntile[ndim] = (long) 0; } else if (gottile) { len = strlen (tile); for (ii=0, ndim=0; ii < len; ) { if (! (isdigit (tile[ii]) || tile[ii] == ',')) { fp_msg ("Error: `-t' requires comma separated tile dims, "); fp_msg ("e.g., `-t 100,100'\n"); exit (-1); } if (tile[ii] == ',') { ii++; continue; } fpptr->ntile[ndim] = atol (&tile[ii]); for ( ; isdigit(tile[ii]); ii++); if (++ndim > MAX_COMPRESS_DIM) { fp_msg ("Error: too many dimensions for `-t', max="); sprintf (tmp, "%d\n", MAX_COMPRESS_DIM); fp_msg (tmp); exit (-1); } } } if (iarg >= argc) { fp_msg ("Error: no FITS files to compress\n"); fp_usage (); exit (-1); } else fpptr->firstfile = iarg; return(0); } /* ================================================================== */ int fp_usage (void) { fp_msg ("usage: fpack "); fp_msg ( "[-r|-h|-g|-p] [-w|-t ] [-q ] [-s ] [-n ] -v \n"); fp_msg ("more: [-T] [-R] [-F] [-D] [-Y] [-S] [-L] [-C] [-H] [-V]\n"); return(0); } /* ================================================================== */ int fp_hint (void) { fp_msg (" `fpack -H' for help\n"); return(0); } /* ================================================================== */ int fp_help (void) { fp_msg ("fpack, a FITS image compression program. Version "); fp_version (); fp_usage (); fp_msg ("\n"); fp_msg ("Flags must be separate and appear before filenames:\n"); fp_msg (" -r Rice compression [default], or\n"); fp_msg (" -h Hcompress compression, or\n"); fp_msg (" -g GZIP (per-tile) compression, or\n"); /* fp_msg (" -b BZIP2 (per-tile) compression, or\n"); */ fp_msg (" -p PLIO compression (only for positive 8 or 16-bit integer images).\n"); fp_msg (" -d Tile the image without compression (debugging mode).\n"); fp_msg (" -w Compress the whole image as a single large tile.\n"); fp_msg (" -t Comma separated list of tile dimensions [default is row by row].\n"); fp_msg (" -q Quantized level spacing when converting floating point images to\n"); fp_msg (" scaled integers. (+value relative to sigma of background noise;\n"); fp_msg (" -value is absolute). Default q value of 4 gives a compression ratio\n"); fp_msg (" of about 6 with very high fidelity (possibly more than necessary).\n"); fp_msg (" Using q values of 2, or 1 will give compression ratios of\n"); fp_msg (" about 8, or 10, respectively, with progressively less (but\n"); fp_msg (" still good) fidelity. The scaled quantized values are randomly\n"); fp_msg (" dithered by default using a seed value determined from the system\n"); fp_msg (" clock at run time. Use -q0 instead of -q to suppress random dithering.\n"); fp_msg (" Use -qt to compute random dithering seed from first tile checksum.\n"); fp_msg (" Use -qN, (N in range 1 to 10000) to use a specific dithering seed.\n"); fp_msg (" -s Scale factor for lossy Hcompress [default = 0 = lossless]\n"); fp_msg (" (+values relative to RMS noise; -value is absolute)\n"); fp_msg (" -n Rescale scaled-integer images to reduce noise and improve compression.\n"); fp_msg (" -v Verbose mode; list each file as it is processed.\n"); fp_msg (" -T Show compression algorithm comparison test statistics; files unchanged.\n"); fp_msg (" -R Write the comparison test report (above) to a text file.\n"); fp_msg ("\nkeywords shared with funpack:\n"); fp_msg (" -F Overwrite input file by output file with same name.\n"); fp_msg (" -D Delete input file after writing output.\n"); fp_msg (" -Y Suppress prompts to confirm -F or -D options.\n"); fp_msg (" -S Output compressed FITS files to STDOUT.\n"); fp_msg (" -L List contents; files unchanged.\n"); fp_msg (" -C Don't update FITS checksum keywords.\n"); fp_msg (" -H Show this message.\n"); fp_msg (" -V Show version number.\n"); fp_msg ("\n FITS files to pack; enter '-' (a hyphen) to read input from stdin stream.\n"); fp_msg (" Refer to the fpack User's Guide for more extensive help.\n"); return(0); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/fpack.h000066400000000000000000000107301310063650500224600ustar00rootroot00000000000000/* used by FPACK and FUNPACK * R. Seaman, NOAO * W. Pence, NASA/GSFC */ #include #include #include /* not needed any more */ /* #include */ /* #include */ /* #include */ #define FPACK_VERSION "1.4.1 (Jan 2010)" /* VERSION History 1.4.0 (Jan 2010) Reduced the default value for the q floating point image quantization parameter from 16 to 8. This results in about 15% better compression with no lost of information (especially with the new subtractive dithering enhancement). Replaced the code for generating temporary filenames to make the code more portable (to Windows). Replaced calls to the unix 'access' and 'stat' functions with more portable code. When unpacking a file, write it first to a temporary file, then rename it when finished, so that other tasks cannot try to read the file before it is complete. 1.3.0 (Oct 2009) added randomization to the dithering pattern so that the same pattern is not used for every image; also added an option for losslessly compressing floating point images with GZIP for test purposes (not recommended for general use). Also added support for reading the input FITS file from the stdin file streams. 1.2.0 (Sept 2009) added subtractive dithering feature (in CFITSIO) when quantizing floating point images; When packing an IRAF .imh + .pix image, the file name is changed to FILE.fits.fz, and if the original file is deleted, then both the .imh and .pix files are deleted. 1.1.4 (May 2009) added -E option to funpack to unpack a list of HDUs 1.1.3 (March 2009) minor modifications to the content and format of the -T report 1.1.2 (September 2008) */ #define FP_INIT_MAGIC 42 #define FPACK 0 #define FUNPACK 1 /* changed from 16 in Jan. 2010 */ #define DEF_QLEVEL 4. #define DEF_HCOMP_SCALE 0. #define DEF_HCOMP_SMOOTH 0 #define DEF_RESCALE_NOISE 0 #define SZ_STR 513 #define SZ_CARD 81 typedef struct { int comptype; float quantize_level; int no_dither; int dither_offset; float scale; float rescale_noise; int smooth; long ntile[MAX_COMPRESS_DIM]; int to_stdout; int listonly; int clobber; int delete_input; int do_not_prompt; int do_checksums; int do_gzip_file; int test_all; int verbose; char prefix[SZ_STR]; char extname[SZ_STR]; int delete_suffix; char outfile[SZ_STR]; int firstfile; int initialized; int preflight_checked; } fpstate; typedef struct { int n_nulls; double minval; double maxval; double mean; double sigma; double noise1; double noise3; } imgstats; int fp_get_param (int argc, char *argv[], fpstate *fpptr); void abort_fpack(int sig); int fp_usage (void); int fp_help (void); int fp_hint (void); int fp_init (fpstate *fpptr); int fp_list (int argc, char *argv[], fpstate fpvar); int fp_info (char *infits); int fp_info_hdu (fitsfile *infptr); int fp_preflight (int argc, char *argv[], int unpack, fpstate *fpptr); int fp_loop (int argc, char *argv[], int unpack, fpstate fpvar); int fp_pack (char *infits, char *outfits, fpvar, int *islossless); int fp_unpack (char *infits, char *outfits, fpstate fpvar); int fp_test (char *infits, char *outfits, char *outfits2, fpstate fpvar); int fp_pack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar, int *islossless, int *status); int fp_unpack_hdu (fitsfile *infptr, fitsfile *outfptr, int *status); int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, float *whole_cpu, float *row_elapse, float *row_cpu, int *status); int fp_test_hdu (fitsfile *infptr, fitsfile *outfptr, fitsfile *outfptr2, fpstate fpvar, int *status); int marktime(int *status); int gettime(float *elapse, float *elapscpu, int *status); int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, float *whole_cpu, float *row_elapse, float *row_cpu, int *status); int fp_i2stat(fitsfile *infptr, int naxis, long *naxes, int *status); int fp_i4stat(fitsfile *infptr, int naxis, long *naxes, int *status); int fp_r4stat(fitsfile *infptr, int naxis, long *naxes, int *status); int fp_i2rescale(fitsfile *infptr, int naxis, long *naxes, double rescale, fitsfile *outfptr, int *status); int fp_i4rescale(fitsfile *infptr, int naxis, long *naxes, double rescale, fitsfile *outfptr, int *status); int fp_msg (char *msg); int fp_version (void); int fp_noop (void); int fu_get_param (int argc, char *argv[], fpstate *fpptr); int fu_usage (void); int fu_hint (void); int fu_help (void); nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/fpackguide.pdf000066400000000000000000010336401310063650500240260ustar00rootroot00000000000000%PDF-1.2 %쏢 5 0 obj <> stream x[ْ󘏸o7BӪ}OHaCaь4#$,'kꮞEȆkrމI[<{y$vO~>ˮqrwa'5'Gy)+'vΫIˣ8I)r8RczRH}8u( x# LA_hD.ڸ6|~YY:ThF汔jrawdsPAM*Lڇ?u>ؠ;8`tBYSO*kp~oqrq8&/5Nb{LAS:BI:Mu4 =OwB&#h^VY?]RG }CCQ푽}sP޿ZQ)(LV ;h3#<{aQ?ǻep-'k fltG-nl`j (1zwqi>w&Tct@%@%X=H‹L`5OK -G"ɹ8-pIAP5%̋2:x~߀=!xL$vƓS-]<M\(魞*5!.|jOBi ~X@^'E c쎍iK !%q $HhɛN 8_00*5Ne)dt8[(]TwK8a_̓ '9IL$pNCd=3 wFq#YgOHƑYzg>ewdწd>IR]`HYV6e8dyY_5=*S.T90dSjmJm32̘\^{6aZ?{9elJ WL{9mRZ(\f>?(OerwF`j?WR/dߔ> +Zf;VMuY82"{ˮuUv:0﾿ %'Zx(d#Q* P]P Jj Ƕ3Wݘgf/u* W4!M Fs<4n+Yy -#[pl.: k4EG4KW&m綍{ }#nY^>قI!DDRk"RבKB0JB>>b^* u1e#*)*z2?2jzLhn RTr^T(LWȿ!3nN@ ٺp '/ĩ|Y[m@\be\X&[H'#rĴ=BH >ȟhR3)+vƱf|g8PkguSLH@+ؐ10A0MΗ]6%|aЌ7i897PmC$)b=Q:壮8t Lt0JQBCMéLHAPmt# MS)Sҡ[a4IQa,WOśr"S]a 7=ui,)MV+;Cwe}Wp@).-s]{b>yڬ*y2Rȵ^P̯m-^Ľv'zƱ;勓~i=T5!X 6 ˣ{>뷏Gw{x<|/۬hJQ&`TT],C0YN3FY &Ƴt?SLB(:7ռs\Pi~mfmKz45ܲ):dRQ|eBMh 1dp#ZﲩB2C@hLeU P]zycaN`YlOЀ 5qQjГ7@ w71(N\ Vر4::2gbNyJNԹ0ߩś8$WR2І+BrX7A %+bD|69 &Zn;]jqQxBNirY;/L Hu]Tj0]DKi>Hc鬞JNj^:BLTt{1=B^-Ͳ"* ϹeaSnest@r!@iaI^巹Zo`;;ࣤWcWǢbYFӝ+?$rQZc QzAM[[8J1crqYA2["Ŷj=$I(ULdž}V:[ :qE9h̠hl3vR+j/;ӈACsN7CC˹ gN1犬z!V D䲣5>E$v}S:Cfeq>-[aIOu12JaxEaȎh Y UÀ. eʶ(lbZpT7>UºB,B)C~s=WX9 4 aYS0.Ǐ`K5ܳJaQ!=dh;sr6{jxBA(8E+|YѲ> Vm*4:6^0}]Sڂ;#9dܨ.1Dxr!ob.$Bw<,…7*nN^PjQY5fۇ]BbB}(V(UЇJ^Q7.Mgl4G䔗ӄ-JE?^ݬ8eބZ׺b@jUIS(Lcb@]rTäUp/&=ê♿UDr43}-[.G OLC"SIZSK&5I9y{xњwŸWE~($@r2CIQ&e"$!m>^% YSچrA~Ѱn~y ܫ%$T 9& KbӚWs7wv|~LI)7v˖ti@e'x> ݚlwd 5^-:?`MTH~o6$V:| OM [5Gȑ2\*Ĕf]gEW&3Xkz[(`6RYAHנr:.W]_VuTՒ 5T!j¤*3Jo4ݥY7*.Ͽ΢|pyKGξ] vB!h"}844LU2fUvNBu'U8)a{Q݌X8;V d5 F=364{+[ЏG1s"܊dXMOj֡UWR4Xt ؤB2K> stream x\Yq~#q&BӬЛDɒpXz@.D;ꪙP hLwWeeQ?W7Ͼ}&ӝϫ7y ~٢g5yٔ"ngs٢ nz0snZݜv C>lG#ct{Қof)ux:>!xm"@惵*Mk;M)%'*(L:QiҸѪM /'=J:-ߤ+#lhQAa l,qJnV̩zy^4p.]= ih9?J Ң>,S9sx9bb g6{x !v_}yQM{'5ʰA p\GqĽPyPfmP^#c;@K gP8o7)+(Ӕ6P?&k`C?C!Д|:o%Jq'4T'C]Q@vԇ9`ΣH.t<\? .&Ug7_J1Hqtb! ,YA0WGvMhS]S8n"6;ByOn:jb mg.M(|2$նm##]|.)-B(KsA^:.'ېȖvzskRBawtI׮k=`d 6*Ft|qn27r'%AK(˜P))yR l3kBF[iLvbl0[:%ݒXeH%b0T z;Q Il1!8E3V(셺`n.+F1=D&.+8uckv ao'6]f6 _XׯXxhJ@7*Њ*E%m9YQ芺W\D=0 f¬=!6@#<6O`|DgwF8&p{ SsW!:53ε߿35hYe" MVf keGV&]UGƙ\dE ^g4OŏUK͈n'PcavҘkGl8Q̋cpt%H){4ue\M0Z~{D69 AhntN Ialدh;è^߈KVS\p lskn=o"2#YsʾI ݕ{.Au7i"S!NBIkgZx©z4^H~<ztE%4%B 4ڥ)] \9,MKh//D|Թ0Řc25Ֆs.f'5H%ۧ?v}Y gV|7#2!,H*D?ࡾ1lok< 1`3`.zNƁPβ׋;[5q8sD3Q#ROq:"t ]D*Rt}2AQE㊗}xץ*[G] uKn6gp~>;`"vfINe,ؒ}(URJ:M_baH(7мOA9w݈*x:k) TĴ`.3Hqۍ6{:ic=Q/*>Nd4WϗF7 big4gR8Fh |; o^Nl-H HXFnKe&oQUxW,¿O{DahH+QgC${>*)*" {ybRի6J`XXϣҖy'~%ǝ@6?4$5 n5·*=X͹P!;),⺱s58xu;nlf\yq5DNxf]KV1GVK"ѴF4϶F~Ų؀gAؕ=m42u&j"М`~XQ1d?zN/:GhƝ<ȫLzv![n^%@KspFƦ)Soxi.x+]^E)>ֿvmÌ.4'yO'ެ#h@tJg:]Ss<׀Dj5UDY\A*:}I^VE ;;t*1jYD^x79h[S㥠JdwV)r?>R4/zQ~9J0` S^nop>ylo zB Gg).ظW ii](Xj6zڜM~  TBxtlj>ɖMQ2Ӧb:|4e߻]`51]t5kLG,L.7\ʾ3rSR}OOH1~'|;6s 9SjcobriH_1$E̤9< 8;ZtT|J$,@AZQ;Oi +\:w0c&mP_as~l <;~]yk\5]Ï `(?J't}N66ͲTL挌t=G.*8qiY|\?G^pnGX#rC"4Q8̗2Zؖ5Q/EC'Z4%k&/Yi0]/#8 v()iU.nY;l<:w̌t /#u.gz2K,N( g}8^5 ϝCjdfԎtV 7>֣ 8ԓ ?I "%Xe_'?/R+n$Dhk1T9ӂ."@Ur$;I= ^Raa>10 O_{\U;y'wCWua4M|{ }e L&d2Y>JޝSJ<Æl, &6N-ɀ|31|קYB!<\ r/JDŶɼjeOոbW٪sLS׳B삀\|x(О}X.6EcV؋IGS( GVTq>vH0%Q43%n]?y>k>;bKm5zI(@n!y:M&dF"[nϟ!r{42p 1-ɝi>R&r%>k(Sפ_ȕ;^d3nNq`O}$BxMdM` <41Bax|6BSa<~x*M߆erT%_kbä]T&~bA"vՉFwż%Z(/R\v3)U_PdtL}L/hv/R\ Rn8N|ڏ$&kY?^ 0#Xb¦!hDt!7} yT )PL/owS9SMwG, sGAcc' -Gs(Jwg (rC&Yܮ$l89PJ=,O?5@9to~|"2s)#s|T936;GDڠgM?;8-LԯM >yVj OJN>ۑ';cS#dj3 k8L4tPruGfżU\jd,V)ꭟp =d^3B֪ղQ _udw+'qc&b<&D'Ns ZH}ȧ~dI=zdo:-mr?pOē/:Bo(GOrM[h> stream x\Y7rb_: 7?,ז%amh( %9#Kw&*:6.D_^"r_-{W2;iy}M@d"+r-{5pJsP@LBWrW`LxHV⡱ڽA)` ׽Fr,dRt?CD-I@8ٔ5-&W/ '˓.3\GŎ/b7IfͣL,׬j'#rUک+e(1mIyrx۽1ԣ%'fPEKYU"5,+3)$ K<6[k^>vDW~uAk=z@I2ɢ A8 qR\΀05O^Կ݉([l0>#9)b!3.v ƅ.O5.0rA{ػI  3n'5Ap v}tR. ]2X]{mYbh nMDo3#` *OE#K&l"x] ITVZ8D/p+Ր|ވ [D=(zaX|Rn'|Mt11]2KND]\z4PjI+ꘌM!7.IhtOR#ZE$PClt8w)8 i3~֪N*YBy) :WĂJsI#$,kh*rBqa'&Bc0жVqe);`,y)VZ ֭ #fyZP֛@WD`FGtaC8&ql\0B,T`$0(>ei`@j$~tECIc7>WqܣF*LCE45U:TcKMvF%Vپbm_s5#y N{E8A Lw/C16d|ִgSS&:|@ Z9_yxo(9Az&0 (Z %L-32,8h°3%d$?~5 "1j7IDSl&Cp)ŒŚ@1<L/Lbi؉t/)⦭SbLlGMW]`]J9UGcOz/\g*% tcE]12{dhbzIAe$:MU18q!P !XQyH5MCp;ԗt5]lzk';}_}մo&vCD\0-Wg$8!_d)9%W%xcobeBџHVl9ęMD;׎ `-E52@ABm@j5JH]M(/܈ä{PysEtba95a/B {0/fBQ'΢o' D':9ZAmyZ,&HɡeRaSagOs~զ9[`-IpZ&_ %m@g7y"t)Ӥ +OΞVkOHaDC A%1`*;s[޽Mk"GZQfl{t#egKua42:HN)4lIn|9 ,"򳳋 N]Q>,H +8 d:?vϸdUbH0( J0D_fP ]?``a~Y|IRY!?ٍ8 45VI .@CmzmW_|"A,`+O^<~/w?}s˃xG'> ;T^}p{УźRƴ/S,N^5rX<`-b.@}!+TYbD V5hNa! LBwU 2ё#PsZ[mE]m IԘNGiR[gg0 oxQ#ld-isrS8vVY4Y$Jr~Hidv 7G_5VM,ׁ5X@{!=JH}&]}1!{7hUVrJi`hAR9FXNJZ{r1,MLUfgĐW1į g\'_/Gxrn2'SYX\zhQgB/ NjdǼm5 ,'H K6TzH G!KnBZ OCX)Tg#Q{JgHTnte*gOH*oz[sD!CJa cB-Z~:]nNYOрRkgzcEc-lJh6c e NlUqHɱv]4HJ f6Jn::"WgDOZCk3Jy!bE'T|mO?J\ywgjDvG@dBP?K_ ^Kj{d4(p cKXKT+]a)L & : p/w?{`x{3*d0({/;͝'U*3!Jʐ)!ÝNh\˼~U1<54X)ir-fV݃OJXʹ8g߹fG)[8G}r[Ɨd mx*`Cy;`\M TnI<4 v66B?"Xt&mWC/M$ڿNư5A vV:܃uxC$kl jԔUgd!ڂWUhGѾCexSL**teȐV}#MwW]*[2x 6 d,=$/=_+/=Bf%6 }W^i*RܾWh^-2p*d%F@) N5z X KU#)u>(UzsC:C&}-mJŋNZeΈvJ1*A:T EG_=ݎ7qvRKc-c䷉b:4Wօ=A.Zs#T#Q-F+[yYA`}B ~'JtqzJԜ&@+~@]9v_5q+D.x|2d@teF{RDGK=OnŐ׹!.R,=%{JF83G*zOYb{A%JN@E/qש?22̵ >&<ךH\lXث\GYM[Z%,jЧ^VqKx-wZј-  9WI۾gШglE\]}c1Y\}8rpɍXbA I4V6Y ,aCbQ}Flّ G2E3{I`„ 2E<P.(Wr a<2:MkE'5_ aKi: 7s/4eKcL[=I~,2^Ā'+fao-ٹ`uPb^NPú" vSɷv #SorOY/1fLI (:GiT۾e-,2ArjΟXCDZ:u(@3Q[UԪc}T{:` rc?y Kyw2xV!b)2'{Wh_J:JC˞f'%5+<'\&0 m{ JikD*e xy';xi]g\}-ŅDž0bjC &zQ~s )hgsvEY ιŌ/'Nbt,qendstream endobj 24 0 obj 5598 endobj 35 0 obj <> stream xK6I/ܥ[^0h$FIw}~]Xn9?yo$o~xGÍ<9>VK1s}6Nc=~+Y'7&]q9٣~5>m}7`Y#fK镊okm|̭o=SIg~$\ܺz?qt}e|{}{~9tOnj[>ur=s_ܝ6r<Ͼc)1.=g\O2F<j^vL7o_xqf`?>!˟Syj[N>+Ի+ y3gd>=u(͂:Ir_U{upgi_[Y~+po[cW!_6wVo ;{ީw*Wfţk;_빊++5:+1U⸋7 /㵭b^eNq2\-7'?||퇎7^ORw{"EERnRW+8{ˤ!3K2{RrNc[?sRIlvG2'/hm "Fzt9'أ,$)("'Iq?Gcs#qp5[ ?r#?1ۢq3b=K(h?(B?/ Q8EhnoacN} ;H=TQ˜EZB`?l9E蹻d98YjO$(B2(B ]D枃(Bkɧ,MN:ZIEeOB`lwFϒiQ*s)w.Ks4{|4\*<#ϥs)z.ƞKs4\Ks4+F{s)~.ƟKqP @Bi.FRiAJc8ȺmSOM<=Żid֭),kEv[%y3Ș9߷s'MͦGsJx%{55!W8G\0Fb X N|gzGYI68KFzr?M_2v6=^һՍD_HFശ!Ws=Mۀ8SJj D^^;{.[S|4]^c o-w甛FS*I4W=4Jm-W JP}Q űNL**Ҷ ZʾH B'!I u JPa?/%P;c}<ȠZ2 ٵQ#el+o#+] ^ܥP.1ނrMp JSsx[߲x{vCpG_#һy͏^$Փ =\UM()v2 }7CFC) {Ƶ#CiywWǍJ@C{jhV톆ڷc9ԯrPkeu q,Yb<6Sڄ\AN{hhSRa+(#tl纝3ŁS>#% imCg2G>Ԅ`~v¦ƙ/4璩Ku7w-hyT#$d'ipZ+ُJfh8׻h~ p+fGy˜s=_x]Ħ|IAH{eh mm BΔavgmm JPf_c`t%qΏ.E3n%^O%IU@z`J~5TԆz[ !K?MLJCFD~~@ܥ$ Nkz9*pԆ3 J<ck9=8Kpl$oVGbPgOH[z940ad j?lnmɻ$8ޞvyB DILZ[οKRD5s)l{ F0aix"Kis.knɗSXڐv7mE6 r w6+h%Jhm(ӯ c^CCWpJ?^b[B:6-VNJg|TCBcת ԰]6RFg|F KdqQel=98c#L/{Oiժ4ׯ~uj#WċQo 3i 71:OoBv7f+ȶWȞU> ee?M}[Lނ;ĞguYH[3mmaF_+\vCC!dߜsjE%e8+6;5Zq-Rġ@h =74P~ť6+_Q j?ܦ\ʜVƦ =jN@4vӓ~_ ;g4È|=!ـc' 7j'2+jy7-^f h@I`imBGJm6±؞7X8[}MMKA~OK~trh=P < @ H ]x D57_P$po,['pd N^IikC g?ӿRJ F 8>Eq][rΣ>hǑys9\:\bI7U6Wӱ [af F(qkӾs0i+1=%_#OmOچ !% }MڮӾ[j?0 ,`=tn|$nÞ 9H4L}aߧ6jΙ%h5NhM]H#Ԏ9Gл.u[uv3[5Zik.1lH[z9K, c a~&`%nhC$I{d}%NvAr&sN%^d)3HVz dŧDa!k9DfKz!qز_NCdx "C"K?8=>1$M_#h^$t%>f_!4nwR?=Kpm10K_S/ @_0!/31WLRtt8cc)<&3:ǔ1(ZH10cMM2r Uc1{ c#8FcM-hcKkk[/@WuNn}H Hj K Kc\\b\-5`tr]&RC7]Q1r2"G>GΏ11cxH 3yHC+h5@K⍺H戁Vh$̹ O `hHVfM0-9FN^) %Ga]6@x9AF Pv9,[^hE3r<[]kpaa< <6R< V_]l <"X>|o *C N7"#1=4 e ZI v%Yv7G@ 99XreVQV V>D,( `( H cL_gh:Ў‚c 5cy 0Z V:[ܾ҅'0|2|!{oHv?䵒x%F+VC˔ePm8nMQTC'1մF^=9|/9|Q448B ~hvGbꇍqxH004W2\]R lf/œ05x m@p ZPZ"+3( Ì88S ) v10/PXꁍ=Zx2!K`S `~1m `#p=E8%h5TN]'9Lz< 4VȒ]B՝48!wv=AáhϷ%`%5~go,ǾL̂_fN-KBKO98sQ>ކ ~T4#2! ) KQ48#444FW@Bc {Ih7%(>9霣$Cj B`J'JTE+GŎa0n{[=btǼj Mmidf mbwڋB.o?P{0'w:I!um'p9í˝PNk- #nn`[4j' :aSu^-힏bSb{m& NOlR n5lhbEVBCk۲㜥XAQһn]AK>  \sdJ%A3a[XA ZI n}/Nr$Q}vfǼ0VϺlyBKjhP%0Zۀp!c)9g V1.J0F  `#4XM nNN4_]sY| %$n% NTcYlYјV4rNu &&;²qaOqi3 FBj%`k(2˝K13r"f3%BF dĎfzy 00+?2J3r0p"fv a.HǦ̠!̎q3Zp+6+dq3p2.w.He6HfmdH].2.Nɐ=dh{J:{t BѾ~.I+| M;12{ץAW(_- WRoG.Aww9#Rm?6bx0pF:ggZeQ5 6RaN2. )`=:V#:H '@ko,Q˚Z0WpMACdऽdN{V)i0=%S%3N%S i/QWKVT67]ؗ ]SGI+b$!P@"m/ߓ~ـ P;K`>sN\[ h[Z'sZsXJ ߷ZSbHbW Wݣ&OSsQr"JɏN{shc+7P{C 85[_') lg̸ҍр ,Avޫn iswL]ylZZulkGy6'7cpy VP­)w-[yح=v@cp40v ݄Xɱ̳} ?maW%@cp41 ?F!3M3cxL]0;U˼M1Bf0f C)f+\V3d;Lker Y8bq5`(T9L , P  #(0hp6 .A+J?$FN$0* &fM`4:'0.h%[ПoѸK8P#Fu^J4ҝ%;AF1R^?„& 6MًDGsd/$ً#{^:xV$6+IF.D6#_*]HZLr GПد$!Ϥ2r&x@o ? PQ#d~F`GȖVBQ1hx؃LpN82X!ɚ%# $V$Fn+c#1ѷy#1ʘ0Ob1f1ƶ21Rvs[YKuyVY#[ПGJehtNeTNJeT ){Y|-$QO$ȘQhVOIrwa9uar#Xb(;`[f+=ٌ Qf$'F02uXΐ td3lFhlFQ$Jf3~x6flF885Y7w'ro, tgAC/H fw$4~xBcNE( qY1.4F1"0x=Y{Ƙ̸d̘h"R#w11;`Q֣͖nmH,ݖANT~!{tuA-ރEQ0oA`e R,EɉV1޷Ev} $ΉXc! bQTࣣP̰w< ɔ. gH.@dJHfB2hp6$ R?^=9\BlBQ>(s7y}O+YE"9[$(c"AY-EޢA[4:-yeJ$(E2JJ-~yƉ'Z9oAe़Ey|[|4XS?r2Y}}O Vġt,I}ϤE־E־?c1(G"X$*Bir Zކ1`̭@-)QX=+Q0VQ/I,Ykb,~y˞HSXLԺ'Zx@J1 ( 夣=9 n1E ݧ>V/=]UZ9آ[dmi3՜i3xyJS<^{qq+D\~`hNTؘusc{B,i쑥x<%M蜧OT4h 4E3 e)&RL=,e),EB0$":LEBRIF"9LU$c6"X$+Q62_ƒb)Ή|gЈiW(H:ɉ|E Dy+^w&b˶Wm=m,m, {覰T6a"!%l1A3kf$\<},8v|kbކ>VP­ڏE\YŦw,rCZ\OYEK֢q 6R"HYfwd-~xhlИrRgBwЊi VCcJИqևjH>xX Ⱦ$.g7!ż2.;>JZ$8*(3RaY7eESN2-1MTo|gjDRaKn.~ 1d *3h>,-Tq`[й ʁ.jEy"Uar鎭9N=-m6BBJB!<gh#3XJ?!K||Fxm ǾG%FA9Qh0B3Fa)ԏEVִΌVjlfü輯L9 Y@g+;[EgjeBgy%8ak<)QpR #`#k<%e@:Q˴F0%R<5TR#F-’ ChDaEJ`T+ScV&zXqh6X*@)  j/OcD)VtXYGqAd*טႌN.HH89ƬkX6{F'%h%5~xJÔF |IiaҮ4:'Rs-|o#4ŠҸr4vJ)xb4 `Njv>+oNy0 <-Dn#MF҉\~q|Ik|rَ?д6S +2x5^9=@rZ2\7v3uTlsJe?Z Ιz/Juex G I =Kݑ٘ChS?۵W+s.!ʻPC}+h[KCbX)UjpZ+iVԆџoa%Jh@|#xǠ_紕""O٭7)<4s]pMHBCDQ"h:zY_$RY$RHuLxIu50hL^,H: 4-Y2 4Z:e.$VFx" }"QV$9jOrÀK\tIr4SFIrE"_~gc7GaM}$AQ89AGF JFJ?{mܶd:-3eT vYyC(N!H N7 sGjhmVPVRCM]xz.W{3dkiC7v]9s '$1]}o05NXN]>rZNS.@L9K(HKK1:I铏9:eYgZ0+\­۳4Y:Ӛ~&z֍Lһg[ҊD@+Y;4RFn74~h߮\:\Nyu2Gwn)ӔJdz)WhhHH6 "66`KJj(7eikDu]i?Fͳ'eᵧimC0!%y? &΃6/![m=tqeC8Ь$YWHӯdh^;ZW .A wK7u{Z`յ&i]#[ uB~sȷ54m 1YFBAk]w OYm3nZ-?MA'f58\[<0Nކr۴NʜK'cZyw{HKF$~+ 9-{~yހrM 6/hOYUvl>*YFv@ZkQIB$ 퉣AԄ*𻛈k'crL68 Gά:gdGlK@+yBXPRiѸv†,+K3_ ^2N9I^ca#h}ʸD?e" NcA(8SBřhcb&p XI @.S_Hn[ժ/pmo%vimC G{.m\$h%4~K<̙“q.m& }e TߡÊBGpxC5ф0t~tISt"צ՛ze>4;K [58ml6T0Zۀ.I h!D>>a$aAi18(h#P  i$+q6Hw\=hok } 8mцn74~:-5s%sz2PC!#NO>dh;W x-|sB&BC[ }Ȅd뤷NrI*$iL*ǔha\\|5bhaK\SP'%aG)wFKA6l Q7 K N7[s m|s+B¬t `ZMjj;gz`\f|)h/8O `/Dfy7ip4/4+w'n-ɒS$9$5c 0#ݻ̝{lYh9uH󴅤qaÖVtH*$ef9- o XJ?N§9y}L,9-^Cq.}e.wҖۜ}~MHqmvyѸ[c, |( N*$j}|N/QԎOHUnﴍ1:pf~%ΆWà:5x؁?uMZJ4@+L0Gj`ư\\XCWؗ@˶Z?Wz߹KC_@zڎNSGJw7Ng;Ԣ_t|Hb{{CA:JYjmm5چCO6+`%nhpIDو<3nKto:5Rv9 MA6>\< +]>q_mzb R-1't==ipJB/p XI gþgrq%ط]GygrY7,}+^mQ'\zܦϏ3)0-<BZ\CZ#Mg  Kƫe_c0tRur,@m,1톻G!_-ц=EŒ ,X-/Hy iٲK Z]~XHtWcC54=+>0P-KP2( p9b\1n,ƘX2D|8TXp#l#4h%nhmTl <5m;|h01෺iǖ82Gh#aKXfeh(ci\[>T/h6ۃ7D\Z"48= {@huYa[XA  6< ZpV_X;鍴r\84THIplWhXZ$XCﴂj?x03$63Cwյpձ{~?a8$UJPi0"r왷\.VJZE8ps؄h~wqyuLFŰqdKmu @ lIBJm$ xiA2ZррegOn44N@<\]WBh)SӉOss"3OV"/(~۾}ؖoʦusN+h3IB U N茦M:EF+$ .-qCx|Ȱ;K݊-ć@9ACs!XǛ7b"޼7mpv{[⚮-*3ɘ fb~հ,ðc{yhuvEf/@zI;G7|[7DFn%4~`j8Öȱ1]‚ԽԃF R_Yj Y­L$B19e[Y<ޢ՚0fQn^!hԞOC 3BKms p Zi^19tWptFtF:m4 + 4$Ah4DmǾl<]+Z-p^Hs5HppߥEǙ@W{SCf-}8IOD~`YB-ip`j 9vSh*+ %?NyyҦErb_?,E~Y;%Ҥt,;ԠKllߖi.֥eK!߷vj @\mr O1 t3_=8:e(dKLm0;t/;5+s4Ls֞G3hĮVh滻ݶtgiQаXa4*,A+%ȹt2&-EȨn{Υİ|jps.Ij@N%KڀD\^;Pم,V- nVQ%UHkY 7`4G|P;Iý_sr} =?VZ4I+I/q& N۸8t  mj?:4_,Fb͸\ذ1g2]@bôZ!/?,Di1.=x,V7lwb19GXvoI(Ao~~l-5_N0C_ Ϻ%%|K qϠ(negG`!>;Hu!E1D%|F48>C ώj?^:r|.$2>sgZ4"3%h%hh;5} o؇="(ĸ>d؇6NMXJ? y3_c!Y0\[ނLrd )ԟD4 @c-@y[e@+;.2i]PQ Ae8(3D\h A23 !2/!ԃpI iŨn #2Dd$Y7 ;ނ`K> ̂iVLl0b>$|F_+Rp)-bMp  8me>2[Wb_{^c1H3#'aŞغ*KXW,zb'rDѱ%e90q7 Hb5w,C1eaemg1+3yfXtFb+$F+ ^E 8.g0NF:eD̎H$sEn"#e6MD }Dq)F*wČH]eHٓ%~X,#e2?#e%b4 #eH-xr,p#XeA) YGʆ(~ ) EMFʬ&HDFХ2#eQ҉?"eU\{FezBBe+h%+3*32|v% "3 G%Lc2VG̅;nKidBd`Y{|cx_2G@F J^Y~dVQ:* im#2y+ N$`%5~8 w%vnY<y3}TcONYo?X n= bu#10S& % @' FaKf,Ays Z0 Jr§E+Mi0;3 Vy8 3N|| ty0D[A"#Y蜭ha)"=5 ,uV9 ?xxZ0:Ov}ce洈AA# ֊ uf\8kYK(c|C1ƅb^Hl]~9i/VS4QmS='AM?EzHxͶXݶ)WKŕIŕܩZH &nfx D~xشV]^kz9/^xRx^|Ĭ !Ea.j)I^-ebVP_R"A< F9Ҙ['aa3h%Nz4tDȉhe4lxg Ksãq:5}YM,'q֜wMt(G)Cm}ܡ_^N$fU8Fcɭ-eB@6CCG`߷IŤĆ0uÌN0F0l&H\mc6>sjs&b XaJ?~?07}oƾUzh췆ƦлǽsPRqJ w:yAw6=lwOݗNr< Zo`}zhQȏ=Hvq ~J'}P;W6s_x/u訳xiV84RY(u,A#` Ƣ=[ ?'R%fcX ZK.D)#[lķ5'U= E|Cb?X $DbVcH%¢#1X$c@b,hHXc1#1+HR'~uq+,*5*FŎ* k,xEhh#X^c˓߳rǰ2n *Ǎr,+O_JQhh.پj>ʀ$FQش!šPe^VRԳYR%Es%EGXh!a0^G=V+QAp~QStkkZL/ֿ@{t:YE (N|WPDy3rgO,,YJ?;Ǒq7}{Qh3&54֨xXxXg<, ݳ"ԳbǷΰ*bW04vYdYb5s083G6ΰ%~,C #fz,r8Đ0a0$f9=Nb H K5c0 GAF06 {zs%tJ(0cC^6?@Fr>` %س5c[\&MW_y#hY$h0Zl29 ;]D&"agn3S,P ZH00`XWg-ha"4@ؑ%NJ(aR°4o/02<ݯ OAR%k SJhh6VBJJhxGp ^ƈ J`KHxKhpZ۰'j +\.zK? `EDZJ}o2 ]R87hl#F+oϾ~;BUu@r%$+L=Vh@/ ga)>DHmj@Nqy͉`uԚj9v_H3}@!Nk DȮwW ;khHm a_^z,qK0%m:2:D24E j8n%nQøz[i.\g:(cg3F]"7 `y}f?n*;vsu,,<:uNO{ طUjW}lچY5~XߤԿp9دv8&H|jʭiTc#O;ݬuݍ|/5o '_!ɉck(tǛAծah XA ZI fC^:V??m9i{wg&禝'h5F@:Poˋ)|e͝wl"F\6U O31]tqshS탆晆 K{tdX^^{P y˜Ea[ \A%=*i%AYWb5x[C5 h,15dB1X MvnqG+ $* hhѳXXķ}oܯ Q{ʈX?7X@\Ab/pۇkЫqV4FvuF@ Hvظ_јzA) {;h,w`D)+s.]ڧȷ|nKOЀcwXB@wj A950ɜmD:҉aRTLsRTX/Q0* }`/Hb%$cf">=NKLk؞8lk ` m$+J`7/_[ιY1ydm3h$"@Z|K6 ]VNΩߏׯ?| (AvM`A&?N@Θ;!FȨ߯qQ_::D |Q"_%WSkb1n bLy`t1n bG8v 8Vsǜ%fG\/ϗ /Ago7hHVPPX7dȐ [&<(ӥʌΠL9DU m$X"Ve}tDFtD;8yE#R"UCJG)hHGKzb:⛦#<( R(Fg 5Q` ŴNEpBΔ. n>^ξ 8#'췅ȸe/cD|l-b8JrrL8)>b[XQc#ۣATFN2͊LL!%T6gEes93* Jҏ!cXd? v&7- < dq:Gf%O:a3rI{&pF J'^Q#RW2cScX/{"G蜣Ƚb{ŢxoElŎh+s "[A,@d"S Zth>KNQCDf"";aEs&Df]AYVԏ\4Bc*Rb5(vv&G6Q|RڟS _خSԆrL}f}#g۹`\+XnZ-mVB ԠYфl4N_@7j;sxYd?3`W;!Ư%mU{ix}Ȅz#1W|{صs` *p=^\M'HKNޓ.r2~52wZKA!cg grh1̤>m3Zn\ChkC Gʡara^+(vCCG^N68bZzEY sPQ֡55iK?^`|~R6o~Զ=V~2öӶgi{~ڶ[A u~؛&j3j{\a]zh3䲄9w' 4;لfoFshx_a<}7u->~.#KEhpW'SyyR F%`#>X$_SN!Rrzi8cJ¡A3?/KH N"ŋy)T4%h)HГ)_I!6_<|v`o\xAO;GOqΓkqqP&أ#|ڃfI5 u dPbsC eFn%4~Ԓ۷z)-x,h\h%-a\.{I Œ>u^}OlZ"MtxHq [ε^^%$)jpg&:;ћn~>X}8#8rJ8] .+~-O8E{֟j mm)Z6+h%nhegt[ߜsC9!ӎ_hpjpz࠲5ؙ1ц P=đ#Y(BCq?-WDh mm蕡P~a[XA j?зi-L LUոY'S۹!1[Egt:: ˃TOj"N8Fd X:euYQ >vՇ7]Y8IS%xe' *ɫHGgKɗniJ,f]{#ٶFbY}^#i mm ecS8Pa\A+\vCC퇯tf:N <TϠ mu'WcE*\$Ҧ&| 05.j'0 }=`D='0=`tF~䀑0?`A!4I6fHl;_B7~fsxf3yf|fsCg6#G6 e:}WԅzrVʏ}rGrdqv`5y4[p 46 rp25;m< P] c}<-Chaڸ0Z ͎l@T;y.]nV\Ѭ2ޒAK0U+AK+P -aȑd.+]CK81s: (a8Q(x 5D-#|R?l(ʡz]2f9cYoqW]IuwV,#cL%p]-c{TPr.yy-]7t9 48}GiO.\%\JJhb[@ `Cgp!Ă ;2Y1!64bBj*`AJ&/` OjN4Htl,֤OF}w 9.DXi^73Knn};$c5~%hiz`h*Gh8UEFA"4 /Z* ,,,,,c6~`T]*X2d˙"]&M sK 8j N'^m Z^o4$D.Dp0R2M( Ds6_:iRh !_4B}%k mmX0ca4#gPa=ֹs!0MLc9y\Lo/j-r[,}1D 3OQնᚹe(tT JT^b &>S*=4. ?S* lWkW✖tv6Z"޵gCaA!@`ReFn7GNj1D[q?lvo?ww%^@:~\Gݏ /8 ë~!QܫXa+8@an4xݰ3*B+nfٰ 영tr&7;2J0K~l `>-MF,h mmLsrܢ gn74~xln<1-tc.Yiu'fqFjpz =?8Pqb20$n%4~ L̝&g!$!p*Q6u AwZkPc"[뽁d KVS5{gHt,g@T9fF7Xn7K?|StnFy8}mV$R=+͂lj,O0͑%Jhύ2S޳2DŸg .kخRFhN[5e7yddȔl/[ٌL;(p{n|NVk׭F/{$ksk.fvG~zT8 !Q^Oқv;xvl!( !o 1o>~+5tٍ Ba)2%Pia jML=akL33#2{!qҗ(X'Lq B]g\)79v9b99Pq8+2v*Cd2vc컺zm9%:7:Ko0o(Zn_Lr/ ]wqO$QHK@,b3O;w s'qP*sD!-ov0B1Ȇ3ĸcLqe/&D2ho3Bc ,-gDQJn46~Zn$4@rvC"$.\+<srGjڌ E!a7Hѱ/̝GGlC0v fsrp:p^8)%9v'  GNlăgCGX^(v$4apha@/2g$0$g(.6U,ֱvRQEKEa'HjtV="=FOep:4}w[q0aW\KU6 VgbA_($"Y0)8\ [Fq<ځ{:crSsgϓ)x=NM$Y.-D|Q'dvׁ@?L]Q; ~PHx-or';svQ .֩KO` (Nyc*#eGpRi9+S`Tt~-2@(qMkՊ$FV-š`vidRp0d?͊e[7c[5*c{Z ]g࿯VeFA0e a9qXϏE HF}Q!3~/G36jBqqQ &98]" zP7 |sF}fL׵p,S<T~PdY >U*L:κ8eP QHKqm3_'!w ZJcƗr/{8ba@2r0(H+s&܌3[ͳ?p Tǎ#PXp,qRDp B,)~`K]\͖l.v9`qm~N-v^,,L&qy̛H ukŻ ,9̣iL_8/iK_8:| J q8Hd+-LA-aL ̝tW{qܞ#Jc 2r0q8d`>`Aץ4FeuMqgy2™6 gsbÃ+#`/qXH0_Aץ4Fő/x1՘I)8_-H[^j/GOpmoLAa7/O9_NjGl_1m@>9fO8Q"@7ҁשVoj!"S0|/7oi'sƉyo,xh_WhogyYg pL::f̊_33p1oD煂.!5+7ddއ>9D#^ Շ0h7N-VF +Mҩ~KGO6]1y c;$vT{feoO_E]\s0|t0=siɆ%pbYHd&o4?:n9"}ǟ( oa\`S^\N ov]1=kb;=4bѧx8 \A̓]>332 i! i)HF7saPr1* `Jͤhs aU`w(KXALAa7a- |v[,f BsP gӶٴm/mہw❍$ cHf姮gɨo.-uQCD抛8_)s&rpWgzp;‚`Hhq. 7Z4fLpYh` (tP?|fu{ԘGy#u0}kгC="`#| )ZBzfG1=*1-BDbm߻ &C ~ ~/ƫ b8^~e6_lk.|!95haNn9r{0-8Ypa6QcԒ]NTAR1gBp,Kۮ=8M8uL@,s0wՂɷYq+B֒v;Ҷ-p7NWLv2k9tyA-FrTx9 v$'"#Z=8PQXEr؍سҩ]1/fy_'䈃#na>[B]}V-C`HEgH;0Vp!yS-'Lc3sYoT2 ja *)Of :>2uI xk˲P1qޒQI QXKrm )o"+-bn.?U4M͖ZשXNr #]"Gg(㹮,dPȯƝN9fpc1ߗ[dZ1g-LA-a 8,_)Lф> mj)2+3bsöDTB֑v+`Yyѣ`+-"8Q ο'b@/lѲILٺ"p<8k0w<7ܜg(QLV_8^ r:??KE-ߌk590٣#g|"S;޹R`^zq0+(%9v@;Rsu-]}yQpzhZH$?]z/^dMHX@La `GT(nDn[|L"=('Eq w9(d=-JX!-LA%`3o6=+^H]}_&-7OWp%{TRj:1&fL69hqqA B^s0FD"3rk$pAܩ i7_c[R 'pjk6 d1o,.5#AaMa7a"svSFLv `Xژ.B?ڹQ`^9\!Lƒ]I[ ֛v;V` y|']tL2W~z`xj X`:nF!-a={cםM[Ԭ8^)\(ĉXE)"9FC pwAL %msޢ6NeYϲ0J`f{ts"#_S( |]2UT34\ &L9ʖRXKrmOHA6&[G&(ߟ-XFqf9\!L6t3xFr72~)79vم0#,p-mk]p9Ԟۅ6 cػp'6 w2vHaaopEBLa:s蘥J*}*!L+h$N%(aԧCŨa)faPMOnݽYh6Vbz" $̄"Oc,BK1K QXorHbPYLq]0hPآ΄q w*+Fo";@k s'Vdlh[Zzn~QVwakEf9w18LVЋ2(в»n9j# z*Z,8lRria eɨxZZE*i^$C,LT<ϋJp[zőQq ,oi+2 @酈J}ůR@֯=E.Ag03'N`.8HW+ Apc12CZBZn(^3_#~y=Ï̟i1`J0U +gn{6o )l7|I+g3$uRLh 4XP~JSP?2X D*E#GxBmS9TIk!!|48 }xƋFUJ1}YQƽrFNJ2*Ψ`Iow7i+SLq c;A޽eZZnxe( {| /e'̯R B§M~dZ@*BJn:oe-fÃ%Pȡ^'I{3"K c/f/Bî>w fX A8Br;8B{Yœ- :( A QҢ(06;p|E#| ވG졼cl$2XYA08&n8.-JFW'k! *)Ly:1 0<<")RDX s`Q%cJ&:yhTAMM9#}J8f~m 31n\W:5[6;41OUv빎lH,.8:L%s LB4%+df߹RXorև=+,ѫmL3!Ea?ma?m `8 x,S04v&3QRwZq| 5^qj҄8W b8|SP?rcg,ѷ$=# Opm{2ÍYT7SaƤ0%@]^r3ҹ`.V " WųPd}107"ۊ;J"),DSO=LfbKjYV&>ebۚiq W"".|7g̛5 7c"ԅ?,8IE Cs nad&\ Ei }8QO2߇\'G2S˨˧19l`IfvR1[i.ߍ`,sSO0%ku T{bqlհ012Z SHonvfq{]7o$rc&0wAFd#;s8ja j)?P}mW {_=P*8>M7}:QxhalVN&JX!-LA-a>lo%|Sٯ|͵"Xvn,U0U4oWd0$))ڶ2eRs1 ;18(L d)B;3x<ʼ]G& OvP0XgҲ0chj_ 8N~wY/8[1~h(]*osx02 i! kIpYȍ޼o޾wF͕1.ê6gbq[2!7 `3|g]QÅW_va_ !o8CC/HyK_9GV+|S)V.ߔԤ3U;{-; (NZ\N(bF;t0/ŞXDxAo|@aHYX_gpIHQ*#cr\:|Q_`N]M_˥1bb _E*TT"/0 ʒlG0}`xD_@kDUbԆ>,,x7D݈WL6|sKc(8;y*j`P&LG Vb`,2(8v0 ؈RR(FDtALn80c:F0Cb-D!-aƘ sb/|8,"ƽPH4CcPHCӽ)t˓n3 _@LS#H&ΰ(F5F!nvg~? /߮ޘ?|h-|Dtp=.57 wc1 2E%pʠ۱GaD5, Q(e;@yL[Q)9lFIYs>ܑ8$`κZE$`rp'|wI>g%$`RXor`@G0=Q;8^ AsD <H2E2DLq枧֩|[(49F0Vat|ogյp0sGLg͠>BpGqpe,:Tfϒ~i 9LYHm ̷Ћ):!mct:I %)7;]ք{ *[^M_Y)Ja0[*2|{jQrϵHP6_wlLѨ,}Lq`p9vy1*&wSRM-L!-aیfܓ lk1 _a)w4vAKF&-L!-inǾMkߦb+ O%^7D*5P@jA3s`h˨B-%[vض|3!]18 8J42.ԓynZ^Aa25 &6 ,pDQ*#hXw"J!%*Nws8y$B4_WvzXff3860j~#=Vo˰'R\'+J:UwAKz' 4qLgHZqKSʋ)Ӧ"YX9b_YRԏ 8S=OtPW.lyr/|rh/W\[fnw !62o>{)Fw[϶]]./ԦTUlZ `h [~?@DR ; wP{T[oVM 0wX#ܻ~n<_9E@\ CD.'bpג8, kM==f|0㐾 (@ eL.3gw2$9M9MҒv;x/u|$NpE& M.(>AC$"p:% i՞ghx1c| cH-n/F'aa/8>)Mw y/Oy9aw ~1mI+(66#ҰgcٿRy TA9DQ(QʨaL!`F])U\9'yg/͂M8ՄnH ÝUa28\Y8b KKE.ߍHî+L qm*q͂B#w߸2NA$I8\齗 BZZfDmԻ*njo|7d&JJ7LqfY3E_^Qf]ecVAVv#p'>8O:E+DX_>-gȝz#k + =;RwqN5_߯ +r_cc_Yp!mmŌ2Z0˘BJޛ/sۡy]n\z@ES;ANu5bfZ %p. þħO וY}ɢ]kYnXp , J8` (xs|ߪl=FDa¢1'F$F\'F(F'VpNA߁=Bdݞu_T=/>F_k^nxY)DkD:L}]a=zHȐ# "\tx0*@ag0˸ٹ0$چ s?}"nz 2Ozr sgh=*,(Ow hU}"0IwԲˬ7ck$,uG 4a(GƳe zrDl׊yy~Q` NT gp'eHZBZn^WLQNtg`ᕅG,8_o8@o;}d}k8_T 6#dzZ(W/LdsVyyoݘ`I8+t, 1syxwÓKw%+"qa²'0Ap Ð^KF H SwħskxژwZw$l2FjC()5lF0v~<7y;g`qL2Qՙt& /f&;50E@_Zـwƃ<՘ F͑'wՏ1+Z)ų/ 79^8Zpd.e$0FamPm 1@DDWqƆ^FLZ5Z )оO}TI )%9vE#w4JV4s`G*0!Q$FaJCo99G^pFR3Rflb7n&}vR7Ioeo1'`no̍ϊօA96dƽRXKrq!CGb$?sC& HqhZ8H_0 qςjBZnc3 lVg=g pS'A&SaФ8\pe-0CB֒v;ԣHC7EyݤCGA@I[bH c7&QF9RBRv;pOkbcޒFdVr+Ss16FѰUH/"\#%}W k|1|L:NXV)ϿI O [IB=fq>{,]1H976J\_!E1`;.#bp""*! + :Nǡɮazq.}>L?&#,So.;] 9ТY|+c0$j'B Q}8dD8фm8/ O& ӋCGP2燂ZJf<DZQt[(8oǎVO=KYFS)ka ji^{cdmzaEu9J\Bipg˜w9-pF-LA-aNF3/ݜdtP53x0 19|XG83. P;Xf\"B;??)JKpclk־)jČ_=j^msKiO!7'NA0d a8\$ BZBzn"Gj@cFo8SB̪? (PaZ0hh'Zƣ֊BR 6#x?o^`"w{Ǔt%Fj9 sMK\\.FO1hވy[|PL8]r(P$`#œx!EĘҁ׭"F _e}A3?8_3 ruTjyd̃N>fӏ%<dHa>>>WLko @yF;V1+`\Ư8$^2ZBHr~ e nji'87M+!i' (J**nxcF)I_3~a" Ƃ"^ Ãمɩš#-2)%9v:vn#qZqk-8`(;?M202 5YTץo0֯ +ˎ#MdbêN/ 02PTI_Z7.޵LJ9چ]1بJs#U[OʼnnsncQmȶ ou[ QHKqՓR2 5AEe = 1,8nD0+ab9VMfa!|hL!ۿk8v ~LXL}OlG΅e9Joܣ_gq B(/l109D[z3"&iB4[fB*nJ].;G8xo\[9`V ۱\@8^V쩀(,֯BAt˱~8ATq@G6b^YcZS ` L <m}?UH qb> ʼn "x08ί8DoEDA , eJ ˮz1lF޾aJ$9Ē IJ_ h:.F!D좲f /0rhHX"[E9ʷӬW*r#ھg|6 gN(*8=O9x0b ލCZHZ]Mka y#OX睢'쀛\O7Vhj2' gDQD=aAץƖmi99|3{Z׸Sw81o _Σcw[NB%ĬURwp/ ٘W;^n\7 ]r+)7laD^yјT[QRںa2hWY@ -L!-aö=OF}y^꼿12'32_*#@*Bjw?kipĘ~!l^=(q$Md঎#Ashux'-Daa/ ek|ճ—۝,5Ec9k }Ur/KK-7;`ۓz>*cc c0}񗼵ys! f}EV 4X6%LK 7#ڻ? ηpo .)#Wв"& ׭4F҂XatQtp۴bb(a),m j>\Onуɘ'.Qy\|}8L޷ Bqb,gFDa`? }یnYG~ ?;KM[ #y+ui՛bcf΅\C@WfD%Bzn~~q'BaPЛZi7qugyAO0YcEUQF HND#̺ )k}Ea_ڰnV2jQ n̊M0ޘLN<ـ&G聇QPzcn~#7B2~J SPKs`K_3cruOdyڼ P6`̘-AQ;B֒v;lGM`x:;~gRTȴ3t,):ᓃLqh8U3ԤN|7Uou^="{sg.oe@p`2k& "Cvwx\-C`x.l4!\uQR;t@Nf6]0:#qLr=r>0 p c5֢)3WL)Jg9ƜS찬6JC6AD9T_$A." p4K0%u8s,𿟂řA!\ ߕZn|,\2SKޣWE*# X 4#OeB֒v;`۠}*w~Bbxw#FHHU(28VœhH= 﫿1U0㞎8 YhЀa^Fw* ʦ@äBD7Z#T~JQXe0,`Zށ&#}x۞a>}=)-:^'}FUaJnfT/ڟR*,5ϣ扪s>+Ѳ E>2vE苃S)D:xekc F/WnNO;9WߠCkr{fo*%”Y0a%#KϏ0&ĝ/q${+{\{{7E?[_8N˜|cd/|lR6;-dٙW7’VuTJq# wn 0`iA p<|cE1JP|8,ys"|4br01Y,{ɨ<|i! kI1`#hOE. f_M58&Cn|UWR5\L5<Dnz7.7º` %;Rh'sP_ Nl08^"P75YkO F +=gI߅l ' :KF|L2[ wVA&i&^͆;TtDCg npQᤅ)mvy=_r}=|rzD'm9zw&Y axng +PA#@ddy1_^В-79/y5B`F|¸L0nJcRX]rwW j [$L6s %da(1NMWadd2ɂpi& z]pG ï|=2 .]>StF`x2j,LFalɨ"mia kIv .|RW M: M*) x茴018Z2sO-Daa L ? .stz,h-_# .7|pטF uiOq\r^Oe%'~ՌND]pȐsa@>?-魂(49F;kȏH޹(8Pr 罏"@<R_x\"$,244~Ω Y:b=]ّhgQ]o|#pK$hqȮ@0血[?.- J)HF Ńt>G&S׋amP[d=nPR 6#c/o/| D)/tK`2r0hd'7c$p:Ԃ7Hv0|wBDs2BQϝ}A0D ad<,oA-LA`3ى^LuDZɒ:R@N|1:mk??)!&(xfeTq7=x/eT Vn3>F\ ǐnT.mƅqpgqpqRQw#h؁R!e>M6w7PY(?]zG`xFY1QTjsnn5@ƍ7bcnVp6L|˦89*KCF>9D""O41-+s+D33T[S/sx2(N p" D/V  (49F) zD!^]+YFwu5ٕt3wς9Z,iL!`Fa1Gxsθ1LŦh.lt=xr×ƌa[f0S]HL!a7ᅝx6]]46y/g#6{]Aah3ظ]-D8yˣb- (X6&Hb;ze^ME gv$%{I(!lIaSl9ւІiC`(R-)O)-LA-aS$ncL{Fb_Yn=LRYd PN\-禒x* skqb9&mPr}5rccX^+lg ngLA `FvI1,(*ayt]-8 Q[AKҍaM8\"\.x*rn7wth '( $*(FdovжYTOXՇ6joe ;_A.xqn ye<9٫EJQ~AQ4l‘㿚l??mYQHEq؍uBw_F_LΎ^BY%w v03kdҒv;8{nq wCU2jHmhli̍ҵkpsL%YgUՑPiIxA0d|~ 50&VȮp+ G.| N@N(.8b P@@/g@ pA` (cUԚi&*ҡمD z/;;}`H]"~KA-L!bbasbaB9l\(&Bs0lϱt̗sp;pSöwC^8ư͆8 dTi!fr])yCǾ8=0tBc!=h(؋ S nQp)<'R SHKrXT 7#l?، BS[-DDs XM .l՚퀪55)$lFo?EabdΪ&TZˏ\+?H]Qz. wv/̑=͠a~JSHEr؍pFD:8K0ش|kBqkhw.ä*b>.x]lJA-acQ՚Xk@G^׀.7< Z͒PT0U4h L"Kk= KO` H |cPa d324}о:Kiw&o'A2_Aeq@fɸb-D!-aJm{c$nbfƬzCGD0zQp\ZΔʼn'߁FyD ߁_ 9(o % uJSPG2l< 9Jei/o|x\1N{Bau`ӞntdEvz✇B "#_H3- @iө+֘w tbӓi4uP_rߕbУY0|JOia -:_aɛCV%J;T_EK`V mTou%XV~kE3 տҫË%vEsܰeɏm=@]U^0B!GbP1`Z=ԓ>k`tV?|i:2؝KY0Υ-SRٹTw.s*$!+tf |ňc.`t4|I,^I\l !rLF& Px`Λ-N0@;?ң+04ZV 6D#l6<`~j; @G,JB(X7OJ+(bfDd ,q>4Nv$,0{K:vf/ia 7텽9\K {[a Y _~7~17 Pv<Ǜt,5kvj/M I<Kt hs| V `vLr5J̝Q4,>ztqJiXb" =:hdQzK:V1Q59vxCz IJiX1ѫQ!W#Ex~j<^P^|GjT" =z5.~pqpbsvg>7vx[w%G zSȈ g4[?[NTcsXj  d c>Y6ޱ9+=DF4!%d"5NM:,9As{܌`hh5A<9BVHVRW9t;!*U0Ypӆ3nT 7Np4ןQn`] Ibu/-CUJX $1CY *+'*+7N)aauq[_ec,.FMF>z l(K#A4OOae f[԰[q" C@d+!74AX3}PPw@O(8{!_- >\| EMC4)k &19:^O$b1Ճ72rKo.*DFrBBSmu6uR͘09h>2ѐznw$lJ$ā Ii9>iآ 6 f\ܙ w"vLۑ{u$X<NjOO-<:I?h6 ՅVA~Dʉ5~w0ؔ'y͗斣M7l'x"2d%h. x%͚'>8uTe^eh{%- Z((݈J}9joe)E"Sh#ĉ02Es^:ǐ6;U ֑[}~IZG9-R˩A4#̉h75cZa - @;3 *ȗ;X"$4K ' /Cq0ՏbulHH>i1LZh+z O0h0e9Qf2E1m4ÿa̘齹>gVyo?Iğ5_&ODL:J`OY`G}.qS֯"`ԅvְ{|`3GbODlkl/9LλdP3M|'\3#d$l"5N` O#UT%sWh"ҿYڒ%.4+@J -JrF R?ޯD‰#U$;m$;"q>5f‰"~Za [I ݮM*Gr"T(ۉ(7%k ÿ4W IJjXoG$/hmW9K_Pt|n⯜sl P;9{P=RrsTLƹװ>'l:/gT-;Nb 0Xfjر̔L4in(XCȉkCAdѽ{`mV+oI?䄴Rh#ĉpDsJ+,!a_3FbEm?Q"zEgozg`a?9w쏦;Os1/AZs%ɉGM>7/{ҟYQ3~I̻'!sJ m(s9h$d4~|\A6 ɉ3$@zEKzZ#*qL%'z\9@~ KJiX3l@H=zroډ,#`Z 0atk`t.BRV?Guو_'_pmXGasNSHimY30OO "29BVX2~chX=9C=͈fF63hIUyT<,LoM5~&B=e1mm܎Vg]}M=A~'*1O&A4"i  @Oq` =W,t)3j8iҲn l4>x-aBr)Nl 8NUΊD㡗>FY=9,m"w 5uUOry&R9T*n8xQrs#27Eq] ۚM1@_Ig$ t&XB&RI:x'i8gunA!$N T`s Q. #`%l%5~=4 ^8FQz"4 s ^Y$ 5Yщfx`xaAxb2ZpcJ'oȨ*ymQsGIlXQq<LҰrV6Haoy=oF;-P! 6HN t<[M [I ]xlL}~1([r}<;Z6&>ksxh+,!+aUOGu;zʽ]_Pqj0*sX% Y_%+$a+a/ ;zkV 0o ?&Aj0>a4\<xYP4 'X݂>%v OA̲'{,x9؇DcSi0ݱX8~M ]<蘃VHVR|;/ۉyh;;^N;:fm'@iĝ䉲Ay=ǻLz԰z6@)ϚoqRi;#2!mripjaQ ZNmo\苻 MYrhpRi,5/P_Z! M >3g- 49  ĪdY73zG 1@|)x:Mr]z_llP!'ԓ錯H]~֤mTɗNJLtxlA V/l`)87$j'NrRDc!paeПB[x:K5tf$yCV*3@)шE Ijh&a:=kj>H3.;zutnvѨĎј#FCXi<^]YVHv vQZ/xzA .z{LdJhN&g98@{KB(X_< sl r# :?anlo8 u}SI0N'mu DBsEUH}h4s @p s2 '7,Lk  #hbvnzw\F>eQugSKDkX`\ve8.Ŷ%.C;Ռ˴ǦtQ\F I Kne˴QX؇ؘzݢ"(`,vqkqŭA "(gD c=*s1MDF а8m"!&#`g|th`ic= ĎVR`ujw4#( Tx?hqF]o,ƬAtF0Ț#hY! M \?xXo @|vIGi :*|~ oMi[y62 >^5G؈Z'|`Ag*ctc璝- BcW`Xb𘐝!.}ipc''ױYT4p6{:KhjX :ՅS!zkXWGxpgOxEIIP99O` - ހ  } h# W*'J h°A|b̌NA m=_uœHd,̌hj)\rR K,d)X] ^V^j]4=-l29S#%ieO պ*9ISyҰ8gQT>$ &QN??J 4GK 1p|ߥ]BV?qŮ,CemrBx8x}`$A׈u5XYk0-meПRV? 1o܆ <g 'R[GHE%Ph$u#b9|rۜ4gV$d4Nv?jB,dX\gv"#i\$#)4Rox My"ue1Bq8BZѸ(cHZ$QÕOP χ4챔9v-m$l%5~cFl}u$-.,Iu,q 1Lr~QK8G)+a͎_22]W' Xs~q*\gcu￳tD "s":5 K;=G IJjX`i>Ar'#jIEj,5I;̚75fWekb\ )q'XGإu]'kq#kdk`K\{|ВsIa\]0Gn >ixФ}3E&RybByM;OIB0|Ґ %'RRXr]l$l%5~p}$lj->qeo?B$i#H\hl 7ӴZ62E㮸muF-\?zvbXQ:6DJr D!ȈzEAQN&H&R+sZEş/\ߪ4h9){9hem԰B+.q8 j訋%.#0%LtFf[Th&pFfothf ͞s ͊_`{(l+568ܴz$'2FzP ..8|w#2>{ M3iѳ;ktMƝuƑ5u╜I{ jl$l45N09zasX"r[ Q rcѮ6GP`PJiBJDjXNg83~3.u].z8H ΰls6V'ְ3d_ae _&2I0a—IaA1#ed$d5,N8, !;x|h *q^kûbI{i~KȾm>c3¼ b\8%7@GuayJHEȜte4 V'KHv]!,̐LdY+I"CPB2.אjH!.>d(!YhX`Hz܀4'7!&% !s9SqD‹%6 w⒃}4k>NՏL`i&2ҭxuՏBDf(2"3AlHwd&:#3Agd@F^BPwd&u^^/N0ngm<  rԏ;ad2A+$qSV?X+gǜe Ѝq'ϵHlѻrvęڿ@)h"d$d$^PU @1@Avf~%"=%At i0Ya - ]rGr՟:>F@Q)0G-DJ/а2VXBVnE(~0 eccGl0<Ú3np =<3^X_ukNP?-=`s#_ Z(TXBUNƍw#i&TF94Dɲd܉DuY(X0ycyoغ7uAڮ 3ӘcjNH cZa - >Cógx;v@2.gH;˖|cxǿt<}rb?SemIO0']|91'~`BP?0!?2Q wB;\D6 .-ϣr`a@ K!J2јS8k T M  NɌfH|,8%,2N S" Pwr۲Df*h%԰$5Ty Evֈs$-KDg54viNF@Fw q?y>҅URTS'aSÛMli tFjK:H+Y,s8pnTobՉv"tfpv-lBA<hgOr[riҪ$E7SGk#oAL7mv B'TY g5lXxDhvpsBߣhnZicp*@tܹ4P ,siEr+9 M#ifJ3h0msN W4I9.dҰl#2Jz-*O(RC+h#@4a[! Y) \  X , abdϫH 9iБ9@sAVXVJC3lMLf'+C .|LVsЌVXBVJknCg'B*QFݢjUcLoS~C[Z! Y) _‰1#WfgpM3(,Fjx]Kx]_Y\wx !%6ógKx&+jp9JxF+$aaE00ãxjwx$kW58)HD \$d%5~|i8& %]"P F rI!%TWv4C5JJjX`rVs O3`%N[g[0jOV%q s0T ޛO"Hj\:aō1B7"8Q EzvVJ߅>)ш4B ws[n~5OZ! Y) ^i=cMY&Yہ `f57Α F+ӄm Vo@:F#BۊxzO$"H FY! M UAQ>pf?2{V%3/* d6U9KhmD.N-Bd2C[88qcctW>x0E 9A 9 H:|TNfi4mǰžE"p4@dp6yC:Kd7uHTcCs/Onpܓ+r(0ysarpRd`.SaTHux>gdVEtb\oYoIl.hYsBV? C<{»Wt?ĘoN+}FID/qT5,~xa{b<-=#t(x$ Ol PZ9@~ IH*Xp7JLstUsIԸW cSiCeCKiHLYa YI >UAG0Zwn% 0翣( dÍ (MLY7Ǝv!h vC}\IxFmi0فd ;c-ͱzZa Z) D8Ey=ֱ1PKx%1Ms&Z"7'C ;б9@qnUVRAmx+D+G {豚t,H nR>oe1!D]y 䪩pʾ.SIt]Fg&|q7$j7b;J x༦(?NDSKRj>\zjxoB0' _Ȍ@MhFsޛ،๵4-!kbs\\TҰ槅19_wVdJ`ZA 5sVXbyhX33fzD(ݛ;C &F7EKaI)0Ԑė(5˕sJZ@O |zI$ПLր$.LoӐil -ss 5-l梜)9 VLiLLz:'Wi0sМ-PS(99BVHBVJ˙?vY⬖X$/dVQY LB Z)@~IFiXg8HNڠqE}[sflJ7^4*2'ޔx4kПV?pGλ3f9x.6&y&)؅gVS>~e4ZoOct]i'VM9s. c̵½|ob5Q% AɳQ\b 'tsZa M 뺱 AVlZiMPPZIBCAN((qE  V JV j˫Ӏt'=i2[hWHn<@3g*Z&A"HF2^]tlC0aΜxGCl3?>0I))V@3|~q᳧^&I$aau5t߾mƾҼ'9X!nFR}қ2ӱJ-rMEB ' FHr%2;' ݉gf$όzSTL7nsTr"s KJkXqDFOp1F$N^sdbEY$:NA4ײ%9x Kj*XoƝ3`ESoehhyF77CԀ3*!+a;sDRrfu< %G`d\r@H#rПV?ۆ#1Gd$VH Ziyp9OR ͐ 2A6V')dxn`a |N7^F>+Z% /N4dL#I: z2ξo'5u\Eu <\rHAdBr'旄xq12>ہ)VA{D/Xk!wA4#̉Ujbŕsfx,[?5_#QLX{Is:*bDof;yrПBRV?ؓx!3sx?l$ěSq--P@8 eIDiXcFgr6^euXyls>*"G?*oas6ZJVHVR!\~8eo2b9pW?LtUkWQ9zc:qN\䘌vpqĚp7/|) _3 _&XBFKIJ=AGޱPw`stNƃj0M15 1?ԡVHV`fglP7 )6R9]%]jK-Q4BsdF5ۭc o o5 o)vM:fjx˞nxnx+ M HT%J0U#0Y)DGc o8yp;19o4鑂yt Ұ: eu\#sxI"P4f%g jQ9@RV?X!M}vb1hQύ-2oL `cp:~yЬ':ސؾw}$q/Hā⼝nqAƋ eYR0?igN7<)`9~ٍ4$_yga'%`$ 1G'?SϘ=G InjX`CL UNsx#Ǔtk6x*qɑ6L΁5(8cZa Zi +6raTO' /jH[aF+a-~srW?9DRnG lEBO/k0^[|!8if{԰A=ٖT%%4ےh뚕[$HgcRsZe\:E1Z#@kE+ܸ ZP)5dfK?H>!,p]]Ye%ꯡ:4uG/pOcq/pjT:I\(XgJ ZiJ4ށyjV,BRV?Xmأнao[_yL@z=D1^{݈d Ѐ՚ T> [Mg]VwM@C糱dE8_ׅ!Qц 5GLhb(&0`-0`h泏ۇܤ*[$lxj ӾlxlӴJ&A ނsEmjeƛTRuQ"^=c[$p &;ޙ@T ЩO'M jWَ"fq2 ň."ӻJ 27D)31ak+َ!tyPJpF|#*1g "ـN) ҚcU$+,!+af\hڿY becWJ:7@sXG*YHtqںsnuCؾPl*$rPWuKxO1!=EJnjX`nufc4jyw#gE;9`௜cqj٪noy 3Pl3kEʼnͬ;f4ܪD `+ F`]i6 @/2Ze/ PP\w Ad05Rs$谬%iÎglla}=n/M69=H0 P*hBJV'>U}x]:~>SM D/!^pм+F2Q'm%sׄm7LPX^hmBZQҨ`̋9\$J͵I+$a+a+8퉒5-bk7Ӄru?EږXk?)*L벍W]J*CY!$Īj *+D(*EJcQ 30+ Z(B@f3xaL<3 h3zɱeL+?O&O, 'Ƭ`LRIUDZRb7GɈ#ٓ &)#ǹ\ћMČwc$8NtN8MfI$7kywhAI*x_w{:g=GKDiXcQ8&N:ű}~vc&cE2^qmūg}j G+f ۠mv|\/Ds%r)h)3~j8#^WmGOQ[6L9469]%c& Y8 F! H xSy4UNlŶ/}vA/*1K 5n؋O9[ܧF5Gge$h,N%~~+3'Hۄ `6+ ZsY^%SuIrcHPÌ29BVXVJ^-<89 )cvھCz>Cr,{j`c@{K -F"x^:w%/(lzDx9CO1Chx kl<;b"[ש4[A%kaccWZ! Y) m 1Ny>Xɞ9!\tL"qLkHĞuD)~jU{4ߧOH+Q'X/t_̬ ̂WӧHRj8y:ޏV?xOFF's6Us ;O|{آqMj0q4W8;Ajأ@'Y4VHVR^#w~chN_F=(}. (=+JR ]\<*9@RV?tx!]lsǦ^z=.O~J9K 1G'6R{ 9EѴBV?=sE&\"^:yCӸRRh8S3^:_s~.F9"3R8a41B1OSCۜ!+,!a&,4|<3/Mz+]C@Vst-Ճ|vsC&g0W襣wF3w)1){Zsq|KCO=G"!az@*DiN-ɸO<Ѧ2jrZ9Cm ( |̎y80&z3Zzh2a*,GްpĥL Ҝt8F$!auw#}%#vi8YHz'PL  i]ٱa {c 5̸=N7s$bZy{iN`B y@2 BGW9'^r鮙4XsZ>wGn@w`mz ԰:wu(8?,@OZ䝑7|h`"w$ǚqE~4_NHDiXc;RqY<>hџK2W.%=ݐ~[ե'?{ɉ 03#h$l%5~8k'[K||BLOz-{KQDߗ 0Ԁ-tA+,!aֶqc.9q{6j[D>:nI/;M`Z9}B,4' C4_GaëƜlcK7ml=b]$: sqaj8mw԰ay "{wc>*@Ս5yhr@FKg9ǮP[! [I mG8so1OiוwC]%pw zpBr/5l@5hEBvS|c Rr@_1yġO8" D'Ĥ9BH>r2pxZ^'QjϬ:~DΈݚb@;%ПDc!@s$Z! M K94ON^q I:Khb ˜-Rٮ2i$d4~y|ȃ'M''x l&<gQ>0'>Vt@„԰:AǶx A\|t%*^irq gc+阁6H6B%-`4]EȄ'CnlNPi%d4,Nn߀Оp#IDoMMmv){FjQEI4GB J-~%G_ELh~HyP'&@\eehLOaWM :<ct̉=X":X݋3:70g+ _:P;VJ@jsaii9Gq`C9k39pҰ͸\$ko?VI`DcB|Nu_c&ykozskNcR_ؼm=~-4V1>r7 _Hb I %)9]jDnRP`Ȣxq%g1DyoixnW=(F[]8nO5D3 ͈oT M l2;s9L+١Uс, C~ikd /op#~( CC?b럣O/xUb11,yuG ؗѹccA5;:i7~0 kpȒhhuٵ^M3/+9&T԰f'*4zZolNEX$G4w뙴ӪF:9HgH H9@BRV?ǃ_9* ysVh>5^%'@CY&(`466&ze]dg@#*N:n/0~QWm{4BK^OpM&M /~;ƒG.c[cwsOMg`Z ɉ=ok8''8zd3 C0L CXcY@|j0}*3l] KJ[ IC3n'p8 >F- #{gJiC‘nU#'(5火Ww8tbdV񝘾%M*G95 (9G)a;jXLq!!*% ZʂxjKI>LdAީSw BV eAV&NC;qa:fXH 9& u ECuH<4C}IDiX y cn&4]]g8ۃ؃<DD`ɂ9!PkzAz)!+ac JLSsJJa'1Mo_y  #4D$- CBI JPA}Ұx@̪Hbg8$?ntrJ |nSh~#iHYaHZiȐ"ijnٜ,؀ ܤ.Ub ,\#; p9@~ KnjX tE`mH `Vlln1|*(L{; GjJVXVJ 3y* x7! /HJDREwAʉӢ%54yf?pOx\1bx)'q: |- R͌ Kl; 㚌vކOLaq Q7>p^qX=;sQsr Q@w<H}v`f zoӘcZj k:%l75~eֱw 3]saڿ7\,8fqƒ6$+0x&(^P߯` x ƚ[#ێXj$aE_R 3|~ D )"< \rVEhf(u-0i0Ø8]Ks&(S%d4~0|UGXLaRKTlHُD;d#3@] @ vrT ZH6mo]goB, }o=wcƺ!}ruFH$aauwqo@1ϐ'_ZqW_AtgZoG&4z/sF YM¶}H. zxM/QEb{NxRCTŘ9EqX6X6BmH+$EOIɾ=\;*"qD#) kxgCMЙe&H&R6 k{R; [k62kɋdr\2Г H'ocJ@*}ݗE&Z+ޑIx/ E* MDgl`5858Q0›ŏġe$ QW)ʓד/uD۷r|F}&Ϧy6E9^]>qv]qLð-*GaF: yba7D (Y< #C'a7 sl'a0m,mu'a] Fj9IJ\7~j0}2E#9 {g{g- }gҰ0 &89 ^5a'"&$x=\ 1eQiQ'|A_0lF< U}?h]0đy60li> rôfð4רJO4Æxi%a؜ð'(a{/V?|Qð٦x'֙: Cj9 ;`Sô4az/G]ъrVðp-Z$@z6O̍N6 8q' `*}{3})8GҊ At'R0ؑ`GsCQՏGc^|8y8}h0]vFg/Qh%dN?|<+WvB :85e+,J.K[O-2vWMU1}jǻxq!8 Bb R#hJQ$h3DSBl< VO-~=0D='Ñ s\NYiCYewV3'(y4!%d4,N|m`mcje[6\rFo?QnOJm9ԑ5eٶQVXBVJO+z*G^ٔ .F%[|(φ>|nƜ1q6F Ny.+6F+68iGGSQ/l枑;{ KsvD]Zs݈%a+wGvCdr"x޿LdFIK-!-^?NM+kO uj"__\7ziqzHiL5ѧ Tj89N! InjX`43cf?QqX"p42@.u]j(*:< *pCQΐ2 Ei;.Np%ҙbr9x;w2V&E`cer"9?-gmt󡮆j@W+@s5|ǫZE0ypJ`m Ebj-\S-,˩\NQ)j0}.*e5+e5%\ݿ^৊<j)?x@;"swwt众_LFH&JY:39r.vVZ\M;Eb#4l>>rru2A+$aa_nBB 1o)G%PiLWڇ|9! /gk]n3YŒg}nU Er"['5\'sַq԰lѱ5GYxYI ` oH _ɐ &b/l:ss?ͤ_ 3wCk;mgVSnD}nAwHB?[WwCٻc#̣7`xdG$ ݟHlՓȦC XijnoH_&G?u!{2pմb:-bi!L+DiL1s)HLy`#3d%2آ73`ƢZ*cQM6Xdae,t"J8p#r !A z+V8\Jg$ЅD9x9xD^Eq_$-_=X ghþ+65jrkh19 .EDH1rr_mX|]̆ݘt*ߋr{U$Uc Ժyx?v&if[9pxb`e1fӫ܊ӃhLish gt 9@~ IJjXVE %ll{ҌEbCYY{e{+M eeۓvT ۭWIS4lz(\8E!%h \$AD#d%d$N7q7UHNcX㊘P>$b!"7&kk,kLau7#[כ͈%gތw I@܌xps9;oLśK7k1=in%oUbxQJ]_h/ $!:\guX8~tXי bXA/8 (V:7#Y_\^;ڐDy)T\_+oXS7ƵE1 ab|17C QHuW!^Ts&:G .'.nYDАBYTKe!+SC*e0+GÄJ&Eɕm*ڇ(|T9arW9%l%5~w;S",qauzX@5d DgE,jp9JE+,A+g-~0z+_8 (պW0UbGT_K8ڴ.dnW_[?LDsĊD8p/aD|X#љHyd" IJiX`uTcA׎ltݨ+tЅL]"td]4qWZwxF]_DwɌ?<#a9舏R,D ٩aDh:F IJjX`vprƎJx |L^okO1*tthΧf(vW M ƚGhe]n|:]v(L2zOG(-T\Mi0Ayjtxc\;lBYMY%#$ Io)3gG5p92:ՏBz9g^J-ԉR K䙗R u^'`Z`Չu {Q3"#ǁ8mnT2@0\.d=b1:`1y(ְDDGU!K km0T_~/(dMHbfOF! )mpeFFMj#7iⵖ7kR}J|svQo;;_bsgnjpS>gHRdBY @L+1rlٯC0_>~a ݣSs#@E3Ezӫf9,1ھMaWuOp۝4(j,i;jv4Dp N2u];dkAA_4VJsnLZ]Z+ץy溤QNdJ /WD]2=i.zd:b'.LI\C.#O !aD5S.hYGQTU@ɴ|XJtG:|tX]0Lx[&G($T4JeуU-RYm\d]OSdîjLhzvI4°P'ʧ `U- QW ܃/]u_t])s e,v (%M.,{ b +?w]m]W2YДdVt\?RYW|GKng(L4jZ3bg uB{jf4m7a$'Z؁!XytilmlO弨дDWUЗOҴJ[kky%}U]U3]q3]U3#?U:(tiAf۔cMޤ--Ia*YF8F90*I;[Ay0bRX0E`(G;w&cw'IZ0%ubt b ZѷIpb$/!8|qS㋣/4Y &A%5F/9>>cpoy'TJ!?:ޅZ-^uŃMVan1 BQC9xdj|^IdXxwk]-qI)=>ǁ%Ch|Y(>y3w+!3f []Fn;SQ;85Dct[^;jgfT=l8c@((d(σHl7Zn 1iRŰvqG(&Mg !!CXܾ91k]~qX&W#Ҹܲ'f Òrb A$p7UD2~>Y.qOW -3%Nd///*kB-*.p~]/7f(c/>bwYr\(dMMe'Q}^EaSP,5p- s @;b^gY aS5h>%~*SL*ޚ cU>bGS5Xť\S `L$wЬ>4t+=,Xy 1CGҀMEYޒ8 l:۟O?}K0oI`%z#Ŀ10@H XSXSg!u>9ÑH.o0~A##p`Q_+h HqWF_/P cϯ{':9~{=§%ڏO峾zȣ‡7{řm_h%*kuhkk\K^M!{m᠅$>[0{/`/ !1Q0S܂bm,*ΊD/ a1Y$6[y&^bml]j3g,FC1w##Avn7_H0nwv=@jjw'D;xQGf`rЦ`bqtgΦ$aBa&p<{B ŨI(#=)Q1J6V*tCx,zRъ [(%:йH: }6+G(G:(H_#KS5[v6±)ǫiX$q\QzB4dj(σL'χOQ J8k'gw5WK a/Tr~hyyH͟y+t^/k HAN< X']d0.<.`e sY1/363RQ@'V~f.:3`{xPLrfVfn1S/ vz=y^XHI'8ï+X\lGw=X^#͋wYƀ<,xZrLQT8J*!؂!:&dž°pF-|^q9B T4QEyqqQQCk.NҤtOGӴ#&ԗ1 +,7=yƍٙ؉7ZQog߯~{nyV[7ڃem {=1W%=y#+zPI{/󻿱1YPw.-*$cB=yo3ǀaZ` ڃH1+sȱ^d @:C$2~O1|\s.VJ9؜I 45˫Ԝ˰9'Gت>܀ k+؟X؟X'Iy>캧vӃdVG+C(C#Dw+b/q:6skMyzCCZX=ϟSCaܱXh Ύ5a &q K( &+c5gtZ1,lXz 0r,Hp%=H k=[z?9``rP'-^G>nnG#eZ\`Vڃ8Baڡr,-= " F! GI}Xg0ߛ4=Ea=AҖ2a\E@^TO|2Qؾ (}\/U?UфzxBm214;<jeL1ǸN!(}a-ER4}8yt1qΔ3ŅG5{c?1ǀc0 Y(Jyԉ;y!^\.O8QE#8%,b9caj1F\!M8~7ǀ_⦇>?._Շmuxp>m+9MHW/,u >=Xy`|0:^_brt+P+6^y%XA\[@ܱHl' rWFcw6#enbK7&Ιc@Y-$I{녊l\3!9Ew|!N:mj{1 iva](dσhLLIx#3vC}d'ƬbEJ,SF̀8l0i*B0l].9A4a`1*L 8#_fBS2bq¢dv,JF,,%#rˈA=8#vyiZ8̈y}ǞF1I|׍-Mq*!F1aT(˝%J: !f:L [&Qc 39*"qa8&Ė1-!=kq-XSbh)1iijj@R[ͩA,گ\w*L)o+ F0,30e0,b2a3`\Y. P` y0qNlYy1 R< |laa;n%̍0oЏf,PIw=̢QƀmZ8Jz 28"V l 27Z o-.o:b|6P_|tuٰ/+Cyq0ȑڈd1)$_~= > &4l5+Ƴb6)Gz M "6"F`ځCeϯMK.:?Us0-[M!ѵ3r96fdZr'5d(gD9`p1܆͛DO>y`"OI4O"['K`YOp+|x ?1-%=y|QSbW-& & ĔJ &M1{@쾊kOBԀ!!<\-VKjۖ X-A;b^gY2 (}`3ovKFa750I# EX0F! M}\S]5!S5Ba;`Y3$q"#$'FH8FcWK[Ëi:n47(ѻ mq=HNb Q<h-[y6"+SY{EI[sE"(NMk LVNbXj]pݦ?$®=|>n.IL™m:2k~̅R-&"؅C`nc:br,r)'65I-d,7#XIܨŐh9beG"ݜ]ʒebn4U Յуe?\(vB#^Y(vubp=y$Γljs̄Ut9Y 3ejq*{0kRaTe ȁ- ܊>'\K%r푕M;3@rI> 0EbҰċ j<ԈQ1*T!]!BL!BL!BM|ҨblbиBř:,;kqXTX0ȵ<,4aZJ"RVf0fda6 -͢5emMW>=X^f~, <d`O9|6&eSE8N}\v9E6.$]* /Α@!!C%#mE:4B苪N<MR&bv.(bbZș@Vc $ZrUl +zqf,S-{Ńd7y`ůpM㦇>.8PTRx`_7'e&jqɉMND\C Pɉ!-Xo%~bmv 2& 3Y32]A3k3Pi)&9sa8Fʅqra ѹ6 ‚^SaT0™ [J+ +9gЫة0 F% l S0꺘GQqQ<,ExbP4C y  "(IxݽW5;oҽԣZ7;ow(MNhf(̓luŨq^lmow;;FɍTn!8'JJ 8#k3h JMD2=C0; )v4b'҃3-m_ވ;IQuxi`orވJ @LQ&arѱ֟YjNS;iۢ%bM"w+ 5(ҏIpa땚HM&\nc/"HPIj6ѨQ-mۋ̞HN(M!'-o׫X:ɅxgC!'BqC/ O,=1a'tD:(9 F0 ;0 Sʅ) rDinIN)nIOD/'BDh8,~^\!&Ǔ;?)~"4%?HYbQ'> T'|#?-)&/`'/'ҏ[x`+pӃDdу4ى˖+D#6 c뼱!&s41يAd8+1dI#LFF\`2Q1 e}'LzًvIOT;{-QլD'JQ;ٛH =Jv⓮[p < YGԬCvyZs(f3XjՑĻ=-تCDKZb_Ѩև)s//r#/iR+r_`^un񁼨 Vݱ]tb^dԠc(J$zҢC`ҭ~,U- =:$gOnnx1JҲ*եyUye ӱܐȈB^"# g%Qӱȫjͣ}o yA°^(a#U/D^ E) KaH|gyEy~;QQza/urzc/uRzٱ^؋-獽ر^논޷I :ulXuMѱqjZ=:Դ^IG4A1JF[xe{Qџ#Qs"s,WuI#ԥI=Sz#!"j3ù[;׼cB<

,["YVk[ԣ`B#ޡz#Uވ;ToDPC:BC^nJ!(CPH1IeK` iuXYx\{#>}Xg ވO1p4 ŝ<:0[՛̧^"Wȇ٫^CYm(W5jD+1{ՋH ǭfmn-&R"[4]nm[h+W QDCd" |(%*" ~(%Wd!%>b3_٪~}jk{^zգګ~9{zȵW}hJ^gY}DY՗yK)yR05W)ջJR05Rh5_JЬ>KԬ>KZvmN7]$RcJ"XVII֨;"IJ"h]I\H҈HrJdPb%yoMa%>{>'?2[X@rJV"J&YזvX^,w?-+D)CB`:Nҫeɶ:F++14΁уr`"Y!Yba%I8GqVP:t)hX(9X f b́ulJ;e)f3Vfs`yUhJB].[SY;Yx^LMԡ΢&P&P> -5"&( ^w+-qXqX;~(8p`!(DP(˨+ZumeNMDL eZjY#)FĶV椗חgZLlm7>`ɧ cf%.:%+qrɍ8H@^%1 +q;6J wc%+MxXL VS-($7V"4J rJdXƹ[V>= 37v;{d(1Fa }Еy`XhJnl:-zn ĠFύm37Ub<$-1ek12 >@XV^4%XJ^K|b{6[od?F1/:(l(KlёDiKT+=e}=8KDiD,9D>WMwwN)5ldo aWzҞu{Z :[F>Ý,Q_Fe K2nQ:ǀ\a-+XtrSArsLNDg%'0N\F:Xz'BALr"r"۾>Փ{H$Iqg'IIJØ;UHAOlD^hQqXhԠ#{kjla ;IJe[ֺebB1LL(be#7fuZbMέZxMv"NJR (tؕwUv"HNvbǿ+F XhJ]9:;'۫ł]- 0hD`ah A'+h[8؉?'岅آIr"&BQqyRePѷ#eJ&B#j"ښ$5O*5JM.!.fnb^_4^7Y>G<E9W^G(C @>&t=o.;.ݦ_9`}bnnŃeNpD`?o 7 &94֬`y }m9?,9."ہwQ4򁜲yg{=sȃ-ts-r}xBAXR+,8D^ycL9`Jm1@lS4bn{&8-$In\͊b ۰9@2y8i]4Oќ܈uC0[(jyh ;qΞWv&8̝-tye>0|FjN;>&~rj`6`ρ[5k0JؓڦuINʲ'%jtS=ɩ@cDv` =ډd1'"JѬ p7c@֍^-%=yj/in,Œ_vse bY#DFW/F! M}^55X54:.*fbٍ|pVK֢#5\3ȁ H}^Kq[kYxmb MY qt%G=|~W6g~W`Cwloq+fW~A2 JX.c@(lcI1k:$TkIazUZHtJL mrg8@Ɉ1[(D$n4C ())'% L -hvDAO쁨c78 Y8nz ^ٸD߿:d۹* >|Ar>A_|Qp灹M@~ b Sxۅ'=lwG]yJG,o*X4ɧ)<9`Pf,QOQ:ptH t+Z0fn aIOF! )mx̎37n/{QyX ,eFymbA <]&(c_ǐWDi+1OL(7u_f9b(cBi88C;h^'B?Uͯ},߆A>ܵˬ)M͸G'#w9QBQC6o> mbsOGʀfrdqv@яG+l|y?,4=InJ4F3E! GI}/rn*qJoߐғ8I_9ʬwƯP_H4?VZ|)gb ˪E!ע8kiqzN a͡C=x1$2U$Ev@+en>b$b&H-X|]4e  >&A"*VǤQ~Wde%c3?&c dq{ 6#NJvz5Ws5emTyQD#s&A=X^b)NaDc!(dσ܈3/R@s/uZhKx{ak-w5F.,7=ypcyEO՜ %a<hjY^4d{'9B<<N,- ]iZ]-6扵.t{9 ]>GnfO!kfc< k-$ͯh *sUq1V.E2fdd<6>dϵ15^,xh'iko&XVTha˽eal0n ,s 6, 'ZœyKъvQlCh/HP>ց"m$W)~#4R-e<ЖVǓ!4F4F!KY2Dhу(QT|OJ;w3hFh̠)ΠYB=,7>b^@Aڅ d PFWG, D dbE fy4ZAAB-.42`{weX)2d3J#2JDV(l(̓WYÔܛ[2$KO@`ɛ.WEȚ9 H}i*)H|}T{r@ /6BDh Q ^q z.-LFYVNB2:Zi+PUTZ!BAVOX#q:DN)-B}a鷔@MDj"Bj#e"Z0H:dZvF`%ӈTL3q2M-@n՜ȭ35Us h2  Q$LHd@ii&ViiJ )0M4}-!RqY-J2 88 8 IɴEӂpڽUani۪!L EB@'e&""Ƅj9C5J$dƓ\_,Yj LUI&ӠIv\_ɴjPm_ yj?j|j.Pi(v^ɴ-K 1ΝL~P2Mr&ӤQ*Jg'6C4m'D !B# 뻆О!f$7tBh_2FAh4DddGK# ur1,#?Q'qyGxcW2tB> >wo5lqs>5%Ϸͣ[QHt3)CCvdO{< Qchv`f) 5ZͤD~A3Fa -mANLY5lYr,6# Vk1R+DH~{c@QqC=7 q:^8#՛qe!^DIWp'!n bz%șMFdӐ V// ?RK}<0̀ ͅÞqԬZrP;rl}jR3QEs9=/67 X,DwO(*N(dEߟV Ec[&1z1Bx,j ",<94Ԛ } ɖ'"gMۋ˧ZoA6"<dY(J}mDv1MA= "q,XB,5MUm 4F62PPV'Dt2@k7K_N.+sg\eAq|`t_ }D6Fh'޼h3mPHA4ZCh#;`[YȂBnLg_3:"Q\g5{G%\g% ,1F%<ŽN[<؈KɊ+ p8OO,suu^b.ay)[Liaa ,lBQCB1K|Ua3ɂ Kr|-EǗÃ:@LyYA3e=s;()sf+rf$4+Vchܯ,f3R,arfOUEwj|ܪ& 8@e`(j,_7K̮,E0NֈHKr(repaXO1 ,f7KִqfI&v5L#aLF@xCc C\> 1E3%zHP`` hFp Rq<Ș-BQ.X?ypL}@j4r)B8g{(p.fpE¶m(N E c,zۤfA yXac@(x .V%WvW+q1IQBqC's,ccBZb%s,cWcU,B5VYH,fXb8h0LO(Wq{$se^Ob疌e3[&Mn%b2FI1 eṁxd:2Ȯ"1<$ӱzxdU0,A<>pάQՙp̲,SꔳLՌ>5eT1 Y8Jz:;H/뮎hW'hWGeffhW'ո67(ͣwqZim2܀&oeRkKЃeZ4Yph- dq,<>g\TJMӚrMɢ$aT4D(/v9r=aOZ -?F4`CtB&t2 MKAi_0H[bS5JqgQ\ٔa tJzad3xyf5ЮdǾ;C WŽh5ȿد7*R"2dj MFgLHQGǔ [UAك HBgDIg,tߋf~*+ 7'ڛV7˩Й-+fCp쨛}ёG`2sf{0=[S<ƽd^]Kf8^sf H yw-gWEnm]n.晀Д3K6B+(1s;}kJ̶Ef[2S?o5ܰP2ˎV,%=yY U4DO'@[kj4 D ehG-Mfhx\$F[Zyhm =.aFҩ¢9S!RwX،?؍ Mfɤa@3)KlmP,d*,d ͐%Kh,Yf Y6f|eldn|$*҃dQٌpl%9f⦇>hƃ@?rAf[bt ZS45c4|#G'ۡNeM.a2ge'.o"n4F,%=ytN<jI!u"sVD WN-8cImWzyrl(=mMUFW3bmS/ncWF=թ-Wc쩿So͛6 :) s>- x`c4Yrmpb Q63)=cUg؆37ju }\YQLtFA3sEgx\cd3rBgd3.&ǃD?PvO;XKCϫ%I!\z p-4mΌ>A3.5:PWpʝvZ:,Ic\Feq({Ïwv@j/ٌC b]-C$cc٣N]1 pADaUe(Y66 WeV}ݯULI$8Fɗ1 em\t\5LMrQ BOq`[i}%g5Bo-Ypj:9_XzV먟l#ȋ-5ff$:* kp}tqSc#5yU#3yUVhfio|.x ?9-=y >l$Xj]TO}1R}]^Z~W:0;HhLݮ$~{S#3MٔQ35e2)7X&̘5V2fLM/Ǩ)reٔQi5e,0,̘]|E!cv3Re R-&Ȩk쨻(;}1cv`<Qu$|B2!|>P<p+a %~RR-%=y> ~z{Ɲ #AɃۀ\?g9c%y=^ۄ ތ'x!Ȣ*AyBGڬ}."Ś6 '<>Djtl9@4:m&FXf `ƄofdqVPkmkY6F@P̤lpʲ5?Bb3w³: #x漫g=B+ο߱jL:Z-.nȃd{h6D2r}glil~\])cTؿ< hcx( p0W2)-ԑ:,i`_Π1Ea FMml8i_gGl ܅GLHWZ Ȍgx X?72=tضSY+u9+8X\A*x^g .l ɾUI67foڵ|>+\a L_9 X)"m1H`~ ,K/3?vs=c w@3dw"EN9Y8f%xβ;,Ue׃=y1b9$͎?<cH1nkqCmIJWqxkeE|+pȗ3ukѝ}[mK7/G./P/C> b/&Ղ3yd~XX8q \㿊f ҕ۶Z-"=Ipzna?y˕rǕ hHÊ!rYvhl̀"x83kǖ7kC;tf0oCr`/tCLOI vB]hQP~w%jkcJͫݨֆ8F]ڀO%P`ʕw⮧A_|r xIN%MSΏz1 [(Ja6LS4sY{Wޘ|q9 H$X duG\)8Yd S=3iUfߗU)e[5X^e犬a*R n9HA-5hFnƫm7yj1ӥŁD5|9;y~s`'a vThƅube)7 pUH(6ߏb7k!1hV*V7H7ɺ8j j>SYy=d*< .ry5 /ZfX $.>ߕP&< At0Wr l՗I(E֋$x/?S.uM&7ٗ^fnwFX2!O\,J&\f%fF<^@B\`sFJ8 dcr qW)pzDcZ-"=I|d)hy5(L^ .w.@bXXqa$8LCa&vx8La(8,B1 /}4:-2%vw?x5bc \,!j|xyI#jJᯕ)k ߥUWܕwv|l#ŕe|5B]XM+$i%/BV"iݞ6iDhJ^t5Ц|ByGϣcv0mk,ZaP+H #zA7-!"ㆇyP k˵+eVa[9nWKU7B^Vy}Xw_}p;_ˡ!֯#u"ACoMWmԘvaXV*)jՒ?bw@C-4Ԣ>ŖEs5(Nr-Z]уev.h D[`}$/$і,z ?KY[m)P'fYc4aEl8PylnܸF^<[$ꆄť2^<FYU[X9/i'8Fz1 ^mS7ECKhuP1XqZC'p }6\ >.c>m/ׂeorsZ-Z-2uɕ`璘R!X9# B~/+j 3_Kߵ,^=X.+ TKEO n7*kR;/Kԅ>zWMiU(Q2/.y R嗋" K !v$BGE SnK_ еͣB^/.^,\I˵{&p\Uj*{j3iYvk "=efAr濤I߼~xb$~( ! هH|U`" %P Cb0q o,`}V!XPp_afhNs>_2䒃4Dv OAdJFdgc*K82| I#2cK8epL`cY ,k*~-S{=ãd؎P0+TQ`A9c_aهWc*ͪbRd.lk4ĭ4H06b1OދcT$I=Z.L}'aRB:sӲֆcE<;1pLcwgeYpL\Dmxvb,^A8vSsNJTD)1a>GXpH<A1(/]O{a<"۾>G L9S`!zr,)*-9j\HeC%E֖W4b!nS7%®{74Q3iGLU*ȅ])nn/q@avMھ>pعU|Q.HLbga$y<> Ć4R.K Db(J$BDb,*U`bPl@l$CjDAb"a^>?v1{ ZDZ%q'RH9PC(D?_0q]k Y6 vLv6&1͍M0a' R& N Vwcs?ߋȰD`Q#Äd>*#äёaB`:2LG0Ž5GDFD.$H,0ܐ0*TGj'Cd\XX9v&M [_wF+Zp|v_N+[–-W]1GN5l}<R e nۃCdkےGy q>0}Hj6>pĈ1::[$;)#ǐ8Y(Dy=_ub`$?[]?l_YӃcԹx?}MV|ɥ),qp^V ]:5pW-rw`yOiƍE {cťA2ƘWXNj0{+C#o~uyoHFMU>>Tnf:yoְbMTѦ1MQqCG',.LN>w \mq{ vL&},c-v~U-hŁ(R`s)˝֥Yl$+@k9]G! )mx]\yoL_]O?rr_V/$Ɉޥp-b(F J. <2Z QXorM N,љw\S=,щ런ٗ0h`l` naLqJ5bm̆sdGrfS a9p9w%ܿRAVj` cSr0>qm`L(*sTWW5"7#` WrU1.5wUVysgFa4=ɨQZv͠aN1գνܕر^}-Fql# }+ ~usc}3M.mzkp{|(W sNVOW5̡Hפm8I74c`0G7pVh^18cDwONwa3b7 q҆͘1j k Wݙ2pJt41RDR_}? _6]q|߆ï~!8ٖg 5)-L!-a|u~Tg)Qù zk@, @ Q R L {D5uD: Qp\G1Q :ÓqІG2jK$-L!ac?3\~:ҡO[! L~wvv? Jm[_,^wý?8qM׻P1ҳ00|ayuq8HBY \ՐÓ~aSz=4ڃ^Y_pj%Ѫ=A\ʉf~pyU>n%왂@q0ܕb́58f.dL\Ρ)k&5}횉`Dr:.`.┹2*7-; v㹵3,7X)Va7,5FAH%J QHI2،rO\ci1`̻B3~7C1P,,݂+o;$?)79vu|,gi'*wanĘV3[mIQXpdǡrQ/nMjJy;Edr8&{Ctrn슣.cúy)ߥ.7 Fœ7(?%sF-D!-a[4-3H$^WC; rPt`ea_QǂyH*~ˬ(79vf;%{a}_FnbвN\óX28µEd[-ZYd~.n9M?x3,xE&s4Z9bb_Yd/ wbW! 'Lmn_|N:o8H=;jڞIearY0<`>xȒL߆V?jMHYh~dI&q824ٱV^ PD:̨Ҧ`1'q0`Fc[0cj;JKCnvx;]s_H*e (sN7LStjݩA7"B1;KiFb ڳU*0SPmr(pؔLW? ԏ^ifZf!6w|KGZ!$,u`.ָtT~,0dp2ur@^dWNʟOپ¨Zξ+Q|6 @U<,(8F09ˤ!"2 .Q" բngae-,pʠby39ӵ"dF+EWf+9N? w%1Z?)5t`vQ]8s+]l|v9.>>wSEb4T{56;|wdxr-?od P^#$p,N` HX<d]]1/sSqߌ5{wKA(Rpcx !d{JJ0<~ڍÃYe`.u  HS)ĉ_ON<ԑ 60wx%+ ^7 z; qٻ 77b Ƚ[թr#PX"5\DUD*T`XEta s`pkagǾdžcqk赦E<)[U!,12, 8\cPeRAT#`_tA:ʉ/z!d5qӰ a7[_Q2{q*t9$scIq…adq@E(N:TnkaVMΑ#+pV=Y7~xyad6λ0x3ŻBӰZn:S۫3 2%P~TM;ab gJ-a7gf |38D{Uj.40 Tud "B ٷ>0<};߹mXX_(V1<7WQ/i! MNV|wpUtzx7 AEpZHt Pu k0\N/*%V=Ŭ (nլ'kc AQQq\>xH}&'3\}==;%{;ag' "qPdTv04v3R cX1(`os>pf$9T5Q|⅒XBZn'͞\Kcf\Yvua\݃ TCT0U4uT|by*<2 }3( ;6GJ& n,;fe'塊\}[SL<]Wv1-IJS қ[ݎ=]PkI".'jYDd8q0|QGγ qXdYLA-ad   ; #||W>  Z0'!* 2*-L!-aÙ;ˤ`ӥJp8`#X QS`w-E|)`0q- <j @9r1,5n[3[ 1ʬ- "{sQ0#J mc`XA9D qMqாs5:T&.}7oyxc8L``OQD)1B NjWn`JšE7z9pW\WU¾D{hsp.A-1LIHFERH TX}"'꟩%TӪ{YcYgr2(^ûgju`1 B֒v;vGvPWg(ݻđݓB@]HT]ZEyƵur=/myeEj,?*,X?S 7{X~0\5\5ٖFcFc"Fc¨}0X*܏)p l]Ơqu[,Ei,*-F\ҀYt"C9-!#ᑺ8nREQHoqe&~WLniOYVzF_ ޒXZf9ɬ6|fY^/qn8,Q8"e,5C)_;L^~1/~y#yBpfVЅ@/|Lզm~ LȞ'r87ŁCs 9B `%pdu71o_ɭǂtLu/-L+wދ)**n eܐU3qQpVFq82-T1++-DaaD!#FaFp;'ǣ+ŧYA0da228l)Z2:ۑ͖wϣq‰4?3>uˑo }rdϏvfZ[It%y`tw}0>3Ѹg);0 s\qd\<+-LA-a χ1L6̍Rs2=o#5P@,͂98<+;m]ԇIgy0O+chx. |oT01q;@q30ݎ\J]1YQ<~ogm|N8ވ7E8&=jafm1U ZBznUe v-#@&-WQ@CkPm `ka4ߒ1UZBZfG6;Ttm+DmQ9DlQQ11uo-01=`x_jb΢@a"re- Ng&[":b`c4/h:^"*! (Usgwp.oO|؂(PJ ʟXz8`UU͂4*ŇE\eDDmp1Tz2T,cM#5Pjї>U6I)`÷yd漹W0 1ס ESavd^0lXάbW2Nv}.-Da-awV;/yx$D,_>=$Y_y ߳HqDCD0& ÑђqZFia j-,Z~ 3ӫ33>6C*cDٳ n([].*<S~ RHKqmE$Z-8h7~E{*s~{&xxsILѼn8|=ƙJ-c\̚Τu72!?}c`GoBz#1@*BfX|srT"<j~dςCjTSP8\>L+s$^a<k3z3(6E 9>x`|8y~W2/v)`L/nvKx<ςSS䢘"p z32G2)-L!a %ڜlRX;UqR"0;W Cq^#(%9vpفʤDD-w\:jQ P _1pL%@H kXy"dDFd Y#|`M#8O4ō|rs0̞DT@ç_8a,pEJf{"r2eUƸx ΅,Bq0Sbq@q0A0$u)M: 5ÙZSz D9Ćk ZBZnmY4cXk`jZځjj gBp [&L!a7rU(lvn5yR ^5kbߘ_ =IߛD¼\?HI\f\ElV ?]0[L*3K SHoqj/g|. J9N80xyC84 /J}*k 2s3Jɺ)8lvphom)):4Fʌs.FFSpK!dqdN6;`ĉMxy`ތn 5rMn¡iq5 %{*4vSkqysݙ?eP\gO= "3As2& tu+?b.&-LʵN..L'ʥUp.>1xÇvܹ+wJ̈́6;`ۧOnN_Na)@ۍV; Jɟ <,đn\<,L!a7E҇6x'nB91qD F<}3:?  C?!,S<$Q¥a]0^be>G|L&#]G . bY$\)49Fа|cެ͞#WY-`mA636A &'Օ%J QXorؼoZ-sE̱x/7Qȷ&Z] <Ó %a)BRv;0=^gēEZʗɂ-gg$l'sr 9BSq;qri! iM\YaOZpa%lHL۰ǂmn#lS*͂p90pV|!_uujћa/Ya^쫾YOS=,"s8.2vfw8tG 9ZZnѨ׮uR7WVUÅe(%9vL=aoU7}U*bz[)t/ 9j"Ey>6w.$cXEH#$ =AaZLaBIA-Da-a AcF=6e",`\u=\p0>b08ZI/Vv#XѱWxQb -Uu!Ӽ\$pJdu1WeTR:b1ͬ `eJ㥴$Yԑ 6|Ҥ Cێ_$yM13WHM8)壟M8a)h^W_~ɟ§pn;&(S3L8É82B&8nӝ7ĂVž~K;_.,jsul(Rw~ s`$m?5ƃ/`(4fr'ul"Φ`~':b Dh#v8jTTQv#Ag rMv?18sp"Z ?osOZ18JkpSH]rubepq/ RlDYH\ -L˯Pq#(lF0㆟r1*`eUJDqQp]<<EG-isՖ,;k&CBKAL!a3} <\{wE9̉p˜%q0qb2zKX5!̐R}q0y\uCK`4޷S;3cV} 4 ԉ7Dpç^Japf6>V (G~ɠa#y# \ش9:7 5ç[^S2m:! 1[}5zJ6mc; jϸ^oAᆽ- Vf%xk:\=FpQgkt fbkpu>/afWq]P0}9_l ݅)-;/⥿+&lLKS9`k˺~s1p̅0绌7:^̓Ug-x(P5z Z(8*%qRbat|\H0ƽP `0@'z-23&zV &IEg0 ߋWuO:[)Y 6tv3"bʕ~QS>coXWJ0z;2OzVdar 9Br}0t5*qQ羰֍v[PG-YJq< $>\Fg⠣sɨuia ji[` q:@W>T_ۻ|#fV8F׈$(t0ull6ly*̝ yS8mpL:Nnx8S_ޙA{'c(8lFl}qԷJq_8帏rg 晃Ƅ3q_le^L!-aM49S1atErO3srŮ|Ek4:+ γ@]ɾN$.K_G`=Y<*c+Z<0HG %l0XaY^ 9R>PD' 0{dZbp0|ga.wޚ8m(;h[J8`t"x"rmPzu\O:G 9*B*?F0~ j)M:Zz\s``&B[0'Pץtdbs1yv>^f"罇G4p.y.k:19? &ȓʒ81)79vk_Ga0J:/.uWV;0ZǓ--[g0=8}B֛v;FC8⠇Dο2f,!M'^CaC=>ТEGqǘyd}3b)e֕dOtR -r0|2˘ឨI+"46Ҝfăk4a'ќ\`8<,Lr|Ku ,-D!%`3 1ol]1OfP/s ^E"΅ᩘJcoYϫdΠhQҒv;೿梑9t^p11pOjˬ0цUgbq虛GzNwahvq_L8Q ܞsR v( 0WZ& 9ZZn?g~/3י &̏u>yZ0W.U r&};]uƓ>l&3D>H|·~\)a) /X;X SHKqceʞɪbzeuűXYS(V wV,~ E>Y2,7;!1Fh$f=?99u U|7@ Np!G"S:Uv2Ơ\9sē5śs (ƈ__WFPϡ 5pOi*zY:' y'| xQb !9{ #)%9vm{`1FO;G}0)A9ia+W+S0v/O8yv7r]]  9@\'= w%Nè)49Fa#fn\mdFUTŸDTSD̽p00!zE!-L%`3";"a 1w\SrZ.b R Sa!!Ȇ|5[;{iSU&<v|nj=͘|exLfB 9@tA|B|nrUr>\v: s¢bdHT@}_G1~7CatQK  H0 0cV2V 2k _v5:itJmYO`a23sd ~|кG3aU7 ""N5|7sFqG7vM9&o!gkJ?Yw.cΥ8Ŕ.$ݎ-J 62IPL6ϙSxΤ}ƽRh~cz7/=ˡ 1BZZnzd$7za*#؅ċ'9#/(]c ͋Ccג8FH QXKrmXha跅a8́IH2s1V i)OYǒM[ 3ruE3lr@Ț>ٴ՘|߿>:~WX<.N.? %rAz9 қIA7An.~f)o^` wWr>b̧5+[4 :Z҃IcfRԲAu=^Ř;7af56[@jnbH% * &WC!'Q0Q!7ʾg–".9ĭ(8V`V@Ǎ,?s c*srb ~ NX0ka jIt5{z#~lQ)&񸇷ˠ@s\c3$<B֛v;wថnALnqP[Ä)0P8((F{zqЖ_2) -L߅{U&2oQ|t%_&M8F! "^c$ #) 7|y 0{kVpu.wP}I3@ (末3* F(<2at-Jݢ/ +̫0=.js%IL!Z0TWLͭЕd}*͂s30֢@Ư9A1=7!SE~Q QHKqIsk 1@|ԋzF)=%7r+u\+]b@0$o l=$?sϕ++|3 ֶtqZ% t7!ϋC,"uuDZg1Rcc}{[g\í8h+9uŅ ݼYRŘj-̕űRDak x@a}qYdLNXbstcr7xJě-r>ᜧ(NY708Yxdy kI\x9獡o=7rʥ=KWr3o|rl|a2z-yk! Mx&G!\~WL6M׽y^ojaE2ʦDHRQv#8b]&SxRnzgI MGB<`y/[fKB})#V24jY-3{gwŲӌ`ئCAKF&-ہݹO0H? 4a rQW'iis2Ӷہvb=\̈+_|`FB}0}+)T0}̅\tZH SPk2،]јB |~5"8|R1Ŗvh" =NJ`>F!af=N|IqNE'k$ysYȭD]N)!lpyn G|o{ /muAc7ecglRA4{ ӑMLAa7" ; j;,L`$#Atx,=c5Ta2DU?)JEpPd-MxKy͋Wgt`0!Y, 1BZBZng"kiL|šO#֏Vܶ6#,Lϐ.gm F\@b9nl`2`8- K^ձYw\mF/|P_s6Fš`(̑Šu3JU&WnS_bV^Iv1?vi8qx--DU=KY b*f|Rϋ30V E)(jƘ šeK40./8]|8W+苋YpŅL \.qEqѵT0U"]peZrUޗe= j0X⯵M}N?Lb"]#R! ;{{w@vOxlU녱޻w&v,~蹾 ђ}z) *Li--PVv#8nDnF:knoFޏX)amF í8h!dvrwxhauFqdB_) 3sYA-Da-az\p Iz}Q0O3G'*PX 'w J0`e;ǵ0;uX[2QpOkQs22j!-а\hv6V#R 3qjkcS_ o9tг}ja-Ha-;}91<%5(ޓg:gZ&s408%6"[ϙ)79v !!I.8ߝ۱8hA :F1F D#F(79v`lV[%0OˮE`:'E'](Λd "~,8DEDAOtY*sn9Falݙ;pȊ'ʅZ@%`X әAC+X]gYH~W0&J`mŪDJkk*aǪHqp"eT0ųGvضgW6``F mU.)NF,ᓷ٘T!q\d{V jitNd58 FB)3#OnBi8 16%sF-D!-asŤڳ/b2)@r.;y&>x'SyMA׊8N9B~p܈ <9W kI-߷h$"I=߃g/n'+d tBuB|in MJ {oh䘂G9)w@@~Cat84q-p& `N}gSs ' ̟' (=` , <工At|vpgNa B]-pn먃(#V!uOl9 k]pÇĄIAs|"ޕfOT&A;+ kh\[N65r0_{!L"0UZn)| Ԃ'9ZeG.<|ZE×ceZ`:{ہKdU7gP'La'QO*lo`xN&[wH 1BZBZnmk8x0&f `3e^(85(g 2#A޺8D}Q"כu+ e 0H6;0LFq6 . 90_p $o/̠FXan>qmLG苓x {׹)J2D H4)Lq?й +<6EENjX}7 U"$Ą'0KPQHEq؍ vѯSc"fm1`sK2p-) Ia8 "ejt]JsnWf1(oe0a0W UipF)de f*Ow wӂɣ>ՔfO>D*|@e*,͟pdൽJש=0A4/έa̵Q6/1Oj\l(kyMi>W'Z݃-d]6Lqy:Cju(:\{dد+τ&oѺ0Eb!_h+m`ӟ[3ǽd6V nx2y0/D:Y%*({Y~`X} ge,;ja kI;TSF<89͘ZތjŨq0A9\)SI!%`38Z3c#HoO[$)\8=389PrϨEQ<=$Qdf\168*n "!v"p)%9vp&a T\&,qx=-Û8a;r:TrXNC[q:=sG0f`Orpg?tz`Xk$[?R6;8KvrUDp]='=h`e,>8j! *)fy*w0+F1JGW Juћ{/0*ܣ:*3V+`18BTq?RQ6#~Y+F  f"*8LEυe1=-7 i)[x)0[)DrbW×2ɌiiᄦOia iI[|Y%{ _"+#fPfwa@~e+4utJ?)8lFp<9aQ\{,v(L+MΘQAKD8QN:GѶ$]1kJ()r 57Ar0l%ck$E%[|&i|QV( oc/ 6Nx[U`d!,Dqۅ8VkckյE`|1,5^%3b9v0cXKTL2|0$ݎ.ļ[5DZ,p)`Dd`yG3Ã#M]YBB=J,:^GǨ*cUF^W *o "}̀1 ] an}'Km]Zt@+m1o-+`k! MHXS SڣQs(;Ja~/L&.c"|;#ia^XϸG{3lsxτ^;0 3Y3ˬ)79veߪ0k*lUVAuDn +ú⠺ Q)79vC|x꽪1w:!mx;]L>bQU>)$V|b NS0۔?v*oyx.%*,Oa2a(+Ӽ (8vcb M^(٦M*[߶6.q_)&lEڱwAUUVG( ?G#'OJ pt$H )+$2"5d+͍bܣP7'8cΛpj @S04Y= Bn:Z)íRpC Q4oa3FGpk! kI8SyfځQ|YGsI+Xs0M 'NʞʞmB}WZų22p4".Wq3y WACzF4P<xK;xfe~6x¨}88O*]vx ;4c:ofAqu21vh'sEq^ # `Y4X\Yꬂ:dcCf|O,ц:3#ǏtĤb#zDI9gp˶9a}:5ZLaF4? ?`!9t ?J QXorv[5$ ۓE"o^ovwUDր0 *=rxA1gEq<G|bwrxU2%9v0{[*O߯=؂s.`"St׉o 8 dTxi! iۼevi|cZV_²Y/i 2r0Wn\d{ynқv;`3)%b7yxf֎p|:Eqc tXQCT@V aeZaÝ2]pѐ(V90a 1Fdo\[@ϒk` 3LkA}nTYPr Ls`&eTPdn}#wZwrƀ?4ro]>UXK O9`~ŻV hsk%9ˈ]5j5*8vn/vyf.X)XF J6- X-Da`ҍY se^,%\u<\p玭0tL8GPQXEr؍`*]TVRd> vɒ(uo'U1% W-b7Ky]ɘ*Ťp(UIM 9'Y㤖.[>. 9UH jT;x ΕdQ8>7.̍<1s2|4`-D!-a߰K뮃2 ՜•jO!r0| ̃<sx2 dBRv;ķww)7(-Z2Wp.s26wsn!#G-9w-2纞Zzk^2AaPW O#jU]1疯wp*KP *J@Z[,SU.e^$P.8Ć(@` ;\};,;U0Юv#`؝Xˢȯk|jy]yMunEqp/kMo %BҚ 6#,UAƨω ;+U+jD]!0~-ҫSM}PLu1"[87`y LmA5AqYG2j^bu=xU|Qb F(I]d|A)bJqc7q-ϟRARQv#X 517u,ۘ',]Lm 3s9ܙRV2E۔$^lfP%ha چn@,hORՓ4ְ;z^d_(ץ FRL{`{Z-ߌ>\2n20;1Eu]i,~;1"C,e O/cBkY( - A.09-œe:3,$$:K21y<'LQtBCי}9e|NHaaZy0K[Hŭ.`{71,o!JᖀJ ]1(bq4) ?`T~i )ۆ*n9>au[ }M?;Vo\l\1Ih,)LAa778%&4\cUÅ 1_%R[R! (@A|]rniU؊F :[@X)N*@j@lKpuu&>ݝ azGP#sT'6\orl°8d8Zznm;c2CTP3(1P,=Wdr\!*0D""-D!`37 0L'p3,a/'\֦}-T+sBfO7Tohd߹7Db_hQ{m(8C’#ɳ>qY!珴0E  ͙v0tA|7?-{n8H,lpb `I<|?bamD!`3=[1D5_&{de0ltv d8>$L9knK+obV?B.S`uAf vXKi5ތx/ܳTaa^w>K‰L 8@ˈQã("9F1B0\{;'jaÏaB.{`0nW2W+F8nӝI11l A=R|@`\N+= 5\pbUkڢե茭-ؠ֥u-UjK(c)E-LA%Uik5vL *Olwqo(FPT8һD0g?s\A65 ۝3NaT0J0wa6F]A]%xK Sjez6\QFCLϯZ5?+^q0AHs` IpIka *I0ԑ7k4RO_bv_jvW`^wa7Rn+~Fn+jNr[aQaÄPs`¨e8ZBZn V/1*OֲD}߃UD} } Fu2ܗZBzn}{~/FLnA.6sLRLF0gp "SF-źXF䈭E#k+c͕&pCY\<*~ <- }IEr؍Xr+ܠCvDïk9CIF8+?j5=ΐy+:*rz@sqFQJ5ehߝ`j)wW=U:ʂQw,51L~X(Wx̉DU ??6!%d"5NM@5*J+ٞuQ-5(bqq]綒V5UrP%* YO%,aaw7{g8*@=T)x^u_vPJuYi6x"z;fk *yDϧH,(h0ݕm[Ӟ%ն4R MJ28A\Dq1J ߕ$T `+^u'Փq.C$A0|5d}g9nbRf ꛞM*m2Tx*Y-$!aweB88ON`)DԺ3ҏM7s+{1.@qz|~k, wv5.<vX^@֬u22YVLCI<l%d"5N̅S,xGjl>'[R,x3GX`9Ȃ7Ya M H X C\FAw|Y$vKQ@Ҹf`[l5.9|+D= "wkM~" D݋"BRp97-^!c:cv4.DuLD|jenҠf#e KJi`M*ߋV}vw$U}*qqA$^7^~ߋ2@eoT^syhw7Px5yեi/jP,MfT3&L :FrI 6B4N[%$ %lr<#M#& %a[b88 Nauۓ # sޑ0C `BJD^ ^c5 ׇuZ>]ȕ^ᒸ@Wdhݪsէ ְ9>_=/~μ6c',T 3uP9&ГD+v^:6M3:g8Q& '&ɉyk3 ȫ_EVR=!@@7B'u-5<D3rR 9͐u2A6v'(؈q\l/Eͨ=!OA)87:Z`:`vp9O9@!!+a!aA~GӄtNr}e$䜑.)3:Jy벏w|+Q%H$_O:^O$Lޖ !erZFVX8јE@l,pϼ#6J:b$VI,Rs.+'^|fSqԅtTC]J}L&`!ip!1-ݓDԥTsdSY! M \oyR`C9͈M^Fސ٫ kюhIS䞔1aKDZko/j&0q@ǚXh0}Rs8bqsKi+,A+ab"H>ω 'I4qch91Hq#dp>ϰM n]$br׌DB9ۛlỳ3m\Es( DMUorx~/,ArI/кO.z=%=?ΤUAPk+/鸷Ï݌Y4cr=h >`ҰA$L,9{dߕ+(N:,} [BFʑ49By KJi 82)_`VjrDB&SQ@y@g $#Y-4!+ܜC+RVRQ"xL]Sy.u{ϥχnZӰ b( ,u٧b~m1|""WoL"RsEvqcǘ$ou%, G DP>rFOx1(ׁƍuyQ OBߘsDgT>O4V%d4N&DcвO[wG1ܴGfI LL݉X|cJо~ƭ3ciyx69" 0ǃxObIsn]eY_/7~=p Wՠ!Ad#JRhM ÛZ DkdWs5f"6<]tYKItlVHhI?D2}gl$l75~0f%Gr6NpAҴz*NȋZpՂs5 ް ԰;k@i}b7nDZ-6 w,@,7+0}Ʊa2`??)K,-8udz7H-XUC,kr.ל)S">EhfN|pR>P9ѴB2 6'cdSDpY8~i4\pmQm 2@NuT/{i~c0_ Ұ;ǘF6P[9:khr/-LU#e2%f Q8Q՛?s9h%d4l~oQ*t x/ֺ6ݏ:dp:h|yW/5{֗%1' b˜U&iuqs \g%Z/{[\m[^ɋ\Ĕ8Ҙ#FYSam2A+(vC?9r'oJe/RƃG`HLNte[9GyҊҰmekLL bw| VNTD}Y/^1BXY#Fga':1Z<$2? 'r)dZ [/H:>5+[tSk~/5\s^#d%h4~ݿڿums?f`hY%Xorq1 5Z?#h%Jh|;4!Voř+o:#Xm7I)R@a 9G,57r<X԰څRONɶXGhIzMoJh0}"Lj/@)@4B2 v/P븾Q-[qT7{p\u?%:RiW&ҠZ͑Bv?XDN]iKn?8$u~rDźp\~y'xxO&_b2=xFbDi%>뉉 Hs8[6ko 4߻}hӓ_4`HeG!+$a+aLl,m3wi'`@ $0C ~{Lq&RQ ˮ8C [I @?-B\i?3$ I |X@? ~ƯP@?S@?I \o!IsxGy]+-{raFU!5=i-q#IYVV:DBsF$9k^Y|@q%~X?ϏU类$ 쿅3rxm?(Eb\Dc.Akfs9h%d4l~qhk"h@:;=5!4$)OhXs`Qf+Ԏk) 6Hy`RL_̉͗neб԰bQqA9 bu9kl!F&i0 LPҠKsd&+,A+aM % Ft3ڐIk cD܎Ԑa4Ps$`?~+x=&﷉0&YOl>3Q⨀/@*O:0f$gcӴp>ULL&97Hp/"6:-;_~c'9W.{tHk2hӝ9n`aO﫲w'3-YY% Kgfy^9&3A-q qHcOtVO8c:s];ԫ{,';G71y^AȜ(%qK(41A-4G6ٓ԰A0c%g5Agrgtd$:n*0);sWgҰ{@1!z4B z!gEUGΤP7 tY u[%K? ZI:"i o@h"e$l$^p.r|un~'WWZCl թ4͓sEJj9@ Rv?ZdEunM ic<[\Fƛ6h1|S8%lvW7w19\@(&L`>,Nd =Xt'b AnKwYE݂QU j"?:5Aӯ T@jU=!!a}ߍoVqo$OO-, H~Oӧ9OVHVROLW%Euh=:W;{XB=|K,Z9웭䧯:\&UFp5R^i 1 &ѳSUiV$>PEcZ! [I ' 'u,Jꛩgl%`ZA52stVHVR!Z&QWP+F JU Gv}:RI T^&XBBR9'[vemKSZ[ݲVt'[ [ֶ?5[_?Y㝉U>>j|O8X(` 6D/pQ:^!vIj*؜`VʸPBk%,bf8ghid&7X41|De1M-8u ʎ8c#=`*?W,N{74?fz #.YQ$dpbq};,ˎƺ&h/V\IDHފh$h sT`Ef|h2OP8g/M ¡w|Q%@M>9n԰2hZݏlU3۸b&K8/8upe&' 1@+&NA-iNϫ6cws` < xj$ElV@g1H*5~ :f T{Af.RgxhWMb$.pp ZXh\z IJjХ69(u14Jg] ZBz#T'VqmrT;ZjNZյYlH ш N;|]6M';g ,Ó Wf*r H  TLVwcu^BЌa%< QhF 4LV84 6?׶ ęq(/@)lqM4HCcqI+,1 X f̔+8 &>Uy 34c~CXDgh&sЌVXBV*4`h6l/r \UWpiA&V@V`W H  aZBqp7Y7%y;aDg#Bjp9JC+,!+l~з;\H+1pYx.9񭵂8$@jO7|Fɋk`4>II|oKÁs|~ gF)75:CGV4O<qr !+Mz5ZQ8{B}4͢B2 6'eؕԈߏQ<#rMPR`fD-Q.=XϠLE :(Ƀ%(cUe cނ2q=FWu,Ad@) ٭lA\%x /.k pTwt^Eh.ɹ]GSL6躌 FdWwV+f&!K\J) 1G'pXS3:3ZaаЬ)ub1P'J4< s$8) ''_"fp&0 \635up" QhE 8G h%d4l~{ rA&{iILhj +i9Ȉ `B} ^fӰhs'w &? ثEbxRIF蓋?Kdj] h X̜Ipalj%  *^%EȦe-I 7*'HT6` -awmH=݃p{%s˧J0YL,3s9) xt܈ C 70Qx/G[RJ|w4v߼5+p<[aǧza;7 NBhOzE[(LP*{Ӷ;P\翿~,@S_\G`̚o5 ULxvHƹ֔9~eqIF*|[,ע9b F3ErysSqNqJAj\q*V:0 v,n|fjkδ3bѱ}o]V%d5:ZRV!_|BH=EȓbN`Jǥ@OhNYgkLM h+[>9\B[z\/z%nZ/zs=_646'WL8c8e؟y!|y+9Z.PV!AQF!%_ ,8B9L+;m̚:=4>f䩗4p7s9h%^w倆1?{B9r^>yO63OUM լ4\ksBv?qǾ!:9Q5zdG+;aT{L̀6@5+P) N5|w¸gbnV"/W,C%`ALT2ꡊEUaA*YaAv'Xd}u\(*ʎ8 j寳\_yOnN97}IJ_}{99~NN.g f$Qi@,fkjs8 V=nV\ NNѪk;|-' p&c}s8~9"$da(=0×h{#| CeK0C!!P8GhC|Xɱ{f^= 7hU$:t̜e/5 YF KJi5c^ Z㏸/:L< 3i>.cј"F3bf 5GB 6'KE T̡BwHDzZ=mD ߝ؁Jݕ8*fK^*`0핇8._qRE+\E+]U bU%Za|1q9@!Hh*,&YXJF6a%qzXb2rQ'qqYbo%uHusFdd( pdZ: d dV8dcf25 N Ȏdz&: ۜx8 s&J.8?fבT i&b`;œQ,RFSEG-ñplX;h^/ɸL+JXl 8qh%9DxDIt l, !0=pb+$!+apFb(뾉[nHhEǑ58%6?Dbm ^b@f%#G @K w=[[6=8l5̓Q2 t ޤ=UBGVa KFFFa1<ð' @l ĮX|Y1_|\/h~ENbBi# '@|cad[a Z b1Õc1 :3c1I(Ң#4h%b6c1X,$l%5~0ԝ#ŁX'ӷ`XwxDX2i0"(XRh|:/=+-bNH۱vqrI8jw6yiWm߾|9B6dXDDHPvx5Gn Jo~u6GhbĄ&H_gGbB{{҉&VGb S$hb(Hv?v1o_m]!ԱM00 N KM`9+ O5OB澝,{rpBqzUI&ęg8Xr. uƞ[O=m$l%5~0ea)ؼ8N)˅Ҝ 4v19Ii,&Ұc8-nI3F F'`gH, Dv) t'mD>-H\m~QbNGra2*^/tgTrXZO a2zeYtþō&z@[Έ._+F)R@3<ʗ4wj#s@԰;fIvDen_K0Dfr%3KD3Kf3K)aa^ /&ZzTǤ?(\@ 1dU?2^CBkPO&̹ ,:lQ< dkW$VHCеACImRb&UwG @(1eqDLӛt'2%h5/ _W8eNxyjZa YI ,Ѝ~P9,qt!: :&(}Eh:#pj t Kn}~6?1ƒs~ crm%%%cͤAhrIN!:F,?лKE:f̙L:էL*,U!)%9~iT)@t]jJɎVu{YcHjDa*ZAI+5Ps 2E1 }x]Fk4Ȣ?Ib~cW[$ڤT Z!4ܑ j*ؽ`:Z̭/$(eu ;vDcv#5>\:՜8^˞4G= IJi*d{-vp}~F8 6DY:q"~ *YNeT 1K.(Akn7I*1XQ* ;C8ihcZ! [I fB^ Hi h =nBv?Pi΃踍lȇ=;D@Ԑ\9 "4DDYڕݥ9:P$Qؙ4#JtߌdHL"3iFbJ\Fbz,Jx;$Ƌ!8bV:c<[Ao4A2Qv's@E7s?`toBD~8%h_p~1!!+aivĈ }nRGk(3 *ѷ85iE!ܖ402m$l%5~;vwL፧=&}_3g-efTS3i o*d7nQe ltzSᦇ^P b4p)KjVaѺ9,9?&YEZ:C9<-4G.d%d%w?P89"5Ue+6tTQ#Ԑ Q4 XsPVPVR?[?HoLlDOo(x;a%M2l qGc%lIpǬ97Ayq_aְA ?F);|^Mj   '/4!#+RBFR愛v/5;m.AItoo[re!A8CIF*ؽ@ni' 8&~ aO'DRy&z*,x 9sV6cZa - ;uk7k7q^F>VdqiE3|2UiX"9H~lEJJj``Z`QŐ]5+PjY]fzpj1E)MU-2A6 6}`cAJTd pO:ײN4|l~Y?9@~ KJiȶ1P` wJ4X[shRtPJk ~ox534Ir-/s)0I^x ĴI!A3$DLLdʬ we & /9X_ aSk 1Ebdj-zT9{ֿ}U+(.؍a Mb,51B/RkgH=5BVHvSK~Gs'ʐ{Wʟ$DPIlE)I$aXw- մt)1aܒ^s> | 1pl Ǿ??i%d75~6.\ԞDusXw/(N Vqr}e2|g%9..^a7n `Eܼ RSr`Cϡ)qe/M݉?+STei/T% xȓDCSozqV[w`p2QRv?zb~'fLQQ%שJs̕9@3jiē74o#C((?-DbP91Ob4PpxIhj؝c }? 7׊ד7YIh ڷ8A7QVHFJ/A@ [9=>Js^DTTm}'#9՘`8+w&!>R+3S׉x5NtE`SU#>TE?:"1١4'G}B9@ǫVXFJ~}iN4pm3Qys%\}E6ƿ5I|jX k9Hv?<+p=c򊦮UHlX! B'50@m%d%5~з{+ Ѧ9Oϛ^Ilsfh4z4N@9@Bv?)NoɡeFw|{qIBav4o2i#}H+ X%ݴ0uwlc%)pP `o 1&Ά<|b} @QkEJDi؝cEky#03fŲf%,Xz.m]i0=XL37a+<oR6?5hO4*᪼ )oz]8U뼫ĉBsk}j%-wxa sҰ(c;;0_S$uq#`ET=i\mhq!$Q`|.'>M >i68frmy/pUqc8Xhc&Ж@KapL[! [I \hjOs9-E&k0$`J7mOs) 9 .p/_}՜u19^ Ӿ-h.QV2蘂&XBFKľ㍤mqh{/Sr[ӝ]uWGpmvM1IZ/Hr>w]?ֱcܛJ< 46uths1V*"EuJig/ӊ**?f/ 9?f};w/vYq4v~prk ԰ʵ>}e\1'M`S`orNqJ9 pғfӨRv?W^7zռntZR^7V9[2[׍nO׍a뎞U.*`Q-@VՄVQ9AV f}@"LL M"w8W2 G6AXD`!(9ps^#d$d4~0]eo0.a<.BrL J\|4{i^spZa Y) uր'M}N2胕ܓewhxOO58T4ZO'mK7Aؿ<9> /ޣ3n?_HܗAzXZ_VXBvS}kݓDL&]n$" +sfN R֚CBv?.f8A&o_'%CJ\F`C]9\w)MB&ꏷ9E1wXr`*BseVjcVEuX7E/L˵2kþ IJj`{yrZS-=Nڏ6%PH/ {R9z԰{RqY Ыkcr, ߽^' 0aGyxʬaD9H/#x~0X:0>f+ % 5PrD)(]zm#xe#'469{ v?\J@N9ITh:k&i)# *4!#+RBVS6c6DjenzڂQٹU 0j$T5fSz6DVApPVd O~ JLfIC9 e!+G'7eEҰ&(2AMl+8A6ySÌc#c2kn?v;a׵4(pP %}Dj0},(9pL5( Y) ,3.VK5k~gQ8 @'?9` P4pYnFwAoa\lr3kzҌ[XL4 +R3R3n Zj:?^D'KĹXZKgrKt`<09wO g@??i$l%5~ˏ9P^<:u\pRx+B R(@ @IG @,!R9`L,(.]?%qUI"Y훌gOz{gQU ( 3ݲD_4؉[[fD_Ypj 9F_4؉ lE͑苲Bv?'+3 ݦŬ!E$Փ=5@sA OgB幩}in=MGuc+{YcZS%Ҙ"FC9iX{N[$d5lN!.n$pgÛ9Njp]* H&2p*j] ԰;y#xͻbH ;G?]9ڤFLL[Y?p4K^j;iW9bk OiП vx `Esnd|Q8b tHIr B'BCB+,=aEҰ5o'*+ r3&y:B -ak |9@dw'!/'ɬv-mU`M qr ^c25~0 -BKs8"H_S}qLOE9h'K(ЄOo`얆͏? 3 qyUhW?(a`j0=+$ps԰OL2fL3fz*ѵ-VD?yʖ}CZt(8MbLlF¥*! ni0ݵelNSéD0Y$JhZD.U* HY7c6Ӊ"@ @-R ˷йr\OG6t\k KǮQư_X# __On#Sp X{K݃OV!‰Kǖu%㴟w>bn-`|#W X H.yazh]8QZco& vGeTJ]4fBb띐_9.԰A pXrumE5lzƒtXDgh HNb$#9|ƹ{N݉plr: 'Њ~ X 9GCcּ E6=wQ8!GvKsMkZL4? <j~YumW>gW|>5`ZO8z4AgKVXFZ^2_@~5ۗqÅK㰁􇯘*10` RV95BVXVJ|;[DA2"rdMp;kcmg  '2R2rk }*nyׂK{*>-5?ebbk0}!p"5"F KJj[Ž9jw/ 3f<_NyH'M"Jd]X9#' [xyt@aNU`fXɴUݓG; DLpz s\z IJj r ? [9}Q_Ghuq 8Ґ`#05V~5YA [I  jjʜ2Zo|Vl>aUJ`LNʤ{Y! [I j *{ײqJ5$Ms hP :}L?N ˆӞ/=c;&l7F7ݿ|B:^ 6x/b{Q8DE JKc =̛w/5s9h$d4~Dx._}1h%}-,q5%ϑ4D s^#hEJJjqMzX9nFmIJWO:(MJ()K 1G'V%r:Bv?HW%9 ]o@o KHOӧq#p1c wx3=mr,HIl3s*,I:o[L7!&=!A J)\AiN Pcr,ͱʖmrM:+cqO$'g+G:, g z+@o|.ֻx&0r=Ʊ9k@26{Q-1??:6$ZHZ&4 ᇕuq9@Bv?`E` SD>XJ h%:,r`QMmЃE`mN^D1o2VK|pQmHz)GL ' '6S9ܴB 6'ׂ!. ~Ɉ`b#kt ϔo [8e iAz)A#`s~r8fMN?☭-G}f]K*Vth 9b8q$d ;A+RBvSߋ=zhR8县*F`-@{.|GȝHFl=]PR=h&!a^Ȱ-/Vokni9Oz{ <%Xdҍ)<9֩EZMјfVHvS^h,9xJ}G9Հ$4s<&ҠHss&+RBVJ_\}{N"s}ܒ+$N =]8S'sKW [I 휌: 'JbwEԁ(@5.؅J OT [I $g,hO$}nt yKqUf`z#9;ZVHVR|CϞh,xќ'j& MY(&2Ӝhn` {6 H p #Q3 Tg􇨝Dy4fXie@ziTG WVsUI/g7]mA`$jQLOV%5`C<GBv?xu7&9`]3 dB|hH:ӆpG V͑BRv?}MSsT~s Iv`XQh0mSq K B2u*+,A+aHwJIsyq#WǦR6 H&!<4>Rs$D&_w3yUιnJ#ÓFD&,Aaspa|?ZrfǃĐD8ٶvĵ35$݈e$4̸Qs1’gjDv6'>݉c{vyx# w%:?[Ba|o|ߕ?^d,𾿔Hy]<~lgB2& Qzlཌྷx\g L |_~05,9@Ѱ"{kp~gH|û sK.`x1{-1d]DѓՙPOA|D=Y71d'?Y's'VXFZAu`m 1mBFc 쿧6!+=c 76!a)`fHHG`HGH \Fj=cBȈ\=IkI`m"[mȱ@1|r ҟa h S0~QM`ƃ&-`o -zYH,2~ cO]$>?[Wm*o;0"_rVXrt-.Xx&&F+%*Dc&IJ(5|ZI[6? 9?[/}h% # >bSŅ,"e7?.lc~eNo?? N' uwrufrK-j+9ߜp0zN swͼ0c2^npu"쓛zG@X{@4%{;[>-ԏ9aU^e1C51N9+('Knx8cCD'N8RakRFi~9%k#͠Wuu]]Fb@R{.3q`5ܣ7paevfnna@wn挴sF5Y\?7KDDu}n߼׾}} n7}c7_A37i%4QWc%^i+fk:{=e/ѢӚ9jF G~_lm^BZErtY~LV46Ƹ9akFBE[I8FAo9@L8ca$u|N6R[s~vjZuY;M9*'ך3޴km%hh~}Onj[_: f%n*jj*,!asě|/ҼLKNP\7JM9Y\ܛå4h9rq/+,!aQM家9~=땅;ME-:w45xϚs]mZ! w΢’4@ўQ[8/bsoIG`A$SD$*p]UsBB'npYP 76PVYh&iY6 7=JNhī Ұ9e"ehِ吃H*ȒJIRL_l/"Kr i%4Uݧ[,HDOVvh&RֵI 568‘LiDҰX3Pyx3yzMM$x@$ O ) Qs^#d%h4~0@UP8i aKc^ $ HHv0d]Q&{ogh;$AasT P*rF748ڛ Z[i?8L"H>FuD?~XO p7![q4UL<9 O 94VXVJY) <l] Ї ^yYL_R*JsSIt]& owsbE; T$p Cl8NȔwˆWft+3N ͐2Q]b@0B‹?Lz%a$4/5Eg 5yGȊT9P(ciD0)S&i4#ӧ:uqpFp CTWHӽB铀:h}d[aMznDljDX_0>yt$l'/j0}3˜ajx&i IJipoqs9c2{/H=P>I37 %i0}!p[aYM׷JJj`4#v 쌃5.?UChMҠ͑;BZꄻ=z[9ڝ ]BcBj0mdEs( M(+$a+aωrbv`%O̺gL:17gkR91C'f/'fd75~91,[)<ӑ3)<ӑ3J|iۡ%J3q`\Gf[:2@T(GfЁ|ρu` <0kY3B=f!Wx^ 7@ g2yTZ7Ӳf׾^h~s0s at]iOk8ן$XQF+,k (  5\&pZ&XBFS?2sQ8wl5sxga=_z#6eA6#ud%h5l~l֭夾j9m|$zU2Nnn΀Y ?se 4OZa M >)³R@*j'{dxCg3<G58%< 6?|Xs5^ܓw5:paSrXv24܍Gs3qrXFaYExuZS]->yƓȱe~r0HiĔ}_燳U[ѻ aNw\#sԦ'8M !S6)#>d$d4~p^F KNc-o=CvȌ."nD_#*Ư-gCci%J_?|jvD:5;yIhm&k; [ tz2- >2迂˸C754@Φ[Y QpF 8G h%dloOtK2牮3 ?f#? 3&sv 7=Uf22v'X$]Kӱ<;WN$+Qt"qL=S_qf̕f̕sosV Z) :{N~ϒ’SDGIw~ 7=IK`0Q3 Ұ95*oc ZęH`ǟ+Vzk$NCi#A{ kY+t԰Wiy = /^aIq_ ݏc;Ñ/ twe:v:>wveK@$ \i6&yXbBT^u^ՅaŎ}JgD i_Gʁi۰"Xc%=,Ż0XnPaukͅLEFGn6K!x AgXR͖~{ov&riG!h<Óx#^u-Gx8/ln`Itgˇk JfnWmHm1[};w2zb!Oﶘhai89.\0j:"(K)U<(7qmJw##v\&|U<ݫW܁zk0ɡ.duwUb.Δw̿Qa cҘ(T| fW@qO$NO2Zӯ#ŞvMIU#2E(_\Jkh\MCn4( XZnL07Wf>3ʹݽZK74wLf9JU-dMo#,$8aզ=ҐD-3P-2`FWӗwpo#/^f,y//R#Xk_//l4=}i_w ̀ C,ճdxC&P|"`L\+F%ڜ֩:sKU*DIWg]r])$cM4y9<2.@9JxKdTa}E.fBW;3bL/4cb<S"+ʀ߽V'P^>1G '$4ObyIKW37(]G>/,r5:!ivĬQAZa3CNj)O <nyR8DF>zi@{x!~e^2oJeQ> 8,ZC)O'DMQG&&o!3z!s96&Tr#R8DPaH-xBN7s/]!k{n;5vт{XJR2R[ #B WaUFMn 7RpIdxCTMˆ@xtĢ0աb ;a +nMcڥ 7!d R8pnR}'Cd:x0BE܋:ؠ[X \3PdTGL3vkf;V0H闌0 mpCinU, 6QG6Vm5tKCܢ?g&PA<7":cMWrOI2We8ɦXT\,,#e%|S]2흱[2C.뾏t+,2¬ԣ4=3(c>.s\Iצxd $i0^>"-d?Mwʂ1K+kHQ#Ґ=*nޓxL!DlۓᥳS0a{3e멟3gpR<|_ =1{XZQ(^p1N7KG`|\VP`G=F+ӑCi &~0_4_z)1G: y`WF~kaX2C2`rخnܚ9 Uċ@Ƒ|-E Npw3 Ke03E@靾`)sĚaic*Dz IY(cmT=ˤ,\n豫3LzY[sKcdj= Sܷ&=7tk f(Irݣ(ŨaI@zʡ5KFAI<:4/0ߥQt7:NLlB*Y/ 9J, <|-8$9#ߐc E9f,:|kM!cw6QIWʡru-nMZKb{P313 a^!5#1Xcu{*N RO^VE,g-pŧY˭NlwM;&c\dfA7KlVOi2|>Os<'-̈́=98mny&\oLq_bIru.~(8ۙN\*WJ5'ە gxc@qCG|HECp>ʢ!d} ݬWHTY`BPY >%GP:/ٹ=  eŁŢErL>"8ED#N֕3XQ/qu?I1}Oq ]k81`9Th@>WTPQ1U2 Ea;֜vEe~-z+eDPT3. ZzjO:驳(2:bp.?hLj J%ܒ2@`b;b\DQ~^BÖGn"^8T7s]KkSlkӱw|!{_(b)c XͅEg3ҟRhgO2F ;+O)0Jc1 XQ:ID/'73hYSlg` |Ν`/Oo܅wTˍj=Ҝ)v>`qݧ4g`RԸd46qɚ&i{.$|*\9:?i2Ri_t .0^WT`x)C4 ZFXmZTU4iQ5Ѥt en Z#b"]p/B[/B.X!#hx+蚃_% YxZrBG@HEV8Rn1d<D$=K@H9zLFF,6(j莸Fqn4yhs!.F5" c/,%CrkD +UݎQ>\@K?d:"Y>\g4{MnB:YkP9tb:rUᚫE@au`:8 ]L+VaSЕk7ƓOȎm7HޛxEc؎e+e8 iwP~RV8tKzEp j%jAd( rt/udlkJstQd4ߚ2ŷBҢ+=|W# J "S&ôq<a`ywc]A=zw8nOs=`RȅQ 7q233pAI5-Gb l/^DP+Rle!*[FR$&bB)[bikז\ V</\"Eqyll v[ޝi#MȐGzX8Ĺ{pJj=P ++UEChÄi-&iVo'BtFߕ,%spif'b7{NYEC]=F:brOq1`iV("s.8Uyeyv[{|EZg_! 6z^}Cż=^i*MnDC0=ݰcEێA6p,MQ,Q"=aiUq`Eg)C$YsC@6[[̨.R\O؎UG)I[B; la/ A9Z EA0'p,bvМέ\AL=>{{(^nPa-mu`Pڸ}Y؈8( a9Gz< %#Ṕ R)FA}(g~nG 7qny"=t~(`C% #{1 Wg ժ_#_+ Fn7O^yP^,HuF ?@ʋP^쨼hH*3P^WaR=\yfҬ+}?G6[ٵrߔent ɮk \v+06Õ8/?\ ?\9~#8ˏq^Rq^`5DR^D!i(%3F•ӡoxZµ*J ҕWǤ6iEy౤KfeշYLov*hR^:1.KK  & W^ȡ֣Ћ2 k*/,:l㮼ĕ2p];$"$EL+Ufƻ^Q5[PSVcMHtCᑁ(s4]BvYuawzv?c)Fa$F:lU!WYM: ؞GʀPa(p0 0EHV*" EqLD#dX?N f$PjW`?)0RL*;9b]FjId-Ҫu0Df6ρOgRR2Z(0,*G Gc0"dtzȀU0aueXS :傔!8r# ~REx)K }9uo4XGR}?~0VN#Dz@RfAF]Uƪt5/㊗S1eI4.g8rI\gP1eiX.m(x[_%.@ʱS~ GvAV4bVǔߝL90aON"Fit T`*Ċ)) ΪLO# :,̿, X3F( trAJnEG$J9tGf|׮U1e/~OK6/<ԶVeFYU ,J WK_efIWs 9 3}JNRueme`LX[ p5U} S$,)u8@tmE ۦqXJ' oc^q&pg*,vRn l eH8wm8į|tMdγb݃F9dH9!p1cONb`ff%LYN0E 4a E X C,T'H~ 3' ;VS`ʺtm?O`:,݇xRnMoS1es|?V&LY Kn2X,:0kLJ ʐr Y}@CSLZ{`a;Ni+\<]UH9\d| ,u G73aºlgA_w2->u)kGWXK#X SVnm]}ܨ8Շ Sm⨬)kX߷.`+1`ʲVX1Gҕ93ZNa,VYK8U!4Ԅ}b%LYzeP5a`pr5)ʚ0e]6ޢy/&,Y; X5aʲ¾U(focndu:a`iVk )G `y]kXSߚXK̒rb)r Ԅ)KWr0e ie KK0e5W[|:#+aRU1eEcMI!%c` m/pϘ,ZeLY6՜:y7`g!U/Ϙt3w(,8bCZY]nϟ1eu 搒^ES֩R٠VŒti/ޮW'LX! tƔe&Y*,;絝%)Љ &&LY%Be”u9N7y9]U73, =/oX0J6y#Ϥk{$%LYL*Y SVՊv.eH+8:S>މU0e6[ S$5Š)V+`;˶ SMoۨ*~?ﶶ,g˰rbJ#`me&*Xa1dqf% ,YY {hWш$cЋke6_hI{񉕰 6 hPM؋г֘Yki6l8sWA>Aa5V ac`i6h͙0eqۊl^ vs8*˂TVW&Hb[tU*VY Sdnv\t̘J2] -NClOY%LY$D7uGV wN0ϙeڬtPXYY]IR5AyfLY 8 iG,vU0͐e+az^s}l4LXeȶ+a8{ͤ DJz5fϰv҂!lſVLYZS֊))Wa,o}1e]61+S.9:dcjc [,VS)kUAH9Mn`3~>Mպ0luLYvt >L&6\mnaN;O|'PNXEwmmUov} !Zͷ=DKIc}-k(`:CLзM=̉.~^dy~"'O[ن/M؋x7誤g`/z[1Y<\w ƅݻ>|!`qֻ.; &fG cCnۈ6Yk_rz 4.rl9АUҸER,X4+Q#7ŌƵ7.ܸeq KkaԸ`qKKF˵-RӲqe17"Ykq'UXK KHڰkai}E,S;kXа4<5x.O_?Xx{H>ºJ(S鋗wۗ_o~_n<69y@$wr_ AП#=r{g?{_SSa$R+O}[|?xOɦx}p _ӟ˗we[/?ۦ^|_Oe?{޷J7SD9Iò_}KE(K!S?th񳯾_ߢ.A5t^?(k>bE?ۿ+֐Թȋ oZ6ߦ? !ﺾe9Q_59l]ޔe<_~(v_޿~}(dΣHa%)x9~%K{j[.1Z. p㾒t[ 8*A*IǸkc?y?qO>|XFP^k-7q}_׻rF_ˋ;J!e9_h4^U/W˞w+{eu'}țsHa+W;|]4#+Q?ZwkFMkRزCw[cQ>,Oh^i< 4\o"Q۲{Do3R*{"' ^ڢ|p ZhC^C n7Z $\f9mZܟVmr.DYZuXkۃ71hNY\.!ʛ׈H>ZqaW$uHqn^+=G|>OϢOoqwv붱x--Գۇ 2gf7xstMFj_}Gq]I2e|.}G4Ajf)-4,*!w{v@o+ˆ(9":vdrӓlQ`۵óF0fM:_#|e{YJu_K\]00s_ib]˨Ɏg;+ݐ~ "n?Jx[z9O\T7AˡQc;?k{ߩ&wHZ[2µ2Z~s_1iyOe~s~r~L٦U3l<_a}yR8s.[W\-'8nTm2ּ)cJhk^RqzhA jQn|v?[nei㑾!@Ůl: (|319xW:K$ +gi郵~xWp olYFV|G#A mGV&;?ٶ!O_>lׯ3s:Mq],I.ձj5(fߧߍ}Z:<*+Mb O~. "KuMS_ko#,>3@zM"|}|?<ϫ gj{{ NsS~D:i :eÜC[>LgH0&_~18QxS@.u=v~k듰4߷?DC;ȻMH6د(P+iOQ1$(lhrgY onBVz)uaz.byL?}}Ţo)2_߀x7 &Rw_ 1$sh(r: 7ĸ^g˛7zx\@^a O r|횟=21O"v+&5Ƅjbzmk7-\JFuD8{s-bl W9+`؋W%(ʰ~D'bKj ~N4{ie?-~7+o ֵ۰9vTۛbbng4?Pw| .4 _C㢢rq~~Z,"nYԡne0.?nn1cY]/P^*pk`,0lO<R{|vN]ƫIo/sw"Ky=1'ͽ.:v;ML,o~̺p-Oxseendstream endobj 36 0 obj 183997 endobj 40 0 obj <> stream x\YyM7cC۪paqA"`?:3gfpbgz~ }L>Wg/deWel|&.6 e{5{􇽘J64 fઉ\|ޟD| 0[-񚟍2s9[띚^zl$4qU6VXnyjkS[|xo;H@Wa9|Zi5}_iZ\jVN³^F 7CQOpC$vVidM%%A3;mA }Wyq[<En>W?eu^ʆkњ*w\0P8'6$,ݕ.S߇XL snv@M'PӜ\A?D>Hvv(F.:m1BV|_t;k0\颓y;>"2dvλQ$!?FDj kl^gqSQ&m@8K3Jb4[=Y1ܘe;&Z-waśȻ|o9l\Tr`@+^DBICGh T L@4`pݕթ0S6lؙ [&VX6 ̰]p,nL5gbتQ:&32l|(zC!]#<V1ռK''ot=CTeQ@\6ȍH̊$]UҗDpI$bdGD$X֭1uzߩ4T҆2-lܺhF"A];,MJy6F׍)%F]%dKJxk/bV !\aG"mEI>0{Z~r%8CTNBGݰ?CTd.|yЬ 8$#CJSAZ;hRIƾ E.D gGq"#e:l2˚]ETu<5䤞Q'cҡưU &OPWxIE8; ,W-[|Gf@>zSQ@G |)_Daϝ95 ) tfK> Q9KL25כ .CQ;`_Tm?wQA1`5"ق0!aN8p}T@B$΋.HEMʉU&L5ݟ k^t5Z܌fLEԟE0ænb<&favԸ 0Z8;ˈp=x  H+,ҥ)yZo6h^FWa//o+&D}eWfl|)^ϐ$xfoFTjn#keeŠϼ/{.<h #⃘,*~FC i(` Rzw!01QPϻ求iMOĩ8WmvAۡkװhֳJrD)qML2I(z?4L+KGUP"m??i C=WM#L2`@]XW%DF=v34ϛh~gM&Hq;->hVm v\[%QKJl`}*̲aV :k- &Q95k+I4\_ikN,`|, ]>f(*6aqFj{롷) e Rog*9V*rD^ĀE-<`֭ݾ4e=f=`G +]*Zrę7-8_ BC/GϤTpmnw~g ?BH_K%:>"!:BW#foR*%4j5P-LDHV= 7dRklqTӏ1-7(l`Π/̃=l H1q^ fo%<>H #^5~+;J3{B\ڈMH)1/M"yt:bQŝA&hiSP):?ZNKҞS SaFr!E$[d78/Z':IҺqpQvW If*$@``Oؠ(@rB%!#mDA Jg|$3U ?{{d1g)U]3(%1D6tAXeK` s1ܬ qj}MqG^"0NJ/r 7pc8 eKdd^ÖX f "7]EHn)ȴY8@GC@| +%b=@`Ql(E;p dlXA_o뎡MxB eQUL) VHGgif՛В+C,O҄PwEvJ"1T 62ԦQ16)=$2tu~Hjuto =`2;]e@ c4{-;v.Ōm8RjykCXC ,y 6p[se1w o`@ _(_5M.Z'gkEۖ}a]?! CY A֋Vۨh5^66Yq4Eњ !mPޱP2781|3Q+,Qn@pQ ZuUu˘ ovP,^T-1ڢk l)R`VDiU&Ra n3pJ2uhP1id [t kb7}fU*=|i,D7#q29\N`R3Ev5"sI%V\3K*Bc,xD6 91@K|teWXG!q"2FAJ&bi N|x@:*Ȕ`qȯN'hm6}XRDDe :J=yǖ•!e0,FY"C "Vި: Wgh{7F9ƷZː!7(yޡ3ekb@z*8u^ }mF{ɡ h"8`U{62dePif9A lb 7j|X2'*OQFsDzF:?mD.n|!\qH^~%/Fs;%+@իώ`) Ohq;%(a[Sx0oqmd=M`1񫮲mN5urfeV;ޯxH:PN*B{$VD_F$X=&-oF48c,;<:7037\Rm'\IXjizՊz))?)J¯DWUQPE~-Vlc$riNRvtj<,gb6ylg曁'C;cRFnbNkDݘu3b9 ,#JqN6vvL;]c8OKR}Gf9j}DӜ5jԺQkեx‘+vFq?Pj\m;-9y9Z ̚/~T &ًmW {hcTP'/25 iyyt7j'GԏAߝP"Lt* J{l >~j@.,:{!2ΌxVz 5fX<&i Pǘ' *RugT[XBaC~ɤUFNaX֭H^Q)-k]N+ndwQ,dG/9ذ4il`1$ޮ#xD APdbr/ZQ xWºTHlۋ}08 I|p1(Ɣ!:=<`ț?ë#.>={pKA'=8ݏn>Nw{>x;> ) 474T.uHqt2qqp QFYzLثߘwKh48(S8*CQŴqҀ: lFĎ`na3>=/)}endstream endobj 41 0 obj 5172 endobj 44 0 obj <> stream x\[G~w"~QЮN~ADBbINISת=v2ٳ;]]WWbWWq)yL\<{qG>\*f"ʫG_=ȷ+̕͢qQW^<ǓVf*|v÷?औOu[;̢hǓ]1n/a:G y_ۍu|sT^?bm&]\_ %:9$=;HБon1Zn b G,MA;|_IEr&*Q%For|:88%\I6)| Χ)Xdz6K#n@WN.ґNя.⃒.HDk`6(Z^狽 eSCtRU>GI%}Y%hELbuf˯TN[yD{RsA%A2͚dMMUQ/k?bqϚ3uopT[!i*sS ev-,*n*nxI: E%jC?;X E?J&`dmʍ!L%hIm01.$B5_|KtrB6 n qWVU%,3{>\y?M򋓒qV X.?d_v-Q.zsy{zڻ%N >$%>]nwK=W!yY{z%+Di;kUL:0MyC,ܓ݊!0|1P>Y$Iww/Q2tn&y/꽞`׾ r* њ,z~M&)8;X'FEEu3bފ0uzp& U(ˎ哖rqP# Xđ" gAEX3Zx+OPVb1Q'c!hE?kzvkDd|}EGe#rxYXPo$tnJdw_u_sf"^{ϫN$ӭ: Lx@I!z#yhӊ>GcM=?Y69_Tx5&kChІԊ N4 S_O-_^+,K~z#p3Od̢``*՘qɬ]Q)b&uqpN2f%T+0_,|TRLM n$4QM%5_)BD/Ă $va . 03R!*ڐymMpf^Ucv'7x|vSc>FrxG|fzy6m̼w,˫HhM'9q fs'a ̆%i> 0C2A&='к\K%(lFƌBaLA[%gVE` FlA!e=8{Z^u-%<sŮ~|i6GH@걍Bqr#v7 RpG;{(thoIb>>E׋5Ьy>?~4]^ Ҝ5MDJ@x'^oT[\PZ)ÒOE[C%(gjLy"Ra;iOu{']J}cJ]E$-D&D)L裡W~ɚQ<^f7cd^4lLa-(j؃ϒ2K.1K@ ggڝU'QtI4 k^TX´ѩ(Q)>VRSz]zX:P$FuGrG-JO!Ҡ }wh=M\G9Qs2)ܼOsuqٴN?Dgĵs[-vh681wYPN al:5#$5뭟Z(S9-Pʐ^Ƌ*xC=|Efja)_-Y% g2|ҷ{"M1s}*;y)Vs[հ !PĽey,^\^sǘg!rYU6M;vn'1)&n-#Ry>ap)A蕰.kzC>( lijo'={:O}m#t= (0҉4ΐ>eR{w($ &)g搦EI8fnn͒)0@9#25TfZcۮs]cDnE n(@Mu^,>H.yX-  nQ{13>)-R_9GH\wol{VXYОVYgfAKrW*3"P$6~ R Jo$a9wڤ6 [_s.tӤʇCM&XņQlсjlQ+-a#'mU fV )&zw*w[v"f+Vh<)7:PHиe#ϙ2Tj\vCȁcM}1QjdQ"4tO,Bi5x FG [wvBr1hSń]rV6N1cvK&4Pxl v6b'B^̽QEkƌs= 7>;"OjM{}x٭a/T>lf^g.Iߩ|0v#nP@}] ?l6$O'%JX^Wfr)~[Uj@3A`ZRw {6Ioͱى)i@ړf3ٵRvrV b &nҋ GnW:YԴJλՑӬc6޵3ު20,&} UC,F5E5MC#{%>{ !t؝ZPjɡ<`h+ُ|xՄ4u 3fc0 n7\sQEZ) ],iB1i`8oyN,NHM5Mnt84q%;t#bcG+ԗэ7,dqrZ"+I!6*]ƛ9z!g/CܠLV\" 9i.r}s_^/}agAⲸ}$Gf<#P ̐ w"1.n܋D,}nMW EsC8U_G[b0>dڤI$L7WAHs{Zm*_<=NZidzj9FƊ_*U&SUO1ɑbA= fק4s寧AL>"dgjV_{!&g]th|Wi_qTY"y~&xNC֊O쭟ƅZrwIqouaiQK&È1} VFe$oǫxrOGtF)$:i͛W;K9Q**=9?YǞsZkD)\(le+~XH #xMO*޶ bHX۝v}J Bg YP5+_PA"ì 5No{Q~rD]bzZ^'Ұ}i6S L0Ƿ̅6o$߽j!ѱ@ũ 0ӏ<ӂFz1QiȎdºjaõ&'Tz޽Gw Bhת]h{,ZwNtP_4³u'΋d,0?Ѫd|IңRr|`z.sǙ\p%ǂ-=I31i4GIݴUμ\m{aTbE:C39!9cB6O;:,Tb2Z>ơj7z>[l)iX> stream x[Io3DÌ7} c;y!-."LG(9>UV԰꫽!!ҿo/<9L:F&?]z܆ m OF8Q|u>oN*$K  j eD%k{L|W\;28~f{ ]8jEeU<.W鴾-? +΋wEVaJ;':=ׯA*0&ռ]>!8?yipKqzJ\Iwu,hQ sLûfǨnh!ANI<+u:\! ^x0f:03LP%C*h Z(/(QV+j,4k#`DYVQ=PĢaC=_)ϑ'̔ GwmM:1\rdz 7=N,DJ:*a=ᠳ-R0҈&?){~٫tz=)@ۊ&Ai WVџVfA=T9yOVw'܍"#+`\WDN2jŖ^W Qv+1`Z/Xpb o E_9)*`8㞮D~m[z(CY 1[;'C:hՈMjQͱm%TFo}BlNζIA!up$lb<HXqQ N*^5!ՄWyWÚjj{d<Hg Lm}$6*YICI(md R cRþL;ҔCq,PN,5/pWTRKXL-]*=Y?Mi \fR>?*SX ԐU^c_H .7JEi>! ^Jr A 40ѳ n00lLa N`iC5fM-?yEu,ˇE-)\ͦ-H:?ߠ5Lk$h\c&ȅHUrmֿUG_/ׅJ%rd'.S3A*ޒ2M#%IŇԆ{Ѭx[ a(ilLp\gnQ"Z|DmXi⨡t[n✖w3fV~|Lɡii"6tSۡLC|on$A("XШ y9rZ]1*H҃ȒVzR?NkHKgq$"q)#p4؀t]sH>$2_0@󬍚$ߎ~T2[CrGZ+Z\}7= ٶ%g_$ȯ] ˚E*^ڮzendstream endobj 49 0 obj 3968 endobj 54 0 obj <> stream x\[I#^8g.W3yc+6=Yʈsm0=u*o_F|Ed;̓:ﻷWWwW*r{{JÕ)In*Nv4۫?u6NIc? pW*5\qo)D5MW U񛓞ByڄZYi}|sTHǿNܞP>&k_3.|ßhw.&NV%w09 c~7iKx+ 8]+h FӦG+{=`drUmGxmhGk,!uFC̬(.n lɗ W c-W5-E `\Wt0Ys+_cנKs:?:ц`:{k+Bw)U2d-Y&|`yIp>t/}.6vLWG\uSڇ jY+$PM &0Q~C^ovrwCf<-ޟ[%/7u9@3gbu4f3yZ57~`Yahc ܷ`"ب;(PÍW^Znr?٠q΍Z~ )f>eH)?/W7 l2Fw[*5(k &ddu*BB' 7h~]vh# ܐ2gC 0qbw e6DR 4 FSKv $m#v:PdmA 4HFnu޺?;׹ۭмÐ"[yͷm}SA*w|70;F.qvj1UK|mm ox)ΐ6XwBYĴYm[ vK?9p*0; XDdY+rs`f5%n{Żh躵fXwQCJӊ0)\}0[dV.;1m Ve ux~thNK[;f/YQ=?̉hZݹ%]+)Zۘg`(>^>;~K_+(@oi0j&YEJjL AOb9v+@6i JnJUa&{ul̦i%|o3,O*(`^ K6Xՙm;{bC5Zhfgi.ȷ:9sq̻i 7[K[k,ͳݱ=Ao;UwQ;kedlaJ{m_'f70uM,,X3-2`XTG$ՃSWmVUzwA HFalWcdSۿkc gkstgUv" l2kUtpU#OӲX ][YW^%2$ng/d#lJٰ!c 6ԗe*+HsiVMLX߬k@G{Gb4 v17@ b)#QyjK QלyEJi!2v[hIhI|s:}65d4{<*m^RAwX20P 5]z D5.-1B un~su?l֔ PBmõrHV[H|tk#M +쑫-0]ɲPe|fϕq 4xiZM\E+b=LHT1*Դ9L98lbFZL?l ufeiցgUIzZ%q ySL7h),4l$Ȁ4rANj ES(֦@4N޸UNoRr q,H*[wq Ru},&0~9$ `n9]<}"}a8a|h:sΎiQLU/cB泶. "$d*4IګiCra,A5̉1xGXPh Mȶb;ܥ+Ij:d83,e/ÊX l {"o}DUp'$vUeEsVf`8]Wk+]5QM-τ^{!_ jQwn`K /HE: dG&;kv/lntdAz]0^)2S=#OPM8X|JYW$G_: ձ%lj|)C$y)*WJ<.\])zZB:RORpj [(?<[Q:Z#kOc ,iЧ[U]no|b_B5+Q\ʹj jJj轣6RtI-:ʪJ[I@*%l҆TXjHm#)> .z "Ƨj&DH1 \:B$]fw_nSus_*Oq!.Dk"0m: G.gDM)E 6Y]":M!j=++Z*f2~~֥ $1IBSAvTMe$kf=s{HNL< WjmEȓ%92cehnb|_F!F87}R+l/h4\ĈmХtZZ6| bh8!*mWK"bYʏ\H#pUrޕY^$Aʉ`=#}:\m&9qB9*ʈ'cH =T.璈͓ȶpd`9vĜd֗w  U(_D=|yj8QIJ0jS^^fg֓bx[:yc:(@$Yڷy3Y9xiUi$v1zsje2׵&q-ko9k~ôҦ!t)}qqTdnmi[POw?ԒM1 Οt4G`tie1V!*@ fbV5WgWI3Iby f:.gJرӟgݰD e]je$ŒzQ𞎉+&V]׽I{ "|z(H7y ~~atWk" t'N! ~^9~jx^?͗m{j`=׉bltYU2 o8#R7|rX՟%Y`GsL*n8Y,8̚HPE n4Tuum=ki4V[ѹcjc4o`'0@cI擟a)% )h\)Z^j?|'%jJ>xNhqNϡf=_N] CD\)6ѿ=aDjr%%g g=|H?a1:Ϧ:wWxl<.t$`:ăMsO~;뇿\=A]=_/~3׋/ߥ}&/9T>닟3\a{g{M0D{L8)x6zmOS*drx=[Or@y r=_y7W\v~wo7"endstream endobj 55 0 obj 5105 endobj 58 0 obj <> stream x\َ\}#yqwAcFؓ(@mf|yۊZӗ\OU*KK?^?]e.> W/b3~)Z/R Qk ?Łk9=@W{Xkv1֨^^g{(Ņؽjdz0v?%teܽ4<΄@$VED?`X#*?q:2̑S`4%s6C-abO|l6AiB@s®- /Z"̉$\1@ٜ׸&\83<k".]#a64#\tF\E6#bI9ʗVXɫ}W56rw}?v ӄ@hHdyNOD82)nڵ$CrWc0j5g0r( <ڌ!ct rB|͚wywq{vc_1Io]w`Qa؏{* } Q'u{%+97)s f@RW:8 p }E:13E3Ћ4Ōtd=ނ'f9kU}MTuǎ$xö»#ɰ|B01qj%m;'_iNrحN=,]1q`7[5;-v:ÎHଜƺ{>Qz1MnqQ;Hސ_;'|Թ(-LoUXh1< hqS >+];XZb+J,?g{.ϔħeL.g&:kix4BZ({&xKEZ}dnEogB(i.pomcF$;<0IS&@k8xuRa8ggYu6~ibdq3_&li|-L*=:a_xɐ)o!S`8#كؘOsp%]'|&RIp+Q>o{c89kFl>T^ah]& m; u8q_Mp}´hch<@`^w!!4w ,M$t3g95iMh_ൃAA$9VS<8Q̤CG. 5{+ ^'6Dʃ4Ɏ^7,.`0D5Gm5 .-DgwL5 ОqJ.jC(ս!}u4o$eSBOЎs^$SYRI$ 9- +g7z➑TfnØK\!L=+8:"a~3 bh. i|b$BL.lksG"?UdNS8}qHd 'pn8>9etV1jWi!qVDs T8RO6TЋ> N鷞6É*T xQɔ  ]sB=_Eg㨫"y.n&EqNL_,Dg?Uv PvJMّvT' P!\+4K3x7,<u.,f10Y.@0I {Ѵc+GhKs^18ATߴKV/) 5]+2wǫ:n!#b0MDxGؒ+f[MMjOOAˑ\wQlfzd߱;ҏdZlbL yU29US&U!/AR90]|Klxv  Sx42 U&魼 Yl?{9L*M$9t_H"u8`X4` 4FΤO5$ܠ΃ۣYIOd,/ĕ^a +l8%VѨ*§QuJ2%b pD'(*8"WUt$ Y!88=zrH=Ԝ+3?XپhAJp DlDQȉT)(ذăn/J99{B-DI8[iU,x#j: tMIUI%%=u#ʭ4ꁐՅ.2qHj(5BPIQu$x4ބF+giM ㌏$͕ui(7mQQ0u.6ĀH~6 P A1w~nu"= u['}H/c!*C4\o:ZH ȇɟ*Wd DT^߾V+ c-D§C')hV,eB}{fTπo/(v[;$AZed3,7KNw SvL ~uOS_hRگ B[;fNĊ OC9_YzUjX_4SRlT'$NTC}iYNB\ ܷNg7 ykPV1g6MraCłӈ)bͮ8ByJ'jT۰R OWOp1Tj7|s9F*@ų%ۯ%2W'(}VqH(];ea>-n92vȭ/4XxP.szf ̓B :;<ްICfJ9q4[qQfx+V78 wn$ud_S~m|z9=%/kFX|&6zģ 秺L+*#w%.JN-KLI7yVMӲn^2Dq 3c=DuD!#!Zi"ۆ,Й1 <1&"ש8zzySUv {Y'BNuIA>rL5T;*iL~wיYk ]WFpu;*h-lAr_ℸǚJ&T`~,IҠ/Ŋݧ҃GgqKGY%YgQҀXsʸ< cwјVu(]l㋚Խۊ\QVg*`+2o6yE+PbPAĚNB]K &xsr>QC16f2[sxJ"F^=y[s+ɨlVө+3{^%#Ne;Q<'behɏ})PmWc`6ڢ2:% hˀfO=\Azك~$skdԠ6-;"GcóTLlkW0Ƌ5m,k:a5[CEBr嶹Ir(ǥKyyr-7{u~C.q=endstream endobj 59 0 obj 4443 endobj 62 0 obj <> stream x[Y~_ | {1)r(%q=A/w%Sw, S>:ämؤ?A; ;=္Iky17ƶM'~pMLk>Y\ml.j2S6S6⏰bRsfydú,3yW9+Ɵ%4mx6W8ßMKXt&F΍?I>0opdǩ:7~ש.ǔ4VLG~VMZ'Sy[vӊ@!7|@ą0]ØX9S ู` 3YYqi\lYCD.wMltdjѣAe@p*xiu`]}'$éRIy%c7!ӧ'B?!n%cZ01팶5KQ$4=E t5}?D` ?d-n%5E< yEܘGFnI>8~-^,~w5-|@N`_Ej<~t0Vnˑ:FO}lD{3t&YUp%Ȍ eWSI?N7‚qZOH#4Y&Hkg*o!g"=0F4>[h-9mJG S?wCΘfwUD{5C]QP2J huX%X IFRnbl`<;t0A!!Ha!~>Tq;TvRGq]b-uܽ qt8^gqNxgMp̨=9 :AgVF Kt^I"Z]4%,H}I%1FAsxZԠS !pdk(ƚ]oTLw1cנ w(]u V5<߁ _489pKGR2188k9XEc4ϩ򲰐wdQmqy PX*BKvrĢ.NkoZ` qv9覗+Y pU`.\5\ Hqkn:݈<IE 8N\%>3'@mY;p;"v .p!1C'(gzU<l ‡6 \/66i l)gT< 2+ftjYH/ V#mdFy^ˤHW5 Dӭ."%|vG_RbogiV,HpGbNΝa!N1K;;/[E!· 4L[5Ȭ?@6}d lVd-=JBOnXOk \T6" 1#oҗ Բ/Da.i$Yj+ʥ¡zp IoRk[@B p0#.nҔ(yljGkU0'Xǚ?ޅ8"A 會Xd'Qy0t;g%띆0^j\[*ƕ/Dgn@]_gS=Vx`!Qy2揣vԍuR~ȳ3BlN<}EF-RE ~hmGJЫʁF`<( ¹roSK.ߗkû Űӆwl=DyxDzh{рǾhC8V{ш}m'~cߨR@=DZZ)wI0C]hE=z.c/P'oCwkNI41E1 o!|]N[ST mGv= b`s&(yVQ:-ӛ w pNq\ +=}<:,ף\;^ح5ud_{|zGN?  !4q/KW?.]@;Z7aYQwk(n + w3Rwydy(\U 1`T澮kss ];_7Eendstream endobj 63 0 obj 3037 endobj 66 0 obj <> stream xY[o[7 ~#3̪mݺu(0l52졵4hliҭ؟+#'m!HrCQ#%_p&/~ߋ'"Y߅xI&2b=iaS1̟_@TRɤ^,A{cPy\N_3sƹ{!ť9ٔyIiiD`Aӫ:x ۨ( ނ$,8On:6t|-t$_e$lH^о>f{1JB`8Y}~ qcq'kF-A1ȮO.l-w?e1 P$7?bv3Z < E;3Kv<Zc%Mi=g t=\lB'dlDjޣKaHʿZpNCO9PFw=Ѧ?6r->y3 ȣdwEno:|ch u 44r0eO4S¦F3#|\=J dK p~v1b!3959% 'jİLp}@5*'hJ2,Jmj, U0shg_jd׵/XJˬ2-J,Sg`AH$sa!ASTd qVAC!Y2fJA|ɬ%hRJ*+w,[G3n,Rvm -͋:*ULbTNΤT.X2OĔiTL֮0d 'њmѡsV|ZN6KACzsj{ԄkjT$&0AtDz]P ˆzvSEq5& x@`J nfZ(G)֠~ SĕL::kyA ֲ68LȏJ ʜTƌB 231c[Nb2̻rWv_XҒhR'ҀX]h:WaH 6cjWVTHHҶ9IA[EBJ+ػN/*`O:+ЌeS@UB&IF{ LݫOwh%]#bBP m<4P\ǻ->(Ωʨu!O` ";.РH$j k[(ӾgЈU.+"(gj_K }I:&C7[)"FeW3SA6v=zO6_qW'Ͽendstream endobj 67 0 obj 2254 endobj 70 0 obj <> stream xZ[sݸ ~NG+$ffiӸ̴t|'Ǘl6 P±||ia'YA~nj|n|vsxto]BKN0:Uao\no}_XjmkgJ-l_{AVZh|;>Z4ѝiTU/JYS9p錭>-T>tnhΡQk=  fzXXu墭bq83@yFLax70F05HX.L AjWM[Q*27(e?=D}]Zu˶A=mP^]maTSZt bY-ci@ Ƶ d[}ADЁ}_LA8O3  bqv$=(k +֊Ԍ%׮:"doķ#\ pp]uր~Ʃ)*%>u:A _p QrZ;[@Ҁ~`2ԍfY:HoIGXXW ,|̕Y(`Q2q9sXNgn2BvoZۓ2UkK-n2f̏j;xP=~ܗDNja47mgqn|pXN>ߘ'AXݖ5L*}v_EWL[nP%m\W9@K- zOT!5(V~w6I]E4os.\ a4&R`"дTblAԐMN(]n쨱O؜-wkFG5_qݱDi%}nl !A^sAb҄ހ 8,@5/$9zˠ"ӡXi){?,%ؾ YD7̕!\›Zhek4K ?o3x%CjD^Pޟ T8yOR`EpKWMX@z}]7dv* 7u~LQ=ie`:\]b66r(2o(nyr;CMʆ0YTPCڎzd'7м" zZ %Sj)BhX~av: GDx[Z !R=W^SAzX_q6moe~)y~;IhWg8{8lLgg1]6ls#6zvkx1uq}2 0T-DI/7~ǟVRJJ@cúP?A.d[U@ׄ-RjJX1-xO,յ?.SlLPo`x]3X_ Sɕbڨ5|r7 $N*VC7i:u W]_.Bױ6lH%ix*~*\sj<+HEcړN$+&`Q%Iʍ҈ZJS|2F r~nvORLJRen<ɜ OAQ  tb?%./[x'QE2aG"E%{jMɉ;-z7ɧNsi&*އW/;fo{M߆ Ʌ1~Bу/ (ME $-IIeBTo6rY<^d?E6OO$}8xM*s\iqrfV@s7?Iz?`c3}8A`=Fl#2Bf !9'՜cn\%JN2Ls)Rj:{WGW>O%ٗ5 5[2mbHJ=p|Ngkk"oH-Qzr9R[&\-wpep%I$_w&;z1g<̍o2drKAc: L뒃`$ o'FoQ1)sCi2+Iג*It ÇOZUaHSC)fq b/t9<įD@] /Et\eB\ul+v>c&׊$߸- cRuhT%>d&ay{շfTem]k`sGLoy,}ekzaߘΩ50~UݫCR7 m˶7@f])x)E|)ID0O('P|9"iQy!Ԙ6;!1n'9ԯFW,mBIl()Zy,R`+Om)7GpbhJREM^3m3wHׂO6܏OanmGfG,E, o*Ϥ j]Ĵ8U=Lt~B*rPU? / TuS#;jȘ;4X0%$xeS&q" ;.N :nFg1'Q)sf,qX.T#:q&/Λb* ;hb#;"Yk1$Wͫ|QL2pݘNOZxVK>%n۲{[endstream endobj 71 0 obj 2931 endobj 74 0 obj <> stream xWM7 s)2(HmIlXۛmȟ9F{  0P$E>Qo)IB6ObvN~ֻr9( 4׋h o B5EFеa_-&W^I%jzR^lCn'Jmz SmLU!?_׎r9ޤ|Ȋ&+`o~n0 FFnY.竓/nnK/QOQ}4 -e\b0,߽h0Wu]L`QuK0:nEM畛x%Òr%7uy})b^+wV֛F<* rʸ%L%%el^ީ'LB^ cyg9R3(1t J!0Bk+ XLityEiGF5 R3_4#4Y>񌠋(+.WYcV(l]6 8B*U{19mGiMڰ]W4UIЯ+f}9"pk,6Dz@4'qJHX%C Ҹ̀+Cp8ڟX!\kA`\&NBA*20}ƣ>_~ [Cd7;׎V܎[! gW]qRu@Pz׻ u~bo܋m[`KT*U7iM]݃4ٚ"L߉>9ukw.!9d5Up:L@?v㖈Wlh0u`y3ZQp W1Sf 9SyH"ճ[<>Oo)na)9)91('=x: y 燲6sj_yubEbgM>p fթaU]^o,'Մ*=DOSbFm5˜->PC!xg@'m*PڭJoK v]^SN^DR^cO1٪u*e7Ӊߖظ*+nŋ϶󩒊xo 3gtQ~;!(x\N6xendstream endobj 75 0 obj 1149 endobj 4 0 obj <> /Contents 5 0 R >> endobj 18 0 obj <> /Contents 19 0 R >> endobj 22 0 obj <> /Contents 23 0 R >> endobj 34 0 obj <> /Contents 35 0 R >> endobj 39 0 obj <> /Contents 40 0 R >> endobj 43 0 obj <> /Contents 44 0 R >> endobj 47 0 obj <> /Contents 48 0 R >> endobj 53 0 obj <> /Contents 54 0 R >> endobj 57 0 obj <> /Contents 58 0 R >> endobj 61 0 obj <> /Contents 62 0 R >> endobj 65 0 obj <> /Contents 66 0 R >> endobj 69 0 obj <> /Contents 70 0 R >> endobj 73 0 obj <> /Contents 74 0 R >> endobj 3 0 obj << /Type /Pages /Kids [ 4 0 R 18 0 R 22 0 R 34 0 R 39 0 R 43 0 R 47 0 R 53 0 R 57 0 R 61 0 R 65 0 R 69 0 R 73 0 R ] /Count 13 >> endobj 1 0 obj <> endobj 17 0 obj <> endobj 21 0 obj <> endobj 33 0 obj <> endobj 38 0 obj <> endobj 42 0 obj <> endobj 46 0 obj <> endobj 52 0 obj <> endobj 56 0 obj <> endobj 60 0 obj <> endobj 64 0 obj <> endobj 68 0 obj <> endobj 72 0 obj <> endobj 76 0 obj <> endobj 77 0 obj <>stream xX T׶RD4FY38qQ@( 2 }AQI(Tpqb'/yFhɋ9E.~-L/ ֭:>څQTE.):U ]@<-Կ am腣z3jmAۣB6os1{W_8k_ށy?:ڸ/,%Y[:.Wu\uvжa~!|BpVTE.Ago[RאMn6[#4~Imƙ̬e0s!Rƕ817f3 cV0J f"LbF2df`1.f 1Z/#0m3 1 3*[]]գԉ]5]5&9wtz{|sfόl_W^{3LO;IT}ڥI+p8 z6-L sw@` *X `zdF/_lw0I_jh;& qe AEOeH|5m:x2g,Yw{j=OieBFq K*5$[_!z*FN-6BF^j% 5n3@Dy=giXSIG8b"Z,ќ-&z]t%օ ;vќV5|uA^<{Xc(~tfYzy!kmn=Wn>o'|Fne2$ EzOt[BM}uo\qJ~BCM_ rY\i Q?Bd5i,p(R!\D,&3d水a掜lp(}2J&b^J& %#`. .`dي=Z+|-C6Z@pfҧdAXVY__&Hzd&KYGa@'VFӮh3Z@}DW %:N>P Z@f8TWVcQ+MMbͰ0DAIm3v {)dCIXu:gN՜n-ɰ 18ۇJ+uk9\! @R(-`+o:]EU g>8?%RB\A ]gŚM0d婇R-P|/xjBR|Aclp}"(L$*.lhUe 3*hLLXs)-ę$'Zp jrx6s0S?{wRy?2$ QLfc efK. :12^{?(0N;zn=^3)hq/iGa}4}kTNm^}ij| ߪeJ,8\t}qݛVnIH2Q8'4=? IuLpBWRgn,.ϩboW*mm&,tV7n%CiŅ(##țd#:!Qġ-w%Rɖ W6\J籏.|Xz.7jM2mk*T|ITD8^I17[ZBIrG#|Yw/3]q {D*jhyHP<y)8Q0cNF+WF¬Su2::j8&E1Xs.kN£zvV(q%f'CғحyRv9Ex[␕gJ>F[{,N])ED)j'>U;**OXq0QیBR#VU+5UwE5A5qm%ra"| ޹֯yuvzXJt)zݑEl1I|H~TYY~]z1*ǻḧ́i>>Ua -6&pǙG[IxoQqE qp/Uvc+Q6"ږAʵJJ$,~X _o*<Gp ZUpLz  ,FGI7TUvW1 %͌ A{E.sr`oSN#DF^bp{a޿6n^{ךt#f這c!|PX19/v Ia:"!`TZZNB!ద5:ߊisP=TLnD<&h{Z|8kV,?Qq{N+;-Wȫ:! t,{VVZҊ4 XiS(z!\ X{e)Tw@1@=1&]>0|,ɍ˦:)>pF'Ƀn0\gM׎a7 I)77 4h\*}]5#qvpt{& E*ji#ph\ie 6oj=,ЦMvZ<ӛa ܘ(\ph*ç6JĴP0>g.Zɴ'N\aYu{=zjϢp]ڣ D#*EƯ v2)đ κ˴X2--EC'o"zt*s:˷>pdENiQLJX,[ÈRޒL>9uC!?C蜐J硲fB.8CVjV:ii)A Ao Wd $A7d5Hliquޖt9h )ʺde,1|`+llX3C)ee?ƜIΟ^z3*QcX|!`UtT(b<#Kծ M>8IMa+xK|-́C%1[l9)1;}2r9M|;B "WsQ=CJs $# 0{G]Tjh,q0=6&cc1dpBTsN(jc)mgm!=7-:pLa#5ϾJ bS 4d}:YWk4 _t7VqjsKhzM55MHI$+Ta,P/mj㌐/N_DC_3s|{chǝÑSCxJn(< vҰ#~ĸTw/.I-ܐlyo:TU_|]ibšl+[t:GGxԢJ] :ByH^(UqoPɴ+V$?cQJ/}G'`=M/Pod {۝-8:_qOȈ@wC^\yr(þAMi1;@E,N<= f{Apf< :LG\Kھ tu%wa:.0؄.{[Z*ßkuoqï~>FmU(}׷E4;:J<#s֛@m}Q7E1%bthV[ߪ(m8Bz'_MN*̸6lXIݑ!v:Ц[} 6=6 oA endstream endobj 78 0 obj 4530 endobj 79 0 obj <>stream xYTT־ܫ2jDMzC왡2 ,cb1vw.9\@k֚5so{_շ%$l7X>G}7X3٬qs~P?MBEk- >jI,7&3tk([pp}NlGm33tGMG{ (~Mtۥ1A/ f)3}W0eI$:ϘILNa=k- |㠑;yŐ֡Æ }fnc~@YziىC,fYtZ&[-n>qx#׏ Y2uvQkG=|1ac~yscvܭx&F"uu x$B#/ džJPGuVM¯[5񯤯޲J[- K%:eQ}ӹM+PgB,iؽk-qƟ5 5ZUjuC 8U `Οn {a?ҴS4mY\f8B2>RXz0Y[}C[ .@M8D.BK`@($iiQNtf(Ltf:]#'2(ADi 񾊄A+BCT)=LuTqxdDBSOWrdUSx9s~%yBڀ,.䰊Qۡj=BHV |< 7Gn&f= _kXK%Ȗ^ƣf٩d~ UqshauM&4PC}71hx=dRIfӬhwxyw"sO.@;{hLQ  1/INc ^dY֧e X_KZ Z/B}R˂:lqZ|d`Em5@3&9m6J!G /K4z% 1"Ea aU[/H(we21~瘨Q|-1RtNb*\/DZݘ!wo#F^#㛴4ROFBFr.X>;=i:Zj.KzsGҬ_;~W$+&]5҄IB48%!i2tш̑:t3 )B>, v -t>guuyJZ)AVX/7Xz>&jQR w!99p+0=(7FVFfNr종4r]M yfG}8؟i tvY,v"\8JbW(Z):loH4Blrކmq&hUZ1&kQ$I)Tbk%tFVv=Ij)2D|4Ծ t>=`Yb|*9$:-ֹ-,R )V-Gs,\lGE2BVIY ~s,}e@=!уxAU!0Š/7T#oZe*c!l]Ǒ`|4 ͳq4A fcߌ6/^["pE2Ӌ]R8˺|^j1]5@GOzu!Jn%_$FQ9<%cKއ4hA$.Mѥ%)jXkH;90\&F[fIw<>=?Hxy?9#;bum$A% }YKp4bJ@&ˢa[";W 9?wz,rS@kJ< nM3 MA_SP!G?sRƼ^w>7v.fChbQl}ۣE$RD!켷EoX?;CU!Iq+=t9L6;#03f2~}:13ȟţiK vf}zm7 ZfSMhlDzHb#"H- ʄG*67wh ok/Oۮh"hAN;|w\b.h @ԭgN !/~#/DsRUhHP *z@fm@J_vքtmUQ>/O_Y!4o J's(USIX 5W .W6,ђ,Njǃ/tq*Md"0AqҪz:ޫ\95ԇƲ_ɫ[pn cWҫ,Ch ;Uh닪d-܇ PYJbx;0}A2`\t99Bִ) k49$21ŹeY% 7WεU+u6zV%E#OS] 3˾ۈ?v\oRm]Z]&t(10Haǯ8~hoC=8,:IQ{V݄ )8΅lס:SQ4or_A5Zg+s]E gXoFwyo/GRFq#:Fo 2[j i G/XOCq\]y*ao㉲g u,'p/#MNr7"2iQDKmܹכ8U[=[O*vsvm& )D6Hp2¶erXd 5L椚}W/ق<`r H3>&vKQ@\&f~{vKm`kٿ= oJ0NL$}<ĮdƳІ+7?#tWB`@A&4 ^%F6Niou͂QLėƃ$6]|; ZJH/&b{g((_]=$ʌ}2^>T} Zl!vVM/B0n(n0B腪0YM2>^F؜+aC#4͚pdR)-q_T( D>4_+!]QbdK+HI$xt i(F-2Afhӕ kT%@ilu4rHĔT<ߕb1`7hZ/9}mm"Gj?ԕCiꄣ < MIz4 ôXTGܿ m'i<1\ I 7@0 []|`UF#Ԝ3/$jE}Ѥ{btMdjIxswW7"v;\{ÕOɴ= Z՞GpYID >]*~AңF!']&1|S:-aαbj"PEw3s2yYQspTdHf/6iB*YfSz23 GM?tU{(ӣ"Ą0ua uUPE:Wu_BcLȯay۵ѼKW,rt 6ݽƵ oNCr_ 9Ҥnطx;0 V80Tזvhe5G9rc̋m{uKvng/ji/R|v=uNmnGOA 2fׄX dg%LBT~$a왮eC2&%_,b/ո<39'9p" r kVca8jV| ZMwt?PGΤ&ڦЧ@>#[ok 599.X֞IOgѺ:8趻5Mos^x߷sz5f=ETnv^\Dew,G~ >'M|REkم54.m#^Y&}%\cez\"7m^S'+ 4TzV7xv{:sʰr"ե$}gDP~<͖ޟO5D`.D_4_R@OrijM:H3P-je7]tnYbY 5zMW!=- 9vuZIPϰDoweh+;i1#L(j<Q֊;סjZ_'p~{oЍ}R;ha졪ФTJqJM EDpF􉳟|Cwl횹q&#$ٷ}Kv/i=7Ugmo< oW=;,sy5:- jvmj0ß1YNr3=v':8$$PI|Nx_uDPIPfz :֗lo}wFT`)CZh{R4 -^d)$XIbBEyxIlv&t}쟮bېr4p#2C|LPRP,Zx`Or<',X} ~u>H w"3R y31M,],Iς|yQlBhE]ˢ܄XybBRt0>OM*V OO)IѫluT2hB848Y\ARAq^~AnFU UJ&]Q^QA2;&[C` :df-b~aQ (z )Hpn 2 e|Ce=vAy5Aye1j(S(4Pvc/8?3;WΈP!; .]O{/hq_}/o!t:+y2)IciZYV\p}y 1k :fA+~+;$ݺuDE/e\ WX*Yx } v5 bt\{X H_)_7uC,{#F0"9 -[H;m9:=dv@(Um]*x)UE*fyW~Bc50ek5]NN4\]z^6WOg#W;6ˤw]ׄT,?V ͦ$?al%9۵O(cLi&U֜`_C y/~*Y68mϱcQ6kGlݓkȪMo&\nE/ӬIoZ&7d]ĽL8aO?}o 9?h)*I HuNKMYMZxf xf$7 lfL/ioIQFU|| 0 7F+Y^ F(=[SPp4ΟmZz|b߮iciM56ߜ&2c+PUs@<^B)ޮ9)Y_lUg2:Bg:r ֓։!β;UM:R4Hb]ryp$WN#W:,)G֘f2^)~Bz w;v+@ m\ؼlv;~|Ϗg1m:6G./9*>X8~? }}oMm\Tylf+[pؿ6=gll^0gٹ;g/|.PhJ1F2ڦw+A';]Wb + HXuKRȏ1̍ۢ\87pWX_MB}?jm;SAll,ps^\stoA#%N~`3Y//PPo4Tό™]%UDuYEkmRS~n"NKЁVeitYUW:R5("D侘(LiI?Fp}JgOel TlFT'u "3Ff mhM*!4 [|7[tcPmSv.Q 3k{\4j 4܏?ս+ӂj8urDBD~li3XF\S$-6׿<À~Ɯ. a_ endstream endobj 80 0 obj 8341 endobj 81 0 obj <>stream xkLSgϡP_AD95*lSC6aC -^B/i@[(~q k9lK>-snٖlno!qE>o?'$gN\HME"i~vVLVY(,8)|Q_x\,& E2BnL$䈤A "8IC&i b}3&ɇ!b^q走HO D|>2>/ IH&ARn9;]P1PҒWk]x̣G熎U$BB"U9( TfiRNh1{m}+/ MW&7 J$wՠ=N۔妓x0a򑋘ĩ8 { HeONM\:%q/c޾Yjsb^Sm4@m-uˠ*7/A JLApi~LQ/~^T,[Hbms]H= c3Ou[aV+'#Qo<g/e|,-?C)ؠllU'"$t+RIW5ewX#x~7/]w`;jz7;m.pr-# *GybW0P̸jyKuJeT AAmuۇ Wzrė3>*dQ'S)~}!ۏnms4vXW`4)2AO#5+,6s=GmWr=ŜRH?.8}& L`B>cqɭ Z6'.~3۵|oAͲ eP;5-}Ixs cfb48)/} R aBS8a IZ#UMuku]=;XIcge9imB+SӅxHA=~8Cy^o9 h2R|@Z{bohmN F.N#.ObfaX5Emb+WRhr,FwPƮ:UBAo2հzBI>ckJk)չwsh,#hЖfK34Blw.L^zp@4 S1lp/ps;\}FjC(# _k 7Gߺ+ endstream endobj 82 0 obj 1407 endobj 83 0 obj <>stream xV TT>9GD*oG%@I+ me @4(Df თ<K80*eXizu{,]ܟHi\ά}c,,{ͳ* HmANG/LDyjUD@,mB\|#YׂJXmM2PIbӳ^P%%)WHSMbcƾ9#=V Lզ0 39+%5&`s6N4=!4cI &,af2! e15Y03`0/2?Z虻*=]e~uN.˧S'Mqg<},ƻ2u錕mBV&jvW oJ!6C 6j z}a^AƑɓ4h,TH`B7 s8^#(M>Q!rˌj-i4]ºcXjslaQ,'qO ~8/NKANtRs!FAΩ\K8@yh.*L40|ؔH$me ;,l`5Ȥ6Lp#^gD&~w8Q$F"I{ tnޗa^CFƵl ^F6ᗔs Qfd,o$3{{,Gaʜ\`QHG[&(<;~wү7u):N5C7˲D?ɞerֈ+LmeO4#|fؕ!.[nd'y$%b CWtWh؞|¼`xׇ~ߕ8(·{/ޭꃋ4Uc~%T nEw4O+w5֌cڏg~3BdjpxfI=rHnp@Ϝ &S{@J7IAd(jW@̿-q\#{н!qs_t'\D#jCR}vbˊý)̎nSgw#TfC^1!Xlj;r Bmjv f%LNimf ->?Dy/ClL)"IB2oWS:s準"}1d󺚷i0dԨuY:1%2#yeIRUtOb[Y捦mi鰚> WKW߿x#Ĭ#ZV7h! .h Cힽt{B ?t+%~ jɱ=̎F =2i%31T#/.nڕ// h¸)Ig"2?=d l2{SGLtRBllGBKGH^%˩՝BJu;ʞoɰ\Z%x4鿄rΝdK ?79;Ҽ|QƑ9s]ɰfT4fS!W^&Dюv+Ѓ@7*={*~y%nQs?k-¶*|4pD zܢA  GT,ǖ p*^#GwI$ƶ@ly&sW"[q gyT/!n\ J0&fbv  & hǕ ]!9;QuB]wA:*\I>4H d0]ol4SY% dON5sm`P 1+8b|CQ?bR(RB }cdԋ#SFFbU:t&>tOɶ¯‡eO|htOFDۏ j<)?M"_v~RBM;vbG("|ٯ9֨~LCuvHx7+hˉ 4eY{.#J%]ǔ1'uKۮ-] 3 {'23%Q^E % 7YZ |~u^YWMQ7ĵk+*Dלj5)ĨF9,Ȏ}]tqAeŕa Wep endstream endobj 84 0 obj 2400 endobj 85 0 obj <>stream xYXWמeav N53Dc-(#"(VcAQ.,R^{ߥ7%V,?&519c.wYd/3s99(s3J$I:LV& pE ,A@1 "ò vJ=dc~`;el=w:˼:o'glY͙KQjMMFRbʎzZE9Ps(j5)5MPS1 HMRk)'j:A\eLj=L->>S YT/7ՇKS ʒ(+SKCͥP<ʆJͧQéw)g{ʜ,]15/5|mhΥ,43fsפ^{z[OFׯO?ŀ⁶4h,!r~V+3aox!5C[^ih`MoC?zmؒa-' ]wc*{D>B/T Ԋ@l]TQgAϿ-ʝ+/'; C8 2tDt=6v2B(X+ %D'$ĠHƿr/*y[Lr,Uti j(L̋҃QbB!ii$VhZba@ tq5y܋"*T w΄x?X/o+ ! HeI}56 d ; тGD6 00 W8luǿOJőӨ>s8VTMY۩^6AKwuڕ󎸀V} ;VN_MԪ:^uj:*e#>qq>fmF5Wi|U(ƻs" Q1sAR*C}O#'+6A 3Xr`<78M<ߞ2ᢅkZZ~,$뉔[tI5qg RnBݘ&MV>Jâhmjx5_$Y6kޢ7:~~5.1)ȎWhkGh);0bu0_KϠђ=K=iM6jODn|jTݩ01!Ұ3ЫDUl BVl P 9Z\$]U&'r)I(%RSYGi(vKwў +CR삶!f?`|K6ai #E9,‡ҙ>tJً\}aGt?ۄ=X-ϛzZRFT;G 7 ֊݄ba.a5西wpch`j} KVhKlѭ#6iLO.sa6en<0pe+g _wDc 4TF,uȴ頖X , 9d7>iv3,CކsiH:u =@P;bc}t5l"XѢkC"L'$bSL\6<[g8lcIqVP,6k$Ʒ@UI"زypլ&h2ŽƁٵc-n0!#7 g;];G9FDLH&qKѬ,4ϙy:n62ݥb""g EGXcNX1ZPA|aݺ  ƃ CtGu!zeMlkUԃXuB0 lC{QE9v6--IWeODXD8<buЏK *W#B]R:4ke.n63&{xE#cq ]͗,/("fU7X JXĮu?~#ڴn-/6=e1oh5UY+SZYP_h\A彙*ߌZS0?yIsЧ(r9 /ۿo =vtDqizFFE( ee1_f?|,IW[ 4cck4e̳ťyfLZRNT>*! X_z Ŝ= }w\1@uaNɱ"!WGl|`[J#&V"Ebjrzbw5^`8>:opg{:>&dd+[BO;9V!Ofgjm`:]t} JRƧp[4[xal/+y'qDsMkJIJ@#iCnIjKy&ԌMߞn/tQ 1(uao2So n[>Ё]&DBܤYq۪B\|/Zʵ 'qo/6Ӥ\|hwrԀ*BP0rGNȿܷԿ.|/b.W}TWq);б3QNl%U/(m4-!\5]σP}>DGa !` L添͗e*²jc |W0C#8$}ð#^;>5T`$7@Ѐk"}*JP*էY&|}'͞nq2=w!*$S9&gf=3Kx@"څ!03 c&A[=R+***UU(qUojٺoh:u܀ 9ڤC2U̷غ YEMm[Î+ mvg\X=+s/hFmN'eq ޶6P'1?ּx[<% 3TOeEɦыّдO5t5v^ !CLwڽU_Ml!THwoN4qPw\z.yu-F^XT7PoIp"l:&_2'0L>TQJL\3yJ: w*a4`EpǢÅ$IiM9D{ԇlՌtH: h6$ g 'Z;Lj7ܣM准J(zuwDBA>56Y5%'u?9AB̼_i<Ϟ=x NҀZQ-6廙P(thPLo?ç,TtRM䱨P )61$'+h5 dH5pG,=9s˴S+T:ӵʈ++CdT0/STkr}$enk8ʄfMl >u71q}U$bS(1cvgH A<|*j8x`eQkp獇#5z\ހ2A)S;ș,ZBHUWsVIc=o}5oxWhZQ $6פ,[q]U$ W6UVl Q &)љP eϺ ҹpա\>٩"dEjN> 2Ԕli &Pf5|,*- 6ѳ}K3P1܅bkwf5UTFcpEg#no B4A,,*3x䈘Deɫa+XC}4Pb-XX(PlAva6*g2Pv\V'|bw:rMAFO(AS+#& :)*G&ك֫H;kPbԅeťQ)=Ezgaٔ:IMP57{(tjB^QGHȟ^ͨx$0HWМF8o JIMDr&8Y0ؿeף^'+;5-[*T] l7dpmllWب2KJ]&WTrRyp|DdC| 80uEvqaVn~]Q0a1]c/Hm`(zc'rԱ0Z[?cX'v D+C4EBTT"15Z IA,`־#\dQCei0Y}Xpgl✝DIGx[BC6D(։%$"% s0Pn/ێ]A0w;{κRMei]Abal6W|x9jV9j[Ԇ@670¸lxX+sSc)ȧH#@~#FVU[d ]ҏtݺ$=m<:^%ྋ7:T+%%s7IO7o43euRB;FzjɪkM>/x LB-GN}h!?~rcEdž.; l8l7ISrfZHO5Ub_`p"ǣ栩h;EhYok+HB/m}FB)F21l" ]b|7@DUt՝ Rxa_ШX ذ^hK`VCq|+hX_KB,#3/m:Xv1V/]4gъ &Db0A>yWO]DI Wq4,ϥ8Ȣ F%lRekGlh/*L$_ ( *TKZ=ώ#a7%/:k67lA hkSTY':5WRm/{gp\ E+:;OяTFly1J\Z_eB Lڋ겴4TBPY(Xwu&Qlr{D~OH~E{ 77t ||~r5-$:'$OgjHIFff*eBՁRyo"LjHb,=ć~8N.=xgv뾼Fu2y _S%U JD(qYzq^ /+JMwOo4g#z("( V|̿o (jL+p6?[:JEAxl*Vx{lBWD9y.MWTWR^PYN>p/@>^Kl<4o/m߾(w endstream endobj 86 0 obj 6279 endobj 87 0 obj <>stream xcd`ab`dddw 441H3a!/X|?'J)wCFF7$ʢ gMCKKsԢ<Ē 'G!8?93RO1'G!X!(8,5bs~nAiIjo~JjQ^Qb H#C2.fFF&>̜Wmf;OU3uϟ_]+g+[mEwy@u B x_v!>sbKdGwgwSDCSWWw;GKľSo}bmo״nݽf"1aZOOiS;Z[J_[`fr\,y87a` endstream endobj 88 0 obj 346 endobj 89 0 obj <>stream x<CMR8q-*'$FJYCopyright (C) 1997 American Mathematical Society. All Rights ReservedCMR8Computer Modern2|b*@t`‹ ' <02ZYΤ<7#B? rGlwYx?{Fv?oa  7 כ -z: endstream endobj 90 0 obj 327 endobj 91 0 obj <>stream x-[LRqS1N,˵Zj>˴HJ[xD@\1/Pn^@.V.[j=A[u[Oa~n0'HJ,nDr-J@6y,Ud$uqڵ堂\GkjYT4fq!c~SʥMDJ7P*)9*\ImFrCOVRzJg%j@S:RtMV8A%X |2l &M<6̏'07vq|47[&ʆ$5ҢS,TQ>c) OBTB=<ݛ0> 0VsbA9 c.'ڴ=D8c;&y0E[Ewzv AҮ9k#XM-uj4M]A/0>h]vE35MBrov~g7ltiip,l#hsrM^Rfhnx^1rj68d "> ?a?{nNLN@|:Vs@*Dsg mHH_BV˹%||>'eW endstream endobj 92 0 obj 772 endobj 93 0 obj <>stream xWyPu !oVd1Yݨc}9JМbbI4#a sOO{2 pIȡC4͹etcuxl뱿fWT5=s}e)L&-9%eM"ׄ\Ytޔob@5f5ۑW9hQ1U&[BmjO:!1ŋHXUؕKK؛ZRkJXwo&?J6edeeN~lrA^a:8! 38D'oA3(x>k[ц=+7Uݵeݒ'zb1@#0`tV|UN6 +̾Vo)p|;m(9i:ue=y^MA[6q!|fuN~l.~<z ߈&DPUU:{ɒF}Sg{x莁gĹR\ ޝtf7h\j,ԲDQ5@>Wxt/{ߠ#O8 0X]WH1fȘ,"j+@\aq$51*HK }P;2zi+v{re-V-;*2լ@C` B ~w @i#ho]=_J4; v0i*2mzBYG%:V8Ǵ@S&z5g*2oFԣ- W^j7d {pPnv2n;@dØO=B<0dM c/4hLA(oW6yO58?k&l^2L yP"}v>Bq6-+p1x@xuǣ/%>+㓑U67Ͼ$ޅ~RBRbXH~ ݯӯs3]gZKgoܬz$$,1&Go1@:\4lrhd 9?mCfeP ]2bQrjPwˍp FZkאQQZy{YqaFfpsN⣭ ؝ď^F\>)!4$;t{bzTkk.u9By0Օb{.ۿaȓ\ )_׷y=_@| ܧDFO9x'Hʩ%lWۭ^- =v-=!?iڪQHEWq˨(snpNhdmefj8o{r*BA '?t9&5p0.>##*=ِ爉=Mz`6fsdKejOu.K4Xo{'xɂtڱW|nٲY,`l u]*Fد1gЛx23EzJ#45;iF/46lnOBBF-IO@踌N!d߇/hhtNE#I6_?^2=K7ē[کDB;7Ohx8yܝm k8ȳ@Y״wvm]s[21!FVU_ {Δ PÏB!+1,7U {lv -/6\9{Gsl#5`WڌĻA9\oݠo*,f~uh=A/*,ERMv6ȗtdž>c_ۏBnamM%;Azex_7C9Ƹa\\6F'u ~APvZ+mTٓj\$?>q\iyJ 6WccH`1y-ڭP jyM-0&kt=\/f8;wZ\Q/:ZCpkCXh{7C^ &uc!/HTṲ&LjW!4|E~t^_DVz*sp o_@A36fL{-j\` HPaWu=FlU1G\;iWQ﬎$QT͓ BAͲoƠoɛ}ҏ|g3/ qLm2G=(_Sfcy8`-`։ lF[[JT@j{¼8Iן=0Ʀ)ԅEpKWKLE3<;҃Y=I N^< 䖙lTHVsћ}G{3قLlقquPu~NJwQի-:#-=>#_UuqO}ĩ6-X('{Vj%>>hiiמz6PUU& ~__ :-A*ZĪLfN1f kX #:mH~?F_1}EGN,Km‹'.􏄂RѫӰnDY7 w|`jΖi+YF|SSh+%e5~lD͚3݊ .z%_6a)RD}(X0tj3ٲmNc$֙":_0 8'Bvw+FV2{TrO(՗sqbvC:L[sa w4՟vw`;qLHf֊U v]\N{!:rvd`H K-lVU/!^P>,  GzSQ>T܉ End2JGumӂ-5 |SZpZ? Кp3 L-(JIc- VIXd47]G:I63PBLz¯y=8V6Om%]󾫠lNW@KnWH=Gd|[ %ZyiQtߡa`B7#\W'T1JwA+:K%%peDaͲS;cv(;L^XJ)V*^Vp]0.͓|L>:]Q2âF.&} Ә ‘i@A-JW}gዣu?ٻ|j+ᅢLKCcOvV.2D魮3 j endstream endobj 94 0 obj 3904 endobj 95 0 obj <>stream xmLSWP/Nwo,uә1 " } *t@)JPZn[n-V[zy 4fds͗l3YexJ. }9y9 uw}Eѭ1ѷD^Tt<.U,yۄp@ƙ2uFYXT)KLMڻ7YR(*YHQ*6%eJEeNYJIȪBvDQhk*YfYB0P*cX9N`~,^&q1T,#v2\1m\ٝv+8/B(UZ.:/\wQG6,POL#h}$luM$V'eeno꘹baxx;L@PՕR]0\v;"wCS z@e3&=?cV`FO]GwMn94`Q]$"$y0OōL7m~[\h.G7&SvnflvߞIj1-Gl\Wbھ&{S~.hleu^tB 2vx[=!]۝wG (%=˚:[TQ 9m`xb{XTMn) MO'g0p3mRk}TUAK:4J3cz#T2MyVN tNc5ꢜksCBb+1Y "h"Et_H|N]ۤ&`4f \R_055O|ymWr1[7d'JsX@ 3Z1 t# :syVZ tWp f 6!FS&~79 y#/-Q"qFTUϏYkhTgixڽHy.?#;DZ35 ǔWTyZy $̪"ǯYB0 @$86 b| ,[e!2@C }ZQmй8=Dn-풬AjqҌe2n`,BI0`I>q76Mjl Zutvz[ R0CS/ &"uB3< |$7`opJwP@uvt.;bZoFk?ܘv|B6YZ4ahp}|%y$]/}^`hu4ApTlVZYRw|1_o}4voh4}EOmf6VqKr|U8ļܷ.~1\ܱ!kݰS endstream endobj 96 0 obj 1435 endobj 14 0 obj <> endobj 97 0 obj <> endobj 12 0 obj <> endobj 98 0 obj <> endobj 10 0 obj <> endobj 8 0 obj <> endobj 99 0 obj <> endobj 51 0 obj <> endobj 32 0 obj <> endobj 100 0 obj <> endobj 30 0 obj <> endobj 28 0 obj <> endobj 37 0 obj <> endobj 26 0 obj <> endobj 101 0 obj <> endobj 16 0 obj <> endobj 13 0 obj <> endobj 11 0 obj <> endobj 9 0 obj <> endobj 7 0 obj <> endobj 50 0 obj <> endobj 31 0 obj <> endobj 29 0 obj <> endobj 27 0 obj <> endobj 25 0 obj <> endobj 15 0 obj <> endobj 2 0 obj <>endobj xref 0 102 0000000000 65535 f 0000235305 00000 n 0000274108 00000 n 0000235161 00000 n 0000233259 00000 n 0000000015 00000 n 0000004118 00000 n 0000272185 00000 n 0000268537 00000 n 0000271956 00000 n 0000268272 00000 n 0000271413 00000 n 0000267562 00000 n 0000271094 00000 n 0000267026 00000 n 0000273880 00000 n 0000270830 00000 n 0000235353 00000 n 0000233409 00000 n 0000004138 00000 n 0000009407 00000 n 0000235427 00000 n 0000233553 00000 n 0000009428 00000 n 0000015098 00000 n 0000273601 00000 n 0000270341 00000 n 0000273387 00000 n 0000270077 00000 n 0000273182 00000 n 0000269924 00000 n 0000272976 00000 n 0000269452 00000 n 0000235492 00000 n 0000233705 00000 n 0000015119 00000 n 0000199188 00000 n 0000270274 00000 n 0000235590 00000 n 0000233849 00000 n 0000199211 00000 n 0000204455 00000 n 0000235644 00000 n 0000234001 00000 n 0000204476 00000 n 0000209700 00000 n 0000235720 00000 n 0000234145 00000 n 0000209721 00000 n 0000213761 00000 n 0000272449 00000 n 0000268982 00000 n 0000235796 00000 n 0000234289 00000 n 0000213782 00000 n 0000218959 00000 n 0000235872 00000 n 0000234441 00000 n 0000218980 00000 n 0000223495 00000 n 0000235915 00000 n 0000234585 00000 n 0000223516 00000 n 0000226625 00000 n 0000235969 00000 n 0000234729 00000 n 0000226646 00000 n 0000228972 00000 n 0000236045 00000 n 0000234873 00000 n 0000228993 00000 n 0000231996 00000 n 0000236121 00000 n 0000235017 00000 n 0000232017 00000 n 0000233238 00000 n 0000236175 00000 n 0000236218 00000 n 0000240834 00000 n 0000240855 00000 n 0000249282 00000 n 0000249303 00000 n 0000250796 00000 n 0000250817 00000 n 0000253303 00000 n 0000253324 00000 n 0000259689 00000 n 0000259710 00000 n 0000260142 00000 n 0000260162 00000 n 0000260575 00000 n 0000260595 00000 n 0000261453 00000 n 0000261473 00000 n 0000265463 00000 n 0000265484 00000 n 0000267005 00000 n 0000267478 00000 n 0000268100 00000 n 0000268890 00000 n 0000269825 00000 n 0000270723 00000 n trailer << /Size 102 /Root 1 0 R /Info 2 0 R /ID [(^`AG8>G)(^`AG8>G)] >> startxref 274219 %%EOF nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/fpackutil.c000066400000000000000000001727311310063650500233630ustar00rootroot00000000000000/* FPACK utility routines R. Seaman, NOAO & W. Pence, NASA/GSFC */ #include #include #include #include #include #include "fitsio.h" #include "fpack.h" /* these filename buffer are used to delete temporary files */ /* in case the program is aborted */ char tempfilename[SZ_STR]; char tempfilename2[SZ_STR]; char tempfilename3[SZ_STR]; /* nearest integer function */ # define NINT(x) ((x >= 0.) ? (int) (x + 0.5) : (int) (x - 0.5)) # define NSHRT(x) ((x >= 0.) ? (short) (x + 0.5) : (short) (x - 0.5)) /* define variables for measuring elapsed time */ clock_t scpu, ecpu; long startsec; /* start of elapsed time interval */ int startmilli; /* start of elapsed time interval */ /* CLOCKS_PER_SEC should be defined by most compilers */ #if defined(CLOCKS_PER_SEC) #define CLOCKTICKS CLOCKS_PER_SEC #else /* on SUN OS machine, CLOCKS_PER_SEC is not defined, so set its value */ #define CLOCKTICKS 1000000 #endif /* structure to hold image statistics (defined in fpack.h) */ imgstats imagestats; FILE *outreport; /* dimension of central image area to be sampled for test statistics */ int XSAMPLE = 4100; int YSAMPLE = 4100; int fp_msg (char *msg) { printf ("%s", msg); return(0); } /*--------------------------------------------------------------------------*/ int fp_noop (void) { fp_msg ("Input and output files are unchanged.\n"); return(0); } /*--------------------------------------------------------------------------*/ int fp_version (void) { float version; char cfitsioversion[40]; fp_msg (FPACK_VERSION); fits_get_version(&version); sprintf(cfitsioversion, " CFITSIO version %5.3f", version); fp_msg(cfitsioversion); fp_msg ("\n"); } /*--------------------------------------------------------------------------*/ int fp_access (char *filename) { /* test if a file exists */ FILE *diskfile; diskfile = fopen(filename, "r"); if (diskfile) { fclose(diskfile); return(0); } else { return(-1); } } /*--------------------------------------------------------------------------*/ int fp_tmpnam(char *suffix, char *rootname, char *tmpnam) { /* create temporary file name */ int maxtry = 30, len, i1 = 0, ii; if (strlen(suffix) + strlen(rootname) > SZ_STR-5) { fp_msg ("Error: filename is too long to create tempory file\n"); exit (-1); } strcpy (tmpnam, rootname); /* start with rootname */ strcat(tmpnam, suffix); /* append the suffix */ maxtry = SZ_STR - strlen(tmpnam) - 1; for (ii = 0; ii < maxtry; ii++) { if (fp_access(tmpnam)) break; /* good, the file does not exist */ strcat(tmpnam, "x"); /* append an x to the name, and try again */ } if (ii == maxtry) { fp_msg ("\nCould not create temporary file name:\n"); fp_msg (tmpnam); fp_msg ("\n"); exit (-1); } return(0); } /*--------------------------------------------------------------------------*/ int fp_init (fpstate *fpptr) { int ii; fpptr->comptype = RICE_1; fpptr->quantize_level = DEF_QLEVEL; fpptr->no_dither = 0; fpptr->dither_offset = 0; fpptr->scale = DEF_HCOMP_SCALE; fpptr->smooth = DEF_HCOMP_SMOOTH; fpptr->rescale_noise = DEF_RESCALE_NOISE; fpptr->ntile[0] = (long) 0; /* 0 means extent of axis */ for (ii=1; ii < MAX_COMPRESS_DIM; ii++) fpptr->ntile[ii] = (long) 1; fpptr->to_stdout = 0; fpptr->listonly = 0; fpptr->clobber = 0; fpptr->delete_input = 0; fpptr->do_not_prompt = 0; fpptr->do_checksums = 1; fpptr->do_gzip_file = 0; fpptr->test_all = 0; fpptr->verbose = 0; fpptr->prefix[0] = (char) NULL; fpptr->extname[0] = (char) NULL; fpptr->delete_suffix = 0; fpptr->outfile[0] = (char) NULL; fpptr->firstfile = 1; /* magic number for initialization check, boolean for preflight */ fpptr->initialized = FP_INIT_MAGIC; fpptr->preflight_checked = 0; return(0); } /*--------------------------------------------------------------------------*/ int fp_list (int argc, char *argv[], fpstate fpvar) { fitsfile *infptr; char infits[SZ_STR], msg[SZ_STR]; int hdunum, iarg, stat=0; LONGLONG sizell; if (fpvar.initialized != FP_INIT_MAGIC) { fp_msg ("Error: internal initialization error\n"); exit (-1); } for (iarg=fpvar.firstfile; iarg < argc; iarg++) { strncpy (infits, argv[iarg], SZ_STR); if (strchr (infits, '[') || strchr (infits, ']')) { fp_msg ("Error: section/extension notation not supported: "); fp_msg (infits); fp_msg ("\n"); exit (-1); } if (fp_access (infits) != 0) { fp_msg ("Error: can't find or read input file "); fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1); } fits_open_file (&infptr, infits, READONLY, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } /* move to the end of file, to get the total size in bytes */ fits_get_num_hdus (infptr, &hdunum, &stat); fits_movabs_hdu (infptr, hdunum, NULL, &stat); fits_get_hduaddrll(infptr, NULL, NULL, &sizell, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } sprintf (msg, "# %s (", infits); fp_msg (msg); #if defined(_MSC_VER) /* Microsoft Visual C++ 6.0 uses '%I64d' syntax for 8-byte integers */ sprintf(msg, "%I64d bytes)\n", sizell); fp_msg (msg); #elif (USE_LL_SUFFIX == 1) sprintf(msg, "%lld bytes)\n", sizell); fp_msg (msg); #else sprintf(msg, "%ld bytes)\n", sizell); fp_msg (msg); #endif fp_info_hdu (infptr); fits_close_file (infptr, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } } return(0); } /*--------------------------------------------------------------------------*/ int fp_info_hdu (fitsfile *infptr) { long naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; char msg[SZ_STR], val[SZ_CARD], com[SZ_CARD]; int comptype, naxis=0, hdutype, bitpix, hdupos, stat=0, ii; unsigned long datasum, hdusum; fits_movabs_hdu (infptr, 1, NULL, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } for (hdupos=1; ! stat; hdupos++) { fits_get_hdu_type (infptr, &hdutype, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } /* fits_get_hdu_type calls unknown extensions "IMAGE_HDU" * so consult XTENSION keyword itself */ fits_read_keyword (infptr, "XTENSION", val, com, &stat); if (stat == KEY_NO_EXIST) { /* in primary HDU which by definition is an "image" */ stat=0; /* clear for later error handling */ } else if (stat) { fits_report_error (stderr, stat); exit (stat); } else if (hdutype == IMAGE_HDU) { /* that is, if XTENSION != "IMAGE" AND != "BINTABLE" */ if (strncmp (val+1, "IMAGE", 5) && strncmp (val+1, "BINTABLE", 5)) { /* assign something other than any of these */ hdutype = IMAGE_HDU + ASCII_TBL + BINARY_TBL; } } fits_get_chksum(infptr, &datasum, &hdusum, &stat); if (hdutype == IMAGE_HDU) { sprintf (msg, " %d IMAGE", hdupos); fp_msg (msg); sprintf (msg, " SUMS=%u/%u", ~hdusum, datasum); fp_msg (msg); fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, &stat); sprintf (msg, " BITPIX=%d", bitpix); fp_msg (msg); if (naxis == 0) { sprintf (msg, " [no_pixels]"); fp_msg (msg); } else if (naxis == 1) { sprintf (msg, " [%d]", naxes[1]); fp_msg (msg); } else { sprintf (msg, " [%d", naxes[0]); fp_msg (msg); for (ii=1; ii < naxis; ii++) { sprintf (msg, "x%d", naxes[ii]); fp_msg (msg); } fp_msg ("]"); } if (fits_is_compressed_image (infptr, &stat)) { fits_read_keyword (infptr, "ZCMPTYPE", val, com, &stat); /* allow for quote in keyword value */ if (! strncmp (val+1, "RICE_1", 6)) fp_msg (" tiled_rice\n"); else if (! strncmp (val+1, "GZIP_1", 6)) fp_msg (" tiled_gzip\n"); else if (! strncmp (val+1, "PLIO_1", 6)) fp_msg (" tiled_plio\n"); else if (! strncmp (val+1, "HCOMPRESS_1", 11)) fp_msg (" tiled_hcompress\n"); else fp_msg (" unknown\n"); } else fp_msg (" not_tiled\n"); } else if (hdutype == ASCII_TBL) { sprintf (msg, " %d ASCII_TBL", hdupos); fp_msg (msg); sprintf (msg, " SUMS=%u/%u\n", ~hdusum, datasum); fp_msg (msg); } else if (hdutype == BINARY_TBL) { sprintf (msg, " %d BINARY_TBL", hdupos); fp_msg (msg); sprintf (msg, " SUMS=%u/%u\n", ~hdusum, datasum); fp_msg (msg); } else { sprintf (msg, " %d OTHER", hdupos); fp_msg (msg); sprintf (msg, " SUMS=%u/%u", ~hdusum, datasum); fp_msg (msg); sprintf (msg, " %s\n", val); fp_msg (msg); } fits_movrel_hdu (infptr, 1, NULL, &stat); } return(0); } /*--------------------------------------------------------------------------*/ int fp_preflight (int argc, char *argv[], int unpack, fpstate *fpptr) { char infits[SZ_STR], outfits[SZ_STR], temp[SZ_STR], *cptr; int iarg, suflen, namelen, nfiles = 0; if (fpptr->initialized != FP_INIT_MAGIC) { fp_msg ("Error: internal initialization error\n"); exit (-1); } for (iarg=fpptr->firstfile; iarg < argc; iarg++) { outfits[0] = '\0'; if (strlen(argv[iarg]) > SZ_STR - 4) { /* allow for .fz or .gz suffix */ fp_msg ("Error: input file name\n "); fp_msg (argv[iarg]); fp_msg ("\n is too long\n"); fp_noop (); exit (-1); } strncpy (infits, argv[iarg], SZ_STR); if (strchr (infits, '[') || strchr (infits, ']')) { fp_msg ("Error: section/extension notation not supported: "); fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1); } if (unpack) { /* ********** This section applies to funpack ************ */ /* check that input file exists */ if (infits[0] != '-') { /* if not reading from stdin stream */ if (fp_access (infits) != 0) { /* if not, then check if */ strcat(infits, ".fz"); /* a .fz version exsits */ if (fp_access (infits) != 0) { namelen = strlen(infits); infits[namelen - 3] = '\0'; /* remove the .fz suffix */ fp_msg ("Error: can't find or read input file "); fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1); } } else { /* make sure a .fz version of the same file doesn't exist */ namelen = strlen(infits); strcat(infits, ".fz"); if (fp_access (infits) == 0) { infits[namelen] = '\0'; /* remove the .fz suffix */ fp_msg ("Error: ambiguous input file name. Which file should be unpacked?:\n "); fp_msg (infits); fp_msg ("\n "); fp_msg (infits); fp_msg (".fz\n"); fp_noop (); exit (-1); } else { infits[namelen] = '\0'; /* remove the .fz suffix */ } } } /* if writing to stdout, then we are all done */ if (fpptr->to_stdout) { continue; } if (fpptr->outfile[0]) { /* user specified output file name */ nfiles++; if (nfiles > 1) { fp_msg ("Error: cannot use same output file name for multiple files:\n "); fp_msg (fpptr->outfile); fp_msg ("\n"); fp_noop (); exit (-1); } /* check that output file doesn't exist */ if (fp_access (fpptr->outfile) == 0) { fp_msg ("Error: output file already exists:\n "); fp_msg (fpptr->outfile); fp_msg ("\n "); fp_noop (); exit (-1); } continue; } /* construct output file name to test */ if (fpptr->prefix[0]) { if (strlen(fpptr->prefix) + strlen(infits) > SZ_STR - 1) { fp_msg ("Error: output file name for\n "); fp_msg (infits); fp_msg ("\n is too long with the prefix\n"); fp_noop (); exit (-1); } strcat(outfits,fpptr->prefix); } /* construct output file name */ if (infits[0] == '-') { strcpy(outfits, "output.fits"); } else { strcpy(outfits, infits); } /* remove .gz suffix, if present (output is not gzipped) */ namelen = strlen(outfits); if ( !strcmp(".gz", outfits + namelen - 3) ) { outfits[namelen - 3] = '\0'; } /* check for .fz suffix that is sometimes required */ /* and remove it if present */ if (infits[0] != '-') { /* if not reading from stdin stream */ namelen = strlen(outfits); if ( !strcmp(".fz", outfits + namelen - 3) ) { /* suffix is present */ outfits[namelen - 3] = '\0'; } else if (fpptr->delete_suffix) { /* required suffix is missing */ fp_msg ("Error: input compressed file "); fp_msg (infits); fp_msg ("\n does not have the default .fz suffix.\n"); fp_noop (); exit (-1); } } /* if infits != outfits, make sure outfits doesn't already exist */ if (strcmp(infits, outfits)) { if (fp_access (outfits) == 0) { fp_msg ("Error: output file already exists:\n "); fp_msg (outfits); fp_msg ("\n "); fp_noop (); exit (-1); } } /* if gzipping the output, make sure .gz file doesn't exist */ if (fpptr->do_gzip_file) { strcat(outfits, ".gz"); if (fp_access (outfits) == 0) { fp_msg ("Error: output file already exists:\n "); fp_msg (outfits); fp_msg ("\n "); fp_noop (); exit (-1); } namelen = strlen(outfits); outfits[namelen - 3] = '\0'; /* remove the .gz suffix again */ } } else { /* ********** This section applies to fpack ************ */ /* check that input file exists */ if (infits[0] != '-') { /* if not reading from stdin stream */ if (fp_access (infits) != 0) { /* if not, then check if */ strcat(infits, ".gz"); /* a gzipped version exsits */ if (fp_access (infits) != 0) { namelen = strlen(infits); infits[namelen - 3] = '\0'; /* remove the .gz suffix */ fp_msg ("Error: can't find or read input file "); fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1); } } } /* make sure the file to pack does not already have a .fz suffix */ namelen = strlen(infits); if ( !strcmp(".fz", infits + namelen - 3) ) { fp_msg ("Error: fpack input file already has '.fz' suffix\n" ); fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1); } /* if writing to stdout, or just testing the files, then we are all done */ if (fpptr->to_stdout || fpptr->test_all) { continue; } /* construct output file name */ if (infits[0] == '-') { strcpy(outfits, "input.fits"); } else { strcpy(outfits, infits); } /* remove .gz suffix, if present (output is not gzipped) */ namelen = strlen(outfits); if ( !strcmp(".gz", outfits + namelen - 3) ) { outfits[namelen - 3] = '\0'; } /* remove .imh suffix (IRAF format image), and replace with .fits */ namelen = strlen(outfits); if ( !strcmp(".imh", outfits + namelen - 4) ) { outfits[namelen - 4] = '\0'; strcat(outfits, ".fits"); } /* If not clobbering the input file, add .fz suffix to output name */ if (! fpptr->clobber) strcat(outfits, ".fz"); /* if infits != outfits, make sure outfits doesn't already exist */ if (strcmp(infits, outfits)) { if (fp_access (outfits) == 0) { fp_msg ("Error: output file already exists:\n "); fp_msg (outfits); fp_msg ("\n "); fp_noop (); exit (-1); } } } /* end of fpack section */ } fpptr->preflight_checked++; return(0); } /*--------------------------------------------------------------------------*/ /* must run fp_preflight() before fp_loop() */ int fp_loop (int argc, char *argv[], int unpack, fpstate fpvar) { char infits[SZ_STR], outfits[SZ_STR]; char temp[SZ_STR], answer[30], *cptr; int ii, iarg, islossless, namelen, iraf_infile = 0, status = 0, ifail; FILE *diskfile; if (fpvar.initialized != FP_INIT_MAGIC) { fp_msg ("Error: internal initialization error\n"); exit (-1); } else if (! fpvar.preflight_checked) { fp_msg ("Error: internal preflight error\n"); exit (-1); } if (fpvar.test_all && fpvar.outfile[0]) { outreport = fopen(fpvar.outfile, "w"); fprintf(outreport," Filename Extension BITPIX NAXIS1 NAXIS2 Size N_nulls Minval Maxval Mean Sigm Noise1 Noise3 T_whole T_rowbyrow "); fprintf(outreport,"[Comp_ratio, Pack_cpu, Unpack_cpu, Lossless readtimes] (repeated for Rice, Hcompress, and GZIP)\n"); } tempfilename[0] = '\0'; tempfilename2[0] = '\0'; tempfilename3[0] = '\0'; /* set up signal handler to delete temporary file on abort */ #ifdef SIGINT if (signal(SIGINT, SIG_IGN) != SIG_IGN) { (void) signal(SIGINT, abort_fpack); } #endif #ifdef SIGTERM if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { (void) signal(SIGTERM, abort_fpack); } #endif #ifdef SIGHUP if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { (void) signal(SIGHUP, abort_fpack); } #endif for (iarg=fpvar.firstfile; iarg < argc; iarg++) { temp[0] = '\0'; outfits[0] = '\0'; islossless = 1; strncpy (infits, argv[iarg], SZ_STR - 1); if (unpack) { /* ********** This section applies to funpack ************ */ /* find input file */ if (infits[0] != '-') { /* if not reading from stdin stream */ if (fp_access (infits) != 0) { /* if not, then */ strcat(infits, ".fz"); /* a .fz version must exsit */ } } if (fpvar.to_stdout) { strcpy(outfits, "-"); } else if (fpvar.outfile[0]) { /* user specified output file name */ strcpy(outfits, fpvar.outfile); } else { /* construct output file name */ if (fpvar.prefix[0]) { strcat(outfits,fpvar.prefix); } /* construct output file name */ if (infits[0] == '-') { strcpy(outfits, "output.fits"); } else { strcpy(outfits, infits); } /* remove .gz suffix, if present (output is not gzipped) */ namelen = strlen(outfits); if ( !strcmp(".gz", outfits + namelen - 3) ) { outfits[namelen - 3] = '\0'; } /* check for .fz suffix that is sometimes required */ /* and remove it if present */ namelen = strlen(outfits); if ( !strcmp(".fz", outfits + namelen - 3) ) { /* suffix is present */ outfits[namelen - 3] = '\0'; } } } else { /* ********** This section applies to fpack ************ */ if (fpvar.to_stdout) { strcpy(outfits, "-"); } else if (! fpvar.test_all) { /* construct output file name */ if (infits[0] == '-') { strcpy(outfits, "input.fits"); } else { strcpy(outfits, infits); } /* remove .gz suffix, if present (output is not gzipped) */ namelen = strlen(outfits); if ( !strcmp(".gz", outfits + namelen - 3) ) { outfits[namelen - 3] = '\0'; } /* remove .imh suffix (IRAF format image), and replace with .fits */ namelen = strlen(outfits); if ( !strcmp(".imh", outfits + namelen - 4) ) { outfits[namelen - 4] = '\0'; strcat(outfits, ".fits"); iraf_infile = 1; /* this is an IRAF format input file */ /* change the output name to "NAME.fits.fz" */ } /* If not clobbering the input file, add .fz suffix to output name */ if (! fpvar.clobber) strcat(outfits, ".fz"); } } strncpy(temp, outfits, SZ_STR-1); if (infits[0] != '-') { /* if not reading from stdin stream */ if (!strcmp(infits, outfits) ) { /* are input and output names the same? */ /* clobber the input file with the output file with the same name */ if (! fpvar.clobber) { fp_msg ("\nError: must use -F flag to clobber input file.\n"); exit (-1); } /* create temporary file name in the output directory (same as input directory)*/ fp_tmpnam("Tmp1", infits, outfits); strcpy(tempfilename, outfits); /* store temp file name, in case of abort */ } } /* *************** now do the real work ********************* */ if (fpvar.verbose && ! fpvar.to_stdout) printf("%s ", infits); if (fpvar.test_all) { /* compare all the algorithms */ /* create 2 temporary file names, in the CWD */ fp_tmpnam("Tmpfile1", "", tempfilename); fp_tmpnam("Tmpfile2", "", tempfilename2); fp_test (infits, tempfilename, tempfilename2, fpvar); remove(tempfilename); tempfilename[0] = '\0'; /* clear the temp file name */ remove(tempfilename2); tempfilename2[0] = '\0'; continue; } else if (unpack) { if (fpvar.to_stdout) { /* unpack the input file to the stdout stream */ fp_unpack (infits, outfits, fpvar); } else { /* unpack to temporary file, so other tasks can't open it until it is renamed */ /* create temporary file name, in the output directory */ fp_tmpnam("Tmp2", outfits, tempfilename2); /* unpack the input file to the temporary file */ fp_unpack (infits, tempfilename2, fpvar); /* rename the temporary file to it's real name */ ifail = rename(tempfilename2, outfits); if (ifail) { fp_msg("Failed to rename temporary file name:\n "); fp_msg(tempfilename2); fp_msg(" -> "); fp_msg(outfits); fp_msg("\n"); exit (-1); } else { tempfilename2[0] = '\0'; /* clear temporary file name */ } } } else { fp_pack (infits, outfits, fpvar, &islossless); } if (fpvar.to_stdout) { continue; } /* ********** clobber and/or delete files, if needed ************** */ if (!strcmp(infits, temp) && fpvar.clobber ) { if (!islossless && ! fpvar.do_not_prompt) { fp_msg ("\nFile "); fp_msg (infits); fp_msg ("\nwas compressed with a LOSSY method. Overwrite the\n"); fp_msg ("original file with the compressed version? (Y/N) "); fgets(answer, 29, stdin); if (answer[0] != 'Y' && answer[0] != 'y') { fp_msg ("\noriginal file NOT overwritten!\n"); remove(outfits); continue; } } /* rename clobbers input, may be unix/shell version dependent */ if (iraf_infile) { /* special case of deleting an IRAF format header and pixel file */ if (fits_delete_iraf_file(infits, &status)) { fp_msg("\nError deleting IRAF .imh and .pix files.\n"); fp_msg(infits); fp_msg ("\n"); exit (-1); } } if (rename (outfits, temp) != 0) { fp_msg ("\nError renaming tmp file to "); fp_msg (temp); fp_msg ("\n"); exit (-1); } tempfilename[0] = '\0'; /* clear temporary file name */ strcpy(outfits, temp); } else if (fpvar.clobber || fpvar.delete_input) { /* delete the input file */ if (!islossless && !fpvar.do_not_prompt) { /* user did not turn off delete prompt */ fp_msg ("\nFile "); fp_msg (infits); fp_msg ("\nwas compressed with a LOSSY method. \n"); fp_msg ("Delete the original file? (Y/N) "); fgets(answer, 29, stdin); if (answer[0] != 'Y' && answer[0] != 'y') { /* user abort */ fp_msg ("\noriginal file NOT deleted!\n"); } else { if (iraf_infile) { /* special case of deleting an IRAF format header and pixel file */ if (fits_delete_iraf_file(infits, &status)) { fp_msg("\nError deleting IRAF .imh and .pix files.\n"); fp_msg(infits); fp_msg ("\n"); exit (-1); } } else if (remove(infits) != 0) { /* normal case of deleting input FITS file */ fp_msg ("\nError deleting input file "); fp_msg (infits); fp_msg ("\n"); exit (-1); } } } else { /* user said don't prompt, so just delete the input file */ if (iraf_infile) { /* special case of deleting an IRAF format header and pixel file */ if (fits_delete_iraf_file(infits, &status)) { fp_msg("\nError deleting IRAF .imh and .pix files.\n"); fp_msg(infits); fp_msg ("\n"); exit (-1); } } else if (remove(infits) != 0) { /* normal case of deleting input FITS file */ fp_msg ("\nError deleting input file "); fp_msg (infits); fp_msg ("\n"); exit (-1); } } } iraf_infile = 0; if (fpvar.do_gzip_file) { /* gzip the output file */ strcpy(temp, "gzip -1 "); strcat(temp,outfits); system(temp); strcat(outfits, ".gz"); /* only possibible with funpack */ } if (fpvar.verbose && ! fpvar.to_stdout) printf("-> %s\n", outfits); } if (fpvar.test_all && fpvar.outfile[0]) fclose(outreport); return(0); } /*--------------------------------------------------------------------------*/ /* fp_pack assumes the output file does not exist (checked by preflight) */ int fp_pack (char *infits, char *outfits, fpstate fpvar, int *islossless) { fitsfile *infptr, *outfptr; int stat=0; fits_open_file (&infptr, infits, READONLY, &stat); fits_create_file (&outfptr, outfits, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } fits_set_compression_type (outfptr, fpvar.comptype, &stat); if (fpvar.no_dither) fits_set_quantize_dither(outfptr, -1, &stat); fits_set_quantize_level (outfptr, fpvar.quantize_level, &stat); fits_set_hcomp_scale (outfptr, fpvar.scale, &stat); fits_set_hcomp_smooth (outfptr, fpvar.smooth, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fits_set_dither_offset(outfptr, fpvar.dither_offset, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } while (! stat) { fp_pack_hdu (infptr, outfptr, fpvar, islossless, &stat); if (fpvar.do_checksums) { fits_write_chksum (outfptr, &stat); } fits_movrel_hdu (infptr, 1, NULL, &stat); } if (stat == END_OF_FILE) stat = 0; /* set checksum for case of newly created primary HDU */ if (fpvar.do_checksums) { fits_movabs_hdu (outfptr, 1, NULL, &stat); fits_write_chksum (outfptr, &stat); } fits_close_file (outfptr, &stat); fits_close_file (infptr, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } return(0); } /*--------------------------------------------------------------------------*/ /* fp_unpack assumes the output file does not exist */ int fp_unpack (char *infits, char *outfits, fpstate fpvar) { fitsfile *infptr, *outfptr; int stat=0, hdutype, extnum, single = 0; char *loc, *hduloc, hduname[SZ_STR]; fits_open_file (&infptr, infits, READONLY, &stat); fits_create_file (&outfptr, outfits, &stat); if (fpvar.extname[0]) { /* unpack a list of HDUs? */ /* move to the first HDU in the list */ hduloc = fpvar.extname; loc = strchr(hduloc, ','); /* look for 'comma' delimiter between names */ if (loc) *loc = '\0'; /* terminate the first name in the string */ strcpy(hduname, hduloc); /* copy the first name into temporary string */ if (loc) hduloc = loc + 1; /* advance to the beginning of the next name, if any */ else { hduloc += strlen(hduname); /* end of the list */ single = 1; /* only 1 HDU is being unpacked */ } if (isdigit( (int) hduname[0]) ) { extnum = strtol(hduname, &loc, 10); /* read the string as an integer */ /* check for junk following the integer */ if (*loc == '\0' ) /* no junk, so move to this HDU number (+1) */ { fits_movabs_hdu(infptr, extnum + 1, &hdutype, &stat); /* move to HDU number */ if (hdutype != IMAGE_HDU) stat = NOT_IMAGE; } else { /* the string is not an integer, so must be the column name */ hdutype = IMAGE_HDU; fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat); } } else { /* move to the named image extension */ hdutype = IMAGE_HDU; fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat); } } if (stat) { fp_msg ("Unable to find and move to extension '"); fp_msg(hduname); fp_msg("'\n"); fits_report_error (stderr, stat); exit (stat); } while (! stat) { if (single) stat = -1; /* special status flag to force output primary array */ fp_unpack_hdu (infptr, outfptr, &stat); if (fpvar.do_checksums) { fits_write_chksum (outfptr, &stat); } /* move to the next HDU */ if (fpvar.extname[0]) { /* unpack a list of HDUs? */ if (!(*hduloc)) { stat = END_OF_FILE; /* we reached the end of the list */ } else { /* parse the next HDU name and move to it */ loc = strchr(hduloc, ','); if (loc) /* look for 'comma' delimiter between names */ *loc = '\0'; /* terminate the first name in the string */ strcpy(hduname, hduloc); /* copy the next name into temporary string */ if (loc) hduloc = loc + 1; /* advance to the beginning of the next name, if any */ else *hduloc = '\0'; /* end of the list */ if (isdigit( (int) hduname[0]) ) { extnum = strtol(hduname, &loc, 10); /* read the string as an integer */ /* check for junk following the integer */ if (*loc == '\0' ) /* no junk, so move to this HDU number (+1) */ { fits_movabs_hdu(infptr, extnum + 1, &hdutype, &stat); /* move to HDU number */ if (hdutype != IMAGE_HDU) stat = NOT_IMAGE; } else { /* the string is not an integer, so must be the column name */ hdutype = IMAGE_HDU; fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat); } } else { /* move to the named image extension */ hdutype = IMAGE_HDU; fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat); } if (stat) { fp_msg ("Unable to find and move to extension '"); fp_msg(hduname); fp_msg("'\n"); } } } else { /* increment to the next HDU */ fits_movrel_hdu (infptr, 1, NULL, &stat); } } if (stat == END_OF_FILE) stat = 0; /* set checksum for case of newly created primary HDU */ if (fpvar.do_checksums) { fits_movabs_hdu (outfptr, 1, NULL, &stat); fits_write_chksum (outfptr, &stat); } fits_close_file (outfptr, &stat); fits_close_file (infptr, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } return(0); } /*--------------------------------------------------------------------------*/ /* fp_test assumes the output files do not exist */ int fp_test (char *infits, char *outfits, char *outfits2, fpstate fpvar) { fitsfile *inputfptr, *infptr, *outfptr, *outfptr2, *tempfile; long naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; long tilesize[9] = {0,1,1,1,1,1,1,1,1}; int stat=0, totpix=0, naxis=0, ii, hdutype, bitpix, extnum = 0, len; int tstatus = 0, hdunum, rescale_flag, bpix; char dtype[8], dimen[100]; double bscale, rescale; long headstart, datastart, dataend; float origdata = 0., whole_cpu, whole_elapse, row_elapse, row_cpu, xbits; FILE *diskfile; fits_open_file (&inputfptr, infits, READONLY, &stat); fits_create_file (&outfptr, outfits, &stat); fits_create_file (&outfptr2, outfits2, &stat); if (stat) { fits_report_error (stderr, stat); exit (stat); } if (fpvar.no_dither) fits_set_quantize_dither(outfptr, -1, &stat); fits_set_quantize_level (outfptr, fpvar.quantize_level, &stat); fits_set_hcomp_scale (outfptr, fpvar.scale, &stat); fits_set_hcomp_smooth (outfptr, fpvar.smooth, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fits_set_dither_offset(outfptr, fpvar.dither_offset, &stat); while (! stat) { rescale_flag = 0; /* LOOP OVER EACH HDU */ fits_get_hdu_type (inputfptr, &hdutype, &stat); if (hdutype == IMAGE_HDU) { fits_get_img_param (inputfptr, 9, &bitpix, &naxis, naxes, &stat); for (totpix=1, ii=0; ii < 9; ii++) totpix *= naxes[ii]; } if ( !fits_is_compressed_image (inputfptr, &stat) && hdutype == IMAGE_HDU && naxis != 0 && totpix != 0) { /* rescale a scaled integer image to reduce noise? */ if (fpvar.rescale_noise != 0. && bitpix > 0 && bitpix < LONGLONG_IMG) { tstatus = 0; fits_read_key(inputfptr, TDOUBLE, "BSCALE", &bscale, 0, &tstatus); if (tstatus == 0 && bscale != 1.0) { /* image must be scaled */ if (bitpix == LONG_IMG) fp_i4stat(inputfptr, naxis, naxes, &stat); else fp_i2stat(inputfptr, naxis, naxes, &stat); rescale = imagestats.noise3 / fpvar.rescale_noise; if (rescale > 1.0) { /* all the criteria are met, so create a temporary file that */ /* contains a rescaled version of the image, in CWD */ /* create temporary file name */ fp_tmpnam("Tmpfile3", "", tempfilename3); fits_create_file(&tempfile, tempfilename3, &stat); fits_get_hdu_num(inputfptr, &hdunum); if (hdunum != 1) { /* the input hdu is an image extension, so create dummy primary */ fits_create_img(tempfile, 8, 0, naxes, &stat); } fits_copy_header(inputfptr, tempfile, &stat); /* copy the header */ /* rescale the data, so that it will compress more efficiently */ if (bitpix == LONG_IMG) fp_i4rescale(inputfptr, naxis, naxes, rescale, tempfile, &stat); else fp_i2rescale(inputfptr, naxis, naxes, rescale, tempfile, &stat); /* scale the BSCALE keyword by the inverse factor */ bscale = bscale * rescale; fits_update_key(tempfile, TDOUBLE, "BSCALE", &bscale, 0, &stat); /* rescan the header, to reset the actual scaling parameters */ fits_set_hdustruc(tempfile, &stat); infptr = tempfile; rescale_flag = 1; } } } if (!rescale_flag) /* just compress the input file, without rescaling */ infptr = inputfptr; /* compute basic statistics about the input image */ if (bitpix == BYTE_IMG) { bpix = 8; strcpy(dtype, "8 "); fp_i2stat(infptr, naxis, naxes, &stat); } else if (bitpix == SHORT_IMG) { bpix = 16; strcpy(dtype, "16 "); fp_i2stat(infptr, naxis, naxes, &stat); } else if (bitpix == LONG_IMG) { bpix = 32; strcpy(dtype, "32 "); fp_i4stat(infptr, naxis, naxes, &stat); } else if (bitpix == LONGLONG_IMG) { bpix = 64; strcpy(dtype, "64 "); } else if (bitpix == FLOAT_IMG) { bpix = 32; strcpy(dtype, "-32"); fp_r4stat(infptr, naxis, naxes, &stat); } else if (bitpix == DOUBLE_IMG) { bpix = 64; strcpy(dtype, "-64"); fp_r4stat(infptr, naxis, naxes, &stat); } xbits = log10(imagestats.noise3)/.301 + 1.792; printf("\n File: %s\n", infits); printf(" Ext BITPIX Dimens. Nulls Min Max Mean Sigma Noise3 Nbits MaxR\n"); printf(" %3d %s", extnum, dtype); sprintf(dimen," (%d", naxes[0]); len =strlen(dimen); for (ii = 1; ii < naxis; ii++) { sprintf(dimen+len,",%d", naxes[ii]); len =strlen(dimen); } strcat(dimen, ")"); printf("%-12s",dimen); fits_get_hduaddr(inputfptr, &headstart, &datastart, &dataend, &stat); origdata = (dataend - datastart)/1000000.; /* get elapsed and cpu times need to read the uncompressed image */ fits_read_image_speed (infptr, &whole_elapse, &whole_cpu, &row_elapse, &row_cpu, &stat); printf(" %5d %6.0f %6.0f %8.1f %#8.2g %#7.3g %#5.1f %#6.2f\n", imagestats.n_nulls, imagestats.minval, imagestats.maxval, imagestats.mean, imagestats.sigma, imagestats.noise3, xbits, bpix/xbits); printf("\n Type Ratio Size (MB) Pk (Sec) UnPk Exact ElpN CPUN Elp1 CPU1\n"); printf(" Native %5.3f %5.3f %5.3f %5.3f\n", whole_elapse, whole_cpu, row_elapse, row_cpu); if (fpvar.outfile[0]) { fprintf(outreport, " %s %d %d %d %d %#10.4g %d %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g", infits, extnum, bitpix, naxes[0], naxes[1], origdata, imagestats.n_nulls, imagestats.minval, imagestats.maxval, imagestats.mean, imagestats.sigma, imagestats.noise1, imagestats.noise3, whole_elapse, whole_cpu, row_elapse, row_cpu); } /* test compression ratio and speed for each algorithm */ fits_set_compression_type (outfptr, RICE_1, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); fits_set_compression_type (outfptr, HCOMPRESS_1, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); fits_set_compression_type (outfptr, GZIP_1, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); /* fits_set_compression_type (outfptr, BZIP2_1, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); */ /* fits_set_compression_type (outfptr, PLIO_1, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); */ if (bitpix == SHORT_IMG || bitpix == LONG_IMG) { fits_set_compression_type (outfptr, NOCOMPRESS, &stat); fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat); fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat); } if (fpvar.outfile[0]) fprintf(outreport,"\n"); /* delete the temporary file */ if (rescale_flag) { fits_delete_file (infptr, &stat); tempfilename3[0] = '\0'; /* clear the temp filename */ } } else { fits_copy_hdu (inputfptr, outfptr, 0, &stat); fits_copy_hdu (inputfptr, outfptr2, 0, &stat); } fits_movrel_hdu (inputfptr, 1, NULL, &stat); extnum++; } if (stat == END_OF_FILE) stat = 0; fits_close_file (outfptr2, &stat); fits_close_file (outfptr, &stat); fits_close_file (inputfptr, &stat); if (stat) { fits_report_error (stderr, stat); } return(0); } /*--------------------------------------------------------------------------*/ int fp_pack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar, int *islossless, int *status) { fitsfile *tempfile; long naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; int stat=0, totpix=0, naxis=0, ii, hdutype, bitpix; int tstatus, hdunum, rescale_flag = 0; double bscale, rescale; FILE *diskfile; char outfits[SZ_STR]; if (*status) return(0); fits_get_hdu_type (infptr, &hdutype, &stat); if (hdutype == IMAGE_HDU) { fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, &stat); for (totpix=1, ii=0; ii < 9; ii++) totpix *= naxes[ii]; } if (fits_is_compressed_image (infptr, &stat) || hdutype != IMAGE_HDU || naxis == 0 || totpix == 0) { fits_copy_hdu (infptr, outfptr, 0, &stat); } else { /* rescale a scaled integer image to reduce noise? */ if (fpvar.rescale_noise != 0. && bitpix > 0 && bitpix < LONGLONG_IMG) { tstatus = 0; fits_read_key(infptr, TDOUBLE, "BSCALE", &bscale, 0, &tstatus); if (tstatus == 0 && bscale != 1.0) { /* image must be scaled */ if (bitpix == LONG_IMG) fp_i4stat(infptr, naxis, naxes, &stat); else fp_i2stat(infptr, naxis, naxes, &stat); rescale = imagestats.noise3 / fpvar.rescale_noise; if (rescale > 1.0) { /* all the criteria are met, so create a temporary file that */ /* contains a rescaled version of the image, in output directory */ /* create temporary file name */ fits_file_name(outfptr, outfits, &stat); /* get the output file name */ fp_tmpnam("Tmp3", outfits, tempfilename3); fits_create_file(&tempfile, tempfilename3, &stat); fits_get_hdu_num(infptr, &hdunum); if (hdunum != 1) { /* the input hdu is an image extension, so create dummy primary */ fits_create_img(tempfile, 8, 0, naxes, &stat); } fits_copy_header(infptr, tempfile, &stat); /* copy the header */ /* rescale the data, so that it will compress more efficiently */ if (bitpix == LONG_IMG) fp_i4rescale(infptr, naxis, naxes, rescale, tempfile, &stat); else fp_i2rescale(infptr, naxis, naxes, rescale, tempfile, &stat); /* scale the BSCALE keyword by the inverse factor */ bscale = bscale * rescale; fits_update_key(tempfile, TDOUBLE, "BSCALE", &bscale, 0, &stat); /* rescan the header, to reset the actual scaling parameters */ fits_set_hdustruc(tempfile, &stat); rescale_flag = 1; } } } if (rescale_flag) { fits_img_compress (tempfile, outfptr, &stat); fits_delete_file (tempfile, &stat); tempfilename3[0] = '\0'; /* clear the temp filename */ } else { fits_img_compress (infptr, outfptr, &stat); } if (bitpix < 0 || rescale_flag || (fpvar.comptype == HCOMPRESS_1 && fpvar.scale != 0.)) { /* compressed image is not identical to original */ *islossless = 0; } } *status = stat; return(0); } /*--------------------------------------------------------------------------*/ int fp_unpack_hdu (fitsfile *infptr, fitsfile *outfptr, int *status) { if (*status > 0) return(0); if (fits_is_compressed_image (infptr, status)) fits_img_decompress (infptr, outfptr, status); else fits_copy_hdu (infptr, outfptr, 0, status); return(0); } /*--------------------------------------------------------------------------*/ int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, float *whole_cpu, float *row_elapse, float *row_cpu, int *status) { unsigned char *carray, cnull = 0; short *sarray, snull=0; int bitpix, naxis, anynull, *iarray, inull = 0; long ii, naxes[9], fpixel[9]={1,1,1,1,1,1,1,1,1}, lpixel[9]={1,1,1,1,1,1,1,1,1}; long inc[9]={1,1,1,1,1,1,1,1,1} ; float *earray, enull = 0, filesize; double *darray, dnull = 0; LONGLONG fpixelll[9]; if (*status) return(*status); fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, status); if (naxis != 2)return(*status); lpixel[0] = naxes[0]; lpixel[1] = naxes[1]; /* filesize in MB */ filesize = naxes[0] * abs(bitpix) / 8000000. * naxes[1]; /* measure time required to read the raw image */ fits_set_bscale(infptr, 1.0, 0.0, status); *whole_elapse = 0.; *whole_cpu = 0; if (bitpix == BYTE_IMG) { carray = calloc(naxes[1]*naxes[0], sizeof(char)); /* remove any cached uncompressed tile (dangerous to directly modify the structure!) */ (infptr->Fptr)->tilerow = 0; marktime(status); fits_read_subset(infptr, TBYTE, fpixel, lpixel, inc, &cnull, carray, &anynull, status); /* get elapsped times */ gettime(whole_elapse, whole_cpu, status); /* now read the image again, row by row */ if (row_elapse) { /* remove any cached uncompressed tile (dangerous to directly modify the structure!) */ (infptr->Fptr)->tilerow = 0; marktime(status); for (ii = 0; ii < naxes[1]; ii++) { fpixel[1] = ii+1; fits_read_pix(infptr, TBYTE, fpixel, naxes[0], &cnull, carray, &anynull, status); } /* get elapsped times */ gettime(row_elapse, row_cpu, status); } free(carray); } else if (bitpix == SHORT_IMG) { sarray = calloc(naxes[0]*naxes[1], sizeof(short)); marktime(status); fits_read_subset(infptr, TSHORT, fpixel, lpixel, inc, &snull, sarray, &anynull, status); gettime(whole_elapse, whole_cpu, status); /* get elapsped times */ /* now read the image again, row by row */ if (row_elapse) { marktime(status); for (ii = 0; ii < naxes[1]; ii++) { fpixel[1] = ii+1; fits_read_pix(infptr, TSHORT, fpixel, naxes[0], &snull, sarray, &anynull, status); } /* get elapsped times */ gettime(row_elapse, row_cpu, status); } free(sarray); } else if (bitpix == LONG_IMG) { iarray = calloc(naxes[0]*naxes[1], sizeof(int)); marktime(status); fits_read_subset(infptr, TINT, fpixel, lpixel, inc, &inull, iarray, &anynull, status); /* get elapsped times */ gettime(whole_elapse, whole_cpu, status); /* now read the image again, row by row */ if (row_elapse) { marktime(status); for (ii = 0; ii < naxes[1]; ii++) { fpixel[1] = ii+1; fits_read_pix(infptr, TINT, fpixel, naxes[0], &inull, iarray, &anynull, status); } /* get elapsped times */ gettime(row_elapse, row_cpu, status); } free(iarray); } else if (bitpix == FLOAT_IMG) { earray = calloc(naxes[1]*naxes[0], sizeof(float)); marktime(status); fits_read_subset(infptr, TFLOAT, fpixel, lpixel, inc, &enull, earray, &anynull, status); /* get elapsped times */ gettime(whole_elapse, whole_cpu, status); /* now read the image again, row by row */ if (row_elapse) { marktime(status); for (ii = 0; ii < naxes[1]; ii++) { fpixel[1] = ii+1; fits_read_pix(infptr, TFLOAT, fpixel, naxes[0], &enull, earray, &anynull, status); } /* get elapsped times */ gettime(row_elapse, row_cpu, status); } free(earray); } else if (bitpix == DOUBLE_IMG) { darray = calloc(naxes[1]*naxes[0], sizeof(double)); marktime(status); fits_read_subset(infptr, TDOUBLE, fpixel, lpixel, inc, &dnull, darray, &anynull, status); /* get elapsped times */ gettime(whole_elapse, whole_cpu, status); /* now read the image again, row by row */ if (row_elapse) { marktime(status); for (ii = 0; ii < naxes[1]; ii++) { fpixel[1] = ii+1; fits_read_pix(infptr, TDOUBLE, fpixel, naxes[0], &dnull, darray, &anynull, status); } /* get elapsped times */ gettime(row_elapse, row_cpu, status); } free(darray); } if (whole_elapse) *whole_elapse = *whole_elapse / filesize; if (row_elapse) *row_elapse = *row_elapse / filesize; if (whole_cpu) *whole_cpu = *whole_cpu / filesize; if (row_cpu) *row_cpu = *row_cpu / filesize; return(*status); } /*--------------------------------------------------------------------------*/ int fp_test_hdu (fitsfile *infptr, fitsfile *outfptr, fitsfile *outfptr2, fpstate fpvar, int *status) { int stat = 0, hdutype, comptype, noloss = 0; char ctype[20], lossless[4]; long headstart, datastart, dataend; float origdata = 0., compressdata = 0.; float compratio = 0., packcpu = 0., unpackcpu = 0., readcpu; float elapse, whole_elapse, row_elapse, whole_cpu, row_cpu; unsigned long datasum1, datasum2, hdusum; if (*status) return(0); origdata = 0; compressdata = 0; compratio = 0.; lossless[0] = '\0'; fits_get_compression_type(outfptr, &comptype, &stat); if (comptype == RICE_1) strcpy(ctype, "RICE"); else if (comptype == GZIP_1) strcpy(ctype, "GZIP"); /* else if (comptype == BZIP2_1) strcpy(ctype, "BZIP2"); */ else if (comptype == PLIO_1) strcpy(ctype, "PLIO"); else if (comptype == HCOMPRESS_1) strcpy(ctype, "HCOMP"); else if (comptype == NOCOMPRESS) strcpy(ctype, "NONE"); else { fp_msg ("Error: unsupported image compression type "); *status = DATA_COMPRESSION_ERR; return(0); } /* -------------- COMPRESS the image ------------------ */ marktime(&stat); fits_img_compress (infptr, outfptr, &stat); /* get elapsped times */ gettime(&elapse, &packcpu, &stat); /* get elapsed and cpu times need to read the compressed image */ /* if whole image is compressed as single tile, don't read row by row because it usually takes a very long time */ if (fpvar.ntile[1] == 0) { fits_read_image_speed (outfptr, &whole_elapse, &whole_cpu, 0, 0, &stat); row_elapse = 0; row_cpu = 0; } else { fits_read_image_speed (outfptr, &whole_elapse, &whole_cpu, &row_elapse, &row_cpu, &stat); } if (!stat) { /* -------------- UNCOMPRESS the image ------------------ */ /* remove any cached uncompressed tile (dangerous to directly modify the structure!) */ (outfptr->Fptr)->tilerow = 0; marktime(&stat); fits_img_decompress (outfptr, outfptr2, &stat); /* get elapsped times */ gettime(&elapse, &unpackcpu, &stat); /* ----------------------------------------------------- */ /* get sizes of original and compressed images */ fits_get_hduaddr(infptr, &headstart, &datastart, &dataend, &stat); origdata = (dataend - datastart)/1000000.; fits_get_hduaddr(outfptr, &headstart, &datastart, &dataend, &stat); compressdata = (dataend - datastart)/1000000.; if (compressdata != 0) compratio = (float) origdata / (float) compressdata; /* is this uncompressed image identical to the original? */ fits_get_chksum(infptr, &datasum1, &hdusum, &stat); fits_get_chksum(outfptr2, &datasum2, &hdusum, &stat); if ( datasum1 == datasum2) { strcpy(lossless, "Yes"); noloss = 1; } else { strcpy(lossless, "No"); } printf(" %-5s %6.2f %7.2f ->%7.2f %7.2f %7.2f %s %5.3f %5.3f %5.3f %5.3f\n", ctype, compratio, origdata, compressdata, packcpu, unpackcpu, lossless, whole_elapse, whole_cpu, row_elapse, row_cpu); if (fpvar.outfile[0]) { fprintf(outreport," %6.3f %5.2f %5.2f %s %7.3f %7.3f %7.3f %7.3f", compratio, packcpu, unpackcpu, lossless, whole_elapse, whole_cpu, row_elapse, row_cpu); } /* delete the output HDUs to concerve disk space */ fits_delete_hdu(outfptr, &hdutype, &stat); fits_delete_hdu(outfptr2, &hdutype, &stat); } else { printf(" %-5s (unable to compress image)\n", ctype); } /* try to recover from any compression errors */ if (stat == DATA_COMPRESSION_ERR) stat = 0; *status = stat; return(0); } /*--------------------------------------------------------------------------*/ int marktime(int *status) { struct timeval tv; /* struct timezone tz; */ /* gettimeofday (&tv, &tz); */ gettimeofday (&tv, NULL); startsec = tv.tv_sec; startmilli = tv.tv_usec/1000; scpu = clock(); return( *status ); } /*--------------------------------------------------------------------------*/ int gettime(float *elapse, float *elapscpu, int *status) { struct timeval tv; /* struct timezone tz; */ int stopmilli; long stopsec; /* gettimeofday (&tv, &tz); */ gettimeofday (&tv, NULL); ecpu = clock(); stopmilli = tv.tv_usec/1000; stopsec = tv.tv_sec; *elapse = (stopsec - startsec) + (stopmilli - startmilli)/1000.; *elapscpu = (ecpu - scpu) * 1.0 / CLOCKTICKS; /* printf(" (start: %ld + %d), stop: (%ld + %d) elapse: %f\n ", startsec,startmilli,stopsec, stopmilli, *elapse); */ return( *status ); } /*--------------------------------------------------------------------------*/ int fp_i2stat(fitsfile *infptr, int naxis, long *naxes, int *status) { /* read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, and then compute basic statistics: min, max, mean, sigma, mean diff, etc. */ long fpixel[9] = {1,1,1,1,1,1,1,1,1}; long lpixel[9] = {1,1,1,1,1,1,1,1,1}; long inc[9] = {1,1,1,1,1,1,1,1,1}; long i1, i2, npix, ii, ngood, nx, ny; short *intarray, minvalue, maxvalue, nullvalue; int anynul, tstatus, checknull = 1; double mean, sigma, noise1, noise3; /* select the middle XSAMPLE by YSAMPLE area of the image */ i1 = naxes[0]/2 - (XSAMPLE/2 - 1); i2 = naxes[0]/2 + (XSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[0]) i2 = naxes[0]; fpixel[0] = i1; lpixel[0] = i2; nx = i2 - i1 +1; if (naxis > 1) { i1 = naxes[1]/2 - (YSAMPLE/2 - 1); i2 = naxes[1]/2 + (YSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[1]) i2 = naxes[1]; fpixel[1] = i1; lpixel[1] = i2; } ny = i2 - i1 +1; npix = nx * ny; /* if there are higher dimensions, read the middle plane of the cube */ if (naxis > 2) { fpixel[2] = naxes[2]/2 + 1; lpixel[2] = naxes[2]/2 + 1; } intarray = calloc(npix, sizeof(short)); if (!intarray) { *status = MEMORY_ALLOCATION; return(*status); } /* turn off any scaling of the integer pixel values */ fits_set_bscale(infptr, 1.0, 0.0, status); fits_read_subset_sht(infptr, 0, naxis, naxes, fpixel, lpixel, inc, 0, intarray, &anynul, status); /* read the null value keyword (BLANK) if present */ tstatus = 0; fits_read_key(infptr, TSHORT, "BLANK", &nullvalue, 0, &tstatus); if (tstatus) { nullvalue = 0; checknull = 0; } /* compute statistics of the image */ fits_img_stats_short(intarray, nx, ny, checknull, nullvalue, &ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise3, status); imagestats.n_nulls = npix - ngood; imagestats.minval = minvalue; imagestats.maxval = maxvalue; imagestats.mean = mean; imagestats.sigma = sigma; imagestats.noise1 = noise1; imagestats.noise3 = noise3; free(intarray); return(*status); } /*--------------------------------------------------------------------------*/ int fp_i4stat(fitsfile *infptr, int naxis, long *naxes, int *status) { /* read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, and then compute basic statistics: min, max, mean, sigma, mean diff, etc. */ long fpixel[9] = {1,1,1,1,1,1,1,1,1}; long lpixel[9] = {1,1,1,1,1,1,1,1,1}; long inc[9] = {1,1,1,1,1,1,1,1,1}; long i1, i2, npix, ii, ngood, nx, ny; int *intarray, minvalue, maxvalue, nullvalue; int anynul, tstatus, checknull = 1; double mean, sigma, noise1, noise3; /* select the middle XSAMPLE by YSAMPLE area of the image */ i1 = naxes[0]/2 - (XSAMPLE/2 - 1); i2 = naxes[0]/2 + (XSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[0]) i2 = naxes[0]; fpixel[0] = i1; lpixel[0] = i2; nx = i2 - i1 +1; if (naxis > 1) { i1 = naxes[1]/2 - (YSAMPLE/2 - 1); i2 = naxes[1]/2 + (YSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[1]) i2 = naxes[1]; fpixel[1] = i1; lpixel[1] = i2; } ny = i2 - i1 +1; npix = nx * ny; /* if there are higher dimensions, read the middle plane of the cube */ if (naxis > 2) { fpixel[2] = naxes[2]/2 + 1; lpixel[2] = naxes[2]/2 + 1; } intarray = calloc(npix, sizeof(int)); if (!intarray) { *status = MEMORY_ALLOCATION; return(*status); } /* turn off any scaling of the integer pixel values */ fits_set_bscale(infptr, 1.0, 0.0, status); fits_read_subset_int(infptr, 0, naxis, naxes, fpixel, lpixel, inc, 0, intarray, &anynul, status); /* read the null value keyword (BLANK) if present */ tstatus = 0; fits_read_key(infptr, TINT, "BLANK", &nullvalue, 0, &tstatus); if (tstatus) { nullvalue = 0; checknull = 0; } /* compute statistics of the image */ fits_img_stats_int(intarray, nx, ny, checknull, nullvalue, &ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise3, status); imagestats.n_nulls = npix - ngood; imagestats.minval = minvalue; imagestats.maxval = maxvalue; imagestats.mean = mean; imagestats.sigma = sigma; imagestats.noise1 = noise1; imagestats.noise3 = noise3; free(intarray); return(*status); } /*--------------------------------------------------------------------------*/ int fp_r4stat(fitsfile *infptr, int naxis, long *naxes, int *status) { /* read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, and then compute basic statistics: min, max, mean, sigma, mean diff, etc. */ long fpixel[9] = {1,1,1,1,1,1,1,1,1}; long lpixel[9] = {1,1,1,1,1,1,1,1,1}; long inc[9] = {1,1,1,1,1,1,1,1,1}; long i1, i2, npix, ii, ngood, nx, ny; float *array, minvalue, maxvalue, nullvalue = FLOATNULLVALUE; int anynul,checknull = 1; double mean, sigma, noise1, noise3; /* select the middle XSAMPLE by YSAMPLE area of the image */ i1 = naxes[0]/2 - (XSAMPLE/2 - 1); i2 = naxes[0]/2 + (XSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[0]) i2 = naxes[0]; fpixel[0] = i1; lpixel[0] = i2; nx = i2 - i1 +1; if (naxis > 1) { i1 = naxes[1]/2 - (YSAMPLE/2 - 1); i2 = naxes[1]/2 + (YSAMPLE/2); if (i1 < 1) i1 = 1; if (i2 > naxes[1]) i2 = naxes[1]; fpixel[1] = i1; lpixel[1] = i2; } ny = i2 - i1 +1; npix = nx * ny; /* if there are higher dimensions, read the middle plane of the cube */ if (naxis > 2) { fpixel[2] = naxes[2]/2 + 1; lpixel[2] = naxes[2]/2 + 1; } array = calloc(npix, sizeof(float)); if (!array) { *status = MEMORY_ALLOCATION; return(*status); } fits_read_subset_flt(infptr, 0, naxis, naxes, fpixel, lpixel, inc, nullvalue, array, &anynul, status); /* are there any null values in the array? */ if (!anynul) { nullvalue = 0.; checknull = 0; } /* compute statistics of the image */ fits_img_stats_float(array, nx, ny, checknull, nullvalue, &ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise3, status); imagestats.n_nulls = npix - ngood; imagestats.minval = minvalue; imagestats.maxval = maxvalue; imagestats.mean = mean; imagestats.sigma = sigma; imagestats.noise1 = noise1; imagestats.noise3 = noise3; free(array); return(*status); } /*--------------------------------------------------------------------------*/ int fp_i2rescale(fitsfile *infptr, int naxis, long *naxes, double rescale, fitsfile *outfptr, int *status) { /* divide the integer pixel values in the input file by rescale, and write back out to the output file.. */ long ii, jj, nelem = 1, nx, ny; short *intarray, nullvalue; int anynul, tstatus, checknull = 1; nx = naxes[0]; ny = 1; for (ii = 1; ii < naxis; ii++) { ny = ny * naxes[ii]; } intarray = calloc(nx, sizeof(short)); if (!intarray) { *status = MEMORY_ALLOCATION; return(*status); } /* read the null value keyword (BLANK) if present */ tstatus = 0; fits_read_key(infptr, TSHORT, "BLANK", &nullvalue, 0, &tstatus); if (tstatus) { checknull = 0; } /* turn off any scaling of the integer pixel values */ fits_set_bscale(infptr, 1.0, 0.0, status); fits_set_bscale(outfptr, 1.0, 0.0, status); for (ii = 0; ii < ny; ii++) { fits_read_img_sht(infptr, 1, nelem, nx, 0, intarray, &anynul, status); if (checknull) { for (jj = 0; jj < nx; jj++) { if (intarray[jj] != nullvalue) intarray[jj] = NSHRT( (intarray[jj] / rescale) ); } } else { for (jj = 0; jj < nx; jj++) intarray[jj] = NSHRT( (intarray[jj] / rescale) ); } fits_write_img_sht(outfptr, 1, nelem, nx, intarray, status); nelem += nx; } free(intarray); return(*status); } /*--------------------------------------------------------------------------*/ int fp_i4rescale(fitsfile *infptr, int naxis, long *naxes, double rescale, fitsfile *outfptr, int *status) { /* divide the integer pixel values in the input file by rescale, and write back out to the output file.. */ long ii, jj, nelem = 1, nx, ny; int *intarray, nullvalue; int anynul, tstatus, checknull = 1; nx = naxes[0]; ny = 1; for (ii = 1; ii < naxis; ii++) { ny = ny * naxes[ii]; } intarray = calloc(nx, sizeof(int)); if (!intarray) { *status = MEMORY_ALLOCATION; return(*status); } /* read the null value keyword (BLANK) if present */ tstatus = 0; fits_read_key(infptr, TINT, "BLANK", &nullvalue, 0, &tstatus); if (tstatus) { checknull = 0; } /* turn off any scaling of the integer pixel values */ fits_set_bscale(infptr, 1.0, 0.0, status); fits_set_bscale(outfptr, 1.0, 0.0, status); for (ii = 0; ii < ny; ii++) { fits_read_img_int(infptr, 1, nelem, nx, 0, intarray, &anynul, status); if (checknull) { for (jj = 0; jj < nx; jj++) { if (intarray[jj] != nullvalue) intarray[jj] = NINT( (intarray[jj] / rescale) ); } } else { for (jj = 0; jj < nx; jj++) intarray[jj] = NINT( (intarray[jj] / rescale) ); } fits_write_img_int(outfptr, 1, nelem, nx, intarray, status); nelem += nx; } free(intarray); return(*status); } /* ======================================================================== * Signal and error handler. */ void abort_fpack(int sig) { /* clean up by deleting temporary files */ if (tempfilename[0]) { remove(tempfilename); } if (tempfilename2[0]) { remove(tempfilename2); } if (tempfilename3[0]) { remove(tempfilename3); } exit(-1); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/funpack.c000066400000000000000000000106261310063650500230220ustar00rootroot00000000000000/* FUNPACK * R. Seaman, NOAO * uses fits_img_compress by W. Pence, HEASARC */ #include "fitsio.h" #include "fpack.h" int main (int argc, char *argv[]) { fpstate fpvar; if (argc <= 1) { fu_usage (); fu_hint (); exit (-1); } fp_init (&fpvar); fu_get_param (argc, argv, &fpvar); if (fpvar.listonly) { fp_list (argc, argv, fpvar); } else { fp_preflight (argc, argv, FUNPACK, &fpvar); fp_loop (argc, argv, FUNPACK, fpvar); } exit (0); } int fu_get_param (int argc, char *argv[], fpstate *fpptr) { int gottype=0, gottile=0, wholetile=0, iarg, len, ndim, ii; char tmp[SZ_STR], tile[SZ_STR]; if (fpptr->initialized != FP_INIT_MAGIC) { fp_msg ("Error: internal initialization error\n"); exit (-1); } tile[0] = (char) NULL; /* by default, .fz suffix characters to be deleted from compressed file */ fpptr->delete_suffix = 1; /* flags must come first and be separately specified */ for (iarg = 1; iarg < argc; iarg++) { if (argv[iarg][0] == '-' && strlen (argv[iarg]) == 2) { if (argv[iarg][1] == 'F') { fpptr->clobber++; fpptr->delete_suffix = 0; /* no suffix in this case */ } else if (argv[iarg][1] == 'D') { fpptr->delete_input++; } else if (argv[iarg][1] == 'P') { if (++iarg >= argc) { fu_usage (); fu_hint (); exit (-1); } else strncpy (fpptr->prefix, argv[iarg], SZ_STR); } else if (argv[iarg][1] == 'E') { if (++iarg >= argc) { fu_usage (); fu_hint (); exit (-1); } else strncpy (fpptr->extname, argv[iarg], SZ_STR); } else if (argv[iarg][1] == 'S') { fpptr->to_stdout++; } else if (argv[iarg][1] == 'L') { fpptr->listonly++; } else if (argv[iarg][1] == 'C') { fpptr->do_checksums = 0; } else if (argv[iarg][1] == 'H') { fu_help (); exit (0); } else if (argv[iarg][1] == 'V') { fp_version (); exit (0); } else if (argv[iarg][1] == 'Z') { fpptr->do_gzip_file++; } else if (argv[iarg][1] == 'v') { fpptr->verbose = 1; } else if (argv[iarg][1] == 'O') { if (++iarg >= argc) { fu_usage (); fu_hint (); exit (-1); } else strncpy (fpptr->outfile, argv[iarg], SZ_STR); } else { fp_msg ("Error: unknown command line flag `"); fp_msg (argv[iarg]); fp_msg ("'\n"); fu_usage (); fu_hint (); exit (-1); } } else break; } if (fpptr->extname[0] && (fpptr->clobber || fpptr->delete_input)) { fp_msg ("Error: -E option may not be used with -F or -D\n"); fu_usage (); exit (-1); } if (fpptr->to_stdout && (fpptr->outfile[0] || fpptr->prefix[0]) ) { fp_msg ("Error: -S option may not be used with -P or -O\n"); fu_usage (); exit (-1); } if (fpptr->outfile[0] && fpptr->prefix[0] ) { fp_msg ("Error: -P and -O options may not be used together\n"); fu_usage (); exit (-1); } if (iarg >= argc) { fp_msg ("Error: no FITS files to uncompress\n"); fu_usage (); exit (-1); } else fpptr->firstfile = iarg; return(0); } int fu_usage (void) { fp_msg ("usage: funpack [-E ] [-P

] [-O ] [-Z] -v \n");
        fp_msg ("more:   [-F] [-D] [-S] [-L] [-C] [-H] [-V] \n");
	return(0);
}

int fu_hint (void)
{
	fp_msg ("      `funpack -H' for help\n");
	return(0);
}

int fu_help (void)
{
fp_msg ("funpack, decompress fpacked files.  Version ");
fp_version ();
fu_usage ();
fp_msg ("\n");

fp_msg ("Flags must be separate and appear before filenames:\n");
fp_msg (" -E  Unpack only the list of HDU names or numbers in the file.\n");
fp_msg (" -P 
    Prepend 
 to create new output filenames.\n");
fp_msg (" -O    Specify full output file name.\n");
fp_msg (" -Z          Recompress the output file with host GZIP program.\n");
fp_msg (" -F          Overwrite input file by output file with same name.\n");
fp_msg (" -D          Delete input file after writing output.\n");
fp_msg (" -S          Output uncompressed file to STDOUT file stream.\n");
fp_msg (" -L          List contents, files unchanged.\n");

fp_msg (" -C          Don't update FITS checksum keywords.\n");

fp_msg (" -v          Verbose mode; list each file as it is processed.\n");
fp_msg (" -H          Show this message.\n");
fp_msg (" -V          Show version number.\n");

fp_msg (" \n       FITS files to unpack; enter '-' (a hyphen) to read from stdin.\n");
fp_msg (" Refer to the fpack User's Guide for more extensive help.\n");
	return(0);
}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/iraffits.c000066400000000000000000001601541310063650500232040ustar00rootroot00000000000000/*------------------------------------------------------------------------*/
/*                                                                        */
/*  These routines have been modified by William Pence for use by CFITSIO */
/*        The original files were provided by Doug Mink                   */
/*------------------------------------------------------------------------*/

/* File imhfile.c
 * August 6, 1998
 * By Doug Mink, based on Mike VanHilst's readiraf.c

 * Module:      imhfile.c (IRAF .imh image file reading and writing)
 * Purpose:     Read and write IRAF image files (and translate headers)
 * Subroutine:  irafrhead (filename, lfhead, fitsheader, lihead)
 *              Read IRAF image header
 * Subroutine:  irafrimage (fitsheader)
 *              Read IRAF image pixels (call after irafrhead)
 * Subroutine:	same_path (pixname, hdrname)
 *		Put filename and header path together
 * Subroutine:	iraf2fits (hdrname, irafheader, nbiraf, nbfits)
 *		Convert IRAF image header to FITS image header
 * Subroutine:  irafgeti4 (irafheader, offset)
 *		Get 4-byte integer from arbitrary part of IRAF header
 * Subroutine:  irafgetc2 (irafheader, offset)
 *		Get character string from arbitrary part of IRAF v.1 header
 * Subroutine:  irafgetc (irafheader, offset)
 *		Get character string from arbitrary part of IRAF header
 * Subroutine:  iraf2str (irafstring, nchar)
 * 		Convert 2-byte/char IRAF string to 1-byte/char string
 * Subroutine:	irafswap (bitpix,string,nbytes)
 *		Swap bytes in string in place, with FITS bits/pixel code
 * Subroutine:	irafswap2 (string,nbytes)
 *		Swap bytes in string in place
 * Subroutine	irafswap4 (string,nbytes)
 *		Reverse bytes of Integer*4 or Real*4 vector in place
 * Subroutine	irafswap8 (string,nbytes)
 *		Reverse bytes of Real*8 vector in place


 * Copyright:   2000 Smithsonian Astrophysical Observatory
 *              You may do anything you like with this file except remove
 *              this copyright.  The Smithsonian Astrophysical Observatory
 *              makes no representations about the suitability of this
 *              software for any purpose.  It is provided "as is" without
 *              express or implied warranty.
 */

#include 		/* define stderr, FD, and NULL */
#include 
#include   /* stddef.h is apparently needed to define size_t */
#include 

#define FILE_NOT_OPENED 104

/* Parameters from iraf/lib/imhdr.h for IRAF version 1 images */
#define SZ_IMPIXFILE	 79		/* name of pixel storage file */
#define SZ_IMHDRFILE	 79   		/* length of header storage file */
#define SZ_IMTITLE	 79		/* image title string */
#define LEN_IMHDR	2052		/* length of std header */

/* Parameters from iraf/lib/imhdr.h for IRAF version 2 images */
#define	SZ_IM2PIXFILE	255		/* name of pixel storage file */
#define	SZ_IM2HDRFILE	255		/* name of header storage file */
#define	SZ_IM2TITLE	383		/* image title string */
#define LEN_IM2HDR	2046		/* length of std header */

/* Offsets into header in bytes for parameters in IRAF version 1 images */
#define IM_HDRLEN	 12		/* Length of header in 4-byte ints */
#define IM_PIXTYPE       16             /* Datatype of the pixels */
#define IM_NDIM          20             /* Number of dimensions */
#define IM_LEN           24             /* Length (as stored) */
#define IM_PHYSLEN       52             /* Physical length (as stored) */
#define IM_PIXOFF        88             /* Offset of the pixels */
#define IM_CTIME        108             /* Time of image creation */
#define IM_MTIME        112             /* Time of last modification */
#define IM_LIMTIME      116             /* Time of min,max computation */
#define IM_MAX          120             /* Maximum pixel value */
#define IM_MIN          124             /* Maximum pixel value */
#define IM_PIXFILE      412             /* Name of pixel storage file */
#define IM_HDRFILE      572             /* Name of header storage file */
#define IM_TITLE        732             /* Image name string */

/* Offsets into header in bytes for parameters in IRAF version 2 images */
#define IM2_HDRLEN	  6		/* Length of header in 4-byte ints */
#define IM2_PIXTYPE      10             /* Datatype of the pixels */
#define IM2_SWAPPED      14             /* Pixels are byte swapped */
#define IM2_NDIM         18             /* Number of dimensions */
#define IM2_LEN          22             /* Length (as stored) */
#define IM2_PHYSLEN      50             /* Physical length (as stored) */
#define IM2_PIXOFF       86             /* Offset of the pixels */
#define IM2_CTIME       106             /* Time of image creation */
#define IM2_MTIME       110             /* Time of last modification */
#define IM2_LIMTIME     114             /* Time of min,max computation */
#define IM2_MAX         118             /* Maximum pixel value */
#define IM2_MIN         122             /* Maximum pixel value */
#define IM2_PIXFILE     126             /* Name of pixel storage file */
#define IM2_HDRFILE     382             /* Name of header storage file */
#define IM2_TITLE       638             /* Image name string */

/* Codes from iraf/unix/hlib/iraf.h */
#define	TY_CHAR		2
#define	TY_SHORT	3
#define	TY_INT		4
#define	TY_LONG		5
#define	TY_REAL		6
#define	TY_DOUBLE	7
#define	TY_COMPLEX	8
#define TY_POINTER      9
#define TY_STRUCT       10
#define TY_USHORT       11
#define TY_UBYTE        12

#define LEN_PIXHDR	1024
#define MAXINT  2147483647 /* Biggest number that can fit in long */

static int isirafswapped(char *irafheader, int offset);
static int irafgeti4(char *irafheader, int offset);
static char *irafgetc2(char *irafheader, int offset, int nc);
static char *irafgetc(char *irafheader,	int offset, int	nc);
static char *iraf2str(char *irafstring, int nchar);
static char *irafrdhead(const char *filename, int *lihead);
static int irafrdimage (char **buffptr, size_t *buffsize,
    size_t *filesize, int *status);
static int iraftofits (char *hdrname, char *irafheader, int nbiraf,
    char **buffptr, size_t *nbfits, size_t *fitssize, int *status);
static char *same_path(char *pixname, const char *hdrname);

static int swaphead=0;	/* =1 to swap data bytes of IRAF header values */
static int swapdata=0;  /* =1 to swap bytes in IRAF data pixels */

static void irafswap(int bitpix, char *string, int nbytes);
static void irafswap2(char *string, int nbytes);
static void irafswap4(char *string, int nbytes);
static void irafswap8(char *string, int nbytes);
static int pix_version (char *irafheader);
static int irafncmp (char *irafheader, char *teststring, int nc);
static int machswap(void);
static int head_version (char *irafheader);
static int hgeti4(char* hstring, char* keyword, int* val);
static int hgets(char* hstring, char* keyword, int lstr, char* string);
static char* hgetc(char* hstring, char* keyword);
static char* ksearch(char* hstring, char* keyword);
static char *blsearch (char* hstring, char* keyword);	
static char *strsrch (char* s1,	char* s2);
static char *strnsrch (	char* s1,char* s2,int ls1);
static void hputi4(char* hstring,char* keyword,	int ival);
static void hputs(char* hstring,char* keyword,char* cval);
static void hputcom(char* hstring,char* keyword,char* comment);
static void hputl(char* hstring,char* keyword,int lval);
static void hputc(char* hstring,char* keyword,char* cval);
static int getirafpixname (const char *hdrname, char *irafheader, char *pixfilename, int *status);
int iraf2mem(char *filename, char **buffptr, size_t *buffsize, 
      size_t *filesize, int *status);

void ffpmsg(const char *err_message);

/* CFITS_API is defined below for use on Windows systems.  */
/* It is used to identify the public functions which should be exported. */
/* This has no effect on non-windows platforms where "WIN32" is not defined */

/* this is only needed to export the "fits_delete_iraf_file" symbol, which */
/* is called in fpackutil.c (and perhaps in other applications programs) */

#if defined (WIN32)
  #if defined(cfitsio_EXPORTS)
    #define CFITS_API __declspec(dllexport)
  #else
    #define CFITS_API //__declspec(dllimport)
  #endif /* CFITS_API */
#else /* defined (WIN32) */
 #define CFITS_API
#endif

int CFITS_API fits_delete_iraf_file(const char *filename, int *status);


/*--------------------------------------------------------------------------*/
int fits_delete_iraf_file(const char *filename,  /* name of input file      */
             int *status)                        /* IO - error status       */

/*
   Delete the iraf .imh header file and the associated .pix data file
*/
{
    char *irafheader;
    int lenirafhead;

    char pixfilename[SZ_IM2PIXFILE+1];

    /* read IRAF header into dynamically created char array (free it later!) */
    irafheader = irafrdhead(filename, &lenirafhead);

    if (!irafheader)
    {
	return(*status = FILE_NOT_OPENED);
    }

    getirafpixname (filename, irafheader, pixfilename, status);

    /* don't need the IRAF header any more */
    free(irafheader);

    if (*status > 0)
       return(*status);

    remove(filename);
    remove(pixfilename);
    
    return(*status);
}

/*--------------------------------------------------------------------------*/
int iraf2mem(char *filename,     /* name of input file                 */
             char **buffptr,     /* O - memory pointer (initially NULL)    */
             size_t *buffsize,   /* O - size of mem buffer, in bytes        */
             size_t *filesize,   /* O - size of FITS file, in bytes         */
             int *status)        /* IO - error status                       */

/*
   Driver routine that reads an IRAF image into memory, also converting
   it into FITS format.
*/
{
    char *irafheader;
    int lenirafhead;

    *buffptr = NULL;
    *buffsize = 0;
    *filesize = 0;

    /* read IRAF header into dynamically created char array (free it later!) */
    irafheader = irafrdhead(filename, &lenirafhead);

    if (!irafheader)
    {
	return(*status = FILE_NOT_OPENED);
    }

    /* convert IRAF header to FITS header in memory */
    iraftofits(filename, irafheader, lenirafhead, buffptr, buffsize, filesize,
               status);

    /* don't need the IRAF header any more */
    free(irafheader);

    if (*status > 0)
       return(*status);

    *filesize = (((*filesize - 1) / 2880 ) + 1 ) * 2880; /* multiple of 2880 */

    /* append the image data onto the FITS header */
    irafrdimage(buffptr, buffsize, filesize, status);

    return(*status);
}

/*--------------------------------------------------------------------------*/
/* Subroutine:	irafrdhead  (was irafrhead in D. Mink's original code)
 * Purpose:	Open and read the iraf .imh file.
 * Returns:	NULL if failure, else pointer to IRAF .imh image header
 * Notes:	The imhdr format is defined in iraf/lib/imhdr.h, some of
 *		which defines or mimicked, above.
 */

static char *irafrdhead (
    const char *filename,  /* Name of IRAF header file */
    int *lihead)           /* Length of IRAF image header in bytes (returned) */
{
    FILE *fd;
    int nbr;
    char *irafheader;
    char errmsg[81];
    long nbhead;
    int nihead;

    *lihead = 0;

    /* open the image header file */
    fd = fopen (filename, "rb");
    if (fd == NULL) {
        ffpmsg("unable to open IRAF header file:");
        ffpmsg(filename);
	return (NULL);
	}

    /* Find size of image header file */
    if (fseek(fd, 0, 2) != 0)  /* move to end of the file */
    {
        ffpmsg("IRAFRHEAD: cannot seek in file:");
        ffpmsg(filename);
        return(NULL);
    }

    nbhead = ftell(fd);     /* position = size of file */
    if (nbhead < 0)
    {
        ffpmsg("IRAFRHEAD: cannot get pos. in file:");
        ffpmsg(filename);
        return(NULL);
    }

    if (fseek(fd, 0, 0) != 0) /* move back to beginning */
    {
        ffpmsg("IRAFRHEAD: cannot seek to beginning of file:");
        ffpmsg(filename);
        return(NULL);
    }

    /* allocate initial sized buffer */
    nihead = nbhead + 5000;
    irafheader = (char *) calloc (1, nihead);
    if (irafheader == NULL) {
	sprintf(errmsg, "IRAFRHEAD Cannot allocate %d-byte header",
		      nihead);
        ffpmsg(errmsg);
        ffpmsg(filename);
	return (NULL);
	}
    *lihead = nihead;

    /* Read IRAF header */
    nbr = fread (irafheader, 1, nbhead, fd);
    fclose (fd);

    /* Reject if header less than minimum length */
    if (nbr < LEN_PIXHDR) {
	sprintf(errmsg, "IRAFRHEAD header file: %d / %d bytes read.",
		      nbr,LEN_PIXHDR);
        ffpmsg(errmsg);
        ffpmsg(filename);
	free (irafheader);
	return (NULL);
	}

    return (irafheader);
}
/*--------------------------------------------------------------------------*/
static int irafrdimage (
    char **buffptr,	/* FITS image header (filled) */
    size_t *buffsize,      /* allocated size of the buffer */
    size_t *filesize,      /* actual size of the FITS file */
    int *status)
{
    FILE *fd;
    char *bang;
    int nax = 1, naxis1 = 1, naxis2 = 1, naxis3 = 1, naxis4 = 1, npaxis1 = 1, npaxis2;
    int bitpix, bytepix, i;
    char *fitsheader, *image;
    int nbr, nbimage, nbaxis, nbl, nbdiff;
    char *pixheader;
    char *linebuff;
    int imhver, lpixhead = 0;
    char pixname[SZ_IM2PIXFILE+1];
    char errmsg[81];
    size_t newfilesize;
 
    fitsheader = *buffptr;           /* pointer to start of header */

    /* Convert pixel file name to character string */
    hgets (fitsheader, "PIXFILE", SZ_IM2PIXFILE, pixname);
    hgeti4 (fitsheader, "PIXOFF", &lpixhead);

    /* Open pixel file, ignoring machine name if present */
    if ((bang = strchr (pixname, '!')) != NULL )
	fd = fopen (bang + 1, "rb");
    else
	fd = fopen (pixname, "rb");

    /* Print error message and exit if pixel file is not found */
    if (!fd) {
        ffpmsg("IRAFRIMAGE: Cannot open IRAF pixel file:");
        ffpmsg(pixname);
	return (*status = FILE_NOT_OPENED);
	}

    /* Read pixel header */
    pixheader = (char *) calloc (lpixhead, 1);
    if (pixheader == NULL) {
            ffpmsg("IRAFRIMAGE: Cannot alloc memory for pixel header");
            ffpmsg(pixname);
            fclose (fd);
	    return (*status = FILE_NOT_OPENED);
	}
    nbr = fread (pixheader, 1, lpixhead, fd);

    /* Check size of pixel header */
    if (nbr < lpixhead) {
	sprintf(errmsg, "IRAF pixel file: %d / %d bytes read.",
		      nbr,LEN_PIXHDR);
        ffpmsg(errmsg);
	free (pixheader);
	fclose (fd);
	return (*status = FILE_NOT_OPENED);
	}

    /* check pixel header magic word */
    imhver = pix_version (pixheader);
    if (imhver < 1) {
        ffpmsg("File not valid IRAF pixel file:");
        ffpmsg(pixname);
	free (pixheader);
	fclose (fd);
	return (*status = FILE_NOT_OPENED);
	}
    free (pixheader);

    /* Find number of bytes to read */
    hgeti4 (fitsheader,"NAXIS",&nax);
    hgeti4 (fitsheader,"NAXIS1",&naxis1);
    hgeti4 (fitsheader,"NPAXIS1",&npaxis1);
    if (nax > 1) {
        hgeti4 (fitsheader,"NAXIS2",&naxis2);
        hgeti4 (fitsheader,"NPAXIS2",&npaxis2);
	}
    if (nax > 2)
        hgeti4 (fitsheader,"NAXIS3",&naxis3);
    if (nax > 3)
        hgeti4 (fitsheader,"NAXIS4",&naxis4);

    hgeti4 (fitsheader,"BITPIX",&bitpix);
    if (bitpix < 0)
	bytepix = -bitpix / 8;
    else
	bytepix = bitpix / 8;

    nbimage = naxis1 * naxis2 * naxis3 * naxis4 * bytepix;
    
    newfilesize = *filesize + nbimage;  /* header + data */
    newfilesize = (((newfilesize - 1) / 2880 ) + 1 ) * 2880;

    if (newfilesize > *buffsize)   /* need to allocate more memory? */
    {
      fitsheader =  (char *) realloc (*buffptr, newfilesize);
      if (fitsheader == NULL) {
	sprintf(errmsg, "IRAFRIMAGE Cannot allocate %d-byte image buffer",
		(int) (*filesize));
        ffpmsg(errmsg);
        ffpmsg(pixname);
	fclose (fd);
	return (*status = FILE_NOT_OPENED);
	}
    }

    *buffptr = fitsheader;
    *buffsize = newfilesize;

    image = fitsheader + *filesize;
    *filesize = newfilesize;

    /* Read IRAF image all at once if physical and image dimensions are the same */
    if (npaxis1 == naxis1)
	nbr = fread (image, 1, nbimage, fd);

    /* Read IRAF image one line at a time if physical and image dimensions differ */
    else {
	nbdiff = (npaxis1 - naxis1) * bytepix;
	nbaxis = naxis1 * bytepix;
	linebuff = image;
	nbr = 0;
	if (naxis2 == 1 && naxis3 > 1)
	    naxis2 = naxis3;
	for (i = 0; i < naxis2; i++) {
	    nbl = fread (linebuff, 1, nbaxis, fd);
	    nbr = nbr + nbl;
	    fseek (fd, nbdiff, 1);
	    linebuff = linebuff + nbaxis;
	    }
	}
    fclose (fd);

    /* Check size of image */
    if (nbr < nbimage) {
	sprintf(errmsg, "IRAF pixel file: %d / %d bytes read.",
		      nbr,nbimage);
        ffpmsg(errmsg);
        ffpmsg(pixname);
	return (*status = FILE_NOT_OPENED);
	}

    /* Byte-reverse image, if necessary */
    if (swapdata)
	irafswap (bitpix, image, nbimage);

    return (*status);
}
/*--------------------------------------------------------------------------*/
/* Return IRAF image format version number from magic word in IRAF header*/

static int head_version (
    char *irafheader)	/* IRAF image header from file */

{

    /* Check header file magic word */
    if (irafncmp (irafheader, "imhdr", 5) != 0 ) {
	if (strncmp (irafheader, "imhv2", 5) != 0)
	    return (0);
	else
	    return (2);
	}
    else
	return (1);
}

/*--------------------------------------------------------------------------*/
/* Return IRAF image format version number from magic word in IRAF pixel file */

static int pix_version (
    char *irafheader)   /* IRAF image header from file */
{

    /* Check pixel file header magic word */
    if (irafncmp (irafheader, "impix", 5) != 0) {
	if (strncmp (irafheader, "impv2", 5) != 0)
	    return (0);
	else
	    return (2);
	}
    else
	return (1);
}

/*--------------------------------------------------------------------------*/
/* Verify that file is valid IRAF imhdr or impix by checking first 5 chars
 * Returns:	0 on success, 1 on failure */

static int irafncmp (

char	*irafheader,	/* IRAF image header from file */
char	*teststring,	/* C character string to compare */
int	nc)		/* Number of characters to compate */

{
    char *line;

    if ((line = iraf2str (irafheader, nc)) == NULL)
	return (1);
    if (strncmp (line, teststring, nc) == 0) {
	free (line);
	return (0);
	}
    else {
	free (line);
	return (1);
	}
}
/*--------------------------------------------------------------------------*/

/* Convert IRAF image header to FITS image header, returning FITS header */

static int iraftofits (
    char    *hdrname,  /* IRAF header file name (may be path) */
    char    *irafheader,  /* IRAF image header */
    int	    nbiraf,	  /* Number of bytes in IRAF header */
    char    **buffptr,    /* pointer to the FITS header  */
    size_t  *nbfits,      /* allocated size of the FITS header buffer */
    size_t  *fitssize,  /* Number of bytes in FITS header (returned) */
                        /*  = number of bytes to the end of the END keyword */
    int     *status)
{
    char *objname;	/* object name from FITS file */
    int lstr, i, j, k, ib, nax, nbits;
    char *pixname, *newpixname, *bang, *chead;
    char *fitsheader;
    int nblock, nlines;
    char *fhead, *fhead1, *fp, endline[81];
    char irafchar;
    char fitsline[81];
    int pixtype;
    int imhver, n, imu, pixoff, impixoff;
/*    int immax, immin, imtime;  */
    int imndim, imlen, imphyslen, impixtype;
    char errmsg[81];

    /* Set up last line of FITS header */
    (void)strncpy (endline,"END", 3);
    for (i = 3; i < 80; i++)
	endline[i] = ' ';
    endline[80] = 0;

    /* Check header magic word */
    imhver = head_version (irafheader);
    if (imhver < 1) {
	ffpmsg("File not valid IRAF image header");
        ffpmsg(hdrname);
	return(*status = FILE_NOT_OPENED);
	}
    if (imhver == 2) {
	nlines = 24 + ((nbiraf - LEN_IM2HDR) / 81);
	imndim = IM2_NDIM;
	imlen = IM2_LEN;
	imphyslen = IM2_PHYSLEN;
	impixtype = IM2_PIXTYPE;
	impixoff = IM2_PIXOFF;
/*	imtime = IM2_MTIME; */
/*	immax = IM2_MAX;  */
/*	immin = IM2_MIN; */
	}
    else {
	nlines = 24 + ((nbiraf - LEN_IMHDR) / 162);
	imndim = IM_NDIM;
	imlen = IM_LEN;
	imphyslen = IM_PHYSLEN;
	impixtype = IM_PIXTYPE;
	impixoff = IM_PIXOFF;
/*	imtime = IM_MTIME; */
/*	immax = IM_MAX; */
/*	immin = IM_MIN; */
	}

    /*  Initialize FITS header */
    nblock = (nlines * 80) / 2880;
    *nbfits = (nblock + 5) * 2880 + 4;
    fitsheader = (char *) calloc (*nbfits, 1);
    if (fitsheader == NULL) {
	sprintf(errmsg, "IRAF2FITS Cannot allocate %d-byte FITS header",
		(int) (*nbfits));
        ffpmsg(hdrname);
	return (*status = FILE_NOT_OPENED);
	}

    fhead = fitsheader;
    *buffptr = fitsheader;
    (void)strncpy (fitsheader, endline, 80);
    hputl (fitsheader, "SIMPLE", 1);
    fhead = fhead + 80;

    /*  check if the IRAF file is in big endian (sun) format (= 0) or not. */
    /*  This is done by checking the 4 byte integer in the header that     */
    /*  represents the iraf pixel type.  This 4-byte word is guaranteed to */
    /*  have the least sig byte != 0 and the most sig byte = 0,  so if the */
    /*  first byte of the word != 0, then the file in little endian format */
    /*  like on an Alpha machine.                                          */

    swaphead = isirafswapped(irafheader, impixtype);
    if (imhver == 1)
        swapdata = swaphead; /* vers 1 data has same swapness as header */
    else
        swapdata = irafgeti4 (irafheader, IM2_SWAPPED); 

    /*  Set pixel size in FITS header */
    pixtype = irafgeti4 (irafheader, impixtype);
    switch (pixtype) {
	case TY_CHAR:
	    nbits = 8;
	    break;
	case TY_UBYTE:
	    nbits = 8;
	    break;
	case TY_SHORT:
	    nbits = 16;
	    break;
	case TY_USHORT:
	    nbits = -16;
	    break;
	case TY_INT:
	case TY_LONG:
	    nbits = 32;
	    break;
	case TY_REAL:
	    nbits = -32;
	    break;
	case TY_DOUBLE:
	    nbits = -64;
	    break;
	default:
	    sprintf(errmsg,"Unsupported IRAF data type: %d", pixtype);
            ffpmsg(errmsg);
            ffpmsg(hdrname);
	    return (*status = FILE_NOT_OPENED);
	}
    hputi4 (fitsheader,"BITPIX",nbits);
    hputcom (fitsheader,"BITPIX", "IRAF .imh pixel type");
    fhead = fhead + 80;

    /*  Set image dimensions in FITS header */
    nax = irafgeti4 (irafheader, imndim);
    hputi4 (fitsheader,"NAXIS",nax);
    hputcom (fitsheader,"NAXIS", "IRAF .imh naxis");
    fhead = fhead + 80;

    n = irafgeti4 (irafheader, imlen);
    hputi4 (fitsheader, "NAXIS1", n);
    hputcom (fitsheader,"NAXIS1", "IRAF .imh image naxis[1]");
    fhead = fhead + 80;

    if (nax > 1) {
	n = irafgeti4 (irafheader, imlen+4);
	hputi4 (fitsheader, "NAXIS2", n);
	hputcom (fitsheader,"NAXIS2", "IRAF .imh image naxis[2]");
        fhead = fhead + 80;
	}
    if (nax > 2) {
	n = irafgeti4 (irafheader, imlen+8);
	hputi4 (fitsheader, "NAXIS3", n);
	hputcom (fitsheader,"NAXIS3", "IRAF .imh image naxis[3]");
	fhead = fhead + 80;
	}
    if (nax > 3) {
	n = irafgeti4 (irafheader, imlen+12);
	hputi4 (fitsheader, "NAXIS4", n);
	hputcom (fitsheader,"NAXIS4", "IRAF .imh image naxis[4]");
	fhead = fhead + 80;
	}

    /* Set object name in FITS header */
    if (imhver == 2)
	objname = irafgetc (irafheader, IM2_TITLE, SZ_IM2TITLE);
    else
	objname = irafgetc2 (irafheader, IM_TITLE, SZ_IMTITLE);
    if ((lstr = strlen (objname)) < 8) {
	for (i = lstr; i < 8; i++)
	    objname[i] = ' ';
	objname[8] = 0;
	}
    hputs (fitsheader,"OBJECT",objname);
    hputcom (fitsheader,"OBJECT", "IRAF .imh title");
    free (objname);
    fhead = fhead + 80;

    /* Save physical axis lengths so image file can be read */
    n = irafgeti4 (irafheader, imphyslen);
    hputi4 (fitsheader, "NPAXIS1", n);
    hputcom (fitsheader,"NPAXIS1", "IRAF .imh physical naxis[1]");
    fhead = fhead + 80;
    if (nax > 1) {
	n = irafgeti4 (irafheader, imphyslen+4);
	hputi4 (fitsheader, "NPAXIS2", n);
	hputcom (fitsheader,"NPAXIS2", "IRAF .imh physical naxis[2]");
	fhead = fhead + 80;
	}
    if (nax > 2) {
	n = irafgeti4 (irafheader, imphyslen+8);
	hputi4 (fitsheader, "NPAXIS3", n);
	hputcom (fitsheader,"NPAXIS3", "IRAF .imh physical naxis[3]");
	fhead = fhead + 80;
	}
    if (nax > 3) {
	n = irafgeti4 (irafheader, imphyslen+12);
	hputi4 (fitsheader, "NPAXIS4", n);
	hputcom (fitsheader,"NPAXIS4", "IRAF .imh physical naxis[4]");
	fhead = fhead + 80;
	}

    /* Save image header filename in header */
    hputs (fitsheader,"IMHFILE",hdrname);
    hputcom (fitsheader,"IMHFILE", "IRAF header file name");
    fhead = fhead + 80;

    /* Save image pixel file pathname in header */
    if (imhver == 2)
	pixname = irafgetc (irafheader, IM2_PIXFILE, SZ_IM2PIXFILE);
    else
	pixname = irafgetc2 (irafheader, IM_PIXFILE, SZ_IMPIXFILE);
    if (strncmp(pixname, "HDR", 3) == 0 ) {
	newpixname = same_path (pixname, hdrname);
        if (newpixname) {
          free (pixname);
          pixname = newpixname;
	  }
	}
    if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
	newpixname = same_path (pixname, hdrname);
        if (newpixname) {
          free (pixname);
          pixname = newpixname;
	  }
	}
	
    if ((bang = strchr (pixname, '!')) != NULL )
	hputs (fitsheader,"PIXFILE",bang+1);
    else
	hputs (fitsheader,"PIXFILE",pixname);
    free (pixname);
    hputcom (fitsheader,"PIXFILE", "IRAF .pix pixel file");
    fhead = fhead + 80;

    /* Save image offset from star of pixel file */
    pixoff = irafgeti4 (irafheader, impixoff);
    pixoff = (pixoff - 1) * 2;
    hputi4 (fitsheader, "PIXOFF", pixoff);
    hputcom (fitsheader,"PIXOFF", "IRAF .pix pixel offset (Do not change!)");
    fhead = fhead + 80;

    /* Save IRAF file format version in header */
    hputi4 (fitsheader,"IMHVER",imhver);
    hputcom (fitsheader,"IMHVER", "IRAF .imh format version (1 or 2)");
    fhead = fhead + 80;

    /* Save flag as to whether to swap IRAF data for this file and machine */
    if (swapdata)
	hputl (fitsheader, "PIXSWAP", 1);
    else
	hputl (fitsheader, "PIXSWAP", 0);
    hputcom (fitsheader,"PIXSWAP", "IRAF pixels, FITS byte orders differ if T");
    fhead = fhead + 80;

    /* Add user portion of IRAF header to FITS header */
    fitsline[80] = 0;
    if (imhver == 2) {
	imu = LEN_IM2HDR;
	chead = irafheader;
	j = 0;
	for (k = 0; k < 80; k++)
	    fitsline[k] = ' ';
	for (i = imu; i < nbiraf; i++) {
	    irafchar = chead[i];
	    if (irafchar == 0)
		break;
	    else if (irafchar == 10) {
		(void)strncpy (fhead, fitsline, 80);
		/* fprintf (stderr,"%80s\n",fitsline); */
		if (strncmp (fitsline, "OBJECT ", 7) != 0) {
		    fhead = fhead + 80;
		    }
		for (k = 0; k < 80; k++)
		    fitsline[k] = ' ';
		j = 0;
		}
	    else {
		if (j > 80) {
		    if (strncmp (fitsline, "OBJECT ", 7) != 0) {
			(void)strncpy (fhead, fitsline, 80);
			/* fprintf (stderr,"%80s\n",fitsline); */
			j = 9;
			fhead = fhead + 80;
			}
		    for (k = 0; k < 80; k++)
			fitsline[k] = ' ';
		    }
		if (irafchar > 32 && irafchar < 127)
		    fitsline[j] = irafchar;
		j++;
		}
	    }
	}
    else {
	imu = LEN_IMHDR;
	chead = irafheader;
	if (swaphead == 1)
	    ib = 0;
	else
	    ib = 1;
	for (k = 0; k < 80; k++)
	    fitsline[k] = ' ';
	j = 0;
	for (i = imu; i < nbiraf; i=i+2) {
	    irafchar = chead[i+ib];
	    if (irafchar == 0)
		break;
	    else if (irafchar == 10) {
		if (strncmp (fitsline, "OBJECT ", 7) != 0) {
		    (void)strncpy (fhead, fitsline, 80);
		    fhead = fhead + 80;
		    }
		/* fprintf (stderr,"%80s\n",fitsline); */
		j = 0;
		for (k = 0; k < 80; k++)
		    fitsline[k] = ' ';
		}
	    else {
		if (j > 80) {
		    if (strncmp (fitsline, "OBJECT ", 7) != 0) {
			(void)strncpy (fhead, fitsline, 80);
			j = 9;
			fhead = fhead + 80;
			}
		    /* fprintf (stderr,"%80s\n",fitsline); */
		    for (k = 0; k < 80; k++)
			fitsline[k] = ' ';
		    }
		if (irafchar > 32 && irafchar < 127)
		    fitsline[j] = irafchar;
		j++;
		}
	    }
	}

    /* Add END to last line */
    (void)strncpy (fhead, endline, 80);

    /* Find end of last 2880-byte block of header */
    fhead = ksearch (fitsheader, "END") + 80;
    nblock = *nbfits / 2880;
    fhead1 = fitsheader + (nblock * 2880);
    *fitssize = fhead - fitsheader;  /* no. of bytes to end of END keyword */

    /* Pad rest of header with spaces */
    strncpy (endline,"   ",3);
    for (fp = fhead; fp < fhead1; fp = fp + 80) {
	(void)strncpy (fp, endline,80);
	}

    return (*status);
}
/*--------------------------------------------------------------------------*/

/* get the IRAF pixel file name */

static int getirafpixname (
    const char *hdrname,  /* IRAF header file name (may be path) */
    char    *irafheader,  /* IRAF image header */
    char    *pixfilename,     /* IRAF pixel file name */
    int     *status)
{
    int imhver;
    char *pixname, *newpixname, *bang;

    /* Check header magic word */
    imhver = head_version (irafheader);
    if (imhver < 1) {
	ffpmsg("File not valid IRAF image header");
        ffpmsg(hdrname);
	return(*status = FILE_NOT_OPENED);
	}

    /* get image pixel file pathname in header */
    if (imhver == 2)
	pixname = irafgetc (irafheader, IM2_PIXFILE, SZ_IM2PIXFILE);
    else
	pixname = irafgetc2 (irafheader, IM_PIXFILE, SZ_IMPIXFILE);

    if (strncmp(pixname, "HDR", 3) == 0 ) {
	newpixname = same_path (pixname, hdrname);
        if (newpixname) {
          free (pixname);
          pixname = newpixname;
	  }
	}

    if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
	newpixname = same_path (pixname, hdrname);
        if (newpixname) {
          free (pixname);
          pixname = newpixname;
	  }
	}
	
    if ((bang = strchr (pixname, '!')) != NULL )
	strcpy(pixfilename,bang+1);
    else
	strcpy(pixfilename,pixname);

    free (pixname);

    return (*status);
}

/*--------------------------------------------------------------------------*/
/* Put filename and header path together */

static char *same_path (

char	*pixname,	/* IRAF pixel file pathname */
const char	*hdrname)	/* IRAF image header file pathname */

{
    int len;
    char *newpixname;

/*  WDP - 10/16/2007 - increased allocation to avoid possible overflow */
/*    newpixname = (char *) calloc (SZ_IM2PIXFILE, sizeof (char)); */

    newpixname = (char *) calloc (2*SZ_IM2PIXFILE+1, sizeof (char));
    if (newpixname == NULL) {
            ffpmsg("iraffits same_path: Cannot alloc memory for newpixname");
	    return (NULL);
	}

    /* Pixel file is in same directory as header */
    if (strncmp(pixname, "HDR$", 4) == 0 ) {
	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);

	/* find the end of the pathname */
	len = strlen (newpixname);
#ifndef VMS
	while( (len > 0) && (newpixname[len-1] != '/') )
#else
	while( (len > 0) && (newpixname[len-1] != ']') && (newpixname[len-1] != ':') )
#endif
	    len--;

	/* add name */
	newpixname[len] = '\0';
	(void)strncat (newpixname, &pixname[4], SZ_IM2PIXFILE);
	}

    /* Bare pixel file with no path is assumed to be same as HDR$filename */
    else if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);

	/* find the end of the pathname */
	len = strlen (newpixname);
#ifndef VMS
	while( (len > 0) && (newpixname[len-1] != '/') )
#else
	while( (len > 0) && (newpixname[len-1] != ']') && (newpixname[len-1] != ':') )
#endif
	    len--;

	/* add name */
	newpixname[len] = '\0';
	(void)strncat (newpixname, pixname, SZ_IM2PIXFILE);
	}

    /* Pixel file has same name as header file, but with .pix extension */
    else if (strncmp (pixname, "HDR", 3) == 0) {

	/* load entire header name string into name buffer */
	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);
	len = strlen (newpixname);
	newpixname[len-3] = 'p';
	newpixname[len-2] = 'i';
	newpixname[len-1] = 'x';
	}

    return (newpixname);
}

/*--------------------------------------------------------------------------*/
static int isirafswapped (

char	*irafheader,	/* IRAF image header */
int	offset)		/* Number of bytes to skip before number */

    /*  check if the IRAF file is in big endian (sun) format (= 0) or not */
    /*  This is done by checking the 4 byte integer in the header that */
    /*  represents the iraf pixel type.  This 4-byte word is guaranteed to */
    /*  have the least sig byte != 0 and the most sig byte = 0,  so if the */
    /*  first byte of the word != 0, then the file in little endian format */
    /*  like on an Alpha machine.                                          */

{
    int  swapped;

    if (irafheader[offset] != 0)
	swapped = 1;
    else
	swapped = 0;

    return (swapped);
}
/*--------------------------------------------------------------------------*/
static int irafgeti4 (

char	*irafheader,	/* IRAF image header */
int	offset)		/* Number of bytes to skip before number */

{
    char *ctemp, *cheader;
    int  temp;

    cheader = irafheader;
    ctemp = (char *) &temp;

    if (machswap() != swaphead) {
	ctemp[3] = cheader[offset];
	ctemp[2] = cheader[offset+1];
	ctemp[1] = cheader[offset+2];
	ctemp[0] = cheader[offset+3];
	}
    else {
	ctemp[0] = cheader[offset];
	ctemp[1] = cheader[offset+1];
	ctemp[2] = cheader[offset+2];
	ctemp[3] = cheader[offset+3];
	}
    return (temp);
}

/*--------------------------------------------------------------------------*/
/* IRAFGETC2 -- Get character string from arbitrary part of v.1 IRAF header */

static char *irafgetc2 (

char	*irafheader,	/* IRAF image header */
int	offset,		/* Number of bytes to skip before string */
int	nc)		/* Maximum number of characters in string */

{
    char *irafstring, *string;

    irafstring = irafgetc (irafheader, offset, 2*(nc+1));
    string = iraf2str (irafstring, nc);
    free (irafstring);

    return (string);
}

/*--------------------------------------------------------------------------*/
/* IRAFGETC -- Get character string from arbitrary part of IRAF header */

static char *irafgetc (

char	*irafheader,	/* IRAF image header */
int	offset,		/* Number of bytes to skip before string */
int	nc)		/* Maximum number of characters in string */

{
    char *ctemp, *cheader;
    int i;

    cheader = irafheader;
    ctemp = (char *) calloc (nc+1, 1);
    if (ctemp == NULL) {
	ffpmsg("IRAFGETC Cannot allocate memory for string variable");
	return (NULL);
	}
    for (i = 0; i < nc; i++) {
	ctemp[i] = cheader[offset+i];
	if (ctemp[i] > 0 && ctemp[i] < 32)
	    ctemp[i] = ' ';
	}

    return (ctemp);
}

/*--------------------------------------------------------------------------*/
/* Convert IRAF 2-byte/char string to 1-byte/char string */

static char *iraf2str (

char	*irafstring,	/* IRAF 2-byte/character string */
int	nchar)		/* Number of characters in string */
{
    char *string;
    int i, j;

    string = (char *) calloc (nchar+1, 1);
    if (string == NULL) {
	ffpmsg("IRAF2STR Cannot allocate memory for string variable");
	return (NULL);
	}

    /* the chars are in bytes 1, 3, 5, ... if bigendian format (SUN) */
    /* else in bytes 0, 2, 4, ... if little endian format (Alpha)    */

    if (irafstring[0] != 0)
	j = 0;
    else
	j = 1;

    /* Convert appropriate byte of input to output character */
    for (i = 0; i < nchar; i++) {
	string[i] = irafstring[j];
	j = j + 2;
	}

    return (string);
}

/*--------------------------------------------------------------------------*/
/* IRAFSWAP -- Reverse bytes of any type of vector in place */

static void irafswap (

int	bitpix,		/* Number of bits per pixel */
			/*  16 = short, -16 = unsigned short, 32 = int */
			/* -32 = float, -64 = double */
char	*string,	/* Address of starting point of bytes to swap */
int	nbytes)		/* Number of bytes to swap */

{
    switch (bitpix) {

	case 16:
	    if (nbytes < 2) return;
	    irafswap2 (string,nbytes);
	    break;

	case 32:
	    if (nbytes < 4) return;
	    irafswap4 (string,nbytes);
	    break;

	case -16:
	    if (nbytes < 2) return;
	    irafswap2 (string,nbytes);
	    break;

	case -32:
	    if (nbytes < 4) return;
	    irafswap4 (string,nbytes);
	    break;

	case -64:
	    if (nbytes < 8) return;
	    irafswap8 (string,nbytes);
	    break;

	}
    return;
}

/*--------------------------------------------------------------------------*/
/* IRAFSWAP2 -- Swap bytes in string in place */

static void irafswap2 (

char *string,	/* Address of starting point of bytes to swap */
int nbytes)	/* Number of bytes to swap */

{
    char *sbyte, temp, *slast;

    slast = string + nbytes;
    sbyte = string;
    while (sbyte < slast) {
	temp = sbyte[0];
	sbyte[0] = sbyte[1];
	sbyte[1] = temp;
	sbyte= sbyte + 2;
	}
    return;
}

/*--------------------------------------------------------------------------*/
/* IRAFSWAP4 -- Reverse bytes of Integer*4 or Real*4 vector in place */

static void irafswap4 (

char *string,	/* Address of Integer*4 or Real*4 vector */
int nbytes)	/* Number of bytes to reverse */

{
    char *sbyte, *slast;
    char temp0, temp1, temp2, temp3;

    slast = string + nbytes;
    sbyte = string;
    while (sbyte < slast) {
	temp3 = sbyte[0];
	temp2 = sbyte[1];
	temp1 = sbyte[2];
	temp0 = sbyte[3];
	sbyte[0] = temp0;
	sbyte[1] = temp1;
	sbyte[2] = temp2;
	sbyte[3] = temp3;
	sbyte = sbyte + 4;
	}

    return;
}

/*--------------------------------------------------------------------------*/
/* IRAFSWAP8 -- Reverse bytes of Real*8 vector in place */

static void irafswap8 (

char *string,	/* Address of Real*8 vector */
int nbytes)	/* Number of bytes to reverse */

{
    char *sbyte, *slast;
    char temp[8];

    slast = string + nbytes;
    sbyte = string;
    while (sbyte < slast) {
	temp[7] = sbyte[0];
	temp[6] = sbyte[1];
	temp[5] = sbyte[2];
	temp[4] = sbyte[3];
	temp[3] = sbyte[4];
	temp[2] = sbyte[5];
	temp[1] = sbyte[6];
	temp[0] = sbyte[7];
	sbyte[0] = temp[0];
	sbyte[1] = temp[1];
	sbyte[2] = temp[2];
	sbyte[3] = temp[3];
	sbyte[4] = temp[4];
	sbyte[5] = temp[5];
	sbyte[6] = temp[6];
	sbyte[7] = temp[7];
	sbyte = sbyte + 8;
	}
    return;
}

/*--------------------------------------------------------------------------*/
static int
machswap (void)

{
    char *ctest;
    int itest;

    itest = 1;
    ctest = (char *)&itest;
    if (*ctest)
	return (1);
    else
	return (0);
}

/*--------------------------------------------------------------------------*/
/*             the following routines were originally in hget.c             */
/*--------------------------------------------------------------------------*/


static int lhead0 = 0;

/*--------------------------------------------------------------------------*/

/* Extract long value for variable from FITS header string */

static int
hgeti4 (hstring,keyword,ival)

char *hstring;	/* character string containing FITS header information
		   in the format =  {/ } */
char *keyword;	/* character string containing the name of the keyword
		   the value of which is returned.  hget searches for a
		   line beginning with this string.  if "[n]" is present,
		   the n'th token in the value is returned.
		   (the first 8 characters must be unique) */
int *ival;
{
char *value;
double dval;
int minint;
char val[30]; 

/* Get value and comment from header string */
	value = hgetc (hstring,keyword);

/* Translate value from ASCII to binary */
	if (value != NULL) {
	    minint = -MAXINT - 1;
	    strcpy (val, value);
	    dval = atof (val);
	    if (dval+0.001 > MAXINT)
		*ival = MAXINT;
	    else if (dval >= 0)
		*ival = (int) (dval + 0.001);
	    else if (dval-0.001 < minint)
		*ival = minint;
	    else
		*ival = (int) (dval - 0.001);
	    return (1);
	    }
	else {
	    return (0);
	    }
}

/*-------------------------------------------------------------------*/
/* Extract string value for variable from FITS header string */

static int
hgets (hstring, keyword, lstr, str)

char *hstring;	/* character string containing FITS header information
		   in the format =  {/ } */
char *keyword;	/* character string containing the name of the keyword
		   the value of which is returned.  hget searches for a
		   line beginning with this string.  if "[n]" is present,
		   the n'th token in the value is returned.
		   (the first 8 characters must be unique) */
int lstr;	/* Size of str in characters */
char *str;	/* String (returned) */
{
	char *value;
	int lval;

/* Get value and comment from header string */
	value = hgetc (hstring,keyword);

	if (value != NULL) {
	    lval = strlen (value);
	    if (lval < lstr)
		strcpy (str, value);
	    else if (lstr > 1)
		strncpy (str, value, lstr-1);
	    else
		str[0] = value[0];
	    return (1);
	    }
	else
	    return (0);
}

/*-------------------------------------------------------------------*/
/* Extract character value for variable from FITS header string */

static char *
hgetc (hstring,keyword0)

char *hstring;	/* character string containing FITS header information
		   in the format =  {/ } */
char *keyword0;	/* character string containing the name of the keyword
		   the value of which is returned.  hget searches for a
		   line beginning with this string.  if "[n]" is present,
		   the n'th token in the value is returned.
		   (the first 8 characters must be unique) */
{
	static char cval[80];
	char *value;
	char cwhite[2];
	char squot[2], dquot[2], lbracket[2], rbracket[2], slash[2], comma[2];
	char keyword[81]; /* large for ESO hierarchical keywords */
	char line[100];
	char *vpos, *cpar = NULL;
	char *q1, *q2 = NULL, *v1, *v2, *c1, *brack1, *brack2;
	int ipar, i;

	squot[0] = 39;
	squot[1] = 0;
	dquot[0] = 34;
	dquot[1] = 0;
	lbracket[0] = 91;
	lbracket[1] = 0;
	comma[0] = 44;
	comma[1] = 0;
	rbracket[0] = 93;
	rbracket[1] = 0;
	slash[0] = 47;
	slash[1] = 0;

/* Find length of variable name */
	strncpy (keyword,keyword0, sizeof(keyword)-1);
	brack1 = strsrch (keyword,lbracket);
	if (brack1 == NULL)
	    brack1 = strsrch (keyword,comma);
	if (brack1 != NULL) {
	    *brack1 = '\0';
	    brack1++;
	    }

/* Search header string for variable name */
	vpos = ksearch (hstring,keyword);

/* Exit if not found */
	if (vpos == NULL) {
	    return (NULL);
	    }

/* Initialize line to nulls */
	 for (i = 0; i < 100; i++)
	    line[i] = 0;

/* In standard FITS, data lasts until 80th character */

/* Extract entry for this variable from the header */
	strncpy (line,vpos,80);

/* check for quoted value */
	q1 = strsrch (line,squot);
	c1 = strsrch (line,slash);
	if (q1 != NULL) {
	    if (c1 != NULL && q1 < c1)
		q2 = strsrch (q1+1,squot);
	    else if (c1 == NULL)
		q2 = strsrch (q1+1,squot);
	    else
		q1 = NULL;
	    }
	else {
	    q1 = strsrch (line,dquot);
	    if (q1 != NULL) {
		if (c1 != NULL && q1 < c1)
		    q2 = strsrch (q1+1,dquot);
		else if (c1 == NULL)
		    q2 = strsrch (q1+1,dquot);
		else
		    q1 = NULL;
		}
	    else {
		q1 = NULL;
		q2 = line + 10;
		}
	    }

/* Extract value and remove excess spaces */
	if (q1 != NULL) {
	    v1 = q1 + 1;
	    v2 = q2;
	    c1 = strsrch (q2,"/");
	    }
	else {
	    v1 = strsrch (line,"=") + 1;
	    c1 = strsrch (line,"/");
	    if (c1 != NULL)
		v2 = c1;
	    else
		v2 = line + 79;
	    }

/* Ignore leading spaces */
	while (*v1 == ' ' && v1 < v2) {
	    v1++;
	    }

/* Drop trailing spaces */
	*v2 = '\0';
	v2--;
	while (*v2 == ' ' && v2 > v1) {
	    *v2 = '\0';
	    v2--;
	    }

	if (!strcmp (v1, "-0"))
	    v1++;
	strcpy (cval,v1);
	value = cval;

/* If keyword has brackets, extract appropriate token from value */
	if (brack1 != NULL) {
	    brack2 = strsrch (brack1,rbracket);
	    if (brack2 != NULL)
		*brack2 = '\0';
	    ipar = atoi (brack1);
	    if (ipar > 0) {
		cwhite[0] = ' ';
		cwhite[1] = '\0';
		for (i = 1; i <= ipar; i++) {
		    cpar = strtok (v1,cwhite);
		    v1 = NULL;
		    }
		if (cpar != NULL) {
		    strcpy (cval,cpar);
		    }
		else
		    value = NULL;
		}
	    }

	return (value);
}


/*-------------------------------------------------------------------*/
/* Find beginning of fillable blank line before FITS header keyword line */

static char *
blsearch (hstring,keyword)

/* Find entry for keyword keyword in FITS header string hstring.
   (the keyword may have a maximum of eight letters)
   NULL is returned if the keyword is not found */

char *hstring;	/* character string containing fits-style header
		information in the format =  {/ }
		the default is that each entry is 80 characters long;
		however, lines may be of arbitrary length terminated by
		nulls, carriage returns or linefeeds, if packed is true.  */
char *keyword;	/* character string containing the name of the variable
		to be returned.  ksearch searches for a line beginning
		with this string.  The string may be a character
		literal or a character variable terminated by a null
		or '$'.  it is truncated to 8 characters. */
{
    char *loc, *headnext, *headlast, *pval, *lc, *line;
    char *bval;
    int icol, nextchar, lkey, nleft, lhstr;

    pval = 0;

    /* Search header string for variable name */
    if (lhead0)
	lhstr = lhead0;
    else {
	lhstr = 0;
	while (lhstr < 57600 && hstring[lhstr] != 0)
	    lhstr++;
	}
    headlast = hstring + lhstr;
    headnext = hstring;
    pval = NULL;
    while (headnext < headlast) {
	nleft = headlast - headnext;
	loc = strnsrch (headnext, keyword, nleft);

	/* Exit if keyword is not found */
	if (loc == NULL) {
	    break;
	    }

	icol = (loc - hstring) % 80;
	lkey = strlen (keyword);
	nextchar = (int) *(loc + lkey);

	/* If this is not in the first 8 characters of a line, keep searching */
	if (icol > 7)
	    headnext = loc + 1;

	/* If parameter name in header is longer, keep searching */
	else if (nextchar != 61 && nextchar > 32 && nextchar < 127)
	    headnext = loc + 1;

	/* If preceeding characters in line are not blanks, keep searching */
	else {
	    line = loc - icol;
	    for (lc = line; lc < loc; lc++) {
		if (*lc != ' ')
		    headnext = loc + 1;
		}

	/* Return pointer to start of line if match */
	    if (loc >= headnext) {
		pval = line;
		break;
		}
	    }
	}

    /* Return NULL if keyword is found at start of FITS header string */
    if (pval == NULL)
	return (pval);

    /* Return NULL if  found the first keyword in the header */
    if (pval == hstring)
        return (NULL);

    /* Find last nonblank line before requested keyword */
    bval = pval - 80;
    while (!strncmp (bval,"        ",8))
	bval = bval - 80;
    bval = bval + 80;

    /* Return pointer to calling program if blank lines found */
    if (bval < pval)
	return (bval);
    else
	return (NULL);
}


/*-------------------------------------------------------------------*/
/* Find FITS header line containing specified keyword */

static char *ksearch (hstring,keyword)

/* Find entry for keyword keyword in FITS header string hstring.
   (the keyword may have a maximum of eight letters)
   NULL is returned if the keyword is not found */

char *hstring;	/* character string containing fits-style header
		information in the format =  {/ }
		the default is that each entry is 80 characters long;
		however, lines may be of arbitrary length terminated by
		nulls, carriage returns or linefeeds, if packed is true.  */
char *keyword;	/* character string containing the name of the variable
		to be returned.  ksearch searches for a line beginning
		with this string.  The string may be a character
		literal or a character variable terminated by a null
		or '$'.  it is truncated to 8 characters. */
{
    char *loc, *headnext, *headlast, *pval, *lc, *line;
    int icol, nextchar, lkey, nleft, lhstr;

    pval = 0;

/* Search header string for variable name */
    if (lhead0)
	lhstr = lhead0;
    else {
	lhstr = 0;
	while (lhstr < 57600 && hstring[lhstr] != 0)
	    lhstr++;
	}
    headlast = hstring + lhstr;
    headnext = hstring;
    pval = NULL;
    while (headnext < headlast) {
	nleft = headlast - headnext;
	loc = strnsrch (headnext, keyword, nleft);

	/* Exit if keyword is not found */
	if (loc == NULL) {
	    break;
	    }

	icol = (loc - hstring) % 80;
	lkey = strlen (keyword);
	nextchar = (int) *(loc + lkey);

	/* If this is not in the first 8 characters of a line, keep searching */
	if (icol > 7)
	    headnext = loc + 1;

	/* If parameter name in header is longer, keep searching */
	else if (nextchar != 61 && nextchar > 32 && nextchar < 127)
	    headnext = loc + 1;

	/* If preceeding characters in line are not blanks, keep searching */
	else {
	    line = loc - icol;
	    for (lc = line; lc < loc; lc++) {
		if (*lc != ' ')
		    headnext = loc + 1;
		}

	/* Return pointer to start of line if match */
	    if (loc >= headnext) {
		pval = line;
		break;
		}
	    }
	}

/* Return pointer to calling program */
	return (pval);

}

/*-------------------------------------------------------------------*/
/* Find string s2 within null-terminated string s1 */

static char *
strsrch (s1, s2)

char *s1;	/* String to search */
char *s2;	/* String to look for */

{
    int ls1;
    ls1 = strlen (s1);
    return (strnsrch (s1, s2, ls1));
}

/*-------------------------------------------------------------------*/
/* Find string s2 within string s1 */

static char *
strnsrch (s1, s2, ls1)

char	*s1;	/* String to search */
char	*s2;	/* String to look for */
int	ls1;	/* Length of string being searched */

{
    char *s,*s1e;
    char cfirst,clast;
    int i,ls2;

    /* Return null string if either pointer is NULL */
    if (s1 == NULL || s2 == NULL)
	return (NULL);

    /* A zero-length pattern is found in any string */
    ls2 = strlen (s2);
    if (ls2 ==0)
	return (s1);

    /* Only a zero-length string can be found in a zero-length string */
    if (ls1 ==0)
	return (NULL);

    cfirst = s2[0];
    clast = s2[ls2-1];
    s1e = s1 + ls1 - ls2 + 1;
    s = s1;
    while (s < s1e) { 

	/* Search for first character in pattern string */
	if (*s == cfirst) {

	    /* If single character search, return */
	    if (ls2 == 1)
		return (s);

	    /* Search for last character in pattern string if first found */
	    if (s[ls2-1] == clast) {

		/* If two-character search, return */
		if (ls2 == 2)
		    return (s);

		/* If 3 or more characters, check for rest of search string */
		i = 1;
		while (i < ls2 && s[i] == s2[i])
		    i++;

		/* If entire string matches, return */
		if (i >= ls2)
		    return (s);
		}
	    }
	s++;
	}
    return (NULL);
}

/*-------------------------------------------------------------------*/
/*             the following routines were originally in hget.c      */
/*-------------------------------------------------------------------*/
/*  HPUTI4 - Set int keyword = ival in FITS header string */

static void
hputi4 (hstring,keyword,ival)

  char *hstring;	/* character string containing FITS-style header
			   information in the format
			   =  {/ }
			   each entry is padded with spaces to 80 characters */

  char *keyword;		/* character string containing the name of the variable
			   to be returned.  hput searches for a line beginning
			   with this string, and if there isn't one, creates one.
		   	   The first 8 characters of keyword must be unique. */
  int ival;		/* int number */
{
    char value[30];

    /* Translate value from binary to ASCII */
    sprintf (value,"%d",ival);

    /* Put value into header string */
    hputc (hstring,keyword,value);

    /* Return to calling program */
    return;
}

/*-------------------------------------------------------------------*/

/*  HPUTL - Set keyword = F if lval=0, else T, in FITS header string */

static void
hputl (hstring, keyword,lval)

char *hstring;		/* FITS header */
char *keyword;		/* Keyword name */
int lval;		/* logical variable (0=false, else true) */
{
    char value[8];

    /* Translate value from binary to ASCII */
    if (lval)
	strcpy (value, "T");
    else
	strcpy (value, "F");

    /* Put value into header string */
    hputc (hstring,keyword,value);

    /* Return to calling program */
    return;
}

/*-------------------------------------------------------------------*/

/*  HPUTS - Set character string keyword = 'cval' in FITS header string */

static void
hputs (hstring,keyword,cval)

char *hstring;	/* FITS header */
char *keyword;	/* Keyword name */
char *cval;	/* character string containing the value for variable
		   keyword.  trailing and leading blanks are removed.  */
{
    char squot = 39;
    char value[70];
    int lcval;

    /*  find length of variable string */

    lcval = strlen (cval);
    if (lcval > 67)
	lcval = 67;

    /* Put quotes around string */
    value[0] = squot;
    strncpy (&value[1],cval,lcval);
    value[lcval+1] = squot;
    value[lcval+2] = 0;

    /* Put value into header string */
    hputc (hstring,keyword,value);

    /* Return to calling program */
    return;
}

/*---------------------------------------------------------------------*/
/*  HPUTC - Set character string keyword = value in FITS header string */

static void
hputc (hstring,keyword,value)

char *hstring;
char *keyword;
char *value;	/* character string containing the value for variable
		   keyword.  trailing and leading blanks are removed.  */
{
    char squot = 39;
    char line[100];
    char newcom[50];
    char blank[80];
    char *v, *vp, *v1, *v2, *q1, *q2, *c1, *ve;
    int lkeyword, lcom, lval, lc, i;

    for (i = 0; i < 80; i++)
	blank[i] = ' ';

    /*  find length of keyword and value */
    lkeyword = strlen (keyword);
    lval = strlen (value);

    /*  If COMMENT or HISTORY, always add it just before the END */
    if (lkeyword == 7 && (strncmp (keyword,"COMMENT",7) == 0 ||
	strncmp (keyword,"HISTORY",7) == 0)) {

	/* Find end of header */
	v1 = ksearch (hstring,"END");
	v2 = v1 + 80;

	/* Move END down one line */
	strncpy (v2, v1, 80);

	/* Insert keyword */
	strncpy (v1,keyword,7);

	/* Pad with spaces */
	for (vp = v1+lkeyword; vp < v2; vp++)
	    *vp = ' ';

	/* Insert comment */
	strncpy (v1+9,value,lval);
	return;
	}

    /* Otherwise search for keyword */
    else
	v1 = ksearch (hstring,keyword);

    /*  If parameter is not found, find a place to put it */
    if (v1 == NULL) {
	
	/* First look for blank lines before END */
        v1 = blsearch (hstring, "END");
    
	/*  Otherwise, create a space for it at the end of the header */
	if (v1 == NULL) {
	    ve = ksearch (hstring,"END");
	    v1 = ve;
	    v2 = v1 + 80;
	    strncpy (v2, ve, 80);
	    }
	else
	    v2 = v1 + 80;
	lcom = 0;
	newcom[0] = 0;
	}

    /*  Otherwise, extract the entry for this keyword from the header */
    else {
	strncpy (line, v1, 80);
	line[80] = 0;
	v2 = v1 + 80;

	/*  check for quoted value */
	q1 = strchr (line, squot);
	if (q1 != NULL)
	    q2 = strchr (q1+1,squot);
	else
	    q2 = line;

	/*  extract comment and remove trailing spaces */

	c1 = strchr (q2,'/');
	if (c1 != NULL) {
	    lcom = 80 - (c1 - line);
	    strncpy (newcom, c1+1, lcom);
	    vp = newcom + lcom - 1;
	    while (vp-- > newcom && *vp == ' ')
		*vp = 0;
	    lcom = strlen (newcom);
	    }
	else {
	    newcom[0] = 0;
	    lcom = 0;
	    }
	}

    /* Fill new entry with spaces */
    for (vp = v1; vp < v2; vp++)
	*vp = ' ';

    /*  Copy keyword to new entry */
    strncpy (v1, keyword, lkeyword);

    /*  Add parameter value in the appropriate place */
    vp = v1 + 8;
    *vp = '=';
    vp = v1 + 9;
    *vp = ' ';
    vp = vp + 1;
    if (*value == squot) {
	strncpy (vp, value, lval);
	if (lval+12 > 31)
	    lc = lval + 12;
	else
	    lc = 30;
	}
    else {
	vp = v1 + 30 - lval;
	strncpy (vp, value, lval);
	lc = 30;
	}

    /* Add comment in the appropriate place */
	if (lcom > 0) {
	    if (lc+2+lcom > 80)
		lcom = 78 - lc;
	    vp = v1 + lc + 2;     /* Jul 16 1997: was vp = v1 + lc * 2 */
	    *vp = '/';
	    vp = vp + 1;
	    strncpy (vp, newcom, lcom);
	    for (v = vp + lcom; v < v2; v++)
		*v = ' ';
	    }

	return;
}

/*-------------------------------------------------------------------*/
/*  HPUTCOM - Set comment for keyword or on line in FITS header string */

static void
hputcom (hstring,keyword,comment)

  char *hstring;
  char *keyword;
  char *comment;
{
	char squot;
	char line[100];
	int lkeyword, lcom;
	char *vp, *v1, *v2, *c0 = NULL, *c1, *q1, *q2;

	squot = 39;

/*  Find length of variable name */
	lkeyword = strlen (keyword);

/*  If COMMENT or HISTORY, always add it just before the END */
	if (lkeyword == 7 && (strncmp (keyword,"COMMENT",7) == 0 ||
	    strncmp (keyword,"HISTORY",7) == 0)) {

	/* Find end of header */
	    v1 = ksearch (hstring,"END");
	    v2 = v1 + 80;
	    strncpy (v2, v1, 80);

	/*  blank out new line and insert keyword */
	    for (vp = v1; vp < v2; vp++)
		*vp = ' ';
	    strncpy (v1, keyword, lkeyword);
	    }

/* search header string for variable name */
	else {
	    v1 = ksearch (hstring,keyword);
	    v2 = v1 + 80;

	/* if parameter is not found, return without doing anything */
	    if (v1 == NULL) {
		return;
		}

	/* otherwise, extract entry for this variable from the header */
	    strncpy (line, v1, 80);

	/* check for quoted value */
	    q1 = strchr (line,squot);
	    if (q1 != NULL)
		q2 = strchr (q1+1,squot);
	    else
		q2 = NULL;

	    if (q2 == NULL || q2-line < 31)
		c0 = v1 + 31;
	    else
		c0 = v1 + (q2-line) + 2; /* allan: 1997-09-30, was c0=q2+2 */

	    strncpy (c0, "/ ",2);
	    }

/* create new entry */
	lcom = strlen (comment);

	if (lcom > 0) {
	    c1 = c0 + 2;
	    if (c1+lcom > v2)
		lcom = v2 - c1;
	    strncpy (c1, comment, lcom);
	    }

}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/quantize.c000066400000000000000000003472731310063650500232460ustar00rootroot00000000000000/*
  The following code is based on algorithms written by Richard White at STScI and made
  available for use in CFITSIO in July 1999 and updated in January 2008. 
*/

# include 
# include 
# include 
# include 
# include 

#include "fitsio2.h"

/* nearest integer function */
# define NINT(x)  ((x >= 0.) ? (int) (x + 0.5) : (int) (x - 0.5))

#define NULL_VALUE -2147483647 /* value used to represent undefined pixels */
#define ZERO_VALUE -2147483646 /* value used to represent zero-valued pixels */
#define N_RESERVED_VALUES 10   /* number of reserved values, starting with */
                               /* and including NULL_VALUE.  These values */
                               /* may not be used to represent the quantized */
                               /* and scaled floating point pixel values */
			       /* If lossy Hcompression is used, and the */
			       /* array contains null values, then it is also */
			       /* possible for the compressed values to slightly */
			       /* exceed the range of the actual (lossless) values */
			       /* so we must reserve a little more space */
			       
/* more than this many standard deviations from the mean is an outlier */
# define SIGMA_CLIP     5.
# define NITER          3	/* number of sigma-clipping iterations */

static int FnMeanSigma_short(short *array, long npix, int nullcheck, 
  short nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
static int FnMeanSigma_int(int *array, long npix, int nullcheck,
  int nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
static int FnMeanSigma_float(float *array, long npix, int nullcheck,
  float nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
static int FnMeanSigma_double(double *array, long npix, int nullcheck,
  double nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       

static int FnNoise5_short(short *array, long nx, long ny, int nullcheck, 
   short nullvalue, long *ngood, short *minval, short *maxval, 
   double *n2, double *n3, double *n5, int *status);   
static int FnNoise5_int(int *array, long nx, long ny, int nullcheck, 
   int nullvalue, long *ngood, int *minval, int *maxval, 
   double *n2, double *n3, double *n5, int *status);   
static int FnNoise5_float(float *array, long nx, long ny, int nullcheck, 
   float nullvalue, long *ngood, float *minval, float *maxval, 
   double *n2, double *n3, double *n5, int *status);   
static int FnNoise5_double(double *array, long nx, long ny, int nullcheck, 
   double nullvalue, long *ngood, double *minval, double *maxval, 
   double *n2, double *n3, double *n5, int *status);   

static int FnNoise3_short(short *array, long nx, long ny, int nullcheck, 
   short nullvalue, long *ngood, short *minval, short *maxval, double *noise, int *status);       
static int FnNoise3_int(int *array, long nx, long ny, int nullcheck, 
   int nullvalue, long *ngood, int *minval, int *maxval, double *noise, int *status);          
static int FnNoise3_float(float *array, long nx, long ny, int nullcheck, 
   float nullvalue, long *ngood, float *minval, float *maxval, double *noise, int *status);        
static int FnNoise3_double(double *array, long nx, long ny, int nullcheck, 
   double nullvalue, long *ngood, double *minval, double *maxval, double *noise, int *status);        

static int FnNoise1_short(short *array, long nx, long ny, 
   int nullcheck, short nullvalue, double *noise, int *status);       
static int FnNoise1_int(int *array, long nx, long ny, 
   int nullcheck, int nullvalue, double *noise, int *status);       
static int FnNoise1_float(float *array, long nx, long ny, 
   int nullcheck, float nullvalue, double *noise, int *status);       
static int FnNoise1_double(double *array, long nx, long ny, 
   int nullcheck, double nullvalue, double *noise, int *status);       

static int FnCompare_short (const void *, const void *);
static int FnCompare_int (const void *, const void *);
static int FnCompare_float (const void *, const void *);
static int FnCompare_double (const void *, const void *);
static float quick_select_float(float arr[], int n);
static short quick_select_short(short arr[], int n);
static int quick_select_int(int arr[], int n);
static LONGLONG quick_select_longlong(LONGLONG arr[], int n);
static double quick_select_double(double arr[], int n);

/*---------------------------------------------------------------------------*/
int fits_quantize_float (long row, float fdata[], long nxpix, long nypix, int nullcheck, 
	float in_null_value, float qlevel, int dither_method, int idata[], double *bscale,
	double *bzero, int *iminval, int *imaxval) {

/* arguments:
long row            i: if positive, used to calculate random dithering seed value
                       (this is only used when dithering the quantized values)
float fdata[]       i: array of image pixels to be compressed
long nxpix          i: number of pixels in each row of fdata
long nypix          i: number of rows in fdata
nullcheck           i: check for nullvalues in fdata?
float in_null_value i: value used to represent undefined pixels in fdata
float qlevel        i: quantization level
int dither_method   i; which dithering method to use
int idata[]         o: values of fdata after applying bzero and bscale
double bscale       o: scale factor
double bzero        o: zero offset
int iminval         o: minimum quantized value that is returned
int imaxval         o: maximum quantized value that is returned

The function value will be one if the input fdata were copied to idata;
in this case the parameters bscale and bzero can be used to convert back to
nearly the original floating point values:  fdata ~= idata * bscale + bzero.
If the function value is zero, the data were not copied to idata.
*/

	int status, iseed = 0;
	long i, nx, ngood = 0;
	double stdev, noise2, noise3, noise5;	/* MAD 2nd, 3rd, and 5th order noise values */
	float minval = 0., maxval = 0.;  /* min & max of fdata */
	double delta;		/* bscale, 1 in idata = delta in fdata */
	double zeropt;	        /* bzero */
	double temp;
        int nextrand = 0;
	extern float *fits_rand_value; /* this is defined in imcompress.c */
	LONGLONG iqfactor;

	nx = nxpix * nypix;
	if (nx <= 1) {
	    *bscale = 1.;
	    *bzero  = 0.;
	    return (0);
	}

        if (qlevel >= 0.) {

	    /* estimate background noise using MAD pixel differences */
	    FnNoise5_float(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
	        &minval, &maxval, &noise2, &noise3, &noise5, &status);      

	    if (nullcheck && ngood == 0) {   /* special case of an image filled with Nulls */
	        /* set parameters to dummy values, which are not used */
		minval = 0.;
		maxval = 1.;
		stdev = 1;
	    } else {

	        /* use the minimum of noise2, noise3, and noise5 as the best noise value */
	        stdev = noise3;
	        if (noise2 != 0. && noise2 < stdev) stdev = noise2;
	        if (noise5 != 0. && noise5 < stdev) stdev = noise5;
            }

	    if (qlevel == 0.)
	        delta = stdev / 4.;  /* default quantization */
	    else
	        delta = stdev / qlevel;

	    if (delta == 0.) 
	        return (0);			/* don't quantize */

	} else {
	    /* negative value represents the absolute quantization level */
	    delta = -qlevel;

	    /* only nned to calculate the min and max values */
	    FnNoise3_float(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
	        &minval, &maxval, 0, &status);      
 	}

        /* check that the range of quantized levels is not > range of int */
	if ((maxval - minval) / delta > 2. * 2147483647. - N_RESERVED_VALUES )
	    return (0);			/* don't quantize */

        if (row > 0) { /* we need to dither the quantized values */
            if (!fits_rand_value) 
	        if (fits_init_randoms()) return(MEMORY_ALLOCATION);

	    /* initialize the index to the next random number in the list */
            iseed = (int) ((row - 1) % N_RANDOM);
	    nextrand = (int) (fits_rand_value[iseed] * 500.);
	}

        if (ngood == nx) {   /* don't have to check for nulls */
            /* return all positive values, if possible since some */
            /* compression algorithms either only work for positive integers, */
            /* or are more efficient.  */

            if (dither_method == SUBTRACTIVE_DITHER_2)
	    {
                /* shift the range to be close to the value used to represent zeros */
                zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);
            }
	    else if ((maxval - minval) / delta < 2147483647. - N_RESERVED_VALUES )
            {
                zeropt = minval;
		/* fudge the zero point so it is an integer multiple of delta */
		/* This helps to ensure the same scaling will be performed if the */
		/* file undergoes multiple fpack/funpack cycles */
		iqfactor = (LONGLONG) (zeropt/delta  + 0.5);
		zeropt = iqfactor * delta;               
            }
            else
            {
                /* center the quantized levels around zero */
                zeropt = (minval + maxval) / 2.;
            }

            if (row > 0) {  /* dither the values when quantizing */
              for (i = 0;  i < nx;  i++) {
	    
		if (dither_method == SUBTRACTIVE_DITHER_2 && fdata[i] == 0.0) {
		   idata[i] = ZERO_VALUE;
		} else {
		   idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
		}

                nextrand++;
		if (nextrand == N_RANDOM) {
                    iseed++;
		    if (iseed == N_RANDOM) iseed = 0;
	            nextrand = (int) (fits_rand_value[iseed] * 500);
                }
              }
            } else {  /* do not dither the values */

       	        for (i = 0;  i < nx;  i++) {
	            idata[i] = NINT ((fdata[i] - zeropt) / delta);
                }
            } 
        }
        else {
            /* data contains null values; shift the range to be */
            /* close to the value used to represent null values */
            zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);

            if (row > 0) {  /* dither the values */
	      for (i = 0;  i < nx;  i++) {
                if (fdata[i] != in_null_value) {
		    if (dither_method == SUBTRACTIVE_DITHER_2 && fdata[i] == 0.0) {
		       idata[i] = ZERO_VALUE;
		    } else {
		       idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
		    }
                } else {
                    idata[i] = NULL_VALUE;
                }

                /* increment the random number index, regardless */
                nextrand++;
		if (nextrand == N_RANDOM) {
                      iseed++;
		      if (iseed == N_RANDOM) iseed = 0;
	              nextrand = (int) (fits_rand_value[iseed] * 500);
                }
              }
            } else {  /* do not dither the values */
	       for (i = 0;  i < nx;  i++) {
 
                 if (fdata[i] != in_null_value) {
		    idata[i] =  NINT((fdata[i] - zeropt) / delta);
                 } else { 
                    idata[i] = NULL_VALUE;
                 }
               }
            }
	}

        /* calc min and max values */
        temp = (minval - zeropt) / delta;
        *iminval =  NINT (temp);
        temp = (maxval - zeropt) / delta;
        *imaxval =  NINT (temp);

	*bscale = delta;
	*bzero = zeropt;
	return (1);			/* yes, data have been quantized */
}
/*---------------------------------------------------------------------------*/
int fits_quantize_double (long row, double fdata[], long nxpix, long nypix, int nullcheck, 
	double in_null_value, float qlevel, int dither_method, int idata[], double *bscale,
	double *bzero, int *iminval, int *imaxval) {

/* arguments:
long row            i: tile number = row number in the binary table
double fdata[]      i: array of image pixels to be compressed
long nxpix          i: number of pixels in each row of fdata
long nypix          i: number of rows in fdata
nullcheck           i: check for nullvalues in fdata?
double in_null_value i: value used to represent undefined pixels in fdata
float qlevel        i: quantization level
int dither_method   i; which dithering method to use
int idata[]         o: values of fdata after applying bzero and bscale
double bscale       o: scale factor
double bzero        o: zero offset
int iminval         o: minimum quantized value that is returned
int imaxval         o: maximum quantized value that is returned

The function value will be one if the input fdata were copied to idata;
in this case the parameters bscale and bzero can be used to convert back to
nearly the original floating point values:  fdata ~= idata * bscale + bzero.
If the function value is zero, the data were not copied to idata.
*/

	int status, iseed = 0;
	long i, nx, ngood = 0;
	double stdev, noise2 = 0., noise3 = 0., noise5 = 0.;	/* MAD 2nd, 3rd, and 5th order noise values */
	double minval = 0., maxval = 0.;  /* min & max of fdata */
	double delta;		/* bscale, 1 in idata = delta in fdata */
	double zeropt;	        /* bzero */
	double temp;
        int nextrand = 0;
	extern float *fits_rand_value;
	LONGLONG iqfactor;

	nx = nxpix * nypix;
	if (nx <= 1) {
	    *bscale = 1.;
	    *bzero  = 0.;
	    return (0);
	}

        if (qlevel >= 0.) {

	    /* estimate background noise using MAD pixel differences */
	    FnNoise5_double(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
	        &minval, &maxval, &noise2, &noise3, &noise5, &status);      

	    if (nullcheck && ngood == 0) {   /* special case of an image filled with Nulls */
	        /* set parameters to dummy values, which are not used */
		minval = 0.;
		maxval = 1.;
		stdev = 1;
	    } else {

	        /* use the minimum of noise2, noise3, and noise5 as the best noise value */
	        stdev = noise3;
	        if (noise2 != 0. && noise2 < stdev) stdev = noise2;
	        if (noise5 != 0. && noise5 < stdev) stdev = noise5;
            }

	    if (qlevel == 0.)
	        delta = stdev / 4.;  /* default quantization */
	    else
	        delta = stdev / qlevel;

	    if (delta == 0.) 
	        return (0);			/* don't quantize */

	} else {
	    /* negative value represents the absolute quantization level */
	    delta = -qlevel;

	    /* only nned to calculate the min and max values */
	    FnNoise3_double(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
	        &minval, &maxval, 0, &status);      
 	}

        /* check that the range of quantized levels is not > range of int */
	if ((maxval - minval) / delta > 2. * 2147483647. - N_RESERVED_VALUES )
	    return (0);			/* don't quantize */

        if (row > 0) { /* we need to dither the quantized values */
            if (!fits_rand_value) 
	       if (fits_init_randoms()) return(MEMORY_ALLOCATION);

	    /* initialize the index to the next random number in the list */
            iseed = (int) ((row - 1) % N_RANDOM);
	    nextrand = (int) (fits_rand_value[iseed] * 500);
	}

        if (ngood == nx) {   /* don't have to check for nulls */
            /* return all positive values, if possible since some */
            /* compression algorithms either only work for positive integers, */
            /* or are more efficient.  */

            if (dither_method == SUBTRACTIVE_DITHER_2)
	    {
                /* shift the range to be close to the value used to represent zeros */
                zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);
            }
	    else if ((maxval - minval) / delta < 2147483647. - N_RESERVED_VALUES )
            {
                zeropt = minval;
		/* fudge the zero point so it is an integer multiple of delta */
		/* This helps to ensure the same scaling will be performed if the */
		/* file undergoes multiple fpack/funpack cycles */
		iqfactor = (LONGLONG) (zeropt/delta  + 0.5);
		zeropt = iqfactor * delta;               
            }
            else
            {
                /* center the quantized levels around zero */
                zeropt = (minval + maxval) / 2.;
            }

            if (row > 0) {  /* dither the values when quantizing */
       	      for (i = 0;  i < nx;  i++) {

		if (dither_method == SUBTRACTIVE_DITHER_2 && fdata[i] == 0.0) {
		   idata[i] = ZERO_VALUE;
		} else {
		   idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
		}

                nextrand++;
		if (nextrand == N_RANDOM) {
                    iseed++;
	            nextrand = (int) (fits_rand_value[iseed] * 500);
                }
              }
            } else {  /* do not dither the values */

       	        for (i = 0;  i < nx;  i++) {
	            idata[i] = NINT ((fdata[i] - zeropt) / delta);
                }
            } 
        }
        else {
            /* data contains null values; shift the range to be */
            /* close to the value used to represent null values */
            zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);

            if (row > 0) {  /* dither the values */
	      for (i = 0;  i < nx;  i++) {
                if (fdata[i] != in_null_value) {
		    if (dither_method == SUBTRACTIVE_DITHER_2 && fdata[i] == 0.0) {
		       idata[i] = ZERO_VALUE;
		    } else {
		       idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
		    }
                } else {
                    idata[i] = NULL_VALUE;
                }

                /* increment the random number index, regardless */
                nextrand++;
		if (nextrand == N_RANDOM) {
                        iseed++;
	                nextrand = (int) (fits_rand_value[iseed] * 500);
                } 
              }
            } else {  /* do not dither the values */
	       for (i = 0;  i < nx;  i++) {
                 if (fdata[i] != in_null_value)
		    idata[i] =  NINT((fdata[i] - zeropt) / delta);
                 else 
                    idata[i] = NULL_VALUE;
               }
            }
	}

        /* calc min and max values */
        temp = (minval - zeropt) / delta;
        *iminval =  NINT (temp);
        temp = (maxval - zeropt) / delta;
        *imaxval =  NINT (temp);

	*bscale = delta;
	*bzero = zeropt;

	return (1);			/* yes, data have been quantized */
}
/*--------------------------------------------------------------------------*/
int fits_img_stats_short(short *array, /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
	long ny,            /* number of rows in the image */
	                    /* (if this is a 3D image, then ny should be the */
			    /* product of the no. of rows times the no. of planes) */
	int nullcheck,      /* check for null values, if true */
	short nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters (if the pointer is not null)  */
	long *ngoodpix,     /* number of non-null pixels in the image */
	short *minvalue,    /* returned minimum non-null value in the array */
	short *maxvalue,    /* returned maximum non-null value in the array */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	double *noise1,     /* 1st order estimate of noise in image background level */
	double *noise2,     /* 2nd order estimate of noise in image background level */
	double *noise3,     /* 3rd order estimate of noise in image background level */
	double *noise5,     /* 5th order estimate of noise in image background level */
	int *status)        /* error status */

/*
    Compute statistics of the input short integer image.
*/
{
	long ngood;
	short minval = 0, maxval = 0;
	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;

	/* need to calculate mean and/or sigma and/or limits? */
	if (mean || sigma ) {
		FnMeanSigma_short(array, nx * ny, nullcheck, nullvalue, 
			&ngood, &xmean, &xsigma, status);

	    if (ngoodpix) *ngoodpix = ngood;
	    if (mean)     *mean = xmean;
	    if (sigma)    *sigma = xsigma;
	}

	if (noise1) {
		FnNoise1_short(array, nx, ny, nullcheck, nullvalue, 
		  &xnoise, status);

		*noise1  = xnoise;
	}

	if (minvalue || maxvalue || noise3) {
		FnNoise5_short(array, nx, ny, nullcheck, nullvalue, 
			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);

		if (ngoodpix) *ngoodpix = ngood;
		if (minvalue) *minvalue= minval;
		if (maxvalue) *maxvalue = maxval;
		if (noise2) *noise2  = xnoise2;
		if (noise3) *noise3  = xnoise3;
		if (noise5) *noise5  = xnoise5;
	}
	return(*status);
}
/*--------------------------------------------------------------------------*/
int fits_img_stats_int(int *array, /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
	long ny,            /* number of rows in the image */
	                    /* (if this is a 3D image, then ny should be the */
			    /* product of the no. of rows times the no. of planes) */
	int nullcheck,      /* check for null values, if true */
	int nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters (if the pointer is not null)  */
	long *ngoodpix,     /* number of non-null pixels in the image */
	int *minvalue,    /* returned minimum non-null value in the array */
	int *maxvalue,    /* returned maximum non-null value in the array */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	double *noise1,     /* 1st order estimate of noise in image background level */
	double *noise2,     /* 2nd order estimate of noise in image background level */
	double *noise3,     /* 3rd order estimate of noise in image background level */
	double *noise5,     /* 5th order estimate of noise in image background level */
	int *status)        /* error status */

/*
    Compute statistics of the input integer image.
*/
{
	long ngood;
	int minval = 0, maxval = 0;
	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;

	/* need to calculate mean and/or sigma and/or limits? */
	if (mean || sigma ) {
		FnMeanSigma_int(array, nx * ny, nullcheck, nullvalue, 
			&ngood, &xmean, &xsigma, status);

	    if (ngoodpix) *ngoodpix = ngood;
	    if (mean)     *mean = xmean;
	    if (sigma)    *sigma = xsigma;
	}

	if (noise1) {
		FnNoise1_int(array, nx, ny, nullcheck, nullvalue, 
		  &xnoise, status);

		*noise1  = xnoise;
	}

	if (minvalue || maxvalue || noise3) {
		FnNoise5_int(array, nx, ny, nullcheck, nullvalue, 
			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);

		if (ngoodpix) *ngoodpix = ngood;
		if (minvalue) *minvalue= minval;
		if (maxvalue) *maxvalue = maxval;
		if (noise2) *noise2  = xnoise2;
		if (noise3) *noise3  = xnoise3;
		if (noise5) *noise5  = xnoise5;
	}
	return(*status);
}
/*--------------------------------------------------------------------------*/
int fits_img_stats_float(float *array, /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
	long ny,            /* number of rows in the image */
	                    /* (if this is a 3D image, then ny should be the */
			    /* product of the no. of rows times the no. of planes) */
	int nullcheck,      /* check for null values, if true */
	float nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters (if the pointer is not null)  */
	long *ngoodpix,     /* number of non-null pixels in the image */
	float *minvalue,    /* returned minimum non-null value in the array */
	float *maxvalue,    /* returned maximum non-null value in the array */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	double *noise1,     /* 1st order estimate of noise in image background level */
	double *noise2,     /* 2nd order estimate of noise in image background level */
	double *noise3,     /* 3rd order estimate of noise in image background level */
	double *noise5,     /* 5th order estimate of noise in image background level */
	int *status)        /* error status */

/*
    Compute statistics of the input float image.
*/
{
	long ngood;
	float minval, maxval;
	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;

	/* need to calculate mean and/or sigma and/or limits? */
	if (mean || sigma ) {
		FnMeanSigma_float(array, nx * ny, nullcheck, nullvalue, 
			&ngood, &xmean, &xsigma, status);

	    if (ngoodpix) *ngoodpix = ngood;
	    if (mean)     *mean = xmean;
	    if (sigma)    *sigma = xsigma;
	}

	if (noise1) {
		FnNoise1_float(array, nx, ny, nullcheck, nullvalue, 
		  &xnoise, status);

		*noise1  = xnoise;
	}

	if (minvalue || maxvalue || noise3) {
		FnNoise5_float(array, nx, ny, nullcheck, nullvalue, 
			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);

		if (ngoodpix) *ngoodpix = ngood;
		if (minvalue) *minvalue= minval;
		if (maxvalue) *maxvalue = maxval;
		if (noise2) *noise2  = xnoise2;
		if (noise3) *noise3  = xnoise3;
		if (noise5) *noise5  = xnoise5;
	}
	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnMeanSigma_short
       (short *array,       /*  2 dimensional array of image pixels */
        long npix,          /* number of pixels in the image */
	int nullcheck,      /* check for null values, if true */
	short nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters */
   
	long *ngoodpix,     /* number of non-null pixels in the image */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Compute mean and RMS sigma of the non-null pixels in the input array.
*/
{
	long ii, ngood = 0;
	short *value;
	double sum = 0., sum2 = 0., xtemp;

	value = array;
	    
	if (nullcheck) {
	        for (ii = 0; ii < npix; ii++, value++) {
		    if (*value != nullvalue) {
		        ngood++;
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		    }
		}
	} else {
	        ngood = npix;
	        for (ii = 0; ii < npix; ii++, value++) {
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		}
	}

	if (ngood > 1) {
		if (ngoodpix) *ngoodpix = ngood;
		xtemp = sum / ngood;
		if (mean)     *mean = xtemp;
		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
	} else if (ngood == 1){
		if (ngoodpix) *ngoodpix = 1;
		if (mean)     *mean = sum;
		if (sigma)    *sigma = 0.0;
	} else {
		if (ngoodpix) *ngoodpix = 0;
	        if (mean)     *mean = 0.;
		if (sigma)    *sigma = 0.;
	}	    
	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnMeanSigma_int
       (int *array,       /*  2 dimensional array of image pixels */
        long npix,          /* number of pixels in the image */
	int nullcheck,      /* check for null values, if true */
	int nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters */
   
	long *ngoodpix,     /* number of non-null pixels in the image */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Compute mean and RMS sigma of the non-null pixels in the input array.
*/
{
	long ii, ngood = 0;
	int *value;
	double sum = 0., sum2 = 0., xtemp;

	value = array;
	    
	if (nullcheck) {
	        for (ii = 0; ii < npix; ii++, value++) {
		    if (*value != nullvalue) {
		        ngood++;
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		    }
		}
	} else {
	        ngood = npix;
	        for (ii = 0; ii < npix; ii++, value++) {
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		}
	}

	if (ngood > 1) {
		if (ngoodpix) *ngoodpix = ngood;
		xtemp = sum / ngood;
		if (mean)     *mean = xtemp;
		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
	} else if (ngood == 1){
		if (ngoodpix) *ngoodpix = 1;
		if (mean)     *mean = sum;
		if (sigma)    *sigma = 0.0;
	} else {
		if (ngoodpix) *ngoodpix = 0;
	        if (mean)     *mean = 0.;
		if (sigma)    *sigma = 0.;
	}	    
	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnMeanSigma_float
       (float *array,       /*  2 dimensional array of image pixels */
        long npix,          /* number of pixels in the image */
	int nullcheck,      /* check for null values, if true */
	float nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters */
   
	long *ngoodpix,     /* number of non-null pixels in the image */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Compute mean and RMS sigma of the non-null pixels in the input array.
*/
{
	long ii, ngood = 0;
	float *value;
	double sum = 0., sum2 = 0., xtemp;

	value = array;
	    
	if (nullcheck) {
	        for (ii = 0; ii < npix; ii++, value++) {
		    if (*value != nullvalue) {
		        ngood++;
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		    }
		}
	} else {
	        ngood = npix;
	        for (ii = 0; ii < npix; ii++, value++) {
		        xtemp = (double) *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		}
	}

	if (ngood > 1) {
		if (ngoodpix) *ngoodpix = ngood;
		xtemp = sum / ngood;
		if (mean)     *mean = xtemp;
		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
	} else if (ngood == 1){
		if (ngoodpix) *ngoodpix = 1;
		if (mean)     *mean = sum;
		if (sigma)    *sigma = 0.0;
	} else {
		if (ngoodpix) *ngoodpix = 0;
	        if (mean)     *mean = 0.;
		if (sigma)    *sigma = 0.;
	}	    
	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnMeanSigma_double
       (double *array,       /*  2 dimensional array of image pixels */
        long npix,          /* number of pixels in the image */
	int nullcheck,      /* check for null values, if true */
	double nullvalue,    /* value of null pixels, if nullcheck is true */

   /* returned parameters */
   
	long *ngoodpix,     /* number of non-null pixels in the image */
	double *mean,       /* returned mean value of all non-null pixels */
	double *sigma,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Compute mean and RMS sigma of the non-null pixels in the input array.
*/
{
	long ii, ngood = 0;
	double *value;
	double sum = 0., sum2 = 0., xtemp;

	value = array;
	    
	if (nullcheck) {
	        for (ii = 0; ii < npix; ii++, value++) {
		    if (*value != nullvalue) {
		        ngood++;
		        xtemp = *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		    }
		}
	} else {
	        ngood = npix;
	        for (ii = 0; ii < npix; ii++, value++) {
		        xtemp = *value;
		        sum += xtemp;
		        sum2 += (xtemp * xtemp);
		}
	}

	if (ngood > 1) {
		if (ngoodpix) *ngoodpix = ngood;
		xtemp = sum / ngood;
		if (mean)     *mean = xtemp;
		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
	} else if (ngood == 1){
		if (ngoodpix) *ngoodpix = 1;
		if (mean)     *mean = sum;
		if (sigma)    *sigma = 0.0;
	} else {
		if (ngoodpix) *ngoodpix = 0;
	        if (mean)     *mean = 0.;
		if (sigma)    *sigma = 0.;
	}	    
	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise5_short
       (short *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	short nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	short *minval,    /* minimum non-null value */
	short *maxval,    /* maximum non-null value */
	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
	double *noise5,      /* returned 5th order MAD of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 2nd, 3rd and 5th
order Median Absolute Differences.

The noise in the background of the image is calculated using the MAD algorithms 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
	int *differences2, *differences3, *differences5;
	short *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
	short xminval = SHRT_MAX, xmaxval = SHRT_MIN;
	int do_range = 0;
	double *diffs2, *diffs3, *diffs5; 
	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 9 pixels */
	if (nx < 9) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise2) *noise2 = 0.;
		if (noise3) *noise3 = 0.;
		if (noise5) *noise5 = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences2 = calloc(nx, sizeof(int));
	if (!differences2) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences3 = calloc(nx, sizeof(int));
	if (!differences3) {
		free(differences2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences5 = calloc(nx, sizeof(int));
	if (!differences5) {
		free(differences2);
		free(differences3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs2 = calloc(ny, sizeof(double));
	if (!diffs2) {
		free(differences2);
		free(differences3);
		free(differences5);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs3 = calloc(ny, sizeof(double));
	if (!diffs3) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs5 = calloc(ny, sizeof(double));
	if (!diffs5) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
		free(diffs3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
			
		/* find the 5th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v5 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		}
				
		/* find the 6th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v6 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v6 < xminval) xminval = v6;
			if (v6 > xmaxval) xmaxval = v6;
		}
				
		/* find the 7th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v7 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v7 < xminval) xminval = v7;
			if (v7 > xmaxval) xmaxval = v7;
		}
				
		/* find the 8th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v8 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v8 < xminval) xminval = v8;
			if (v8 > xmaxval) xmaxval = v8;
		}
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		nvals2 = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v9 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v9 < xminval) xminval = v9;
			if (v9 > xmaxval) xmaxval = v9;
		    }

		    /* construct array of absolute differences */

		    if (!(v5 == v6 && v6 == v7) ) {
		        differences2[nvals2] =  abs((int) v5 - (int) v7);
			nvals2++;
		    }

		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
		        differences3[nvals] =  abs((2 * (int) v5) - (int) v3 - (int) v7);
		        differences5[nvals] =  abs((6 * (int) v5) - (4 * (int) v3) - (4 * (int) v7) + (int) v1 + (int) v9);
		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
		    v5 = v6;
		    v6 = v7;
		    v7 = v8;
		    v8 = v9;
	        }  /* end of loop over pixels in the row */

		/* compute the median diffs */
		/* Note that there are 8 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 8);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    if (nvals2 == 1) {
		        diffs2[nrows2] = differences2[0];
			nrows2++;
		    }
		        
		    diffs3[nrows] = differences3[0];
		    diffs5[nrows] = differences5[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
		    if (nvals2 > 1) {
                        diffs2[nrows2] = quick_select_int(differences2, nvals);
			nrows2++;
		    }

                    diffs3[nrows] = quick_select_int(differences3, nvals);
                    diffs5[nrows] = quick_select_int(differences5, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise3 = 0;
	       xnoise5 = 0;
	} else if (nrows == 1) {
	       xnoise3 = diffs3[0];
	       xnoise5 = diffs5[0];
	} else {	    
	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
	}

	if (nrows2 == 0) { 
	       xnoise2 = 0;
	} else if (nrows2 == 1) {
	       xnoise2 = diffs2[0];
	} else {	    
	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise2)  *noise2  = 1.0483579 * xnoise2;
	if (noise3)  *noise3  = 0.6052697 * xnoise3;
	if (noise5)  *noise5  = 0.1772048 * xnoise5;

	free(diffs5);
	free(diffs3);
	free(diffs2);
	free(differences5);
	free(differences3);
	free(differences2);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise5_int
       (int *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	int nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	int *minval,    /* minimum non-null value */
	int *maxval,    /* maximum non-null value */
	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
	double *noise5,      /* returned 5th order MAD of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 2nd, 3rd and 5th
order Median Absolute Differences.

The noise in the background of the image is calculated using the MAD algorithms 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
	LONGLONG *differences2, *differences3, *differences5, tdiff;
	int *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
	int xminval = INT_MAX, xmaxval = INT_MIN;
	int do_range = 0;
	double *diffs2, *diffs3, *diffs5; 
	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 9 pixels */
	if (nx < 9) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise2) *noise2 = 0.;
		if (noise3) *noise3 = 0.;
		if (noise5) *noise5 = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences2 = calloc(nx, sizeof(LONGLONG));
	if (!differences2) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences3 = calloc(nx, sizeof(LONGLONG));
	if (!differences3) {
		free(differences2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences5 = calloc(nx, sizeof(LONGLONG));
	if (!differences5) {
		free(differences2);
		free(differences3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs2 = calloc(ny, sizeof(double));
	if (!diffs2) {
		free(differences2);
		free(differences3);
		free(differences5);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs3 = calloc(ny, sizeof(double));
	if (!diffs3) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs5 = calloc(ny, sizeof(double));
	if (!diffs5) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
		free(diffs3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
			
		/* find the 5th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v5 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		}
				
		/* find the 6th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v6 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v6 < xminval) xminval = v6;
			if (v6 > xmaxval) xmaxval = v6;
		}
				
		/* find the 7th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v7 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v7 < xminval) xminval = v7;
			if (v7 > xmaxval) xmaxval = v7;
		}
				
		/* find the 8th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v8 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v8 < xminval) xminval = v8;
			if (v8 > xmaxval) xmaxval = v8;
		}
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		nvals2 = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v9 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v9 < xminval) xminval = v9;
			if (v9 > xmaxval) xmaxval = v9;
		    }

		    /* construct array of absolute differences */

		    if (!(v5 == v6 && v6 == v7) ) {
		        tdiff =  (LONGLONG) v5 - (LONGLONG) v7;
			if (tdiff < 0)
		            differences2[nvals2] =  -1 * tdiff;
			else
		            differences2[nvals2] =  tdiff;

			nvals2++;
		    }

		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
		        tdiff =  (2 * (LONGLONG) v5) - (LONGLONG) v3 - (LONGLONG) v7;
			if (tdiff < 0)
		            differences3[nvals] =  -1 * tdiff;
			else
		            differences3[nvals] =  tdiff;

		        tdiff =  (6 * (LONGLONG) v5) - (4 * (LONGLONG) v3) - (4 * (LONGLONG) v7) + (LONGLONG) v1 + (LONGLONG) v9;
			if (tdiff < 0)
		            differences5[nvals] =  -1 * tdiff;
			else
		            differences5[nvals] =  tdiff;

		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
		    v5 = v6;
		    v6 = v7;
		    v7 = v8;
		    v8 = v9;
	        }  /* end of loop over pixels in the row */

		/* compute the median diffs */
		/* Note that there are 8 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 8);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    if (nvals2 == 1) {
		        diffs2[nrows2] = (double) differences2[0];
			nrows2++;
		    }
		        
		    diffs3[nrows] = (double) differences3[0];
		    diffs5[nrows] = (double) differences5[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
		    if (nvals2 > 1) {
                        diffs2[nrows2] = (double) quick_select_longlong(differences2, nvals);
			nrows2++;
		    }

                    diffs3[nrows] = (double) quick_select_longlong(differences3, nvals);
                    diffs5[nrows] = (double) quick_select_longlong(differences5, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise3 = 0;
	       xnoise5 = 0;
	} else if (nrows == 1) {
	       xnoise3 = diffs3[0];
	       xnoise5 = diffs5[0];
	} else {	    
	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
	}

	if (nrows2 == 0) { 
	       xnoise2 = 0;
	} else if (nrows2 == 1) {
	       xnoise2 = diffs2[0];
	} else {	    
	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise2)  *noise2  = 1.0483579 * xnoise2;
	if (noise3)  *noise3  = 0.6052697 * xnoise3;
	if (noise5)  *noise5  = 0.1772048 * xnoise5;

	free(diffs5);
	free(diffs3);
	free(diffs2);
	free(differences5);
	free(differences3);
	free(differences2);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise5_float
       (float *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	float nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	float *minval,    /* minimum non-null value */
	float *maxval,    /* maximum non-null value */
	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
	double *noise5,      /* returned 5th order MAD of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 2nd, 3rd and 5th
order Median Absolute Differences.

The noise in the background of the image is calculated using the MAD algorithms 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
	float *differences2, *differences3, *differences5;
	float *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
	float xminval = FLT_MAX, xmaxval = -FLT_MAX;
	int do_range = 0;
	double *diffs2, *diffs3, *diffs5; 
	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 9 pixels */
	if (nx < 9) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise2) *noise2 = 0.;
		if (noise3) *noise3 = 0.;
		if (noise5) *noise5 = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences2 = calloc(nx, sizeof(float));
	if (!differences2) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences3 = calloc(nx, sizeof(float));
	if (!differences3) {
		free(differences2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences5 = calloc(nx, sizeof(float));
	if (!differences5) {
		free(differences2);
		free(differences3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs2 = calloc(ny, sizeof(double));
	if (!diffs2) {
		free(differences2);
		free(differences3);
		free(differences5);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs3 = calloc(ny, sizeof(double));
	if (!diffs3) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs5 = calloc(ny, sizeof(double));
	if (!diffs5) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
		free(diffs3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
			
		/* find the 5th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v5 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		}
				
		/* find the 6th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v6 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v6 < xminval) xminval = v6;
			if (v6 > xmaxval) xmaxval = v6;
		}
				
		/* find the 7th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v7 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v7 < xminval) xminval = v7;
			if (v7 > xmaxval) xmaxval = v7;
		}
				
		/* find the 8th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v8 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v8 < xminval) xminval = v8;
			if (v8 > xmaxval) xmaxval = v8;
		}
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		nvals2 = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v9 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v9 < xminval) xminval = v9;
			if (v9 > xmaxval) xmaxval = v9;
		    }

		    /* construct array of absolute differences */

		    if (!(v5 == v6 && v6 == v7) ) {
		        differences2[nvals2] = (float) fabs(v5 - v7);
			nvals2++;
		    }

		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
		        differences3[nvals] = (float) fabs((2 * v5) - v3 - v7);
		        differences5[nvals] = (float) fabs((6 * v5) - (4 * v3) - (4 * v7) + v1 + v9);
		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
		    v5 = v6;
		    v6 = v7;
		    v7 = v8;
		    v8 = v9;
	        }  /* end of loop over pixels in the row */

		/* compute the median diffs */
		/* Note that there are 8 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 8);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    if (nvals2 == 1) {
		        diffs2[nrows2] = differences2[0];
			nrows2++;
		    }
		        
		    diffs3[nrows] = differences3[0];
		    diffs5[nrows] = differences5[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
		    if (nvals2 > 1) {
                        diffs2[nrows2] = quick_select_float(differences2, nvals);
			nrows2++;
		    }

                    diffs3[nrows] = quick_select_float(differences3, nvals);
                    diffs5[nrows] = quick_select_float(differences5, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise3 = 0;
	       xnoise5 = 0;
	} else if (nrows == 1) {
	       xnoise3 = diffs3[0];
	       xnoise5 = diffs5[0];
	} else {	    
	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
	}

	if (nrows2 == 0) { 
	       xnoise2 = 0;
	} else if (nrows2 == 1) {
	       xnoise2 = diffs2[0];
	} else {	    
	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise2)  *noise2  = 1.0483579 * xnoise2;
	if (noise3)  *noise3  = 0.6052697 * xnoise3;
	if (noise5)  *noise5  = 0.1772048 * xnoise5;

	free(diffs5);
	free(diffs3);
	free(diffs2);
	free(differences5);
	free(differences3);
	free(differences2);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise5_double
       (double *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	double nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	double *minval,    /* minimum non-null value */
	double *maxval,    /* maximum non-null value */
	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
	double *noise5,      /* returned 5th order MAD of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 2nd, 3rd and 5th
order Median Absolute Differences.

The noise in the background of the image is calculated using the MAD algorithms 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
	double *differences2, *differences3, *differences5;
	double *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
	double xminval = DBL_MAX, xmaxval = -DBL_MAX;
	int do_range = 0;
	double *diffs2, *diffs3, *diffs5; 
	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 9 pixels */
	if (nx < 9) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise2) *noise2 = 0.;
		if (noise3) *noise3 = 0.;
		if (noise5) *noise5 = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences2 = calloc(nx, sizeof(double));
	if (!differences2) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences3 = calloc(nx, sizeof(double));
	if (!differences3) {
		free(differences2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}
	differences5 = calloc(nx, sizeof(double));
	if (!differences5) {
		free(differences2);
		free(differences3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs2 = calloc(ny, sizeof(double));
	if (!diffs2) {
		free(differences2);
		free(differences3);
		free(differences5);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs3 = calloc(ny, sizeof(double));
	if (!diffs3) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs5 = calloc(ny, sizeof(double));
	if (!diffs5) {
		free(differences2);
		free(differences3);
		free(differences5);
		free(diffs2);
		free(diffs3);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
			
		/* find the 5th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v5 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		}
				
		/* find the 6th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v6 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v6 < xminval) xminval = v6;
			if (v6 > xmaxval) xmaxval = v6;
		}
				
		/* find the 7th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v7 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v7 < xminval) xminval = v7;
			if (v7 > xmaxval) xmaxval = v7;
		}
				
		/* find the 8th valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v8 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v8 < xminval) xminval = v8;
			if (v8 > xmaxval) xmaxval = v8;
		}
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		nvals2 = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v9 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v9 < xminval) xminval = v9;
			if (v9 > xmaxval) xmaxval = v9;
		    }

		    /* construct array of absolute differences */

		    if (!(v5 == v6 && v6 == v7) ) {
		        differences2[nvals2] =  fabs(v5 - v7);
			nvals2++;
		    }

		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
		        differences3[nvals] =  fabs((2 * v5) - v3 - v7);
		        differences5[nvals] =  fabs((6 * v5) - (4 * v3) - (4 * v7) + v1 + v9);
		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
		    v5 = v6;
		    v6 = v7;
		    v7 = v8;
		    v8 = v9;
	        }  /* end of loop over pixels in the row */

		/* compute the median diffs */
		/* Note that there are 8 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 8);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    if (nvals2 == 1) {
		        diffs2[nrows2] = differences2[0];
			nrows2++;
		    }
		        
		    diffs3[nrows] = differences3[0];
		    diffs5[nrows] = differences5[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
		    if (nvals2 > 1) {
                        diffs2[nrows2] = quick_select_double(differences2, nvals);
			nrows2++;
		    }

                    diffs3[nrows] = quick_select_double(differences3, nvals);
                    diffs5[nrows] = quick_select_double(differences5, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise3 = 0;
	       xnoise5 = 0;
	} else if (nrows == 1) {
	       xnoise3 = diffs3[0];
	       xnoise5 = diffs5[0];
	} else {	    
	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
	}

	if (nrows2 == 0) { 
	       xnoise2 = 0;
	} else if (nrows2 == 1) {
	       xnoise2 = diffs2[0];
	} else {	    
	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise2)  *noise2  = 1.0483579 * xnoise2;
	if (noise3)  *noise3  = 0.6052697 * xnoise3;
	if (noise5)  *noise5  = 0.1772048 * xnoise5;

	free(diffs5);
	free(diffs3);
	free(diffs2);
	free(differences5);
	free(differences3);
	free(differences2);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise3_short
       (short *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	short nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	short *minval,    /* minimum non-null value */
	short *maxval,    /* maximum non-null value */
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 3rd order differences.

The noise in the background of the image is calculated using the 3rd order algorithm 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
	short *differences, *rowpix, v1, v2, v3, v4, v5;
	short xminval = SHRT_MAX, xmaxval = SHRT_MIN, do_range = 0;
	double *diffs, xnoise = 0, sigma;

	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 5 pixels */
	if (nx < 5) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise) *noise = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(short));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
		
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v5 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		    }

		    /* construct array of 3rd order absolute differences */
		    if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
		        differences[nvals] = abs((2 * v3) - v1 - v5);
		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }


		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
	        }  /* end of loop over pixels in the row */

		/* compute the 3rd order diffs */
		/* Note that there are 4 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 4);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    diffs[nrows] = differences[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
                    diffs[nrows] = quick_select_short(differences, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {	    


	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;

              FnMeanSigma_double(diffs, nrows, 0, 0.0, 0, &xnoise, &sigma, status); 

	      /* do a 4.5 sigma rejection of outliers */
	      jj = 0;
	      sigma = 4.5 * sigma;
	      for (ii = 0; ii < nrows; ii++) {
		if ( fabs(diffs[ii] - xnoise) <= sigma)	 {
		   if (jj != ii)
		       diffs[jj] = diffs[ii];
		   jj++;
	        } 
	      }
	      if (ii != jj)
                FnMeanSigma_double(diffs, jj, 0, 0.0, 0, &xnoise, &sigma, status); 
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise)  *noise  = 0.6052697 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise3_int
       (int *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	int nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	int *minval,    /* minimum non-null value */
	int *maxval,    /* maximum non-null value */
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the background noise in the input image using 3rd order differences.

The noise in the background of the image is calculated using the 3rd order algorithm 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
	int *differences, *rowpix, v1, v2, v3, v4, v5;
	int xminval = INT_MAX, xmaxval = INT_MIN, do_range = 0;
	double *diffs, xnoise = 0, sigma;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 5 pixels */
	if (nx < 5) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise) *noise = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(int));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
		
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v5 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		    }

		    /* construct array of 3rd order absolute differences */
		    if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
		        differences[nvals] = abs((2 * v3) - v1 - v5);
		        nvals++;  
		    } else {
		        /* ignore constant background regions */
			ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
	        }  /* end of loop over pixels in the row */

		/* compute the 3rd order diffs */
		/* Note that there are 4 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 4);

		if (nvals == 0) {
		    continue;  /* cannot compute medians on this row */
		} else if (nvals == 1) {
		    diffs[nrows] = differences[0];
		} else {
                    /* quick_select returns the median MUCH faster than using qsort */
                    diffs[nrows] = quick_select_int(differences, nvals);
		}

		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {	    

	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;

              FnMeanSigma_double(diffs, nrows, 0, 0.0, 0, &xnoise, &sigma, status); 

	      /* do a 4.5 sigma rejection of outliers */
	      jj = 0;
	      sigma = 4.5 * sigma;
	      for (ii = 0; ii < nrows; ii++) {
		if ( fabs(diffs[ii] - xnoise) <= sigma)	 {
		   if (jj != ii)
		       diffs[jj] = diffs[ii];
		   jj++;
	        }
	      }
	      if (ii != jj)
                FnMeanSigma_double(diffs, jj, 0, 0.0, 0, &xnoise, &sigma, status); 
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise)  *noise  = 0.6052697 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise3_float
       (float *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	float nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	float *minval,    /* minimum non-null value */
	float *maxval,    /* maximum non-null value */
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 3rd order differences.

The noise in the background of the image is calculated using the 3rd order algorithm 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
	float *differences, *rowpix, v1, v2, v3, v4, v5;
	float xminval = FLT_MAX, xmaxval = -FLT_MAX;
	int do_range = 0;
	double *diffs, xnoise = 0;

	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 5 pixels to calc noise, so just calc min, max, ngood */
	if (nx < 5) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise) *noise = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	if (noise) {
	    differences = calloc(nx, sizeof(float));
	    if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	    }

	    diffs = calloc(ny, sizeof(double));
	    if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	    }
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
		
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) {
			  ii++;
		        }
			
		    if (ii == nx) break;  /* hit end of row */
		    v5 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		    }

		    /* construct array of 3rd order absolute differences */
		    if (noise) {
		        if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {

		            differences[nvals] = (float) fabs((2. * v3) - v1 - v5);
		            nvals++;  
		       } else {
		            /* ignore constant background regions */
			    ngoodpix++;
		       }
		    } else {
		       /* just increment the number of non-null pixels */
		       ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
	        }  /* end of loop over pixels in the row */

		/* compute the 3rd order diffs */
		/* Note that there are 4 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 4);

		if (noise) {
		    if (nvals == 0) {
		        continue;  /* cannot compute medians on this row */
		    } else if (nvals == 1) {
		        diffs[nrows] = differences[0];
		    } else {
                        /* quick_select returns the median MUCH faster than using qsort */
                        diffs[nrows] = quick_select_float(differences, nvals);
		    }
		}
		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (noise) {
	    if (nrows == 0) { 
	       xnoise = 0;
	    } else if (nrows == 1) {
	       xnoise = diffs[0];
	    } else {	    
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	    }
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise) {
		*noise  = 0.6052697 * xnoise;
		free(diffs);
		free(differences);
	}

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise3_double
       (double *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	double nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	long *ngood,        /* number of good, non-null pixels? */
	double *minval,    /* minimum non-null value */
	double *maxval,    /* maximum non-null value */
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */

/*
Estimate the median and background noise in the input image using 3rd order differences.

The noise in the background of the image is calculated using the 3rd order algorithm 
developed for deriving the signal to noise ratio in spectra
(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)

  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))

The returned estimates are the median of the values that are computed for each 
row of the image.
*/
{
	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
	double *differences, *rowpix, v1, v2, v3, v4, v5;
	double xminval = DBL_MAX, xmaxval = -DBL_MAX;
	int do_range = 0;
	double *diffs, xnoise = 0;
	
	if (nx < 5) {
		/* treat entire array as an image with a single row */
		nx = nx * ny;
		ny = 1;
	}

	/* rows must have at least 5 pixels */
	if (nx < 5) {

		for (ii = 0; ii < nx; ii++) {
		    if (nullcheck && array[ii] == nullvalue)
		        continue;
		    else {
			if (array[ii] < xminval) xminval = array[ii];
			if (array[ii] > xmaxval) xmaxval = array[ii];
			ngoodpix++;
		    }
		}
		if (minval) *minval = xminval;
		if (maxval) *maxval = xmaxval;
		if (ngood) *ngood = ngoodpix;
		if (noise) *noise = 0.;
		return(*status);
	}

	/* do we need to compute the min and max value? */
	if (minval || maxval) do_range = 1;
	
        /* allocate arrays used to compute the median and noise estimates */
	if (noise) {
	    differences = calloc(nx, sizeof(double));
	    if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	    }

	    diffs = calloc(ny, sizeof(double));
	    if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	    }
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v1 < xminval) xminval = v1;
			if (v1 > xmaxval) xmaxval = v1;
		}

		/***** find the 2nd valid pixel in row (which we will skip over) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v2 = rowpix[ii];  /* store the good pixel value */
		
		if (do_range) {
			if (v2 < xminval) xminval = v2;
			if (v2 > xmaxval) xmaxval = v2;
		}

		/***** find the 3rd valid pixel in row */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v3 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v3 < xminval) xminval = v3;
			if (v3 > xmaxval) xmaxval = v3;
		}
				
		/* find the 4nd valid pixel in row (to be skipped) */
		ii++;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v4 = rowpix[ii];  /* store the good pixel value */

		if (do_range) {
			if (v4 < xminval) xminval = v4;
			if (v4 > xmaxval) xmaxval = v4;
		}
		
		/* now populate the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		    v5 = rowpix[ii];  /* store the good pixel value */

		    if (do_range) {
			if (v5 < xminval) xminval = v5;
			if (v5 > xmaxval) xmaxval = v5;
		    }

		    /* construct array of 3rd order absolute differences */
		    if (noise) {
		        if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {

		            differences[nvals] = fabs((2. * v3) - v1 - v5);
		            nvals++;  
		        } else {
		            /* ignore constant background regions */
			    ngoodpix++;
		        }
		    } else {
		       /* just increment the number of non-null pixels */
		       ngoodpix++;
		    }

		    /* shift over 1 pixel */
		    v1 = v2;
		    v2 = v3;
		    v3 = v4;
		    v4 = v5;
	        }  /* end of loop over pixels in the row */

		/* compute the 3rd order diffs */
		/* Note that there are 4 more pixel values than there are diffs values. */
		ngoodpix += (nvals + 4);

		if (noise) {
		    if (nvals == 0) {
		        continue;  /* cannot compute medians on this row */
		    } else if (nvals == 1) {
		        diffs[nrows] = differences[0];
		    } else {
                        /* quick_select returns the median MUCH faster than using qsort */
                        diffs[nrows] = quick_select_double(differences, nvals);
		    }
		}
		nrows++;
	}  /* end of loop over rows */

	    /* compute median of the values for each row */
	if (noise) {
	    if (nrows == 0) { 
	       xnoise = 0;
	    } else if (nrows == 1) {
	       xnoise = diffs[0];
	    } else {	    
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	    }
	}

	if (ngood)  *ngood  = ngoodpix;
	if (minval) *minval = xminval;
	if (maxval) *maxval = xmaxval;
	if (noise) {
		*noise  = 0.6052697 * xnoise;
		free(diffs);
		free(differences);
	}

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise1_short
       (short *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	short nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */
/*
Estimate the background noise in the input image using sigma of 1st order differences.

  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])

The returned estimate is the median of the values that are computed for each 
row of the image.
*/
{
	int iter;
	long ii, jj, kk, nrows = 0, nvals;
	short *differences, *rowpix, v1;
	double  *diffs, xnoise, mean, stdev;

	/* rows must have at least 3 pixels to estimate noise */
	if (nx < 3) {
		*noise = 0;
		return(*status);
	}
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(short));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		/* now continue populating the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		
		    /* construct array of 1st order differences */
		    differences[nvals] = v1 - rowpix[ii];

		    nvals++;  
		    /* shift over 1 pixel */
		    v1 = rowpix[ii];
	        }  /* end of loop over pixels in the row */

		if (nvals < 2)
		   continue;
		else {

		    FnMeanSigma_short(differences, nvals, 0, 0, 0, &mean, &stdev, status);

		    if (stdev > 0.) {
		        for (iter = 0;  iter < NITER;  iter++) {
		            kk = 0;
		            for (ii = 0;  ii < nvals;  ii++) {
		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
			            if (kk < ii)
			                differences[kk] = differences[ii];
			            kk++;
		                }
		            }
		            if (kk == nvals) break;

		            nvals = kk;
		            FnMeanSigma_short(differences, nvals, 0, 0, 0, &mean, &stdev, status);
	              }
		   }

		   diffs[nrows] = stdev;
		   nrows++;
		}
	}  /* end of loop over rows */

	/* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	}

	*noise = .70710678 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise1_int
       (int *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	int nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */
/*
Estimate the background noise in the input image using sigma of 1st order differences.

  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])

The returned estimate is the median of the values that are computed for each 
row of the image.
*/
{
	int iter;
	long ii, jj, kk, nrows = 0, nvals;
	int *differences, *rowpix, v1;
	double  *diffs, xnoise, mean, stdev;

	/* rows must have at least 3 pixels to estimate noise */
	if (nx < 3) {
		*noise = 0;
		return(*status);
	}
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(int));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		/* now continue populating the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		
		    /* construct array of 1st order differences */
		    differences[nvals] = v1 - rowpix[ii];

		    nvals++;  
		    /* shift over 1 pixel */
		    v1 = rowpix[ii];
	        }  /* end of loop over pixels in the row */

		if (nvals < 2)
		   continue;
		else {

		    FnMeanSigma_int(differences, nvals, 0, 0, 0, &mean, &stdev, status);

		    if (stdev > 0.) {
		        for (iter = 0;  iter < NITER;  iter++) {
		            kk = 0;
		            for (ii = 0;  ii < nvals;  ii++) {
		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
			            if (kk < ii)
			                differences[kk] = differences[ii];
			            kk++;
		                }
		            }
		            if (kk == nvals) break;

		            nvals = kk;
		            FnMeanSigma_int(differences, nvals, 0, 0, 0, &mean, &stdev, status);
	              }
		   }

		   diffs[nrows] = stdev;
		   nrows++;
		}
	}  /* end of loop over rows */

	/* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	}

	*noise = .70710678 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise1_float
       (float *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	float nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */
/*
Estimate the background noise in the input image using sigma of 1st order differences.

  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])

The returned estimate is the median of the values that are computed for each 
row of the image.
*/
{
	int iter;
	long ii, jj, kk, nrows = 0, nvals;
	float *differences, *rowpix, v1;
	double  *diffs, xnoise, mean, stdev;

	/* rows must have at least 3 pixels to estimate noise */
	if (nx < 3) {
		*noise = 0;
		return(*status);
	}
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(float));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		/* now continue populating the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		
		    /* construct array of 1st order differences */
		    differences[nvals] = v1 - rowpix[ii];

		    nvals++;  
		    /* shift over 1 pixel */
		    v1 = rowpix[ii];
	        }  /* end of loop over pixels in the row */

		if (nvals < 2)
		   continue;
		else {

		    FnMeanSigma_float(differences, nvals, 0, 0, 0, &mean, &stdev, status);

		    if (stdev > 0.) {
		        for (iter = 0;  iter < NITER;  iter++) {
		            kk = 0;
		            for (ii = 0;  ii < nvals;  ii++) {
		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
			            if (kk < ii)
			                differences[kk] = differences[ii];
			            kk++;
		                }
		            }
		            if (kk == nvals) break;

		            nvals = kk;
		            FnMeanSigma_float(differences, nvals, 0, 0, 0, &mean, &stdev, status);
	              }
		   }

		   diffs[nrows] = stdev;
		   nrows++;
		}
	}  /* end of loop over rows */

	/* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	}

	*noise = .70710678 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnNoise1_double
       (double *array,       /*  2 dimensional array of image pixels */
        long nx,            /* number of pixels in each row of the image */
        long ny,            /* number of rows in the image */
	int nullcheck,      /* check for null values, if true */
	double nullvalue,    /* value of null pixels, if nullcheck is true */
   /* returned parameters */   
	double *noise,      /* returned R.M.S. value of all non-null pixels */
	int *status)        /* error status */
/*
Estimate the background noise in the input image using sigma of 1st order differences.

  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])

The returned estimate is the median of the values that are computed for each 
row of the image.
*/
{
	int iter;
	long ii, jj, kk, nrows = 0, nvals;
	double *differences, *rowpix, v1;
	double  *diffs, xnoise, mean, stdev;

	/* rows must have at least 3 pixels to estimate noise */
	if (nx < 3) {
		*noise = 0;
		return(*status);
	}
	
        /* allocate arrays used to compute the median and noise estimates */
	differences = calloc(nx, sizeof(double));
	if (!differences) {
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	diffs = calloc(ny, sizeof(double));
	if (!diffs) {
		free(differences);
        	*status = MEMORY_ALLOCATION;
		return(*status);
	}

	/* loop over each row of the image */
	for (jj=0; jj < ny; jj++) {

                rowpix = array + (jj * nx); /* point to first pixel in the row */

		/***** find the first valid pixel in row */
		ii = 0;
		if (nullcheck)
		    while (ii < nx && rowpix[ii] == nullvalue) ii++;

		if (ii == nx) continue;  /* hit end of row */
		v1 = rowpix[ii];  /* store the good pixel value */

		/* now continue populating the differences arrays */
		/* for the remaining pixels in the row */
		nvals = 0;
		for (ii++; ii < nx; ii++) {

		    /* find the next valid pixel in row */
                    if (nullcheck)
		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
		     
		    if (ii == nx) break;  /* hit end of row */
		
		    /* construct array of 1st order differences */
		    differences[nvals] = v1 - rowpix[ii];

		    nvals++;  
		    /* shift over 1 pixel */
		    v1 = rowpix[ii];
	        }  /* end of loop over pixels in the row */

		if (nvals < 2)
		   continue;
		else {

		    FnMeanSigma_double(differences, nvals, 0, 0, 0, &mean, &stdev, status);

		    if (stdev > 0.) {
		        for (iter = 0;  iter < NITER;  iter++) {
		            kk = 0;
		            for (ii = 0;  ii < nvals;  ii++) {
		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
			            if (kk < ii)
			                differences[kk] = differences[ii];
			            kk++;
		                }
		            }
		            if (kk == nvals) break;

		            nvals = kk;
		            FnMeanSigma_double(differences, nvals, 0, 0, 0, &mean, &stdev, status);
	              }
		   }

		   diffs[nrows] = stdev;
		   nrows++;
		}
	}  /* end of loop over rows */

	/* compute median of the values for each row */
	if (nrows == 0) { 
	       xnoise = 0;
	} else if (nrows == 1) {
	       xnoise = diffs[0];
	} else {
	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
	}

	*noise = .70710678 * xnoise;

	free(diffs);
	free(differences);

	return(*status);
}
/*--------------------------------------------------------------------------*/
static int FnCompare_short(const void *v1, const void *v2)
{
   const short *i1 = v1;
   const short *i2 = v2;
   
   if (*i1 < *i2)
     return(-1);
   else if (*i1 > *i2)
     return(1);
   else
     return(0);
}
/*--------------------------------------------------------------------------*/
static int FnCompare_int(const void *v1, const void *v2)
{
   const int *i1 = v1;
   const int *i2 = v2;
   
   if (*i1 < *i2)
     return(-1);
   else if (*i1 > *i2)
     return(1);
   else
     return(0);
}
/*--------------------------------------------------------------------------*/
static int FnCompare_float(const void *v1, const void *v2)
{
   const float *i1 = v1;
   const float *i2 = v2;
   
   if (*i1 < *i2)
     return(-1);
   else if (*i1 > *i2)
     return(1);
   else
     return(0);
}
/*--------------------------------------------------------------------------*/
static int FnCompare_double(const void *v1, const void *v2)
{
   const double *i1 = v1;
   const double *i2 = v2;
   
   if (*i1 < *i2)
     return(-1);
   else if (*i1 > *i2)
     return(1);
   else
     return(0);
}
/*--------------------------------------------------------------------------*/

/*
 *  These Quickselect routines are based on the algorithm described in
 *  "Numerical recipes in C", Second Edition,
 *  Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5
 *  This code by Nicolas Devillard - 1998. Public domain.
 */

/*--------------------------------------------------------------------------*/

#define ELEM_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; }

static float quick_select_float(float arr[], int n) 
{
    int low, high ;
    int median;
    int middle, ll, hh;

    low = 0 ; high = n-1 ; median = (low + high) / 2;
    for (;;) {
        if (high <= low) /* One element only */
            return arr[median] ;

        if (high == low + 1) {  /* Two elements only */
            if (arr[low] > arr[high])
                ELEM_SWAP(arr[low], arr[high]) ;
            return arr[median] ;
        }

    /* Find median of low, middle and high items; swap into position low */
    middle = (low + high) / 2;
    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;

    /* Swap low item (now in position middle) into position (low+1) */
    ELEM_SWAP(arr[middle], arr[low+1]) ;

    /* Nibble from each end towards middle, swapping items when stuck */
    ll = low + 1;
    hh = high;
    for (;;) {
        do ll++; while (arr[low] > arr[ll]) ;
        do hh--; while (arr[hh]  > arr[low]) ;

        if (hh < ll)
        break;

        ELEM_SWAP(arr[ll], arr[hh]) ;
    }

    /* Swap middle item (in position low) back into correct position */
    ELEM_SWAP(arr[low], arr[hh]) ;

    /* Re-set active partition */
    if (hh <= median)
        low = ll;
        if (hh >= median)
        high = hh - 1;
    }
}

#undef ELEM_SWAP

/*--------------------------------------------------------------------------*/

#define ELEM_SWAP(a,b) { register short t=(a);(a)=(b);(b)=t; }

static short quick_select_short(short arr[], int n) 
{
    int low, high ;
    int median;
    int middle, ll, hh;

    low = 0 ; high = n-1 ; median = (low + high) / 2;
    for (;;) {
        if (high <= low) /* One element only */
            return arr[median] ;

        if (high == low + 1) {  /* Two elements only */
            if (arr[low] > arr[high])
                ELEM_SWAP(arr[low], arr[high]) ;
            return arr[median] ;
        }

    /* Find median of low, middle and high items; swap into position low */
    middle = (low + high) / 2;
    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;

    /* Swap low item (now in position middle) into position (low+1) */
    ELEM_SWAP(arr[middle], arr[low+1]) ;

    /* Nibble from each end towards middle, swapping items when stuck */
    ll = low + 1;
    hh = high;
    for (;;) {
        do ll++; while (arr[low] > arr[ll]) ;
        do hh--; while (arr[hh]  > arr[low]) ;

        if (hh < ll)
        break;

        ELEM_SWAP(arr[ll], arr[hh]) ;
    }

    /* Swap middle item (in position low) back into correct position */
    ELEM_SWAP(arr[low], arr[hh]) ;

    /* Re-set active partition */
    if (hh <= median)
        low = ll;
        if (hh >= median)
        high = hh - 1;
    }
}

#undef ELEM_SWAP

/*--------------------------------------------------------------------------*/

#define ELEM_SWAP(a,b) { register int t=(a);(a)=(b);(b)=t; }

static int quick_select_int(int arr[], int n) 
{
    int low, high ;
    int median;
    int middle, ll, hh;

    low = 0 ; high = n-1 ; median = (low + high) / 2;
    for (;;) {
        if (high <= low) /* One element only */
            return arr[median] ;

        if (high == low + 1) {  /* Two elements only */
            if (arr[low] > arr[high])
                ELEM_SWAP(arr[low], arr[high]) ;
            return arr[median] ;
        }

    /* Find median of low, middle and high items; swap into position low */
    middle = (low + high) / 2;
    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;

    /* Swap low item (now in position middle) into position (low+1) */
    ELEM_SWAP(arr[middle], arr[low+1]) ;

    /* Nibble from each end towards middle, swapping items when stuck */
    ll = low + 1;
    hh = high;
    for (;;) {
        do ll++; while (arr[low] > arr[ll]) ;
        do hh--; while (arr[hh]  > arr[low]) ;

        if (hh < ll)
        break;

        ELEM_SWAP(arr[ll], arr[hh]) ;
    }

    /* Swap middle item (in position low) back into correct position */
    ELEM_SWAP(arr[low], arr[hh]) ;

    /* Re-set active partition */
    if (hh <= median)
        low = ll;
        if (hh >= median)
        high = hh - 1;
    }
}

#undef ELEM_SWAP

/*--------------------------------------------------------------------------*/

#define ELEM_SWAP(a,b) { register LONGLONG  t=(a);(a)=(b);(b)=t; }

static LONGLONG quick_select_longlong(LONGLONG arr[], int n) 
{
    int low, high ;
    int median;
    int middle, ll, hh;

    low = 0 ; high = n-1 ; median = (low + high) / 2;
    for (;;) {
        if (high <= low) /* One element only */
            return arr[median] ;

        if (high == low + 1) {  /* Two elements only */
            if (arr[low] > arr[high])
                ELEM_SWAP(arr[low], arr[high]) ;
            return arr[median] ;
        }

    /* Find median of low, middle and high items; swap into position low */
    middle = (low + high) / 2;
    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;

    /* Swap low item (now in position middle) into position (low+1) */
    ELEM_SWAP(arr[middle], arr[low+1]) ;

    /* Nibble from each end towards middle, swapping items when stuck */
    ll = low + 1;
    hh = high;
    for (;;) {
        do ll++; while (arr[low] > arr[ll]) ;
        do hh--; while (arr[hh]  > arr[low]) ;

        if (hh < ll)
        break;

        ELEM_SWAP(arr[ll], arr[hh]) ;
    }

    /* Swap middle item (in position low) back into correct position */
    ELEM_SWAP(arr[low], arr[hh]) ;

    /* Re-set active partition */
    if (hh <= median)
        low = ll;
        if (hh >= median)
        high = hh - 1;
    }
}

#undef ELEM_SWAP

/*--------------------------------------------------------------------------*/

#define ELEM_SWAP(a,b) { register double t=(a);(a)=(b);(b)=t; }

static double quick_select_double(double arr[], int n) 
{
    int low, high ;
    int median;
    int middle, ll, hh;

    low = 0 ; high = n-1 ; median = (low + high) / 2;
    for (;;) {
        if (high <= low) /* One element only */
            return arr[median] ;

        if (high == low + 1) {  /* Two elements only */
            if (arr[low] > arr[high])
                ELEM_SWAP(arr[low], arr[high]) ;
            return arr[median] ;
        }

    /* Find median of low, middle and high items; swap into position low */
    middle = (low + high) / 2;
    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;

    /* Swap low item (now in position middle) into position (low+1) */
    ELEM_SWAP(arr[middle], arr[low+1]) ;

    /* Nibble from each end towards middle, swapping items when stuck */
    ll = low + 1;
    hh = high;
    for (;;) {
        do ll++; while (arr[low] > arr[ll]) ;
        do hh--; while (arr[hh]  > arr[low]) ;

        if (hh < ll)
        break;

        ELEM_SWAP(arr[ll], arr[hh]) ;
    }

    /* Swap middle item (in position low) back into correct position */
    ELEM_SWAP(arr[low], arr[hh]) ;

    /* Re-set active partition */
    if (hh <= median)
        low = ll;
        if (hh >= median)
        high = hh - 1;
    }
}

#undef ELEM_SWAP


nom-tam-fits-nom-tam-fits-1.15.2/src/main/fpack/ricecomp.c000066400000000000000000001046221310063650500231740ustar00rootroot00000000000000/*
  The following code was written by Richard White at STScI and made
  available for use in CFITSIO in July 1999.  These routines were
  originally contained in 2 source files: rcomp.c and rdecomp.c,
  and the 'include' file now called ricecomp.h was originally called buffer.h.
*/

/*----------------------------------------------------------*/
/*                                                          */
/*    START OF SOURCE FILE ORIGINALLY CALLED rcomp.c        */
/*                                                          */
/*----------------------------------------------------------*/
/* @(#) rcomp.c 1.5 99/03/01 12:40:27 */
/* rcomp.c	Compress image line using
 *		(1) Difference of adjacent pixels
 *		(2) Rice algorithm coding
 *
 * Returns number of bytes written to code buffer or
 * -1 on failure
 */

#include 
#include 
#include 

/*
 * nonzero_count is lookup table giving number of bits in 8-bit values not including
 * leading zeros used in fits_rdecomp, fits_rdecomp_short and fits_rdecomp_byte
 */
static const int nonzero_count[256] = {
0, 
1, 
2, 2, 
3, 3, 3, 3, 
4, 4, 4, 4, 4, 4, 4, 4, 
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8};

typedef unsigned char Buffer_t;

typedef struct {
	int bitbuffer;		/* bit buffer			*/
	int bits_to_go;		/* bits to go in buffer		*/
	Buffer_t *start;	/* start of buffer		*/
	Buffer_t *current;	/* current position in buffer	*/
	Buffer_t *end;		/* end of buffer		*/
} Buffer;

#define putcbuf(c,mf) 	((*(mf->current)++ = c), 0)

#include "fitsio2.h"

static void start_outputing_bits(Buffer *buffer);
static int done_outputing_bits(Buffer *buffer);
static int output_nbits(Buffer *buffer, int bits, int n);

/*  only used for diagnoistics
static int case1, case2, case3;
int fits_get_case(int *c1, int*c2, int*c3) {

  *c1 = case1;
  *c2 = case2;
  *c3 = case3;
  return(0);
}
*/

/* this routine used to be called 'rcomp'  (WDP)  */
/*---------------------------------------------------------------------------*/

int fits_rcomp(int a[],		/* input array			*/
	  int nx,		/* number of input pixels	*/
	  unsigned char *c,	/* output buffer		*/
	  int clen,		/* max length of output		*/
	  int nblock)		/* coding block size		*/
{
Buffer bufmem, *buffer = &bufmem;
/* int bsize;  */
int i, j, thisblock;
int lastpix, nextpix, pdiff;
int v, fs, fsmask, top, fsmax, fsbits, bbits;
int lbitbuffer, lbits_to_go;
unsigned int psum;
double pixelsum, dpsum;
unsigned int *diff;

    /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */
/*    bsize = 4;   */

/*    nblock = 32; now an input parameter*/
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return(-1);
    }
*/

    /* move out of switch block, to tweak performance */
    fsbits = 5;
    fsmax = 25;

    bbits = 1<start = c;
    buffer->current = c;
    buffer->end = c+clen;
    buffer->bits_to_go = 8;
    /*
     * array for differences mapped to non-negative values
     */
    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
    if (diff == (unsigned int *) NULL) {
        ffpmsg("fits_rcomp: insufficient memory");
	return(-1);
    }
    /*
     * Code in blocks of nblock pixels
     */
    start_outputing_bits(buffer);

    /* write out first int value to the first 4 bytes of the buffer */
    if (output_nbits(buffer, a[0], 32) == EOF) {
        ffpmsg("rice_encode: end of buffer");
        free(diff);
        return(-1);
    }

    lastpix = a[0];  /* the first difference will always be zero */

    thisblock = nblock;
    for (i=0; i> 1;
	for (fs = 0; psum>0; fs++) psum >>= 1;

	/*
	 * write the codes
	 * fsbits ID bits used to indicate split level
	 */
	if (fs >= fsmax) {
	    /* Special high entropy case when FS >= fsmax
	     * Just write pixel difference values directly, no Rice coding at all.
	     */
	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
                ffpmsg("rice_encode: end of buffer");
                free(diff);
		return(-1);
	    }
	    for (j=0; jbitbuffer;
	    lbits_to_go = buffer->bits_to_go;
	    for (j=0; j> fs;
		/*
		 * top is coded by top zeros + 1
		 */
		if (lbits_to_go >= top+1) {
		    lbitbuffer <<= top+1;
		    lbitbuffer |= 1;
		    lbits_to_go -= top+1;
		} else {
		    lbitbuffer <<= lbits_to_go;
		    putcbuf(lbitbuffer & 0xff,buffer);

		    for (top -= lbits_to_go; top>=8; top -= 8) {
			putcbuf(0, buffer);
		    }
		    lbitbuffer = 1;
		    lbits_to_go = 7-top;
		}
		/*
		 * bottom FS bits are written without coding
		 * code is output_nbits, moved into this routine to reduce overheads
		 * This code potentially breaks if FS>24, so I am limiting
		 * FS to 24 by choice of FSMAX above.
		 */
		if (fs > 0) {
		    lbitbuffer <<= fs;
		    lbitbuffer |= v & fsmask;
		    lbits_to_go -= fs;
		    while (lbits_to_go <= 0) {
			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
			lbits_to_go += 8;
		    }
		}
	    }

	    /* check if overflowed output buffer */
	    if (buffer->current > buffer->end) {
                 ffpmsg("rice_encode: end of buffer");
                 free(diff);
		 return(-1);
	    }
	    buffer->bitbuffer = lbitbuffer;
	    buffer->bits_to_go = lbits_to_go;
	}
    }
    done_outputing_bits(buffer);
    free(diff);
    /*
     * return number of bytes used
     */
    return(buffer->current - buffer->start);
}
/*---------------------------------------------------------------------------*/

int fits_rcomp_short(
	  short a[],		/* input array			*/
	  int nx,		/* number of input pixels	*/
	  unsigned char *c,	/* output buffer		*/
	  int clen,		/* max length of output		*/
	  int nblock)		/* coding block size		*/
{
Buffer bufmem, *buffer = &bufmem;
/* int bsize;  */
int i, j, thisblock;

/* 
NOTE: in principle, the following 2 variable could be declared as 'short'
but in fact the code runs faster (on 32-bit Linux at least) as 'int'
*/
int lastpix, nextpix;
/* int pdiff; */
short pdiff; 
int v, fs, fsmask, top, fsmax, fsbits, bbits;
int lbitbuffer, lbits_to_go;
/* unsigned int psum; */
unsigned short psum;
double pixelsum, dpsum;
unsigned int *diff;

    /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */
/*    bsize = 2; */

/*    nblock = 32; now an input parameter */
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return(-1);
    }
*/

    /* move these out of switch block to further tweak performance */
    fsbits = 4;
    fsmax = 14;
    
    bbits = 1<start = c;
    buffer->current = c;
    buffer->end = c+clen;
    buffer->bits_to_go = 8;
    /*
     * array for differences mapped to non-negative values
     */
    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
    if (diff == (unsigned int *) NULL) {
        ffpmsg("fits_rcomp: insufficient memory");
	return(-1);
    }
    /*
     * Code in blocks of nblock pixels
     */
    start_outputing_bits(buffer);

    /* write out first short value to the first 2 bytes of the buffer */
    if (output_nbits(buffer, a[0], 16) == EOF) {
        ffpmsg("rice_encode: end of buffer");
        free(diff);
        return(-1);
    }

    lastpix = a[0];  /* the first difference will always be zero */

    thisblock = nblock;
    for (i=0; i> 1; */
	psum = ((unsigned short) dpsum ) >> 1;
	for (fs = 0; psum>0; fs++) psum >>= 1;

	/*
	 * write the codes
	 * fsbits ID bits used to indicate split level
	 */
	if (fs >= fsmax) {
/* case3++; */
	    /* Special high entropy case when FS >= fsmax
	     * Just write pixel difference values directly, no Rice coding at all.
	     */
	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
                ffpmsg("rice_encode: end of buffer");
                free(diff);
		return(-1);
	    }
	    for (j=0; jbitbuffer;
	    lbits_to_go = buffer->bits_to_go;
	    for (j=0; j> fs;
		/*
		 * top is coded by top zeros + 1
		 */
		if (lbits_to_go >= top+1) {
		    lbitbuffer <<= top+1;
		    lbitbuffer |= 1;
		    lbits_to_go -= top+1;
		} else {
		    lbitbuffer <<= lbits_to_go;
		    putcbuf(lbitbuffer & 0xff,buffer);
		    for (top -= lbits_to_go; top>=8; top -= 8) {
			putcbuf(0, buffer);
		    }
		    lbitbuffer = 1;
		    lbits_to_go = 7-top;
		}
		/*
		 * bottom FS bits are written without coding
		 * code is output_nbits, moved into this routine to reduce overheads
		 * This code potentially breaks if FS>24, so I am limiting
		 * FS to 24 by choice of FSMAX above.
		 */
		if (fs > 0) {
		    lbitbuffer <<= fs;
		    lbitbuffer |= v & fsmask;
		    lbits_to_go -= fs;
		    while (lbits_to_go <= 0) {
			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
			lbits_to_go += 8;
		    }
		}
	    }
	    /* check if overflowed output buffer */
	    if (buffer->current > buffer->end) {
                 ffpmsg("rice_encode: end of buffer");
                 free(diff);
		 return(-1);
	    }
	    buffer->bitbuffer = lbitbuffer;
	    buffer->bits_to_go = lbits_to_go;
	}
    }
    done_outputing_bits(buffer);
    free(diff);
    /*
     * return number of bytes used
     */
    return(buffer->current - buffer->start);
}
/*---------------------------------------------------------------------------*/

int fits_rcomp_byte(
	  signed char a[],		/* input array			*/
	  int nx,		/* number of input pixels	*/
	  unsigned char *c,	/* output buffer		*/
	  int clen,		/* max length of output		*/
	  int nblock)		/* coding block size		*/
{
Buffer bufmem, *buffer = &bufmem;
/* int bsize; */
int i, j, thisblock;

/* 
NOTE: in principle, the following 2 variable could be declared as 'short'
but in fact the code runs faster (on 32-bit Linux at least) as 'int'
*/
int lastpix, nextpix;
/* int pdiff; */
signed char pdiff; 
int v, fs, fsmask, top, fsmax, fsbits, bbits;
int lbitbuffer, lbits_to_go;
/* unsigned int psum; */
unsigned char psum;
double pixelsum, dpsum;
unsigned int *diff;

    /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */
/*    bsize = 1;  */

/*    nblock = 32; now an input parameter */
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return(-1);
    }
*/

    /* move these out of switch block to further tweak performance */
    fsbits = 3;
    fsmax = 6;
    bbits = 1<start = c;
    buffer->current = c;
    buffer->end = c+clen;
    buffer->bits_to_go = 8;
    /*
     * array for differences mapped to non-negative values
     */
    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
    if (diff == (unsigned int *) NULL) {
        ffpmsg("fits_rcomp: insufficient memory");
	return(-1);
    }
    /*
     * Code in blocks of nblock pixels
     */
    start_outputing_bits(buffer);

    /* write out first byte value to the first  byte of the buffer */
    if (output_nbits(buffer, a[0], 8) == EOF) {
        ffpmsg("rice_encode: end of buffer");
        free(diff);
        return(-1);
    }

    lastpix = a[0];  /* the first difference will always be zero */

    thisblock = nblock;
    for (i=0; i> 1; */
	psum = ((unsigned char) dpsum ) >> 1;
	for (fs = 0; psum>0; fs++) psum >>= 1;

	/*
	 * write the codes
	 * fsbits ID bits used to indicate split level
	 */
	if (fs >= fsmax) {
	    /* Special high entropy case when FS >= fsmax
	     * Just write pixel difference values directly, no Rice coding at all.
	     */
	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
                ffpmsg("rice_encode: end of buffer");
                free(diff);
		return(-1);
	    }
	    for (j=0; jbitbuffer;
	    lbits_to_go = buffer->bits_to_go;
	    for (j=0; j> fs;
		/*
		 * top is coded by top zeros + 1
		 */
		if (lbits_to_go >= top+1) {
		    lbitbuffer <<= top+1;
		    lbitbuffer |= 1;
		    lbits_to_go -= top+1;
		} else {
		    lbitbuffer <<= lbits_to_go;
		    putcbuf(lbitbuffer & 0xff,buffer);
		    for (top -= lbits_to_go; top>=8; top -= 8) {
			putcbuf(0, buffer);
		    }
		    lbitbuffer = 1;
		    lbits_to_go = 7-top;
		}
		/*
		 * bottom FS bits are written without coding
		 * code is output_nbits, moved into this routine to reduce overheads
		 * This code potentially breaks if FS>24, so I am limiting
		 * FS to 24 by choice of FSMAX above.
		 */
		if (fs > 0) {
		    lbitbuffer <<= fs;
		    lbitbuffer |= v & fsmask;
		    lbits_to_go -= fs;
		    while (lbits_to_go <= 0) {
			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
			lbits_to_go += 8;
		    }
		}
	    }
	    /* check if overflowed output buffer */
	    if (buffer->current > buffer->end) {
                 ffpmsg("rice_encode: end of buffer");
                 free(diff);
		 return(-1);
	    }
	    buffer->bitbuffer = lbitbuffer;
	    buffer->bits_to_go = lbits_to_go;
	}
    }
    done_outputing_bits(buffer);
    free(diff);
    /*
     * return number of bytes used
     */
    return(buffer->current - buffer->start);
}
/*---------------------------------------------------------------------------*/
/* bit_output.c
 *
 * Bit output routines
 * Procedures return zero on success, EOF on end-of-buffer
 *
 * Programmer: R. White     Date: 20 July 1998
 */

/* Initialize for bit output */

static void start_outputing_bits(Buffer *buffer)
{
    /*
     * Buffer is empty to start with
     */
    buffer->bitbuffer = 0;
    buffer->bits_to_go = 8;
}

/*---------------------------------------------------------------------------*/
/* Output N bits (N must be <= 32) */

static int output_nbits(Buffer *buffer, int bits, int n)
{
/* local copies */
int lbitbuffer;
int lbits_to_go;
    /* AND mask for the right-most n bits */
    static unsigned int mask[33] = 
         {0,
	  0x1,       0x3,       0x7,       0xf,       0x1f,       0x3f,       0x7f,       0xff,
	  0x1ff,     0x3ff,     0x7ff,     0xfff,     0x1fff,     0x3fff,     0x7fff,     0xffff,
	  0x1ffff,   0x3ffff,   0x7ffff,   0xfffff,   0x1fffff,   0x3fffff,   0x7fffff,   0xffffff,
	  0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};

    /*
     * insert bits at end of bitbuffer
     */
    lbitbuffer = buffer->bitbuffer;
    lbits_to_go = buffer->bits_to_go;
    if (lbits_to_go+n > 32) {
	/*
	 * special case for large n: put out the top lbits_to_go bits first
	 * note that 0 < lbits_to_go <= 8
	 */
	lbitbuffer <<= lbits_to_go;
/*	lbitbuffer |= (bits>>(n-lbits_to_go)) & ((1<>(n-lbits_to_go)) & *(mask+lbits_to_go);
	putcbuf(lbitbuffer & 0xff,buffer);
	n -= lbits_to_go;
	lbits_to_go = 8;
    }
    lbitbuffer <<= n;
/*    lbitbuffer |= ( bits & ((1<>(-lbits_to_go)) & 0xff,buffer);
	lbits_to_go += 8;
    }
    buffer->bitbuffer = lbitbuffer;
    buffer->bits_to_go = lbits_to_go;
    return(0);
}
/*---------------------------------------------------------------------------*/
/* Flush out the last bits */

static int done_outputing_bits(Buffer *buffer)
{
    if(buffer->bits_to_go < 8) {
	putcbuf(buffer->bitbuffer<bits_to_go,buffer);
	
/*	if (putcbuf(buffer->bitbuffer<bits_to_go,buffer) == EOF)
	    return(EOF);
*/
    }
    return(0);
}
/*---------------------------------------------------------------------------*/
/*----------------------------------------------------------*/
/*                                                          */
/*    START OF SOURCE FILE ORIGINALLY CALLED rdecomp.c      */
/*                                                          */
/*----------------------------------------------------------*/

/* @(#) rdecomp.c 1.4 99/03/01 12:38:41 */
/* rdecomp.c	Decompress image line using
 *		(1) Difference of adjacent pixels
 *		(2) Rice algorithm coding
 *
 * Returns 0 on success or 1 on failure
 */

/*    moved these 'includes' to the beginning of the file (WDP)
#include 
#include 
*/

/*---------------------------------------------------------------------------*/
/* this routine used to be called 'rdecomp'  (WDP)  */

int fits_rdecomp (unsigned char *c,		/* input buffer			*/
	     int clen,			/* length of input		*/
	     unsigned int array[],	/* output array			*/
	     int nx,			/* number of output pixels	*/
	     int nblock)		/* coding block size		*/
{
/* int bsize;  */
int i, k, imax;
int nbits, nzero, fs;
unsigned char *cend, bytevalue;
unsigned int b, diff, lastpix;
int fsmax, fsbits, bbits;
extern const int nonzero_count[];

   /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */
/*    bsize = 4; */

/*    nblock = 32; now an input parameter */
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return 1;
    }
*/

    /* move out of switch block, to tweak performance */
    fsbits = 5;
    fsmax = 25;

    bbits = 1<> nbits) - 1;

	b &= (1< nx) imax = nx;
	if (fs<0) {
	    /* low-entropy case, all zero differences */
	    for ( ; i= 0; k -= 8) {
		    b = *c++;
		    diff |= b<0) {
		    b = *c++;
		    diff |= b>>(-k);
		    b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	} else {
	    /* normal case, Rice coding */
	    for ( ; i>nbits);
		b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	}
	if (c > cend) {
            ffpmsg("decompression error: hit end of compressed byte stream");
	    return 1;
	}
    }
    if (c < cend) {
        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
    }
    return 0;
}
/*---------------------------------------------------------------------------*/
/* this routine used to be called 'rdecomp'  (WDP)  */

int fits_rdecomp_short (unsigned char *c,		/* input buffer			*/
	     int clen,			/* length of input		*/
	     unsigned short array[],  	/* output array			*/
	     int nx,			/* number of output pixels	*/
	     int nblock)		/* coding block size		*/
{
int i, imax;
/* int bsize; */
int k;
int nbits, nzero, fs;
unsigned char *cend, bytevalue;
unsigned int b, diff, lastpix;
int fsmax, fsbits, bbits;
extern const int nonzero_count[];

   /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */

/*    bsize = 2; */
    
/*    nblock = 32; now an input parameter */
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return 1;
    }
*/

    /* move out of switch block, to tweak performance */
    fsbits = 4;
    fsmax = 14;

    bbits = 1<> nbits) - 1;

	b &= (1< nx) imax = nx;
	if (fs<0) {
	    /* low-entropy case, all zero differences */
	    for ( ; i= 0; k -= 8) {
		    b = *c++;
		    diff |= b<0) {
		    b = *c++;
		    diff |= b>>(-k);
		    b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	} else {
	    /* normal case, Rice coding */
	    for ( ; i>nbits);
		b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	}
	if (c > cend) {
            ffpmsg("decompression error: hit end of compressed byte stream");
	    return 1;
	}
    }
    if (c < cend) {
        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
    }
    return 0;
}
/*---------------------------------------------------------------------------*/
/* this routine used to be called 'rdecomp'  (WDP)  */

int fits_rdecomp_byte (unsigned char *c,		/* input buffer			*/
	     int clen,			/* length of input		*/
	     unsigned char array[],  	/* output array			*/
	     int nx,			/* number of output pixels	*/
	     int nblock)		/* coding block size		*/
{
int i, imax;
/* int bsize; */
int k;
int nbits, nzero, fs;
unsigned char *cend;
unsigned int b, diff, lastpix;
int fsmax, fsbits, bbits;
extern const int nonzero_count[];

   /*
     * Original size of each pixel (bsize, bytes) and coding block
     * size (nblock, pixels)
     * Could make bsize a parameter to allow more efficient
     * compression of short & byte images.
     */

/*    bsize = 1; */
    
/*    nblock = 32; now an input parameter */
    /*
     * From bsize derive:
     * FSBITS = # bits required to store FS
     * FSMAX = maximum value for FS
     * BBITS = bits/pixel for direct coding
     */

/*
    switch (bsize) {
    case 1:
	fsbits = 3;
	fsmax = 6;
	break;
    case 2:
	fsbits = 4;
	fsmax = 14;
	break;
    case 4:
	fsbits = 5;
	fsmax = 25;
	break;
    default:
        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
	return 1;
    }
*/

    /* move out of switch block, to tweak performance */
    fsbits = 3;
    fsmax = 6;

    bbits = 1<> nbits) - 1;

	b &= (1< nx) imax = nx;
	if (fs<0) {
	    /* low-entropy case, all zero differences */
	    for ( ; i= 0; k -= 8) {
		    b = *c++;
		    diff |= b<0) {
		    b = *c++;
		    diff |= b>>(-k);
		    b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	} else {
	    /* normal case, Rice coding */
	    for ( ; i>nbits);
		b &= (1<>1;
		} else {
		    diff = ~(diff>>1);
		}
		array[i] = diff+lastpix;
		lastpix = array[i];
	    }
	}
	if (c > cend) {
            ffpmsg("decompression error: hit end of compressed byte stream");
	    return 1;
	}
    }
    if (c < cend) {
        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
    }
    return 0;
}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/000077500000000000000000000000001310063650500210575ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/000077500000000000000000000000001310063650500216505ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/000077500000000000000000000000001310063650500224315ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/000077500000000000000000000000001310063650500233765ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/AbstractTableData.java000066400000000000000000000030061310063650500275450ustar00rootroot00000000000000package nom.tam.fits;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 1996 - 2015 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 * 
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 * 
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

/**
 * Just an abstract class to reuse the myData in all subclasses of the HDU's.
 * 
 * @author Richard van Nieuwenhoven
 */
public abstract class AbstractTableData extends Data implements TableData {

}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/AsciiTable.java000066400000000000000000001053431310063650500262470ustar00rootroot00000000000000package nom.tam.fits;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2015 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 * 
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 * 
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

import static nom.tam.fits.header.Standard.GCOUNT;
import static nom.tam.fits.header.Standard.NAXIS1;
import static nom.tam.fits.header.Standard.NAXIS2;
import static nom.tam.fits.header.Standard.PCOUNT;
import static nom.tam.fits.header.Standard.TBCOLn;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.TFORMn;
import static nom.tam.fits.header.Standard.TNULLn;

import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.logging.Level;
import java.util.logging.Logger;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import nom.tam.fits.header.IFitsHeader;
import nom.tam.fits.header.Standard;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.ArrayFuncs;
import nom.tam.util.ByteFormatter;
import nom.tam.util.ByteParser;
import nom.tam.util.Cursor;
import nom.tam.util.FormatException;
import nom.tam.util.RandomAccess;

/**
 * This class represents the data in an ASCII table
 */
public class AsciiTable extends AbstractTableData {

    private static final int MAX_INTEGER_LENGTH = 10;

    private static final int FLOAT_MAX_LENGTH = 16;

    private static final int LONG_MAX_LENGTH = 20;

    private static final int INT_MAX_LENGTH = 10;

    private static final int DOUBLE_MAX_LENGTH = 24;

    private static final Logger LOG = Logger.getLogger(AsciiTable.class.getName());

    /** The number of rows in the table */
    private int nRows;

    /** The number of fields in the table */
    private int nFields;

    /** The number of bytes in a row */
    private int rowLen;

    /** The null string for the field */
    private String[] nulls;

    /** The type of data in the field */
    private Class[] types;

    /** The offset from the beginning of the row at which the field starts */
    private int[] offsets;

    /** The number of bytes in the field */
    private int[] lengths;

    /** The byte buffer used to read/write the ASCII table */
    private byte[] buffer;

    /** Markers indicating fields that are null */
    private boolean[] isNull;

    /**
     * An array of arrays giving the data in the table in binary numbers
     */
    private Object[] data;

    /**
     * The parser used to convert from buffer to data.
     */
    private ByteParser bp;

    /** The actual stream used to input data */
    private ArrayDataInput currInput;

    /** Create an empty ASCII table */
    public AsciiTable() {

        this.data = new Object[0];
        this.buffer = null;
        this.nFields = 0;
        this.nRows = 0;
        this.rowLen = 0;
        this.types = new Class[0];
        this.lengths = new int[0];
        this.offsets = new int[0];
        this.nulls = new String[0];
    }

    /**
     * Create an ASCII table given a header
     *
     * @param hdr
     *            The header describing the table
     * @throws FitsException
     *             if the operation failed
     */
    public AsciiTable(Header hdr) throws FitsException {

        this.nRows = hdr.getIntValue(NAXIS2);
        this.nFields = hdr.getIntValue(TFIELDS);
        this.rowLen = hdr.getIntValue(NAXIS1);

        this.types = new Class[this.nFields];
        this.offsets = new int[this.nFields];
        this.lengths = new int[this.nFields];
        this.nulls = new String[this.nFields];

        for (int i = 0; i < this.nFields; i += 1) {
            this.offsets[i] = hdr.getIntValue(TBCOLn.n(i + 1)) - 1;
            String s = hdr.getStringValue(TFORMn.n(i + 1));
            if (this.offsets[i] < 0 || s == null) {
                throw new FitsException("Invalid Specification for column:" + (i + 1));
            }
            s = s.trim();
            char c = s.charAt(0);
            s = s.substring(1);
            if (s.indexOf('.') > 0) {
                s = s.substring(0, s.indexOf('.'));
            }
            this.lengths[i] = Integer.parseInt(s);

            switch (c) {
                case 'A':
                    this.types[i] = String.class;
                    break;
                case 'I':
                    if (this.lengths[i] > MAX_INTEGER_LENGTH) {
                        this.types[i] = long.class;
                    } else {
                        this.types[i] = int.class;
                    }
                    break;
                case 'F':
                case 'E':
                    this.types[i] = float.class;
                    break;
                case 'D':
                    this.types[i] = double.class;
                    break;
                default:
                    throw new FitsException("could not parse column type of ascii table");
            }

            this.nulls[i] = hdr.getStringValue(TNULLn.n(i + 1));
            if (this.nulls[i] != null) {
                this.nulls[i] = this.nulls[i].trim();
            }
        }
    }

    int addColInfo(int col, Cursor iter) throws HeaderCardException {

        String tform = null;
        if (this.types[col] == String.class) {
            tform = "A" + this.lengths[col];
        } else if (this.types[col] == int.class || this.types[col] == long.class) {
            tform = "I" + this.lengths[col];
        } else if (this.types[col] == float.class) {
            tform = "E" + this.lengths[col] + ".0";
        } else if (this.types[col] == double.class) {
            tform = "D" + this.lengths[col] + ".0";
        }
        Standard.context(AsciiTable.class);
        IFitsHeader key = TFORMn.n(col + 1);
        iter.add(new HeaderCard(key.key(), tform, key.comment()));
        key = TBCOLn.n(col + 1);
        iter.add(new HeaderCard(key.key(), this.offsets[col] + 1, key.comment()));
        Standard.context(null);
        return this.lengths[col];
    }

    @Override
    public int addColumn(Object newCol) throws FitsException {
        int maxLen = 1;
        if (newCol instanceof String[]) {

            String[] sa = (String[]) newCol;
            for (String element : sa) {
                if (element != null && element.length() > maxLen) {
                    maxLen = element.length();
                }
            }
        } else if (newCol instanceof double[]) {
            maxLen = DOUBLE_MAX_LENGTH;
        } else if (newCol instanceof int[]) {
            maxLen = INT_MAX_LENGTH;
        } else if (newCol instanceof long[]) {
            maxLen = LONG_MAX_LENGTH;
        } else if (newCol instanceof float[]) {
            maxLen = FLOAT_MAX_LENGTH;
        } else {
            throw new FitsException("Adding invalid type to ASCII table");
        }
        addColumn(newCol, maxLen);

        // Invalidate the buffer
        this.buffer = null;

        return this.nFields;
    }

    /**
     * This version of addColumn allows the user to override the default length
     * associated with each column type.
     *
     * @param newCol
     *            The new column data
     * @param length
     *            the requested length for the column
     * @return the number of columns after this one is added.
     * @throws FitsException
     *             if the operation failed
     */
    public int addColumn(Object newCol, int length) throws FitsException {

        if (this.nFields > 0 && Array.getLength(newCol) != this.nRows) {
            throw new FitsException("New column has different number of rows");
        }

        if (this.nFields == 0) {
            this.nRows = Array.getLength(newCol);
        }

        Object[] newData = new Object[this.nFields + 1];
        int[] newOffsets = new int[this.nFields + 1];
        int[] newLengths = new int[this.nFields + 1];
        Class[] newTypes = new Class[this.nFields + 1];
        String[] newNulls = new String[this.nFields + 1];

        System.arraycopy(this.data, 0, newData, 0, this.nFields);
        System.arraycopy(this.offsets, 0, newOffsets, 0, this.nFields);
        System.arraycopy(this.lengths, 0, newLengths, 0, this.nFields);
        System.arraycopy(this.types, 0, newTypes, 0, this.nFields);
        System.arraycopy(this.nulls, 0, newNulls, 0, this.nFields);

        this.data = newData;
        this.offsets = newOffsets;
        this.lengths = newLengths;
        this.types = newTypes;
        this.nulls = newNulls;

        newData[this.nFields] = newCol;
        this.offsets[this.nFields] = this.rowLen + 1;
        this.lengths[this.nFields] = length;
        this.types[this.nFields] = ArrayFuncs.getBaseClass(newCol);

        this.rowLen += length + 1;
        if (this.isNull != null) {
            boolean[] newIsNull = new boolean[this.nRows * (this.nFields + 1)];
            // Fix the null pointers.
            int add = 0;
            for (int i = 0; i < this.isNull.length; i += 1) {
                if (i % this.nFields == 0) {
                    add += 1;
                }
                if (this.isNull[i]) {
                    newIsNull[i + add] = true;
                }
            }
            this.isNull = newIsNull;
        }
        this.nFields += 1;

        // Invalidate the buffer
        this.buffer = null;

        return this.nFields;
    }

    @Override
    public int addRow(Object[] newRow) throws FitsException {
        try {
            // If there are no fields, then this is the
            // first row. We need to add in each of the columns
            // to get the descriptors set up.
            if (this.nFields == 0) {
                for (Object element : newRow) {
                    addColumn(element);
                }
            } else {
                for (int i = 0; i < this.nFields; i += 1) {
                    Object o = ArrayFuncs.newInstance(this.types[i], this.nRows + 1);
                    System.arraycopy(this.data[i], 0, o, 0, this.nRows);
                    System.arraycopy(newRow[i], 0, o, this.nRows, 1);
                    this.data[i] = o;
                }
                this.nRows += 1;
            }
            // Invalidate the buffer
            this.buffer = null;
            return this.nRows;
        } catch (Exception e) {
            throw new FitsException("Error addnig row:" + e.getMessage(), e);
        }
    }

    /**
     * Delete columns from the table.
     *
     * @param start
     *            The first, 0-indexed, column to be deleted.
     * @param len
     *            The number of columns to be deleted.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void deleteColumns(int start, int len) throws FitsException {
        ensureData();

        Object[] newData = new Object[this.nFields - len];
        int[] newOffsets = new int[this.nFields - len];
        int[] newLengths = new int[this.nFields - len];
        Class[] newTypes = new Class[this.nFields - len];
        String[] newNulls = new String[this.nFields - len];

        // Copy in the initial stuff...
        System.arraycopy(this.data, 0, newData, 0, start);
        // Don't do the offsets here.
        System.arraycopy(this.lengths, 0, newLengths, 0, start);
        System.arraycopy(this.types, 0, newTypes, 0, start);
        System.arraycopy(this.nulls, 0, newNulls, 0, start);

        // Copy in the final
        System.arraycopy(this.data, start + len, newData, start, this.nFields - start - len);
        // Don't do the offsets here.
        System.arraycopy(this.lengths, start + len, newLengths, start, this.nFields - start - len);
        System.arraycopy(this.types, start + len, newTypes, start, this.nFields - start - len);
        System.arraycopy(this.nulls, start + len, newNulls, start, this.nFields - start - len);

        for (int i = start; i < start + len; i += 1) {
            this.rowLen -= this.lengths[i] + 1;
        }

        this.data = newData;
        this.offsets = newOffsets;
        this.lengths = newLengths;
        this.types = newTypes;
        this.nulls = newNulls;

        if (this.isNull != null) {
            boolean found = false;

            boolean[] newIsNull = new boolean[this.nRows * (this.nFields - len)];
            for (int i = 0; i < this.nRows; i += 1) {
                int oldOff = this.nFields * i;
                int newOff = (this.nFields - len) * i;
                for (int col = 0; col < start; col += 1) {
                    newIsNull[newOff + col] = this.isNull[oldOff + col];
                    found = found || this.isNull[oldOff + col];
                }
                for (int col = start + len; col < this.nFields; col += 1) {
                    newIsNull[newOff + col - len] = this.isNull[oldOff + col];
                    found = found || this.isNull[oldOff + col];
                }
            }
            if (found) {
                this.isNull = newIsNull;
            } else {
                this.isNull = null;
            }
        }

        // Invalidate the buffer
        this.buffer = null;

        this.nFields -= len;
    }

    /**
     * Delete rows from a FITS table
     *
     * @param start
     *            The first (0-indexed) row to be deleted.
     * @param len
     *            The number of rows to be deleted.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void deleteRows(int start, int len) throws FitsException {
        try {
            if (this.nRows == 0 || start < 0 || start >= this.nRows || len <= 0) {
                return;
            }
            if (start + len > this.nRows) {
                len = this.nRows - start;
            }
            ensureData();
            for (int i = 0; i < this.nFields; i += 1) {
                Object o = ArrayFuncs.newInstance(this.types[i], this.nRows - len);
                System.arraycopy(this.data[i], 0, o, 0, start);
                System.arraycopy(this.data[i], start + len, o, start, this.nRows - len - start);
                this.data[i] = o;
            }
            this.nRows -= len;
        } catch (FitsException e) {
            throw e;
        } catch (Exception e) {
            throw new FitsException("Error deleting row:" + e.getMessage(), e);
        }
    }

    /**
     * be sure that the data is filled. because the getData already tests null
     * the getData is called without check.
     *
     * @throws FitsException
     *             if the operation failed
     */
    private void ensureData() throws FitsException {
        getData();
    }

    /**
     * Move an element from the buffer into a data array.
     *
     * @param offset
     *            The offset within buffer at which the element starts.
     * @param length
     *            The number of bytes in the buffer for the element.
     * @param array
     *            An array of objects, each of which is a simple array.
     * @param col
     *            Which element of array is to be modified?
     * @param row
     *            Which index into that element is to be modified?
     * @param nullFld
     *            What string signifies a null element?
     * @throws FitsException
     *             if the operation failed
     */
    private boolean extractElement(int offset, int length, Object[] array, int col, int row, String nullFld) throws FitsException {

        this.bp.setOffset(offset);

        if (nullFld != null) {
            String s = this.bp.getString(length);
            if (s.trim().equals(nullFld)) {
                return false;
            }
            this.bp.skip(-length);
        }
        try {
            if (array[col] instanceof String[]) {
                ((String[]) array[col])[row] = this.bp.getString(length);
            } else if (array[col] instanceof int[]) {
                ((int[]) array[col])[row] = this.bp.getInt(length);
            } else if (array[col] instanceof float[]) {
                ((float[]) array[col])[row] = this.bp.getFloat(length);
            } else if (array[col] instanceof double[]) {
                ((double[]) array[col])[row] = this.bp.getDouble(length);
            } else if (array[col] instanceof long[]) {
                ((long[]) array[col])[row] = this.bp.getLong(length);
            } else {
                throw new FitsException("Invalid type for ASCII table conversion:" + array[col]);
            }
        } catch (FormatException e) {
            throw new FitsException("Error parsing data at row,col:" + row + "," + col + "  " + e);
        }
        return true;
    }

    /**
     * Fill in a header with information that points to this data.
     *
     * @param hdr
     *            The header to be updated with information appropriate to the
     *            current table data.
     */

    @Override
    public void fillHeader(Header hdr) {
        try {
            Standard.context(AsciiTable.class);
            hdr.setXtension("TABLE");
            hdr.setBitpix(BasicHDU.BITPIX_BYTE);
            hdr.setNaxes(2);
            hdr.setNaxis(1, this.rowLen);
            hdr.setNaxis(2, this.nRows);
            Cursor iter = hdr.iterator();
            iter.setKey(NAXIS2.key());
            iter.next();
            iter.add(new HeaderCard(PCOUNT.key(), 0, PCOUNT.comment()));
            iter.add(new HeaderCard(GCOUNT.key(), 1, GCOUNT.comment()));
            iter.add(new HeaderCard(TFIELDS.key(), this.nFields, TFIELDS.comment()));

            for (int i = 0; i < this.nFields; i += 1) {
                addColInfo(i, iter);
            }
        } catch (HeaderCardException e) {
            LOG.log(Level.SEVERE, "ImpossibleException in fillHeader:" + e.getMessage(), e);
        } finally {
            Standard.context(null);
        }
    }

    /**
     * Read some data into the buffer.
     */
    private void getBuffer(int size, long offset) throws IOException, FitsException {

        if (this.currInput == null) {
            throw new IOException("No stream open to read");
        }

        this.buffer = new byte[size];
        if (offset != 0) {
            FitsUtil.reposition(this.currInput, offset);
        }
        this.currInput.readFully(this.buffer);
        this.bp = new ByteParser(this.buffer);
    }

    /**
     * Get a column of data
     *
     * @param col
     *            The 0-indexed column to be returned.
     * @return The column object -- typically as a 1-d array.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public Object getColumn(int col) throws FitsException {
        ensureData();
        return this.data[col];
    }

    /**
     * Get the ASCII table information. This will actually do the read if it had
     * previously been deferred
     *
     * @return The table data as an Object[] array.
     * @throws FitsException
     *             if the operation failed
     */
    @Override
    @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data")
    public Object getData() throws FitsException {

        if (this.data == null) {

            this.data = new Object[this.nFields];

            for (int i = 0; i < this.nFields; i += 1) {
                this.data[i] = ArrayFuncs.newInstance(this.types[i], this.nRows);
            }

            if (this.buffer == null) {
                long newOffset = FitsUtil.findOffset(this.currInput);
                try {
                    getBuffer(this.nRows * this.rowLen, this.fileOffset);

                } catch (IOException e) {
                    throw new FitsException("Error in deferred read -- file closed prematurely?:" + e.getMessage(), e);
                }
                FitsUtil.reposition(this.currInput, newOffset);
            }

            this.bp.setOffset(0);

            int rowOffset;
            for (int i = 0; i < this.nRows; i += 1) {
                rowOffset = this.rowLen * i;
                for (int j = 0; j < this.nFields; j += 1) {
                    if (!extractElement(rowOffset + this.offsets[j], this.lengths[j], this.data, j, i, this.nulls[j])) {
                        if (this.isNull == null) {
                            this.isNull = new boolean[this.nRows * this.nFields];
                        }

                        this.isNull[j + i * this.nFields] = true;
                    }
                }
            }
        }
        return this.data;
    }

    /**
     * Get a single element as a one-d array. We return String's as arrays for
     * consistency though they could be returned as a scalar.
     *
     * @param row
     *            The 0-based row
     * @param col
     *            The 0-based column
     * @return The requested cell data.
     * @throws FitsException
     *             when unable to get the data.
     */

    @Override
    public Object getElement(int row, int col) throws FitsException {
        if (this.data != null) {
            return singleElement(row, col);
        } else {
            return parseSingleElement(row, col);
        }
    }

    /**
     * Get the number of columns in the table
     *
     * @return The number of columns
     */

    @Override
    public int getNCols() {
        return this.nFields;
    }

    /**
     * Get the number of rows in the table
     *
     * @return The number of rows.
     */

    @Override
    public int getNRows() {
        return this.nRows;
    }

    /**
     * Get a row. If the data has not yet been read just read this row.
     *
     * @param row
     *            The 0-indexed row to be returned.
     * @return A row of data.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public Object[] getRow(int row) throws FitsException {

        if (this.data != null) {
            return singleRow(row);
        } else {
            return parseSingleRow(row);
        }
    }

    /**
     * Get the number of bytes in a row
     *
     * @return The number of bytes for a single row in the table.
     */
    public int getRowLen() {
        return this.rowLen;
    }

    /**
     * Return the size of the data section
     *
     * @return The size in bytes of the data section, not includeing the
     *         padding.
     */

    @Override
    protected long getTrueSize() {
        return (long) this.nRows * this.rowLen;
    }

    /**
     * See if an element is null.
     *
     * @param row
     *            The 0-based row
     * @param col
     *            The 0-based column
     * @return if the given element has been nulled.
     */
    public boolean isNull(int row, int col) {
        if (this.isNull != null) {
            return this.isNull[row * this.nFields + col];
        } else {
            return false;
        }
    }

    /**
     * Read a single element from the table. This returns an array of dimension
     * 1.
     *
     * @throws FitsException
     *             if the operation failed
     */
    private Object parseSingleElement(int row, int col) throws FitsException {

        Object[] res = new Object[1];
        try {
            getBuffer(this.lengths[col], this.fileOffset + (long) row * (long) this.rowLen + this.offsets[col]);
        } catch (IOException e) {
            this.buffer = null;
            throw new FitsException("Unable to read element", e);
        }
        res[0] = ArrayFuncs.newInstance(this.types[col], 1);

        if (extractElement(0, this.lengths[col], res, 0, 0, this.nulls[col])) {
            this.buffer = null;
            return res[0];

        } else {

            this.buffer = null;
            return null;
        }
    }

    /**
     * Read a single row from the table. This returns a set of arrays of
     * dimension 1.
     *
     * @throws FitsException
     *             if the operation failed
     */
    private Object[] parseSingleRow(int row) throws FitsException {

        Object[] res = new Object[this.nFields];

        try {
            getBuffer(this.rowLen, this.fileOffset + (long) row * (long) this.rowLen);
        } catch (IOException e) {
            throw new FitsException("Unable to read row", e);
        }

        for (int i = 0; i < this.nFields; i += 1) {
            res[i] = ArrayFuncs.newInstance(this.types[i], 1);
            if (!extractElement(this.offsets[i], this.lengths[i], res, i, 0, this.nulls[i])) {
                res[i] = null;
            }
        }

        // Invalidate buffer for future use.
        this.buffer = null;
        return res;
    }

    /**
     * Read in an ASCII table. Reading is deferred if we are reading from a
     * random access device
     *
     * @param str
     *            the stream to read from
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void read(ArrayDataInput str) throws FitsException {
        try {
            setFileOffset(str);
            this.currInput = str;
            if (str instanceof RandomAccess) {
                str.skipAllBytes((long) this.nRows * this.rowLen);
            } else {
                if ((long) this.rowLen * this.nRows > Integer.MAX_VALUE) {
                    throw new FitsException("Cannot read ASCII table > 2 GB");
                }
                getBuffer(this.rowLen * this.nRows, 0);
            }
            str.skipAllBytes(FitsUtil.padding(this.nRows * this.rowLen));
        } catch (EOFException e) {
            throw new PaddingException("EOF skipping padding after ASCII Table", this, e);
        } catch (IOException e) {
            throw new FitsException("Error skipping padding after ASCII Table", e);
        }
    }

    /**
     * Replace a column with new data.
     *
     * @param col
     *            The 0-based index to the column
     * @param newData
     *            The column data. This is typically a 1-d array.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void setColumn(int col, Object newData) throws FitsException {
        ensureData();
        if (col < 0 || col >= this.nFields || newData.getClass() != this.data[col].getClass() || Array.getLength(newData) != Array.getLength(this.data[col])) {
            throw new FitsException("Invalid column/column mismatch:" + col);
        }
        this.data[col] = newData;

        // Invalidate the buffer.
        this.buffer = null;

    }

    /**
     * Modify an element in the table
     *
     * @param row
     *            the 0-based row
     * @param col
     *            the 0-based column
     * @param newData
     *            The new value for the column. Typically a primitive[1] array.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void setElement(int row, int col, Object newData) throws FitsException {
        ensureData();
        try {
            System.arraycopy(newData, 0, this.data[col], row, 1);
        } catch (Exception e) {
            throw new FitsException("Incompatible element:" + row + "," + col, e);
        }
        setNull(row, col, false);

        // Invalidate the buffer
        this.buffer = null;

    }

    /**
     * Mark (or unmark) an element as null. Note that if this FITS file is
     * latter written out, a TNULL keyword needs to be defined in the
     * corresponding header. This routine does not add an element for String
     * columns.
     *
     * @param row
     *            The 0-based row.
     * @param col
     *            The 0-based column.
     * @param flag
     *            True if the element is to be set to null.
     */
    public void setNull(int row, int col, boolean flag) {
        if (flag) {
            if (this.isNull == null) {
                this.isNull = new boolean[this.nRows * this.nFields];
            }
            this.isNull[col + row * this.nFields] = true;
        } else if (this.isNull != null) {
            this.isNull[col + row * this.nFields] = false;
        }

        // Invalidate the buffer
        this.buffer = null;
    }

    /**
     * Set the null string for a columns. This is not a public method since we
     * want users to call the method in AsciiTableHDU and update the header
     * also.
     */
    void setNullString(int col, String newNull) {
        if (col >= 0 && col < this.nulls.length) {
            this.nulls[col] = newNull;
        }
    }

    /**
     * Modify a row in the table
     *
     * @param row
     *            The 0-based index of the row
     * @param newData
     *            The new data. Each element of this array is typically a
     *            primitive[1] array.
     * @throws FitsException
     *             if the operation failed
     */

    @Override
    public void setRow(int row, Object[] newData) throws FitsException {
        if (row < 0 || row > this.nRows) {
            throw new FitsException("Invalid row in setRow");
        }
        ensureData();
        for (int i = 0; i < this.nFields; i += 1) {
            try {
                System.arraycopy(newData[i], 0, this.data[i], row, 1);
            } catch (Exception e) {
                throw new FitsException("Unable to modify row: incompatible data:" + row, e);
            }
            setNull(row, i, false);
        }

        // Invalidate the buffer
        this.buffer = null;

    }

    /**
     * Extract a single element from a table. This returns an array of length 1.
     */
    private Object singleElement(int row, int col) {

        Object res = null;
        if (this.isNull == null || !this.isNull[row * this.nFields + col]) {
            res = ArrayFuncs.newInstance(this.types[col], 1);
            System.arraycopy(this.data[col], row, res, 0, 1);
        }
        return res;
    }

    /**
     * Extract a single row from a table. This returns an array of Objects each
     * of which is an array of length 1.
     */
    private Object[] singleRow(int row) {

        Object[] res = new Object[this.nFields];
        for (int i = 0; i < this.nFields; i += 1) {
            if (this.isNull == null || !this.isNull[row * this.nFields + i]) {
                res[i] = ArrayFuncs.newInstance(this.types[i], 1);
                System.arraycopy(this.data[i], row, res[i], 0, 1);
            }
        }
        return res;
    }

    /**
     * This is called after we delete columns. The HDU doesn't know how to
     * update the TBCOL entries.
     *
     * @param oldNCol
     *            The number of columns we had before deletion.
     * @param hdr
     *            The associated header. @throws FitsException if the operation
     *            failed
     */

    @Override
    public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException {

        int offset = 0;
        for (int i = 0; i < this.nFields; i += 1) {
            this.offsets[i] = offset;
            hdr.addValue(TBCOLn.n(i + 1), offset + 1);
            offset += this.lengths[i] + 1;
        }
        for (int i = this.nFields; i < oldNCol; i += 1) {
            hdr.deleteKey(TBCOLn.n(i + 1));
        }

        hdr.addValue(NAXIS1, this.rowLen);
    }

    /**
     * Write the data to an output stream.
     *
     * @param str
     *            The output stream to be written to
     * @throws FitsException
     *             if any IO exception is found or some inconsistency the FITS
     *             file arises.
     */

    @Override
    public void write(ArrayDataOutput str) throws FitsException {
        // Make sure we have the data in hand.
        ensureData();
        // If buffer is still around we can just reuse it,
        // since nothing we've done has invalidated it.

        if (this.buffer == null) {

            if (this.data == null) {
                throw new FitsException("Attempt to write undefined ASCII Table");
            }

            if ((long) this.nRows * this.rowLen > Integer.MAX_VALUE) {
                throw new FitsException("Cannot write ASCII table > 2 GB");
            }

            this.buffer = new byte[this.nRows * this.rowLen];

            this.bp = new ByteParser(this.buffer);
            for (int i = 0; i < this.buffer.length; i += 1) {
                this.buffer[i] = (byte) ' ';
            }

            ByteFormatter bf = new ByteFormatter();

            for (int i = 0; i < this.nRows; i += 1) {

                for (int j = 0; j < this.nFields; j += 1) {
                    int offset = i * this.rowLen + this.offsets[j];
                    int len = this.lengths[j];
                    if (this.isNull != null && this.isNull[i * this.nFields + j]) {
                        if (this.nulls[j] == null) {
                            throw new FitsException("No null value set when needed");
                        }
                        bf.format(this.nulls[j], this.buffer, offset, len);
                    } else {
                        if (this.types[j] == String.class) {
                            String[] s = (String[]) this.data[j];
                            bf.format(s[i], this.buffer, offset, len);
                        } else if (this.types[j] == int.class) {
                            int[] ia = (int[]) this.data[j];
                            bf.format(ia[i], this.buffer, offset, len);
                        } else if (this.types[j] == float.class) {
                            float[] fa = (float[]) this.data[j];
                            bf.format(fa[i], this.buffer, offset, len);
                        } else if (this.types[j] == double.class) {
                            double[] da = (double[]) this.data[j];
                            bf.format(da[i], this.buffer, offset, len);
                        } else if (this.types[j] == long.class) {
                            long[] la = (long[]) this.data[j];
                            bf.format(la[i], this.buffer, offset, len);
                        }
                    }
                }
            }
        }

        // Now write the buffer.
        try {
            str.write(this.buffer);
            FitsUtil.pad(str, this.buffer.length, (byte) ' ');
        } catch (IOException e) {
            throw new FitsException("Error writing ASCII Table data", e);
        }
    }
}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/AsciiTableHDU.java000066400000000000000000000177221310063650500266130ustar00rootroot00000000000000package nom.tam.fits;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2015 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 * 
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 * 
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

import static nom.tam.fits.header.Standard.NAXIS1;
import static nom.tam.fits.header.Standard.NAXIS2;
import static nom.tam.fits.header.Standard.TBCOLn;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.TFORMn;
import static nom.tam.fits.header.Standard.TNULLn;
import static nom.tam.fits.header.Standard.TTYPEn;
import static nom.tam.fits.header.Standard.TUNITn;
import static nom.tam.fits.header.Standard.TZEROn;
import static nom.tam.fits.header.Standard.XTENSION;

import java.io.PrintStream;
import java.util.logging.Logger;

import nom.tam.fits.header.IFitsHeader;
import nom.tam.fits.header.Standard;
import nom.tam.util.ArrayFuncs;
import nom.tam.util.Cursor;

/**
 * FITS ASCII table header/data unit
 */
public class AsciiTableHDU extends TableHDU {

    private static final Logger LOG = Logger.getLogger(AsciiTableHDU.class.getName());

    /**
     * The standard column stems for an ASCII table. Note that TBCOL is not
     * included here -- it needs to be handled specially since it does not
     * simply shift.
     */
    private static final IFitsHeader[] KEY_STEMS = {
        TFORMn,
        TZEROn,
        TNULLn,
        TTYPEn,
        TUNITn
    };

    /**
     * Create an ASCII table header/data unit.
     *
     * @param h
     *            the template specifying the ASCII table.
     * @param d
     *            the FITS data structure containing the table data.
     */
    public AsciiTableHDU(Header h, AsciiTable d) {
        super(h, d);
    }

    /**
     * @return a ASCII table data structure from an array of objects
     *         representing the columns.
     * @param o
     *            the array of object to create the ASCII table
     * @throws FitsException
     *             if the table could not be created.
     */
    public static AsciiTable encapsulate(Object o) throws FitsException {

        Object[] oo = (Object[]) o;
        AsciiTable d = new AsciiTable();
        for (Object element : oo) {
            d.addColumn(element);
        }
        return d;
    }

    /**
     * @return true if this data is usable as an ASCII table.
     * @param o
     *            object representing the data
     */
    public static boolean isData(Object o) {

        if (o instanceof Object[]) {
            Object[] oo = (Object[]) o;
            for (Object element : oo) {
                if (!(element instanceof String[]) && //
                        !(element instanceof int[]) && //
                        !(element instanceof long[]) && //
                        !(element instanceof float[]) && //
                        !(element instanceof double[])) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check that this is a valid ascii table header.
     *
     * @param header
     *            to validate.
     * @return true if this is an ascii table header.
     */
    public static boolean isHeader(Header header) {
        String xtension = header.getStringValue(XTENSION);
        xtension = xtension == null ? "" : xtension.trim();
        return "TABLE".equals(xtension);
    }

    /**
     * Create a Data object to correspond to the header description.
     *
     * @param hdr
     *            the header to create the data for
     * @return An unfilled Data object which can be used to read in the data for
     *         this HDU.
     * @throws FitsException
     *             if the Data object could not be created from this HDU's
     *             Header
     */
    public static Data manufactureData(Header hdr) throws FitsException {
        return new AsciiTable(hdr);
    }

    /**
     * @return a created header to match the input data.
     * @param d
     *            data to create a header for
     * @throws FitsException
     *             if the header could not b e created
     */
    public static Header manufactureHeader(Data d) throws FitsException {
        Header hdr = new Header();
        d.fillHeader(hdr);
        hdr.iterator();
        return hdr;
    }

    @Override
    public int addColumn(Object newCol) throws FitsException {
        Standard.context(AsciiTable.class);
        this.myData.addColumn(newCol);
        // Move the iterator to point after all the data describing
        // the previous column.

        Cursor iter = this.myHeader.positionAfterIndex(TBCOLn, this.myData.getNCols());

        int rowlen = this.myData.addColInfo(getNCols() - 1, iter);
        int oldRowlen = this.myHeader.getIntValue(NAXIS1);
        this.myHeader.setNaxis(1, rowlen + oldRowlen);

        super.addColumn(newCol);
        Standard.context(null);
        return getNCols();
    }

    @Override
    protected IFitsHeader[] columnKeyStems() {
        return KEY_STEMS;
    }

    @Override
    public void info(PrintStream stream) {
        stream.println("ASCII Table:");
        stream.println("  Header:");
        stream.println("    Number of fields:" + this.myHeader.getIntValue(TFIELDS));
        stream.println("    Number of rows:  " + this.myHeader.getIntValue(NAXIS2));
        stream.println("    Length of row:   " + this.myHeader.getIntValue(NAXIS1));
        stream.println("  Data:");
        Object[] data = (Object[]) getKernel();
        for (int i = 0; i < getNCols(); i += 1) {
            stream.println("      " + i + ":" + ArrayFuncs.arrayDescription(data[i]));
        }
    }

    /**
     * @param row
     *            row index of the element
     * @param col
     *            column index of the element
     * @return true if an element is null
     */
    public boolean isNull(int row, int col) {
        return this.myData.isNull(row, col);
    }

    /**
     * Mark an entry as null.
     *
     * @param row
     *            row index of the element
     * @param col
     *            column index of the element
     * @param flag
     *            set to null or not
     */
    public void setNull(int row, int col, boolean flag) {

        if (flag) {
            String nullStr = this.myHeader.getStringValue(TNULLn.n(col + 1));
            if (nullStr == null) {
                setNullString(col, "NULL");
            }
        }
        this.myData.setNull(row, col, flag);
    }

    /**
     * Set the null string for a column.
     *
     * @param col
     *            the column index
     * @param newNull
     *            the String representing null
     */
    public void setNullString(int col, String newNull) {
        this.myHeader.positionAfterIndex(TBCOLn, col + 1);
        saveReplaceCard(TNULLn.n(col + 1).key(), true, newNull);
        this.myData.setNullString(col, newNull);
    }

}
nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/BasicHDU.java000066400000000000000000000461371310063650500256360ustar00rootroot00000000000000package nom.tam.fits;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2015 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 * 
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 * 
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

import static nom.tam.fits.header.Standard.AUTHOR;
import static nom.tam.fits.header.Standard.BLANK;
import static nom.tam.fits.header.Standard.BSCALE;
import static nom.tam.fits.header.Standard.BUNIT;
import static nom.tam.fits.header.Standard.BZERO;
import static nom.tam.fits.header.Standard.DATAMAX;
import static nom.tam.fits.header.Standard.DATAMIN;
import static nom.tam.fits.header.Standard.DATE;
import static nom.tam.fits.header.Standard.DATE_OBS;
import static nom.tam.fits.header.Standard.EPOCH;
import static nom.tam.fits.header.Standard.EQUINOX;
import static nom.tam.fits.header.Standard.EXTEND;
import static nom.tam.fits.header.Standard.GCOUNT;
import static nom.tam.fits.header.Standard.GROUPS;
import static nom.tam.fits.header.Standard.INSTRUME;
import static nom.tam.fits.header.Standard.NAXIS;
import static nom.tam.fits.header.Standard.NAXISn;
import static nom.tam.fits.header.Standard.OBJECT;
import static nom.tam.fits.header.Standard.OBSERVER;
import static nom.tam.fits.header.Standard.ORIGIN;
import static nom.tam.fits.header.Standard.PCOUNT;
import static nom.tam.fits.header.Standard.REFERENC;
import static nom.tam.fits.header.Standard.TELESCOP;
import static nom.tam.util.LoggerHelper.getLogger;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import nom.tam.fits.header.IFitsHeader;
import nom.tam.fits.header.Standard;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;

/**
 * This abstract class is the parent of all HDU types. It provides basic
 * functionality for an HDU.
 */
public abstract class BasicHDU implements FitsElement {

    private static final int MAX_NAXIS_ALLOWED = 999;

    private static final Logger LOG = getLogger(BasicHDU.class);

    public static final int BITPIX_BYTE = 8;

    public static final int BITPIX_SHORT = 16;

    public static final int BITPIX_INT = 32;

    public static final int BITPIX_LONG = 64;

    public static final int BITPIX_FLOAT = -32;

    public static final int BITPIX_DOUBLE = -64;


    /** The associated header. */
    protected Header myHeader = null;

    /** The associated data unit. */
    protected DataClass myData = null;

    /** Is this the first HDU in a FITS file? */
    protected boolean isPrimary = false;

    protected BasicHDU(Header myHeader, DataClass myData) {
        this.myHeader = myHeader;
        this.myData = myData;
    }

    /**
     * @return an HDU without content
     */
    public static BasicHDU getDummyHDU() {
        try {
            // Update suggested by Laurent Bourges
            ImageData img = new ImageData((Object) null);
            return FitsFactory.hduFactory(ImageHDU.manufactureHeader(img), img);
        } catch (FitsException e) {
            LOG.log(Level.SEVERE, "Impossible exception in getDummyHDU", e);
            return null;
        }
    }

    /**
     * Check that this is a valid header for the HDU. This method is static but
     * should be implemented by all subclasses. TODO: refactor this to be in a
     * meta object so it can inherit normally also see {@link #isData(Object)}
     * 
     * @param header
     *            to validate.
     * @return true if this is a valid header.
     */
    public static boolean isHeader(Header header) {
        return false;
    }

    /**
     * @return if this object can be described as a FITS image. This method is
     *         static but should be implemented by all subclasses. TODO:
     *         refactor this to be in a meta object so it can inherit normally
     *         also see {@link #isHeader(Header)}
     * @param o
     *            The Object being tested.
     */
    public static boolean isData(Object o) {
        return false;
    }


    /**
     * Safely replace a card in the header, knowing that no exception will
     * occur. Only for internal use!
     * 
     * @param key
     *            the key of the card
     * @param isString
     *            is the value a String
     * @param value
     *            the String representation of the value
     */
    protected void saveReplaceCard(String key, boolean isString, String value) {
        HeaderCard card = HeaderCard.saveNewHeaderCard(key, null, isString);
        card.setValue(value);
        this.myHeader.deleteKey(card.getKey());
        this.myHeader.addLine(card);
    }

    public void addValue(IFitsHeader key, boolean val) throws HeaderCardException {
        this.myHeader.addValue(key.key(), val, key.comment());
    }

    public void addValue(IFitsHeader key, double val) throws HeaderCardException {
        this.myHeader.addValue(key.key(), val, key.comment());
    }

    public void addValue(IFitsHeader key, int val) throws HeaderCardException {
        this.myHeader.addValue(key.key(), val, key.comment());
    }

    public void addValue(IFitsHeader key, String val) throws HeaderCardException {
        this.myHeader.addValue(key.key(), val, key.comment());
    }

    /**
     * Add information to the header.
     * 
     * @param key
     *            key to add to the header
     * @param val
     *            value for the key to add
     * @param comment
     *            comment for the key/value pair
     * @throws HeaderCardException
     *             if the card does not follow the specification
     */
    public void addValue(String key, boolean val, String comment) throws HeaderCardException {
        this.myHeader.addValue(key, val, comment);
    }

    public void addValue(String key, double val, String comment) throws HeaderCardException {
        this.myHeader.addValue(key, val, comment);
    }

    public void addValue(String key, int val, String comment) throws HeaderCardException {
        this.myHeader.addValue(key, val, comment);
    }

    public void addValue(String key, String val, String comment) throws HeaderCardException {
        this.myHeader.addValue(key, val, comment);
    }

    /**
     * @return Indicate whether HDU can be primary HDU. This method must be
     *         overriden in HDU types which can appear at the beginning of a
     *         FITS file.
     */
    boolean canBePrimary() {
        return false;
    }

    /**
     * Return the name of the person who compiled the information in the data
     * associated with this header.
     * 
     * @return either null or a String object
     */
    public String getAuthor() {
        return getTrimmedString(AUTHOR);
    }

    /**
     * In FITS files the index represented by NAXIS1 is the index that changes
     * most rapidly. This reflects the behavior of Fortran where there are true
     * multidimensional arrays. In Java in a multidimensional array is an array
     * of arrays and the first index is the index that changes slowest. So at
     * some point a client of the library is going to have to invert the order.
     * E.g., if I have a FITS file will
     * 
     * 
     * BITPIX=16
     * NAXIS1=10
     * NAXIS2=20
     * NAXIS3=30
     * 
* * this will be read into a Java array short[30][20][10] so it makes sense * to me at least that the returned dimensions are 30,20,10 * * @return the dimensions of the axis. * @throws FitsException * if the axis are configured wrong. */ public int[] getAxes() throws FitsException { int nAxis = this.myHeader.getIntValue(NAXIS, 0); if (nAxis < 0) { throw new FitsException("Negative NAXIS value " + nAxis); } if (nAxis > MAX_NAXIS_ALLOWED) { throw new FitsException("NAXIS value " + nAxis + " too large"); } if (nAxis == 0) { return null; } int[] axes = new int[nAxis]; for (int i = 1; i <= nAxis; i++) { axes[nAxis - i] = this.myHeader.getIntValue(NAXISn.n(i), 0); } return axes; } public int getBitPix() throws FitsException { int bitpix = this.myHeader.getIntValue(Standard.BITPIX, -1); switch (bitpix) { case BITPIX_BYTE: case BITPIX_SHORT: case BITPIX_INT: case BITPIX_LONG: case BITPIX_FLOAT: case BITPIX_DOUBLE: break; default: throw new FitsException("Unknown BITPIX type " + bitpix); } return bitpix; } public long getBlankValue() throws FitsException { if (!this.myHeader.containsKey(BLANK.key())) { throw new FitsException("BLANK undefined"); } return this.myHeader.getLongValue(BLANK); } public double getBScale() { return this.myHeader.getDoubleValue(BSCALE, 1.0); } public String getBUnit() { return getTrimmedString(BUNIT); } public double getBZero() { return this.myHeader.getDoubleValue(BZERO, 0.0); } /** * Get the FITS file creation date as a Date object. * * @return either null or a Date object */ public Date getCreationDate() { try { return new FitsDate(this.myHeader.getStringValue(DATE)).toDate(); } catch (FitsException e) { LOG.log(Level.SEVERE, "Unable to convert string to FITS date", e); return null; } } /** * @return the associated Data object */ public DataClass getData() { return this.myData; } /** * Get the equinox in years for the celestial coordinate system in which * positions given in either the header or data are expressed. * * @return either null or a String object * @deprecated use {@link #getEquinox()} instead */ @Deprecated public double getEpoch() { return this.myHeader.getDoubleValue(EPOCH, -1.0); } /** * Get the equinox in years for the celestial coordinate system in which * positions given in either the header or data are expressed. * * @return either null or a String object */ public double getEquinox() { return this.myHeader.getDoubleValue(EQUINOX, -1.0); } /** Get the starting offset of the HDU */ @Override public long getFileOffset() { return this.myHeader.getFileOffset(); } public int getGroupCount() { return this.myHeader.getIntValue(GCOUNT, 1); } /** * @return the associated header */ public Header getHeader() { return this.myHeader; } /** * get a builder for filling the header cards using the builder pattern. * * @param key * the key for the first card. * @return the builder for header cards. */ public HeaderCardBuilder card(IFitsHeader key) { return this.myHeader.card(key); } /** * Get the name of the instrument which was used to acquire the data in this * FITS file. * * @return either null or a String object */ public String getInstrument() { return getTrimmedString(INSTRUME); } /** * @return the non-FITS data object */ public Object getKernel() { try { return this.myData.getKernel(); } catch (FitsException e) { LOG.log(Level.SEVERE, "Unable to get kernel data", e); return null; } } /** * Return the minimum valid value in the array. * * @return minimum value. */ public double getMaximumValue() { return this.myHeader.getDoubleValue(DATAMAX); } /** * Return the minimum valid value in the array. * * @return minimum value. */ public double getMinimumValue() { return this.myHeader.getDoubleValue(DATAMIN); } /** * Get the name of the observed object in this FITS file. * * @return either null or a String object */ public String getObject() { return getTrimmedString(OBJECT); } /** * Get the FITS file observation date as a Date object. * * @return either null or a Date object */ public Date getObservationDate() { try { return new FitsDate(this.myHeader.getStringValue(DATE_OBS)).toDate(); } catch (FitsException e) { LOG.log(Level.SEVERE, "Unable to convert string to FITS observation date", e); return null; } } /** * Get the name of the person who acquired the data in this FITS file. * * @return either null or a String object */ public String getObserver() { return getTrimmedString(OBSERVER); } /** * Get the name of the organization which created this FITS file. * * @return either null or a String object */ public String getOrigin() { return getTrimmedString(ORIGIN); } public int getParameterCount() { return this.myHeader.getIntValue(PCOUNT, 0); } /** * Return the citation of a reference where the data associated with this * header are published. * * @return either null or a String object */ public String getReference() { return getTrimmedString(REFERENC); } @Override public long getSize() { int size = 0; if (this.myHeader != null) { size += this.myHeader.getSize(); } if (this.myData != null) { size += this.myData.getSize(); } return size; } /** * Get the name of the telescope which was used to acquire the data in this * FITS file. * * @return either null or a String object */ public String getTelescope() { return getTrimmedString(TELESCOP); } /** * Get the String value associated with keyword. * * @param keyword * the FITS keyword * @return either null or a String with leading/trailing blanks * stripped. */ public String getTrimmedString(String keyword) { String s = this.myHeader.getStringValue(keyword); if (s != null) { s = s.trim(); } return s; } /** * Get the String value associated with keyword. * * @param keyword * the FITS keyword * @return either null or a String with leading/trailing blanks * stripped. */ public String getTrimmedString(IFitsHeader keyword) { String s = this.myHeader.getStringValue(keyword); if (s != null) { s = s.trim(); } return s; } /** * Print out some information about this HDU. * * @param stream * the printstream to write the info on */ public abstract void info(PrintStream stream); @SuppressWarnings("unchecked") @Override public void read(ArrayDataInput stream) throws FitsException, IOException { this.myHeader = Header.readHeader(stream); this.myData = (DataClass) this.myHeader.makeData(); this.myData.read(stream); } @Override public boolean reset() { return this.myHeader.reset(); } @Override public void rewrite() throws FitsException, IOException { if (rewriteable()) { this.myHeader.rewrite(); this.myData.rewrite(); } else { throw new FitsException("Invalid attempt to rewrite HDU"); } } @Override public boolean rewriteable() { return this.myHeader.rewriteable() && this.myData.rewriteable(); } /** * Indicate that an HDU is the first element of a FITS file. * * @param newPrimary * value to set * @throws FitsException * if the operation failed */ void setPrimaryHDU(boolean newPrimary) throws FitsException { if (newPrimary && !canBePrimary()) { throw new FitsException("Invalid attempt to make HDU of type:" + this.getClass().getName() + " primary."); } else { this.isPrimary = newPrimary; } // Some FITS readers don't like the PCOUNT and GCOUNT keywords // in a primary array or they EXTEND keyword in extensions. if (this.isPrimary && !this.myHeader.getBooleanValue(GROUPS, false)) { this.myHeader.deleteKey(PCOUNT); this.myHeader.deleteKey(GCOUNT); } if (this.isPrimary) { HeaderCard card = this.myHeader.findCard(EXTEND); if (card == null) { getAxes(); // Leaves the iterator pointing to the last NAXISn // card. this.myHeader.nextCard(); this.myHeader.addValue(EXTEND, true); } } if (!this.isPrimary) { this.myHeader.iterator(); int pcount = this.myHeader.getIntValue(PCOUNT, 0); int gcount = this.myHeader.getIntValue(GCOUNT, 1); int naxis = this.myHeader.getIntValue(NAXIS, 0); this.myHeader.deleteKey(EXTEND); HeaderCard pcard = this.myHeader.findCard(PCOUNT); HeaderCard gcard = this.myHeader.findCard(GCOUNT); this.myHeader.getCard(2 + naxis); if (pcard == null) { this.myHeader.addValue(PCOUNT, pcount); } if (gcard == null) { this.myHeader.addValue(GCOUNT, gcount); } this.myHeader.iterator(); } } @Override public void write(ArrayDataOutput stream) throws FitsException { if (this.myHeader != null) { this.myHeader.write(stream); } if (this.myData != null) { this.myData.write(stream); } try { stream.flush(); } catch (IOException e) { throw new FitsException("Error flushing at end of HDU", e); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/BinaryTable.java000066400000000000000000001765751310063650500264620ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.NAXIS1; import static nom.tam.fits.header.Standard.NAXIS2; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.fits.header.Standard.TDIMn; import static nom.tam.fits.header.Standard.TFIELDS; import static nom.tam.fits.header.Standard.TFORMn; import static nom.tam.fits.header.Standard.THEAP; import static nom.tam.fits.header.Standard.XTENSION_BINTABLE; import java.io.EOFException; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import nom.tam.fits.header.IFitsHeader; import nom.tam.fits.header.Standard; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; import nom.tam.util.ColumnTable; import nom.tam.util.Cursor; import nom.tam.util.FitsIO; import nom.tam.util.RandomAccess; import nom.tam.util.TableException; import nom.tam.util.type.PrimitiveTypeHandler; /** * This class defines the methods for accessing FITS binary table data. */ public class BinaryTable extends AbstractTableData { /** * Collect all of the information we are using to describe a column into a * single object. */ protected static class ColumnDesc implements Cloneable { /** The size of the column in the type of the column */ private int size; /** The dimensions of the column (or just [1] if a scalar) */ private int[] dimens; /** The underlying class associated with the column. */ private Class base; /** * An example of the kind of data that should be read/written in one row */ private Object model; /** Is this a variable length column ? */ private boolean isVarying; /** * Is this a variable length column using longs? [Must have isVarying * true too] */ private boolean isLongVary; /** * Is this a complex column. Each entry will be associated with a * float[2]/double[2] */ private boolean isComplex; /** * Is this a string column. Strings will normally be converted to fixed * length byte arrays with the length given by the longest string. */ private boolean isString; /** * Is this a boolean column? Booleans are stored as bytes with the value * 'T'/'F' */ private boolean isBoolean; /** * The flattened column data. This should be nulled when the data is * copied into the ColumnTable */ private Object column; @Override public Object clone() { try { ColumnDesc copy = (ColumnDesc) super.clone(); if (getDimens() != null) { this.dimens = getDimens().clone(); } // Model should not be changed... return copy; } catch (CloneNotSupportedException e) { throw new IllegalStateException("ColumnDesc is not clonable, but it must be!", e); } } public Class getBase() { return this.base; } @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") public int[] getDimens() { return this.dimens; } /** * @return new instance of the array with space for the specified number * of rows. * @param nRow * the number of rows to allocate the array for */ public Object newInstance(int nRow) { return ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(this.model), this.size * nRow); } public int rowLen() { return this.size * PrimitiveTypeHandler.valueOf(this.base).size(); } /** * @return Is this a variable length column using longs? [Must have * isVarying true too] */ boolean isLongVary() { return this.isLongVary; } /** * @returnIs this a variable length column ? */ boolean isVarying() { return this.isVarying; } } /** Opaque state to pass to ColumnTable */ protected static class SaveState { private final List columns; private final FitsHeap heap; public SaveState(List columns, FitsHeap heap) { this.columns = columns; this.heap = heap; } } private static final long MAX_INTEGER_VALUE = Integer.MAX_VALUE; private static final int MAX_EMPTY_BLOCK_SIZE = 4000000; private static final Logger LOG = Logger.getLogger(BinaryTable.class.getName()); /** * This is the area in which variable length column data lives. */ private final FitsHeap heap; /** * The number of bytes between the end of the data and the heap */ private int heapOffset; /** * Switched to an initial value of true TAM, 11/20/12, since the heap may be * generated without any I/O. In that case it's valid. We set * heapReadFromStream to false when we skip input. */ private boolean heapReadFromStream = true; private boolean warnedOnVariableConversion = false; /** * A list describing each of the columns in the table */ private List columnList = new ArrayList(); /** * The number of rows in the table. */ private int nRow; /** * The length in bytes of each row. */ private int rowLen; /** * Where the data is actually stored. */ private ColumnTable table; /** * The stream used to input the data. This is saved so that we possibly skip * reading the data if the user doesn't wish to read all or parts of this * table. */ private ArrayDataInput currInput; /** * Create a null binary table data segment. */ public BinaryTable() { try { this.table = createColumnTable(new Object[0], new int[0]); } catch (TableException e) { throw new IllegalStateException("Impossible exception in BinaryTable() constructor", e); } this.heap = new FitsHeap(0); saveExtraState(); this.nRow = 0; this.rowLen = 0; } /** * Create a binary table from an existing ColumnTable * * @param tabIn * the column table to create the binary table from */ public BinaryTable(ColumnTable tabIn) { @SuppressWarnings("unchecked") ColumnTable tab = (ColumnTable) tabIn; // This will throw an error if this isn't the correct type. SaveState extra = tab.getExtraState(); this.columnList = new ArrayList(); for (ColumnDesc col : extra.columns) { ColumnDesc copy = (ColumnDesc) col.clone(); copy.column = null; this.columnList.add(copy); } try { this.table = tab.copy(); } catch (Exception e) { throw new IllegalStateException("Unexpected Exception", e); } this.heap = extra.heap.copy(); this.nRow = tab.getNRows(); saveExtraState(); } /** * Create a binary table from given header information. * * @param myHeader * A header describing what the binary table should look like. * @throws FitsException * if the specified header is not usable for a binary table */ public BinaryTable(Header myHeader) throws FitsException { long heapSizeL = myHeader.getLongValue(PCOUNT); long heapOffsetL = myHeader.getLongValue(THEAP); if (heapOffsetL > MAX_INTEGER_VALUE) { throw new FitsException("Heap Offset > 2GB"); } if (heapSizeL > MAX_INTEGER_VALUE) { throw new FitsException("Heap size > 2 GB"); } if (heapSizeL - heapOffsetL > MAX_INTEGER_VALUE) { throw new FitsException("Unable to allocate heap > 2GB"); } this.heapOffset = (int) heapOffsetL; int heapSize = (int) heapSizeL; int rwsz = myHeader.getIntValue(NAXIS1); this.nRow = myHeader.getIntValue(NAXIS2); // Subtract out the size of the regular table from // the heap offset. if (this.heapOffset > 0) { this.heapOffset -= this.nRow * rwsz; } if (this.heapOffset < 0 || this.heapOffset > heapSize) { throw new FitsException("Inconsistent THEAP and PCOUNT"); } this.heap = new FitsHeap(heapSize - this.heapOffset); int nCol = myHeader.getIntValue(TFIELDS); this.rowLen = 0; for (int col = 0; col < nCol; col++) { this.rowLen += processCol(myHeader, col); } HeaderCard card = myHeader.findCard(NAXIS1); card.setValue(String.valueOf(this.rowLen)); myHeader.updateLine(NAXIS1, card); } /** * Create a binary table from existing data in column order. * * @param o * array of columns * @throws FitsException * if the data for the columns could not be used as coulumns */ public BinaryTable(Object[] o) throws FitsException { this.heap = new FitsHeap(0); for (Object element : o) { addColumn(element); } createTable(); } /** * Create a binary table from existing data in row order. * * @param data * The data used to initialize the binary table. * @throws FitsException * if the data could not be converted to a binary table */ public BinaryTable(Object[][] data) throws FitsException { this(convertToColumns(data)); } /** * TODO: this is only for internal access! * * @param table * the table to create the column data. * @throws FitsException * if the data could not be created. */ public static void createColumnDataFor(BinaryTable table) throws FitsException { table.createTable(); } /** * Parse the TDIMS value. If the TDIMS value cannot be deciphered a one-d * array with the size given in arrsiz is returned. * * @param tdims * The value of the TDIMSn card. * @return An int array of the desired dimensions. Note that the order of * the tdims is the inverse of the order in the TDIMS key. */ public static int[] getTDims(String tdims) { // The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)" int[] dims = null; int first = tdims.indexOf('('); int last = tdims.lastIndexOf(')'); if (first >= 0 && last > first) { tdims = tdims.substring(first + 1, last - first); java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ","); int dim = st.countTokens(); if (dim > 0) { dims = new int[dim]; for (int i = dim - 1; i >= 0; i -= 1) { dims[i] = Integer.parseInt(st.nextToken().trim()); } } } return dims; } /** * Convert a two-d table to a table of columns. Handle String specially. * Every other element of data should be a primitive array of some * dimensionality. Basically the translates a table expressed as objects in * row order to a table with objects in column order. */ private static Object[] convertToColumns(Object[][] data) { Object[] row = data[0]; int nrow = data.length; Object[] results = new Object[row.length]; for (int col = 0; col < row.length; col++) { if (row[col] instanceof String) { String[] sa = new String[nrow]; for (int irow = 0; irow < nrow; irow++) { sa[irow] = (String) data[irow][col]; } results[col] = sa; } else { Class base = ArrayFuncs.getBaseClass(row[col]); int[] dims = ArrayFuncs.getDimensions(row[col]); if (dims.length > 1 || dims[0] > 1) { int[] xdims = new int[dims.length + 1]; xdims[0] = nrow; Object[] arr = (Object[]) ArrayFuncs.newInstance(base, xdims); for (int irow = 0; irow < nrow; irow++) { arr[irow] = data[irow][col]; } results[col] = arr; } else { Object arr = ArrayFuncs.newInstance(base, nrow); for (int irow = 0; irow < nrow; irow++) { System.arraycopy(data[irow][col], 0, arr, irow, 1); } results[col] = arr; } } } return results; } @Override public int addColumn(Object o) throws FitsException { int primeDim = Array.getLength(o); ColumnDesc added = new ColumnDesc(); this.columnList.add(added); // A varying length column is a two-d primitive // array where the second index is not constant. // We do not support Q types here, since Java // can't handle the long indices anyway... // This will probably change in some version of Java. if (isVarying(o)) { added.isVarying = true; added.dimens = new int[]{ 2 }; } if (isVaryingComp(o)) { added.isVarying = true; added.isComplex = true; added.dimens = new int[]{ 2 }; } // Flatten out everything but 1-D arrays and the // two-D arrays associated with variable length columns. if (!added.isVarying) { int[] allDim = ArrayFuncs.getDimensions(o); Class base = ArrayFuncs.getBaseClass(o); // Add a dimension for the length of Strings. if (base == String.class) { int[] xdim = new int[allDim.length + 1]; System.arraycopy(allDim, 0, xdim, 0, allDim.length); xdim[allDim.length] = -1; allDim = xdim; } if (allDim.length == 1) { added.dimens = new int[0]; } else { added.dimens = new int[allDim.length - 1]; System.arraycopy(allDim, 1, added.dimens, 0, allDim.length - 1); o = ArrayFuncs.flatten(o); } } addFlattenedColumn(o, added.dimens, true); if (this.nRow == 0 && this.columnList.size() == 1) { // Adding the first // column this.nRow = primeDim; } return this.columnList.size(); } /** * Add a column where the data is already flattened. * * @param o * The new column data. This should be a one-dimensional * primitive array. * @param dims * The dimensions of one row of the column. * @return the new column size * @throws FitsException * if the array could not be flattened */ public int addFlattenedColumn(Object o, int[] dims) throws FitsException { return addFlattenedColumn(o, dims, false); } @Override public int addRow(Object[] o) throws FitsException { ensureData(); if (this.columnList.size() == 0 && this.nRow == 0) { for (Object element : o) { if (element == null) { throw new FitsException("Cannot add initial rows with nulls"); } addColumn(encapsulate(element)); } createTable(); } else { Object[] flatRow = new Object[getNCols()]; for (int i = 0; i < getNCols(); i++) { Object x = ArrayFuncs.flatten(o[i]); ColumnDesc colDesc = this.columnList.get(i); flatRow[i] = arrayToColumn(colDesc, x); } this.table.addRow(flatRow); this.nRow++; } return this.nRow; } /** * Delete a set of columns. Note that this does not fix the header, so users * should normally call the routine in TableHDU. * @throws FitsException if * the operation failed */ @Override public void deleteColumns(int start, int len) throws FitsException { ensureData(); try { this.table.deleteColumns(start, len); // Need to get rid of the column level metadata. for (int i = start + len - 1; i >= start; i -= 1) { if (i >= 0 && i <= this.columnList.size()) { ColumnDesc columnDesc = this.columnList.get(i); this.rowLen -= columnDesc.rowLen(); this.columnList.remove(i); } } } catch (Exception e) { throw new FitsException("Error deleting columns from BinaryTable", e); } } /** * Delete rows from a table. * * @param row * The 0-indexed start of the rows to be deleted. * @param len * The number of rows to be deleted. * @throws FitsException if * the operation failed */ @Override public void deleteRows(int row, int len) throws FitsException { ensureData(); this.table.deleteRows(row, len); this.nRow -= len; } /** * Update a FITS header to reflect the current state of the data. * @throws * FitsException if the operation failed */ @Override public void fillHeader(Header h) throws FitsException { try { Standard.context(BinaryTable.class); h.setXtension(XTENSION_BINTABLE); h.setBitpix(BasicHDU.BITPIX_BYTE); h.setNaxes(2); h.setNaxis(1, this.rowLen); h.setNaxis(2, this.nRow); h.addValue(PCOUNT, this.heap.size()); h.addValue(GCOUNT, 1); Cursor iter = h.iterator(); iter.setKey(GCOUNT.key()); iter.next(); iter.add(new HeaderCard(TFIELDS.key(), this.columnList.size(), TFIELDS.comment())); for (int i = 0; i < this.columnList.size(); i++) { if (i > 0) { h.positionAfterIndex(TFORMn, i); } fillForColumn(h, i, iter); } } finally { Standard.context(null); } } /** * @return the types in the table, not the underlying types (e.g., for * varying length arrays or booleans). */ public Class[] getBases() { return this.table.getBases(); } /** * Get a given column * * @param col * The index of the column. * @throws FitsException if the * operation failed */ @Override public Object getColumn(int col) throws FitsException { ensureData(); Object res = getFlattenedColumn(col); res = encurl(res, col, this.nRow); return res; } @Override public ColumnTable getData() throws FitsException { if (this.table == null) { if (this.currInput == null) { throw new FitsException("Cannot find input for deferred read"); } this.table = createTable(); long currentOffset = FitsUtil.findOffset(this.currInput); FitsUtil.reposition(this.currInput, this.fileOffset); readTrueData(this.input); FitsUtil.reposition(this.currInput, currentOffset); } return this.table; } public int[][] getDimens() { int[][] dimens = new int[this.columnList.size()][]; for (int i = 0; i < dimens.length; i++) { dimens[i] = this.columnList.get(i).dimens; } return dimens; } /** * Get a particular element from the table. * * @param i * The row of the element. * @param j * The column of the element. * @throws FitsException if the * operation failed */ @Override public Object getElement(int i, int j) throws FitsException { if (!validRow(i) || !validColumn(j)) { throw new FitsException("No such element (" + i + "," + j + ")"); } ColumnDesc colDesc = this.columnList.get(j); Object ele; if (colDesc.isVarying) { // Have to read in entire data set. ensureData(); } if (this.table == null) { // This is really inefficient. // Need to either save the row, or just read the one element. Object[] row = getRow(i); ele = row[j]; } else { ele = this.table.getElement(i, j); ele = columnToArray(colDesc, ele, 1); ele = encurl(ele, j, 1); if (ele instanceof Object[]) { ele = ((Object[]) ele)[0]; } } return ele; } public Object[] getFlatColumns() { ensureDataSilent(); return this.table.getColumns(); } /** * @return column in flattened format. For large tables getting a column in * standard format can be inefficient because a separate object is * needed for each row. Leaving the data in flattened format means * that only a single object is created. * @param col * the column to flatten * @throws FitsException * if the column could not be flattened */ public Object getFlattenedColumn(int col) throws FitsException { ensureData(); if (!validColumn(col)) { throw new FitsException("Invalid column"); } Object res = this.table.getColumn(col); ColumnDesc colDesc = this.columnList.get(col); return columnToArray(colDesc, res, this.nRow); } /** * @return the offset to the heap */ public int getHeapOffset() { return this.heapOffset; } /** * @return the size of the heap -- including the offset from the end of the * table data. */ public int getHeapSize() { return this.heapOffset + this.heap.size(); } /** * @return a row that may be used for direct i/o to the table. */ public Object[] getModelRow() { Object[] modelRow = new Object[this.columnList.size()]; for (int i = 0; i < modelRow.length; i++) { modelRow[i] = this.columnList.get(i).model; } return modelRow; } /** * Get the number of columns in the table. */ @Override public int getNCols() { return this.columnList.size(); } /** * Get the number of rows in the table */ @Override public int getNRows() { return this.nRow; } /** * @return a particular element from the table but do no processing of this * element (e.g., dimension conversion or extraction of variable * length array elements/) * @param i * The row of the element. * @param j * The column of the element. * @throws FitsException * if the operation failed */ public Object getRawElement(int i, int j) throws FitsException { ensureData(); return this.table.getElement(i, j); } /** * Get a given row * * @param row * The index of the row to be returned. * @return A row of data. * @throws FitsException if the operation failed */ @Override public Object[] getRow(int row) throws FitsException { if (!validRow(row)) { throw new FitsException("Invalid row"); } Object[] res; if (this.table != null) { res = getMemoryRow(row); } else { res = getFileRow(row); } return res; } public int[] getSizes() { int[] sizes = new int[this.columnList.size()]; for (int i = 0; i < sizes.length; i++) { sizes[i] = this.columnList.get(i).size; } return sizes; } /** * Get the size of the data in the HDU sans padding. */ @Override public long getTrueSize() { long len = (long) this.nRow * this.rowLen; if (this.heap.size() > 0) { len += this.heap.size() + this.heapOffset; } return len; } public char[] getTypes() { ensureDataSilent(); return this.table.getTypes(); } /** * Read the data -- or defer reading on random access. * @throws * FitsException if the operation failed */ @Override public void read(ArrayDataInput i) throws FitsException { setFileOffset(i); this.currInput = i; if (i instanceof RandomAccess) { try { i.skipAllBytes(getTrueSize()); this.heapReadFromStream = false; } catch (IOException e) { throw new FitsException("Unable to skip binary table HDU:" + e, e); } try { i.skipAllBytes(FitsUtil.padding(getTrueSize())); } catch (EOFException e) { throw new PaddingException("Missing padding after binary table", this, e); } catch (IOException e) { throw new FitsException("Error skipping padding after binary table", e); } } else { /** * Read the data associated with the HDU including the hash area if * present. * * @param i * The input stream */ if (this.table == null) { this.table = createTable(); } readTrueData(i); } } /** * Replace a column in the table. * * @param col * The index of the column to be replaced. * @param xcol * The new data for the column * @throws FitsException * Thrown if the data does not match the current column * description. */ @Override public void setColumn(int col, Object xcol) throws FitsException { ColumnDesc colDesc = this.columnList.get(col); xcol = arrayToColumn(colDesc, xcol); xcol = ArrayFuncs.flatten(xcol); setFlattenedColumn(col, xcol); } /** * Replace a single element within the table. * * @param i * The row of the data. * @param j * The column of the data. * @param o * The replacement data. * @throws FitsException if the operation * failed */ @Override public void setElement(int i, int j, Object o) throws FitsException { ensureData(); ColumnDesc colDesc = this.columnList.get(j); if (colDesc.isVarying) { int size = Array.getLength(o); // The offset for the row is the offset to the heap plus the // offset within the heap. int offset = (int) this.heap.getSize(); this.heap.putData(o); if (colDesc.isLongVary) { this.table.setElement(i, j, new long[]{ size, offset }); } else { this.table.setElement(i, j, new int[]{ size, offset }); } } else { this.table.setElement(i, j, ArrayFuncs.flatten(o)); } } /** * Set a column with the data already flattened. * * @param col * The index of the column to be replaced. * @param data * The new data array. This should be a one-d primitive array. * @throws FitsException * Thrown if the type of length of the replacement data differs * from the original. */ public void setFlattenedColumn(int col, Object data) throws FitsException { ensureData(); Object oldCol = this.table.getColumn(col); if (data.getClass() != oldCol.getClass() || Array.getLength(data) != Array.getLength(oldCol)) { throw new FitsException("Replacement column mismatch at column:" + col); } this.table.setColumn(col, data); } /** * Replace a row in the table. * * @param row * The index of the row to be replaced. * @param data * The new values for the row. * @throws FitsException * Thrown if the new row cannot match the existing data. */ @Override public void setRow(int row, Object[] data) throws FitsException { ensureData(); if (data.length != getNCols()) { throw new FitsException("Updated row size does not agree with table"); } Object[] ydata = new Object[data.length]; for (int col = 0; col < data.length; col++) { Object o = ArrayFuncs.flatten(data[col]); ColumnDesc colDesc = this.columnList.get(col); ydata[col] = arrayToColumn(colDesc, o); } this.table.setRow(row, ydata); } /** * Update the header after a deletion. * * @throws FitsException * if the operation failed */ @Override public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException { hdr.addValue(NAXIS1, this.rowLen); } /** * Write the table, heap and padding. * @throws FitsException if the * operation failed */ @Override public void write(ArrayDataOutput os) throws FitsException { ensureData(); try { this.table.write(os); if (this.heapOffset > 0) { int off = this.heapOffset; // Minimize memory usage. This also accommodates // the possibility that heapOffset > 2GB. // Previous code might have allocated up to 2GB // array. [In practice this is always going // to be really small though...] int arrSiz = MAX_EMPTY_BLOCK_SIZE; while (off > 0) { if (arrSiz > off) { arrSiz = off; } os.write(new byte[arrSiz]); off -= arrSiz; } } // Now check if we need to write the heap if (this.heap.size() > 0) { this.heap.write(os); } FitsUtil.pad(os, getTrueSize()); } catch (IOException e) { throw new FitsException("Unable to write table:" + e, e); } } /** * Convert the external representation to the BinaryTable representation. * Transformation include boolean -> T/F, Strings -> byte arrays, variable * length arrays -> pointers (after writing data to heap). * * @throws FitsException * if the operation failed */ private Object arrayToColumn(ColumnDesc added, Object o) throws FitsException { if (!added.isVarying && !added.isBoolean && !added.isComplex && !added.isString) { return o; } if (!added.isVarying) { if (added.isString) { // Convert strings to array of bytes. int[] dims = added.dimens; // Set the length of the string if we are just adding the // column. if (dims[dims.length - 1] < 0) { dims[dims.length - 1] = FitsUtil.maxLength((String[]) o); } if (o instanceof String) { o = new String[]{ (String) o }; } o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]); } else if (added.isBoolean) { // Convert true/false to 'T'/'F' o = FitsUtil.booleanToByte((boolean[]) o); } } else { if (added.isBoolean) { // Handle addRow/addElement if (o instanceof boolean[]) { o = new boolean[][]{ (boolean[]) o }; } // Convert boolean to byte arrays boolean[][] to = (boolean[][]) o; byte[][] xo = new byte[to.length][]; for (int i = 0; i < to.length; i++) { xo[i] = FitsUtil.booleanToByte(to[i]); } o = xo; } // Write all rows of data onto the heap. int offset = this.heap.putData(o); int blen = ArrayFuncs.getBaseLength(o); // Handle an addRow of a variable length element. // In this case we only get a one-d array, but we just // make is 1 x n to get the second dimension. if (!(o instanceof Object[])) { o = new Object[]{ o }; } // Create the array descriptors int nrow = Array.getLength(o); int factor = 1; if (added.isComplex) { factor = 2; } if (added.isLongVary) { long[] descrip = new long[2 * nrow]; Object[] x = (Object[]) o; // Fill the descriptor for each row. for (int i = 0; i < nrow; i++) { int len = Array.getLength(x[i]); descrip[2 * i] = len; descrip[2 * i + 1] = offset; offset += len * blen * factor; } o = descrip; } else { int[] descrip = new int[2 * nrow]; Object[] x = (Object[]) o; // Fill the descriptor for each row. for (int i = 0; i < nrow; i++) { int len = Array.getLength(x[i]); descrip[2 * i] = len; descrip[2 * i + 1] = offset; offset += len * blen * factor; } o = descrip; } } return o; } // Check if this is consistent with a varying // complex row. That requires // The second index varies. // The third index is 2 whenever the second // index is non-zero. // This function will fail if nulls are encountered. private boolean checkCompVary(float[][][] o) { boolean varying = false; int len0 = o[0].length; for (float[][] element : o) { if (element.length != len0) { varying = true; } if (element.length > 0) { for (float[] element2 : element) { if (element2.length != 2) { return false; } } } } return varying; } private boolean checkDCompVary(double[][][] o) { boolean varying = false; int len0 = o[0].length; for (double[][] element : o) { if (element.length != len0) { varying = true; } if (element.length > 0) { for (double[] element2 : element) { if (element2.length != 2) { return false; } } } } return varying; } /** * Convert data from binary table representation to external Java * representation. * @throws FitsException if the operation failed */ private Object columnToArray(ColumnDesc colDesc, Object o, int rows) throws FitsException { // Most of the time we need do nothing! if (!colDesc.isVarying && !colDesc.isBoolean && !colDesc.isString && !colDesc.isComplex) { return o; } // If a varying length column use the descriptors to // extract appropriate information from the headers. if (colDesc.isVarying) { // A. Kovacs (4/1/08) // Ensure that the heap has been initialized if (!this.heapReadFromStream) { readHeap(this.currInput); } int[] descrip; if (colDesc.isLongVary) { // Convert longs to int's. This is dangerous. if (!this.warnedOnVariableConversion) { LOG.log(Level.WARNING, "Warning: converting long variable array pointers to int's"); this.warnedOnVariableConversion = true; } descrip = (int[]) ArrayFuncs.convertArray(o, int.class); } else { descrip = (int[]) o; } int nrow = descrip.length / 2; Object[] res; // Res will be the result of extracting from the heap. int[] dims; // Used to create result arrays. if (colDesc.isComplex) { // Complex columns have an extra dimension for each row dims = new int[]{ nrow, 0, 0 }; res = (Object[]) ArrayFuncs.newInstance(colDesc.base, dims); // Set up dims for individual rows. dims = new int[2]; dims[1] = 2; // ---> Added clause by Attila Kovacs (13 July 2007) // String columns have to read data into a byte array at first // then do the string conversion later. } else if (colDesc.isString) { dims = new int[]{ nrow, 0 }; res = (Object[]) ArrayFuncs.newInstance(byte.class, dims); } else { // Non-complex data has a simple primitive array for each row dims = new int[]{ nrow, 0 }; res = (Object[]) ArrayFuncs.newInstance(colDesc.base, dims); } // Now read in each requested row. for (int i = 0; i < nrow; i++) { Object row; int offset = descrip[2 * i + 1]; int dim = descrip[2 * i]; if (colDesc.isComplex) { dims[0] = dim; row = ArrayFuncs.newInstance(colDesc.base, dims); // ---> Added clause by Attila Kovacs (13 July 2007) // Again, String entries read data into a byte array at // first // then do the string conversion later. } else if (colDesc.isString) { // For string data, we need to read bytes and convert // to strings row = ArrayFuncs.newInstance(byte.class, dim); } else if (colDesc.isBoolean) { // For boolean data, we need to read bytes and convert // to booleans. row = ArrayFuncs.newInstance(byte.class, dim); } else { row = ArrayFuncs.newInstance(colDesc.base, dim); } this.heap.getData(offset, row); // Now do the boolean conversion. if (colDesc.isBoolean) { row = FitsUtil.byteToBoolean((byte[]) row); } res[i] = row; } o = res; } else { // Fixed length columns // Need to convert String byte arrays to appropriate Strings. if (colDesc.isString) { int[] dims = colDesc.dimens; byte[] bytes = (byte[]) o; if (bytes.length > 0) { if (dims.length > 0) { o = FitsUtil.byteArrayToStrings(bytes, dims[dims.length - 1]); } else { o = FitsUtil.byteArrayToStrings(bytes, 1); } } else { // This probably fails for multidimensional arrays of // strings where // all elements are null. String[] str = new String[rows]; for (int i = 0; i < str.length; i++) { str[i] = ""; } o = str; } } else if (colDesc.isBoolean) { o = FitsUtil.byteToBoolean((byte[]) o); } } return o; } /** * Create a column table given the number of rows and a model row. This is * used when we defer instantiation of the ColumnTable until the user * requests data from the table. * @throws FitsException if the operation * failed */ private ColumnTable createTable() throws FitsException { int nfields = this.columnList.size(); Object[] arrCol = new Object[nfields]; int[] sizes = new int[nfields]; for (int i = 0; i < nfields; i++) { ColumnDesc desc = this.columnList.get(i); sizes[i] = desc.size; if (desc.column != null) { arrCol[i] = desc.column; desc.column = null; } else { arrCol[i] = desc.newInstance(this.nRow); } } this.table = createColumnTable(arrCol, sizes); saveExtraState(); return this.table; } private Object encapsulate(Object o) { if (o.getClass().isArray() && ArrayFuncs.getDimensions(o).length == 1 && ArrayFuncs.getDimensions(o)[0] == 1) { return o; } Object[] array = (Object[]) Array.newInstance(o.getClass(), 1); array[0] = o; return array; } private Object encurl(Object res, int col, int rows) { ColumnDesc colDesc = this.columnList.get(col); if (colDesc.base != String.class) { if (!colDesc.isVarying && colDesc.dimens.length > 0) { int[] dims = new int[colDesc.dimens.length + 1]; System.arraycopy(colDesc.dimens, 0, dims, 1, colDesc.dimens.length); dims[0] = rows; res = ArrayFuncs.curl(res, dims); } } else { // Handle Strings. Remember the last element // in dimens is the length of the Strings and // we already used that when we converted from // byte arrays to strings. So we need to ignore // the last element of dimens, and add the row count // at the beginning to curl. if (colDesc.dimens.length > 1) { int[] dims = new int[colDesc.dimens.length]; System.arraycopy(colDesc.dimens, 0, dims, 1, colDesc.dimens.length - 1); dims[0] = rows; res = ArrayFuncs.curl(res, dims); } } return res; } private void ensureData() throws FitsException { getData(); } private void ensureDataSilent() { try { getData(); } catch (Exception e) { BinaryTable.LOG.log(Level.SEVERE, "reading data of binary table failed!", e); } } /** * @return row from the file. * @throws FitsException * if the operation failed */ private Object[] getFileRow(int row) throws FitsException { /** * Read the row from memory */ Object[] data = new Object[this.columnList.size()]; for (int col = 0; col < data.length; col++) { ColumnDesc colDesc = this.columnList.get(col); data[col] = colDesc.newInstance(1); } try { FitsUtil.reposition(this.currInput, this.fileOffset + (long) row * (long) this.rowLen); this.currInput.readLArray(data); } catch (IOException e) { throw new FitsException("Error in deferred row read", e); } for (int col = 0; col < data.length; col++) { ColumnDesc colDesc = this.columnList.get(col); data[col] = columnToArray(colDesc, data[col], 1); data[col] = encurl(data[col], col, 1); if (data[col] instanceof Object[]) { data[col] = ((Object[]) data[col])[0]; } } return data; } /** * Get a row from memory. * @throws FitsException if the operation failed */ private Object[] getMemoryRow(int row) throws FitsException { Object[] modelRow = getModelRow(); Object[] data = new Object[modelRow.length]; for (int col = 0; col < modelRow.length; col++) { ColumnDesc colDesc = this.columnList.get(col); Object o = this.table.getElement(row, col); o = columnToArray(colDesc, o, 1); data[col] = encurl(o, col, 1); if (data[col] instanceof Object[]) { data[col] = ((Object[]) data[col])[0]; } } return data; } /** * Get an unsigned number at the beginning of a string */ private int initialNumber(String tform) { int i; for (i = 0; i < tform.length(); i++) { if (!Character.isDigit(tform.charAt(i))) { break; } } return Integer.parseInt(tform.substring(0, i)); } /** * Is this a variable length column? It is if it's a two-d primitive array * and the second dimension is not constant. It may also be a 3-d array of * type float or double where the last index is always 2 (when the second * index is non-zero). In this case it can be a complex varying column. */ private boolean isVarying(Object o) { if (o == null || // !o.getClass().isArray() || // !o.getClass().getComponentType().isArray() || // !o.getClass().getComponentType().getComponentType().isPrimitive()) { return false; } int oLength = Array.getLength(o); if (oLength < 2) { return false; } int flen = Array.getLength(Array.get(o, 0)); for (int i = 1; i < oLength; i++) { if (Array.getLength(Array.get(o, i)) != flen) { return true; } } return false; } private boolean isVaryingComp(Object o) { if (o instanceof float[][][]) { return checkCompVary((float[][][]) o); } else if (o instanceof double[][][]) { return checkDCompVary((double[][][]) o); } return false; } /** * Process one column from a FITS Header. * @throws FitsException if the * operation failed */ private int processCol(Header header, int col) throws FitsException { String tform = header.getStringValue(TFORMn.n(col + 1)); if (tform == null) { throw new FitsException("Attempt to process column " + (col + 1) + " but no TFORMn found."); } tform = tform.trim(); ColumnDesc colDesc = new ColumnDesc(); String tdims = header.getStringValue(TDIMn.n(col + 1)); if (tdims != null) { tdims = tdims.trim(); } char type = getTFORMType(tform); if (type == 'P' || type == 'Q') { colDesc.isVarying = true; colDesc.isLongVary = type == 'Q'; type = getTFORMVarType(tform); } int size = getTFORMLength(tform); // Handle the special size cases. // // Bit arrays (8 bits fit in a byte) if (type == 'X') { size = (size + FitsIO.BITS_OF_1_BYTE - 1) / FitsIO.BITS_OF_1_BYTE; // Variable length arrays always have a two-element pointer (offset // and size) } else if (colDesc.isVarying) { size = 2; } // bSize is the number of bytes in the field. int bSize = size; int[] dims = null; // Cannot really handle arbitrary arrays of bits. if (tdims != null && type != 'X' && !colDesc.isVarying) { dims = getTDims(tdims); } if (dims == null) { if (size == 1) { dims = new int[0]; // Marks this as a scalar column } else { dims = new int[]{ size }; } } colDesc.isComplex = type == 'C' || type == 'M'; Class colBase; switch (type) { case 'A': colBase = byte.class; colDesc.isString = true; colDesc.base = String.class; break; case 'L': colBase = byte.class; colDesc.base = boolean.class; colDesc.isBoolean = true; break; case 'X': case 'B': colBase = byte.class; colDesc.base = byte.class; break; case 'I': colBase = short.class; colDesc.base = short.class; bSize *= FitsIO.BYTES_IN_SHORT; break; case 'J': colBase = int.class; colDesc.base = int.class; bSize *= FitsIO.BYTES_IN_INTEGER; break; case 'K': colBase = long.class; colDesc.base = long.class; bSize *= FitsIO.BYTES_IN_LONG; break; case 'E': case 'C': colBase = float.class; colDesc.base = float.class; bSize *= FitsIO.BYTES_IN_FLOAT; break; case 'D': case 'M': colBase = double.class; colDesc.base = double.class; bSize *= FitsIO.BYTES_IN_DOUBLE; break; default: throw new FitsException("Invalid type in column:" + col); } if (colDesc.isVarying) { dims = new int[]{ 2 }; colBase = int.class; bSize = FitsIO.BYTES_IN_INTEGER * 2; if (colDesc.isLongVary) { colBase = long.class; bSize = FitsIO.BYTES_IN_LONG * 2; } } if (!colDesc.isVarying && colDesc.isComplex) { int[] xdims = new int[dims.length + 1]; System.arraycopy(dims, 0, xdims, 0, dims.length); xdims[dims.length] = 2; dims = xdims; bSize *= 2; size *= 2; } colDesc.model = ArrayFuncs.newInstance(colBase, dims); colDesc.dimens = dims; colDesc.size = size; this.columnList.add(colDesc); return bSize; } private void saveExtraState() { this.table.setExtraState(new SaveState(this.columnList, this.heap)); } protected void addByteVaryingColumn() throws TableException { ColumnDesc added = new ColumnDesc(); this.columnList.add(added); added.isVarying = true; added.isLongVary = true; added.dimens = new int[]{ 2 }; added.size = 2; added.base = byte.class; added.isBoolean = false; added.isString = false; added.model = new long[2]; this.rowLen += FitsIO.BYTES_IN_LONG * 2; added.column = new long[this.table.getNRows() * 2]; this.table.addColumn(added.column, added.size); } protected ColumnTable createColumnTable(Object[] arrCol, int[] sizes) throws TableException { return new ColumnTable(arrCol, sizes); } /** * Read the heap which contains the data for variable length arrays. A. * Kovacs (4/1/08) Separated heap reading, s.t. the heap can be properly * initialized even if in deferred read mode. columnToArray() checks and * initializes the heap as necessary. * * @param input * stream to read from. * @throws FitsException * if the heap could not be read from the stream */ protected void readHeap(ArrayDataInput input) throws FitsException { FitsUtil.reposition(input, this.fileOffset + this.nRow * this.rowLen + this.heapOffset); this.heap.read(input); this.heapReadFromStream = true; } /** * Read table, heap and padding * * @param i * the stream to read the data from. * @throws FitsException * if the reading failed */ protected void readTrueData(ArrayDataInput i) throws FitsException { try { this.table.read(i); i.skipAllBytes(this.heapOffset); this.heap.read(i); this.heapReadFromStream = true; } catch (IOException e) { throw new FitsException("Error reading binary table data:" + e, e); } try { i.skipAllBytes(FitsUtil.padding(getTrueSize())); } catch (EOFException e) { throw new PaddingException("Error skipping padding after binary table", this, e); } catch (IOException e) { throw new FitsException("Error reading binary table data padding", e); } } /** * Check if the column number is valid. * * @param j * The Java index (first=0) of the column to check. * @return true if the column is valid */ protected boolean validColumn(int j) { return j >= 0 && j < getNCols(); } /** * Check to see if this is a valid row. * * @param i * The Java index (first=0) of the row to check. * @return true if the row is valid */ protected boolean validRow(int i) { return getNRows() > 0 && i >= 0 && i < getNRows(); } /** * This function is needed since we had made addFlattenedColumn public so in * principle a user might have called it directly. * * @param o * The new column data. This should be a one-dimensional * primitive array. * @param dims * The dimensions of one row of the column. * @param allocated * is it already in the columnList? * @return the new column size * @throws FitsException */ int addFlattenedColumn(Object o, int[] dims, boolean allocated) throws FitsException { ColumnDesc added; if (!allocated) { added = new ColumnDesc(); added.dimens = dims; } else { added = this.columnList.get(this.columnList.size() - 1); } added.base = ArrayFuncs.getBaseClass(o); added.isBoolean = added.base == boolean.class; added.isString = added.base == String.class; // Convert to column first in case // this is a String or variable length array. o = arrayToColumn(added, o); int size = 1; for (int dim2 : dims) { size *= dim2; } added.size = size; // Check that the number of rows is consistent. if (size != 0 && this.columnList.size() > 1) { int xRow = Array.getLength(o) / size; if (xRow > 0 && xRow != this.nRow) { throw new FitsException("Added column does not have correct row count"); } } if (!added.isVarying) { added.model = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims); this.rowLen += size * ArrayFuncs.getBaseLength(o); } else { if (added.isLongVary) { added.model = new long[2]; this.rowLen += FitsIO.BYTES_IN_LONG * 2; } else { added.model = new int[2]; this.rowLen += FitsIO.BYTES_IN_INTEGER * 2; } } // Only add to table if table already exists or if we // are filling up the last element in columns. // This way if we allocate a bunch of columns at the beginning // we only create the column table after we have all the columns // ready. added.column = o; if (this.table != null) { this.table.addColumn(o, added.size); } if (!this.columnList.contains(added)) { this.columnList.add(added); } return this.columnList.size(); } /** * Update the header to reflect the details of a given column. * @throws * FitsException if the operation failed */ void fillForColumn(Header h, int col, Cursor iter) throws FitsException { ColumnDesc colDesc = this.columnList.get(col); String tform; if (colDesc.isVarying) { if (colDesc.isLongVary) { tform = "1Q"; } else { tform = "1P"; } } else { tform = Integer.toString(colDesc.size); } if (colDesc.base == int.class) { tform += "J"; } else if (colDesc.base == short.class || colDesc.base == char.class) { tform += "I"; } else if (colDesc.base == byte.class) { tform += "B"; } else if (colDesc.base == float.class) { if (colDesc.isComplex) { tform += "C"; } else { tform += "E"; } } else if (colDesc.base == double.class) { if (colDesc.isComplex) { tform += "M"; } else { tform += "D"; } } else if (colDesc.base == long.class) { tform += "K"; } else if (colDesc.base == boolean.class) { tform += "L"; } else if (colDesc.base == String.class) { tform += "A"; } else { throw new FitsException("Invalid column data class:" + colDesc.base); } IFitsHeader key = TFORMn.n(col + 1); iter.add(new HeaderCard(key.key(), tform, key.comment())); if (colDesc.dimens.length > 0 && !colDesc.isVarying) { StringBuffer tdim = new StringBuffer(); char comma = '('; for (int i = colDesc.dimens.length - 1; i >= 0; i -= 1) { tdim.append(comma); tdim.append(colDesc.dimens[i]); comma = ','; } tdim.append(')'); key = TDIMn.n(col + 1); iter.add(new HeaderCard(key.key(), tdim.toString(), key.comment())); } } ColumnDesc getDescriptor(int column) { return this.columnList.get(column); } /** * Get the explicit or implied length of the TFORM field */ int getTFORMLength(String tform) { tform = tform.trim(); if (Character.isDigit(tform.charAt(0))) { return initialNumber(tform); } else { return 1; } } /** * Get the type in the TFORM field */ char getTFORMType(String tform) { for (int i = 0; i < tform.length(); i++) { if (!Character.isDigit(tform.charAt(i))) { return tform.charAt(i); } } return 0; } /** * Get the type in a varying length column TFORM */ char getTFORMVarType(String tform) { int ind = tform.indexOf("P"); if (ind < 0) { ind = tform.indexOf("Q"); } if (tform.length() > ind + 1) { return tform.charAt(ind + 1); } else { return 0; } } /** * Update the header to reflect information about a given column. This * routine tries to ensure that the Header is organized by column. * @throws * FitsException if the operation failed */ void pointToColumn(int col, Header hdr) throws FitsException { Cursor iter = hdr.iterator(); if (col > 0) { hdr.positionAfterIndex(TFORMn, col); } fillForColumn(hdr, col, iter); } /** * Convert a column from float/double to float complex/double complex. This * is only possible for certain columns. The return status indicates if the * conversion is possible. * * @param index * The 0-based index of the column to be reset. * @return Whether the conversion is possible. * @throws FitsException if * the operation failed */ boolean setComplexColumn(int index) throws FitsException { // Currently there is almost no change required to the BinaryTable // object itself when we convert an eligible column to complex, since // the internal // representation of the data is unchanged. We just need // to set the flag that the column is complex. // Check that the index is valid, // the data type is float or double // the most rapidly changing index in the array has dimension 2. if (index >= 0 && index < this.columnList.size()) { ColumnDesc colDesc = this.columnList.get(index); if (colDesc.isComplex) { return true; } if ((colDesc.base == float.class || colDesc.base == double.class) && colDesc.dimens[colDesc.dimens.length - 1] == 2) { if (colDesc.isVarying) { // We need to make sure that for every row, there are // an even number of elements so that we can // convert to an integral number of complex numbers. Object col = getFlattenedColumn(index); if (col instanceof int[]) { int[] ptrs = (int[]) col; for (int i = 1; i < ptrs.length; i += 2) { if (ptrs[i] % 2 != 0) { return false; } } } else { long[] ptrs = (long[]) col; for (int i = 1; i < ptrs.length; i++) { if (ptrs[i] % 2 != 0) { return false; } } } } // Set the column to complex colDesc.isComplex = true; return true; } } return false; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/BinaryTableHDU.java000066400000000000000000000261121310063650500270000ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.NAXIS1; import static nom.tam.fits.header.Standard.NAXIS2; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.fits.header.Standard.TDIMn; import static nom.tam.fits.header.Standard.TDISPn; import static nom.tam.fits.header.Standard.TFIELDS; import static nom.tam.fits.header.Standard.TFORMn; import static nom.tam.fits.header.Standard.THEAP; import static nom.tam.fits.header.Standard.TNULLn; import static nom.tam.fits.header.Standard.TSCALn; import static nom.tam.fits.header.Standard.TTYPEn; import static nom.tam.fits.header.Standard.TUNITn; import static nom.tam.fits.header.Standard.TZEROn; import static nom.tam.fits.header.Standard.XTENSION; import static nom.tam.fits.header.Standard.XTENSION_BINTABLE; import java.io.PrintStream; import nom.tam.fits.header.IFitsHeader; import nom.tam.fits.header.Standard; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; /** FITS binary table header/data unit */ public class BinaryTableHDU extends TableHDU { /** The standard column keywords for a binary table. */ private static final IFitsHeader[] KEY_STEMS = { TTYPEn, TFORMn, TUNITn, TNULLn, TSCALn, TZEROn, TDISPn, TDIMn }; public BinaryTableHDU(Header hdr, BinaryTable datum) { super(hdr, datum); } /** * @return Encapsulate data in a BinaryTable data type * @param o * data to encapsulate * @throws FitsException * if the type of the data is not usable as data */ public static BinaryTable encapsulate(Object o) throws FitsException { if (o instanceof nom.tam.util.ColumnTable) { return new BinaryTable((nom.tam.util.ColumnTable) o); } else if (o instanceof Object[][]) { return new BinaryTable((Object[][]) o); } else if (o instanceof Object[]) { return new BinaryTable((Object[]) o); } else { throw new FitsException("Unable to encapsulate object of type:" + o.getClass().getName() + " as BinaryTable"); } } /* * Check if this data object is consistent with a binary table. There are * three options: a column table object, an Object[][], or an Object[]. This * routine doesn't check that the dimensions of arrays are properly * consistent. */ public static boolean isData(Object o) { return o instanceof nom.tam.util.ColumnTable || o instanceof Object[][] || o instanceof Object[]; } /** * Check that this is a valid binary table header. * * @param header * to validate. * @return true if this is a binary table header. */ public static boolean isHeader(Header header) { String xten = header.getStringValue(XTENSION); if (xten == null) { return false; } xten = xten.trim(); return xten.equals(XTENSION_BINTABLE) || xten.equals("A3DTABLE"); } /** * @return a new created data from a binary table header. * @param header * the template specifying the binary table. * @throws FitsException * if there was a problem with the header. */ public static BinaryTable manufactureData(Header header) throws FitsException { return new BinaryTable(header); } /** * @return a newly created binary table HDU from the supplied data. * @param data * the data used to build the binary table. This is typically * some kind of array of objects. * @throws FitsException * if there was a problem with the data. */ public static Header manufactureHeader(Data data) throws FitsException { Header hdr = new Header(); data.fillHeader(hdr); return hdr; } @Override public int addColumn(Object data) throws FitsException { this.myData.addColumn(data); this.myData.pointToColumn(getNCols() - 1, this.myHeader); return super.addColumn(data); } protected static IFitsHeader[] binaryTableColumnKeyStems() { return KEY_STEMS; } /** * What are the standard column stems for a binary table? */ @Override protected IFitsHeader[] columnKeyStems() { return BinaryTableHDU.KEY_STEMS; } /** * Print out some information about this HDU. */ @Override public void info(PrintStream stream) { BinaryTable myData = this.myData; stream.println(" Binary Table"); stream.println(" Header Information:"); int nhcol = this.myHeader.getIntValue(TFIELDS, -1); int nrow = this.myHeader.getIntValue(NAXIS2, -1); int rowsize = this.myHeader.getIntValue(NAXIS1, -1); stream.print(" " + nhcol + " fields"); stream.println(", " + nrow + " rows of length " + rowsize); for (int i = 1; i <= nhcol; i += 1) { stream.print(" " + i + ":"); prtField(stream, "Name", TTYPEn.n(i).key()); prtField(stream, "Format", TFORMn.n(i).key()); prtField(stream, "Dimens", TDIMn.n(i).key()); stream.println(""); } stream.println(" Data Information:"); if (myData == null || this.myData.getNRows() == 0 || this.myData.getNCols() == 0) { stream.println(" No data present"); if (this.myData.getHeapSize() > 0) { stream.println(" Heap size is: " + this.myData.getHeapSize() + " bytes"); } } else { stream.println(" Number of rows=" + this.myData.getNRows()); stream.println(" Number of columns=" + this.myData.getNCols()); if (this.myData.getHeapSize() > 0) { stream.println(" Heap size is: " + this.myData.getHeapSize() + " bytes"); } Object[] cols = this.myData.getFlatColumns(); for (int i = 0; i < cols.length; i += 1) { stream.println(" " + i + ":" + ArrayFuncs.arrayDescription(cols[i])); } } } /** * Check that this HDU has a valid header. * * @return true if this HDU has a valid header. */ public boolean isHeader() { return isHeader(this.myHeader); } private void prtField(PrintStream stream, String type, String field) { String val = this.myHeader.getStringValue(field); if (val != null) { stream.print(type + '=' + val + "; "); } } /** * Convert a column in the table to complex. Only tables with appropriate * types and dimensionalities can be converted. It is legal to call this on * a column that is already complex. * * @param index * The 0-based index of the column to be converted. * @return Whether the column can be converted * @throws FitsException * if the header could not be adapted */ public boolean setComplexColumn(int index) throws FitsException { Standard.context(BinaryTable.class); boolean status = false; if (this.myData.setComplexColumn(index)) { // No problem with the data. Make sure the header // is right. BinaryTable.ColumnDesc colDesc = this.myData.getDescriptor(index); int dim = 1; String tdim = ""; String sep = ""; // Don't loop over all values. // The last is the [2] for the complex data. int[] dimens = colDesc.getDimens(); for (int i = 0; i < dimens.length - 1; i += 1) { dim *= dimens[i]; tdim = dimens[i] + sep + tdim; sep = ","; } String suffix = "C"; // For complex // Update the TFORMn keyword. if (colDesc.getBase() == double.class) { suffix = "M"; } // Worry about variable length columns. String prefix = ""; if (this.myData.getDescriptor(index).isVarying()) { prefix = "P"; dim = 1; if (this.myData.getDescriptor(index).isLongVary()) { prefix = "Q"; } } // Now update the header. this.myHeader.findCard(TFORMn.n(index + 1)); HeaderCard hc = this.myHeader.nextCard(); String oldComment = hc.getComment(); if (oldComment == null) { oldComment = "Column converted to complex"; } this.myHeader.card(TFORMn.n(index + 1)).value(dim + prefix + suffix).comment(oldComment); if (tdim.length() > 0) { this.myHeader.addValue(TDIMn.n(index + 1), "(" + tdim + ")"); } else { // Just in case there used to be a TDIM card that's no longer // needed. this.myHeader.deleteKey(TDIMn.n(index + 1)); } status = true; } Standard.context(null); return status; } // Need to tell header about the Heap before writing. @Override public void write(ArrayDataOutput ado) throws FitsException { int oldSize = this.myHeader.getIntValue(PCOUNT); if (oldSize != this.myData.getHeapSize()) { this.myHeader.addValue(PCOUNT, this.myData.getHeapSize()); } if (this.myHeader.getIntValue(PCOUNT) == 0) { this.myHeader.deleteKey(THEAP); } else { this.myHeader.getIntValue(TFIELDS); int offset = this.myHeader.getIntValue(NAXIS1) * this.myHeader.getIntValue(NAXIS2) + this.myData.getHeapOffset(); this.myHeader.addValue(THEAP, offset); } super.write(ado); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/Data.java000066400000000000000000000131261310063650500251150ustar00rootroot00000000000000package nom.tam.fits; import static nom.tam.util.LoggerHelper.getLogger; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.RandomAccess; /** * This class provides methods to access the data segment of an HDU. *

* This is the object which contains the actual data for the HDU. *

*
    *
  • For images and primary data this is a simple (but possibly * multi-dimensional) primitive array. When group data is supported it will be a * possibly multidimensional array of group objects. *
  • For ASCII data it is a two dimensional Object array where each of the * constituent objects is a primitive array of length 1. *
  • For Binary data it is a two dimensional Object array where each of the * constituent objects is a primitive array of arbitrary (more or less) * dimensionality. *
*/ public abstract class Data implements FitsElement { private static final Logger LOG = getLogger(Data.class); private static final int FITS_BLOCK_SIZE_MINUS_ONE = FitsFactory.FITS_BLOCK_SIZE - 1; /** * The starting location of the data when last read */ protected long fileOffset = -1; /** * The size of the data when last read */ protected long dataSize; /** * The input stream used. */ protected RandomAccess input; /** * Modify a header to point to this data, this differs per subclass, they * all need oder provided different informations to the header. Basically * they describe the structure of this data object. * * @param head * header to fill with the data from the current data object * @throws FitsException * if the operation fails */ abstract void fillHeader(Header head) throws FitsException; /** * @return the data array object. * @throws FitsException * if the data could not be gathered . */ public abstract Object getData() throws FitsException; /** * @return the file offset */ @Override public long getFileOffset() { return this.fileOffset; } /** * @return the non-FITS data object. * @throws FitsException * if the data could not be gathered . */ public Object getKernel() throws FitsException { return getData(); } /** * @return the size of the data element in bytes. */ @Override public long getSize() { return FitsUtil.addPadding(getTrueSize()); } abstract long getTrueSize(); @Override public abstract void read(ArrayDataInput in) throws FitsException; @Override public boolean reset() { try { FitsUtil.reposition(this.input, this.fileOffset); return true; } catch (Exception e) { LOG.log(Level.SEVERE, "Unable to reset", e); return false; } } @Override public void rewrite() throws FitsException { if (!rewriteable()) { throw new FitsException("Illegal attempt to rewrite data"); } FitsUtil.reposition(this.input, this.fileOffset); write((ArrayDataOutput) this.input); try { ((ArrayDataOutput) this.input).flush(); } catch (IOException e) { throw new FitsException("Error in rewrite flush: " + e); } } @Override public boolean rewriteable() { return this.input != null && this.fileOffset >= 0 && (getTrueSize() + FITS_BLOCK_SIZE_MINUS_ONE) / FitsFactory.FITS_BLOCK_SIZE == (this.dataSize + FITS_BLOCK_SIZE_MINUS_ONE) / FitsFactory.FITS_BLOCK_SIZE; } /** * Set the fields needed for a re-read. * * @param o * reread information. */ protected void setFileOffset(ArrayDataInput o) { if (o instanceof RandomAccess) { this.fileOffset = FitsUtil.findOffset(o); this.dataSize = getTrueSize(); this.input = (RandomAccess) o; } } /** * Write the data -- including any buffering needed * * @param o * The output stream on which to write the data. */ @Override public abstract void write(ArrayDataOutput o) throws FitsException; } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/Fits.java000066400000000000000000000752561310063650500251650ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.Closeable; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import nom.tam.fits.compress.CompressionManager; import nom.tam.fits.utilities.FitsCheckSum; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.BufferedDataInputStream; import nom.tam.util.BufferedDataOutputStream; import nom.tam.util.BufferedFile; import nom.tam.util.RandomAccess; import nom.tam.util.SafeClose; /** * This class provides access to routines to allow users to read and write FITS * files.
* Description of the Package *

* This FITS package attempts to make using FITS files easy, but does not do * exhaustive error checking. Users should not assume that just because a FITS * file can be read and written that it is necessarily legal FITS. These classes * try to make it easy to transform between arrays of Java primitives and their * FITS encodings. *

    *
  • The Fits class provides capabilities to read and write data at the HDU * level, and to add and delete HDU's from the current Fits object. A large * number of constructors are provided which allow users to associate the Fits * object with some form of external data. This external data may be in a * compressed format. *

    * Note that this association is limited, it only specifies where the various * read methods should read data from. It does not automatically read the data * content and store the results. To ensure that the external content has been * read and parsed the user may wish to invoke the read() method after creating * the Fits object associated with external data. E.g., * *

     *     File fl = ...  ; 
     *     Fits f = new Fits(fl); // Or we could have used one of the other constructors.
     *     // At this point the Fits object is empty.
     *     f.read();    // Read the external data into the Fits object
     *     // At this point the Fits object should have one or more HDUs depending
     *     // upon the external content.
     * 
    * * Users can choose to read only some of the HDUs in a given input, and may add * HDU's that were either read from other files or generated by the program. See * the various read and addHDU methods. *
  • The FitsFactory class is a factory class which is used to create HDUs. * HDU's can be of a number of types derived from the abstract class BasicHDU. * The hierarchy of HDUs is: *
      *
    • BasicHDU *
        *
      • ImageHDU *
      • RandomGroupsHDU *
      • TableHDU *
          *
        • BinaryTableHDU *
        • AsciiTableHDU *
        *
      • UndefinedHDU *
      *
    *
  • The Header class provides many functions to add, delete and read header * keywords in a variety of formats. *
  • The HeaderCard class provides access to the structure of a FITS header * card. *
  • The header package defines sets of enumerations that allow users to * create and access header keywords in a controlled way. *
  • The Data class is an abstract class which provides the basic methods for * reading and writing FITS data. It provides methods to get the the actual * underlying arrays and detailed methods for manipulation specific to the * different data types. *
  • The TableHDU class provides a large number of methods to access and * modify information in tables. *
  • The utilities package includes simple tools to copy and list FITS files. *
* * @version 1.12 */ public class Fits implements Closeable { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(Fits.class.getName()); /** * The input stream associated with this Fits object. */ private ArrayDataInput dataStr; /** * A vector of HDUs that have been added to this Fits object. */ private final List> hduList = new ArrayList>(); /** * Has the input stream reached the EOF? */ private boolean atEOF; /** * The last offset we reached. A -1 is used to indicate that we cannot use * the offset. */ private long lastFileOffset = -1; /** * Create an empty Fits object which is not associated with an input stream. */ public Fits() { } /** * Associate FITS object with a File. If the file is compressed a stream * will be used, otherwise random access will be supported. * * @param myFile * The File object. The content of this file will not be read * into the Fits object until the user makes some explicit * request. * @throws FitsException if the operation failed * @throws FitsException * if the operation failed */ public Fits(File myFile) throws FitsException { this(myFile, CompressionManager.isCompressed(myFile)); } /** * Associate the Fits object with a File * * @param myFile * The File object. The content of this file will not be read * into the Fits object until the user makes some explicit * request. * @param compressed * Is the data compressed? * @throws FitsException * if the operation failed */ public Fits(File myFile, boolean compressed) throws FitsException { fileInit(myFile, compressed); } /** * Create a Fits object associated with the given data stream. Compression * is determined from the first few bytes of the stream. * * @param str * The data stream. The content of this stream will not be read * into the Fits object until the user makes some explicit * request. * @throws FitsException * if the operation failed */ public Fits(InputStream str) throws FitsException { streamInit(str); } /** * Create a Fits object associated with a data stream. * * @param str * The data stream. The content of this stream will not be read * into the Fits object until the user makes some explicit * request. * @param compressed * Is the stream compressed? This is currently ignored. * Compression is determined from the first two bytes in the * stream. * @throws FitsException * if the operation failed * @deprecated use {@link #Fits(InputStream)} compression is auto detected. */ @Deprecated public Fits(InputStream str, boolean compressed) throws FitsException { this(str); LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed); } /** * Associate the FITS object with a file or URL. The string is assumed to be * a URL if it begins one of the protocol strings. If the string ends in .gz * it is assumed that the data is in a compressed format. All string * comparisons are case insensitive. * * @param filename * The name of the file or URL to be processed. The content of * this file will not be read into the Fits object until the user * makes some explicit request. * @throws FitsException * Thrown if unable to find or open a file or URL from the * string given. **/ public Fits(String filename) throws FitsException { this(filename, CompressionManager.isCompressed(filename)); } /** * Associate the FITS object with a file or URL. The string is assumed to be * a URL if it begins one of the protocol strings. If the string ends in .gz * it is assumed that the data is in a compressed format. All string * comparisons are case insensitive. * * @param filename * The name of the file or URL to be processed. The content of * this file will not be read into the Fits object until the user * makes some explicit request. * @param compressed * is the file compressed? * @throws FitsException * Thrown if unable to find or open a file or URL from the * string given. **/ public Fits(String filename, boolean compressed) throws FitsException { if (filename == null) { throw new FitsException("Null FITS Identifier String"); } try { File fil = new File(filename); if (fil.exists()) { fileInit(fil, compressed); return; } } catch (Exception e) { LOG.log(Level.FINE, "not a file " + filename, e); } try { InputStream str = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); if (str != null) { streamInit(str); return; } } catch (Exception e) { LOG.log(Level.FINE, "not a resource " + filename, e); } try { InputStream is = FitsUtil.getURLStream(new URL(filename), 0); streamInit(is); return; } catch (Exception e) { LOG.log(Level.FINE, "not a url " + filename, e); } throw new FitsException("could not detect type of " + filename); } /** * Associate the FITS object with a given URL * * @param myURL * The URL to be read. The content of this URL will not be read * into the Fits object until the user makes some explicit * request. * @throws FitsException * Thrown if unable to find or open a file or URL from the * string given. */ public Fits(URL myURL) throws FitsException { try { streamInit(FitsUtil.getURLStream(myURL, 0)); } catch (IOException e) { throw new FitsException("Unable to open input from URL:" + myURL, e); } } /** * Associate the FITS object with a given uncompressed URL * * @param myURL * The URL to be associated with the FITS file. The content of * this URL will not be read into the Fits object until the user * makes some explicit request. * @param compressed * Compression flag, ignored. * @throws FitsException * Thrown if unable to use the specified URL. * @deprecated use {@link #Fits(InputStream)} compression is auto detected. */ @Deprecated public Fits(URL myURL, boolean compressed) throws FitsException { this(myURL); LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed); } /** * @return a newly created HDU from the given Data. * @param data * The data to be described in this HDU. * @param * the class of the HDU * @throws FitsException * if the operation failed */ public static BasicHDU makeHDU(DataClass data) throws FitsException { Header hdr = new Header(); data.fillHeader(hdr); return FitsFactory.hduFactory(hdr, data); } /** * @return a newly created HDU from the given header. * @param h * The header which describes the FITS extension * @throws FitsException * if the header could not be converted to a HDU. */ public static BasicHDU makeHDU(Header h) throws FitsException { Data d = FitsFactory.dataFactory(h); return FitsFactory.hduFactory(h, d); } /** * @return a newly created HDU from the given data kernel. * @param o * The data to be described in this HDU. * @throws FitsException * if the parameter could not be converted to a HDU. */ public static BasicHDU makeHDU(Object o) throws FitsException { return FitsFactory.hduFactory(o); } /** * @return the version of the library. */ public static String version() { Properties props = new Properties(); InputStream versionProperties = null; try { versionProperties = Fits.class.getResourceAsStream("/META-INF/maven/gov.nasa.gsfc.heasarc/nom-tam-fits/pom.properties"); props.load(versionProperties); return props.getProperty("version"); } catch (IOException e) { LOG.log(Level.INFO, "reading version failed, ignoring", e); return "unknown"; } finally { saveClose(versionProperties); } } /** * close the input stream, and ignore eventual errors. * * @param in * the input stream to close. */ public static void saveClose(InputStream in) { SafeClose.close(in); } /** * Add an HDU to the Fits object. Users may intermix calls to functions * which read HDUs from an associated input stream with the addHDU and * insertHDU calls, but should be careful to understand the consequences. * * @param myHDU * The HDU to be added to the end of the FITS object. * @throws FitsException * if the HDU could not be inserted. */ public void addHDU(BasicHDU myHDU) throws FitsException { insertHDU(myHDU, getNumberOfHDUs()); } /** * Get the current number of HDUs in the Fits object. * * @return The number of HDU's in the object. * @deprecated use {@link #getNumberOfHDUs()} instead */ @Deprecated public int currentSize() { return getNumberOfHDUs(); } /** * Delete an HDU from the HDU list. * * @param n * The index of the HDU to be deleted. If n is 0 and there is * more than one HDU present, then the next HDU will be converted * from an image to primary HDU if possible. If not a dummy * header HDU will then be inserted. * @throws FitsException * if the HDU could not be deleted. */ public void deleteHDU(int n) throws FitsException { int size = getNumberOfHDUs(); if (n < 0 || n >= size) { throw new FitsException("Attempt to delete non-existent HDU:" + n); } this.hduList.remove(n); if (n == 0 && size > 1) { BasicHDU newFirst = this.hduList.get(0); if (newFirst.canBePrimary()) { newFirst.setPrimaryHDU(true); } else { insertHDU(BasicHDU.getDummyHDU(), 0); } } } /** * Get a stream from the file and then use the stream initialization. * * @param myFile * The File to be associated. * @param compressed * Is the data compressed? * @throws FitsException * if the opening of the file failed. */ @SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "stream stays open, and will be read when nessesary.") protected void fileInit(File myFile, boolean compressed) throws FitsException { try { if (compressed) { streamInit(new FileInputStream(myFile)); } else { randomInit(myFile); } } catch (IOException e) { throw new FitsException("Unable to create Input Stream from File: " + myFile, e); } } /** * @return the n'th HDU. If the HDU is already read simply return a pointer * to the cached data. Otherwise read the associated stream until * the n'th HDU is read. * @param n * The index of the HDU to be read. The primary HDU is index 0. * @return The n'th HDU or null if it could not be found. * @throws FitsException * if the header could not be read * @throws IOException * if the underlying buffer threw an error */ public BasicHDU getHDU(int n) throws FitsException, IOException { int size = getNumberOfHDUs(); for (int i = size; i <= n; i += 1) { BasicHDU hdu = readHDU(); if (hdu == null) { return null; } } return this.hduList.get(n); } /** * Get the current number of HDUs in the Fits object. * * @return The number of HDU's in the object. */ public int getNumberOfHDUs() { return this.hduList.size(); } /** * Get the data stream used for the Fits Data. * * @return The associated data stream. Users may wish to call this function * after opening a Fits object when they wish detailed control for * writing some part of the FITS file. */ public ArrayDataInput getStream() { return this.dataStr; } /** * Insert a FITS object into the list of HDUs. * * @param myHDU * The HDU to be inserted into the list of HDUs. * @param position * The location at which the HDU is to be inserted. * @throws FitsException * if the HDU could not be inserted. */ public void insertHDU(BasicHDU myHDU, int position) throws FitsException { if (myHDU == null) { return; } if (position < 0 || position > getNumberOfHDUs()) { throw new FitsException("Attempt to insert HDU at invalid location: " + position); } try { if (position == 0) { // Note that the previous initial HDU is no longer the first. // If we were to insert tables backwards from last to first, // we could get a lot of extraneous DummyHDUs but we currently // do not worry about that. if (getNumberOfHDUs() > 0) { this.hduList.get(0).setPrimaryHDU(false); } if (myHDU.canBePrimary()) { myHDU.setPrimaryHDU(true); this.hduList.add(0, myHDU); } else { insertHDU(BasicHDU.getDummyHDU(), 0); myHDU.setPrimaryHDU(false); this.hduList.add(1, myHDU); } } else { myHDU.setPrimaryHDU(false); this.hduList.add(position, myHDU); } } catch (NoSuchElementException e) { throw new FitsException("hduList inconsistency in insertHDU", e); } } /** * Initialize using buffered random access. This implies that the data is * uncompressed. * * @param file * the file to open * @throws FitsException * if the file could not be read */ protected void randomInit(File file) throws FitsException { String permissions = "r"; if (!file.exists() || !file.canRead()) { throw new FitsException("Non-existent or unreadable file"); } if (file.canWrite()) { permissions += "w"; } try { this.dataStr = new BufferedFile(file, permissions); ((BufferedFile) this.dataStr).seek(0); } catch (IOException e) { throw new FitsException("Unable to open file " + file.getPath(), e); } } /** * Return all HDUs for the Fits object. If the FITS file is associated with * an external stream make sure that we have exhausted the stream. * * @return an array of all HDUs in the Fits object. Returns null if there * are no HDUs associated with this object. * @throws FitsException * if the reading failed. */ public BasicHDU[] read() throws FitsException { readToEnd(); int size = getNumberOfHDUs(); if (size == 0) { return new BasicHDU[0]; } return this.hduList.toArray(new BasicHDU[size]); } /** * Read a FITS file from an InputStream object. * * @param is * The InputStream stream whence the FITS information is found. * @throws FitsException * if the data read could not be interpreted */ public void read(InputStream is) throws FitsException { if (is instanceof ArrayDataInput) { this.dataStr = (ArrayDataInput) is; } else { this.dataStr = new BufferedDataInputStream(is); } read(); } /** * Read the next HDU on the default input stream. * * @return The HDU read, or null if an EOF was detected. Note that null is * only returned when the EOF is detected immediately at the * beginning of reading the HDU. * @throws FitsException * if the header could not be read * @throws IOException * if the underlying buffer threw an error */ public BasicHDU readHDU() throws FitsException, IOException { if (this.dataStr == null || this.atEOF) { if (this.dataStr == null) { LOG.warning("trying to read a hdu, without an input source!"); } return null; } if (this.dataStr instanceof RandomAccess && this.lastFileOffset > 0) { FitsUtil.reposition(this.dataStr, this.lastFileOffset); } Header hdr = Header.readHeader(this.dataStr); if (hdr == null) { this.atEOF = true; return null; } Data data = hdr.makeData(); try { data.read(this.dataStr); } catch (PaddingException e) { e.updateHeader(hdr); throw e; } this.lastFileOffset = FitsUtil.findOffset(this.dataStr); BasicHDU nextHDU = FitsFactory.hduFactory(hdr, data); this.hduList.add(nextHDU); return nextHDU; } /** * Read to the end of the associated input stream * * @throws FitsException * if the operation failed */ private void readToEnd() throws FitsException { while (this.dataStr != null && !this.atEOF) { try { if (readHDU() == null) { break; } } catch (EOFException e) { if (FitsFactory.getAllowTerminalJunk() && // e.getCause() instanceof TruncatedFileException && // getNumberOfHDUs() > 0) { this.atEOF = true; return; } throw new FitsException("IO error: " + e); } catch (IOException e) { throw new FitsException("IO error: " + e); } } } /** * Add or Modify the CHECKSUM keyword in all headers. by R J Mathar * * @throws FitsException * if the operation failed * @throws IOException * if the underlying stream failed */ public void setChecksum() throws FitsException, IOException { for (int i = 0; i < getNumberOfHDUs(); i += 1) { setChecksum(getHDU(i)); } } /** * Set the data stream to be used for future input. * * @param stream * The data stream to be used. */ public void setStream(ArrayDataInput stream) { this.dataStr = stream; this.atEOF = false; this.lastFileOffset = -1; } /** * Return the number of HDUs in the Fits object. If the FITS file is * associated with an external stream make sure that we have exhausted the * stream. * * @return number of HDUs. * @deprecated The meaning of size of ambiguous. Use * {@link #getNumberOfHDUs()} instead. Note size() will read the * input file/stream to the EOF before returning the number of * HDUs which {@link #getNumberOfHDUs()} does not. If you wish * to duplicate this behavior and ensure that the input has been * exhausted before getting the number of HDUs then use the * sequence: * read(); * getNumberofHDUs(); * * @throws FitsException * if the file could not be read. */ @Deprecated public int size() throws FitsException { readToEnd(); return getNumberOfHDUs(); } /** * Skip the next HDU on the default input stream. * * @throws FitsException * if the HDU could not be skipped * @throws IOException * if the underlying stream failed */ public void skipHDU() throws FitsException, IOException { if (this.atEOF) { return; } else { Header hdr = new Header(this.dataStr); int dataSize = (int) hdr.getDataSize(); this.dataStr.skipAllBytes(dataSize); if (this.dataStr instanceof RandomAccess) { this.lastFileOffset = ((RandomAccess) this.dataStr).getFilePointer(); } } } /** * Skip HDUs on the associate input stream. * * @param n * The number of HDUs to be skipped. * @throws FitsException * if the HDU could not be skipped * @throws IOException * if the underlying stream failed */ public void skipHDU(int n) throws FitsException, IOException { for (int i = 0; i < n; i += 1) { skipHDU(); } } /** * Initialize the input stream. Mostly this checks to see if the stream is * compressed and wraps the stream if necessary. Even if the stream is not * compressed, it will likely be wrapped in a PushbackInputStream. So users * should probably not supply a BufferedDataInputStream themselves, but * should allow the Fits class to do the wrapping. * * @param inputStream * stream to initialize * @throws FitsException * if the initialization failed */ protected void streamInit(InputStream inputStream) throws FitsException { this.dataStr = new BufferedDataInputStream(CompressionManager.decompress(inputStream)); } /** * Write a Fits Object to an external Stream. * * @param os * A DataOutput stream. * @throws FitsException * if the operation failed */ public void write(DataOutput os) throws FitsException { ArrayDataOutput obs; boolean newOS = false; if (os instanceof ArrayDataOutput) { obs = (ArrayDataOutput) os; } else if (os instanceof DataOutputStream) { newOS = true; obs = new BufferedDataOutputStream((DataOutputStream) os); } else { throw new FitsException("Cannot create ArrayDataOutput from class " + os.getClass().getName()); } for (BasicHDU basicHDU : hduList) { basicHDU.write(obs); } if (newOS) { try { obs.flush(); obs.close(); } catch (IOException e) { throw new FitsException("Error flushing/closing the FITS output stream: " + e, e); } } if (obs instanceof BufferedFile) { try { ((BufferedFile) obs).setLength(((BufferedFile) obs).getFilePointer()); } catch (IOException e) { throw new FitsException("Error resizing the FITS output stream: " + e, e); } } } /** * Write the FITS to the specified file. This is a wrapper method provided * for convenience, which calls the {@link #write(DataOutput)} method. It * creates a suitable {@link nom.tam.util.BufferedFile}, to which the FITS * is then written. Upon completion the underlying stream is closed. * * @param file * a file to which the FITS is to be written. * @throws FitsException * if {@link #write(DataOutput)} failed * @throws IOException * if the underlying output stream could not be created or * closed. */ public void write(File file) throws IOException, FitsException { BufferedFile bf = null; try { bf = new BufferedFile(file, "rw"); write(bf); } finally { SafeClose.close(bf); } } @Override public void close() throws IOException { if (dataStr != null) { this.dataStr.close(); } } /** * set the checksum of a HDU. * * @param hdu * the HDU to add a checksum * @throws FitsException * the checksum could not be added to the header * @deprecated use {@link FitsCheckSum#setChecksum(BasicHDU)} */ @Deprecated public static void setChecksum(BasicHDU hdu) throws FitsException { FitsCheckSum.setChecksum(hdu); } /** * calculate the checksum for the block of data * * @param data * the data to create the checksum for * @return the checksum * @deprecated use {@link FitsCheckSum#checksum(byte[])} */ @Deprecated public static long checksum(final byte[] data) { return FitsCheckSum.checksum(data); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsDate.java000066400000000000000000000251571310063650500257560ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.text.DecimalFormat; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Fits date object parsed from the different type of date combinations */ public class FitsDate { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(FitsDate.class.getName()); private static final int FIRST_THREE_CHARACTER_VALUE = 100; private static final int FIRST_TWO_CHARACTER_VALUE = 10; private static final int FITS_DATE_STRING_SIZE = 23; private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); private static final int NEW_FORMAT_DAY_OF_MONTH_GROUP = 4; private static final int NEW_FORMAT_HOUR_GROUP = 6; private static final int NEW_FORMAT_MILLISECOND_GROUP = 10; private static final int NEW_FORMAT_MINUTE_GROUP = 7; private static final int NEW_FORMAT_MONTH_GROUP = 3; private static final int NEW_FORMAT_SECOND_GROUP = 8; private static final int NEW_FORMAT_YEAR_GROUP = 2; private static final Pattern NORMAL_REGEX = Pattern .compile("\\s*(([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]))(T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])(\\.([0-9][0-9][0-9]|[0-9][0-9]))?)?\\s*"); private static final int OLD_FORMAT_DAY_OF_MONTH_GROUP = 1; private static final int OLD_FORMAT_MONTH_GROUP = 2; private static final int OLD_FORMAT_YEAR_GROUP = 3; private static final Pattern OLD_REGEX = Pattern.compile("\\s*([0-9][0-9])/([0-9][0-9])/([0-9][0-9])\\s*"); private static final int TWO_DIGIT_MILISECONDS_FACTOR = 10; private static final int YEAR_OFFSET = 1900; /** * @return the current date in FITS date format */ public static String getFitsDateString() { return getFitsDateString(new Date(), true); } /** * @return a created FITS format date string Java Date object. * @param epoch * The epoch to be converted to FITS format. */ public static String getFitsDateString(Date epoch) { return getFitsDateString(epoch, true); } /** * @return a created FITS format date string. Note that the date is not * rounded. * @param epoch * The epoch to be converted to FITS format. * @param timeOfDay * Should time of day information be included? */ public static String getFitsDateString(Date epoch, boolean timeOfDay) { Calendar cal = Calendar.getInstance(FitsDate.GMT); cal.setTime(epoch); StringBuilder fitsDate = new StringBuilder(); DecimalFormat df = new DecimalFormat("0000"); fitsDate.append(df.format(cal.get(Calendar.YEAR))); fitsDate.append("-"); df = new DecimalFormat("00"); fitsDate.append(df.format(cal.get(Calendar.MONTH) + 1)); fitsDate.append("-"); fitsDate.append(df.format(cal.get(Calendar.DAY_OF_MONTH))); if (timeOfDay) { fitsDate.append("T"); fitsDate.append(df.format(cal.get(Calendar.HOUR_OF_DAY))); fitsDate.append(":"); fitsDate.append(df.format(cal.get(Calendar.MINUTE))); fitsDate.append(":"); fitsDate.append(df.format(cal.get(Calendar.SECOND))); fitsDate.append("."); df = new DecimalFormat("000"); fitsDate.append(df.format(cal.get(Calendar.MILLISECOND))); } return fitsDate.toString(); } private Date date = null; private int hour = -1; private int mday = -1; private int millisecond = -1; private int minute = -1; private int month = -1; private int second = -1; private int year = -1; /** * Convert a FITS date string to a Java Date object. * * @param dStr * the FITS date * @throws FitsException * if dStr does not contain a valid FITS date. */ public FitsDate(String dStr) throws FitsException { // if the date string is null, we are done if (dStr == null || dStr.isEmpty()) { return; } Matcher match = FitsDate.NORMAL_REGEX.matcher(dStr); if (match.matches()) { this.year = getInt(match, FitsDate.NEW_FORMAT_YEAR_GROUP); this.month = getInt(match, FitsDate.NEW_FORMAT_MONTH_GROUP); this.mday = getInt(match, FitsDate.NEW_FORMAT_DAY_OF_MONTH_GROUP); this.hour = getInt(match, FitsDate.NEW_FORMAT_HOUR_GROUP); this.minute = getInt(match, FitsDate.NEW_FORMAT_MINUTE_GROUP); this.second = getInt(match, FitsDate.NEW_FORMAT_SECOND_GROUP); this.millisecond = getMilliseconds(match, FitsDate.NEW_FORMAT_MILLISECOND_GROUP); } else { match = FitsDate.OLD_REGEX.matcher(dStr); if (match.matches()) { this.year = getInt(match, FitsDate.OLD_FORMAT_YEAR_GROUP) + FitsDate.YEAR_OFFSET; this.month = getInt(match, FitsDate.OLD_FORMAT_MONTH_GROUP); this.mday = getInt(match, FitsDate.OLD_FORMAT_DAY_OF_MONTH_GROUP); } else { if (dStr.trim().isEmpty()) { return; } throw new FitsException("Bad FITS date string \"" + dStr + '"'); } } } private static int getInt(Matcher match, int groupIndex) { String value = match.group(groupIndex); if (value != null) { return Integer.parseInt(value); } return -1; } private static int getMilliseconds(Matcher match, int groupIndex) { String value = match.group(groupIndex); if (value != null) { int result = Integer.parseInt(value); if (value.length() == 2) { result = result * FitsDate.TWO_DIGIT_MILISECONDS_FACTOR; } return result; } return -1; } /** * Get a Java Date object corresponding to this FITS date. * * @return The Java Date object. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") public Date toDate() { if (this.date == null && this.year != -1) { Calendar cal = Calendar.getInstance(FitsDate.GMT); cal.set(Calendar.YEAR, this.year); cal.set(Calendar.MONTH, this.month - 1); cal.set(Calendar.DAY_OF_MONTH, this.mday); if (FitsDate.LOG.isLoggable(Level.FINEST)) { FitsDate.LOG.log(Level.FINEST, "At this point:" + cal.getTime()); } if (this.hour == -1) { cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); if (FitsDate.LOG.isLoggable(Level.FINEST)) { FitsDate.LOG.log(Level.FINEST, "2At this point:" + cal.getTime()); } } else { cal.set(Calendar.HOUR_OF_DAY, this.hour); cal.set(Calendar.MINUTE, this.minute); cal.set(Calendar.SECOND, this.second); if (this.millisecond == -1) { cal.set(Calendar.MILLISECOND, 0); } else { cal.set(Calendar.MILLISECOND, this.millisecond); } if (FitsDate.LOG.isLoggable(Level.FINEST)) { FitsDate.LOG.log(Level.FINEST, "3At this point:" + cal.getTime()); } } this.date = cal.getTime(); } if (FitsDate.LOG.isLoggable(Level.FINEST)) { FitsDate.LOG.log(Level.FINEST, " date:" + this.date); FitsDate.LOG.log(Level.FINEST, " year:" + this.year); FitsDate.LOG.log(Level.FINEST, " month:" + this.month); FitsDate.LOG.log(Level.FINEST, " mday:" + this.mday); FitsDate.LOG.log(Level.FINEST, " hour:" + this.hour); } return this.date; } @Override public String toString() { if (this.year == -1) { return ""; } StringBuilder buf = new StringBuilder(FitsDate.FITS_DATE_STRING_SIZE); buf.append(this.year); buf.append('-'); appendTwoDigitValue(buf, this.month); buf.append('-'); appendTwoDigitValue(buf, mday); if (this.hour != -1) { buf.append('T'); appendTwoDigitValue(buf, this.hour); buf.append(':'); appendTwoDigitValue(buf, this.minute); buf.append(':'); appendTwoDigitValue(buf, this.second); if (this.millisecond != -1) { buf.append('.'); appendThreeDigitValue(buf, this.millisecond); } } return buf.toString(); } private void appendThreeDigitValue(StringBuilder buf, int value) { if (value < FitsDate.FIRST_THREE_CHARACTER_VALUE) { buf.append('0'); } appendTwoDigitValue(buf, value); } private void appendTwoDigitValue(StringBuilder buf, int value) { if (value < FitsDate.FIRST_TWO_CHARACTER_VALUE) { buf.append('0'); } buf.append(value); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsElement.java000066400000000000000000000064021310063650500264620ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; /** * This interface allows to easily perform basic I/O operations on a FITS * element. */ public interface FitsElement { /** * @return the byte at which this element begins. This is only available if * the data is originally read from a random access medium. */ long getFileOffset(); /** * @return The size of this element in bytes */ long getSize(); /** * Read a data array into the current object and if needed position to the * beginning of the next FITS block. * * @param in * The input data stream * @throws FitsException * if the read was unsuccessful. * @throws IOException * if the read was unsuccessful. */ void read(ArrayDataInput in) throws FitsException, IOException; /** * Reset the input stream to point to the beginning of this element * * @return True if the reset succeeded. */ boolean reset(); /** * Rewrite the contents of the element in place. The data must have been * originally read from a random access device, and the size of the element * may not have changed. * * @throws FitsException * if the rewrite was unsuccessful. * @throws IOException * if the rewrite was unsuccessful. */ void rewrite() throws FitsException, IOException; /** * @return true if this element can be rewritten? */ boolean rewriteable(); /** * Write the contents of the element to a data sink. * * @param out * The data sink. * @throws FitsException * if the write was unsuccessful. * @throws IOException * if the write was unsuccessful. */ void write(ArrayDataOutput out) throws FitsException, IOException; } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsException.java000066400000000000000000000033221310063650500270250ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This is a general exception class allow us to distinguish issues detected by * this library. */ public class FitsException extends Exception { /** * serial version UID. */ private static final long serialVersionUID = 1L; public FitsException(String msg) { super(msg); } public FitsException(String msg, Exception reason) { super(msg, reason); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsFactory.java000066400000000000000000000352461310063650500265100ustar00rootroot00000000000000package nom.tam.fits; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import nom.tam.fits.header.hierarch.IHierarchKeyFormatter; import nom.tam.fits.header.hierarch.StandardIHierarchKeyFormatter; import nom.tam.image.compression.hdu.CompressedImageData; import nom.tam.image.compression.hdu.CompressedImageHDU; import nom.tam.image.compression.hdu.CompressedTableData; import nom.tam.image.compression.hdu.CompressedTableHDU; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This class contains the code which associates particular FITS types with * header and data configurations. It comprises a set of Factory methods which * call appropriate methods in the HDU classes. If -- God forbid -- a new FITS * HDU type were created, then the XXHDU, XXData classes would need to be added * and this file modified but no other changes should be needed in the FITS * libraries. */ public final class FitsFactory { protected static final class FitsSettings { private boolean useAsciiTables = true; private boolean useHierarch = false; private boolean checkAsciiStrings = false; private boolean allowTerminalJunk = false; private boolean longStringsEnabled = false; private boolean skipBlankAfterAssign = false; private IHierarchKeyFormatter hierarchKeyFormatter = new StandardIHierarchKeyFormatter(); private FitsSettings copy() { FitsSettings settings = new FitsSettings(); settings.useAsciiTables = this.useAsciiTables; settings.useHierarch = this.useHierarch; settings.checkAsciiStrings = this.checkAsciiStrings; settings.allowTerminalJunk = this.allowTerminalJunk; settings.longStringsEnabled = this.longStringsEnabled; settings.hierarchKeyFormatter = this.hierarchKeyFormatter; settings.skipBlankAfterAssign = this.skipBlankAfterAssign; return settings; } protected IHierarchKeyFormatter getHierarchKeyFormatter() { return this.hierarchKeyFormatter; } protected boolean isAllowTerminalJunk() { return this.allowTerminalJunk; } protected boolean isCheckAsciiStrings() { return this.checkAsciiStrings; } protected boolean isLongStringsEnabled() { return this.longStringsEnabled; } protected boolean isSkipBlankAfterAssign() { return this.skipBlankAfterAssign; } protected boolean isUseAsciiTables() { return this.useAsciiTables; } protected boolean isUseHierarch() { return this.useHierarch; } } private static final FitsSettings GLOBAL_SETTINGS = new FitsSettings(); private static final ThreadLocal LOCAL_SETTINGS = new ThreadLocal(); private static ExecutorService threadPool; public static final int FITS_BLOCK_SIZE = 2880; /** * @return Given a Header construct an appropriate data. * @param hdr * header to create the data from * @throws FitsException * if the header did not contain enough information to detect * the type of the data */ public static Data dataFactory(Header hdr) throws FitsException { if (ImageHDU.isHeader(hdr)) { Data d = ImageHDU.manufactureData(hdr); hdr.afterExtend(); // Fix for positioning error noted by V. Forchi return d; } else if (RandomGroupsHDU.isHeader(hdr)) { return RandomGroupsHDU.manufactureData(hdr); } else if (current().useAsciiTables && AsciiTableHDU.isHeader(hdr)) { return AsciiTableHDU.manufactureData(hdr); } else if (CompressedImageHDU.isHeader(hdr)) { return CompressedImageHDU.manufactureData(hdr); } else if (CompressedTableHDU.isHeader(hdr)) { return CompressedTableHDU.manufactureData(hdr); } else if (BinaryTableHDU.isHeader(hdr)) { return BinaryTableHDU.manufactureData(hdr); } else if (UndefinedHDU.isHeader(hdr)) { return UndefinedHDU.manufactureData(hdr); } else { throw new FitsException("Unrecognizable header in dataFactory"); } } /** * @return Is terminal junk (i.e., non-FITS data following a valid HDU) * allowed. */ public static boolean getAllowTerminalJunk() { return current().allowTerminalJunk; } /** * @return the formatter to use for hierarch keys. */ public static IHierarchKeyFormatter getHierarchFormater() { return current().hierarchKeyFormatter; } /** * @return true if we are processing HIERARCH style keywords */ public static boolean getUseHierarch() { return current().useHierarch; } /** * @return Given Header and data objects return the appropriate type of HDU. * @param hdr * the header of the date * @param d * the data * @param * the class of the data * @throws FitsException * if the operation failed */ @SuppressWarnings("unchecked") public static BasicHDU hduFactory(Header hdr, DataClass d) throws FitsException { if (d instanceof ImageData) { return (BasicHDU) new ImageHDU(hdr, (ImageData) d); } else if (d instanceof CompressedImageData) { return (BasicHDU) new CompressedImageHDU(hdr, (CompressedImageData) d); } else if (d instanceof RandomGroupsData) { return (BasicHDU) new RandomGroupsHDU(hdr, (RandomGroupsData) d); } else if (current().useAsciiTables && d instanceof AsciiTable) { return (BasicHDU) new AsciiTableHDU(hdr, (AsciiTable) d); } else if (d instanceof CompressedTableData) { return (BasicHDU) new CompressedTableHDU(hdr, (CompressedTableData) d); } else if (d instanceof BinaryTable) { return (BasicHDU) new BinaryTableHDU(hdr, (BinaryTable) d); } else if (d instanceof UndefinedData) { return (BasicHDU) new UndefinedHDU(hdr, (UndefinedData) d); } return null; } /** * @return Given an object, create the appropriate FITS header to describe * it. * @param o * The object to be described. * @throws FitsException * if the parameter could not be converted to a hdu. */ public static BasicHDU hduFactory(Object o) throws FitsException { Data d; Header h; if (o instanceof Header) { h = (Header) o; d = dataFactory(h); } else if (ImageHDU.isData(o)) { d = ImageHDU.encapsulate(o); h = ImageHDU.manufactureHeader(d); } else if (RandomGroupsHDU.isData(o)) { d = RandomGroupsHDU.encapsulate(o); h = RandomGroupsHDU.manufactureHeader(d); } else if (current().useAsciiTables && AsciiTableHDU.isData(o)) { d = AsciiTableHDU.encapsulate(o); h = AsciiTableHDU.manufactureHeader(d); } else if (BinaryTableHDU.isData(o)) { d = BinaryTableHDU.encapsulate(o); h = BinaryTableHDU.manufactureHeader(d); } else if (UndefinedHDU.isData(o)) { d = UndefinedHDU.encapsulate(o); h = UndefinedHDU.manufactureHeader(d); } else { throw new FitsException("Invalid data presented to HDUFactory"); } return hduFactory(h, d); } // CHECKSTYLE:OFF /** * @return Given Header and data objects return the appropriate type of HDU. * @param hdr * the header of the date * @param d * the data * @param * the class of the data * @throws FitsException * if the operation failed * @deprecated use {@link #hduFactory(Header, Data)} instead */ @Deprecated public static BasicHDU HDUFactory(Header hdr, DataClass d) throws FitsException { return hduFactory(hdr, d); } // CHECKSTYLE:ON // CHECKSTYLE:OFF /** * @return Given an object, create the appropriate FITS header to describe * it. * @param o * The object to be described. * @throws FitsException * if the parameter could not be converted to a hdu. * @deprecated use {@link #hduFactory(Object)} instead */ @Deprecated public static BasicHDU HDUFactory(Object o) throws FitsException { return hduFactory(o); } // CHECKSTYLE:ON /** * @return true If long string support is enabled. */ public static boolean isLongStringsEnabled() { return current().longStringsEnabled; } /** * @return true If blanks after the assign are ommitted in the * header. */ public static boolean isSkipBlankAfterAssign() { return current().skipBlankAfterAssign; } /** * Do we allow junk after a valid FITS file? * * @param allowTerminalJunk * value to set */ public static void setAllowTerminalJunk(boolean allowTerminalJunk) { current().allowTerminalJunk = allowTerminalJunk; } /** * Enable/Disable checking of strings values used in tables to ensure that * they are within the range specified by the FITS standard. The standard * only allows the values 0x20 - 0x7E with null bytes allowed in one limited * context. Disabled by default. * * @param checkAsciiStrings * value to set */ public static void setCheckAsciiStrings(boolean checkAsciiStrings) { current().checkAsciiStrings = checkAsciiStrings; } /** * There is not a real standard how to write hierarch keys, default we use * the one where every key is separated by a blank. If you want or need * another format assing the formater here. * * @param formatter * the hierarch key formatter. */ public static void setHierarchFormater(IHierarchKeyFormatter formatter) { current().hierarchKeyFormatter = formatter; } /** * Enable/Disable longstring support. * * @param longStringsEnabled * value to set */ public static void setLongStringsEnabled(boolean longStringsEnabled) { current().longStringsEnabled = longStringsEnabled; } /** * If set to true the blank after the assign in the header cards in not * written. The blank is stronly recommendet but in some cases it is * important that it can be ommitted. * * @param skipBlankAfterAssign * value to set */ public static void setSkipBlankAfterAssign(boolean skipBlankAfterAssign) { current().skipBlankAfterAssign = skipBlankAfterAssign; } /** * Indicate whether ASCII tables should be used where feasible. * * @param useAsciiTables * value to set */ public static void setUseAsciiTables(boolean useAsciiTables) { current().useAsciiTables = useAsciiTables; } /** * Enable/Disable hierarchical keyword processing. * * @param useHierarch * value to set */ public static void setUseHierarch(boolean useHierarch) { current().useHierarch = useHierarch; } public static ExecutorService threadPool() { if (threadPool == null) { initializeThreadPool(); } return threadPool; } /** * Use thread local settings for the current thread instead of the global * ones if the parameter is set to true, else use the shared global * settings. * * @param useThreadSettings * true if the thread should not share the global settings. */ public static void useThreadLocalSettings(boolean useThreadSettings) { if (useThreadSettings) { LOCAL_SETTINGS.set(GLOBAL_SETTINGS.copy()); } else { LOCAL_SETTINGS.remove(); } } private static void initializeThreadPool() { synchronized (GLOBAL_SETTINGS) { if (threadPool == null) { // 1.5 thread per core threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2, // new ThreadFactory() { private int counter = 1; @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "nom-tam-fits worker " + this.counter++); thread.setDaemon(true); return thread; } }); } } } protected static FitsSettings current() { FitsSettings settings = LOCAL_SETTINGS.get(); if (settings == null) { return GLOBAL_SETTINGS; } else { return settings; } } /** * @return Get the current status for string checking. */ static boolean getCheckAsciiStrings() { return current().checkAsciiStrings; } private FitsFactory() { } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsHeap.java000066400000000000000000000170621310063650500257520ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; import nom.tam.util.BufferedDataInputStream; import nom.tam.util.BufferedDataOutputStream; /** * This class supports the FITS heap. This is currently used for variable length * columns in binary tables. */ public class FitsHeap implements FitsElement { private static final int MINIMUM_HEAP_SIZE = 16384; /** * The storage buffer */ private byte[] heap; /** * The current used size of the buffer <= heap.length */ private int heapSize; /** * Our current offset into the heap. When we read from the heap we use a * byte array input stream. So long as we continue to read further into the * heap, we can continue to use the same stream, but we need to recreate the * stream whenever we skip backwards. */ private int heapOffset = 0; /** * A stream used to read the heap data */ private BufferedDataInputStream bstr; /** * Create a heap of a given size. */ FitsHeap(int size) { this.heapSize = size; if (size < 0) { throw new IllegalArgumentException("Illegal size for FITS heap:" + size); } } private void allocate() { if (this.heap == null) { this.heap = new byte[this.heapSize]; } } /** * Add a copy constructor to allow us to duplicate a heap. This would be * necessary if we wanted to copy an HDU that included variable length * columns. */ FitsHeap copy() { FitsHeap copy = new FitsHeap(0); if (this.heap != null) { copy.heap = this.heap.clone(); } copy.heapSize = this.heapSize; copy.heapOffset = this.heapOffset; return copy; } /** * Check if the Heap can accommodate a given requirement. If not expand the * heap. */ void expandHeap(int need) { // Invalidate any existing input stream to the heap. this.bstr = null; allocate(); if (this.heapSize + need > this.heap.length) { int newlen = (this.heapSize + need) * 2; if (newlen < MINIMUM_HEAP_SIZE) { newlen = MINIMUM_HEAP_SIZE; } byte[] newHeap = new byte[newlen]; System.arraycopy(this.heap, 0, newHeap, 0, this.heapSize); this.heap = newHeap; } } /** * Get data from the heap. * * @param offset * The offset at which the data begins. * @param array * The array to be extracted. * @throws FitsException * if the operation failed */ public void getData(int offset, Object array) throws FitsException { allocate(); try { // Can we reuse the existing byte stream? if (this.bstr == null || this.heapOffset > offset) { this.heapOffset = 0; this.bstr = new BufferedDataInputStream(new ByteArrayInputStream(this.heap)); } this.bstr.skipAllBytes(offset - this.heapOffset); this.heapOffset = offset; this.heapOffset += this.bstr.readLArray(array); } catch (IOException e) { throw new FitsException("Error decoding heap area at offset=" + offset + ". Exception: Exception " + e); } } /** * Get the file offset of the heap */ @Override public long getFileOffset() { throw new IllegalStateException("FitsHeap should only be reset from inside its parent, never alone"); } /** * Return the size of the heap using the more bean compatible format */ @Override public long getSize() { return size(); } /** * Add some data to the heap. */ int putData(Object data) throws FitsException { long lsize = ArrayFuncs.computeLSize(data); if (lsize > Integer.MAX_VALUE) { throw new FitsException("FITS Heap > 2 G"); } int size = (int) lsize; expandHeap(size); ByteArrayOutputStream bo = new ByteArrayOutputStream(size); try { BufferedDataOutputStream o = new BufferedDataOutputStream(bo); o.writeArray(data); o.flush(); o.close(); } catch (IOException e) { throw new FitsException("Unable to write variable column length data", e); } System.arraycopy(bo.toByteArray(), 0, this.heap, this.heapSize, size); int oldOffset = this.heapSize; this.heapSize += size; return oldOffset; } /** * Read the heap */ @SuppressFBWarnings(value = "RR_NOT_CHECKED", justification = "this read will never return less than the requested length") @Override public void read(ArrayDataInput str) throws FitsException { if (this.heapSize > 0) { allocate(); try { if (str.read(this.heap, 0, this.heapSize) < this.heapSize) { throw new FitsException("Error reading heap, no more data"); } } catch (IOException e) { throw new FitsException("Error reading heap " + e.getMessage(), e); } } this.bstr = null; } @Override public boolean reset() { throw new IllegalStateException("FitsHeap should only be reset from inside its parent, never alone"); } @Override public void rewrite() throws IOException, FitsException { throw new FitsException("FitsHeap should only be rewritten from inside its parent, never alone"); } @Override public boolean rewriteable() { return false; } /** * @return the size of the Heap */ public int size() { return this.heapSize; } /** * Write the heap */ @Override public void write(ArrayDataOutput str) throws FitsException { allocate(); try { str.write(this.heap, 0, this.heapSize); } catch (IOException e) { throw new FitsException("Error writing heap:" + e.getMessage(), e); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/FitsUtil.java000066400000000000000000000270271310063650500260140ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; import java.net.URLConnection; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.util.ArrayDataOutput; import nom.tam.util.AsciiFuncs; import nom.tam.util.RandomAccess; /** * This class comprises static utility functions used throughout the FITS * classes. */ public final class FitsUtil { private static final int BYTE_REPRESENTING_BLANK = 32; private static final int BYTE_REPRESENTING_MAX_ASCII_VALUE = 126; /** * the logger to log to. */ private static final Logger LOG = Logger.getLogger(FitsUtil.class.getName()); private static boolean wroteCheckingError = false; /** * Utility class, do not instantiate it. */ private FitsUtil() { } /** * @return Total size of blocked FITS element, using e.v. padding to fits * block size. * @param size * the current size. */ public static int addPadding(int size) { return size + padding(size); } /** * @return Total size of blocked FITS element, using e.v. padding to fits * block size. * @param size * the current size. */ public static long addPadding(long size) { return size + padding(size); } /** * @return Convert an array of booleans to bytes. * @param bool * array of booleans */ static byte[] booleanToByte(boolean[] bool) { byte[] byt = new byte[bool.length]; for (int i = 0; i < bool.length; i += 1) { byt[i] = bool[i] ? (byte) 'T' : (byte) 'F'; } return byt; } /** * @return Convert bytes to Strings. * @param bytes * byte array to convert * @param maxLen * the max string length */ public static String[] byteArrayToStrings(byte[] bytes, int maxLen) { boolean checking = FitsFactory.getCheckAsciiStrings(); // Note that if a String in a binary table contains an internal 0, // the FITS standard says that it is to be considered as terminating // the string at that point, so that software reading the // data back may not include subsequent characters. // No warning of this truncation is given. String[] res = new String[bytes.length / maxLen]; for (int i = 0; i < res.length; i += 1) { int start = i * maxLen; int end = start + maxLen; // Pre-trim the string to avoid keeping memory // hanging around. (Suggested by J.C. Segovia, ESA). // Note that the FITS standard does not mandate // that we should be trimming the string at all, but // this seems to best meet the desires of the community. for (; start < end; start += 1) { if (bytes[start] != BYTE_REPRESENTING_BLANK) { break; // Skip only spaces. } } for (; end > start; end -= 1) { if (bytes[end - 1] != BYTE_REPRESENTING_BLANK) { break; } } // For FITS binary tables, 0 values are supposed // to terminate strings, a la C. [They shouldn't appear in // any other context.] // Other non-printing ASCII characters // should always be an error which we can check for // if the user requests. // The lack of handling of null bytes was noted by Laurent Bourges. boolean errFound = false; for (int j = start; j < end; j += 1) { if (bytes[j] == 0) { end = j; break; } if (checking && (bytes[j] < BYTE_REPRESENTING_BLANK || bytes[j] > BYTE_REPRESENTING_MAX_ASCII_VALUE)) { errFound = true; bytes[j] = BYTE_REPRESENTING_BLANK; } } res[i] = AsciiFuncs.asciiString(bytes, start, end - start); if (errFound && !FitsUtil.wroteCheckingError) { LOG.log(Level.SEVERE, "Warning: Invalid ASCII character[s] detected in string: " + res[i] + " Converted to space[s]. Any subsequent invalid characters will be converted silently"); FitsUtil.wroteCheckingError = true; } } return res; } /** * @return Convert an array of bytes to booleans. * @param bytes * the array of bytes to get the booleans from. */ static boolean[] byteToBoolean(byte[] bytes) { boolean[] bool = new boolean[bytes.length]; for (int i = 0; i < bytes.length; i += 1) { bool[i] = bytes[i] == 'T'; } return bool; } /** * @return Find out where we are in a random access file . * @param o * the stream to get the position */ public static long findOffset(Closeable o) { if (o instanceof RandomAccess) { return ((RandomAccess) o).getFilePointer(); } else { return -1; } } /** * @return Get a stream to a URL accommodating possible redirections. Note * that if a redirection request points to a different protocol than * the original request, then the redirection is not handled * automatically. * @param url * the url to get the stream from * @param level * max levels of redirection * @throws IOException * if the operation failed */ public static InputStream getURLStream(URL url, int level) throws IOException { URLConnection conn = null; int code = -1; try { conn = url.openConnection(); if (conn instanceof HttpURLConnection) { code = ((HttpURLConnection) conn).getResponseCode(); } return conn.getInputStream(); } catch (ProtocolException e) { LOG.log(Level.WARNING, "could not connect to " + url + (code >= 0 ? " got responce-code" + code : ""), e); throw e; } } /** * @return Get the maximum length of a String in a String array. * @param strings * array of strings to check * @throws FitsException * if the operation failed */ public static int maxLength(String[] strings) throws FitsException { int max = 0; for (String element : strings) { if (element != null && element.length() > max) { max = element.length(); } } return max; } /** * Add padding to an output stream. * * @param stream * stream to pad * @param size * the current size * @throws FitsException * if the operation failed */ public static void pad(ArrayDataOutput stream, long size) throws FitsException { pad(stream, size, (byte) 0); } /** * Add padding to an output stream. * * @param stream * stream to pad * @param size * the current size * @param fill * the fill byte to use * @throws FitsException * if the operation failed */ public static void pad(ArrayDataOutput stream, long size, byte fill) throws FitsException { int len = padding(size); if (len > 0) { byte[] buf = new byte[len]; Arrays.fill(buf, fill); try { stream.write(buf); stream.flush(); } catch (Exception e) { throw new FitsException("Unable to write padding", e); } } } /** * @return How many bytes are needed to fill a 2880 block? * @param size * the size without padding */ public static int padding(int size) { return padding((long) size); } public static int padding(long size) { int mod = (int) (size % FitsFactory.FITS_BLOCK_SIZE); if (mod > 0) { mod = FitsFactory.FITS_BLOCK_SIZE - mod; } return mod; } /** * Reposition a random access stream to a requested offset. * * @param o * the closable to reposition * @param offset * the offset to position it to. * @throws FitsException * if the operation was failed or not possible */ public static void reposition(Closeable o, long offset) throws FitsException { if (o == null) { throw new FitsException("Attempt to reposition null stream"); } else if (!(o instanceof RandomAccess) || offset < 0) { throw new FitsException("Invalid attempt to reposition stream " + o + " of type " + o.getClass().getName() + " to " + offset); } try { ((RandomAccess) o).seek(offset); } catch (IOException e) { throw new FitsException("Unable to repostion stream " + o + " of type " + o.getClass().getName() + " to " + offset + " Exception:" + e.getMessage(), e); } } /** * Convert an array of Strings to bytes. * * @return the resulting bytes * @param stringArray * the array with Strings * @param maxLen * the max length (in bytes) of every String */ public static byte[] stringsToByteArray(String[] stringArray, int maxLen) { byte[] res = new byte[stringArray.length * maxLen]; for (int i = 0; i < stringArray.length; i += 1) { byte[] bstr; if (stringArray[i] == null) { bstr = new byte[0]; } else { bstr = AsciiFuncs.getBytes(stringArray[i]); } int cnt = bstr.length; if (cnt > maxLen) { cnt = maxLen; } System.arraycopy(bstr, 0, res, i * maxLen, cnt); for (int j = cnt; j < maxLen; j += 1) { res[i * maxLen + j] = (byte) ' '; } } return res; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/Header.java000066400000000000000000001550611310063650500254410ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.COMMENT; import static nom.tam.fits.header.Standard.END; import static nom.tam.fits.header.Standard.EXTEND; import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.GROUPS; import static nom.tam.fits.header.Standard.HISTORY; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.fits.header.Standard.SIMPLE; import static nom.tam.fits.header.Standard.TFIELDS; import static nom.tam.fits.header.Standard.XTENSION; import static nom.tam.fits.header.Standard.XTENSION_BINTABLE; import static nom.tam.fits.header.extra.CXCExt.LONGSTRN; import java.io.EOFException; import java.io.IOException; import java.io.PrintStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.fits.FitsFactory.FitsSettings; import nom.tam.fits.header.IFitsHeader; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.AsciiFuncs; import nom.tam.util.Cursor; import nom.tam.util.FitsIO; import nom.tam.util.HashedList; import nom.tam.util.RandomAccess; /** * This class describes methods to access and manipulate the header for a FITS * HDU. This class does not include code specific to particular types of HDU. As * of version 1.1 this class supports the long keyword convention which allows * long string keyword values to be split among multiple keywords * *
 *    KEY        = 'ABC&'   /A comment
 *    CONTINUE      'DEF&'  / Another comment
 *    CONTINUE      'GHIJKL '
 * 
* * The methods getStringValue(key), addValue(key,value,comment) and * deleteCard(key) will get, create/update and delete long string values if the * longStringsEnabled flag is set. This flag is set automatically when a FITS * header with a LONGSTRN card is found. The value is not checked. It may also * be set/unset using the static method setLongStringsEnabled(boolean). [So if a * user wishes to ensure that it is not set, it should be unset after any header * is read] When long strings are found in the FITS header users should be * careful not to interpose new header cards within a long value sequence. When * writing long strings, the comment is included in the last card. If a user is * writing long strings, a the keyword LONGSTRN = 'OGIP 1.0' should be added to * the FITS header, but this is not done automatically for the user. */ public class Header implements FitsElement { private static final int MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER = 4; private static final int MAX_CARDS_PER_HEADER = FitsFactory.FITS_BLOCK_SIZE / HeaderCard.FITS_HEADER_CARD_SIZE; private static final Logger LOG = Logger.getLogger(Header.class.getName()); /** * The actual header data stored as a HashedList of HeaderCard's. */ private final HashedList cards = new HashedList(); /** * This iterator allows one to run through the list. */ private Cursor iter = this.cards.iterator(0); /** Offset of this Header in the FITS file */ private long fileOffset = -1; private List duplicates; /** Input descriptor last time header was read */ private ArrayDataInput input; /** * Number of cards in header before duplicates were removed. A user may want * to know how large the actual FITS header was on input. Since the keyword * hash removes duplicate keys the internal size may be smaller. Added by * Booth Hartley (IPAC/Caltech). */ private int originalCardCount = 0; // RBH ADDED /** * the sorter used to sort the header cards defore writing the header. */ private Comparator headerSorter = new HeaderOrder(); /** * Create a header by reading the information from the input stream. * * @param dis * The input stream to read the data from. * @return null if there was a problem with the header; * otherwise return the header read from the input stream. * @throws TruncatedFileException * if the stream ended prematurely * @throws IOException * if the header could not be read. */ public static Header readHeader(ArrayDataInput dis) throws TruncatedFileException, IOException { Header myHeader = new Header(); try { myHeader.read(dis); } catch (EOFException e) { if (e.getCause() instanceof TruncatedFileException) { throw e; } // An EOF exception is thrown only if the EOF was detected // when reading the first card. In this case we want // to return a null. return null; } return myHeader; } /** * please use {@link FitsFactory#setLongStringsEnabled(boolean)} instead. * * @param flag * the new value for long-string enabling. */ @Deprecated public static void setLongStringsEnabled(boolean flag) { FitsFactory.setLongStringsEnabled(flag); } /** Create an empty header */ public Header() { super(); } /** * Create a header and populate it from the input stream * * @param is * The input stream where header information is expected. * @throws IOException * if the header could not be read. * @throws TruncatedFileException * if the stream ended prematurely */ public Header(ArrayDataInput is) throws TruncatedFileException, IOException { read(is); } /** * Create a header which points to the given data object. * * @param o * The data object to be described. * @throws FitsException * if the data was not valid for this header. */ public Header(Data o) throws FitsException { o.fillHeader(this); } /** * Create a header and initialize it with a vector of strings. * * @param newCards * Card images to be placed in the header. */ public Header(String[] newCards) { for (String newCard : newCards) { this.cards.add(HeaderCard.create(newCard)); } } /** * Add a card image to the header. * * @param fcard * The card to be added. */ public void addLine(HeaderCard fcard) { if (fcard != null) { this.iter.add(fcard); } } /** * Add or replace a key with the given boolean value and comment. * * @param key * The header key. * @param val * The boolean value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, boolean val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, double val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, int val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, long val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given string value and comment. * * @param key * The header key. * @param val * The string value. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(IFitsHeader key, String val) throws HeaderCardException { addValue(key.key(), val, key.comment()); } /** * Add or replace a key with the given bigdecimal value and comment. Note * that float values will be promoted to doubles. * * @param key * The header key. * @param val * The bigDecimal value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, BigDecimal val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given BigInteger value and comment. Note * that float values will be promoted to doubles. * * @param key * The header key. * @param val * The BigInteger value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, BigInteger val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given boolean value and comment. * * @param key * The header key. * @param val * The boolean value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, boolean val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @param precision * The fixed number of decimal places to show. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, double val, int precision, String comment) throws HeaderCardException { this.iter.add(new HeaderCard(key, val, precision, comment)); } /** * Add or replace a key with the given double value and comment. Note that * float values will be promoted to doubles. * * @param key * The header key. * @param val * The double value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, double val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given long value and comment. Note that * int's will be promoted to long's. * * @param key * The header key. * @param val * The long value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, long val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * Add or replace a key with the given string value and comment. * * @param key * The header key. * @param val * The string value. * @param comment * A comment to append to the card. * @throws HeaderCardException * If the parameters cannot build a valid FITS card. */ public void addValue(String key, String val, String comment) throws HeaderCardException { addHeaderCard(key, new HeaderCard(key, val, comment)); } /** * get a builder for filling the header cards using the builder pattern. * * @param key * the key for the first card. * @return the builder for header cards. */ public HeaderCardBuilder card(IFitsHeader key) { return new HeaderCardBuilder(this, key); } /** * Tests if the specified keyword is present in this table. * * @param key * the keyword to be found. * @return true if the specified keyword is present in this * table; false otherwise. */ public final boolean containsKey(IFitsHeader key) { return this.cards.containsKey(key.key()); } /** * Tests if the specified keyword is present in this table. * * @param key * the keyword to be found. * @return true if the specified keyword is present in this * table; false otherwise. */ public final boolean containsKey(String key) { return this.cards.containsKey(key); } /** * Delete the card associated with the given key. Nothing occurs if the key * is not found. * * @param key * The header key. */ public void deleteKey(IFitsHeader key) { deleteKey(key.key()); } /** * Delete the card associated with the given key. Nothing occurs if the key * is not found. * * @param key * The header key. */ public void deleteKey(String key) { if (containsKey(key)) { this.iter.setKey(key); if (this.iter.hasNext()) { this.iter.next(); this.iter.remove(); } } } /** * Print the header to a given stream. * * @param ps * the stream to which the card images are dumped. */ public void dumpHeader(PrintStream ps) { this.iter = iterator(); while (this.iter.hasNext()) { ps.println(this.iter.next()); } } /** * Find the card associated with a given key. If found this sets the mark to * the card, otherwise it unsets the mark. * * @param key * The header key. * @return null if the keyword could not be found; return the * HeaderCard object otherwise. */ public HeaderCard findCard(IFitsHeader key) { return this.findCard(key.key()); } /** * Find the card associated with a given key. If found this sets the mark to * the card, otherwise it unsets the mark. * * @param key * The header key. * @return null if the keyword could not be found; return the * HeaderCard object otherwise. */ public HeaderCard findCard(String key) { HeaderCard card = this.cards.get(key); if (card != null) { this.iter.setKey(key); } return card; } /** * Find the card associated with a given key. * * @param key * The header key. * @return null if the keyword could not be found; return the * card image otherwise. */ public String findKey(String key) { HeaderCard card = findCard(key); if (card == null) { return null; } else { return card.toString(); } } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public BigDecimal getBigDecimalValue(IFitsHeader key) { return getBigDecimalValue(key.key()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public BigDecimal getBigDecimalValue(String key) { return getBigDecimalValue(key, BigDecimal.ZERO); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public BigDecimal getBigDecimalValue(String key, BigDecimal dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigDecimal.class, dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public BigInteger getBigIntegerValue(IFitsHeader key, BigInteger dft) { return getBigIntegerValue(key.key(), dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public BigInteger getBigIntegerValue(String key) { return getBigIntegerValue(key, BigInteger.ZERO); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public BigInteger getBigIntegerValue(String key, BigInteger dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigInteger.class, dft); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @return The value found, or false if not found or if the keyword is not a * logical keyword. */ public boolean getBooleanValue(IFitsHeader key) { return getBooleanValue(key.key()); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @param dft * The value to be returned if the key cannot be found or if the * parameter does not seem to be a boolean. * @return the associated value. */ public boolean getBooleanValue(IFitsHeader key, boolean dft) { return getBooleanValue(key.key(), dft); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @return The value found, or false if not found or if the keyword is not a * logical keyword. */ public boolean getBooleanValue(String key) { return getBooleanValue(key, false); } /** * Get the boolean value associated with the given key. * * @param key * The header key. * @param dft * The value to be returned if the key cannot be found or if the * parameter does not seem to be a boolean. * @return the associated value. */ public boolean getBooleanValue(String key, boolean dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Boolean.class, dft).booleanValue(); } /** * Get the n'th card image in the header * * @param n * the card index to get * @return the card image; return null if the n'th card does * not exist. * @deprecated An iterator from {@link #iterator(int)} or * {@link #iterator()} should be used for sequential access to * the header. */ @Deprecated public String getCard(int n) { if (n >= 0 && n < this.cards.size()) { return this.cards.get(n).toString(); } return null; } /** * Return the size of the data including any needed padding. * * @return the data segment size including any needed padding. */ public long getDataSize() { return FitsUtil.addPadding(trueDataSize()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public double getDoubleValue(IFitsHeader key) { return getDoubleValue(key.key()); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public double getDoubleValue(IFitsHeader key, double dft) { return getDoubleValue(key.key(), dft); } /** * Get the double value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public double getDoubleValue(String key) { return getDoubleValue(key, 0.); } /** * Get the double value associated with the given key. * * @param key * The header key. * @param dft * The default value to return if the key cannot be found. * @return the associated value. */ public double getDoubleValue(String key, double dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Double.class, dft).doubleValue(); } /** * @return the list of duplicate cards. Note that when the header is read * in, only the last entry for a given keyword is retained in the * active header. This method returns earlier cards that have been * discarded in the order in which they were encountered in the * header. It is possible for there to be many cards with the same * keyword in this list. */ public List getDuplicates() { return this.duplicates; } /** * @return Get the offset of this header */ @Override public long getFileOffset() { return this.fileOffset; } /** * Get the float value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public float getFloatValue(IFitsHeader key) { return getFloatValue(key.key()); } /** * @return the float value associated with the given key. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public float getFloatValue(IFitsHeader key, float dft) { return (float) getDoubleValue(key, dft); } /** * Get the float value associated with the given key. * * @param key * The header key. * @return The associated value or 0.0 if not found. */ public float getFloatValue(String key) { return (float) getDoubleValue(key); } /** * @return the float value associated with the given key. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public float getFloatValue(String key, float dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Float.class, dft).floatValue(); } /** * Get the int value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public int getIntValue(IFitsHeader key) { return (int) getLongValue(key); } /** * @return the value associated with the key as an int. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public int getIntValue(IFitsHeader key, int dft) { return (int) getLongValue(key, dft); } /** * Get the int value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public int getIntValue(String key) { return (int) getLongValue(key); } /** * @return the value associated with the key as an int. * @param key * The header key. * @param dft * The value to be returned if the key is not found. */ public int getIntValue(String key, int dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Integer.class, dft).intValue(); } /** * Get the n'th key in the header. * * @param n * the index of the key * @return the card image; return null if the n'th key does not * exist. * @deprecated An iterator from {@link #iterator(int)} or * {@link #iterator()} should be used for sequential access to * the header. */ @Deprecated public String getKey(int n) { if (n >= 0 && n < this.cards.size()) { return this.cards.get(n).getKey(); } return null; } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public long getLongValue(IFitsHeader key) { return getLongValue(key.key()); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public long getLongValue(IFitsHeader key, long dft) { return getLongValue(key.key(), dft); } /** * Get the long value associated with the given key. * * @param key * The header key. * @return The associated value or 0 if not found. */ public long getLongValue(String key) { return getLongValue(key, 0L); } /** * Get the long value associated with the given key. * * @param key * The header key. * @param dft * The default value to be returned if the key cannot be found. * @return the associated value. */ public long getLongValue(String key, long dft) { HeaderCard fcard = findCard(key); if (fcard == null) { return dft; } return fcard.getValue(Long.class, dft).longValue(); } /** * @return the number of cards in the header */ public int getNumberOfCards() { return this.cards.size(); } /** * @return the number of physical cards in the header. */ public int getNumberOfPhysicalCards() { int count = 0; for (HeaderCard card : this.cards) { count += card.cardSize(); } return count; } /** * @return the size of the original header in bytes. */ public long getOriginalSize() { return FitsUtil.addPadding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * @return the size of the header in bytes */ @Override public long getSize() { return headerSize(); } public String getStringValue(IFitsHeader header) { return getStringValue(header.key()); } /** * Get the String value associated with the given key. * * @param key * The header key. * @return The associated value or null if not found or if the value is not * a string. */ public String getStringValue(String key) { HeaderCard fcard = findCard(key); if (fcard == null || !fcard.isStringValue()) { return null; } return fcard.getValue(); } /** * @return Were duplicate header keys found when this record was read in? */ public boolean hadDuplicates() { return this.duplicates != null; } /** * Add a COMMENT line. * * @param value * The comment. * @exception HeaderCardException * If the parameter is not a valid FITS comment. */ public void insertComment(String value) throws HeaderCardException { insertCommentStyle(COMMENT.key(), value); } /** * Add a line to the header using the COMMENT style, i.e., no '=' in column * 9. * * @param header * The comment style header. * @param value * A string to follow the header. */ public void insertCommentStyle(String header, String value) { this.iter.add(HeaderCard.saveNewHeaderCard(header, value, false)); } /** * Add a HISTORY line. * * @param value * The history record. * @exception HeaderCardException * If the parameter is not a valid FITS comment. */ public void insertHistory(String value) throws HeaderCardException { insertCommentStyle(HISTORY.key(), value); } /** @return an iterator over the header cards */ public Cursor iterator() { return this.cards.iterator(0); } /** * @return an iterator over the header cards starting at an index * @param index * the card index to start the iterator */ public Cursor iterator(int index) { return this.cards.iterator(index); } /** * @return Create the data element corresponding to the current header * @throws FitsException * if the header did not contain enough information to detect * the type of the data */ public Data makeData() throws FitsException { return FitsFactory.dataFactory(this); } /** * @return the next card in the Header using the current iterator */ public HeaderCard nextCard() { if (this.iter.hasNext()) { return this.iter.next(); } else { return null; } } /** * Create a header which points to the given data object. * * @param o * The data object to be described. * @throws FitsException * if the data was not valid for this header. * @deprecated Use the appropriate Header constructor. */ @Deprecated public void pointToData(Data o) throws FitsException { o.fillHeader(this); } /** * Read a stream for header data. * * @param dis * The input stream to read the data from. * @throws TruncatedFileException * the the stream ended prematurely * @throws IOException * if the operation failed */ @Override public void read(ArrayDataInput dis) throws TruncatedFileException, IOException { if (dis instanceof RandomAccess) { this.fileOffset = FitsUtil.findOffset(dis); } else { this.fileOffset = -1; } boolean firstCard = true; HeaderCardCountingArrayDataInput cardCountingArray = new HeaderCardCountingArrayDataInput(dis); try { while (true) { HeaderCard fcard = new HeaderCard(cardCountingArray); String key = fcard.getKey(); if (firstCard) { checkFirstCard(key); firstCard = false; } if (key != null && this.cards.containsKey(key)) { addDuplicate(this.cards.get(key)); } // We don't check the value here. If the user // wants to be sure that long strings are disabled, // they can call setLongStringsEnabled(false) after // reading the header. if (LONGSTRN.key().equals(key)) { FitsFactory.setLongStringsEnabled(true); } // save card addLine(fcard); if (END.key().equals(key)) { break; // Out of reading the header. } } } catch (EOFException e) { if (!firstCard) { throw new IOException("Invalid FITS Header:", new TruncatedFileException(e.getMessage())); } throw e; } catch (TruncatedFileException e) { if (firstCard && FitsFactory.getAllowTerminalJunk()) { EOFException eofException = new EOFException("First card truncated"); eofException.initCause(e); throw eofException; } throw new IOException("Invalid FITS Header:", new TruncatedFileException(e.getMessage())); } catch (Exception e) { throw new IOException("Invalid FITS Header", e); } if (this.fileOffset >= 0) { this.input = dis; } this.originalCardCount = cardCountingArray.getPhysicalCardsRead(); // Read to the end of the current FITS block. // try { dis.skipAllBytes(FitsUtil.padding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE)); } catch (IOException e) { throw new TruncatedFileException("Failed to skip " + FitsUtil.padding(this.originalCardCount * HeaderCard.FITS_HEADER_CARD_SIZE) + " bytes", e); } } /** * Delete a key. * * @param key * The header key. * @throws HeaderCardException * if the operation failed * @deprecated see {@link #deleteKey(String)} */ @Deprecated public void removeCard(String key) throws HeaderCardException { deleteKey(key); } /** Reset the file pointer to the beginning of the header */ @Override public boolean reset() { try { FitsUtil.reposition(this.input, this.fileOffset); return true; } catch (Exception e) { LOG.log(Level.WARNING, "Exception while repositioning " + this.input, e); return false; } } /** * Indicate that we can use the current internal size of the Header as the * 'original' size (e.g., perhaps we've rewritten the header to disk). Note * that affects the results of rewriteable(), so users should not call this * method unless the underlying data has actually been updated. */ public void resetOriginalSize() { this.originalCardCount = getNumberOfPhysicalCards(); } /** Rewrite the header. */ @Override public void rewrite() throws FitsException, IOException { ArrayDataOutput dos = (ArrayDataOutput) this.input; if (rewriteable()) { FitsUtil.reposition(dos, this.fileOffset); write(dos); dos.flush(); } else { throw new FitsException("Invalid attempt to rewrite Header."); } } @Override public boolean rewriteable() { return this.fileOffset >= 0 && this.input instanceof ArrayDataOutput && // (getNumberOfPhysicalCards() + MAX_CARDS_PER_HEADER - 1) / MAX_CARDS_PER_HEADER == // (this.originalCardCount + MAX_CARDS_PER_HEADER - 1) / MAX_CARDS_PER_HEADER; } /** * Set the BITPIX value for the header. The following values are permitted * by FITS conventions: *
    *
  • 8 -- signed byte data. Also used for tables.
  • *
  • 16 -- signed short data.
  • *
  • 32 -- signed int data.
  • *
  • 64 -- signed long data.
  • *
  • -32 -- IEEE 32 bit floating point numbers.
  • *
  • -64 -- IEEE 64 bit floating point numbers.
  • *
* * @param val * The value set by the user. */ public void setBitpix(int val) { this.iter = iterator(); this.iter.next(); this.iter.add(HeaderCard.saveNewHeaderCard(BITPIX.key(), BITPIX.comment(), false).setValue(val)); } /** * Overwite the default header card sorter. * * @param headerSorter * the sorter tu use or null to disable sorting */ public void setHeaderSorter(Comparator headerSorter) { this.headerSorter = headerSorter; } /** * Set the value of the NAXIS keyword * * @param val * The dimensionality of the data. */ public void setNaxes(int val) { this.iter.setKey(BITPIX.key()); if (this.iter.hasNext()) { this.iter.next(); } this.iter.add(HeaderCard.saveNewHeaderCard(NAXIS.key(), NAXIS.comment(), false).setValue(val)); } /** * Set the dimension for a given axis. * * @param axis * The axis being set. * @param dim * The dimension */ public void setNaxis(int axis, int dim) { if (axis <= 0) { LOG.warning("setNaxis ignored because axis less than 0"); return; } if (axis == 1) { this.iter.setKey(NAXIS.key()); } else if (axis > 1) { this.iter.setKey(NAXISn.n(axis - 1).key()); } if (this.iter.hasNext()) { this.iter.next(); } IFitsHeader naxisKey = NAXISn.n(axis); this.iter.add(HeaderCard.saveNewHeaderCard(naxisKey.key(), naxisKey.comment(), false).setValue(dim)); } /** * Set the SIMPLE keyword to the given value. * * @param val * The boolean value -- Should be true for FITS data. */ public void setSimple(boolean val) { deleteKey(SIMPLE); deleteKey(XTENSION); // If we're flipping back to and from the primary header // we need to add in the EXTEND keyword whenever we become // a primary, because it's not permitted in the extensions // (at least not where it needs to be in the primary array). if (findCard(NAXIS) != null) { int nax = getIntValue(NAXIS); this.iter = iterator(); if (findCard(NAXISn.n(nax)) != null) { this.iter.next(); deleteKey(EXTEND); this.iter.add(HeaderCard.saveNewHeaderCard(EXTEND.key(), EXTEND.comment(), false).setValue(true)); } } this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(SIMPLE.key(), SIMPLE.comment(), false).setValue(val)); } /** * Set the XTENSION keyword to the given value. * * @param val * The name of the extension. */ public void setXtension(String val) { deleteKey(SIMPLE); deleteKey(XTENSION); deleteKey(EXTEND); this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(XTENSION.key(), XTENSION.comment(), true).setValue(val)); } /** * @return the number of cards in the header * @deprecated use {@link #getNumberOfCards()}. The units of the size of the * header may be unclear. */ @Deprecated public int size() { return this.cards.size(); } /** * Update a line in the header * * @param key * The key of the card to be replaced. * @param card * A new card * @throws HeaderCardException * if the operation failed */ public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException { deleteKey(key); this.iter.add(card); } /** * Update a line in the header * * @param key * The key of the card to be replaced. * @param card * A new card * @throws HeaderCardException * if the operation failed */ public void updateLine(String key, HeaderCard card) throws HeaderCardException { addHeaderCard(key, card); } /** * Overwrite the lines in the header. Add the new PHDU header to the current * one. If keywords appear twice, the new value and comment overwrite the * current contents. By Richard J Mathar. * * @param newHdr * the list of new header data lines to replace the current ones. * @throws HeaderCardException * if the operation failed */ public void updateLines(final Header newHdr) throws HeaderCardException { Cursor j = newHdr.iterator(); while (j.hasNext()) { HeaderCard nextHCard = j.next(); // updateLine() doesn't work with COMMENT and HISTORYs because // this would allow only one COMMENT in total in each header if (nextHCard.getKey().equals(COMMENT.key())) { insertComment(nextHCard.getComment()); } else if (nextHCard.getKey().equals(HISTORY.key())) { insertHistory(nextHCard.getComment()); } else { updateLine(nextHCard.getKey(), nextHCard); } } } /** * Write the current header (including any needed padding) to the output * stream. * * @param dos * The output stream to which the data is to be written. * @throws FitsException * if the header could not be written. */ @Override public void write(ArrayDataOutput dos) throws FitsException { FitsSettings settings = FitsFactory.current(); this.fileOffset = FitsUtil.findOffset(dos); // Ensure that all cards are in the proper order. if (this.headerSorter != null) { this.cards.sort(this.headerSorter); } checkBeginning(); checkEnd(); Cursor writeIterator = this.cards.iterator(0); try { while (writeIterator.hasNext()) { HeaderCard card = writeIterator.next(); byte[] b = AsciiFuncs.getBytes(card.toString(settings)); dos.write(b); } FitsUtil.pad(dos, getNumberOfPhysicalCards() * HeaderCard.FITS_HEADER_CARD_SIZE, (byte) ' '); dos.flush(); } catch (IOException e) { throw new FitsException("IO Error writing header: " + e); } } private void addDuplicate(HeaderCard dup) { if (!COMMENT.key().equals(dup.getKey()) && !HISTORY.key().equals(dup.getKey()) && !dup.getKey().trim().isEmpty()) { LOG.log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey()); if (this.duplicates == null) { this.duplicates = new ArrayList(); } this.duplicates.add(dup); } } private void addHeaderCard(String key, HeaderCard card) { deleteKey(key); this.iter.add(card); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(IFitsHeader key) throws FitsException { cardCheck(key.key()); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(String key) throws FitsException { if (!this.iter.hasNext()) { throw new FitsException("Header terminates before " + key); } HeaderCard card = this.iter.next(); if (!card.getKey().equals(key)) { throw new FitsException("Key " + key + " not found where expected." + "Found " + card.getKey()); } } private void checkFirstCard(String key) throws IOException { if (key == null || !key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { if (this.fileOffset > 0 && FitsFactory.getAllowTerminalJunk()) { throw new EOFException("Not FITS format at " + this.fileOffset + ":" + key); } else { throw new IOException("Not FITS format at " + this.fileOffset + ":" + key); } } } private void doCardChecks(boolean isTable, boolean isExtension) throws FitsException { cardCheck(BITPIX); cardCheck(NAXIS); int nax = getIntValue(NAXIS); this.iter.next(); for (int i = 1; i <= nax; i += 1) { cardCheck(NAXISn.n(i)); } if (isExtension) { cardCheck(PCOUNT); cardCheck(GCOUNT); if (isTable) { cardCheck(TFIELDS); } } // This does not check for the EXTEND keyword which // if present in the primary array must immediately follow // the NAXISn. } /** * Move after the EXTEND keyword in images. Used in bug fix noted by V. * Forchi */ void afterExtend() { if (findCard(EXTEND) != null) { nextCard(); } } /** * Ensure that the header begins with a valid set of keywords. Note that we * do not check the values of these keywords. */ void checkBeginning() throws FitsException { this.iter = iterator(); if (!this.iter.hasNext()) { throw new FitsException("Empty Header"); } HeaderCard card = this.iter.next(); String key = card.getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { throw new FitsException("No SIMPLE or XTENSION at beginning of Header"); } boolean isTable = false; boolean isExtension = false; if (key.equals(XTENSION.key())) { String value = card.getValue(); if (value == null || value.isEmpty()) { throw new FitsException("Empty XTENSION keyword"); } isExtension = true; if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) { isTable = true; } } doCardChecks(isTable, isExtension); } /** * Ensure that the header has exactly one END keyword in the appropriate * location. */ void checkEnd() { // Ensure we have an END card only at the end of the // header. // this.iter = iterator(); HeaderCard card; while (this.iter.hasNext()) { card = this.iter.next(); if (!card.isKeyValuePair() && card.getKey().equals(END.key())) { this.iter.remove(); } } // End cannot have a comment this.iter.add(HeaderCard.saveNewHeaderCard(END.key(), null, false)); } /** * Return the size of the header data including padding. * * @return the header size including any needed padding. */ int headerSize() { if (!isValidHeader()) { return 0; } return FitsUtil.addPadding(getNumberOfPhysicalCards() * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * Is this a valid header. * * @return true for a valid header, false * otherwise. */ boolean isValidHeader() { if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) { return false; } this.iter = iterator(); String key = this.iter.next().getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { return false; } key = this.iter.next().getKey(); if (!key.equals(BITPIX.key())) { return false; } key = this.iter.next().getKey(); if (!key.equals(NAXIS.key())) { return false; } while (this.iter.hasNext()) { key = this.iter.next().getKey(); } return key.equals(END.key()); } /** * Create a header for a null image. */ void nullImage() { this.iter = iterator(); this.iter.add(HeaderCard.saveNewHeaderCard(SIMPLE.key(), SIMPLE.comment(), false).setValue(true)); this.iter.add(HeaderCard.saveNewHeaderCard(BITPIX.key(), BITPIX.comment(), false).setValue(BasicHDU.BITPIX_BYTE)); this.iter.add(HeaderCard.saveNewHeaderCard(NAXIS.key(), NAXIS.comment(), false).setValue(0)); this.iter.add(HeaderCard.saveNewHeaderCard(EXTEND.key(), EXTEND.comment(), false).setValue(true)); } /** * Find the end of a set of keywords describing a column or axis (or * anything else terminated by an index. This routine leaves the header * ready to add keywords after any existing keywords with the index * specified. The user should specify a prefix to a keyword that is * guaranteed to be present. */ Cursor positionAfterIndex(IFitsHeader prefix, int col) { String colnum = String.valueOf(col); this.iter.setKey(prefix.n(col).key()); if (this.iter.hasNext()) { // Bug fix (references to forward) here by Laurent Borges boolean toFar = false; while (this.iter.hasNext()) { String key = this.iter.next().getKey().trim(); if (key == null || key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) { toFar = true; break; } } if (toFar) { this.iter.prev(); // Gone one too far, so skip back an element. } } return this.iter; } /** * Replace the key with a new key. Typically this is used when deleting or * inserting columns so that TFORMx -> TFORMx-1 * * @param oldKey * The old header keyword. * @param newKey * the new header keyword. * @return true if the card was replaced. * @exception HeaderCardException * If newKey is not a valid FITS keyword. */ boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException { return replaceKey(oldKey.key(), newKey.key()); } /** * Replace the key with a new key. Typically this is used when deleting or * inserting columns so that TFORMx -> TFORMx-1 * * @param oldKey * The old header keyword. * @param newKey * the new header keyword. * @return true if the card was replaced. * @exception HeaderCardException * If newKey is not a valid FITS keyword. */ boolean replaceKey(String oldKey, String newKey) throws HeaderCardException { HeaderCard oldCard = findCard(oldKey); if (oldCard == null) { return false; } if (!this.cards.replaceKey(oldKey, newKey)) { throw new HeaderCardException("Duplicate key in replace"); } oldCard.setKey(newKey); return true; } /** * Calculate the unpadded size of the data segment from the header * information. * * @return the unpadded data segment size. */ long trueDataSize() { if (!isValidHeader()) { return 0L; } int naxis = getIntValue(NAXIS, 0); // If there are no axes then there is no data. if (naxis == 0) { return 0L; } getIntValue(BITPIX); int[] axes = new int[naxis]; for (int axis = 1; axis <= naxis; axis += 1) { axes[axis - 1] = getIntValue(NAXISn.n(axis), 0); } boolean isGroup = getBooleanValue(GROUPS, false); int pcount = getIntValue(PCOUNT, 0); int gcount = getIntValue(GCOUNT, 1); int startAxis = 0; if (isGroup && naxis > 1 && axes[0] == 0) { startAxis = 1; } long size = 1; for (int i = startAxis; i < naxis; i += 1) { size *= axes[i]; } size += pcount; size *= gcount; // Now multiply by the number of bits per pixel and // convert to bytes. size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE; return size; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderCard.java000066400000000000000000001203761310063650500262340ustar00rootroot00000000000000package nom.tam.fits; import static nom.tam.fits.header.NonStandard.CONTINUE; import static nom.tam.fits.header.Standard.COMMENT; import static nom.tam.fits.header.Standard.HISTORY; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import nom.tam.fits.FitsFactory.FitsSettings; import nom.tam.fits.header.NonStandard; import nom.tam.fits.utilities.FitsHeaderCardParser; import nom.tam.fits.utilities.FitsHeaderCardParser.ParsedValue; import nom.tam.fits.utilities.FitsLineAppender; import nom.tam.fits.utilities.FitsSubString; import nom.tam.util.ArrayDataInput; import nom.tam.util.AsciiFuncs; import nom.tam.util.BufferedDataInputStream; import nom.tam.util.CursorValue; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This class describes methods to access and manipulate the individual cards * for a FITS Header. */ public class HeaderCard implements CursorValue { private static final int SPACE_NEEDED_FOR_EQUAL_AND_TWO_BLANKS = 3; private static final double MAX_DECIMAL_VALUE_TO_USE_PLAIN_STRING = 1.0E16; private static final Logger LOG = Logger.getLogger(HeaderCard.class.getName()); private static final String CONTINUE_CARD_PREFIX = CONTINUE.key() + " '"; public static final int FITS_HEADER_CARD_SIZE = 80; private static final String HIERARCH_WITH_BLANK = NonStandard.HIERARCH.key() + " "; private static final int HIERARCH_WITH_BLANK_LENGTH = HIERARCH_WITH_BLANK.length(); private static final String HIERARCH_WITH_DOT = NonStandard.HIERARCH.key() + "."; /** * regexp for IEEE floats */ private static final Pattern IEEE_REGEX = Pattern.compile("[+-]?(?=\\d*[.eE])(?=\\.?\\d)\\d*\\.?\\d*(?:[eE][+-]?\\d+)?"); private static final BigDecimal LONG_MAX_VALUE_AS_BIG_DECIMAL = BigDecimal.valueOf(Long.MAX_VALUE); /** * regexp for numbers. */ private static final Pattern LONG_REGEX = Pattern.compile("[+-]?[0-9][0-9]*"); /** * max number of characters an integer can have. */ private static final int MAX_INTEGER_STRING_SIZE = Integer.toString(Integer.MAX_VALUE).length() - 1; /** Maximum length of a FITS keyword field */ public static final int MAX_KEYWORD_LENGTH = 8; /** * the start and end quotes of the string and the ampasant to continue the * string. */ public static final int MAX_LONG_STRING_CONTINUE_OVERHEAD = 3; /** * max number of characters a long can have. */ private static final int MAX_LONG_STRING_SIZE = Long.toString(Long.MAX_VALUE).length() - 1; /** * Maximum length of a FITS long string value field. the & for the * continuation needs one char. */ public static final int MAX_LONG_STRING_VALUE_LENGTH = HeaderCard.MAX_STRING_VALUE_LENGTH - 1; /** * if a commend needs the be specified 2 extra chars are needed to start the * comment */ public static final int MAX_LONG_STRING_VALUE_WITH_COMMENT_LENGTH = HeaderCard.MAX_LONG_STRING_VALUE_LENGTH - 2; /** * Maximum length of a FITS string value field. */ public static final int MAX_STRING_VALUE_LENGTH = HeaderCard.MAX_VALUE_LENGTH - 2; /** * Maximum length of a FITS value field. */ public static final int MAX_VALUE_LENGTH = 70; private static final int NORMAL_ALIGN_POSITION = 30; private static final int NORMAL_SMALL_STRING_ALIGN_POSITION = 19; private static final int STRING_SPLIT_POSITION_FOR_EXTRA_COMMENT_SPACE = 35; /** * The comment part of the card (set to null if there's no comment) */ private String comment; /** * A flag indicating whether or not this is a string value */ private boolean isString; /** * The keyword part of the card (set to null if there's no keyword) */ private String key; /** * Does this card represent a nullable field. ? */ private boolean nullable; /** * The value part of the card (set to null if there's no value) */ private String value; /** * @return a created HeaderCard from a FITS card string. * @param card * the 80 character card image */ public static HeaderCard create(String card) { try { return new HeaderCard(stringToArrayInputStream(card)); } catch (Exception e) { throw new IllegalArgumentException("card not legal", e); } } /** * Create a string from a BigDecimal making sure that it's not longer than * the available space. * * @param decimalValue * the decimal value to print * @param availableSpace * the space available for the value * @return the string representing the value. */ private static String dblString(BigDecimal decimalValue, int availableSpace) { return dblString(decimalValue, -1, availableSpace); } /** * Create a string from a BigDecimal making sure that it's not longer than * the available space. * * @param decimalValue * the decimal value to print * @param precision * the precision to use * @param availableSpace * the space available for the value * @return the string representing the value. */ private static String dblString(BigDecimal decimalValue, int precision, int availableSpace) { BigDecimal decimal = decimalValue; if (precision >= 0) { decimal = decimalValue.setScale(precision, RoundingMode.HALF_UP); } double absInput = Math.abs(decimalValue.doubleValue()); if (absInput > 0d && absInput < MAX_DECIMAL_VALUE_TO_USE_PLAIN_STRING) { String value = decimal.toPlainString(); if (value.length() < availableSpace) { return value; } } String value = decimalValue.toString(); while (value.length() > availableSpace) { decimal = decimalValue.setScale(decimal.scale() - 1, BigDecimal.ROUND_HALF_UP); value = decimal.toString(); } return value; } /** * Create a string from a BigDecimal making sure that it's not longer than * the available space. * * @param decimalValue * the decimal value to print * @param availableSpace * the space available for the value * @return the string representing the value. */ private static String dblString(double decimalValue, int availableSpace) { return dblString(BigDecimal.valueOf(decimalValue), -1, availableSpace); } /** * @param input * float value being converted * @param precision * the number of decimal places to show * @return Create a fixed decimal string from a double with the specified * precision. */ private static String dblString(double input, int precision, int availableSpace) { return dblString(BigDecimal.valueOf(input), precision, availableSpace); } /** * attention float to double cases are very lossy so a toString is needed to * keep the precision. proof (double)500.055f = 500.05499267578125d * * @param floatValue * the float value * @return the BigDecimal as close to the value of the float as possible */ private static BigDecimal floatToBigDecimal(float floatValue) { return new BigDecimal(floatValue, MathContext.DECIMAL32); } /** * detect the decimal type of the value, does it fit in a Double/BigInteger * or must it be a BigDecimal to keep the needed precission. * * @param value * the String value to check. * @return the type to fit the value */ private static Class getDecimalNumberType(String value) { BigDecimal bigDecimal = new BigDecimal(value); if (bigDecimal.abs().compareTo(HeaderCard.LONG_MAX_VALUE_AS_BIG_DECIMAL) > 0 && bigDecimal.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) == 0) { return BigInteger.class; } else if (bigDecimal.equals(BigDecimal.valueOf(Double.valueOf(value)))) { return Double.class; } else { return BigDecimal.class; } } private static Class getIntegerNumberType(String value) { int length = value.length(); if (value.charAt(0) == '-' || value.charAt(0) == '+') { length--; } if (length <= HeaderCard.MAX_INTEGER_STRING_SIZE) { return Integer.class; } else if (length <= HeaderCard.MAX_LONG_STRING_SIZE) { return Long.class; } else { return BigInteger.class; } } /** * Read exactly one complete fits header line from the input. * * @param dis * the data input stream to read the line * @return a string of exactly 80 characters * @throws IOException * if the input stream could not be read * @throws TruncatedFileException * is there was not a complete line available in the input. */ private static String readOneHeaderLine(HeaderCardCountingArrayDataInput dis) throws IOException, TruncatedFileException { byte[] buffer = new byte[FITS_HEADER_CARD_SIZE]; int len; int need = FITS_HEADER_CARD_SIZE; try { while (need > 0) { len = dis.in().read(buffer, FITS_HEADER_CARD_SIZE - need, need); if (len == 0) { throw new TruncatedFileException("nothing to read left"); } need -= len; } } catch (EOFException e) { if (need == FITS_HEADER_CARD_SIZE) { throw e; } throw new TruncatedFileException(e.getMessage()); } dis.cardRead(); return AsciiFuncs.asciiString(buffer); } private static int spaceAvailableForValue(String key) { return FITS_HEADER_CARD_SIZE - (Math.max(key.length(), CONTINUE.key().length()) + SPACE_NEEDED_FOR_EQUAL_AND_TWO_BLANKS); } private static ArrayDataInput stringToArrayInputStream(String card) { byte[] bytes = AsciiFuncs.getBytes(card); if (bytes.length % FITS_HEADER_CARD_SIZE != 0) { byte[] newBytes = new byte[bytes.length + FITS_HEADER_CARD_SIZE - bytes.length % FITS_HEADER_CARD_SIZE]; System.arraycopy(bytes, 0, newBytes, 0, bytes.length); Arrays.fill(newBytes, bytes.length, newBytes.length, (byte) ' '); bytes = newBytes; } return new BufferedDataInputStream(new ByteArrayInputStream(bytes)); } /** * This method is only used internally when it is sure that the creation of * the card is granted not to throw an exception * * @param key * the key for the card * @param comment * the comment for the card * @param isString * is this a string value card? * @return the new HeaderCard */ protected static HeaderCard saveNewHeaderCard(String key, String comment, boolean isString) { try { return new HeaderCard(key, null, comment, false, isString); } catch (HeaderCardException e) { LOG.log(Level.SEVERE, "Impossible Exception for internal card creation:" + key, e); throw new IllegalStateException(e); } } public HeaderCard(ArrayDataInput dis) throws TruncatedFileException, IOException { this(new HeaderCardCountingArrayDataInput(dis)); } public HeaderCard(HeaderCardCountingArrayDataInput dis) throws TruncatedFileException, IOException { this.key = null; this.value = null; this.comment = null; this.isString = false; String card = readOneHeaderLine(dis); if (FitsFactory.getUseHierarch() && card.startsWith(HIERARCH_WITH_BLANK)) { hierarchCard(card, dis); return; } // extract the key this.key = card.substring(0, MAX_KEYWORD_LENGTH).trim(); // if it is an empty key, assume the remainder of the card is a comment if (this.key.isEmpty()) { this.comment = card.substring(MAX_KEYWORD_LENGTH); return; } // Non-key/value pair lines are treated as keyed comments if (this.key.equals(COMMENT.key()) || this.key.equals(HISTORY.key()) || !card.startsWith("=", MAX_KEYWORD_LENGTH)) { this.comment = card.substring(MAX_KEYWORD_LENGTH).trim(); return; } extractValueCommentFromString(dis, card); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, BigDecimal value, String comment) throws HeaderCardException { this(key, dblString(value, spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, BigInteger value, String comment) throws HeaderCardException { this(key, dblString(new BigDecimal(value), spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, boolean value, String comment) throws HeaderCardException { this(key, value ? "T" : "F", comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param precision * Number of decimal places (fixed format). * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, double value, int precision, String comment) throws HeaderCardException { this(key, dblString(value, precision, spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, double value, String comment) throws HeaderCardException { this(key, dblString(value, spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param precision * Number of decimal places (fixed format). * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, float value, int precision, String comment) throws HeaderCardException { this(key, dblString(floatToBigDecimal(value), precision, spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, float value, String comment) throws HeaderCardException { this(key, dblString(floatToBigDecimal(value), spaceAvailableForValue(key)), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, int value, String comment) throws HeaderCardException { this(key, String.valueOf(value), comment, false, false); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword */ public HeaderCard(String key, long value, String comment) throws HeaderCardException { this(key, String.valueOf(value), comment, false, false); } /** * Create a comment style card. This constructor builds a card which has no * value. This may be either a comment style card in which case the nullable * field should be false, or a value field which has a null value, in which * case the nullable field should be true. * * @param key * The key for the comment or nullable field. * @param comment * The comment * @param nullable * Is this a nullable field or a comment-style card? * @throws HeaderCardException * for any invalid keyword or value */ public HeaderCard(String key, String comment, boolean nullable) throws HeaderCardException { this(key, null, comment, nullable, true); } /** * Create a HeaderCard from its component parts * * @param key * keyword (null for a comment) * @param value * value (null for a comment or keyword without an '=') * @param comment * comment * @exception HeaderCardException * for any invalid keyword or value */ public HeaderCard(String key, String value, String comment) throws HeaderCardException { this(key, value, comment, false, true); } /** * Create a HeaderCard from its component parts * * @param key * Keyword (null for a COMMENT) * @param value * Value * @param comment * Comment * @param nullable * Is this a nullable value card? * @exception HeaderCardException * for any invalid keyword or value */ public HeaderCard(String key, String value, String comment, boolean nullable) throws HeaderCardException { this(key, value, comment, nullable, true); } /** * Create a HeaderCard from its component parts * * @param key * Keyword (null for a COMMENT) * @param value * Value * @param comment * Comment * @param nullable * Is this a nullable value card? * @exception HeaderCardException * for any invalid keyword or value */ private HeaderCard(String key, String value, String comment, boolean nullable, boolean isString) throws HeaderCardException { this.isString = isString; if (key == null && value != null) { throw new HeaderCardException("Null keyword with non-null value"); } else if (key != null && key.length() > HeaderCard.MAX_KEYWORD_LENGTH && // (!FitsFactory.getUseHierarch() || !key.startsWith(HIERARCH_WITH_DOT))) { throw new HeaderCardException("Keyword too long"); } if (value != null) { value = value.replaceAll(" *$", ""); if (value.startsWith("'")) { if (value.charAt(value.length() - 1) != '\'') { throw new HeaderCardException("Missing end quote in string value"); } value = value.substring(1, value.length() - 1).trim(); } // Remember that quotes get doubled in the value... if (!FitsFactory.isLongStringsEnabled() && value.replace("'", "''").length() > (this.isString ? HeaderCard.MAX_STRING_VALUE_LENGTH : HeaderCard.MAX_VALUE_LENGTH)) { throw new HeaderCardException("Value too long"); } } this.key = key; this.value = value; this.comment = comment; this.nullable = nullable; } /** * @return the size of the card in blocks of 80 bytes. So normally every * card will return 1. only long stings can return more than one. */ public int cardSize() { if (this.isString && this.value != null && FitsFactory.isLongStringsEnabled()) { int maxStringValueLength = maxStringValueLength(); String stringValue = this.value.replace("'", "''"); if (stringValue.length() > maxStringValueLength) { // this is very bad for performance but it is to difficult to // keep the cardSize and the toString compatible at all times return toString().length() / FITS_HEADER_CARD_SIZE; } } return 1; } public HeaderCard copy() throws HeaderCardException { HeaderCard copy = new HeaderCard(this.key, null, this.comment, this.nullable, this.isString); copy.value = this.value; return copy; } /** * @return the comment from this card */ public String getComment() { return this.comment; } /** * @return the keyword from this card */ @Override public String getKey() { return this.key; } /** * @return the value from this card */ public String getValue() { return this.value; } /** * @param clazz * the requested class of the value * @param defaultValue * the value if the card was not present. * @param * the type of the requested class * @return the value from this card as a specific type */ public T getValue(Class clazz, T defaultValue) { if (String.class.isAssignableFrom(clazz)) { return clazz.cast(this.value); } else if (this.value == null || this.value.isEmpty()) { return defaultValue; } else if (Boolean.class.isAssignableFrom(clazz)) { return clazz.cast(getBooleanValue((Boolean) defaultValue)); } BigDecimal parsedValue; try { parsedValue = new BigDecimal(this.value); } catch (NumberFormatException e) { return defaultValue; } if (Integer.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue.intValueExact()); } else if (Long.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue.longValueExact()); } else if (Double.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue.doubleValue()); } else if (Float.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue.floatValue()); } else if (BigDecimal.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue); } else if (BigInteger.class.isAssignableFrom(clazz)) { return clazz.cast(parsedValue.toBigIntegerExact()); } else { throw new IllegalArgumentException("unsupported class " + clazz); } } /** * @return Is this a key/value card? */ public boolean isKeyValuePair() { return this.key != null && this.value != null; } /** * @return if this card contain does a string value? */ public boolean isStringValue() { return this.isString; } /** * set the comment of a card. * * @param comment * the comment to set. */ public void setComment(String comment) { this.comment = comment; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(BigDecimal update) { this.value = dblString(update, spaceAvailableForValue(this.key)); return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(boolean update) { this.value = update ? "T" : "F"; return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(double update) { this.value = dblString(update, spaceAvailableForValue(this.key)); return this; } /** * Set the value for this card. * * @param update * the new value to set * @param precision * the number of decimal places to show * @return the HeaderCard itself */ public HeaderCard setValue(double update, int precision) { this.value = dblString(update, precision, spaceAvailableForValue(this.key)); return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(float update) { this.value = dblString(floatToBigDecimal(update), spaceAvailableForValue(this.key)); return this; } /** * Set the value for this card. * * @param update * the new value to set * @param precision * the number of decimal places to show * @return the HeaderCard itself */ public HeaderCard setValue(float update, int precision) { this.value = dblString(floatToBigDecimal(update), precision, spaceAvailableForValue(this.key)); return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(int update) { this.value = String.valueOf(update); return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(long update) { this.value = String.valueOf(update); return this; } /** * Set the value for this card. * * @param update * the new value to set * @return the HeaderCard itself */ public HeaderCard setValue(String update) { this.value = update; return this; } /** * Return the modulo 80 character card image, the toString tries to preserve * as much as possible of the comment value by reducing the alignment of the * Strings if the comment is longer and if longString is enabled the string * can be split into one more card to have more space for the comment. */ @Override public String toString() { return toString(FitsFactory.current()); } /** * Same as {@link #toString()} just with a prefetched settings object * * @param settings * the settings to use for writing the header card * @return the string representing the card. */ protected String toString(final FitsSettings settings) { int alignSmallString = NORMAL_SMALL_STRING_ALIGN_POSITION; int alignPosition = NORMAL_ALIGN_POSITION; FitsLineAppender buf = new FitsLineAppender(); // start with the keyword, if there is one if (this.key != null) { if (this.key.length() > HIERARCH_WITH_BLANK_LENGTH && this.key.startsWith(HIERARCH_WITH_DOT)) { settings.getHierarchKeyFormatter().append(this.key, buf); alignSmallString = buf.length(); alignPosition = buf.length(); } else { buf.append(this.key); if (this.key.isEmpty()) { buf.append(' '); } buf.appendSpacesTo(MAX_KEYWORD_LENGTH); } } FitsSubString commentSubString = new FitsSubString(this.comment); if (FITS_HEADER_CARD_SIZE - alignPosition - MAX_LONG_STRING_CONTINUE_OVERHEAD < commentSubString.length()) { // with alignment the comment would not fit so lets make more space alignPosition = Math.max(buf.length(), FITS_HEADER_CARD_SIZE - MAX_LONG_STRING_CONTINUE_OVERHEAD - commentSubString.length()); alignSmallString = buf.length(); } boolean commentHandled = false; if (this.value != null || this.nullable) { if (settings.isSkipBlankAfterAssign()) { buf.append('='); } else { buf.append("= "); } if (this.value != null) { if (this.isString) { commentHandled = stringValueToString(alignSmallString, alignPosition, buf, commentHandled); } else { buf.appendSpacesTo(alignPosition - this.value.length()); buf.append(this.value); } } else { // Pad out a null value. buf.appendSpacesTo(alignPosition); } // is there space left for a comment? int spaceLeft = FITS_HEADER_CARD_SIZE - buf.length(); int spaceLeftInCard = spaceLeft % FITS_HEADER_CARD_SIZE; commentSubString.getAdjustedLength(spaceLeftInCard - MAX_LONG_STRING_CONTINUE_OVERHEAD); // if there is a comment, add a comment delimiter if (!commentHandled && commentSubString.length() > 0) { buf.append(" / "); } } else if (commentSubString.startsWith("=")) { buf.append(" "); } // finally, add any comment if (!commentHandled && commentSubString.length() > 0) { if (commentSubString.startsWith(" ")) { commentSubString.skip(1); } // is there space left for a comment? commentSubString.getAdjustedLength((FITS_HEADER_CARD_SIZE - buf.length()) % FITS_HEADER_CARD_SIZE); buf.append(commentSubString); commentSubString.rest(); if (commentSubString.length() > 0) { LOG.log(Level.INFO, "" + this.key + " was trimmed to fit"); } } buf.completeLine(); return buf.toString(); } /** * @return the type of the value. */ public Class valueType() { if (this.isString) { return String.class; } else if (this.value != null) { String trimedValue = this.value.trim(); if ("T".equals(trimedValue) || "F".equals(trimedValue)) { return Boolean.class; } else if (HeaderCard.LONG_REGEX.matcher(trimedValue).matches()) { return getIntegerNumberType(trimedValue); } else if (HeaderCard.IEEE_REGEX.matcher(trimedValue).find()) { return getDecimalNumberType(trimedValue); } } return null; } private void extractValueCommentFromString(HeaderCardCountingArrayDataInput dis, String card) throws IOException, TruncatedFileException { // extract the value/comment part of the string ParsedValue parsedValue = FitsHeaderCardParser.parseCardValue(card); if (FitsFactory.isLongStringsEnabled() && parsedValue.isString() && parsedValue.getValue().endsWith("&")) { longStringCard(dis, parsedValue); } else { this.value = parsedValue.getValue(); this.isString = parsedValue.isString(); this.comment = parsedValue.getComment(); if (!this.isString && this.value.indexOf('\'') >= 0) { throw new IllegalArgumentException("no single quotes allowed in values"); } } } private Boolean getBooleanValue(Boolean defaultValue) { if ("T".equals(this.value)) { return Boolean.TRUE; } else if ("F".equals(this.value)) { return Boolean.FALSE; } return defaultValue; } /** * Process HIERARCH style cards... HIERARCH LEV1 LEV2 ... = value / comment * The keyword for the card will be "HIERARCH.LEV1.LEV2..." A '/' is assumed * to start a comment. * * @param dis */ private void hierarchCard(String card, HeaderCardCountingArrayDataInput dis) throws IOException, TruncatedFileException { this.key = FitsHeaderCardParser.parseCardKey(card); extractValueCommentFromString(dis, card); } private void longStringCard(HeaderCardCountingArrayDataInput dis, ParsedValue parsedValue) throws IOException, TruncatedFileException { // ok this is a longString now read over all continues. StringBuilder longValue = new StringBuilder(); StringBuilder longComment = null; ParsedValue continueCard = parsedValue; do { if (continueCard.getValue() != null) { longValue.append(continueCard.getValue()); } if (continueCard.getComment() != null) { if (longComment == null) { longComment = new StringBuilder(); } else { longComment.append(' '); } longComment.append(continueCard.getComment()); } continueCard = null; if (longValue.length() > 0 && longValue.charAt(longValue.length() - 1) == '&') { longValue.setLength(longValue.length() - 1); dis.mark(); String card = readOneHeaderLine(dis); if (card.startsWith(CONTINUE.key())) { // extract the value/comment part of the string continueCard = FitsHeaderCardParser.parseCardValue(card); } else { // the & was part of the string put it back. longValue.append('&'); // ok move the input stream one card back. dis.reset(); } } } while (continueCard != null); this.comment = longComment == null ? null : longComment.toString(); this.value = longValue.toString(); this.isString = true; } private int maxStringValueLength() { int maxStringValueLength = HeaderCard.MAX_STRING_VALUE_LENGTH; if (FitsFactory.getUseHierarch() && getKey().length() > MAX_KEYWORD_LENGTH) { maxStringValueLength -= getKey().length() - MAX_KEYWORD_LENGTH; } return maxStringValueLength; } private boolean stringValueToString(int alignSmallString, int alignPosition, FitsLineAppender buf, boolean commentHandled) { String stringValue = this.value.replace("'", "''"); if (FitsFactory.isLongStringsEnabled() && stringValue.length() > maxStringValueLength()) { writeLongStringValue(buf, stringValue); commentHandled = true; } else { // left justify the string inside the quotes buf.append('\''); buf.append(stringValue); buf.appendSpacesTo(alignSmallString); buf.append('\''); // Now add space to the comment area starting at column // 30 buf.appendSpacesTo(alignPosition); } return commentHandled; } private void writeLongStringValue(FitsLineAppender buf, String stringValueString) { FitsSubString stringValue = new FitsSubString(stringValueString); FitsSubString commentValue = new FitsSubString(this.comment); // We assume that we've made the test so that // we need to write a long string. // We also need to be careful that single quotes don't // make the string too long and that we don't split // in the middle of a quote. stringValue.getAdjustedLength(FITS_HEADER_CARD_SIZE - buf.length() - MAX_LONG_STRING_CONTINUE_OVERHEAD); // No comment here since we're using as much of the card // as we can buf.append('\''); buf.append(stringValue); buf.append("&'"); buf.completeLine(); stringValue.rest(); if (commentValue.startsWith(" ")) { commentValue.skip(1); } while (stringValue.length() > 0) { stringValue.getAdjustedLength(MAX_LONG_STRING_VALUE_LENGTH); if (stringValue.fullLength() > MAX_LONG_STRING_VALUE_LENGTH) { buf.append(CONTINUE_CARD_PREFIX); buf.append(stringValue); buf.append("&'"); stringValue.rest(); } else { if (commentValue.length() > MAX_LONG_STRING_VALUE_WITH_COMMENT_LENGTH - stringValue.length()) { // ok comment does not fit lets give it a little more room stringValue.getAdjustedLength(STRING_SPLIT_POSITION_FOR_EXTRA_COMMENT_SPACE); if (stringValue.fullLength() > stringValue.length()) { buf.append(CONTINUE_CARD_PREFIX); buf.append(stringValue); buf.append("&'"); } else { buf.append(CONTINUE_CARD_PREFIX); buf.append(stringValue); buf.append("'"); } int spaceForComment = buf.spaceLeftInLine() - MAX_LONG_STRING_CONTINUE_OVERHEAD; commentValue.getAdjustedLength(spaceForComment); } else { buf.append(CONTINUE_CARD_PREFIX); buf.append(stringValue); buf.append('\''); } if (commentValue.length() > 0) { buf.append(" / "); buf.append(commentValue); commentValue.rest(); } buf.completeLine(); stringValue.rest(); } } } /** * Set the key. */ void setKey(String newKey) { this.key = newKey; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderCardBuilder.java000066400000000000000000000226421310063650500275400ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import nom.tam.fits.header.IFitsHeader; /** * builder pattern implementation for easy readable header card creation. * * @author nir */ public class HeaderCardBuilder { /** * the header to fill. */ private final Header header; /** * the current card to fill. */ private HeaderCard card; /** * the current selected key. */ private IFitsHeader key; /** * scale to use for decimal values. */ private int scale = -1; /** * constructor to the header card builder. * * @param header * the header to fill. * @param key * the first header card to set. */ protected HeaderCardBuilder(Header header, IFitsHeader key) { this.header = header; card(key); } /** * get the current build card of the builder. * * @return the current card */ public HeaderCard card() { return this.card; } /** * switch focus to the card with the specified key. If the card does not * exist the card will be created when the value or the comment is set. * * @param newKey * the new card to set * @return this */ public HeaderCardBuilder card(IFitsHeader newKey) { this.key = newKey; this.card = this.header.findCard(this.key); return this; } /** * set the comment of the current card. If the card does not exist yet the * card is created with a null value, if the card needs a value use the * value setter first! * * @param newComment * the new comment to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder comment(String newComment) throws HeaderCardException { if (this.card == null) { this.card = new HeaderCard(this.key.key(), (String) null, null); this.header.addLine(this.card); } this.card.setComment(newComment); return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(boolean newValue) throws HeaderCardException { if (this.card == null) { this.card = new HeaderCard(this.key.key(), newValue, null); this.header.addLine(this.card); } else { this.card.setValue(newValue); } return this; } /** * set the value of the current card. If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(Date newValue) throws HeaderCardException { return value(FitsDate.getFitsDateString(newValue)); } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(double newValue) throws HeaderCardException { if (this.card == null) { if (scale >= 0) { this.card = new HeaderCard(this.key.key(), newValue, scale, null); } else { this.card = new HeaderCard(this.key.key(), newValue, null); } this.header.addLine(this.card); } else { if (scale >= 0) { this.card.setValue(newValue, scale); } else { this.card.setValue(newValue); } } return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(BigDecimal newValue) throws HeaderCardException { final BigDecimal scaledValue; if (scale >= 0) { scaledValue = newValue.setScale(scale, RoundingMode.HALF_UP); } else { scaledValue = newValue; } if (this.card == null) { this.card = new HeaderCard(this.key.key(), scaledValue, null); this.header.addLine(this.card); } else { this.card.setValue(scaledValue); } return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(float newValue) throws HeaderCardException { if (this.card == null) { if (scale >= 0) { this.card = new HeaderCard(this.key.key(), newValue, scale, null); } else { this.card = new HeaderCard(this.key.key(), newValue, null); } this.header.addLine(this.card); } else { if (scale >= 0) { this.card.setValue(newValue, scale); } else { this.card.setValue(newValue); } } return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(int newValue) throws HeaderCardException { if (this.card == null) { this.card = new HeaderCard(this.key.key(), newValue, null); this.header.addLine(this.card); } else { this.card.setValue(newValue); } return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(long newValue) throws HeaderCardException { if (this.card == null) { this.card = new HeaderCard(this.key.key(), newValue, null); this.header.addLine(this.card); } else { this.card.setValue(newValue); } return this; } /** * set the value of the current card.If the card did not exist yet the card * will be created. * * @param newValue * the new value to set. * @return this * @throws HeaderCardException * if the card creation failed. */ public HeaderCardBuilder value(String newValue) throws HeaderCardException { if (this.card == null) { this.card = new HeaderCard(this.key.key(), newValue, null); this.header.addLine(this.card); } else { this.card.setValue(newValue); } return this; } /** * set the scale for the following decimal values. * * @param newScale * the new scale to use * @return this */ public HeaderCardBuilder scale(int newScale) { this.scale = newScale; return this; } /** * use no scale for the following decimal values. * * @return this */ public HeaderCardBuilder noScale() { this.scale = -1; return this; } /** * @return the filled header. */ public Header header() { return header; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderCardCountingArrayDataInput.java000066400000000000000000000056001310063650500325440ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import nom.tam.util.ArrayDataInput; /** * A helper class to keep track of the number of physical cards for a logical * card. * * @author Richard van Nieuwenhoven */ public class HeaderCardCountingArrayDataInput { /** * the input stream. */ private final ArrayDataInput input; /** * the number of 80 byte cards read. */ private int physicalCardsRead; private int markedPhysicalCardsRead; protected HeaderCardCountingArrayDataInput(ArrayDataInput input) { this.input = input; } /** * @return the number of cards realy read form the stream */ protected int getPhysicalCardsRead() { return physicalCardsRead; } /** * @return the stream to read the cards from */ protected ArrayDataInput in() { return input; } /** * report a readed card. */ public void cardRead() { physicalCardsRead++; } /** * mark the current position in the stream. * * @throws IOException * if the underlaying stream does not allow the mark. */ public void mark() throws IOException { input.mark(HeaderCard.FITS_HEADER_CARD_SIZE); markedPhysicalCardsRead = physicalCardsRead; } /** * reset the stream th the last marked prosition. * * @throws IOException * if the underlaying stream does not allow the mark. */ public void reset() throws IOException { input.reset(); physicalCardsRead = markedPhysicalCardsRead; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderCardException.java000066400000000000000000000031141310063650500301010ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /* This class was contributed by David Glowacki */ public class HeaderCardException extends FitsException { /** * serial version UID. */ private static final long serialVersionUID = 1L; public HeaderCardException(String s) { super(s); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderCommentsMap.java000066400000000000000000000062061310063650500276010ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.header.Standard; /** * This class provides a modifiable map in which the comment fields for FITS * header keywords produced by this library are set. The map is a simple String * -> String map where the key Strings are normally class:keyword:id where * class is the class name where the keyword is set, keyword is the keyword set * and id is an integer used to distinguish multiple instances. Most users need * not worry about this class, but users who wish to customize the appearance of * FITS files may update the map. The code itself is likely to be needed to * understand which values in the map must be modified. */ @Deprecated public final class HeaderCommentsMap { @Deprecated public static void deleteComment(String key) { key = simplyfyKey(key); for (Standard value : Standard.values()) { value.setCommentByKey(key, ""); } } @Deprecated public static String getComment(String key) { key = simplyfyKey(key); for (Standard value : Standard.values()) { String comment = value.getCommentByKey(key); if (comment != null) { return comment; } } return null; } private static String simplyfyKey(String key) { int firstDbPoint = key.indexOf(':'); if (firstDbPoint > 0) { int secondDoublePoint = key.indexOf(':', firstDbPoint + 1); if (secondDoublePoint > 0) { return key.substring(0, secondDoublePoint); } } return key; } @Deprecated public static void updateComment(String key, String comment) { key = simplyfyKey(key); for (Standard value : Standard.values()) { value.setCommentByKey(key, comment); } } private HeaderCommentsMap() { } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/HeaderOrder.java000066400000000000000000000135261310063650500264340ustar00rootroot00000000000000package nom.tam.fits; import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.BLOCKED; import static nom.tam.fits.header.Standard.END; import static nom.tam.fits.header.Standard.EXTEND; import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.fits.header.Standard.SIMPLE; import static nom.tam.fits.header.Standard.TFIELDS; import static nom.tam.fits.header.Standard.THEAP; import static nom.tam.fits.header.Standard.XTENSION; import java.io.Serializable; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This class implements a comparator which ensures that FITS keywords are * written out in a proper order. */ public class HeaderOrder implements java.util.Comparator, Serializable { /** * Serialization id. */ private static final long serialVersionUID = 1L; /** * Which order should the cards indexed by these keys be written out? This * method assumes that the arguments are either the FITS Header keywords as * strings, and some other type (or null) for comment style keywords. * * @return -1 if the first argument should be written first
* 1 if the second argument should be written first
* 0 if either is legal. */ @Override public int compare(String c1, String c2) { // Note that we look at each of the ordered FITS keywords in the // required // order. // Equals are equal if (c1.equals(c2)) { return 0; } // Now search in the order in which cards must appear // in the header. if (c1.equals(SIMPLE.key()) || c1.equals(XTENSION.key())) { return -1; } else if (c2.equals(SIMPLE.key()) || c2.equals(XTENSION.key())) { return 1; } else if (c1.equals(BITPIX.key())) { return -1; } else if (c2.equals(BITPIX.key())) { return 1; } else if (c1.equals(NAXIS.key())) { return -1; } else if (c2.equals(NAXIS.key())) { return 1; } // Check the NAXISn cards. These must // be in axis order. final int naxisNc1 = naxisN(c1); final int naxisNc2 = naxisN(c2); if (naxisNc1 > 0) { if (naxisNc2 > 0) { if (naxisNc1 < naxisNc2) { return -1; } else { return 1; } } return -1; } else if (naxisNc2 > 0) { return 1; } // The EXTEND keyword is no longer required in the FITS standard // but in earlier versions of the standard it was required to // be here if present in the primary data array. if (c1.equals(EXTEND.key())) { return -1; } else if (c2.equals(EXTEND.key())) { return 1; } else if (c1.equals(PCOUNT.key())) { return -1; } else if (c2.equals(PCOUNT.key())) { return 1; } else if (c1.equals(GCOUNT.key())) { return -1; } else if (c2.equals(GCOUNT.key())) { return 1; } else if (c1.equals(TFIELDS.key())) { return -1; } else if (c2.equals(TFIELDS.key())) { return 1; } // In principal this only needs to be in the first 36 cards, // but we put it here since it's convenient. BLOCKED is // deprecated currently. if (c1.equals(BLOCKED.key())) { return -1; } else if (c2.equals(BLOCKED.key())) { return 1; } // Note that this must be at the end, so the // values returned are inverted. THEAP is put to the end of the file // because os a bug in cfitsio that causes confusion when the header // appears befor any compression headers. if (c1.equals(THEAP.key())) { return 1; } else if (c2.equals(THEAP.key())) { return -1; } else if (c1.equals(END.key())) { return 1; } else if (c2.equals(END.key())) { return -1; } // All other cards can be in any order. return 0; } /** Find the index for NAXISn keywords */ private static int naxisN(String key) { int startOfNumber = NAXIS.key().length(); if (key.length() > startOfNumber && key.startsWith(NAXIS.key()) && Character.isDigit(key.charAt(startOfNumber))) { return Integer.parseInt(key.substring(startOfNumber)); } return -1; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/ImageData.java000066400000000000000000000313041310063650500260560ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.EXTEND; import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.util.LoggerHelper.getLogger; import java.io.EOFException; import java.io.IOException; import java.nio.Buffer; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.fits.header.Standard; import nom.tam.image.StandardImageTiler; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; import nom.tam.util.RandomAccess; import nom.tam.util.array.MultiArrayIterator; import nom.tam.util.type.PrimitiveType; import nom.tam.util.type.PrimitiveTypeHandler; import nom.tam.util.type.PrimitiveTypes; /** * This class instantiates FITS primary HDU and IMAGE extension data. * Essentially these data are a primitive multi-dimensional array. *

* Starting in version 0.9 of the FITS library, this routine allows users to * defer the reading of images if the FITS data is being read from a file. An * ImageTiler object is supplied which can return an arbitrary subset of the * image as a one dimensional array -- suitable for manipulation by standard * Java libraries. A call to the getData() method will still return a * multi-dimensional array, but the image data will not be read until the user * explicitly requests. it. */ public class ImageData extends Data { /** * This class describes an array */ protected static class ArrayDesc { private final int[] dims; private final Class type; ArrayDesc(int[] dims, Class type) { this.dims = dims; this.type = type; } } /** * This inner class allows the ImageTiler to see if the user has read in the * data. */ protected class ImageDataTiler extends StandardImageTiler { ImageDataTiler(RandomAccess o, long offset, ArrayDesc d) { super(o, offset, d.dims, d.type); } @Override protected Object getMemoryImage() { return ImageData.this.dataArray; } } private static final Logger LOG = getLogger(ImageData.class); /** The size of the data */ private long byteSize; /** * The actual array of data. This is normally a multi-dimensional primitive * array. It may be null until the getData() routine is invoked, or it may * be filled by during the read call when a non-random access device is * used. */ private Object dataArray; /** A description of what the data should look like */ private ArrayDesc dataDescription; /** The image tiler associated with this image. */ private StandardImageTiler tiler; /** * Create the equivalent of a null data element. */ public ImageData() { this.dataArray = new byte[0]; this.byteSize = 0; } /** * Create an array from a header description. This is typically how data * will be created when reading FITS data from a file where the header is * read first. This creates an empty array. * * @param h * header to be used as a template. * @throws FitsException * if there was a problem with the header description. */ public ImageData(Header h) throws FitsException { this.dataDescription = parseHeader(h); } /** * Create an ImageData object using the specified object to initialize the * data array. * * @param x * The initial data array. This should be a primitive array but * this is not checked currently. */ public ImageData(Object x) { this.dataArray = x; this.byteSize = ArrayFuncs.computeLSize(x); } /** * Return the actual data. Note that this may return a null when the data is * not readable. It might be better to throw a FitsException, but this is a * very commonly called method and we prefered not to change how users must * invoke it. */ @Override public Object getData() { if (this.dataArray == null && this.tiler != null) { try { this.dataArray = this.tiler.getCompleteImage(); } catch (Exception e) { LOG.log(Level.SEVERE, "Unable to get complete image", e); return null; } } return this.dataArray; } public StandardImageTiler getTiler() { return this.tiler; } @Override public void read(ArrayDataInput i) throws FitsException { // Don't need to read null data (noted by Jens Knudstrup) if (this.byteSize == 0) { return; } setFileOffset(i); if (i instanceof RandomAccess) { this.tiler = new ImageDataTiler((RandomAccess) i, ((RandomAccess) i).getFilePointer(), this.dataDescription); try { // Handle long skips. i.skipAllBytes(this.byteSize); } catch (IOException e) { throw new FitsException("Unable to skip over image:" + e); } } else { this.dataArray = ArrayFuncs.newInstance(this.dataDescription.type, this.dataDescription.dims); try { i.readLArray(this.dataArray); } catch (IOException e) { throw new FitsException("Unable to read image data:" + e); } this.tiler = new ImageDataTiler(null, 0, this.dataDescription); } int pad = FitsUtil.padding(getTrueSize()); try { i.skipAllBytes(pad); } catch (EOFException e) { throw new PaddingException("Error skipping padding after image", this, e); } catch (IOException e) { throw new FitsException("Error skipping padding after image", e); } } public void setBuffer(Buffer data) { PrimitiveType primType = PrimitiveTypeHandler.valueOf(this.dataDescription.type); this.dataArray = ArrayFuncs.newInstance(this.dataDescription.type, this.dataDescription.dims); MultiArrayIterator iterator = new MultiArrayIterator(this.dataArray); Object array = iterator.next(); while (array != null) { primType.getArray(data, array); array = iterator.next(); } this.tiler = new ImageDataTiler(null, 0, this.dataDescription); } @Override public void write(ArrayDataOutput o) throws FitsException { // Don't need to write null data (noted by Jens Knudstrup) if (this.byteSize == 0) { return; } if (this.dataArray == null) { if (this.tiler != null) { // Need to read in the whole image first. try { this.dataArray = this.tiler.getCompleteImage(); } catch (IOException e) { throw new FitsException("Error attempting to fill image", e); } } else if (this.dataArray == null && this.dataDescription != null) { // Need to create an array to match a specified header. this.dataArray = ArrayFuncs.newInstance(this.dataDescription.type, this.dataDescription.dims); } else { // This image isn't ready to be written! throw new FitsException("Null image data"); } } try { o.writeArray(this.dataArray); } catch (IOException e) { throw new FitsException("IO Error on image write" + e); } FitsUtil.pad(o, getTrueSize()); } /** * Fill header with keywords that describe image data. * * @param head * The FITS header * @throws FitsException * if the object does not contain valid image data. */ @Override protected void fillHeader(Header head) throws FitsException { if (this.dataArray == null) { head.nullImage(); return; } Standard.context(ImageData.class); String classname = this.dataArray.getClass().getName(); int[] dimens = ArrayFuncs.getDimensions(this.dataArray); if (dimens == null || dimens.length == 0) { throw new FitsException("Image data object not array"); } int bitpix; switch (classname.charAt(dimens.length)) { case 'B': bitpix = BasicHDU.BITPIX_BYTE; break; case 'S': bitpix = BasicHDU.BITPIX_SHORT; break; case 'I': bitpix = BasicHDU.BITPIX_INT; break; case 'J': bitpix = BasicHDU.BITPIX_LONG; break; case 'F': bitpix = BasicHDU.BITPIX_FLOAT; break; case 'D': bitpix = BasicHDU.BITPIX_DOUBLE; break; default: throw new FitsException("Invalid Object Type for FITS data:" + classname.charAt(dimens.length)); } // if this is neither a primary header nor an image extension, // make it a primary header head.setSimple(true); head.setBitpix(bitpix); head.setNaxes(dimens.length); for (int i = 1; i <= dimens.length; i += 1) { if (dimens[i - 1] == -1) { throw new FitsException("Unfilled array for dimension: " + i); } head.setNaxis(i, dimens[dimens.length - i]); } // Just in case! head.addValue(EXTEND, true); head.addValue(PCOUNT, 0); head.addValue(GCOUNT, 1); Standard.context(null); } /** Get the size in bytes of the data */ @Override protected long getTrueSize() { return this.byteSize; } protected ArrayDesc parseHeader(Header h) throws FitsException { int gCount = h.getIntValue(GCOUNT, 1); int pCount = h.getIntValue(PCOUNT, 0); if (gCount > 1 || pCount != 0) { throw new FitsException("Group data treated as images"); } int bitPix = h.getIntValue(BITPIX, 0); PrimitiveType primitivType = PrimitiveTypeHandler.valueOf(bitPix); if (primitivType == null) { primitivType = PrimitiveTypeHandler.nearestValueOf(bitPix); if (primitivType == PrimitiveTypes.UNKNOWN) { throw new FitsException("illegal bitpix value " + bitPix); } } Class baseClass = primitivType.primitiveClass(); int ndim = h.getIntValue(NAXIS, 0); int[] dims = new int[ndim]; // Note that we have to invert the order of the axes // for the FITS file to get the order in the array we // are generating. this.byteSize = 1; for (int i = 0; i < ndim; i += 1) { int cdim = h.getIntValue(NAXISn.n(i + 1), 0); if (cdim < 0) { throw new FitsException("Invalid array dimension:" + cdim); } this.byteSize *= cdim; dims[ndim - i - 1] = cdim; } this.byteSize *= primitivType.size(); if (ndim == 0) { this.byteSize = 0; } return new ArrayDesc(dims, baseClass); } void setTiler(StandardImageTiler tiler) { this.tiler = tiler; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/ImageHDU.java000066400000000000000000000146051310063650500256320ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.GROUPS; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.SIMPLE; import static nom.tam.fits.header.Standard.XTENSION; import static nom.tam.fits.header.Standard.XTENSION_IMAGE; import static nom.tam.util.LoggerHelper.getLogger; import java.io.PrintStream; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.image.StandardImageTiler; import nom.tam.util.ArrayFuncs; import nom.tam.util.type.PrimitiveType; import nom.tam.util.type.PrimitiveTypeHandler; import nom.tam.util.type.PrimitiveTypes; /** * FITS image header/data unit */ public class ImageHDU extends BasicHDU { private static final Logger LOG = getLogger(ImageHDU.class); /** * @return Encapsulate an object as an ImageHDU. * @param o * object to encapsulate * @throws FitsException * if the operation failed */ public static ImageData encapsulate(Object o) throws FitsException { return new ImageData(o); } /** * @return is this object can be described as a FITS image. * @param o * The Object being tested. */ public static boolean isData(Object o) { if (o.getClass().isArray()) { PrimitiveType type = PrimitiveTypeHandler.valueOf(ArrayFuncs.getBaseClass(o)); return type != PrimitiveTypes.BOOLEAN && // type != PrimitiveTypes.STRING && // type != PrimitiveTypes.UNKNOWN; } return false; } /** * Check that this HDU has a valid header for this type. * * @param hdr * header to check * @return true if this HDU has a valid header. */ public static boolean isHeader(Header hdr) { boolean found = hdr.getBooleanValue(SIMPLE); if (!found) { String xtension = hdr.getStringValue(XTENSION); xtension = xtension == null ? "" : xtension.trim(); if (XTENSION_IMAGE.equals(xtension) || "IUEIMAGE".equals(xtension)) { found = true; } } if (!found) { return false; } return !hdr.getBooleanValue(GROUPS); } public static Data manufactureData(Header hdr) throws FitsException { return new ImageData(hdr); } /** * @return Create a header that describes the given image data. * @param d * The image to be described. * @throws FitsException * if the object does not contain valid image data. */ public static Header manufactureHeader(Data d) throws FitsException { if (d == null) { return null; } Header h = new Header(); d.fillHeader(h); return h; } /** * Build an image HDU using the supplied data. * * @param h * the header for the image. * @param d * the data used in the image. * @throws FitsException * if there was a problem with the data. */ public ImageHDU(Header h, ImageData d) throws FitsException { super(h, d); } /** Indicate that Images can appear at the beginning of a FITS dataset */ @Override protected boolean canBePrimary() { return true; } public StandardImageTiler getTiler() { return this.myData.getTiler(); } /** * Print out some information about this HDU. */ @Override public void info(PrintStream stream) { if (isHeader(this.myHeader)) { stream.println(" Image"); } else { stream.println(" Image (bad header)"); } stream.println(" Header Information:"); stream.println(" BITPIX=" + this.myHeader.getIntValue(BITPIX, -1)); int naxis = this.myHeader.getIntValue(NAXIS, -1); stream.println(" NAXIS=" + naxis); for (int i = 1; i <= naxis; i += 1) { stream.println(" NAXIS" + i + "=" + this.myHeader.getIntValue(NAXISn.n(i), -1)); } stream.println(" Data information:"); try { if (this.myData.getData() == null) { stream.println(" No Data"); } else { stream.println(" " + ArrayFuncs.arrayDescription(this.myData.getData())); } } catch (Exception e) { LOG.log(Level.SEVERE, "Unable to get image data", e); stream.println(" Unable to get data"); } } /** Change the Image from/to primary */ @Override protected void setPrimaryHDU(boolean status) { try { super.setPrimaryHDU(status); } catch (FitsException e) { LOG.log(Level.SEVERE, "Impossible exception in ImageData", e); } if (status) { this.myHeader.setSimple(true); } else { this.myHeader.setXtension(XTENSION_IMAGE); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/PaddingException.java000066400000000000000000000051631310063650500274730ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This exception is thrown if an error is found reading the padding following a * valid FITS HDU. This padding is required by the FITS standard, but some FITS * writes forego writing it. To access such data users can use something like: * * Fits f = new Fits("somefile"); * try { * f.read(); * } catch (PaddingException e) { * f.addHDU(e.getHDU()); * } * to ensure that a truncated HDU is included in the FITS object. * Generally the FITS file have already added any HDUs prior to the truncated * one. */ public class PaddingException extends FitsException { /** * serial version id. */ private static final long serialVersionUID = 1L; /** * The HDU where the error happened. */ private BasicHDU truncatedHDU; public PaddingException(String msg, Data data, Exception cause) throws FitsException { super(msg, cause); this.truncatedHDU = FitsFactory.hduFactory(data.getKernel()); this.truncatedHDU = FitsFactory.hduFactory(this.truncatedHDU.getHeader(), data); } public BasicHDU getTruncatedHDU() { return this.truncatedHDU; } void updateHeader(Header hdr) throws FitsException { this.truncatedHDU = FitsFactory.hduFactory(hdr, this.truncatedHDU.getData()); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/RandomGroupsData.java000066400000000000000000000144471310063650500274650ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.GROUPS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.PCOUNT; import java.io.EOFException; import java.io.IOException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import nom.tam.fits.header.Standard; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; /** * This class instantiates FITS Random Groups data. Random groups are * instantiated as a two-dimensional array of objects. The first dimension of * the array is the number of groups. The second dimension is 2. The first * object in every row is a one dimensional parameter array. The second element * is the n-dimensional data array. */ public class RandomGroupsData extends Data { private final Object[][] dataArray; /** * Create the equivalent of a null data element. */ public RandomGroupsData() { this.dataArray = new Object[0][]; } /** * Create a RandomGroupsData object using the specified object to initialize * the data array. * * @param x * The initial data array. This should a two-d array of objects * as described above. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") public RandomGroupsData(Object[][] x) { this.dataArray = x; } @Override protected void fillHeader(Header h) throws FitsException { if (this.dataArray.length <= 0 || this.dataArray[0].length != 2) { throw new FitsException("Data not conformable to Random Groups"); } Standard.context(RandomGroupsData.class); Object paraSamp = this.dataArray[0][0]; Object dataSamp = this.dataArray[0][1]; Class pbase = ArrayFuncs.getBaseClass(paraSamp); Class dbase = ArrayFuncs.getBaseClass(dataSamp); if (pbase != dbase) { throw new FitsException("Data and parameters do not agree in type for random group"); } int[] pdims = ArrayFuncs.getDimensions(paraSamp); int[] ddims = ArrayFuncs.getDimensions(dataSamp); if (pdims.length != 1) { throw new FitsException("Parameters are not 1 d array for random groups"); } // Got the information we need to build the header. h.setSimple(true); if (dbase == byte.class) { h.setBitpix(BasicHDU.BITPIX_BYTE); } else if (dbase == short.class) { h.setBitpix(BasicHDU.BITPIX_SHORT); } else if (dbase == int.class) { h.setBitpix(BasicHDU.BITPIX_INT); } else if (dbase == long.class) { // Non-standard h.setBitpix(BasicHDU.BITPIX_LONG); } else if (dbase == float.class) { h.setBitpix(BasicHDU.BITPIX_FLOAT); } else if (dbase == double.class) { h.setBitpix(BasicHDU.BITPIX_DOUBLE); } else { throw new FitsException("Data type:" + dbase + " not supported for random groups"); } h.setNaxes(ddims.length + 1); h.addValue(NAXISn.n(1), 0); for (int i = 2; i <= ddims.length + 1; i += 1) { h.addValue(NAXISn.n(i), ddims[i - 2]); } h.addValue(GROUPS, true); h.addValue(GCOUNT, this.dataArray.length); h.addValue(PCOUNT, pdims[0]); Standard.context(null); } @Override @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") public Object getData() { return this.dataArray; } /** Get the size of the actual data element. */ @Override protected long getTrueSize() { if (this.dataArray != null && this.dataArray.length > 0) { return (ArrayFuncs.computeLSize(this.dataArray[0][0]) + ArrayFuncs.computeLSize(this.dataArray[0][1])) * this.dataArray.length; } else { return 0; } } /** Read the RandomGroupsData */ @Override public void read(ArrayDataInput str) throws FitsException { setFileOffset(str); try { str.readLArray(this.dataArray); } catch (IOException e) { throw new FitsException("IO error reading Random Groups data " + e); } int pad = FitsUtil.padding(getTrueSize()); try { str.skipAllBytes(pad); } catch (EOFException e) { throw new PaddingException("EOF reading padding after random groups", this, e); } catch (IOException e) { throw new FitsException("IO error reading padding after random groups", e); } } /** Write the RandomGroupsData */ @Override public void write(ArrayDataOutput str) throws FitsException { try { str.writeArray(this.dataArray); FitsUtil.pad(str, getTrueSize()); } catch (IOException e) { throw new FitsException("IO error writing random groups data " + e); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/RandomGroupsHDU.java000066400000000000000000000241301310063650500272220ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.GROUPS; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.fits.header.Standard.SIMPLE; import static nom.tam.fits.header.Standard.XTENSION; import static nom.tam.fits.header.Standard.XTENSION_IMAGE; import java.io.PrintStream; import java.util.logging.Logger; import nom.tam.util.ArrayFuncs; /** * Random groups HDUs. Note that the internal storage of random groups is a * Object[ngroup][2] array. The first element of each group is the parameter * data from that group. The second element is the data. The parameters should * be a one dimensional array of the primitive types byte, short, int, long, * float or double. The second element is a n-dimensional array of the same * type. When analyzing group data structure only the first group is examined, * but for a valid FITS file all groups must have the same structure. */ public class RandomGroupsHDU extends BasicHDU { private static final Logger LOG = Logger.getLogger(RandomGroupsHDU.class.getName()); public static RandomGroupsData encapsulate(Object o) throws FitsException { if (o instanceof Object[][]) { return new RandomGroupsData((Object[][]) o); } else { throw new FitsException("Attempt to encapsulate invalid data in Random Group"); } } static Object[] generateSampleRow(Header h) throws FitsException { int ndim = h.getIntValue(NAXIS, 0) - 1; int[] dims = new int[ndim]; int bitpix = h.getIntValue(BITPIX, 0); Class baseClass; switch (bitpix) { case BasicHDU.BITPIX_BYTE: baseClass = Byte.TYPE; break; case BasicHDU.BITPIX_SHORT: baseClass = Short.TYPE; break; case BasicHDU.BITPIX_INT: baseClass = Integer.TYPE; break; case BasicHDU.BITPIX_LONG: baseClass = Long.TYPE; break; case BasicHDU.BITPIX_FLOAT: baseClass = Float.TYPE; break; case BasicHDU.BITPIX_DOUBLE: baseClass = Double.TYPE; break; default: throw new FitsException("Invalid BITPIX:" + bitpix); } // Note that we have to invert the order of the axes // for the FITS file to get the order in the array we // are generating. Also recall that NAXIS1=0, so that // we have an 'extra' dimension. for (int i = 0; i < ndim; i += 1) { long cdim = h.getIntValue(NAXISn.n(i + 2), 0); if (cdim < 0) { throw new FitsException("Invalid array dimension:" + cdim); } dims[ndim - i - 1] = (int) cdim; } Object[] sample = new Object[2]; sample[0] = ArrayFuncs.newInstance(baseClass, h.getIntValue(PCOUNT)); sample[1] = ArrayFuncs.newInstance(baseClass, dims); return sample; } /** * Check if this data is compatible with Random Groups structure. Must be an * Object[ngr][2] structure with both elements of each group having the same * base type and the first element being a simple primitive array. We do not * check anything but the first row. * * @param potentialData * data to check * @return is this data compatible with Random Groups structure */ public static boolean isData(Object potentialData) { if (potentialData instanceof Object[][]) { Object[][] o = (Object[][]) potentialData; if (o.length > 0 && o[0].length == 2 && // ArrayFuncs.getBaseClass(o[0][0]) == ArrayFuncs.getBaseClass(o[0][1])) { String cn = o[0][0].getClass().getName(); if (cn.length() == 2 && cn.charAt(1) != 'Z' || cn.charAt(1) != 'C') { return true; } } } return false; } /** * @return Is this a random groups header? * @param hdr * The header to be tested. */ public static boolean isHeader(Header hdr) { if (hdr.getBooleanValue(SIMPLE)) { return hdr.getBooleanValue(GROUPS); } String xtension = hdr.getStringValue(XTENSION); xtension = xtension == null ? "" : xtension.trim(); if (XTENSION_IMAGE.equals(xtension)) { return hdr.getBooleanValue(GROUPS); } return false; } /** * @return Create FITS data object corresponding to a given header. * @param header * header for the data creation * @throws FitsException * if the operation failed */ public static RandomGroupsData manufactureData(Header header) throws FitsException { int gcount = header.getIntValue(GCOUNT, -1); int pcount = header.getIntValue(PCOUNT, -1); if (!header.getBooleanValue(GROUPS) || header.getIntValue(NAXISn.n(1), -1) != 0 || gcount < 0 || pcount < 0 || header.getIntValue(NAXIS) < 2) { throw new FitsException("Invalid Random Groups Parameters"); } // Allocate the object. Object[][] dataArray; if (gcount > 0) { dataArray = new Object[gcount][2]; } else { dataArray = new Object[0][]; } Object[] sampleRow = generateSampleRow(header); for (int i = 0; i < gcount; i += 1) { dataArray[i][0] = ((Object[]) ArrayFuncs.deepClone(sampleRow))[0]; dataArray[i][1] = ((Object[]) ArrayFuncs.deepClone(sampleRow))[1]; } return new RandomGroupsData(dataArray); } /** * @return Make a header point to the given object. * @param d * The random groups data the header should describe. * @throws FitsException * if the operation failed */ static Header manufactureHeader(Data d) throws FitsException { if (d == null) { throw new FitsException("Attempt to create null Random Groups data"); } Header h = new Header(); d.fillHeader(h); return h; } /** * Create an HDU from the given header and data . * * @param header * header to use * @param data * data to use */ public RandomGroupsHDU(Header header, RandomGroupsData data) { super(header, data); } @Override protected boolean canBePrimary() { return true; } @Override public void info(PrintStream stream) { stream.println("Random Groups HDU"); if (this.myHeader != null) { stream.println(" HeaderInformation:"); stream.println(" Ngroups:" + this.myHeader.getIntValue(GCOUNT)); stream.println(" Npar: " + this.myHeader.getIntValue(PCOUNT)); stream.println(" BITPIX: " + this.myHeader.getIntValue(BITPIX)); stream.println(" NAXIS: " + this.myHeader.getIntValue(NAXIS)); for (int i = 0; i < this.myHeader.getIntValue(NAXIS); i += 1) { stream.println(" NAXIS" + (i + 1) + "= " + this.myHeader.getIntValue(NAXISn.n(i + 1))); } } else { stream.println(" No Header Information"); } Object[][] data = null; if (this.myData != null) { data = (Object[][]) this.myData.getData(); } if (data == null || data.length < 1 || data[0].length != 2) { stream.println(" Invalid/unreadable data"); } else { stream.println(" Number of groups:" + data.length); stream.println(" Parameters: " + ArrayFuncs.arrayDescription(data[0][0])); stream.println(" Data:" + ArrayFuncs.arrayDescription(data[0][1])); } } /** * Check that this HDU has a valid header. * * @return true if this HDU has a valid header. */ public boolean isHeader() { return isHeader(this.myHeader); } /** * Move a RandomGroupsHDU to or from the beginning of a FITS file. Note that * the FITS standard only supports Random Groups data at the beginning of * the file, but we allow it within Image extensions. * * @param status * true if the header should be primary */ @Override protected void setPrimaryHDU(boolean status) throws FitsException { super.setPrimaryHDU(status); if (status) { this.myHeader.setSimple(true); } else { this.myHeader.setXtension(XTENSION_IMAGE); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/TableData.java000066400000000000000000000075741310063650500260770ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This class allows FITS binary and ASCII tables to be accessed via a common * interface. */ public interface TableData { /** * Add a column to the table without any associated header information. * Users should be cautious of calling this routine directly rather than the * corresponding routine in AsciiTableHDU since this routine knows nothing * of the FITS header modifications required. * * @param newCol * the new column information. the newCol should be an Object[] * where type of all of the constituents is identical. The length * of data should match the other columns. Note: It is * valid for data to be a 2 or higher dimensionality primitive * array. In this case the column index is the first (in Java * speak) index of the array. E.g., if called with * int[30][20][10], the number of rows in the table should be 30 * and this column will have elements which are 2-d integer * arrays with TDIM = (10,20). * @return the number of columns in the adapted table * @throws FitsException * if the operation failed */ int addColumn(Object newCol) throws FitsException; /** * Add a row at the end of the table. Given the way the table is structured * this will normally not be very efficient.Users should be cautious of * calling this routine directly rather than the corresponding routine in * AsciiTableHDU since this routine knows nothing of the FITS header * modifications required. * * @param newRow * An array of elements to be added. Each element of o should be * an array of primitives or a String. * @throws FitsException * if the operation failed * @return the number of rows in the adapted table */ int addRow(Object[] newRow) throws FitsException; void deleteColumns(int row, int len) throws FitsException; void deleteRows(int row, int len) throws FitsException; Object getColumn(int col) throws FitsException; Object getElement(int row, int col) throws FitsException; int getNCols(); int getNRows(); Object[] getRow(int row) throws FitsException; void setColumn(int col, Object newCol) throws FitsException; void setElement(int row, int col, Object element) throws FitsException; void setRow(int row, Object[] newRow) throws FitsException; void updateAfterDelete(int oldNcol, Header hdr) throws FitsException; } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/TableHDU.java000066400000000000000000000461651310063650500256450ustar00rootroot00000000000000package nom.tam.fits; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.TFIELDS; import static nom.tam.fits.header.Standard.TFORMn; import static nom.tam.fits.header.Standard.TTYPEn; import java.util.logging.Logger; import nom.tam.fits.header.GenericKey; import nom.tam.fits.header.IFitsHeader; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This class allows FITS binary and ASCII tables to be accessed via a common * interface. */ public abstract class TableHDU extends BasicHDU { private static final Logger LOG = Logger.getLogger(TableHDU.class.getName()); /** * Create the TableHDU. Note that this will normally only be invoked by * subclasses in the FITS package. * * @param hdr * the header * @param td * The data for the table. */ protected TableHDU(Header hdr, T td) { super(hdr, td); } /** * Add a column to the table without any associated header information. * * @param newCol * the new column information. the newCol should be an Object[] * where type of all of the constituents is identical. The length * of data should match the other columns. Note: It is * valid for data to be a 2 or higher dimensionality primitive * array. In this case the column index is the first (in Java * speak) index of the array. E.g., if called with * int[30][20][10], the number of rows in the table should be 30 * and this column will have elements which are 2-d integer * arrays with TDIM = (10,20). * @return the number of columns in the adapted table * @throws FitsException * if the operation failed */ public int addColumn(Object newCol) throws FitsException { int nCols = getNCols(); this.myHeader.addValue(TFIELDS, nCols); return nCols; } /** * Add a row to the end of the table. If this is the first row, then this * will add appropriate columns for each of the entries. The rows to add * must be supplied as column based array of arrays. * * @return the number of rows in the adapted table * @param newRows * rows to add to the table * @throws FitsException * if the operation failed */ public int addRow(Object[] newRows) throws FitsException { int row = this.myData.addRow(newRows); this.myHeader.addValue(NAXISn.n(2), getNRows()); return row; } /** * @return the stems of the keywords that are associated with table columns. * Users can supplement this with their own and call the appropriate * deleteColumns fields. */ protected abstract IFitsHeader[] columnKeyStems(); /** * Delete a set of columns from a table. * * @param column * The one-indexed start column. * @param len * The number of columns to delete. * @throws FitsException * if the operation failed */ public void deleteColumnsIndexOne(int column, int len) throws FitsException { deleteColumnsIndexZero(column - 1, len); } /** * Delete a set of columns from a table. * * @param column * The one-indexed start column. * @param len * The number of columns to delete. * @param fields * Stems for the header fields to be removed for the table. * @throws FitsException * if the operation failed */ public void deleteColumnsIndexOne(int column, int len, String[] fields) throws FitsException { deleteColumnsIndexZero(column - 1, len, GenericKey.create(fields)); } /** * Delete a set of columns from a table. * * @param column * The one-indexed start column. * @param len * The number of columns to delete. * @throws FitsException * if the operation failed */ public void deleteColumnsIndexZero(int column, int len) throws FitsException { deleteColumnsIndexZero(column, len, columnKeyStems()); } /** * Delete a set of columns from a table. * * @param column * The zero-indexed start column. * @param len * The number of columns to delete. * @param fields * Stems for the header fields to be removed for the table. * @throws FitsException * if the operation failed */ public void deleteColumnsIndexZero(int column, int len, IFitsHeader[] fields) throws FitsException { if (column < 0 || len < 0 || column + len > getNCols()) { throw new FitsException("Illegal columns deletion request- Start:" + column + " Len:" + len + " from table with " + getNCols() + " columns"); } if (len == 0) { return; } int ncol = getNCols(); this.myData.deleteColumns(column, len); // Get rid of the keywords for the deleted columns for (int col = column; col < column + len; col += 1) { for (IFitsHeader field : fields) { this.myHeader.deleteKey(field.n(col + 1)); } } // Shift the keywords for the columns after the deleted columns for (int col = column + len; col < ncol; col += 1) { for (IFitsHeader field : fields) { IFitsHeader oldKey = field.n(col + 1); IFitsHeader newKey = field.n(col + 1 - len); if (this.myHeader.containsKey(oldKey)) { this.myHeader.replaceKey(oldKey, newKey); } } } // Update the number of fields. this.myHeader.addValue(TFIELDS, getNCols()); // Give the data sections a chance to update the header too. this.myData.updateAfterDelete(ncol, this.myHeader); } /** * Remove all rows from the table starting at some specific index from the * table. Inspired by a routine by R. Mathar but re-implemented using the * DataTable and changes to AsciiTable so that it can be done easily for * both Binary and ASCII tables. * * @param row * the (0-based) index of the first row to be deleted. * @throws FitsException * if an error occurs. */ public void deleteRows(final int row) throws FitsException { deleteRows(row, getNRows() - row); } /** * Remove a number of adjacent rows from the table. This routine was * inspired by code by R.Mathar but re-implemented using changes in the * ColumnTable class abd AsciiTable so that we can do it for all FITS * tables. * * @param firstRow * the (0-based) index of the first row to be deleted. This is * zero-based indexing: 0<=firstrow< number of rows. * @param nRow * the total number of rows to be deleted. * @throws FitsException * If an error occurs in the deletion. */ public void deleteRows(final int firstRow, int nRow) throws FitsException { // Just ignore invalid requests. if (nRow <= 0 || firstRow >= getNRows() || firstRow <= 0) { return; } /* correct if more rows are requested than available */ if (nRow > getNRows() - firstRow) { nRow = getNRows() - firstRow; } this.myData.deleteRows(firstRow, nRow); this.myHeader.setNaxis(2, getNRows()); } /** * Find the 0-based column index corresponding to a particular column name. * * @return index of the column * @param colName * the name of the column */ public int findColumn(String colName) { for (int i = 0; i < getNCols(); i += 1) { String val = this.myHeader.getStringValue(TTYPEn.n(i + 1)); if (val != null && val.trim().equals(colName)) { return i; } } return -1; } /** * @return a specific column from the table using 0-based column indexing. * @param col * column index to get * @throws FitsException * if the operation failed */ public Object getColumn(int col) throws FitsException { return this.myData.getColumn(col); } /** * @return a specific column of the table where the column name is specified * using the TTYPEn keywords in the header. * @param colName * The name of the column to be extracted. * @throws FitsException * if the operation failed */ public Object getColumn(String colName) throws FitsException { return getColumn(findColumn(colName)); } /** * Get the FITS type of a column in the table. * * @param index * The 0-based index of the column. * @return The FITS type. * @throws FitsException * if an invalid index was requested. */ public String getColumnFormat(int index) throws FitsException { int flds = this.myHeader.getIntValue(TFIELDS, 0); if (index < 0 || index >= flds) { throw new FitsException("Bad column index " + index + " (only " + flds + " columns)"); } return this.myHeader.getStringValue(TFORMn.n(index + 1)).trim(); } /** * Convenience method for getting column data. Note that this works only for * metadata that returns a string value. This is equivalent to * getStringValue(type+index); * * @return meta data string value * @param index * index of the colum * @param type * the key type to get */ public String getColumnMeta(int index, String type) { return this.myHeader.getStringValue(type + (index + 1)); } /** * Get the name of a column in the table. * * @param index * The 0-based column index. * @return The column name. */ public String getColumnName(int index) { String ttype = this.myHeader.getStringValue(TTYPEn.n(index + 1)); if (ttype != null) { ttype = ttype.trim(); } return ttype; } /** * @return all of the columns of the table. * @throws FitsException * if the operation failed */ public Object[] getColumns() throws FitsException { Object[] result = new Object[getNCols()]; for (int i = 0; i < result.length; i += 1) { result[i] = getColumn(i); } return result; } /** * @return a specific element of the table using 0-based indices. * @param row * the row index of the element * @param col * the column index of the element * @throws FitsException * if the operation failed */ public Object getElement(int row, int col) throws FitsException { return this.myData.getElement(row, col); } /** * Get the number of columns for this table * * @return The number of columns in the table. */ public int getNCols() { return this.myData.getNCols(); } /** * Get the number of rows for this table * * @return The number of rows in the table. */ public int getNRows() { return this.myData.getNRows(); } /** * @return a specific row of the table. * @param row * the index of the row to retreive * @throws FitsException * if the operation failed */ public Object[] getRow(int row) throws FitsException { return this.myData.getRow(row); } /** * Update a column within a table. The new column should have the same * format ast the column being replaced. * * @param col * index of the column to replace * @param newCol * the replacement column * @throws FitsException * if the operation failed */ public void setColumn(int col, Object newCol) throws FitsException { this.myData.setColumn(col, newCol); } /** * Update a column within a table. The new column should have the same * format as the column being replaced. * * @param colName * name of the column to replace * @param newCol * the replacement column * @throws FitsException * if the operation failed */ public void setColumn(String colName, Object newCol) throws FitsException { setColumn(findColumn(colName), newCol); } /** * Specify column metadata for a given column in a way that allows all of * the column metadata for a given column to be organized together. * * @param index * The 0-based index of the column * @param key * The column key. I.e., the keyword will be key+(index+1) * @param value * The value to be placed in the header. * @param comment * The comment for the header * @param after * Should the header card be after the current column metadata * block (true), or immediately before the TFORM card (false). @throws * FitsException if the operation failed * @throws FitsException * if the header could not be updated */ public void setColumnMeta(int index, IFitsHeader key, String value, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key.n(index + 1).key(), value, comment); } public void setColumnMeta(int index, String key, boolean value, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key + (index + 1), value, comment); } public void setColumnMeta(int index, String key, double value, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key + (index + 1), value, comment); } public void setColumnMeta(int index, String key, double value, int precision, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key + (index + 1), value, precision, comment); } public void setColumnMeta(int index, String key, long value, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key + (index + 1), value, comment); } public void setColumnMeta(int index, String key, String value, String comment) throws FitsException { setColumnMeta(index, key, value, comment, true); } /** * Specify column metadata for a given column in a way that allows all of * the column metadata for a given column to be organized together. * * @param index * The 0-based index of the column * @param key * The column key. I.e., the keyword will be key+(index+1) * @param value * The value to be placed in the header. * @param comment * The comment for the header * @param after * Should the header card be after the current column metadata * block (true), or immediately before the TFORM card (false). @throws * FitsException if the operation failed * @throws FitsException * if the header could not be updated * @deprecated use * {@link #setColumnMeta(int, IFitsHeader, String, String, boolean)} */ @Deprecated public void setColumnMeta(int index, String key, String value, String comment, boolean after) throws FitsException { setCurrentColumn(index, after); this.myHeader.addValue(key + (index + 1), value, comment); } public void setColumnName(int index, String name, String comment) throws FitsException { setColumnMeta(index, TTYPEn, name, comment, true); } /** * Set the cursor in the header to point after the metadata for the * specified column * * @param col * The 0-based index of the column */ public void setCurrentColumn(int col) { setCurrentColumn(col, true); } /** * Set the cursor in the header to point either before the TFORM value or * after the column metadat * * @param col * The 0-based index of the column * @param after * True if the cursor should be placed after the existing column * metadata or false if the cursor is to be placed before the * TFORM value. If no corresponding TFORM is found, the cursoe * will be placed at the end of current header. */ public void setCurrentColumn(int col, boolean after) { if (after) { this.myHeader.positionAfterIndex(TFORMn, col + 1); } else { this.myHeader.findCard(TFORMn.n(col + 1)); } } /** * Update a single element within the table. * * @param row * the row index * @param col * the column index * @param element * the replacement element * @throws FitsException * if the operation failed */ public void setElement(int row, int col, Object element) throws FitsException { this.myData.setElement(row, col, element); } /** * Update a row within a table. * * @param row * row index * @param newRow * the replacement row * @throws FitsException * if the operation failed */ public void setRow(int row, Object[] newRow) throws FitsException { this.myData.setRow(row, newRow); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/TruncatedFileException.java000066400000000000000000000033331310063650500306530ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This exception is thrown when an EOF is detected in the middle of an HDU. */ public class TruncatedFileException extends FitsException { /** * serial version UID. */ private static final long serialVersionUID = 1L; public TruncatedFileException(String msg) { super(msg); } public TruncatedFileException(String msg, Exception cause) { super(msg, cause); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/UndefinedData.java000066400000000000000000000121361310063650500267370ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.BITPIX; import static nom.tam.fits.header.Standard.EXTEND; import static nom.tam.fits.header.Standard.GCOUNT; import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.NAXISn; import static nom.tam.fits.header.Standard.PCOUNT; import static nom.tam.util.LoggerHelper.getLogger; import java.io.EOFException; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import nom.tam.fits.header.Standard; import nom.tam.util.ArrayDataInput; import nom.tam.util.ArrayDataOutput; import nom.tam.util.ArrayFuncs; /** * This class provides a simple holder for data which is not handled by other * classes. */ public class UndefinedData extends Data { private static final Logger LOG = getLogger(UndefinedData.class); private static final int BITS_PER_BYTE = 8; private byte[] data; public UndefinedData(Header h) throws FitsException { /** * Just get a byte buffer to hold the data. */ // Bug fix by Vincenzo Forzi. int naxis = h.getIntValue(NAXIS); int size = naxis > 0 ? 1 : 0; for (int i = 0; i < naxis; i += 1) { size *= h.getIntValue(NAXISn.n(i + 1)); } size += h.getIntValue(PCOUNT); if (h.getIntValue(GCOUNT) > 1) { size *= h.getIntValue(GCOUNT); } size *= Math.abs(h.getIntValue(BITPIX) / BITS_PER_BYTE); this.data = new byte[size]; } /** * Create an UndefinedData object using the specified object. * * @param x * object to create the hdu from */ public UndefinedData(Object x) { this.data = new byte[(int) ArrayFuncs.computeLSize(x)]; ArrayFuncs.copyInto(x, this.data); } /** * Fill header with keywords that describe data. * * @param head * The FITS header */ @Override protected void fillHeader(Header head) { try { Standard.context(UndefinedData.class); head.setXtension("UNKNOWN"); head.setBitpix(BasicHDU.BITPIX_BYTE); head.setNaxes(1); head.addValue(NAXISn.n(1), this.data.length); head.addValue(PCOUNT, 0); head.addValue(GCOUNT, 1); // Just in case! head.addValue(EXTEND, true); } catch (HeaderCardException e) { LOG.log(Level.SEVERE, "Unable to create unknown header", e); } finally { Standard.context(null); } } @Override @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") public Object getData() { return this.data; } /** Get the size in bytes of the data */ @Override protected long getTrueSize() { return this.data.length; } @Override public void read(ArrayDataInput i) throws FitsException { setFileOffset(i); try { i.readFully(this.data); } catch (IOException e) { throw new FitsException("Unable to read unknown data:" + e); } int pad = FitsUtil.padding(getTrueSize()); try { i.skipAllBytes(pad); } catch (EOFException e) { throw new PaddingException("EOF skipping padding in undefined data", this, e); } catch (IOException e) { throw new FitsException("Error skipping padding in undefined data", e); } } @Override public void write(ArrayDataOutput o) throws FitsException { try { o.write(this.data); } catch (IOException e) { throw new FitsException("IO Error on unknown data write" + e); } FitsUtil.pad(o, getTrueSize()); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/UndefinedHDU.java000066400000000000000000000075661310063650500265210ustar00rootroot00000000000000package nom.tam.fits; /* * #%L * nom.tam FITS library * %% * Copyright (C) 2004 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.header.Standard.NAXIS; import static nom.tam.fits.header.Standard.XTENSION; import java.io.PrintStream; import nom.tam.util.ArrayFuncs; /** * Holder for unknown data types. */ public class UndefinedHDU extends BasicHDU { /** * @return Encapsulate an object as an ImageHDU. * @param o * the object to encapsulate * @throws FitsException * if the operation failed */ public static UndefinedData encapsulate(Object o) throws FitsException { return new UndefinedData(o); } /** * Check if we can use the following object as in an Undefined FITS block. * We allow this so long as computeLSize can get a size. Note that * computeLSize may be wrong! * * @param o * The Object being tested. * @return true if o can be an Undefined FITS block. */ public static boolean isData(Object o) { return ArrayFuncs.computeLSize(o) > 0; } /** * Check if we can find the length of the data for this header. * * @param hdr * header to check. * @return true if this HDU has a valid header. */ public static boolean isHeader(Header hdr) { if (hdr.getStringValue(XTENSION) != null && hdr.getIntValue(NAXIS, -1) >= 0) { return true; } return false; } public static Data manufactureData(Header hdr) throws FitsException { return new UndefinedData(hdr); } /** * @return Create a header that describes the given image data. * @param d * The image to be described. * @throws FitsException * if the object does not contain valid image data. */ public static Header manufactureHeader(Data d) throws FitsException { Header h = new Header(); d.fillHeader(h); return h; } /** * Build an image HDU using the supplied data. * * @param h * the header for this HDU * @param d * the data used to build the image. * @throws FitsException * if there was a problem with the data. */ public UndefinedHDU(Header h, UndefinedData d) throws FitsException { super(h, d); } @Override public void info(PrintStream stream) { stream.println(" Unhandled/Undefined/Unknown Type"); stream.println(" XTENSION=" + this.myHeader.getStringValue(XTENSION).trim()); stream.println(" Apparent size:" + this.myData.getTrueSize()); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/000077500000000000000000000000001310063650500252315ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/BZip2CompressionProvider.java000066400000000000000000000037441310063650500327670ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; public class BZip2CompressionProvider implements ICompressProvider { private static final int PRIORITY = 5; @Override public InputStream decompress(InputStream in) throws IOException { try { return CompressionLibLoaderProtection.createBZip2Stream(in); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e); } } @Override public int priority() { return PRIORITY; } @Override public boolean provides(int mag1, int mag2) { return mag1 == 'B' && mag2 == 'Z'; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/BasicCompressProvider.java000066400000000000000000000057001310063650500323460ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.util.LoggerHelper.getLogger; import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.fits.FitsException; public class BasicCompressProvider implements ICompressProvider { private static final int PRIORITY = 10; private static final int COMPRESS_MAGIC_BYTE1 = 0x1f; private static final int COMPRESS_MAGIC_BYTE2 = 0x9d; private static final Logger LOG = getLogger(BasicCompressProvider.class); private InputStream compressInputStream(final InputStream compressed) throws IOException, FitsException { try { Process proc = new ProcessBuilder("uncompress", "-c").start(); return new CloseIS(proc, compressed); } catch (Exception e) { ICompressProvider next = CompressionManager.nextCompressionProvider(COMPRESS_MAGIC_BYTE1, COMPRESS_MAGIC_BYTE2, this); if (next != null) { LOG.log(Level.WARNING, "Error initiating .Z decompression: " + e.getMessage() + " trying alternative decompressor", e); return next.decompress(compressed); } throw new FitsException("Unable to read .Z compressed stream.\nIs 'uncompress' in the path?", e); } } @Override public InputStream decompress(InputStream in) throws IOException, FitsException { return compressInputStream(in); } @Override public int priority() { return PRIORITY; } @Override public boolean provides(int mag1, int mag2) { return mag1 == COMPRESS_MAGIC_BYTE1 && mag2 == COMPRESS_MAGIC_BYTE2; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/CloseIS.java000066400000000000000000000132571310063650500274050ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.util.LoggerHelper.getLogger; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.logging.Level; import java.util.logging.Logger; public class CloseIS extends FilterInputStream { private static final Logger LOG = getLogger(CloseIS.class); private static final int COPY_BUFFER_SIZE = 64 * 1024; private InputStream output; private OutputStream input; private String errorText; private IOException exception; private final Thread stdError; private final Thread copier; private final Process proc; public CloseIS(Process proc, final InputStream compressed) { super(new BufferedInputStream(proc.getInputStream(), CompressionManager.ONE_MEGABYTE)); if (compressed == null) { throw new NullPointerException(); } this.proc = proc; final InputStream error = proc.getErrorStream(); this.output = proc.getInputStream(); this.input = proc.getOutputStream(); stdError = new Thread(new Runnable() { @Override public void run() { try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[COPY_BUFFER_SIZE]; int len; while ((len = error.read(buffer, 0, buffer.length)) >= 0) { bytes.write(buffer, 0, len); } error.close(); errorText = new String(bytes.toByteArray(), Charset.defaultCharset()); } catch (IOException e) { exception = e; } } }); // Now copy everything in a separate thread. copier = new Thread(new Runnable() { @Override public void run() { try { byte[] buffer = new byte[COPY_BUFFER_SIZE]; int len; while ((len = compressed.read(buffer, 0, buffer.length)) >= 0) { input.write(buffer, 0, len); } input.close(); } catch (IOException e) { exception = e; } try { compressed.close(); } catch (IOException e) { if (exception == null) { exception = e; } } } }); start(); } /** * start all threads. */ private void start() { stdError.start(); copier.start(); } @Override public int read() throws IOException { int result = 0; try { result = super.read(); return result; } catch (IOException e) { result = -1; throw e; } finally { handledOccuredException(result); } } private void handledOccuredException(int result) throws IOException { int exitValue = 0; if (result < 0) { try { stdError.join(); copier.join(); exitValue = proc.exitValue(); } catch (Exception e) { LOG.log(Level.WARNING, "could not join the stream processes", e); } } if (exception != null || exitValue != 0) { if (errorText != null && !errorText.trim().isEmpty()) { throw new IOException(errorText, exception); } else { if (exception == null) { throw new IOException("exit value was " + exitValue); } else { throw exception; } } } } @Override public int read(byte[] b, int off, int len) throws IOException { int result = 0; try { result = super.read(b, off, len); return result; } catch (IOException e) { throw e; } finally { handledOccuredException(result); } } @Override public void close() throws IOException { super.close(); this.input.close(); this.output.close(); } } CompressionLibLoaderProtection.java000066400000000000000000000037341310063650500341520ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compresspackage nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; /** * Indirect Apache compression access. This indirection keeps the classes * loadable. * * @author Richard van Nieuwenhoven */ public final class CompressionLibLoaderProtection { private CompressionLibLoaderProtection() { } public static InputStream createBZip2Stream(InputStream in) throws IOException { return new org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream(in); } public static InputStream createZStream(InputStream in) throws IOException { return new org.apache.commons.compress.compressors.z.ZCompressorInputStream(in); } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/CompressionManager.java000066400000000000000000000142751310063650500317010ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.util.LoggerHelper.getLogger; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.fits.FitsException; import nom.tam.util.SafeClose; public final class CompressionManager { private static final String BZIP2_EXTENTION = ".bz2"; private static final String COMPRESS_EXTENTION = ".Z"; private static final String GZIP_EXTENTION = ".gz"; public static final int ONE_MEGABYTE = 1024 * 1024; /** * logger to log to. */ private static final Logger LOG = getLogger(CompressionManager.class); private CompressionManager() { } /** * This method decompresses a compressed input stream. The decompression * method is selected automatically based upon the first two bytes read. * * @param compressed * The compressed input stream * @return A stream which wraps the input stream and decompresses it. If the * input stream is not compressed, a pushback input stream wrapping * the original stream is returned. * @throws FitsException * when the stream could not be read or decompressed */ public static InputStream decompress(InputStream compressed) throws FitsException { BufferedInputStream pb = new BufferedInputStream(compressed, ONE_MEGABYTE); pb.mark(2); int mag1 = -1; int mag2 = -1; try { mag1 = pb.read(); mag2 = pb.read(); // Push the data back into the stream pb.reset(); ICompressProvider selectedProvider = selectCompressionProvider(mag1, mag2); if (selectedProvider != null) { return selectedProvider.decompress(pb); } else { return pb; } } catch (IOException e) { // This is probably a prelude to failure... throw new FitsException("Unable to analyze input stream", e); } } /** * Is a file compressed? (the magic number in the first 2 bytes is used to * detect the compression. * * @param file * file to test for compression algorithms * @return true if the file is compressed */ public static boolean isCompressed(File file) { InputStream fis = null; try { if (file.exists()) { fis = new FileInputStream(file); int mag1 = fis.read(); int mag2 = fis.read(); fis.close(); return selectCompressionProvider(mag1, mag2) != null; } } catch (IOException e) { LOG.log(Level.FINEST, "Error while checking if file " + file + " is compressed", e); // This is probably a prelude to failure... return false; } finally { SafeClose.close(fis); } return false; } /** * Is a file compressed? (the magic number in the first 2 bytes is used to * detect the compression. * * @param filename * of the file to test for compression algorithms * @return true if the file is compressed */ public static boolean isCompressed(String filename) { if (filename == null) { return false; } File test = new File(filename); if (test.exists()) { return isCompressed(test); } int len = filename.length(); return len > 2 && (filename.substring(len - GZIP_EXTENTION.length()).equalsIgnoreCase(GZIP_EXTENTION) || // filename.substring(len - COMPRESS_EXTENTION.length()).equals(COMPRESS_EXTENTION) || // filename.substring(len - BZIP2_EXTENTION.length()).equals(BZIP2_EXTENTION)); } private static ICompressProvider selectCompressionProvider(int mag1, int mag2) { return nextCompressionProvider(mag1, mag2, null); } protected static ICompressProvider nextCompressionProvider(int mag1, int mag2, ICompressProvider old) { ICompressProvider selectedProvider = null; int priority = 0; int maxPriority = Integer.MAX_VALUE; if (old != null) { maxPriority = old.priority(); } ServiceLoader compressionProviders = ServiceLoader.load(ICompressProvider.class, Thread.currentThread().getContextClassLoader()); for (ICompressProvider provider : compressionProviders) { if (provider.priority() > Math.max(0, priority) && provider.priority() < maxPriority && provider != old && // provider.provides(mag1, mag2)) { priority = provider.priority(); selectedProvider = provider; } } return selectedProvider; } } ExternalBZip2CompressionProvider.java000066400000000000000000000060231310063650500344040ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compresspackage nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; import nom.tam.fits.FitsException; public class ExternalBZip2CompressionProvider implements ICompressProvider { private static final int PRIORITY = 10; private static final Logger LOG = Logger.getLogger(ExternalBZip2CompressionProvider.class.getName()); private InputStream bunzipper(final InputStream compressed) throws IOException, FitsException { String cmd = getBzip2Cmd(); // Allow the user to have already specified the - option. if (cmd.indexOf(" -") < 0) { cmd += " -"; } String[] flds = cmd.split(" +"); Process proc; try { proc = new ProcessBuilder(flds).start(); return new CloseIS(proc, compressed); } catch (Exception e) { ICompressProvider next = CompressionManager.nextCompressionProvider('B', 'Z', this); if (next != null) { LOG.warning("Error initiating BZIP decompression: " + e.getMessage() + " trieing alternative decompressor"); return next.decompress(compressed); } throw new FitsException("Error initiating BZIP decompression: " + e); } } public String getBzip2Cmd() { return System.getProperty("BZIP_DECOMPRESSOR", System.getenv("BZIP_DECOMPRESSOR")); } @Override public InputStream decompress(InputStream in) throws IOException, FitsException { return bunzipper(in); } @Override public int priority() { return PRIORITY; } @Override public boolean provides(int mag1, int mag2) { return mag1 == 'B' && mag2 == 'Z' && getBzip2Cmd() != null; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/GZipCompressionProvider.java000066400000000000000000000037341310063650500327110ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; public class GZipCompressionProvider implements ICompressProvider { private static final int GZIP_MAGIC_BYTE1 = 0x1f; private static final int GZIP_MAGIC_BYTE2 = 0x8b; private static final int PRIORITY = 5; @Override public InputStream decompress(InputStream in) throws IOException { return new GZIPInputStream(in); } @Override public int priority() { return PRIORITY; } @Override public boolean provides(int mag1, int mag2) { return mag1 == GZIP_MAGIC_BYTE1 && mag2 == GZIP_MAGIC_BYTE2; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/ICompressProvider.java000066400000000000000000000031271310063650500315160ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; import nom.tam.fits.FitsException; public interface ICompressProvider { InputStream decompress(InputStream in) throws IOException, FitsException; int priority(); boolean provides(int byte1, int byte2); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compress/ZCompressionProvider.java000066400000000000000000000041741310063650500322500ustar00rootroot00000000000000package nom.tam.fits.compress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.io.InputStream; public class ZCompressionProvider implements ICompressProvider { private static final int Z_COMPRESS_MAGIC_BYTE1 = 0x1f; private static final int Z_COMPRESS_MAGIC_BYTE2 = 0x9d; private static final int PRIORITY = 5; @Override public InputStream decompress(InputStream in) throws IOException { try { return CompressionLibLoaderProtection.createZStream(in); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e); } } @Override public int priority() { return PRIORITY; } @Override public boolean provides(int mag1, int mag2) { return mag1 == Z_COMPRESS_MAGIC_BYTE1 && mag2 == Z_COMPRESS_MAGIC_BYTE2; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/000077500000000000000000000000001310063650500257375ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/000077500000000000000000000000001310063650500277255ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/api/000077500000000000000000000000001310063650500304765ustar00rootroot00000000000000ICompressOption.java000066400000000000000000000060331310063650500343610ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/apipackage nom.tam.fits.compression.algorithm.api; import nom.tam.fits.compression.provider.param.api.ICompressParameters; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * Option for the compression algorithm, implementors are used to control the * compression algorithm. */ public interface ICompressOption extends Cloneable { /** * @return copy the option (normally the option from with the copy happened * is saved as original). */ ICompressOption copy(); /** * @return the parameters that must be synchronized with the hdu meta data. */ ICompressParameters getCompressionParameters(); /** * @return true if the compression done with this specified options uses * approximations. That means if the reconstruction of the data is * excact the return should be false. */ boolean isLossyCompression(); /** * set the parameters that must be synchronized with the hdu meta data. * * @param parameters * the parameters to synchronized */ void setParameters(ICompressParameters parameters); /** * set the tile height in pixel. * * @param value * the number of pixel. * @return this (builder pattern) */ ICompressOption setTileHeight(int value); /** * set the tile width. * * @param value * the number of pixel. * @return this (builder pattern) */ ICompressOption setTileWidth(int value); /** * un wrap a specific implementation detail. * * @param clazz * the type to unwrap * @param * the class to unrwap * @return the implementation detail or null if no such detail is avalable. */ T unwrap(Class clazz); } ICompressor.java000066400000000000000000000044541310063650500335360ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/apipackage nom.tam.fits.compression.algorithm.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.nio.Buffer; import java.nio.ByteBuffer; /** * Compressor that can compress a Buffer into a ByteBuffer and vize versa. the * Byte buffer must have enough space allocated else an exception will be * thrown. */ public interface ICompressor { /** * compress the buffer into the byte buffer. Attention enough space must * already be allocated. * * @param buffer * the buffer to compress. * @param compressed * the compressed data * @return true if the compression succeeded. */ boolean compress(T buffer, ByteBuffer compressed); /** * Decompress the byte buffer and restore the buffer from it, again enough * space must already be allocated. * * @param compressed * the compressed data * @param buffer * the buffer to fill with the uncompressed data. */ void decompress(ByteBuffer compressed, T buffer); } ICompressorControl.java000066400000000000000000000051031310063650500350670ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/apipackage nom.tam.fits.compression.algorithm.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.nio.Buffer; import java.nio.ByteBuffer; /** * The interface to a provided compression algorithm. */ public interface ICompressorControl { /** * Compress the buffer into the byte buffer using the specified options. * * @param in * the buffer to compress. * @param out * the compressed data to fill (must already be allocated with * enough space) * @param option * the options to use for the compression * @return true if the compression succeded. */ boolean compress(Buffer in, ByteBuffer out, ICompressOption option); /** * decompress the byte buffer back into the buffer using the specified * options. * * @param in * the bytes to decompress. * @param out * the buffer to fill with the decompressed data (must already be * allocated with enough space) * @param option * the options to use for decompressing. */ void decompress(ByteBuffer in, Buffer out, ICompressOption option); /** * @return a option instance that can be used to control the compression * meganism. */ ICompressOption option(); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/gzip/000077500000000000000000000000001310063650500306765ustar00rootroot00000000000000GZipCompressor.java000066400000000000000000000261031310063650500344120ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/gzippackage nom.tam.fits.compression.algorithm.gzip; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import nom.tam.fits.compression.algorithm.api.ICompressor; import nom.tam.util.ArrayFuncs; import nom.tam.util.ByteBufferInputStream; import nom.tam.util.ByteBufferOutputStream; import nom.tam.util.FitsIO; import nom.tam.util.SafeClose; import nom.tam.util.type.PrimitiveType; import nom.tam.util.type.PrimitiveTypeHandler; import nom.tam.util.type.PrimitiveTypes; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ public abstract class GZipCompressor implements ICompressor { /** * Byte compress is a special case, the only one that does not extends * GZipCompress because it can write the buffer directly. */ public static class ByteGZipCompressor extends GZipCompressor { public ByteGZipCompressor() { super(1); this.nioBuffer = ByteBuffer.wrap(this.buffer); } @Override protected void getPixel(ByteBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(ByteBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } public static class DoubleGZipCompressor extends GZipCompressor { protected static final int BYTE_SIZE_OF_DOUBLE = 8; public DoubleGZipCompressor() { super(BYTE_SIZE_OF_DOUBLE); this.nioBuffer = ByteBuffer.wrap(this.buffer).asDoubleBuffer(); } @Override protected void getPixel(DoubleBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(DoubleBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } public static class FloatGZipCompressor extends GZipCompressor { protected static final int BYTE_SIZE_OF_FLOAT = 4; public FloatGZipCompressor() { super(BYTE_SIZE_OF_FLOAT); this.nioBuffer = ByteBuffer.wrap(this.buffer).asFloatBuffer(); } @Override protected void getPixel(FloatBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(FloatBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } public static class IntGZipCompressor extends GZipCompressor { protected static final int BYTE_SIZE_OF_INT = 4; public IntGZipCompressor() { super(BYTE_SIZE_OF_INT); this.nioBuffer = ByteBuffer.wrap(this.buffer).asIntBuffer(); } @Override protected void getPixel(IntBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(IntBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } public static class LongGZipCompressor extends GZipCompressor { protected static final int BYTE_SIZE_OF_LONG = 8; public LongGZipCompressor() { super(BYTE_SIZE_OF_LONG); this.nioBuffer = ByteBuffer.wrap(this.buffer).asLongBuffer(); } @Override protected void getPixel(LongBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(LongBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } public static class ShortGZipCompressor extends GZipCompressor { protected static final int BYTE_SIZE_OF_SHORT = 2; public ShortGZipCompressor() { super(BYTE_SIZE_OF_SHORT); this.nioBuffer = ByteBuffer.wrap(this.buffer).asShortBuffer(); } @Override protected void getPixel(ShortBuffer pixelData, byte[] pixelBytes) { this.nioBuffer.put(pixelData); } @Override protected void setPixel(ShortBuffer pixelData, byte[] pixelBytes) { pixelData.put(this.nioBuffer); } } private final class TypeConversion { private final PrimitiveType from; private final PrimitiveType to; private final B fromBuffer; private final T toBuffer; private final Object fromArray; private final Object toArray; private TypeConversion(PrimitiveType from) { this.from = from; this.to = getPrimitiveType(GZipCompressor.this.primitiveSize); this.toBuffer = GZipCompressor.this.nioBuffer; this.fromBuffer = from.asTypedBuffer(ByteBuffer.wrap(GZipCompressor.this.buffer)); this.fromArray = from.newArray(DEFAULT_GZIP_BUFFER_SIZE / from.size()); this.toArray = this.to.newArray(DEFAULT_GZIP_BUFFER_SIZE / this.to.size()); } int copy(int byteCount) { this.fromBuffer.rewind(); this.toBuffer.rewind(); this.from.getArray(this.fromBuffer, this.fromArray); ArrayFuncs.copyInto(this.fromArray, this.toArray); this.to.putArray(this.toBuffer, this.toArray); return byteCount * this.to.size() / this.from.size(); } } private static final int DEFAULT_GZIP_BUFFER_SIZE = 65536; private static final int MINIMAL_GZIP_BUFFER_SIZE = 65536; protected final int primitiveSize; protected byte[] buffer = new byte[DEFAULT_GZIP_BUFFER_SIZE]; protected T nioBuffer; private final byte[] sizeArray = new byte[PrimitiveTypes.INT.size()]; private final IntBuffer sizeBuffer = ByteBuffer.wrap(this.sizeArray).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer(); public GZipCompressor(int primitiveSize) { this.primitiveSize = primitiveSize; } @Override public boolean compress(T pixelData, ByteBuffer compressed) { this.nioBuffer.rewind(); int pixelDataLimit = pixelData.limit(); GZIPOutputStream zip = null; try { zip = createGZipOutputStream(pixelDataLimit, compressed); while (pixelData.hasRemaining()) { int count = Math.min(pixelData.remaining(), this.nioBuffer.capacity()); pixelData.limit(pixelData.position() + count); getPixel(pixelData, null); zip.write(this.buffer, 0, this.nioBuffer.position() * this.primitiveSize); this.nioBuffer.rewind(); pixelData.limit(pixelDataLimit); } } catch (IOException e) { throw new IllegalStateException("could not gzip data", e); } finally { SafeClose.close(zip); } compressed.limit(compressed.position()); return true; } @Override public void decompress(ByteBuffer compressed, T pixelData) { this.nioBuffer.rewind(); TypeConversion typeConverter = getTypeConverter(compressed, pixelData.limit()); GZIPInputStream zip = null; try { zip = createGZipInputStream(compressed); int count; while ((count = zip.read(this.buffer)) >= 0) { if (typeConverter != null) { count = typeConverter.copy(count); } this.nioBuffer.position(0); this.nioBuffer.limit(count / this.primitiveSize); setPixel(pixelData, null); } } catch (IOException e) { throw new IllegalStateException("could not gunzip data", e); } finally { SafeClose.close(zip); } } @SuppressWarnings("unchecked") private PrimitiveType getPrimitiveType(int size) { return (PrimitiveType) PrimitiveTypeHandler.valueOf(size * FitsIO.BITS_OF_1_BYTE); } private TypeConversion getTypeConverter(ByteBuffer compressed, int nrOfPrimitiveElements) { if (compressed.limit() > FitsIO.BYTES_IN_INTEGER) { int oldPosition = compressed.position(); try { compressed.position(compressed.limit() - this.sizeArray.length); compressed.get(this.sizeArray); int uncompressedSize = this.sizeBuffer.get(0); if (uncompressedSize > 0) { compressed.position(oldPosition); if (uncompressedSize % nrOfPrimitiveElements == 0) { int compressedPrimitiveSize = uncompressedSize / nrOfPrimitiveElements; if (compressedPrimitiveSize != this.primitiveSize) { return new TypeConversion(getPrimitiveType(compressedPrimitiveSize)); } } } } finally { compressed.position(oldPosition); } } return null; } protected GZIPInputStream createGZipInputStream(ByteBuffer compressed) throws IOException { return new GZIPInputStream(new ByteBufferInputStream(compressed), Math.min(compressed.limit() * 2, DEFAULT_GZIP_BUFFER_SIZE)); } protected GZIPOutputStream createGZipOutputStream(int length, ByteBuffer compressed) throws IOException { return new GZIPOutputStream(new ByteBufferOutputStream(compressed), Math.min(Math.max(length * 2, MINIMAL_GZIP_BUFFER_SIZE), DEFAULT_GZIP_BUFFER_SIZE)); } protected abstract void getPixel(T pixelData, byte[] pixelBytes); protected abstract void setPixel(T pixelData, byte[] pixelBytes); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/gzip2/000077500000000000000000000000001310063650500307605ustar00rootroot00000000000000GZip2Compressor.java000066400000000000000000000174761310063650500345730ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/gzip2package nom.tam.fits.compression.algorithm.gzip2; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor; import nom.tam.util.SafeClose; import nom.tam.util.type.PrimitiveTypes; public abstract class GZip2Compressor extends GZipCompressor { public static class ByteGZip2Compressor extends ByteGZipCompressor { } public static class IntGZip2Compressor extends GZip2Compressor { public IntGZip2Compressor() { super(PrimitiveTypes.INT.size()); } @Override protected void getPixel(IntBuffer pixelData, byte[] pixelBytes) { IntBuffer pixelBuffer = ByteBuffer.wrap(pixelBytes).asIntBuffer(); pixelBuffer.put(pixelData); } @Override protected void setPixel(IntBuffer pixelData, byte[] pixelBytes) { pixelData.put(ByteBuffer.wrap(pixelBytes).asIntBuffer()); } } public static class FloatGZip2Compressor extends GZip2Compressor { public FloatGZip2Compressor() { super(PrimitiveTypes.FLOAT.size()); } @Override protected void getPixel(FloatBuffer pixelData, byte[] pixelBytes) { FloatBuffer pixelBuffer = ByteBuffer.wrap(pixelBytes).asFloatBuffer(); pixelBuffer.put(pixelData); } @Override protected void setPixel(FloatBuffer pixelData, byte[] pixelBytes) { pixelData.put(ByteBuffer.wrap(pixelBytes).asFloatBuffer()); } } public static class LongGZip2Compressor extends GZip2Compressor { public LongGZip2Compressor() { super(PrimitiveTypes.LONG.size()); } @Override protected void getPixel(LongBuffer pixelData, byte[] pixelBytes) { LongBuffer pixelBuffer = ByteBuffer.wrap(pixelBytes).asLongBuffer(); pixelBuffer.put(pixelData); } @Override protected void setPixel(LongBuffer pixelData, byte[] pixelBytes) { pixelData.put(ByteBuffer.wrap(pixelBytes).asLongBuffer()); } } public static class DoubleGZip2Compressor extends GZip2Compressor { public DoubleGZip2Compressor() { super(PrimitiveTypes.DOUBLE.size()); } @Override protected void getPixel(DoubleBuffer pixelData, byte[] pixelBytes) { DoubleBuffer pixelBuffer = ByteBuffer.wrap(pixelBytes).asDoubleBuffer(); pixelBuffer.put(pixelData); } @Override protected void setPixel(DoubleBuffer pixelData, byte[] pixelBytes) { pixelData.put(ByteBuffer.wrap(pixelBytes).asDoubleBuffer()); } } public static class ShortGZip2Compressor extends GZip2Compressor { public ShortGZip2Compressor() { super(PrimitiveTypes.SHORT.size()); } @Override protected void getPixel(ShortBuffer pixelData, byte[] pixelBytes) { ShortBuffer shortBuffer = ByteBuffer.wrap(pixelBytes).asShortBuffer(); shortBuffer.put(pixelData); } @Override protected void setPixel(ShortBuffer pixelData, byte[] pixelBytes) { pixelData.put(ByteBuffer.wrap(pixelBytes).asShortBuffer()); } } public GZip2Compressor(int primitiveSize) { super(primitiveSize); } private int[] calculateOffsets(byte[] byteArray) { int[] offset = new int[this.primitiveSize]; offset[0] = 0; for (int primitivIndex = 1; primitivIndex < this.primitiveSize; primitivIndex++) { offset[primitivIndex] = offset[primitivIndex - 1] + byteArray.length / this.primitiveSize; } return offset; } @Override public boolean compress(T pixelData, ByteBuffer compressed) { int pixelDataLimit = pixelData.limit(); byte[] pixelBytes = new byte[pixelDataLimit * this.primitiveSize]; getPixel(pixelData, pixelBytes); pixelBytes = shuffle(pixelBytes); GZIPOutputStream zip = null; try { zip = createGZipOutputStream(pixelDataLimit, compressed); zip.write(pixelBytes, 0, pixelBytes.length); } catch (IOException e) { throw new IllegalStateException("could not gzip data", e); } finally { SafeClose.close(zip); } return true; } @Override public void decompress(ByteBuffer compressed, T pixelData) { int pixelDataLimit = pixelData.limit(); byte[] pixelBytes = new byte[pixelDataLimit * this.primitiveSize]; GZIPInputStream zip = null; try { zip = createGZipInputStream(compressed); int count = 0; int offset = 0; while (offset < pixelBytes.length && count >= 0) { count = zip.read(pixelBytes, offset, pixelBytes.length - offset); if (count >= 0) { offset = offset + count; } } } catch (IOException e) { throw new IllegalStateException("could not gunzip data", e); } finally { SafeClose.close(zip); } pixelBytes = unshuffle(pixelBytes); setPixel(pixelData, pixelBytes); } public byte[] shuffle(byte[] byteArray) { byte[] result = new byte[byteArray.length]; int resultIndex = 0; int[] offset = calculateOffsets(byteArray); for (int index = 0; index < byteArray.length; index += this.primitiveSize) { for (int primitiveIndex = 0; primitiveIndex < this.primitiveSize; primitiveIndex++) { result[resultIndex + offset[primitiveIndex]] = byteArray[index + primitiveIndex]; } resultIndex++; } return result; } public byte[] unshuffle(byte[] byteArray) { byte[] result = new byte[byteArray.length]; int resultIndex = 0; int[] offset = calculateOffsets(byteArray); for (int index = 0; index < byteArray.length; index += this.primitiveSize) { for (int primitiveIndex = 0; primitiveIndex < this.primitiveSize; primitiveIndex++) { result[index + primitiveIndex] = byteArray[resultIndex + offset[primitiveIndex]]; } resultIndex++; } return result; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompress/000077500000000000000000000000001310063650500317305ustar00rootroot00000000000000HCompress.java000066400000000000000000000704411310063650500344250ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompresspackage nom.tam.fits.compression.algorithm.hcompress; import java.nio.ByteBuffer; import java.nio.LongBuffer; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * The original compression code was written by Richard White at the STScI and * included (ported to c and adapted) in cfitsio by William Pence, NASA/GSFC. * That code was then ported to java by R. van Nieuwenhoven. Later it was * massively refactored to harmonize the different compression algorithms and * reduce the duplicate code pieces without obscuring the algorithm itself as * far as possible. The original site for the algorithm is * *

 *  @see http://www.stsci.edu/software/hcompress.html
 * 
* * @author Richard White * @author William Pence * @author Richard van Nieuwenhoven */ public class HCompress { private static final int HTRANS_START_MASK = -2; protected static final double ROUNDING_HALF = 0.5; protected static final int BITS_OF_1_BYTE = 8; protected static final int BITS_OF_1_NYBBLE = 4; protected static final int BYTE_MASK = 0xff; protected static final int NYBBLE_MASK = 0xF; /** * to be refactored to a good name. */ private static final int N3 = 3; private static final int[] BITS_MASK = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; /* * Huffman code values and number of bits in each code */ private static final int[] CODE = { 0x3e, 0x00, 0x01, 0x08, 0x02, 0x09, 0x1a, 0x1b, 0x03, 0x1c, 0x0a, 0x1d, 0x0b, 0x1e, 0x3f, 0x0c }; private static final byte[] CODE_MAGIC = { (byte) 0xDD, (byte) 0x99 }; private static final int[] NCODE = { 6, 3, 3, 4, 3, 4, 5, 5, 3, 5, 4, 5, 4, 5, 6, 4 }; /** * variables for bit output to buffer when Huffman coding */ private int bitbuffer; /** Number of bits free in buffer */ private int bitsToGo2; private int bitsToGo3; /** Bits buffered for output */ private int buffer2; private int b2i(boolean b) { return b ? 1 : 0; } private int bufcopy(byte[] a, int n, byte[] buffer, int b, long bmax) { int i; for (i = 0; i < n; i++) { if (a[i] != 0) { /* * add Huffman code for a[i] to buffer */ this.bitbuffer |= CODE[a[i]] << this.bitsToGo3; this.bitsToGo3 += NCODE[a[i]]; if (this.bitsToGo3 >= BITS_OF_1_BYTE) { buffer[b] = (byte) (this.bitbuffer & BYTE_MASK); b += 1; /* * return warning code if we fill buffer */ if (b >= bmax) { return b; } this.bitbuffer >>= BITS_OF_1_BYTE; this.bitsToGo3 -= BITS_OF_1_BYTE; } } } return b; } protected void compress(long[] aa, int ny, int nx, int scale, ByteBuffer output) { /* * compress the input image using the H-compress algorithm a - input * image tiledImageOperation nx - size of X axis of image ny - size of Y * axis of image scale - quantization scale factor. Larger values * results in more (lossy) compression scale = 0 does lossless * compression output - pre-allocated tiledImageOperation to hold the * output compressed stream of bytes nbyts - input value = size of the * output buffer; returned value = size of the compressed byte stream, * in bytes NOTE: the nx and ny dimensions as defined within this code * are reversed from the usual FITS notation. ny is the fastest varying * dimension, which is usually considered the X axis in the FITS image * display */ /* H-transform */ htrans(aa, nx, ny); LongBuffer a = LongBuffer.wrap(aa); /* digitize */ digitize(a, 0, nx, ny, scale); /* encode and write to output tiledImageOperation */ encode(output, a, nx, ny, scale); } private LongBuffer copy(LongBuffer a, int i) { a.position(i); return a.slice(); } private void digitize(LongBuffer a, int aOffset, int nx, int ny, long scale) { /* * round to multiple of scale */ if (scale <= 1) { return; } long d = (scale + 1L) / 2L - 1L; for (int index = 0; index < a.limit(); index++) { long current = a.get(index); a.put(index, (current > 0 ? current + d : current - d) / scale); } } /** * encode pixels. * * @param compressedBytes * compressed data * @param pixels * pixels to compress * @param nx * image width dimension * @param ny * image height dimension * @param nbitplanes * Number of bit planes in quadrants */ private void doEncode(ByteBuffer compressedBytes, LongBuffer pixels, int nx, int ny, byte[] nbitplanes) { int nx2 = (nx + 1) / 2; int ny2 = (ny + 1) / 2; /* * Initialize bit output */ startOutputtingBits(); /* * write out the bit planes for each quadrant */ qtreeEncode(compressedBytes, copy(pixels, 0), ny, nx2, ny2, nbitplanes[0]); qtreeEncode(compressedBytes, copy(pixels, ny2), ny, nx2, ny / 2, nbitplanes[1]); qtreeEncode(compressedBytes, copy(pixels, ny * nx2), ny, nx / 2, ny2, nbitplanes[1]); qtreeEncode(compressedBytes, copy(pixels, ny * nx2 + ny2), ny, nx / 2, ny / 2, nbitplanes[2]); /* * Add zero as an EOF symbol */ outputNybble(compressedBytes, 0); doneOutputtingBits(compressedBytes); } private void doneOutputtingBits(ByteBuffer outfile) { if (this.bitsToGo2 < BITS_OF_1_BYTE) { /* putc(buffer2< 0) { /* * positive element, put zero at end of buffer */ signbits[nsign] <<= 1; bitsToGo -= 1; } else if (a.get(i) < 0) { /* * negative element, shift in a one */ signbits[nsign] <<= 1; signbits[nsign] |= 1; bitsToGo -= 1; /* * replace a by absolute value */ a.put(i, -a.get(i)); } if (bitsToGo == 0) { /* * filled up this byte, go to the next one */ bitsToGo = BITS_OF_1_BYTE; nsign += 1; signbits[nsign] = 0; } } if (bitsToGo != BITS_OF_1_BYTE) { /* * some bits in last element move bits in last byte to bottom and * increment nsign */ signbits[nsign] <<= bitsToGo; nsign += 1; } /* * calculate number of bit planes for 3 quadrants quadrant 0=bottom * left, 1=bottom right or top left, 2=top right, */ for (int q = 0; q < N3; q++) { vmax[q] = 0; } /* * get maximum absolute value in each quadrant */ int nx2 = (nx + 1) / 2; int ny2 = (ny + 1) / 2; int j = 0; /* column counter */ int k = 0; /* row counter */ for (int i = 0; i < nel; i++) { int q = (j >= ny2 ? 1 : 0) + (k >= nx2 ? 1 : 0); if (vmax[q] < a.get(i)) { vmax[q] = a.get(i); } if (++j >= ny) { j = 0; k += 1; } } /* * now calculate number of bits for each quadrant */ /* this is a more efficient way to do this, */ for (int q = 0; q < N3; q++) { nbitplanes[q] = 0; while (vmax[q] > 0) { vmax[q] = vmax[q] >> 1; nbitplanes[q]++; } } /* * write nbitplanes */ compressedBytes.put(nbitplanes, 0, nbitplanes.length); /* * write coded tiledImageOperation */ doEncode(compressedBytes, a, nx, ny, nbitplanes); /* * write sign bits */ if (nsign > 0) { compressedBytes.put(signbits, 0, nsign); } return (int) noutchar; } private int htrans(long[] a, int nx, int ny) { /* * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ int nmax = nx > ny ? nx : ny; int log2n = log2n(nmax); if (nmax > 1 << log2n) { log2n += 1; } /* * get temporary storage for shuffling elements */ long[] tmp = new long[(nmax + 1) / 2]; /* * set up rounding and shifting masks */ long shift = 0; long mask = HTRANS_START_MASK; long mask2 = mask << 1; long prnd = 1; long prnd2 = prnd << 1; long nrnd2 = prnd2 - 1; /* * do log2n reductions We're indexing a as a 2-D tiledImageOperation * with dimensions (nx,ny). */ int nxtop = nx; int nytop = ny; for (int k = 0; k < log2n; k++) { int oddx = nxtop % 2; int oddy = nytop % 2; int i = 0; for (; i < nxtop - oddx; i += 2) { int s00 = i * ny; /* s00 is index of a[i,j] */ int s10 = s00 + ny; /* s10 is index of a[i+1,j] */ for (int j = 0; j < nytop - oddy; j += 2) { /* * Divide h0,hx,hy,hc by 2 (1 the first time through). */ long h0 = a[s10 + 1] + a[s10] + a[s00 + 1] + a[s00] >> shift; long hx = a[s10 + 1] + a[s10] - a[s00 + 1] - a[s00] >> shift; long hy = a[s10 + 1] - a[s10] + a[s00 + 1] - a[s00] >> shift; long hc = a[s10 + 1] - a[s10] - a[s00 + 1] + a[s00] >> shift; /* * Throw away the 2 bottom bits of h0, bottom bit of hx,hy. * To get rounding to be same for positive and negative * numbers, nrnd2 = prnd2 - 1. */ a[s10 + 1] = hc; a[s10] = (hx >= 0 ? hx + prnd : hx) & mask; a[s00 + 1] = (hy >= 0 ? hy + prnd : hy) & mask; a[s00] = (h0 >= 0 ? h0 + prnd2 : h0 + nrnd2) & mask2; s00 += 2; s10 += 2; } if (oddy != 0) { /* * do last element in row if row length is odd s00+1, s10+1 * are off edge */ long h0 = a[s10] + a[s00] << 1 - shift; long hx = a[s10] - a[s00] << 1 - shift; a[s10] = (hx >= 0 ? hx + prnd : hx) & mask; a[s00] = (h0 >= 0 ? h0 + prnd2 : h0 + nrnd2) & mask2; s00 += 1; s10 += 1; } } if (oddx != 0) { /* * do last row if column length is odd s10, s10+1 are off edge */ int s00 = i * ny; for (int j = 0; j < nytop - oddy; j += 2) { long h0 = a[s00 + 1] + a[s00] << 1 - shift; long hy = a[s00 + 1] - a[s00] << 1 - shift; a[s00 + 1] = (hy >= 0 ? hy + prnd : hy) & mask; a[s00] = (h0 >= 0 ? h0 + prnd2 : h0 + nrnd2) & mask2; s00 += 2; } if (oddy != 0) { /* * do corner element if both row and column lengths are odd * s00+1, s10, s10+1 are off edge */ long h0 = a[s00] << 2 - shift; a[s00] = (h0 >= 0 ? h0 + prnd2 : h0 + nrnd2) & mask2; } } /* * now shuffle in each dimension to group coefficients by order */ // achtung eigenlich pointer nach a for (i = 0; i < nxtop; i++) { shuffle(a, ny * i, nytop, 1, tmp); } for (int j = 0; j < nytop; j++) { shuffle(a, j, nxtop, ny, tmp); } /* * image size reduced by 2 (round up if odd) */ nxtop = nxtop + 1 >> 1; nytop = nytop + 1 >> 1; /* * divisor doubles after first reduction */ shift = 1; /* * masks, rounding values double after each iteration */ mask = mask2; prnd = prnd2; mask2 = mask2 << 1; prnd2 = prnd2 << 1; nrnd2 = prnd2 - 1; } return 0; } private int log2n(int nqmax) { return (int) (Math.log(nqmax) / Math.log(2.0) + ROUNDING_HALF); } private void outputNbits(ByteBuffer outfile, int bits, int n) { /* AND mask for the right-most n bits */ /* * insert bits at end of buffer */ this.buffer2 <<= n; /* buffer2 |= ( bits & ((1<> -this.bitsToGo2 & BYTE_MASK)); this.bitsToGo2 += BITS_OF_1_BYTE; } } private void outputNnybble(ByteBuffer outfile, int n, byte[] array) { /* * pack the 4 lower bits in each element of the tiledImageOperation into * the outfile tiledImageOperation */ int ii, jj, kk = 0, shift; if (n == 1) { outputNybble(outfile, array[0]); return; } /* * forcing byte alignment doesn;t help, and even makes it go slightly * slower if (bits_to_go2 != 8) output_nbits(outfile, kk, bits_to_go2); */ if (this.bitsToGo2 <= BITS_OF_1_NYBBLE) { /* just room for 1 nybble; write it out separately */ outputNybble(outfile, array[0]); kk++; /* index to next tiledImageOperation element */ if (n == 2) { // only 1 more nybble to write out outputNybble(outfile, array[1]); return; } } /* bits_to_go2 is now in the range 5 - 8 */ shift = BITS_OF_1_BYTE - this.bitsToGo2; /* * now write out pairs of nybbles; this does not affect value of * bits_to_go2 */ jj = (n - kk) / 2; if (this.bitsToGo2 == BITS_OF_1_BYTE) { /* special case if nybbles are aligned on byte boundary */ /* this actually seems to make very little difference in speed */ this.buffer2 = 0; for (ii = 0; ii < jj; ii++) { outfile.put((byte) ((array[kk] & NYBBLE_MASK) << BITS_OF_1_NYBBLE | array[kk + 1] & NYBBLE_MASK)); kk += 2; } } else { for (ii = 0; ii < jj; ii++) { this.buffer2 = this.buffer2 << BITS_OF_1_BYTE | (array[kk] & NYBBLE_MASK) << BITS_OF_1_NYBBLE | array[kk + 1] & NYBBLE_MASK; kk += 2; /* * buffer2 full, put out top 8 bits */ outfile.put((byte) (this.buffer2 >> shift & BYTE_MASK)); } } /* write out last odd nybble, if present */ if (kk != n) { outputNybble(outfile, array[n - 1]); } } private void outputNybble(ByteBuffer outfile, int bits) { /* * insert 4 bits at end of buffer */ this.buffer2 = this.buffer2 << BITS_OF_1_NYBBLE | bits & NYBBLE_MASK; this.bitsToGo2 -= BITS_OF_1_NYBBLE; if (this.bitsToGo2 <= 0) { /* * buffer2 full, put out top 8 bits */ outfile.put((byte) (this.buffer2 >> -this.bitsToGo2 & BYTE_MASK)); this.bitsToGo2 += BITS_OF_1_BYTE; } } /** * macros to write out 4-bit nybble, Huffman code for this value */ private int qtreeEncode(ByteBuffer outfile, LongBuffer a, int n, int nqx, int nqy, int nbitplanes) { /* * int a[]; int n; physical dimension of row in a int nqx; length of row * int nqy; length of column (<=n) int nbitplanes; number of bit planes * to output */ int log2n, i, k, bit, b, nqmax, nqx2, nqy2, nx, ny; long bmax; byte[] scratch, buffer; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ nqmax = nqx > nqy ? nqx : nqy; log2n = log2n(nqmax); if (nqmax > 1 << log2n) { log2n += 1; } /* * initialize buffer point, max buffer size */ nqx2 = (nqx + 1) / 2; nqy2 = (nqy + 1) / 2; bmax = (nqx2 * nqy2 + 1) / 2; /* * We're indexing A as a 2-D tiledImageOperation with dimensions * (nqx,nqy). Scratch is 2-D with dimensions (nqx/2,nqy/2) rounded up. * Buffer is used to store string of codes for output. */ scratch = new byte[(int) (2 * bmax)]; buffer = new byte[(int) bmax]; /* * now encode each bit plane, starting with the top */ bitplane_done: for (bit = nbitplanes - 1; bit >= 0; bit--) { /* * initial bit buffer */ b = 0; this.bitbuffer = 0; this.bitsToGo3 = 0; /* * on first pass copy A to scratch tiledImageOperation */ qtreeOnebit(a, n, nqx, nqy, scratch, bit); nx = nqx + 1 >> 1; ny = nqy + 1 >> 1; /* * copy non-zero values to output buffer, which will be written in * reverse order */ b = bufcopy(scratch, nx * ny, buffer, b, bmax); if (b >= bmax) { /* * quadtree is expanding data, change warning code and just fill * buffer with bit-map */ writeBdirect(outfile, a, n, nqx, nqy, scratch, bit); continue bitplane_done; } /* * do log2n reductions */ for (k = 1; k < log2n; k++) { qtreeReduce(scratch, ny, nx, ny, scratch); nx = nx + 1 >> 1; ny = ny + 1 >> 1; b = bufcopy(scratch, nx * ny, buffer, b, bmax); if (b >= bmax) { writeBdirect(outfile, a, n, nqx, nqy, scratch, bit); continue bitplane_done; } } /* * OK, we've got the code in buffer Write quadtree warning code, * then write buffer in reverse order */ outputNybble(outfile, NYBBLE_MASK); if (b == 0) { if (this.bitsToGo3 > 0) { /* * put out the last few bits */ outputNbits(outfile, this.bitbuffer & (1 << this.bitsToGo3) - 1, this.bitsToGo3); } else { /* * have to write a zero nybble if there are no 1's in * tiledImageOperation */ outputNbits(outfile, CODE[0], NCODE[0]); } } else { if (this.bitsToGo3 > 0) { /* * put out the last few bits */ outputNbits(outfile, this.bitbuffer & (1 << this.bitsToGo3) - 1, this.bitsToGo3); } for (i = b - 1; i >= 0; i--) { outputNbits(outfile, buffer[i], BITS_OF_1_BYTE); } } } return 0; } private void qtreeOnebit(LongBuffer a, int n, int nx, int ny, byte[] b, int bit) { int i, j, k; long b0, b1, b2, b3; int s10, s00; /* * use selected bit to get amount to shift */ b0 = 1L << bit; b1 = b0 << 1; b2 = b1 << 1; b3 = b2 << 1; k = 0; /* k is index of b[i/2,j/2] */ for (i = 0; i < nx - 1; i += 2) { s00 = n * i; /* s00 is index of a[i,j] */ /* * tried using s00+n directly in the statements, but this had no * effect on performance */ s10 = s00 + n; /* s10 is index of a[i+1,j] */ for (j = 0; j < ny - 1; j += 2) { b[k] = (byte) ((a.get(s10 + 1) & b0 // | a.get(s10) << 1 & b1 // | a.get(s00 + 1) << 2 & b2 // | a.get(s00) << N3 & b3) >> bit); k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row s00+1,s10+1 are off * edge */ b[k] = (byte) ((a.get(s10) << 1 & b1 | a.get(s00) << N3 & b3) >> bit); k += 1; } } if (i < nx) { /* * column size is odd, do last row s10,s10+1 are off edge */ s00 = n * i; for (j = 0; j < ny - 1; j += 2) { b[k] = (byte) ((a.get(s00 + 1) << 2 & b2 | a.get(s00) << N3 & b3) >> bit); k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element s00+1, * s10, s10+1 are off edge */ b[k] = (byte) ((a.get(s00) << N3 & b3) >> bit); k += 1; } } } private void qtreeReduce(byte[] a, int n, int nx, int ny, byte[] b) { int i, j, k; int s10, s00; k = 0; /* k is index of b[i/2,j/2] */ for (i = 0; i < nx - 1; i += 2) { s00 = n * i; /* s00 is index of a[i,j] */ s10 = s00 + n; /* s10 is index of a[i+1,j] */ for (j = 0; j < ny - 1; j += 2) { b[k] = (byte) (b2i(a[s10 + 1] != 0) | b2i(a[s10] != 0) << 1 | b2i(a[s00 + 1] != 0) << 2 | b2i(a[s00] != 0) << N3); k += 1; s00 += 2; s10 += 2; } if (j < ny) { /* * row size is odd, do last element in row s00+1,s10+1 are off * edge */ b[k] = (byte) (b2i(a[s10] != 0) << 1 | b2i(a[s00] != 0) << N3); k += 1; } } if (i < nx) { /* * column size is odd, do last row s10,s10+1 are off edge */ s00 = n * i; for (j = 0; j < ny - 1; j += 2) { b[k] = (byte) (b2i(a[s00 + 1] != 0) << 2 | b2i(a[s00] != 0) << N3); k += 1; s00 += 2; } if (j < ny) { /* * both row and column size are odd, do corner element s00+1, * s10, s10+1 are off edge */ b[k] = (byte) (b2i(a[s00] != 0) << N3); k += 1; } } } private void shuffle(long[] a, int aOffset, int n, int n2, long[] tmp) { /* * int a[]; tiledImageOperation to shuffle int n; number of elements to * shuffle int n2; second dimension int tmp[]; scratch storage */ int i; long[] p1, p2, pt; int p1Offset; int ptOffset; int p2Offset; /* * copy odd elements to tmp */ pt = tmp; ptOffset = 0; p1 = a; p1Offset = aOffset + n2; for (i = 1; i < n; i += 2) { pt[ptOffset] = p1[p1Offset]; ptOffset += 1; p1Offset += n2 + n2; } /* * compress even elements into first half of A */ p1 = a; p1Offset = aOffset + n2; p2 = a; p2Offset = aOffset + n2 + n2; for (i = 2; i < n; i += 2) { p1[p1Offset] = p2[p2Offset]; p1Offset += n2; p2Offset += n2 + n2; } /* * put odd elements into 2nd half */ pt = tmp; ptOffset = 0; for (i = 1; i < n; i += 2) { p1[p1Offset] = pt[ptOffset]; p1Offset += n2; ptOffset += 1; } } private void startOutputtingBits() { this.buffer2 = 0; /* Buffer is empty to start */ this.bitsToGo2 = BITS_OF_1_BYTE; /* with */ } private void writeBdirect(ByteBuffer outfile, LongBuffer a, int n, int nqx, int nqy, byte[] scratch, int bit) { /* * Write the direct bitmap warning code */ outputNybble(outfile, 0x0); /* * Copy A to scratch tiledImageOperation (again!), packing 4 bits/nybble */ qtreeOnebit(a, n, nqx, nqy, scratch, bit); /* * write to outfile */ /* * int i; for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) { * output_nybble(outfile,scratch[i]); } */ outputNnybble(outfile, (nqx + 1) / 2 * ((nqy + 1) / 2), scratch); } } HCompressor.java000066400000000000000000000135611310063650500347660ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompresspackage nom.tam.fits.compression.algorithm.hcompress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import nom.tam.fits.compression.algorithm.api.ICompressor; import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.DoubleQuantCompressor; import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.FloatQuantCompressor; import nom.tam.util.ArrayFuncs; public abstract class HCompressor implements ICompressor { public static class ByteHCompressor extends HCompressor { private static final long BYTE_MASK_FOR_LONG = 0xFFL; public ByteHCompressor(HCompressorOption options) { super(options); } @Override public boolean compress(ByteBuffer buffer, ByteBuffer compressed) { byte[] byteArray = new byte[buffer.limit()]; buffer.get(byteArray); long[] longArray = new long[byteArray.length]; for (int index = 0; index < longArray.length; index++) { longArray[index] = byteArray[index] & BYTE_MASK_FOR_LONG; } compress(longArray, compressed); return true; } @Override public void decompress(ByteBuffer compressed, ByteBuffer buffer) { long[] longArray = new long[buffer.limit()]; decompress(compressed, longArray); for (long element : longArray) { buffer.put((byte) element); } } } public static class DoubleHCompressor extends DoubleQuantCompressor { public DoubleHCompressor(HCompressorQuantizeOption options) { super(options, new IntHCompressor(options.getHCompressorOption())); } } public static class FloatHCompressor extends FloatQuantCompressor { public FloatHCompressor(HCompressorQuantizeOption options) { super(options, new IntHCompressor(options.getHCompressorOption())); } } public static class IntHCompressor extends HCompressor { public IntHCompressor(HCompressorOption options) { super(options); } @Override public boolean compress(IntBuffer buffer, ByteBuffer compressed) { int[] intArray = new int[buffer.limit()]; buffer.get(intArray); long[] longArray = new long[intArray.length]; ArrayFuncs.copyInto(intArray, longArray); compress(longArray, compressed); return true; } @Override public void decompress(ByteBuffer compressed, IntBuffer buffer) { long[] longArray = new long[buffer.limit()]; decompress(compressed, longArray); for (long element : longArray) { buffer.put((int) element); } } } public static class ShortHCompressor extends HCompressor { public ShortHCompressor(HCompressorOption options) { super(options); } @Override public boolean compress(ShortBuffer buffer, ByteBuffer compressed) { short[] shortArray = new short[buffer.limit()]; buffer.get(shortArray); long[] longArray = new long[shortArray.length]; ArrayFuncs.copyInto(shortArray, longArray); compress(longArray, compressed); return true; } @Override public void decompress(ByteBuffer compressed, ShortBuffer buffer) { long[] longArray = new long[buffer.limit()]; decompress(compressed, longArray); for (long element : longArray) { buffer.put((short) element); } } } private HCompress compress; private HDecompress decompress; private final HCompressorOption options; public HCompressor(HCompressorOption options) { this.options = options; } private HCompress compress() { if (this.compress == null) { this.compress = new HCompress(); } return this.compress; } protected void compress(long[] longArray, ByteBuffer compressed) { compress().compress(longArray, this.options.getTileHeight(), this.options.getTileWidth(), this.options.getScale(), compressed); } private HDecompress decompress() { if (this.decompress == null) { this.decompress = new HDecompress(); } return this.decompress; } protected void decompress(ByteBuffer compressed, long[] aa) { decompress().decompress(compressed, this.options.isSmooth(), aa); } } HCompressorOption.java000066400000000000000000000066601310063650500361610ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompresspackage nom.tam.fits.compression.algorithm.hcompress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.compression.algorithm.api.ICompressOption; import nom.tam.fits.compression.provider.param.api.ICompressParameters; public class HCompressorOption implements ICompressOption { /** * circular dependency, has to be cut. */ private ICompressParameters parameters; private int scale; private boolean smooth; private int tileHeight; private int tileWidth; @Override public HCompressorOption copy() { try { return ((HCompressorOption) clone()).setOriginal(this); } catch (CloneNotSupportedException e) { throw new IllegalStateException("option could not be cloned", e); } } @Override public ICompressParameters getCompressionParameters() { return this.parameters; } public int getScale() { return this.scale; } public int getTileHeight() { return this.tileHeight; } public int getTileWidth() { return this.tileWidth; } @Override public boolean isLossyCompression() { return this.scale > 1 || this.smooth; } public boolean isSmooth() { return this.smooth; } @Override public void setParameters(ICompressParameters parameters) { this.parameters = parameters; } public HCompressorOption setScale(int value) { this.scale = value; return this; } public HCompressorOption setSmooth(boolean value) { this.smooth = value; return this; } @Override public HCompressorOption setTileHeight(int value) { this.tileHeight = value; return this; } @Override public HCompressorOption setTileWidth(int value) { this.tileWidth = value; return this; } @Override public T unwrap(Class clazz) { if (clazz.isAssignableFrom(this.getClass())) { return clazz.cast(this); } return null; } private HCompressorOption setOriginal(HCompressorOption hCompressorOption) { this.parameters = this.parameters.copy(this); return this; } } HCompressorQuantizeOption.java000066400000000000000000000052621310063650500376770ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompresspackage nom.tam.fits.compression.algorithm.hcompress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.compression.algorithm.quant.QuantizeOption; import nom.tam.fits.compression.provider.param.hcompress.HCompressQuantizeParameters; public class HCompressorQuantizeOption extends QuantizeOption { private HCompressorOption hCompressorOption = new HCompressorOption(); public HCompressorQuantizeOption() { super(); this.parameters = new HCompressQuantizeParameters(this); } @Override public HCompressorQuantizeOption copy() { HCompressorQuantizeOption copy = (HCompressorQuantizeOption) super.copy(); copy.hCompressorOption = this.hCompressorOption.copy(); return copy; } public HCompressorOption getHCompressorOption() { return this.hCompressorOption; } @Override public HCompressorQuantizeOption setTileHeight(int value) { super.setTileHeight(value); this.hCompressorOption.setTileHeight(value); return this; } @Override public HCompressorQuantizeOption setTileWidth(int value) { super.setTileWidth(value); this.hCompressorOption.setTileWidth(value); return this; } @Override public T unwrap(Class clazz) { T result = super.unwrap(clazz); if (result == null) { return this.hCompressorOption.unwrap(clazz); } return result; } } HDecompress.java000066400000000000000000001152401310063650500347330ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/hcompresspackage nom.tam.fits.compression.algorithm.hcompress; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import static nom.tam.fits.compression.algorithm.hcompress.HCompress.BITS_OF_1_BYTE; import static nom.tam.fits.compression.algorithm.hcompress.HCompress.BITS_OF_1_NYBBLE; import static nom.tam.fits.compression.algorithm.hcompress.HCompress.BYTE_MASK; import static nom.tam.fits.compression.algorithm.hcompress.HCompress.NYBBLE_MASK; import static nom.tam.fits.compression.algorithm.hcompress.HCompress.ROUNDING_HALF; import java.nio.ByteBuffer; /** * The original decompression code was written by R. White at the STScI and * included (ported to c and adapted) in cfitsio by William Pence, NASA/GSFC. * That code was then ported to java by R. van Nieuwenhoven. Later it was * massively refactored to harmonize the different compression algorithms and * reduce the duplicate code pieces without obscuring the algorithm itself as * far as possible. The original site for the algorithm is * *
 *  @see http://www.stsci.edu/software/hcompress.html
 * 
* * @author Richard White * @author William Pence * @author Richard van Nieuwenhoven */ public class HDecompress { private static class LongArrayPointer { private final long[] a; private int offset; LongArrayPointer(long[] tmp) { this.a = tmp; this.offset = 0; } public void bitOr(int i, long planeVal) { this.a[this.offset + i] |= planeVal; } public LongArrayPointer copy(int extraOffset) { LongArrayPointer intAP = new LongArrayPointer(this.a); intAP.offset = this.offset + extraOffset; return intAP; } public long get() { return this.a[this.offset]; } public long get(int i) { return this.a[this.offset + i]; } public void set(int i, long value) { this.a[this.offset + i] = value; } public void set(long value) { this.a[this.offset] = value; } } private static final byte[] CODE_MAGIC = { (byte) 0xDD, (byte) 0x99 }; private static final int[] MASKS = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; private static final byte ZERO = 0; private static final byte BIT_ONE = 1; private static final byte BIT_TWO = 2; private static final byte BIT_THREE = 4; private static final byte BIT_FOUR = 8; /** * these N constants are obscuring the algorithm and should get some * explaining javadoc if somebody understands the algorithm. */ private static final int N03 = 3; private static final int N04 = 4; private static final int N05 = 5; private static final int N06 = 6; private static final int N07 = 7; private static final int N08 = 8; private static final int N09 = 9; private static final int N10 = 10; private static final int N11 = 11; private static final int N12 = 12; private static final int N13 = 13; private static final int N14 = 14; private static final int N15 = 15; private static final int N26 = 26; private static final int N27 = 27; private static final int N28 = 28; private static final int N29 = 29; private static final int N30 = 30; private static final int N31 = 31; private static final int N62 = 62; private static final int N63 = 63; /** * Number of bits still in buffer */ private int bitsToGo; /** Bits waiting to be input */ private int buffer2; private int nx; private int ny; private int scale; /** * log2n is log2 of max(nx,ny) rounded up to next power of 2 */ private int calculateLog2N(int nmax) { int log2n; log2n = (int) (Math.log(nmax) / Math.log(2.0) + ROUNDING_HALF); if (nmax > 1 << log2n) { log2n += 1; } return log2n; } /** * char *infile; input file long *a; address of output tiledImageOperation * [nx][ny] int *nx,*ny; size of output tiledImageOperation int *scale; * scale factor for digitization * * @param infile * @param a */ private void decode64(ByteBuffer infile, LongArrayPointer a) { byte[] nbitplanes = new byte[N03]; byte[] tmagic = new byte[2]; /* * File starts either with special 2-byte magic code or with FITS * keyword "SIMPLE =" */ infile.get(tmagic); /* * check for correct magic code value */ if (tmagic[0] != CODE_MAGIC[0] || tmagic[1] != CODE_MAGIC[1]) { throw new RuntimeException("Compression error"); } this.nx = infile.getInt(); /* x size of image */ this.ny = infile.getInt(); /* y size of image */ this.scale = infile.getInt(); /* scale factor for digitization */ /* sum of all pixels */ long sumall = infile.getLong(); /* # bits in quadrants */ infile.get(nbitplanes); dodecode64(infile, a, nbitplanes); /* * put sum of all pixels back into pixel 0 */ a.set(0, sumall); } /** * decompress the input byte stream using the H-compress algorithm input - * input tiledImageOperation of compressed bytes a - pre-allocated * tiledImageOperation to hold the output uncompressed image nx - returned X * axis size ny - returned Y axis size NOTE: the nx and ny dimensions as * defined within this code are reversed from the usual FITS notation. ny is * the fastest varying dimension, which is usually considered the X axis in * the FITS image display * * @param input * the input buffer to decompress * @param smooth * should the image be smoothed * @param aa * the resulting long tiledImageOperation */ public void decompress(ByteBuffer input, boolean smooth, long[] aa) { LongArrayPointer a = new LongArrayPointer(aa); /* decode the input tiledImageOperation */ decode64(input, a); /* * Un-Digitize */ undigitize64(a); /* * Inverse H-transform */ hinv64(a, smooth); } /** * long a[]; int nx,ny; Array dimensions are [nx][ny] unsigned char * nbitplanes[3]; Number of bit planes in quadrants */ private int dodecode64(ByteBuffer infile, LongArrayPointer a, byte[] nbitplanes) { int nel = this.nx * this.ny; int nx2 = (this.nx + 1) / 2; int ny2 = (this.ny + 1) / 2; /* * initialize a to zero */ for (int i = 0; i < nel; i++) { a.set(i, 0); } /* * Initialize bit input */ startInputingBits(); /* * read bit planes for each quadrant */ qtreeDecode64(infile, a.copy(0), this.ny, nx2, ny2, nbitplanes[0]); qtreeDecode64(infile, a.copy(ny2), this.ny, nx2, this.ny / 2, nbitplanes[1]); qtreeDecode64(infile, a.copy(this.ny * nx2), this.ny, this.nx / 2, ny2, nbitplanes[1]); qtreeDecode64(infile, a.copy(this.ny * nx2 + ny2), this.ny, this.nx / 2, this.ny / 2, nbitplanes[2]); /* * make sure there is an EOF symbol (nybble=0) at end */ if (inputNybble(infile) != 0) { throw new RuntimeException("Compression error"); } /* * now get the sign bits Re-initialize bit input */ startInputingBits(); for (int i = 0; i < nel; i++) { if (a.get(i) != 0) { if (inputBit(infile) != 0) { a.set(i, -a.get(i)); } } } return 0; } /** * int smooth; 0 for no smoothing, else smooth during inversion int scale; * used if smoothing is specified */ private int hinv64(LongArrayPointer a, boolean smooth) { int nmax = this.nx > this.ny ? this.nx : this.ny; int log2n = calculateLog2N(nmax); // get temporary storage for shuffling elements long[] tmp = new long[(nmax + 1) / 2]; // set up masks, rounding parameters int shift = 1; long bit0 = (long) 1 << log2n - 1; long bit1 = bit0 << 1; long bit2 = bit0 << 2; long mask0 = -bit0; long mask1 = mask0 << 1; long mask2 = mask0 << 2; long prnd0 = bit0 >> 1; long prnd1 = bit1 >> 1; long prnd2 = bit2 >> 1; long nrnd0 = prnd0 - 1; long nrnd1 = prnd1 - 1; long nrnd2 = prnd2 - 1; // round h0 to multiple of bit2 a.set(0, a.get(0) + (a.get(0) >= 0 ? prnd2 : nrnd2) & mask2); // do log2n expansions We're indexing a as a 2-D tiledImageOperation // with dimensions // (nx,ny). int nxtop = 1; int nytop = 1; int nxf = this.nx; int nyf = this.ny; int c = 1 << log2n; int i; for (int k = log2n - 1; k >= 0; k--) { // this somewhat cryptic code generates the sequence ntop[k-1] = // (ntop[k]+1)/2, where ntop[log2n] = n c = c >> 1; nxtop = nxtop << 1; nytop = nytop << 1; if (nxf <= c) { nxtop -= 1; } else { nxf -= c; } if (nyf <= c) { nytop -= 1; } else { nyf -= c; } // double shift and fix nrnd0 (because prnd0=0) on last pass if (k == 0) { nrnd0 = 0; shift = 2; } // unshuffle in each dimension to interleave coefficients for (i = 0; i < nxtop; i++) { unshuffle64(a.copy(this.ny * i), nytop, 1, tmp); } for (int j = 0; j < nytop; j++) { unshuffle64(a.copy(j), nxtop, this.ny, tmp); } // smooth by interpolating coefficients if SMOOTH != 0 if (smooth) { hsmooth64(a, nxtop, nytop); } int oddx = nxtop % 2; int oddy = nytop % 2; for (i = 0; i < nxtop - oddx; i += 2) { int s00 = this.ny * i; /* s00 is index of a[i,j] */ int s10 = s00 + this.ny; /* s10 is index of a[i+1,j] */ for (int j = 0; j < nytop - oddy; j += 2) { long h0 = a.get(s00); long hx = a.get(s10); long hy = a.get(s00 + 1); long hc = a.get(s10 + 1); // round hx and hy to multiple of bit1, hc to multiple of // bit0 h0 is already a multiple of bit2 hx = hx + (hx >= 0 ? prnd1 : nrnd1) & mask1; hy = hy + (hy >= 0 ? prnd1 : nrnd1) & mask1; hc = hc + (hc >= 0 ? prnd0 : nrnd0) & mask0; // propagate bit0 of hc to hx,hy long lowbit0 = hc & bit0; hx = hx >= 0 ? hx - lowbit0 : hx + lowbit0; hy = hy >= 0 ? hy - lowbit0 : hy + lowbit0; // Propagate bits 0 and 1 of hc,hx,hy to h0. This could be // simplified if we assume h0>0, but then the inversion // would not be lossless for images with negative pixels. long lowbit1 = (hc ^ hx ^ hy) & bit1; h0 = h0 >= 0 ? h0 + lowbit0 - lowbit1 : h0 + (lowbit0 == 0 ? lowbit1 : lowbit0 - lowbit1); // Divide sums by 2 (4 last time) a.set(s10 + 1, h0 + hx + hy + hc >> shift); a.set(s10, h0 + hx - hy - hc >> shift); a.set(s00 + 1, h0 - hx + hy - hc >> shift); a.set(s00, h0 - hx - hy + hc >> shift); s00 += 2; s10 += 2; } if (oddy != 0) { // do last element in row if row length is odd s00+1, s10+1 // are off edge long h0 = a.get(s00); long hx = a.get(s10); hx = (hx >= 0 ? hx + prnd1 : hx + nrnd1) & mask1; long lowbit1 = hx & bit1; h0 = h0 >= 0 ? h0 - lowbit1 : h0 + lowbit1; a.set(s10, h0 + hx >> shift); a.set(s00, h0 - hx >> shift); } } if (oddx != 0) { // do last row if column length is odd s10, s10+1 are off edge int s00 = this.ny * i; for (int j = 0; j < nytop - oddy; j += 2) { long h0 = a.get(s00); long hy = a.get(s00 + 1); hy = (hy >= 0 ? hy + prnd1 : hy + nrnd1) & mask1; long lowbit1 = hy & bit1; h0 = h0 >= 0 ? h0 - lowbit1 : h0 + lowbit1; a.set(s00 + 1, h0 + hy >> shift); a.set(s00, h0 - hy >> shift); s00 += 2; } if (oddy != 0) { // do corner element if both row and column lengths are odd // s00+1, s10, s10+1 are off edge long h0 = a.get(s00); a.set(s00, h0 >> shift); } } // divide all the masks and rounding values by 2 bit1 = bit0; bit0 = bit0 >> 1; mask1 = mask0; mask0 = mask0 >> 1; prnd1 = prnd0; prnd0 = prnd0 >> 1; nrnd1 = nrnd0; nrnd0 = prnd0 - 1; } return 0; } /** * long a[]; tiledImageOperation of H-transform coefficients int * nxtop,nytop; size of coefficient block to use int ny; actual 1st * dimension of tiledImageOperation int scale; truncation scale factor that * was used */ private void hsmooth64(LongArrayPointer a, int nxtop, int nytop) { int i, j; int ny2, s10, s00; long hm, h0, hp, hmm, hpm, hmp, hpp, hx2, hy2, diff, dmax, dmin, s, smax, m1, m2; /* * Maximum change in coefficients is determined by scale factor. Since * we rounded during division (see digitize.c), the biggest permitted * change is scale/2. */ smax = this.scale >> 1; if (smax <= 0) { return; } ny2 = this.ny << 1; /* * We're indexing a as a 2-D tiledImageOperation with dimensions * (nxtop,ny) of which only (nxtop,nytop) are used. The coefficients on * the edge of the tiledImageOperation are not adjusted (which is why * the loops below start at 2 instead of 0 and end at nxtop-2 instead of * nxtop.) */ /* * Adjust x difference hx */ for (i = 2; i < nxtop - 2; i += 2) { s00 = this.ny * i; /* s00 is index of a[i,j] */ s10 = s00 + this.ny; /* s10 is index of a[i+1,j] */ for (j = 0; j < nytop; j += 2) { /* * hp is h0 (mean value) in next x zone, hm is h0 in previous x * zone */ hm = a.get(s00 - ny2); h0 = a.get(s00); hp = a.get(s00 + ny2); /* * diff = 8 * hx slope that would match h0 in neighboring zones */ diff = hp - hm; /* * monotonicity constraints on diff */ dmax = Math.max(Math.min(hp - h0, h0 - hm), 0) << 2; dmin = Math.min(Math.max(hp - h0, h0 - hm), 0) << 2; /* * if monotonicity would set slope = 0 then don't change hx. * note dmax>=0, dmin<=0. */ if (dmin < dmax) { diff = Math.max(Math.min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using shift * for divide by 8. */ s = diff - (a.get(s10) << N03); s = s >= 0 ? s >> N03 : s + N07 >> N03; s = Math.max(Math.min(s, smax), -smax); a.set(s10, a.get(s10) + s); } s00 += 2; s10 += 2; } } /* * Adjust y difference hy */ for (i = 0; i < nxtop; i += 2) { s00 = this.ny * i + 2; s10 = s00 + this.ny; for (j = 2; j < nytop - 2; j += 2) { hm = a.get(s00 - 2); h0 = a.get(s00); hp = a.get(s00 + 2); diff = hp - hm; dmax = Math.max(Math.min(hp - h0, h0 - hm), 0) << 2; dmin = Math.min(Math.max(hp - h0, h0 - hm), 0) << 2; if (dmin < dmax) { diff = Math.max(Math.min(diff, dmax), dmin); s = diff - (a.get(s00 + 1) << N03); s = s >= 0 ? s >> N03 : s + N07 >> N03; s = Math.max(Math.min(s, smax), -smax); a.set(s00 + 1, a.get(s00 + 1) + s); } s00 += 2; s10 += 2; } } /* * Adjust curvature difference hc */ for (i = 2; i < nxtop - 2; i += 2) { s00 = this.ny * i + 2; s10 = s00 + this.ny; for (j = 2; j < nytop - 2; j += 2) { /* * ------------------ y | hmp | | hpp | | ------------------ | | * | h0 | | | ------------------ -------x | hmm | | hpm | * ------------------ */ hmm = a.get(s00 - ny2 - 2); hpm = a.get(s00 + ny2 - 2); hmp = a.get(s00 - ny2 + 2); hpp = a.get(s00 + ny2 + 2); h0 = a.get(s00); /* * diff = 64 * hc value that would match h0 in neighboring zones */ diff = hpp + hmm - hmp - hpm; /* * 2 times x,y slopes in this zone */ hx2 = a.get(s10) << 1; hy2 = a.get(s00 + 1) << 1; /* * monotonicity constraints on diff */ m1 = Math.min(Math.max(hpp - h0, 0) - hx2 - hy2, Math.max(h0 - hpm, 0) + hx2 - hy2); m2 = Math.min(Math.max(h0 - hmp, 0) - hx2 + hy2, Math.max(hmm - h0, 0) + hx2 + hy2); dmax = Math.min(m1, m2) << BITS_OF_1_NYBBLE; m1 = Math.max(Math.min(hpp - h0, 0) - hx2 - hy2, Math.min(h0 - hpm, 0) + hx2 - hy2); m2 = Math.max(Math.min(h0 - hmp, 0) - hx2 + hy2, Math.min(hmm - h0, 0) + hx2 + hy2); dmin = Math.max(m1, m2) << BITS_OF_1_NYBBLE; /* * if monotonicity would set slope = 0 then don't change hc. * note dmax>=0, dmin<=0. */ if (dmin < dmax) { diff = Math.max(Math.min(diff, dmax), dmin); /* * Compute change in slope limited to range +/- smax. * Careful with rounding negative numbers when using shift * for divide by 64. */ s = diff - (a.get(s10 + 1) << N06); s = s >= 0 ? s >> N06 : s + N63 >> N06; s = Math.max(Math.min(s, smax), -smax); a.set(s10 + 1, a.get(s10 + 1) + s); } s00 += 2; s10 += 2; } } } private int inputBit(ByteBuffer infile) { if (this.bitsToGo == 0) { /* Read the next byte if no */ this.buffer2 = infile.get() & BYTE_MASK; this.bitsToGo = BITS_OF_1_BYTE; } /* * Return the next bit */ this.bitsToGo -= 1; return this.buffer2 >> this.bitsToGo & 1; } /* * Huffman decoding for fixed codes Coded values range from 0-15 Huffman * code values (hex): 3e, 00, 01, 08, 02, 09, 1a, 1b, 03, 1c, 0a, 1d, 0b, * 1e, 3f, 0c and number of bits in each code: 6, 3, 3, 4, 3, 4, 5, 5, 3, 5, * 4, 5, 4, 5, 6, 4 */ private int inputHuffman(ByteBuffer infile) { int c; /* * get first 3 bits to start */ c = inputNbits(infile, N03); if (c < N04) { /* * this is all we need return 1,2,4,8 for c=0,1,2,3 */ return 1 << c; } /* * get the next bit */ c = inputBit(infile) | c << 1; if (c < N13) { /* * OK, 4 bits is enough */ switch (c) { case N08: return N03; case N09: return N05; case N10: return N10; case N11: return N12; case N12: return N15; default: } } /* * get yet another bit */ c = inputBit(infile) | c << 1; if (c < N31) { /* * OK, 5 bits is enough */ switch (c) { case N26: return N06; case N27: return N07; case N28: return N09; case N29: return N11; case N30: return N13; default: } } /* * need the 6th bit */ c = inputBit(infile) | c << 1; if (c == N62) { return 0; } else { return N14; } } private int inputNbits(ByteBuffer infile, int n) { if (this.bitsToGo < n) { /* * need another byte's worth of bits */ this.buffer2 = this.buffer2 << BITS_OF_1_BYTE | infile.get() & BYTE_MASK; this.bitsToGo += BITS_OF_1_BYTE; } /* * now pick off the first n bits */ this.bitsToGo -= n; /* there was a slight gain in speed by replacing the following line */ /* return( (buffer2>>bits_to_go) & ((1<> this.bitsToGo & MASKS[n]; } /* INITIALIZE BIT INPUT */ private int inputNnybble(ByteBuffer infile, int n, byte[] array) { /* * copy n 4-bit nybbles from infile to the lower 4 bits of * tiledImageOperation */ int ii, kk, shift1, shift2; /* * forcing byte alignment doesn;t help, and even makes it go slightly * slower if (bits_to_go != 8) input_nbits(infile, bits_to_go); */ if (n == 1) { array[0] = (byte) inputNybble(infile); return 0; } if (this.bitsToGo == BITS_OF_1_BYTE) { /* * already have 2 full nybbles in buffer2, so backspace the infile * tiledImageOperation to reuse last char */ infile.position(infile.position() - 1); this.bitsToGo = 0; } /* bits_to_go now has a value in the range 0 - 7. After adding */ /* another byte, bits_to_go effectively will be in range 8 - 15 */ shift1 = this.bitsToGo + BITS_OF_1_NYBBLE; /* * shift1 will be in range 4 * - 11 */ shift2 = this.bitsToGo; /* shift2 will be in range 0 - 7 */ kk = 0; /* special case */ if (this.bitsToGo == 0) { for (ii = 0; ii < n / 2; ii++) { /* * refill the buffer with next byte */ this.buffer2 = this.buffer2 << BITS_OF_1_BYTE | infile.get() & BYTE_MASK; array[kk] = (byte) (this.buffer2 >> BITS_OF_1_NYBBLE & NYBBLE_MASK); array[kk + 1] = (byte) (this.buffer2 & NYBBLE_MASK); /* * no shift * required */ kk += 2; } } else { for (ii = 0; ii < n / 2; ii++) { /* * refill the buffer with next byte */ this.buffer2 = this.buffer2 << BITS_OF_1_BYTE | infile.get() & BYTE_MASK; array[kk] = (byte) (this.buffer2 >> shift1 & NYBBLE_MASK); array[kk + 1] = (byte) (this.buffer2 >> shift2 & NYBBLE_MASK); kk += 2; } } if (ii * 2 != n) { /* have to read last odd byte */ array[n - 1] = (byte) inputNybble(infile); } return this.buffer2 >> this.bitsToGo & NYBBLE_MASK; } private int inputNybble(ByteBuffer infile) { if (this.bitsToGo < BITS_OF_1_NYBBLE) { /* * need another byte's worth of bits */ this.buffer2 = this.buffer2 << BITS_OF_1_BYTE | infile.get() & BYTE_MASK; this.bitsToGo += BITS_OF_1_BYTE; } /* * now pick off the first 4 bits */ this.bitsToGo -= BITS_OF_1_NYBBLE; return this.buffer2 >> this.bitsToGo & NYBBLE_MASK; } /** * Copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding each * value to 2x2 pixels and inserting into bitplane BIT of B. A,B may NOT be * same tiledImageOperation (it wouldn't make sense to be inserting bits * into the same tiledImageOperation anyway.) */ private void qtreeBitins64(byte[] a, int lnx, int lny, LongArrayPointer b, int n, int bit) { int i, j, s00; long planeVal = 1L << bit; // expand each 2x2 block ByteBuffer k = ByteBuffer.wrap(a); /* k is index of a[i/2,j/2] */ for (i = 0; i < lnx - 1; i += 2) { s00 = n * i; /* s00 is index of b[i,j] */ // Note: this code appears to run very slightly faster on a 32-bit // linux machine using s00+n rather than the s10 intermediate // variable // s10 = s00+n; *//* s10 is index of b[i+1,j] for (j = 0; j < lny - 1; j += 2) { byte value = k.get(); if ((value & BIT_ONE) != ZERO) { b.bitOr(s00 + n + 1, planeVal); } if ((value & BIT_TWO) != ZERO) { b.bitOr(s00 + n, planeVal); } if ((value & BIT_THREE) != ZERO) { b.bitOr(s00 + 1, planeVal); } if ((value & BIT_FOUR) != ZERO) { b.bitOr(s00, planeVal); } // b.bitOr(s10+1, ((LONGLONG) ( a[k] & 1)) << bit; b.bitOr(s10 , // ((((LONGLONG)a[k])>>1) & 1) << bit; b.bitOr(s00+1, // ((((LONGLONG)a[k])>>2) & 1) << bit; b.bitOr(s00 // ,((((LONGLONG)a[k])>>3) & 1) << bit; s00 += 2; /* s10 += 2; */ } if (j < lny) { // row size is odd, do last element in row s00+1, s10+1 are off // edge byte value = k.get(); if ((value & BIT_TWO) != ZERO) { b.bitOr(s00 + n, planeVal); } if ((value & BIT_FOUR) != ZERO) { b.bitOr(s00, planeVal); } // b.bitOr(s10 , ((((LONGLONG)a[k])>>1) & 1) << bit; b.bitOr(s00 // , ((((LONGLONG)a[k])>>3) & 1) << bit; } } if (i < lnx) { // column size is odd, do last row s10, s10+1 are off edge s00 = n * i; for (j = 0; j < lny - 1; j += 2) { byte value = k.get(); if ((value & BIT_THREE) != ZERO) { b.bitOr(s00 + 1, planeVal); } if ((value & BIT_FOUR) != ZERO) { b.bitOr(s00, planeVal); } // b.bitOr(s00+1, ((((LONGLONG)a[k])>>2) & 1) << bit; // b.bitOr(s00 , ((((LONGLONG)a[k])>>3) & 1) << bit; s00 += 2; } if (j < lny) { // both row and column size are odd, do corner element s00+1, // s10, s10+1 are off edge if ((k.get() & BIT_FOUR) != ZERO) { b.bitOr(s00, planeVal); } // b.bitOr(s00 , ((((LONGLONG)a[k])>>3) & 1) << bit; } } } /** * copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding each * value to 2x2 pixels a,b may be same tiledImageOperation */ private void qtreeCopy(byte[] a, int lnx, int lny, byte[] b, int n) { int i, j, k, nx2, ny2; int s00, s10; // first copy 4-bit values to b start at end in case a,b are same // tiledImageOperation nx2 = (lnx + 1) / 2; ny2 = (lny + 1) / 2; k = ny2 * (nx2 - 1) + ny2 - 1; /* k is index of a[i,j] */ for (i = nx2 - 1; i >= 0; i--) { s00 = 2 * (n * i + ny2 - 1); /* s00 is index of b[2*i,2*j] */ for (j = ny2 - 1; j >= 0; j--) { b[s00] = a[k]; k -= 1; s00 -= 2; } } for (i = 0; i < lnx - 1; i += 2) { // now expand each 2x2 block // Note: Unlike the case in qtree_bitins, this code runs faster on a // 32-bit linux machine using the s10 intermediate variable, rather // that using s00+n. Go figure! s00 = n * i; // s00 is index of b[i,j] s10 = s00 + n; // s10 is index of b[i+1,j] for (j = 0; j < lny - 1; j += 2) { b[s10 + 1] = (b[s00] & BIT_ONE) == ZERO ? ZERO : BIT_ONE; b[s10] = (b[s00] & BIT_TWO) == ZERO ? ZERO : BIT_ONE; b[s00 + 1] = (b[s00] & BIT_THREE) == ZERO ? ZERO : BIT_ONE; b[s00] = (b[s00] & BIT_FOUR) == ZERO ? ZERO : BIT_ONE; s00 += 2; s10 += 2; } if (j < lny) { // row size is odd, do last element in row s00+1, s10+1 are off // edge not worth converting this to use 16 case statements b[s10] = (byte) (b[s00] >> 1 & 1); b[s00] = (byte) (b[s00] >> N03 & 1); } } if (i < lnx) { // column size is odd, do last row s10, s10+1 are off edge s00 = n * i; for (j = 0; j < lny - 1; j += 2) { // not worth converting this to use 16 case statements b[s00 + 1] = (byte) (b[s00] >> 2 & 1); b[s00] = (byte) (b[s00] >> N03 & 1); s00 += 2; } if (j < lny) { // both row and column size are odd, do corner element s00+1, // s10, s10+1 are off edge not worth converting this to use 16 // case statements b[s00] = (byte) (b[s00] >> N03 & 1); } } } /** * char *infile; long a[]; a is 2-D tiledImageOperation with dimensions * (n,n) int n; length of full row in a int nqx; partial length of row to * decode int nqy; partial length of column (<=n) int nbitplanes; number of * bitplanes to decode */ private int qtreeDecode64(ByteBuffer infile, LongArrayPointer a, int n, int nqx, int nqy, int nbitplanes) { int k, bit, b; int nx2, ny2, nfx, nfy, c; byte[] scratch; /* * log2n is log2 of max(nqx,nqy) rounded up to next power of 2 */ int nqmax = nqx > nqy ? nqx : nqy; int log2n = calculateLog2N(nqmax); /* * allocate scratch tiledImageOperation for working space */ int nqx2 = (nqx + 1) / 2; int nqy2 = (nqy + 1) / 2; scratch = new byte[nqx2 * nqy2]; /* * now decode each bit plane, starting at the top A is assumed to be * initialized to zero */ for (bit = nbitplanes - 1; bit >= 0; bit--) { /* * Was bitplane was quadtree-coded or written directly? */ b = inputNybble(infile); if (b == 0) { /* * bit map was written directly */ readBdirect64(infile, a, n, nqx, nqy, scratch, bit); } else if (b != NYBBLE_MASK) { throw new RuntimeException("Compression error"); } else { /* * bitmap was quadtree-coded, do log2n expansions read first * code */ scratch[0] = (byte) inputHuffman(infile); /* * now do log2n expansions, reading codes from file as necessary */ nx2 = 1; ny2 = 1; nfx = nqx; nfy = nqy; c = 1 << log2n; for (k = 1; k < log2n; k++) { /* * this somewhat cryptic code generates the sequence n[k-1] * = (n[k]+1)/2 where n[log2n]=nqx or nqy */ c = c >> 1; nx2 = nx2 << 1; ny2 = ny2 << 1; if (nfx <= c) { nx2 -= 1; } else { nfx -= c; } if (nfy <= c) { ny2 -= 1; } else { nfy -= c; } qtreeExpand(infile, scratch, nx2, ny2, scratch); } /* * now copy last set of 4-bit codes to bitplane bit of * tiledImageOperation a */ qtreeBitins64(scratch, nqx, nqy, a, n, bit); } } return 0; } /* * do one quadtree expansion step on tiledImageOperation * a[(nqx+1)/2,(nqy+1)/2] results put into b[nqx,nqy] (which may be the same * as a) */ private void qtreeExpand(ByteBuffer infile, byte[] a, int nx2, int ny2, byte[] b) { int i; /* * first copy a to b, expanding each 4-bit value */ qtreeCopy(a, nx2, ny2, b, ny2); /* * now read new 4-bit values into b for each non-zero element */ for (i = nx2 * ny2 - 1; i >= 0; i--) { if (b[i] != 0) { b[i] = (byte) inputHuffman(infile); } } } private void readBdirect64(ByteBuffer infile, LongArrayPointer a, int n, int nqx, int nqy, byte[] scratch, int bit) { /* * read bit image packed 4 pixels/nybble */ /* * int i; for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) { scratch[i] = * input_nybble(infile); } */ inputNnybble(infile, (nqx + 1) / 2 * ((nqy + 1) / 2), scratch); /* * insert in bitplane BIT of image A */ qtreeBitins64(scratch, nqx, nqy, a, n, bit); } /* * ########################################################################## * ## */ private void startInputingBits() { /* * Buffer starts out with no bits in it */ this.bitsToGo = 0; } private void undigitize64(LongArrayPointer a) { long scale64; /* * multiply by scale */ if (this.scale <= 1) { return; } scale64 = this.scale; /* * use a 64-bit int for efficiency in the big loop */ for (int index = 0; index < a.a.length; index++) { a.a[index] = a.a[index] * scale64; } } /** * long a[]; tiledImageOperation to shuffle int n; number of elements to * shuffle int n2; second dimension long tmp[]; scratch storage */ private void unshuffle64(LongArrayPointer a, int n, int n2, long[] tmp) { int i; int nhalf; LongArrayPointer p1, p2, pt; /* * copy 2nd half of tiledImageOperation to tmp */ nhalf = n + 1 >> 1; pt = new LongArrayPointer(tmp); p1 = a.copy(n2 * nhalf); /* pointer to a[i] */ for (i = nhalf; i < n; i++) { pt.set(p1.get()); p1.offset += n2; pt.offset += 1; } /* * distribute 1st half of tiledImageOperation to even elements */ p2 = a.copy(n2 * (nhalf - 1)); /* pointer to a[i] */ p1 = a.copy(n2 * (nhalf - 1) << 1); /* pointer to a[2*i] */ for (i = nhalf - 1; i >= 0; i--) { p1.set(p2.get()); p2.offset -= n2; p1.offset -= n2 + n2; } /* * now distribute 2nd half of tiledImageOperation (in tmp) to odd * elements */ pt = new LongArrayPointer(tmp); p1 = a.copy(n2); /* pointer to a[i] */ for (i = 1; i < n; i += 2) { p1.set(pt.get()); p1.offset += n2 + n2; pt.offset += 1; } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/plio/000077500000000000000000000000001310063650500306705ustar00rootroot00000000000000PLIOCompress.java000066400000000000000000000305631310063650500337420ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/pliopackage nom.tam.fits.compression.algorithm.plio; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import nom.tam.fits.compression.algorithm.api.ICompressor; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * The original decompression code was written by Doug Tody, NRAO and included * (ported to c and adapted) in cfitsio by William Pence, NASA/GSFC. That code * was then ported to Java by R. van Nieuwenhoven. Later it was massively * refactored to harmonize the different compression algorithms and reduce the * duplicate code pieces without obscuring the algorithm itself as good as * possible. * * @author Doug Tody * @author William Pence * @author Richard van Nieuwenhoven */ public abstract class PLIOCompress { public static class BytePLIOCompressor extends PLIOCompress implements ICompressor { private ByteBuffer pixelData; @Override public boolean compress(ByteBuffer buffer, ByteBuffer compressed) { this.pixelData = buffer; compress(compressed.asShortBuffer(), this.pixelData.limit()); return true; } @Override public void decompress(ByteBuffer compressed, ByteBuffer buffer) { this.pixelData = buffer; decompress(compressed.asShortBuffer(), this.pixelData.limit()); } @Override protected int nextPixel() { return this.pixelData.get(); } @Override protected void put(int index, int pixel) { this.pixelData.put(index, (byte) pixel); } } public static class ShortPLIOCompressor extends PLIOCompress implements ICompressor { private ShortBuffer pixelData; @Override public boolean compress(ShortBuffer buffer, ByteBuffer compressed) { this.pixelData = buffer; super.compress(compressed.asShortBuffer(), this.pixelData.limit()); return true; } @Override public void decompress(ByteBuffer compressed, ShortBuffer buffer) { this.pixelData = buffer; decompress(compressed.asShortBuffer(), this.pixelData.limit()); } @Override protected int nextPixel() { return this.pixelData.get(); } @Override protected void put(int index, int pixel) { this.pixelData.put(index, (short) pixel); } } /** * Attention int values are limited to 24 bits! */ public static class IntPLIOCompressor extends PLIOCompress implements ICompressor { private IntBuffer pixelData; @Override public boolean compress(IntBuffer buffer, ByteBuffer compressed) { this.pixelData = buffer; super.compress(compressed.asShortBuffer(), this.pixelData.limit()); return true; } @Override public void decompress(ByteBuffer compressed, IntBuffer buffer) { this.pixelData = buffer; decompress(compressed.asShortBuffer(), this.pixelData.limit()); } @Override protected int nextPixel() { return this.pixelData.get(); } @Override protected void put(int index, int pixel) { this.pixelData.put(index, (short) pixel); } } private static final int FIRST_VALUE_WITH_13_BIT = 4096; private static final int FIRST_VALUE_WITH_14_BIT = 8192; private static final int FIRST_VALUE_WITH_15_BIT = 16384; private static final int FIRST_VALUE_WITH_16_BIT = 32768; private static final int HEADER_SIZE_FIELD1 = 3; private static final int HEADER_SIZE_FIELD2 = 4; private static final int LAST_VALUE_FITTING_IN_12_BIT = FIRST_VALUE_WITH_13_BIT - 1; private static final int MINI_HEADER_SIZE = 3; private static final int MINI_HEADER_SIZE_FIELD = 2; /** * The exact meaning of this var is not clear at the moment of porting the * algorithm to Java. */ private static final int N20481 = 20481; private static final int OPCODE_1 = 1; private static final int OPCODE_2 = 2; private static final int OPCODE_3 = 3; private static final int OPCODE_4 = 4; private static final int OPCODE_5 = 5; private static final int OPCODE_6 = 6; private static final int OPCODE_7 = 7; private static final int OPCODE_8 = 8; private static final short[] PLIO_HEADER = { (short) 0, (short) 7, (short) -100, (short) 0, (short) 0, (short) 0, (short) 0 }; private static final int SHIFT_12_BITS = 12; private static final int SHIFT_15_BITS = 15; private static final int VALUE_OF_BIT_13_AND14_ON = 12288; /** * PL_P2L -- Convert a pixel tiledImageOperation to a line list. The length * of the list is returned as the function value. * * @param compressedData * encoded line list * @param npix * number of pixels to convert */ protected void compress(ShortBuffer compressedData, int npix) { compressedData.put(PLIO_HEADER); final int xe = npix - 1; int op = PLIO_HEADER.length; /* Computing MAX */ int pv = Math.max(0, nextPixel()); int x1 = 0; int iz = 0; int hi = 1; int nv = 0; for (int ip = 0; ip <= xe; ++ip) { if (ip < xe) { /* Computing MAX */ nv = Math.max(0, nextPixel()); if (nv == pv) { continue; } if (pv == 0) { pv = nv; x1 = ip + 1; continue; } } else { if (pv == 0) { x1 = xe + 1; } } int np = ip - x1 + 1; int nz = x1 - iz; boolean skip = false; if (pv > 0) { int dv = pv - hi; if (dv != 0) { hi = pv; if (Math.abs(dv) > LAST_VALUE_FITTING_IN_12_BIT) { compressedData.put(op, (short) ((pv & LAST_VALUE_FITTING_IN_12_BIT) + FIRST_VALUE_WITH_13_BIT)); ++op; compressedData.put(op, (short) (pv / FIRST_VALUE_WITH_13_BIT)); ++op; } else { if (dv < 0) { compressedData.put(op, (short) (-dv + VALUE_OF_BIT_13_AND14_ON)); } else { compressedData.put(op, (short) (dv + FIRST_VALUE_WITH_14_BIT)); } ++op; if (np == 1 && nz == 0) { int v = compressedData.get(op - 1); compressedData.put(op - 1, (short) (v | FIRST_VALUE_WITH_15_BIT)); skip = true; } } } } if (!skip) { if (nz > 0) { while (nz > 0) { compressedData.put(op, (short) Math.min(LAST_VALUE_FITTING_IN_12_BIT, nz)); ++op; nz += -LAST_VALUE_FITTING_IN_12_BIT; } if (np == 1 && pv > 0) { compressedData.put(op - 1, (short) (compressedData.get(op - 1) + N20481)); skip = true; } } } if (!skip) { while (np > 0) { compressedData.put(op, (short) (Math.min(LAST_VALUE_FITTING_IN_12_BIT, np) + FIRST_VALUE_WITH_15_BIT)); ++op; np += -LAST_VALUE_FITTING_IN_12_BIT; } } x1 = ip + 1; iz = x1; pv = nv; } compressedData.put(HEADER_SIZE_FIELD1, (short) (op % FIRST_VALUE_WITH_16_BIT)); compressedData.put(HEADER_SIZE_FIELD2, (short) (op / FIRST_VALUE_WITH_16_BIT)); compressedData.position(op); } /** * PL_L2PI -- Translate a PLIO line list into an integer pixel * tiledImageOperation. The number of pixels output (always npix) is * returned as the function value. * * @param compressedData * encoded line list * @param npix * number of pixels to convert * @return number of pixels converted */ protected int decompress(ShortBuffer compressedData, int npix) { int llfirt; int lllen; if (!(compressedData.get(2) > 0)) { lllen = (compressedData.get(HEADER_SIZE_FIELD2) << SHIFT_15_BITS) + compressedData.get(HEADER_SIZE_FIELD1); llfirt = compressedData.get(1); } else { lllen = compressedData.get(MINI_HEADER_SIZE_FIELD); llfirt = MINI_HEADER_SIZE; } final int xe = npix; int op = 0; int x1 = 1; int pv = 1; for (int ip = llfirt; ip <= lllen; ++ip) { final int opcode = compressedData.get(ip) / FIRST_VALUE_WITH_13_BIT; final int data = compressedData.get(ip) & LAST_VALUE_FITTING_IN_12_BIT; final int sw0001 = opcode + 1; if (sw0001 == OPCODE_1 || sw0001 == OPCODE_5 || sw0001 == OPCODE_6) { final int x2 = x1 + data - 1; final int i2 = Math.min(x2, xe); final int np = i2 - Math.max(x1, 0) + 1; if (np > 0) { final int otop = op + np - 1; if (!(opcode == OPCODE_4)) { for (int index = op; index <= otop; ++index) { put(index, 0); } if (opcode == OPCODE_5 && i2 == x2) { put(otop, pv); } } else { for (int index = op; index <= otop; ++index) { put(index, pv); } } op = otop + 1; } x1 = x2 + 1; } else if (sw0001 == OPCODE_2) { pv = (compressedData.get(ip + 1) << SHIFT_12_BITS) + data; ++ip; } else if (sw0001 == OPCODE_3) { pv += data; } else if (sw0001 == OPCODE_4) { pv -= data; } else if (sw0001 == OPCODE_7) { pv += data; if (x1 >= 0 && x1 <= xe) { put(op, pv); ++op; } ++x1; } else if (sw0001 == OPCODE_8) { pv -= data; if (x1 >= 0 && x1 <= xe) { put(op, pv); ++op; } ++x1; } if (x1 > xe) { break; } } for (int index = op; index < npix; ++index) { put(index, 0); } return npix; } protected abstract int nextPixel(); protected abstract void put(int index, int pixel); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/quant/000077500000000000000000000000001310063650500310555ustar00rootroot00000000000000Quantize.java000066400000000000000000000461311310063650500334460ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/quantpackage nom.tam.fits.compression.algorithm.quant; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.util.Arrays; public class Quantize { class DoubleArrayPointer { private final double[] array; private int startIndex; DoubleArrayPointer(double[] arrayIn) { this.array = arrayIn; } public DoubleArrayPointer copy(long l) { DoubleArrayPointer result = new DoubleArrayPointer(this.array); result.startIndex = (int) l; return result; } public double get(int ii) { return this.array[ii + this.startIndex]; } } private static final double DEFAULT_QUANT_LEVEL = 4.; private static final double MAX_INT_AS_DOUBLE = Integer.MAX_VALUE; private static final int MINIMUM_PIXEL_WIDTH = 9; /** * number of reserved values, starting with */ private static final long N_RESERVED_VALUES = 10; private static final int N4 = 4; private static final int N6 = 6; private static final double NOISE_2_MULTIPLICATOR = 1.0483579; private static final double NOISE_3_MULTIPLICATOR = 0.6052697; private static final double NOISE_5_MULTIPLICATOR = 0.1772048; private final QuantizeOption parameter; /** * maximum non-null value */ private double maxValue; /** * minimum non-null value */ private double minValue; /** * number of good, non-null pixels? */ private long ngood; /** * returned 2nd order MAD of all non-null pixels */ private double noise2; /** * returned 3rd order MAD of all non-null pixels */ private double noise3; /* returned 5th order MAD of all non-null pixels */ private double noise5; private double xmaxval; private double xminval; private double xnoise2; private double xnoise3; private double xnoise5; public Quantize(QuantizeOption quantizeOption) { this.parameter = quantizeOption; } /** * Estimate the median and background noise in the input image using 2nd, * 3rd and 5th order Median Absolute Differences. The noise in the * background of the image is calculated using the MAD algorithms developed * for deriving the signal to noise ratio in spectra (see issue #42 of the * ST-ECF newsletter, http://www.stecf.org/documents/newsletter/) 3rd order: * noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - * flux(i+2))) The returned estimates are the median of the values that are * computed for each row of the image. * * @param arrayIn * 2 dimensional tiledImageOperation of image pixels * @param nx * number of pixels in each row of the image * @param ny * number of rows in the image * @param nullcheck * check for null values, if true * @param nullvalue * value of null pixels, if nullcheck is true * @return error status */ private void calculateNoise(double[] arrayIn, int nx, int ny) { DoubleArrayPointer array = new DoubleArrayPointer(arrayIn); initializeNoise(); if (nx < MINIMUM_PIXEL_WIDTH) { // treat entire tiledImageOperation as an image with a single row nx = nx * ny; ny = 1; } if (calculateNoiseShortRow(array, nx, ny)) { return; } DoubleArrayPointer rowpix; int nrows = 0, nrows2 = 0; long ngoodpix = 0; /* allocate arrays used to compute the median and noise estimates */ double[] differences2 = new double[nx]; double[] differences3 = new double[nx]; double[] differences5 = new double[nx]; double[] diffs2 = new double[ny]; double[] diffs3 = new double[ny]; double[] diffs5 = new double[ny]; /* loop over each row of the image */ for (int jj = 0; jj < ny; jj++) { rowpix = array.copy(jj * nx); /* point to first pixel in the row */ int ii = 0; ii = findNextValidPixelWithNullCheck(nx, rowpix, ii); if (ii == nx) { continue; /* hit end of row */ } double v1 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v2 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v3 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v4 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v5 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v6 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v7 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; ii = findNextValidPixelWithNullCheck(nx, rowpix, ++ii); if (ii == nx) { continue; /* hit end of row */ } double v8 = getNextPixelAndCheckMinMax(rowpix, ii); ngoodpix++; // now populate the differences arrays for the remaining pixels in // the row */ int nvals = 0; int nvals2 = 0; for (ii++; ii < nx; ii++) { ii = findNextValidPixelWithNullCheck(nx, rowpix, ii); if (ii == nx) { continue; /* hit end of row */ } double v9 = getNextPixelAndCheckMinMax(rowpix, ii); /* construct tiledImageOperation of absolute differences */ if (!(v5 == v6 && v6 == v7)) { differences2[nvals2] = Math.abs(v5 - v7); nvals2++; } if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7)) { differences3[nvals] = Math.abs(2 * v5 - v3 - v7); differences5[nvals] = Math.abs(N6 * v5 - N4 * v3 - N4 * v7 + v1 + v9); nvals++; } else { /* ignore constant background regions */ ngoodpix++; } /* shift over 1 pixel */ v1 = v2; v2 = v3; v3 = v4; v4 = v5; v5 = v6; v6 = v7; v7 = v8; v8 = v9; } /* end of loop over pixels in the row */ // compute the median diffs Note that there are 8 more pixel values // than there are diffs values. ngoodpix += nvals; if (nvals == 0) { continue; /* cannot compute medians on this row */ } else if (nvals == 1) { if (nvals2 == 1) { diffs2[nrows2] = differences2[0]; nrows2++; } diffs3[nrows] = differences3[0]; diffs5[nrows] = differences5[0]; } else { /* quick_select returns the median MUCH faster than using qsort */ if (nvals2 > 1) { diffs2[nrows2] = quickSelect(differences2, nvals); nrows2++; } diffs3[nrows] = quickSelect(differences3, nvals); diffs5[nrows] = quickSelect(differences5, nvals); } nrows++; } /* end of loop over rows */ computeMedianOfValuesEachRow(nrows, nrows2, diffs2, diffs3, diffs5); setNoiseResult(ngoodpix); } private boolean calculateNoiseShortRow(DoubleArrayPointer array, int nx, int ny) { /* rows must have at least 9 pixels */ if (nx < MINIMUM_PIXEL_WIDTH) { int ngoodpix = 0; for (int index = 0; index < nx; index++) { if (isNull(array.get(index))) { continue; } else { if (array.get(index) < this.xminval) { this.xminval = array.get(index); } if (array.get(index) > this.xmaxval) { this.xmaxval = array.get(index); } ngoodpix++; } } setNoiseResult(ngoodpix); return true; } return false; } protected void computeMedianOfValuesEachRow(int nrows, int nrows2, double[] diffs2, double[] diffs3, double[] diffs5) { // compute median of the values for each row. if (nrows == 0) { this.xnoise3 = 0; this.xnoise5 = 0; } else if (nrows == 1) { this.xnoise3 = diffs3[0]; this.xnoise5 = diffs5[0]; } else { Arrays.sort(diffs3, 0, nrows); Arrays.sort(diffs5, 0, nrows); this.xnoise3 = (diffs3[(nrows - 1) / 2] + diffs3[nrows / 2]) / 2.; this.xnoise5 = (diffs5[(nrows - 1) / 2] + diffs5[nrows / 2]) / 2.; } if (nrows2 == 0) { this.xnoise2 = 0; } else if (nrows2 == 1) { this.xnoise2 = diffs2[0]; } else { Arrays.sort(diffs2, 0, nrows2); this.xnoise2 = (diffs2[(nrows2 - 1) / 2] + diffs2[nrows2 / 2]) / 2.; } } protected int findNextValidPixelWithNullCheck(int nx, DoubleArrayPointer rowpix, int ii) { return ii; } private double getNextPixelAndCheckMinMax(DoubleArrayPointer rowpix, int ii) { double pixelValue = rowpix.get(ii); /* store the good pixel value */ if (pixelValue < this.xminval) { this.xminval = pixelValue; } if (pixelValue > this.xmaxval) { this.xmaxval = pixelValue; } return pixelValue; } protected double getNoise2() { return this.noise2; } protected double getNoise3() { return this.noise3; } protected double getNoise5() { return this.noise5; } private void initializeNoise() { this.xnoise2 = 0; this.xnoise3 = 0; this.xnoise5 = 0; this.xminval = Double.MAX_VALUE; this.xmaxval = Double.MIN_VALUE; } protected boolean isNull(double d) { return false; } /** * arguments: long row i: tile number = row number in the binary table * double fdata[] i: tiledImageOperation of image pixels to be compressed * long nxpix i: number of pixels in each row of fdata long nypix i: number * of rows in fdata nullcheck i: check for nullvalues in fdata? double * in_null_value i: value used to represent undefined pixels in fdata float * qlevel i: quantization level int dither_method i; which dithering method * to use int idata[] o: values of fdata after applying bzero and bscale * double bscale o: scale factor double bzero o: zero offset int iminval o: * minimum quantized value that is returned int imaxval o: maximum quantized * value that is returned The function value will be one if the input fdata * were copied to idata; in this case the parameters bscale and bzero can be * used to convert back to nearly the original floating point values: fdata * ~= idata * bscale + bzero. If the function value is zero, the data were * not copied to idata. *

* In earlier implementations of the compression code, we only used the * noise3 value as the most reliable estimate of the background noise in an * image. If it is not possible to compute a noise3 value, then this serves * as a red flag to indicate that quantizing the image could cause a loss of * significant information in the image. *

*

* At some later date, we decided to take the more conservative approach of * using the minimum of all three of the noise values (while still requiring * that noise3 has a defined value) as the best estimate of the noise. Note * that if an image contains pure Gaussian distributed noise, then noise2, * noise3, and noise5 will have exactly the same value (within statistical * measurement errors). *

* * @param fdata * the data to quantinize * @param nxpix * the image width * @param nypix * the image hight * @return true if the quantification was possible */ public boolean quantize(double[] fdata, int nxpix, int nypix) { // MAD 2nd, 3rd, and 5th order noise values double stdev; double bScale; /* bscale, 1 in intdata = delta in fdata */ long nx = (long) nxpix * (long) nypix; if (nx <= 1L) { this.parameter.setBScale(1.); this.parameter.setBZero(0.); return false; } if (this.parameter.getQLevel() >= 0.) { /* estimate background noise using MAD pixel differences */ calculateNoise(fdata, nxpix, nypix); // special case of an image filled with Nulls if (this.parameter.isCheckNull() && this.ngood == 0) { /* set parameters to dummy values, which are not used */ this.minValue = 0.; this.maxValue = 1.; stdev = 1; } else { // use the minimum of noise2, noise3, and noise5 as the best // noise value stdev = this.noise3; if (this.noise2 != 0. && this.noise2 < stdev) { stdev = this.noise2; } if (this.noise5 != 0. && this.noise5 < stdev) { stdev = this.noise5; } } if (this.parameter.getQLevel() == 0.) { bScale = stdev / DEFAULT_QUANT_LEVEL; /* default quantization */ } else { bScale = stdev / this.parameter.getQLevel(); } if (bScale == 0.) { return false; /* don't quantize */ } } else { /* negative value represents the absolute quantization level */ bScale = -this.parameter.getQLevel(); /* only nned to calculate the min and max values */ calculateNoise(fdata, nxpix, nypix); } /* check that the range of quantized levels is not > range of int */ if ((this.maxValue - this.minValue) / bScale > 2. * MAX_INT_AS_DOUBLE - N_RESERVED_VALUES) { return false; /* don't quantize */ } this.parameter.setBScale(bScale); this.parameter.setMinValue(this.minValue); this.parameter.setMaxValue(this.maxValue); this.parameter.setCheckNull(this.parameter.isCheckNull() && this.ngood != nx); return true; /* yes, data have been quantized */ } private double quickSelect(double[] arr, int n) { int low, high; int median; int middle, ll, hh; low = 0; high = n - 1; median = low + high >>> 1; // was (low + high) / 2; for (;;) { if (high <= low) { return arr[median]; } if (high == low + 1) { /* Two elements only */ if (arr[low] > arr[high]) { swapElements(arr, low, high); } return arr[median]; } /* Find median of low, middle and high items; swap into position low */ middle = low + high >>> 1; // was (low + high) / 2; if (arr[middle] > arr[high]) { swapElements(arr, middle, high); } if (arr[low] > arr[high]) { swapElements(arr, low, high); } if (arr[middle] > arr[low]) { swapElements(arr, middle, low); } /* Swap low item (now in position middle) into position (low+1) */ swapElements(arr, middle, low + 1); /* Nibble from each end towards middle, swapping items when stuck */ ll = low + 1; hh = high; for (;;) { do { ll++; } while (arr[low] > arr[ll]); do { hh--; } while (arr[hh] > arr[low]); if (hh < ll) { break; } swapElements(arr, ll, hh); } /* Swap middle item (in position low) back into correct position */ swapElements(arr, low, hh); /* Re-set active partition */ if (hh <= median) { low = ll; } if (hh >= median) { high = hh - 1; } } } private void setNoiseResult(long ngoodpix) { this.minValue = this.xminval; this.maxValue = this.xmaxval; this.ngood = ngoodpix; this.noise2 = NOISE_2_MULTIPLICATOR * this.xnoise2; this.noise3 = NOISE_3_MULTIPLICATOR * this.xnoise3; this.noise5 = NOISE_5_MULTIPLICATOR * this.xnoise5; } private void swapElements(double[] array, int one, int second) { double value = array[one]; array[one] = array[second]; array[second] = value; } } QuantizeOption.java000066400000000000000000000162161310063650500346400ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/quantpackage nom.tam.fits.compression.algorithm.quant; import nom.tam.fits.compression.algorithm.api.ICompressOption; import nom.tam.fits.compression.provider.param.api.ICompressParameters; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ public class QuantizeOption implements ICompressOption { /** * and including NULL_VALUE. These values may not be used to represent the * quantized and scaled floating point pixel values If lossy Hcompression is * used, and the tiledImageOperation contains null values, then it is also * possible for the compressed values to slightly exceed the range of the * actual (lossless) values so we must reserve a little more space value * used to represent undefined pixels */ private static final int NULL_VALUE = Integer.MIN_VALUE + 1; protected ICompressParameters parameters; private double bScale = Double.NaN; private double bZero = Double.NaN; private boolean centerOnZero; private boolean checkNull; private boolean checkZero; private boolean dither; private boolean dither2; private int intMaxValue; private int intMinValue; private double maxValue; private double minValue; private double nullValue = Double.NaN; private Integer nullValueIndicator; private double qlevel = Double.NaN; private long seed = 1L; private int tileHeight; private int tileWidth; private QuantizeOption original; @Override public QuantizeOption copy() { try { return ((QuantizeOption) clone()).setOriginal(this); } catch (CloneNotSupportedException e) { throw new IllegalStateException("option could not be cloned", e); } } public Integer getBNull() { return this.nullValueIndicator; } public double getBScale() { return this.bScale; } public double getBZero() { return this.bZero; } @Override public ICompressParameters getCompressionParameters() { return this.parameters; } public T getCompressOption(Class clazz) { return unwrap(clazz); } public int getIntMaxValue() { return this.intMaxValue; } public int getIntMinValue() { return this.intMinValue; } public double getMaxValue() { return this.maxValue; } public double getMinValue() { return this.minValue; } public double getNullValue() { return this.nullValue; } public Integer getNullValueIndicator() { return this.nullValueIndicator; } public QuantizeOption getOriginal() { return this.original; } public double getQLevel() { return this.qlevel; } public long getSeed() { return this.seed; } public int getTileHeight() { return this.tileHeight; } public int getTileWidth() { return this.tileWidth; } public boolean isCenterOnZero() { return this.centerOnZero; } public boolean isCheckNull() { return this.checkNull; } public boolean isCheckZero() { return this.checkZero; } public boolean isDither() { return this.dither; } public boolean isDither2() { return this.dither2; } @Override public boolean isLossyCompression() { return true; } public ICompressOption setBNull(Integer blank) { if (blank != null) { this.checkNull = true; this.nullValueIndicator = blank; } return this; } public QuantizeOption setBScale(double value) { this.bScale = value; return this; } public QuantizeOption setBZero(double value) { this.bZero = value; return this; } public QuantizeOption setCenterOnZero(boolean value) { this.centerOnZero = value; return this; } public QuantizeOption setCheckNull(boolean value) { this.checkNull = value; if (this.nullValueIndicator == null) { this.nullValueIndicator = NULL_VALUE; } return this; } public QuantizeOption setCheckZero(boolean value) { this.checkZero = value; return this; } public QuantizeOption setDither(boolean value) { this.dither = value; return this; } public QuantizeOption setDither2(boolean value) { this.dither2 = value; return this; } public QuantizeOption setIntMaxValue(int value) { this.intMaxValue = value; return this; } public QuantizeOption setIntMinValue(int value) { this.intMinValue = value; return this; } public QuantizeOption setMaxValue(double value) { this.maxValue = value; return this; } public QuantizeOption setMinValue(double value) { this.minValue = value; return this; } public QuantizeOption setNullValue(double value) { this.nullValue = value; return this; } @Override public void setParameters(ICompressParameters parameters) { this.parameters = parameters; } public QuantizeOption setQlevel(double value) { this.qlevel = value; return this; } public QuantizeOption setSeed(long value) { this.seed = value; return this; } @Override public QuantizeOption setTileHeight(int value) { this.tileHeight = value; return this; } @Override public QuantizeOption setTileWidth(int value) { this.tileWidth = value; return this; } @Override public T unwrap(Class clazz) { if (clazz.isAssignableFrom(this.getClass())) { return clazz.cast(this); } return null; } private QuantizeOption setOriginal(QuantizeOption quantizeOption) { this.original = quantizeOption; this.parameters = this.parameters.copy(this); return this; } } QuantizeProcessor.java000066400000000000000000000375441310063650500353560ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/quantpackage nom.tam.fits.compression.algorithm.quant; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.nio.ByteBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import nom.tam.fits.compression.algorithm.api.ICompressor; public class QuantizeProcessor { public static class DoubleQuantCompressor extends QuantizeProcessor implements ICompressor { private final ICompressor postCompressor; public DoubleQuantCompressor(QuantizeOption quantizeOption, ICompressor postCompressor) { super(quantizeOption); this.postCompressor = postCompressor; } @Override public boolean compress(DoubleBuffer buffer, ByteBuffer compressed) { IntBuffer intData = IntBuffer.wrap(new int[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]); double[] doubles = new double[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]; buffer.get(doubles); if (!this.quantize(doubles, intData)) { return false; } intData.rewind(); this.postCompressor.compress(intData, compressed); return true; } @Override public void decompress(ByteBuffer compressed, DoubleBuffer buffer) { IntBuffer intData = IntBuffer.wrap(new int[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]); this.postCompressor.decompress(compressed, intData); intData.rewind(); unquantize(intData, buffer); } } /** * TODO this is done very inefficient and should be refactored! */ public static class FloatQuantCompressor extends QuantizeProcessor implements ICompressor { private final ICompressor postCompressor; public FloatQuantCompressor(QuantizeOption quantizeOption, ICompressor postCompressor) { super(quantizeOption); this.postCompressor = postCompressor; } @Override public boolean compress(FloatBuffer buffer, ByteBuffer compressed) { float[] floats = new float[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]; double[] doubles = new double[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]; buffer.get(floats); for (int index = 0; index < doubles.length; index++) { doubles[index] = floats[index]; } IntBuffer intData = IntBuffer.wrap(new int[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]); if (!this.quantize(doubles, intData)) { return false; } intData.rewind(); this.postCompressor.compress(intData, compressed); return true; } @Override public void decompress(ByteBuffer compressed, FloatBuffer buffer) { IntBuffer intData = IntBuffer.wrap(new int[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]); this.postCompressor.decompress(compressed, intData); intData.rewind(); double[] doubles = new double[this.quantizeOption.getTileHeight() * this.quantizeOption.getTileWidth()]; DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles); unquantize(intData, doubleBuffer); for (double d : doubles) { buffer.put((float) d); } } } private class BaseFilter extends PixelFilter { BaseFilter() { super(null); } @Override protected void nextPixel() { } @Override protected double toDouble(int pixel) { return (pixel + ROUNDING_HALF) * QuantizeProcessor.this.bScale + QuantizeProcessor.this.bZero; } @Override protected int toInt(double pixel) { return nint((pixel - QuantizeProcessor.this.bZero) / QuantizeProcessor.this.bScale + ROUNDING_HALF); } } private class DitherFilter extends PixelFilter { private static final int LAST_RANDOM_VALUE = 1043618065; private static final double MAX_INT_AS_DOUBLE = Integer.MAX_VALUE; /** * DO NOT CHANGE THIS; used when quantizing real numbers */ private static final int N_RANDOM = 10000; private static final int RANDOM_MULTIPLICATOR = 500; private static final double RANDOM_START_VALUE = 16807.0; private int iseed = 0; private int nextRandom = 0; private final double[] randomValues; DitherFilter(long seed) { super(null); this.randomValues = initRandoms(); initialize(seed); } public void initialize(long ditherSeed) { this.iseed = (int) ((ditherSeed - 1) % N_RANDOM); this.nextRandom = (int) (this.randomValues[this.iseed] * RANDOM_MULTIPLICATOR); } public double nextRandom() { return this.randomValues[this.nextRandom]; } private double[] initRandoms() { /* initialize an tiledImageOperation of random numbers */ int ii; double a = RANDOM_START_VALUE; double m = MAX_INT_AS_DOUBLE; double temp; double seed; /* allocate tiledImageOperation for the random number sequence */ double[] randomValue = new double[N_RANDOM]; /* * We need a portable algorithm that anyone can use to generate this * exact same sequence of random number. The C 'rand' function is * not suitable because it is not available to Fortran or Java * programmers. Instead, use a well known simple algorithm published * here: "Random number generators: good ones are hard to find", * Communications of the ACM, Volume 31 , Issue 10 (October 1988) * Pages: 1192 - 1201 */ /* initialize the random numbers */ seed = 1; for (ii = 0; ii < N_RANDOM; ii++) { temp = a * seed; seed = temp - m * (int) (temp / m); randomValue[ii] = seed / m; } /* * IMPORTANT NOTE: the 10000th seed value must have the value * 1043618065 if the algorithm has been implemented correctly */ if ((int) seed != LAST_RANDOM_VALUE) { throw new IllegalArgumentException("randomValue generated incorrect random number sequence"); } return randomValue; } @Override protected void nextPixel() { this.nextRandom++; if (this.nextRandom >= N_RANDOM) { this.iseed++; if (this.iseed >= N_RANDOM) { this.iseed = 0; } this.nextRandom = (int) (this.randomValues[this.iseed] * RANDOM_MULTIPLICATOR); } } @Override protected double toDouble(int pixel) { return (pixel - nextRandom() + ROUNDING_HALF) * QuantizeProcessor.this.bScale + QuantizeProcessor.this.bZero; } @Override protected int toInt(double pixel) { return nint((pixel - QuantizeProcessor.this.bZero) / QuantizeProcessor.this.bScale + nextRandom() - ROUNDING_HALF); } } private class NullFilter extends PixelFilter { private final double nullValue; private final boolean isNaN; private final int nullValueIndicator; NullFilter(double nullValue, int nullValueIndicator, PixelFilter next) { super(next); this.nullValue = nullValue; this.isNaN = Double.isNaN(this.nullValue); this.nullValueIndicator = nullValueIndicator; } public final boolean isNull(double pixel) { return this.isNaN ? Double.isNaN(pixel) : this.nullValue == pixel; } @Override protected double toDouble(int pixel) { if (pixel == this.nullValueIndicator) { return this.nullValue; } return super.toDouble(pixel); } @Override protected int toInt(double pixel) { if (isNull(pixel)) { return this.nullValueIndicator; } return super.toInt(pixel); } } private class PixelFilter { private final PixelFilter next; protected PixelFilter(PixelFilter next) { this.next = next; } protected void nextPixel() { this.next.nextPixel(); } protected double toDouble(int pixel) { return this.next.toDouble(pixel); } protected int toInt(double pixel) { return this.next.toInt(pixel); } } private class ZeroFilter extends PixelFilter { ZeroFilter(PixelFilter next) { super(next); } @Override protected double toDouble(int pixel) { if (pixel == ZERO_VALUE) { return 0.0; } return super.toDouble(pixel); } @Override protected int toInt(double pixel) { if (pixel == 0.0) { return ZERO_VALUE; } return super.toInt(pixel); } } private static final double MAX_INT_AS_DOUBLE = Integer.MAX_VALUE; /** * number of reserved values, starting with */ private static final long N_RESERVED_VALUES = 10; private static final double ROUNDING_HALF = 0.5; /** * value used to represent zero-valued pixels */ private static final int ZERO_VALUE = Integer.MIN_VALUE + 2; private final boolean centerOnZero; private final PixelFilter pixelFilter; private double bScale; private double bZero; private Quantize quantize; protected final QuantizeOption quantizeOption; public QuantizeProcessor(QuantizeOption quantizeOption) { this.quantizeOption = quantizeOption; this.bScale = quantizeOption.getBScale(); this.bZero = quantizeOption.getBZero(); PixelFilter filter = null; boolean localCenterOnZero = quantizeOption.isCenterOnZero(); if (quantizeOption.isDither2()) { filter = new DitherFilter(quantizeOption.getSeed()); localCenterOnZero = true; quantizeOption.setCheckZero(true); } else if (quantizeOption.isDither()) { filter = new DitherFilter(quantizeOption.getSeed()); } else { filter = new BaseFilter(); } if (quantizeOption.isCheckZero()) { filter = new ZeroFilter(filter); } if (quantizeOption.isCheckNull()) { final NullFilter nullFilter = new NullFilter(quantizeOption.getNullValue(), quantizeOption.getNullValueIndicator(), filter); filter = nullFilter; this.quantize = new Quantize(quantizeOption) { @Override protected int findNextValidPixelWithNullCheck(int nx, DoubleArrayPointer rowpix, int ii) { while (ii < nx && nullFilter.isNull(rowpix.get(ii))) { ii++; } return ii; } @Override protected boolean isNull(double d) { return nullFilter.isNull(d); } }; } else { this.quantize = new Quantize(quantizeOption); } this.pixelFilter = filter; this.centerOnZero = localCenterOnZero; } public Quantize getQuantize() { return this.quantize; } public boolean quantize(double[] doubles, IntBuffer quants) { boolean success = this.quantize.quantize(doubles, this.quantizeOption.getTileWidth(), this.quantizeOption.getTileHeight()); if (success) { calculateBZeroAndBscale(); quantize(DoubleBuffer.wrap(doubles, 0, this.quantizeOption.getTileWidth() * this.quantizeOption.getTileHeight()), quants); } return success; } public void quantize(final DoubleBuffer fdata, final IntBuffer intData) { while (fdata.hasRemaining()) { intData.put(this.pixelFilter.toInt(fdata.get())); this.pixelFilter.nextPixel(); } } public void unquantize(final IntBuffer intData, final DoubleBuffer fdata) { while (fdata.hasRemaining()) { fdata.put(this.pixelFilter.toDouble(intData.get())); this.pixelFilter.nextPixel(); } } private void calculateBZeroAndBscale() { this.bScale = this.quantizeOption.getBScale(); this.bZero = zeroCenter(); this.quantizeOption.setIntMinValue(nint((this.quantizeOption.getMinValue() - this.bZero) / this.bScale)); this.quantizeOption.setIntMaxValue(nint((this.quantizeOption.getMaxValue() - this.bZero) / this.bScale)); this.quantizeOption.setBZero(this.bZero); } private int nint(double x) { return x >= 0. ? (int) (x + ROUNDING_HALF) : (int) (x - ROUNDING_HALF); } private double zeroCenter() { final double minValue = this.quantizeOption.getMinValue(); final double maxValue = this.quantizeOption.getMaxValue(); double evaluatedBZero; if (!this.quantizeOption.isCheckNull() && !this.centerOnZero) { // don't have to check for nulls // return all positive values, if possible since some compression // algorithms either only work for positive integers, or are more // efficient. if ((maxValue - minValue) / this.bScale < MAX_INT_AS_DOUBLE - N_RESERVED_VALUES) { evaluatedBZero = minValue; // fudge the zero point so it is an integer multiple of bScale // This helps to ensure the same scaling will be performed if // the file undergoes multiple fpack/funpack cycles long iqfactor = (long) (evaluatedBZero / this.bScale + ROUNDING_HALF); evaluatedBZero = iqfactor * this.bScale; } else { /* center the quantized levels around zero */ evaluatedBZero = (minValue + maxValue) / 2.; } } else { // data contains null values or has be forced to center on zero // shift the range to be close to the value used to represent null // values evaluatedBZero = minValue - this.bScale * (Integer.MIN_VALUE + N_RESERVED_VALUES + 1); } return evaluatedBZero; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/rice/000077500000000000000000000000001310063650500306475ustar00rootroot00000000000000BitBuffer.java000066400000000000000000000126271310063650500333130ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/ricepackage nom.tam.fits.compression.algorithm.rice; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.nio.ByteBuffer; /** * A bit wise reader writer around a bytebuffer. * * @author Ritchie */ public class BitBuffer { private static final int BITS_OF_4_BYTES = 32; private static final int BYTE_MASK = 0xFF; private static final long INTEGER_MASK = 0xFFFFFFFFL; private static final int BITS_OF_1_BYTE = 8; private static final int BITS_OF_2_BYTES = 16; private static final int BITS_OF_3_BYTES = 24; private static final int BYTE_1_OF_INT = 0x000000FF; private static final int BYTE_2_OF_INT = 0x0000FF00; private static final int BYTE_3_OF_INT = 0x00FF0000; private static final int BYTE_4_OF_INT = 0xFF000000; private final ByteBuffer buffer; private long position; public BitBuffer(ByteBuffer writeBuffer) { this.buffer = writeBuffer; } public int bitbuffer() { return this.buffer.get((int) (this.position / BITS_OF_1_BYTE)); } void close() { if (this.position % BITS_OF_1_BYTE != 0) { putByte((byte) 0, (int) (BITS_OF_1_BYTE - this.position % BITS_OF_1_BYTE)); } this.buffer.position((int) (this.position / BITS_OF_1_BYTE)); } public int missingBitsInCurrentByte() { return (int) (BITS_OF_1_BYTE - this.position % BITS_OF_1_BYTE); } public void movePosition(int i) { this.position += i; } public void putByte(byte byteToAdd) { final int bytePosition = (int) (this.position / BITS_OF_1_BYTE); final int positionInByte = (int) (this.position % BITS_OF_1_BYTE); final byte old = (byte) (this.buffer.get(bytePosition) & (byte) ~(BYTE_MASK >>> positionInByte)); final int byteAsInt = byteToAdd & BYTE_MASK; this.buffer.put(bytePosition, (byte) (old | (byte) (byteAsInt >>> positionInByte))); if (positionInByte > 0) { this.buffer.put(bytePosition + 1, (byte) (byteAsInt << BITS_OF_1_BYTE - positionInByte)); } this.position += BITS_OF_1_BYTE; } public void putByte(byte byteToAdd, int bits) { final int bytePosition = (int) (this.position / BITS_OF_1_BYTE); final int positionInByte = (int) (this.position % BITS_OF_1_BYTE); final byte old = this.buffer.get(bytePosition); final int byteAsInt = BYTE_MASK & (byteToAdd & BYTE_MASK >>> BITS_OF_1_BYTE - bits) << BITS_OF_1_BYTE - bits; this.buffer.put(bytePosition, (byte) (BYTE_MASK & // (old & BYTE_MASK << BITS_OF_1_BYTE - positionInByte | byteAsInt >>> positionInByte))); if (BITS_OF_1_BYTE - positionInByte < bits) { this.buffer.put(bytePosition + 1, (byte) (BYTE_MASK & byteAsInt << BITS_OF_1_BYTE - positionInByte)); } this.position += bits; } /** * write out int value to the next 4 bytes of the buffer * * @param i * integer to write */ public void putInt(int i) { putByte((byte) ((i & BYTE_4_OF_INT) >>> BITS_OF_3_BYTES)); putByte((byte) ((i & BYTE_3_OF_INT) >>> BITS_OF_2_BYTES)); putByte((byte) ((i & BYTE_2_OF_INT) >>> BITS_OF_1_BYTE)); putByte((byte) (i & BYTE_1_OF_INT)); } public void putInt(int i, int bits) { if (bits == 0) { return; } do { if (bits >= BITS_OF_1_BYTE) { putByte((byte) ((i & BYTE_MASK << bits - BITS_OF_1_BYTE) >>> bits - BITS_OF_1_BYTE & BYTE_MASK)); bits -= BITS_OF_1_BYTE; } else { putByte((byte) (i & BYTE_MASK >> -(bits - BITS_OF_1_BYTE)), bits); bits = 0; } } while (bits > 0); } public void putLong(long l, int bits) { if (bits == 0) { return; } do { if (bits >= BITS_OF_4_BYTES) { putInt((int) ((l & INTEGER_MASK << bits - BITS_OF_4_BYTES) >>> bits - BITS_OF_4_BYTES)); bits -= BITS_OF_4_BYTES; } else { putInt((int) (l & INTEGER_MASK >> -(bits - BITS_OF_4_BYTES)), bits); bits = 0; } } while (bits > 0); } } RiceCompressOption.java000066400000000000000000000075011310063650500352250ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/ricepackage nom.tam.fits.compression.algorithm.rice; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.compression.algorithm.api.ICompressOption; import nom.tam.fits.compression.provider.param.api.ICompressParameters; import nom.tam.util.type.PrimitiveTypes; public class RiceCompressOption implements ICompressOption { public static final int DEFAULT_RICE_BLOCKSIZE = 32; public static final int DEFAULT_RICE_BYTEPIX = PrimitiveTypes.INT.size(); /** * this is a circular dependency that still has to be cut. */ private ICompressParameters parameters; private int blockSize = DEFAULT_RICE_BLOCKSIZE; private Integer bytePix = null; private RiceCompressOption original; @Override public RiceCompressOption copy() { try { return ((RiceCompressOption) clone()).setOriginal(this); } catch (CloneNotSupportedException e) { throw new IllegalStateException("option could not be cloned", e); } } public int getBlockSize() { return this.blockSize; } public int getBytePix() { return this.bytePix; } @Override public ICompressParameters getCompressionParameters() { return this.parameters; } @Override public boolean isLossyCompression() { return false; } public RiceCompressOption setBlockSize(int value) { this.blockSize = value; return this; } public RiceCompressOption setBytePix(int value) { this.bytePix = value; return this; } @Override public void setParameters(ICompressParameters parameters) { this.parameters = parameters; } @Override public RiceCompressOption setTileHeight(int value) { return this; } @Override public RiceCompressOption setTileWidth(int value) { return this; } @Override public T unwrap(Class clazz) { if (clazz.isAssignableFrom(this.getClass())) { return clazz.cast(this); } return null; } private RiceCompressOption setOriginal(RiceCompressOption riceCompressOption) { this.original = riceCompressOption; this.parameters = this.parameters.copy(this); return this; } protected RiceCompressOption setDefaultBytePix(int defaultBytePix) { if (this.original != null) { this.original.setDefaultBytePix(defaultBytePix); this.bytePix = this.original.getBytePix(); } else if (this.bytePix == null) { this.bytePix = defaultBytePix; } return this; } } RiceCompressor.java000066400000000000000000000462641310063650500344060ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/ricepackage nom.tam.fits.compression.algorithm.rice; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.logging.Logger; import nom.tam.fits.compression.algorithm.api.ICompressor; import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.DoubleQuantCompressor; import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.FloatQuantCompressor; import nom.tam.util.FitsIO; import nom.tam.util.type.PrimitiveTypes; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * The original compression was designed by Rice, Yeh, and Miller the code was * written by Richard White at STSc at the STScI and included (ported to c and * adapted) in cfitsio by William Pence, NASA/GSFC. That code was then ported to * java by R. van Nieuwenhoven. Later it was massively refactored to harmonize * the different compression algorithms and reduce the duplicate code pieces * without obscuring the algorithm itself as far as possible. * * @author Richard White * @author William Pence * @author Richard van Nieuwenhoven */ public abstract class RiceCompressor implements ICompressor { public static class ByteRiceCompressor extends RiceCompressor { private ByteBuffer pixelBuffer; public ByteRiceCompressor(RiceCompressOption option) { super(option.setDefaultBytePix(PrimitiveTypes.BYTE.size())); } @Override public boolean compress(ByteBuffer buffer, ByteBuffer writeBuffer) { this.pixelBuffer = buffer; super.compress(buffer.limit(), this.pixelBuffer.get(this.pixelBuffer.position()), new BitBuffer(writeBuffer)); return true; } @Override public void decompress(ByteBuffer readBuffer, ByteBuffer buffer) { this.pixelBuffer = buffer; super.decompressBuffer(readBuffer, buffer.limit()); } @Override protected int nextPixel() { return this.pixelBuffer.get(); } @Override protected void nextPixel(int pixel) { this.pixelBuffer.put((byte) pixel); } } public static class DoubleRiceCompressor extends DoubleQuantCompressor { public DoubleRiceCompressor(RiceQuantizeCompressOption options) { super(options, new IntRiceCompressor(options.getRiceCompressOption())); } } public static class FloatRiceCompressor extends FloatQuantCompressor { public FloatRiceCompressor(RiceQuantizeCompressOption options) { super(options, new IntRiceCompressor(options.getRiceCompressOption())); } } public static class IntRiceCompressor extends RiceCompressor { private IntBuffer pixelBuffer; public IntRiceCompressor(RiceCompressOption option) { super(option.setDefaultBytePix(PrimitiveTypes.INT.size())); } @Override public boolean compress(IntBuffer buffer, ByteBuffer writeBuffer) { this.pixelBuffer = buffer; super.compress(buffer.limit(), this.pixelBuffer.get(this.pixelBuffer.position()), new BitBuffer(writeBuffer)); return true; } @Override public void decompress(ByteBuffer readBuffer, IntBuffer buffer) { this.pixelBuffer = buffer; super.decompressBuffer(readBuffer, buffer.limit()); } @Override protected int nextPixel() { return this.pixelBuffer.get(); } @Override protected void nextPixel(int pixel) { this.pixelBuffer.put(pixel); } } public static class ShortRiceCompressor extends RiceCompressor { private ShortBuffer pixelBuffer; public ShortRiceCompressor(RiceCompressOption option) { super(option.setDefaultBytePix(PrimitiveTypes.SHORT.size())); } @Override public boolean compress(ShortBuffer buffer, ByteBuffer writeBuffer) { this.pixelBuffer = buffer; super.compress(buffer.limit(), this.pixelBuffer.get(this.pixelBuffer.position()), new BitBuffer(writeBuffer)); return true; } @Override public void decompress(ByteBuffer readBuffer, ShortBuffer buffer) { this.pixelBuffer = buffer; super.decompressBuffer(readBuffer, buffer.limit()); } @Override protected int nextPixel() { return this.pixelBuffer.get(); } @Override protected void nextPixel(int pixel) { this.pixelBuffer.put((short) pixel); } } /** * mask to convert a "unsigned" byte to a long. */ private static final long UNSIGNED_BYTE_MASK = 0xFFL; /** * mask to convert a "unsigned" short to a long. */ private static final long UNSIGNED_SHORT_MASK = 0xFFFFL; /** * mask to convert a "unsigned" int to a long. */ private static final long UNSIGNED_INTEGER_MASK = 0xFFFFFFFFL; /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(RiceCompressor.class.getName()); private static final int BITS_OF_1_BYTE = 8; private static final int BITS_PER_BYTE = 8; private static final int BYTE_MASK = 0xff; private static final int FS_BITS_FOR_BYTE = 3; private static final int FS_BITS_FOR_INT = 5; private static final int FS_BITS_FOR_SHORT = 4; private static final int FS_MAX_FOR_BYTE = 6; private static final int FS_MAX_FOR_INT = 25; private static final int FS_MAX_FOR_SHORT = 14; /* * nonzero_count is lookup table giving number of bits in 8-bit values not * including leading zeros used in fits_rdecomp, fits_rdecomp_short and * fits_rdecomp_byte. * @formatter:off */ private static final int[] NONZERO_COUNT = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; // @formatter:on private final int bBits; private final int bitsPerPixel; private final int blockSize; private final int fsBits; private final int fsMax; private RiceCompressor(RiceCompressOption option) { this.blockSize = option.getBlockSize(); if (option.getBytePix() == PrimitiveTypes.BYTE.size()) { this.fsBits = FS_BITS_FOR_BYTE; this.fsMax = FS_MAX_FOR_BYTE; this.bitsPerPixel = FitsIO.BITS_OF_1_BYTE; } else if (option.getBytePix() == PrimitiveTypes.SHORT.size()) { this.fsBits = FS_BITS_FOR_SHORT; this.fsMax = FS_MAX_FOR_SHORT; this.bitsPerPixel = FitsIO.BITS_OF_2_BYTES; } else if (option.getBytePix() == PrimitiveTypes.INT.size()) { this.fsBits = FS_BITS_FOR_INT; this.fsMax = FS_MAX_FOR_INT; this.bitsPerPixel = FitsIO.BITS_OF_4_BYTES; } else { throw new UnsupportedOperationException("Rice only supports 1/2/4 type per pixel"); } /* * From bsize derive: FSBITS = # bits required to store FS FSMAX = * maximum value for FS BBITS = bits/pixel for direct coding */ this.bBits = 1 << this.fsBits; } /** *

* undo mapping and differencing Note that some of these operations will * overflow the unsigned int arithmetic -- that's OK, it all works out to * give the right answers in the output file. *

*

* In java this is more complicated because of the missing unsigned * integers. trying to simulate the behavior *

* * @param lastpix * the current last pix value * @param diff * the difference to "add" * @return return the new lastpiy value */ private long undoMappingAndDifferencing(long lastpix, long diff) { diff &= UNSIGNED_INTEGER_MASK; if ((diff & 1) == 0) { diff = diff >>> 1; } else { diff = diff >>> 1 ^ UNSIGNED_INTEGER_MASK; } lastpix = diff + lastpix & UNSIGNED_INTEGER_MASK; nextPixel((int) lastpix); return lastpix; } /** * compress the integer tiledImageOperation on a rise compressed byte * buffer. * * @param dataLength * length of the data to compress * @param firstPixel * the value of the first pixel * @param buffer * the buffer to write to */ protected void compress(final int dataLength, int firstPixel, BitBuffer buffer) { /* the first difference will always be zero */ int lastpix = firstPixel; /* write out first int value to the first 4 bytes of the buffer */ buffer.putInt(firstPixel, this.bitsPerPixel); int thisblock = this.blockSize; for (int i = 0; i < dataLength; i += this.blockSize) { /* last block may be shorter */ if (dataLength - i < this.blockSize) { thisblock = dataLength - i; } /* * Compute differences of adjacent pixels and map them to unsigned * values. Note that this may overflow the integer variables -- * that's OK, because we can recover when decompressing. If we were * compressing shorts or bytes, would want to do this arithmetic * with short/byte working variables (though diff will still be * passed as an int.) compute sum of mapped pixel values at same * time use double precision for sum to allow 32-bit integer inputs */ long[] diff = new long[this.blockSize]; double pixelsum = 0.0; int nextpix; /* * tiledImageOperation for differences mapped to non-negative values */ for (int j = 0; j < thisblock; j++) { nextpix = nextPixel(); long pdiff = (nextpix - lastpix); diff[j] = (pdiff < 0 ? (pdiff << 1) ^ UNSIGNED_INTEGER_MASK : pdiff << 1) & UNSIGNED_INTEGER_MASK; pixelsum += diff[j]; lastpix = nextpix; } /* * compute number of bits to split from sum */ double dpsum = (pixelsum - thisblock / 2d - 1d) / thisblock; if (dpsum < 0) { dpsum = 0.0; } long psum = (long) dpsum >> 1; int fs; for (fs = 0; psum > 0; fs++) { // NOSONAR psum >>= 1; } /* * write the codes fsbits ID bits used to indicate split level */ if (fs >= this.fsMax) { /* * Special high entropy case when FS >= fsmax Just write pixel * difference values directly, no Rice coding at all. */ buffer.putInt(this.fsMax + 1, this.fsBits); for (int j = 0; j < thisblock; j++) { buffer.putLong(diff[j], this.bBits); } } else if (fs == 0 && pixelsum == 0) { // NOSONAR /* * special low entropy case when FS = 0 and pixelsum=0 (all * pixels in block are zero.) Output a 0 and return */ buffer.putInt(0, this.fsBits); } else { /* normal case: not either very high or very low entropy */ buffer.putInt(fs + 1, this.fsBits); int fsmask = (1 << fs) - 1; /* * local copies of bit buffer to improve optimization */ int bitsToGo = buffer.missingBitsInCurrentByte(); int bitBuffer = buffer.bitbuffer() >> bitsToGo; buffer.movePosition(bitsToGo - BITS_OF_1_BYTE); for (int j = 0; j < thisblock; j++) { int v = (int) diff[j]; int top = v >> fs; /* * top is coded by top zeros + 1 */ if (bitsToGo >= top + 1) { bitBuffer <<= top + 1; bitBuffer |= 1; bitsToGo -= top + 1; } else { bitBuffer <<= bitsToGo; buffer.putByte((byte) (bitBuffer & BYTE_MASK)); for (top -= bitsToGo; top >= BITS_OF_1_BYTE; top -= BITS_OF_1_BYTE) { buffer.putByte((byte) 0); } bitBuffer = 1; bitsToGo = BITS_OF_1_BYTE - 1 - top; } /* * bottom FS bits are written without coding code is * output_nbits, moved into this routine to reduce overheads * This code potentially breaks if FS>24, so I am limiting * FS to 24 by choice of FSMAX above. */ if (fs > 0) { bitBuffer <<= fs; bitBuffer |= v & fsmask; bitsToGo -= fs; while (bitsToGo <= 0) { buffer.putByte((byte) (bitBuffer >> -bitsToGo & BYTE_MASK)); bitsToGo += BITS_OF_1_BYTE; } } } buffer.putByte((byte) (bitBuffer & BYTE_MASK), BITS_OF_1_BYTE - bitsToGo); } } buffer.close(); } /** * decompress the readbuffer and fill the pixelarray. * * @param readBuffer * input buffer * @param nx * the number of pixel to uncompress */ protected void decompressBuffer(final ByteBuffer readBuffer, final int nx) { /* first x bytes of input buffer contain the value of the first */ /* x byte integer value, without any encoding */ long lastpix = 0L; if (this.bitsPerPixel == PrimitiveTypes.BYTE.bitPix()) { lastpix = readBuffer.get() & UNSIGNED_BYTE_MASK; } else if (this.bitsPerPixel == PrimitiveTypes.SHORT.bitPix()) { lastpix = readBuffer.getShort() & UNSIGNED_SHORT_MASK; } else if (this.bitsPerPixel == PrimitiveTypes.INT.bitPix()) { lastpix = readBuffer.getInt() & UNSIGNED_INTEGER_MASK; } long b = readBuffer.get() & BYTE_MASK; /* bit buffer */ int nbits = BITS_PER_BYTE; /* number of bits remaining in b */ for (int i = 0; i < nx;) { /* get the FS value from first fsbits */ nbits -= this.fsBits; while (nbits < 0) { b = b << BITS_PER_BYTE | readBuffer.get() & BYTE_MASK; nbits += BITS_PER_BYTE; } long fs = (b >>> nbits) - 1L; b &= (1 << nbits) - 1; /* loop over the next block */ int imax = i + this.blockSize; if (imax > nx) { imax = nx; } if (fs < 0) { /* low-entropy case, all zero differences */ for (; i < imax; i++) { nextPixel((int) lastpix); } } else if (fs == this.fsMax) { /* high-entropy case, directly coded pixel values */ for (; i < imax; i++) { int k = this.bBits - nbits; long diff = b << k; for (k -= BITS_PER_BYTE; k >= 0; k -= BITS_PER_BYTE) { b = readBuffer.get() & BYTE_MASK; diff |= b << k; } if (nbits > 0) { b = readBuffer.get() & BYTE_MASK; diff |= b >>> -k; b &= (1 << nbits) - 1L; } else { b = 0; } lastpix = undoMappingAndDifferencing(lastpix, diff); } } else { /* normal case, Rice coding */ for (; i < imax; i++) { /* count number of leading zeros */ while (b == 0) { nbits += BITS_PER_BYTE; b = readBuffer.get() & BYTE_MASK; } long nzero = nbits - NONZERO_COUNT[(int) (b & BYTE_MASK)]; nbits -= nzero + 1; /* flip the leading one-bit */ b ^= 1 << nbits; /* get the FS trailing bits */ nbits -= fs; while (nbits < 0) { b = b << BITS_PER_BYTE | readBuffer.get() & BYTE_MASK; nbits += BITS_PER_BYTE; } long diff = nzero << fs | b >> nbits; b &= (1 << nbits) - 1L; lastpix = undoMappingAndDifferencing(lastpix, diff); } } } if (readBuffer.limit() > readBuffer.position()) { LOG.warning("decompressing left over some extra bytes got: " + readBuffer.limit() + " but needed only " + readBuffer.position()); } } protected abstract int nextPixel(); protected abstract void nextPixel(int pixel); } RiceQuantizeCompressOption.java000066400000000000000000000053561310063650500367540ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/ricepackage nom.tam.fits.compression.algorithm.rice; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.compression.algorithm.quant.QuantizeOption; import nom.tam.fits.compression.provider.param.rice.RiceQuantizeCompressParameters; public class RiceQuantizeCompressOption extends QuantizeOption { private RiceCompressOption riceCompressOption = new RiceCompressOption(); public RiceQuantizeCompressOption() { super(); // circulat dependency, musst be cut. this.parameters = new RiceQuantizeCompressParameters(this); } @Override public RiceQuantizeCompressOption copy() { RiceQuantizeCompressOption copy = (RiceQuantizeCompressOption) super.copy(); copy.riceCompressOption = this.riceCompressOption.copy(); return copy; } public RiceCompressOption getRiceCompressOption() { return this.riceCompressOption; } @Override public RiceQuantizeCompressOption setTileHeight(int value) { super.setTileHeight(value); this.riceCompressOption.setTileHeight(value); return this; } @Override public RiceQuantizeCompressOption setTileWidth(int value) { super.setTileWidth(value); this.riceCompressOption.setTileWidth(value); return this; } @Override public T unwrap(Class clazz) { T result = super.unwrap(clazz); if (result == null) { return this.riceCompressOption.unwrap(clazz); } return result; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/uncompressed/000077500000000000000000000000001310063650500324345ustar00rootroot00000000000000NoCompressCompressor.java000066400000000000000000000122551310063650500373720ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/algorithm/uncompressedpackage nom.tam.fits.compression.algorithm.uncompressed; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import nom.tam.fits.compression.algorithm.api.ICompressor; import nom.tam.util.type.PrimitiveTypes; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * This compression algorithm will just copy the input to the output and do * nothing at all. * * @param * the buffer type of the pixel data */ public abstract class NoCompressCompressor implements ICompressor { public static class ByteNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(ByteBuffer pixelData, ByteBuffer compressed) { compressed.put(pixelData); return true; } @Override public void decompress(ByteBuffer compressed, ByteBuffer pixelData) { pixelData.put(compressed); } } public static class DoubleNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(DoubleBuffer pixelData, ByteBuffer compressed) { int size = pixelData.remaining(); compressed.asDoubleBuffer().put(pixelData); compressed.position(compressed.position() + size * PrimitiveTypes.DOUBLE.size()); return true; } @Override public void decompress(ByteBuffer compressed, DoubleBuffer pixelData) { pixelData.put(compressed.asDoubleBuffer()); } } public static class FloatNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(FloatBuffer pixelData, ByteBuffer compressed) { int size = pixelData.remaining(); compressed.asFloatBuffer().put(pixelData); compressed.position(compressed.position() + size * PrimitiveTypes.FLOAT.size()); return true; } @Override public void decompress(ByteBuffer compressed, FloatBuffer pixelData) { pixelData.put(compressed.asFloatBuffer()); } } public static class IntNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(IntBuffer pixelData, ByteBuffer compressed) { int size = pixelData.remaining(); compressed.asIntBuffer().put(pixelData); compressed.position(compressed.position() + size * PrimitiveTypes.INT.size()); return true; } @Override public void decompress(ByteBuffer compressed, IntBuffer pixelData) { pixelData.put(compressed.asIntBuffer()); } } public static class LongNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(LongBuffer pixelData, ByteBuffer compressed) { int size = pixelData.remaining(); compressed.asLongBuffer().put(pixelData); compressed.position(compressed.position() + size * PrimitiveTypes.LONG.size()); return true; } @Override public void decompress(ByteBuffer compressed, LongBuffer pixelData) { pixelData.put(compressed.asLongBuffer()); } } public static class ShortNoCompressCompressor extends NoCompressCompressor { @Override public boolean compress(ShortBuffer pixelData, ByteBuffer compressed) { int size = pixelData.remaining(); compressed.asShortBuffer().put(pixelData); compressed.position(compressed.position() + size * PrimitiveTypes.SHORT.size()); return true; } @Override public void decompress(ByteBuffer compressed, ShortBuffer pixelData) { pixelData.put(compressed.asShortBuffer()); } } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/000077500000000000000000000000001310063650500275715ustar00rootroot00000000000000CompressorControlNameComputer.java000066400000000000000000000114451310063650500363770ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/providerpackage nom.tam.fits.compression.provider; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.header.Compression; /** *

* Computes the name of the tile compressor class name given the algorithm used * to quantize and compress the tile and the type of data the tile contains. *

* The name of the class is built of four parts: *
    *
  • the capitalized simple name of the base type of the elements in the tile * (like Int, Long etc.);
  • *
  • if a known quantize algorithm is used, the word "Quant", the word * "Unknown" if the quantize algorithm is not recognized, nothing (i.e. the * empty string) if it is null;
  • *
  • the short name of the compression algorithm to use (Rice, PLIO, Gzip * etc.) or the word "Unknown" if the algorithm is not supported;
  • *
  • the suffix "Compressor"
  • *
*

* Following exception to above rules exist: *

*
    *
  • If the primitive type is double or float, the quantize algorithm is * ignored (as if it were specified as null)
  • *
* See the associated unit tests for concrete examples. */ public class CompressorControlNameComputer { private static final String COMPRESSOR_CLASS_SUFFIX = "Compressor"; private static String standardizeBaseType(String simpleName) { return Character.toUpperCase(simpleName.charAt(0)) + simpleName.substring(1).toLowerCase(); } private static String standardizeCompressionAlgorithm(String compressionAlgorithm) { if (Compression.ZCMPTYPE_RICE_1.equalsIgnoreCase(compressionAlgorithm) || // Compression.ZCMPTYPE_RICE_ONE.equalsIgnoreCase(compressionAlgorithm)) { return "Rice"; } else if (Compression.ZCMPTYPE_PLIO_1.equalsIgnoreCase(compressionAlgorithm)) { return "PLIO"; } else if (Compression.ZCMPTYPE_HCOMPRESS_1.equalsIgnoreCase(compressionAlgorithm)) { return "H"; } else if (Compression.ZCMPTYPE_GZIP_2.equalsIgnoreCase(compressionAlgorithm)) { return "GZip2"; } else if (Compression.ZCMPTYPE_GZIP_1.equalsIgnoreCase(compressionAlgorithm)) { return "GZip"; } else if (Compression.ZCMPTYPE_NOCOMPRESS.equalsIgnoreCase(compressionAlgorithm)) { return "NoCompress"; } return "Unknown"; } private static String standardizeQuantAlgorithm(String quantAlgorithm) { if (quantAlgorithm != null) { if (Compression.ZQUANTIZ_NO_DITHER.equalsIgnoreCase(quantAlgorithm) || // Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_1.equalsIgnoreCase(quantAlgorithm) || // Compression.ZQUANTIZ_SUBTRACTIVE_DITHER_2.equalsIgnoreCase(quantAlgorithm)) { return "Quant"; } else { return "Unknown"; } } return ""; } public CompressorControlNameComputer() { super(); } public String createCompressorClassName(String quantAlgorithm, String compressionAlgorithm, Class baseType) { StringBuilder className = new StringBuilder(); className.append(standardizeBaseType(baseType.getSimpleName())); if (className.indexOf(Float.class.getSimpleName()) == 0 || className.indexOf(Double.class.getSimpleName()) == 0) { quantAlgorithm = null; // default so not in the className } className.append(standardizeQuantAlgorithm(quantAlgorithm)); className.append(standardizeCompressionAlgorithm(compressionAlgorithm)); className.append(COMPRESSOR_CLASS_SUFFIX); return className.toString(); } } CompressorProvider.java000066400000000000000000000324151310063650500342310ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/providerpackage nom.tam.fits.compression.provider; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; import nom.tam.fits.BinaryTable; import nom.tam.fits.BinaryTableHDU; import nom.tam.fits.FitsException; import nom.tam.fits.compression.algorithm.api.ICompressOption; import nom.tam.fits.compression.algorithm.api.ICompressor; import nom.tam.fits.compression.algorithm.api.ICompressorControl; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.ByteGZipCompressor; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.DoubleGZipCompressor; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.FloatGZipCompressor; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.IntGZipCompressor; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.LongGZipCompressor; import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.ShortGZipCompressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.ByteGZip2Compressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.DoubleGZip2Compressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.FloatGZip2Compressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.IntGZip2Compressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.LongGZip2Compressor; import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.ShortGZip2Compressor; import nom.tam.fits.compression.algorithm.hcompress.HCompressor.ByteHCompressor; import nom.tam.fits.compression.algorithm.hcompress.HCompressor.DoubleHCompressor; import nom.tam.fits.compression.algorithm.hcompress.HCompressor.FloatHCompressor; import nom.tam.fits.compression.algorithm.hcompress.HCompressor.IntHCompressor; import nom.tam.fits.compression.algorithm.hcompress.HCompressor.ShortHCompressor; import nom.tam.fits.compression.algorithm.plio.PLIOCompress.BytePLIOCompressor; import nom.tam.fits.compression.algorithm.plio.PLIOCompress.IntPLIOCompressor; import nom.tam.fits.compression.algorithm.plio.PLIOCompress.ShortPLIOCompressor; import nom.tam.fits.compression.algorithm.rice.RiceCompressor.ByteRiceCompressor; import nom.tam.fits.compression.algorithm.rice.RiceCompressor.DoubleRiceCompressor; import nom.tam.fits.compression.algorithm.rice.RiceCompressor.FloatRiceCompressor; import nom.tam.fits.compression.algorithm.rice.RiceCompressor.IntRiceCompressor; import nom.tam.fits.compression.algorithm.rice.RiceCompressor.ShortRiceCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.ByteNoCompressCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.DoubleNoCompressCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.FloatNoCompressCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.IntNoCompressCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.LongNoCompressCompressor; import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.ShortNoCompressCompressor; import nom.tam.fits.compression.provider.api.ICompressorProvider; import nom.tam.fits.compression.provider.param.api.ICompressParameters; import nom.tam.fits.compression.provider.param.api.IHeaderAccess; import nom.tam.fits.compression.provider.param.hcompress.HCompressParameters; import nom.tam.fits.compression.provider.param.hcompress.HCompressQuantizeParameters; import nom.tam.fits.compression.provider.param.rice.RiceCompressParameters; import nom.tam.fits.compression.provider.param.rice.RiceQuantizeCompressParameters; /** * Standard implementation of the {@code ICompressorProvider} interface. */ public class CompressorProvider implements ICompressorProvider { /** * private implementation of the tile compression provider, all is based on * the option based constructor of the compressors. */ protected static class TileCompressorControl implements ICompressorControl { private final Constructor> constructor; private final Class optionClass; private final Constructor parametersConstructor; protected TileCompressorControl(Class compressorClass) { this(compressorClass, null); } @SuppressWarnings("unchecked") protected TileCompressorControl(Class compressorClass, Class parametersClass) { this.constructor = (Constructor>) compressorClass.getConstructors()[0]; this.optionClass = (Class) (this.constructor.getParameterTypes().length == 0 ? null : this.constructor.getParameterTypes()[0]); if (parametersClass != null) { this.parametersConstructor = (Constructor) parametersClass.getConstructors()[0]; } else { this.parametersConstructor = null; } } @Override public boolean compress(Buffer in, ByteBuffer out, ICompressOption option) { try { return newCompressor(option).compress(in, out); } catch (Exception e) { LOG.log(Level.FINE, "could not compress using " + this.constructor + " must fallback to other compression method", e); return false; } } @Override public void decompress(ByteBuffer in, Buffer out, ICompressOption option) { try { newCompressor(option).decompress(in, out); } catch (Exception e) { throw new IllegalStateException("could not decompress " + this.constructor, e); } } @Override public ICompressOption option() { if (this.optionClass != null) { try { ICompressOption option = this.optionClass.newInstance(); if (this.parametersConstructor != null) { option.setParameters(this.parametersConstructor.newInstance(option)); } else { option.setParameters(NULL_PARAMETERS); } return option; } catch (Exception e) { throw new IllegalStateException("could not instantiate option class for " + this.constructor, e); } } return NULL_OPTION; } private ICompressor newCompressor(ICompressOption option) throws InstantiationException, IllegalAccessException, InvocationTargetException { return this.constructor.getParameterTypes().length == 0 ? this.constructor.newInstance() : this.constructor.newInstance(option); } } private static final ICompressOption NULL_OPTION = new ICompressOption() { @Override public ICompressOption copy() { return this; } @Override public ICompressParameters getCompressionParameters() { return NULL_PARAMETERS; } @Override public boolean isLossyCompression() { return false; } @Override public void setParameters(ICompressParameters parameters) { } @Override public ICompressOption setTileHeight(int value) { return this; } @Override public ICompressOption setTileWidth(int value) { return this; } @Override public T unwrap(Class clazz) { return clazz.isAssignableFrom(this.getClass()) ? clazz.cast(this) : null; } }; private static final ICompressParameters NULL_PARAMETERS = new ICompressParameters() { @Override public void addColumnsToTable(BinaryTableHDU hdu) { } @Override public ICompressParameters copy(ICompressOption clone) { return this; } @Override public void getValuesFromColumn(int index) { } @Override public void getValuesFromHeader(IHeaderAccess header) { } @Override public void initializeColumns(IHeaderAccess header, BinaryTable binaryTable, int size) throws FitsException { } @Override public void initializeColumns(int length) { } @Override public void setValueFromColumn(int index) { } @Override public void setValuesInHeader(IHeaderAccess header) { } }; // @formatter:off private static final Class[][] AVAILABLE_COMPRESSORS = { {ByteRiceCompressor.class, RiceCompressParameters.class}, {ShortRiceCompressor.class, RiceCompressParameters.class}, {IntRiceCompressor.class, RiceCompressParameters.class}, {FloatRiceCompressor.class, RiceQuantizeCompressParameters.class}, {DoubleRiceCompressor.class, RiceQuantizeCompressParameters.class}, {BytePLIOCompressor.class}, {ShortPLIOCompressor.class}, {IntPLIOCompressor.class}, {ByteHCompressor.class, HCompressParameters.class}, {ShortHCompressor.class, HCompressParameters.class}, {IntHCompressor.class, HCompressParameters.class}, {FloatHCompressor.class, HCompressQuantizeParameters.class}, {DoubleHCompressor.class, HCompressQuantizeParameters.class}, {DoubleHCompressor.class}, {ByteGZip2Compressor.class}, {ShortGZip2Compressor.class}, {IntGZip2Compressor.class}, {FloatGZip2Compressor.class}, {DoubleGZip2Compressor.class}, {LongGZip2Compressor.class}, {ByteGZipCompressor.class}, {ShortGZipCompressor.class}, {IntGZipCompressor.class}, {LongGZipCompressor.class}, {FloatGZipCompressor.class}, {DoubleGZipCompressor.class}, {ByteNoCompressCompressor.class}, {ShortNoCompressCompressor.class}, {IntNoCompressCompressor.class}, {LongNoCompressCompressor.class}, {FloatNoCompressCompressor.class}, {DoubleNoCompressCompressor.class}, }; // @formatter:on private static final CompressorControlNameComputer NAME_COMPUTER = new CompressorControlNameComputer(); /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(CompressorProvider.class.getName()); public static ICompressorControl findCompressorControl(String quantAlgorithm, String compressionAlgorithm, Class baseType) { ICompressorProvider defaultProvider = null; for (ICompressorProvider iTileCompressorProvider : ServiceLoader.load(ICompressorProvider.class, Thread.currentThread().getContextClassLoader())) { if (iTileCompressorProvider instanceof CompressorProvider) { defaultProvider = iTileCompressorProvider; } else { ICompressorControl result = iTileCompressorProvider.createCompressorControl(quantAlgorithm, compressionAlgorithm, baseType); if (result != null) { return result; } } } return defaultProvider.createCompressorControl(quantAlgorithm, compressionAlgorithm, baseType); } @Override public ICompressorControl createCompressorControl(String quantAlgorithm, String compressionAlgorithm, Class baseType) { String className = NAME_COMPUTER.createCompressorClassName(quantAlgorithm, compressionAlgorithm, baseType); for (Class[] clazz : AVAILABLE_COMPRESSORS) { Class compressorClass = clazz[0]; if (compressorClass.getSimpleName().equals(className)) { if (clazz.length > 1) { Class parametersClass = clazz[1]; return new TileCompressorControl(compressorClass, parametersClass); } else { return new TileCompressorControl(compressorClass); } } } return null; } } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/api/000077500000000000000000000000001310063650500303425ustar00rootroot00000000000000ICompressorProvider.java000066400000000000000000000040541310063650500351110ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/apipackage nom.tam.fits.compression.provider.api; import nom.tam.fits.compression.algorithm.api.ICompressorControl; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * Service loader Interface to provide compression algorithms to fits. */ public interface ICompressorProvider { /** * @return the {@code ICompressorControl} to use for the specified * quantize and compression algorithms and base type. * @param quantAlgorithm * the quantification algorithm to use or null if none * @param compressionAlgorithm * the compression algorithm to use * @param baseType * the base type of the data to (de)compress. */ ICompressorControl createCompressorControl(String quantAlgorithm, String compressionAlgorithm, Class baseType); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/000077500000000000000000000000001310063650500306715ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/api/000077500000000000000000000000001310063650500314425ustar00rootroot00000000000000HeaderAccess.java000066400000000000000000000053031310063650500345410ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.Header; import nom.tam.fits.HeaderCard; import nom.tam.fits.HeaderCardBuilder; import nom.tam.fits.HeaderCardException; import nom.tam.fits.header.IFitsHeader; public class HeaderAccess implements IHeaderAccess { private final Header header; private HeaderCardBuilder builder; public HeaderAccess(Header header) { this.header = header; } @Override public void addValue(IFitsHeader key, int value) { try { card(key).value(value); } catch (HeaderCardException e) { throw new IllegalArgumentException("header card could not be created"); } } @Override public void addValue(IFitsHeader key, String value) { try { card(key).value(value); } catch (HeaderCardException e) { throw new IllegalArgumentException("header card could not be created"); } } @Override public HeaderCard findCard(IFitsHeader key) { return this.header.findCard(key); } @Override public HeaderCard findCard(String key) { return this.header.findCard(key); } private HeaderCardBuilder card(IFitsHeader key) { if (this.builder == null) { this.builder = this.header.card(key); return this.builder; } else { return this.builder.card(key); } } } HeaderCardAccess.java000066400000000000000000000047761310063650500353500ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.HeaderCard; import nom.tam.fits.HeaderCardException; import nom.tam.fits.header.IFitsHeader; public class HeaderCardAccess implements IHeaderAccess { private final HeaderCard headerCard; public HeaderCardAccess(IFitsHeader headerCard, String value) { try { this.headerCard = new HeaderCard(headerCard.key(), value, null); } catch (HeaderCardException e) { throw new IllegalArgumentException("header card could not be created"); } } @Override public void addValue(IFitsHeader key, int value) { if (this.headerCard.getKey().equals(key.key())) { this.headerCard.setValue(value); } } @Override public void addValue(IFitsHeader key, String value) { if (this.headerCard.getKey().equals(key.key())) { this.headerCard.setValue(value); } } @Override public HeaderCard findCard(IFitsHeader key) { return findCard(key.key()); } @Override public HeaderCard findCard(String key) { if (this.headerCard.getKey().equals(key)) { return this.headerCard; } return null; } } ICompressColumnParameter.java000066400000000000000000000030631310063650500371530ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ public interface ICompressColumnParameter extends ICompressParameter { Object column(); void column(Object column, int size); void getValueFromColumn(int index); void setValueInColumn(int index); } ICompressHeaderParameter.java000066400000000000000000000036661310063650500371170ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * Compression parameter that must be stored along the header meta data of the * hdu. */ public interface ICompressHeaderParameter extends ICompressParameter { /** * get the value from the header and set it in the compression option. * * @param header * the header of the hdu */ void getValueFromHeader(IHeaderAccess header); /** * Get the parameter value from the option and set it into the fits header. * * @param header * the header to add the parameter. */ void setValueInHeader(IHeaderAccess header); } ICompressParameter.java000066400000000000000000000031271310063650500357760ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2015 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * Compression parameter that must be stored along the meta data. */ public interface ICompressParameter { /** * @return the name of the parameter, normally the fits header key or column * name. */ String getName(); } ICompressParameters.java000066400000000000000000000100071310063650500361540ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; import nom.tam.fits.BinaryTable; import nom.tam.fits.BinaryTableHDU; import nom.tam.fits.FitsException; import nom.tam.fits.HeaderCardException; import nom.tam.fits.compression.algorithm.api.ICompressOption; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ /** * Group of parameters that must be synchronized with the hdu meta data for a * specific compression algorithm. */ public interface ICompressParameters { /** * Add the columns that hold the metadata for the parameters that are column * based to the dhu. * * @param hdu * the hdu to add the column * @throws FitsException * if the column could not be added. */ void addColumnsToTable(BinaryTableHDU hdu) throws FitsException; /** * create a copy of this parameter for another option (normally a copy of * the current option). * * @param option * the new option for the copied parameter * @return this (builder pattern) */ ICompressParameters copy(ICompressOption option); /** * extract the option data from the column and set it in the option. * * @param index * the index in the column. */ void getValuesFromColumn(int index); /** * extract the option values that are represented by headers from the hdu * header. * * @param header * the header to extract the option values. */ void getValuesFromHeader(IHeaderAccess header); /** * initialize the column based options of the compression algorithm from the * binary table. * * @param header * the header of the hdu * @param binaryTable * the table of the hdu * @param size * the column size * @throws FitsException * if the column could not be initialized */ void initializeColumns(IHeaderAccess header, BinaryTable binaryTable, int size) throws FitsException; /** * initialize the column based parameter to the specified column length. * * @param length * the column length. */ void initializeColumns(int length); /** * set the option values, that are column based, into the columns at the * specified index. * * @param index * the index in the columns to set. */ void setValueFromColumn(int index); /** * set the options values, that are hdu based, into the header. * * @param header * the header to set the option value * @throws HeaderCardException * if the header could not be set. */ void setValuesInHeader(IHeaderAccess header) throws HeaderCardException; } IHeaderAccess.java000066400000000000000000000031711310063650500346530ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/apipackage nom.tam.fits.compression.provider.param.api; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import nom.tam.fits.HeaderCard; import nom.tam.fits.header.IFitsHeader; public interface IHeaderAccess { void addValue(IFitsHeader key, int value); void addValue(IFitsHeader key, String value); HeaderCard findCard(IFitsHeader key); HeaderCard findCard(String key); } nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/base/000077500000000000000000000000001310063650500316035ustar00rootroot00000000000000CompressColumnParameter.java000066400000000000000000000056231310063650500372070ustar00rootroot00000000000000nom-tam-fits-nom-tam-fits-1.15.2/src/main/java/nom/tam/fits/compression/provider/param/basepackage nom.tam.fits.compression.provider.param.base; /* * #%L * nom.tam FITS library * %% * Copyright (C) 1996 - 2016 nom-tam-fits * %% * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * #L% */ import java.lang.reflect.Array; import nom.tam.fits.compression.provider.param.api.ICompressColumnParameter; public abstract class CompressColumnParameter extends CompressParameter