Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified FitsOperations to use Cutout class. Added test for HDU Iter… #67

Merged
merged 5 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cadc-data-ops-fits/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repositories {

sourceCompatibility = 1.8
group = 'org.opencadc'
version = '0.2.2'
version = '0.2.3'

description = 'OpenCADC FITS cutout library'
def git_url = 'https://github.com/opencadc/dal'
Expand All @@ -24,7 +24,7 @@ dependencies {
compile 'org.opencadc:cadc-soda-server:[1.2.1,2.0)'
compile 'org.opencadc:cadc-wcs:[2.0,3.0)'
compile 'org.opencadc:jsky:[1.0.0,2.0.0)'
compile 'org.opencadc:nom-tam-fits:[1.16.1,1.17.0)'
compile 'org.opencadc:nom-tam-fits:[1.16.2,1.16.3)'

// Use JUnit test framework
testCompile 'junit:junit:[4.13,5.0)'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@
import nom.tam.fits.HeaderCard;
import nom.tam.util.Cursor;
import nom.tam.util.RandomAccessDataObject;
import org.apache.log4j.Logger;
import org.opencadc.fits.slice.NDimensionalSlicer;
import org.opencadc.soda.ExtensionSlice;
import org.opencadc.soda.server.Cutout;

/**
Expand All @@ -94,6 +94,8 @@
* @author pdowler
*/
public class FitsOperations {
private static final Logger log = Logger.getLogger(FitsOperations.class);

private final RandomAccessDataObject src;

public FitsOperations(RandomAccessDataObject src) {
Expand Down Expand Up @@ -156,20 +158,22 @@ public void headersToStream(final OutputStream outputStream) throws IOException
/**
* Implement prototype SODA pixel cutout action.
*
* @param slices subsets of data to extract
* @param cutout cutout spec
* @param outputStream The Stream to write out to.
* @throws ReadException Any errors to report back to the caller.
* @throws NoOverlapException A valid cutout was provided, but there is no match (overlap).
* @throws ReadException Any errors reading reported by the storage system.
*/
public void cutoutToStream(final List<ExtensionSlice> slices, final OutputStream outputStream) throws ReadException {
public void cutoutToStream(final Cutout cutout, final OutputStream outputStream)
throws NoOverlapException, ReadException {
log.debug("cutoutToStream() start.");
pdowler marked this conversation as resolved.
Show resolved Hide resolved
try {
final NDimensionalSlicer slicer = new NDimensionalSlicer();
final Cutout cutout = new Cutout();
cutout.pixelCutouts = slices;
slicer.slice(src, cutout, outputStream);
} catch (FitsException | NoSuchKeywordException ex) {
throw new ReadException("invalid fits data: " + src + " reason: " + ex.getMessage(), ex);
} catch (IOException ex) {
throw new ReadException("failed to read " + src + " reason: " + ex.getMessage(), ex);
}
log.debug("cutoutToStream() OK.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
************************************************************************
******************* CANADIAN ASTRONOMY DATA CENTRE *******************
************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
*
* (c) 2021. (c) 2021.
* Government of Canada Gouvernement du Canada
* National Research Council Conseil national de recherches
* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
* All rights reserved Tous droits réservés
*
* NRC disclaims any warranties, Le CNRC dénie toute garantie
* expressed, implied, or énoncée, implicite ou légale,
* statutory, of any kind with de quelque nature que ce
* respect to the software, soit, concernant le logiciel,
* including without limitation y compris sans restriction
* any warranty of merchantability toute garantie de valeur
* or fitness for a particular marchande ou de pertinence
* purpose. NRC shall not be pour un usage particulier.
* liable in any event for any Le CNRC ne pourra en aucun cas
* damages, whether direct or être tenu responsable de tout
* indirect, special or general, dommage, direct ou indirect,
* consequential or incidental, particulier ou général,
* arising from the use of the accessoire ou fortuit, résultant
* software. Neither the name de l'utilisation du logiciel. Ni
* of the National Research le nom du Conseil National de
* Council of Canada nor the Recherches du Canada ni les noms
* names of its contributors may de ses participants ne peuvent
* be used to endorse or promote être utilisés pour approuver ou
* products derived from this promouvoir les produits dérivés
* software without specific prior de ce logiciel sans autorisation
* written permission. préalable et particulière
* par écrit.
*
* This file is part of the Ce fichier fait partie du projet
* OpenCADC project. OpenCADC.
*
* OpenCADC is free software: OpenCADC est un logiciel libre ;
* you can redistribute it and/or vous pouvez le redistribuer ou le
* modify it under the terms of modifier suivant les termes de
* the GNU Affero General Public la “GNU Affero General Public
* License as published by the License” telle que publiée
* Free Software Foundation, par la Free Software Foundation
* either version 3 of the : soit la version 3 de cette
* License, or (at your option) licence, soit (à votre gré)
* any later version. toute version ultérieure.
*
* OpenCADC is distributed in the OpenCADC est distribué
* hope that it will be useful, dans l’espoir qu’il vous
* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
* without even the implied GARANTIE : sans même la garantie
* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
* General Public License for Générale Publique GNU Affero
* more details. pour plus de détails.
*
* You should have received Vous devriez avoir reçu une
* a copy of the GNU Affero copie de la Licence Générale
* General Public License along Publique GNU Affero avec
* with OpenCADC. If not, see OpenCADC ; si ce n’est
* <http://www.gnu.org/licenses/>. pas le cas, consultez :
* <http://www.gnu.org/licenses/>.
*
*
************************************************************************
*/

package org.opencadc.fits;

/**
* Exception thrown to indicate a client error in the supplied cutout.
*/
public class NoOverlapException extends Exception {
/**
* Constructs an <code>IllegalArgumentException</code> with the
* specified detail message.
*/
public NoOverlapException() {
this("No overlap found.");
}

/**
* Constructs an <code>IllegalArgumentException</code> with the
* specified detail message.
*
* @param s the detail message.
*/
public NoOverlapException(String s) {
super(s);
}

/**
* Constructs a new exception with the specified detail message and
* cause.
*
* <p>Note that the detail message associated with <code>cause</code> is
* <i>not</i> automatically incorporated in this exception's detail
* message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link Throwable#getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link Throwable#getCause()} method). (A {@code null} value
* is permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.5
*/
public NoOverlapException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import nom.tam.util.RandomAccessFileExt;
import org.apache.log4j.Logger;
import org.opencadc.fits.HDUIterator;
import org.opencadc.fits.NoOverlapException;
import org.opencadc.soda.ExtensionSlice;
import org.opencadc.soda.PixelRange;
import org.opencadc.soda.server.Cutout;
Expand Down Expand Up @@ -134,10 +135,11 @@ public NDimensionalSlicer() {
* @param outputStream Where to write bytes to. This method will not close this stream.
* @throws FitsException Any FITS related errors from the NOM TAM Fits library.
* @throws IOException Reading Writing errors.
* @throws NoOverlapException Client error to inform that the supplied cutout is valid but yields no results.
* @throws NoSuchKeywordException Reading the FITS file failed.
*/
public void slice(final File fitsFile, final Cutout cutout, final OutputStream outputStream)
throws FitsException, IOException, NoSuchKeywordException {
throws FitsException, IOException, NoSuchKeywordException, NoOverlapException {
slice(new RandomAccessFileExt(fitsFile, "r"), cutout, outputStream);
}

Expand All @@ -158,18 +160,19 @@ public void slice(final File fitsFile, final Cutout cutout, final OutputStream o
* @param outputStream Where to write bytes to. This method will not close this stream.
* @throws FitsException Any FITS related errors from the NOM TAM Fits library.
* @throws IOException Reading Writing errors.
* @throws NoOverlapException Client error to inform that the supplied cutout is valid but yields no results.
* @throws NoSuchKeywordException Reading the FITS file failed.
*/
public void slice(final RandomAccessDataObject randomAccessDataObject, final Cutout cutout,
final OutputStream outputStream)
throws FitsException, IOException, NoSuchKeywordException {
throws FitsException, IOException, NoOverlapException, NoSuchKeywordException {
final ArrayDataOutput output = new BufferedDataOutputStream(outputStream);
slice(randomAccessDataObject, cutout, output);
}

private void slice(final RandomAccessDataObject randomAccessDataObject, final Cutout cutout,
final ArrayDataOutput output)
throws FitsException, IOException, NoSuchKeywordException {
throws FitsException, IOException, NoOverlapException, NoSuchKeywordException {
if (isEmpty(cutout)) {
throw new IllegalStateException("No cutout specified.");
}
Expand All @@ -182,7 +185,7 @@ private void slice(final RandomAccessDataObject randomAccessDataObject, final Cu
final Map<Integer, List<ExtensionSlice>> overlapHDUs = getOverlap(fitsInput, cutout);

if (overlapHDUs.isEmpty()) {
throw new FitsException("No overlap found.");
throw new NoOverlapException();
} else {
LOGGER.debug("Found " + overlapHDUs.size() + " overlapping slices.");
}
Expand Down Expand Up @@ -282,7 +285,7 @@ private void setupPrimaryHeader(final Header headerCopy, final int nextEndSize)

private void writeSlices(final BasicHDU<?> hdu, List<ExtensionSlice> extensionSliceList, final Fits fitsOutput,
final boolean mefOutput, final boolean firstHDUAlreadyWritten, final int nextEndSize)
throws FitsException {
throws FitsException, NoOverlapException {
final ImageHDU imageHDU = (hdu instanceof CompressedImageHDU)
? ((CompressedImageHDU) hdu).asImageHDU() : (ImageHDU) hdu;
final Header header = imageHDU.getHeader();
Expand All @@ -292,7 +295,7 @@ private void writeSlices(final BasicHDU<?> hdu, List<ExtensionSlice> extensionSl
if (extensionSliceValue.getPixelRanges().isEmpty()) {
fitsOutput.addHDU(hdu);
} else if (dimensions == null) {
throw new FitsException("Sub-image not within image");
throw new NoOverlapException();
} else {
final int dimensionLength = dimensions.length;
final int[] corners = new int[dimensionLength];
Expand All @@ -305,7 +308,7 @@ private void writeSlices(final BasicHDU<?> hdu, List<ExtensionSlice> extensionSl

// The data contained in this HDU cannot be used to slice from.
if (corners.length == 0) {
throw new FitsException("Sub-image not within image");
throw new NoOverlapException();
}

LOGGER.debug("Tiling out " + Arrays.toString(lengths) + " at corner "
Expand Down Expand Up @@ -563,7 +566,7 @@ private int mapOverlap(final BasicHDU<?> hdu, final int hduIndex, final List<Ext
* @throws FitsException if the header could not be read
*/
private Map<Integer, List<ExtensionSlice>> getOverlap(final Fits fits, final Cutout cutout)
throws FitsException, NoSuchKeywordException {
throws FitsException, NoOverlapException, NoSuchKeywordException {
if ((cutout.pixelCutouts != null) && !cutout.pixelCutouts.isEmpty()) {
return getOverlap(fits, cutout.pixelCutouts);
} else {
Expand All @@ -585,7 +588,7 @@ private Map<Integer, List<ExtensionSlice>> getOverlap(final Fits fits, final Cut
}

private Map<Integer, List<ExtensionSlice>> getOverlap(final Fits fits, final List<ExtensionSlice> extensionSlices)
throws FitsException {
throws FitsException, NoOverlapException {
// A Set is used to eliminate duplicates from the inner loop below.
final Map<Integer, List<ExtensionSlice>> overlapHDUIndexesSlices = new LinkedHashMap<>();

Expand Down Expand Up @@ -617,7 +620,7 @@ private Map<Integer, List<ExtensionSlice>> getOverlap(final Fits fits, final Lis
}).collect(Collectors.toList());

if (!containsAll.isEmpty()) {
throw new IllegalArgumentException("One or more requested slices could not be found:\n" + containsAll);
throw new NoOverlapException("One or more requested slices could not be found:\n" + containsAll);
}

return overlapHDUIndexesSlices;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
*/
public class PolarizationCutout extends FITSCutout<PolarizationState[]> {
private static final Logger LOGGER = Logger.getLogger(PolarizationCutout.class);
private static final double DEFAULT_VALUE = 1.0D;

public PolarizationCutout(final Header header) throws HeaderCardException {
super(header);
Expand All @@ -106,16 +107,23 @@ public long[] getBounds(final PolarizationState[] states) {
final int naxis = this.fitsHeaderWCSKeywords.getIntValue(Standard.NAXIS.key());
final double crpix = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CRPIXn.n(polarizationAxis).key());
final double crval = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CRVALn.n(polarizationAxis).key());
final double cdelt = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CDELTn.n(polarizationAxis).key());
final double cdelt = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CDELTn.n(polarizationAxis).key(),
DEFAULT_VALUE);

// Nothing to check.
if (crpix == 0.0D || crval == 0.0D) {
return null;
}

double pix1 = Double.MAX_VALUE;
double pix2 = Double.MIN_VALUE;
for (final PolarizationState headerState : getHeaderStates(polarizationAxis)) {
for (final PolarizationState headerState : getHeaderStates(polarizationAxis, crpix, crval, cdelt)) {
LOGGER.debug("Checking next header state " + headerState.name());
for (final PolarizationState cutoutState : states) {
if (cutoutState.equals(headerState)) {
final int value = headerState.getValue();
final double pix = crpix + (value - crval) / cdelt;
LOGGER.debug("Found pixel value " + crpix + " + (" + value + "-" + crval + ") / " + cdelt + " = " + pix);
pix1 = Math.min(pix1, pix);
pix2 = Math.max(pix2, pix);

Expand Down Expand Up @@ -150,16 +158,14 @@ public long[] getBounds(final PolarizationState[] states) {
* @param polarizationAxis The polarization axis to get values from.
* @return Array of PolarizationState objects. Never null.
*/
PolarizationState[] getHeaderStates(final int polarizationAxis) {
final int naxis = this.fitsHeaderWCSKeywords.getIntValue(Standard.NAXIS.key());
final double crpix = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CRPIXn.n(polarizationAxis).key());
final double crval = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CRVALn.n(polarizationAxis).key());
final double cdelt = this.fitsHeaderWCSKeywords.getDoubleValue(Standard.CDELTn.n(polarizationAxis).key());
PolarizationState[] getHeaderStates(final int polarizationAxis, final double crpix, final double crval,
final double cdelt) {
final int naxisValue = this.fitsHeaderWCSKeywords.getIntValue(Standard.NAXISn.n(polarizationAxis).key());

final List<PolarizationState> polarizationStates = new ArrayList<>();

IntStream.range(1, naxis + 1)
.map(i -> (int) (crpix + (i - crval) / cdelt))
IntStream.range(1, naxisValue + 1)
.map(i -> (int) (crval + cdelt * (i - crpix)))
.filter(i -> PolarizationState.fromValue(i) != null)
.forEach(i -> polarizationStates.add(PolarizationState.fromValue(i)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
import org.junit.Assert;
import org.junit.Test;
import org.opencadc.soda.ExtensionSlice;
import org.opencadc.soda.server.Cutout;

/**
*
Expand Down Expand Up @@ -227,7 +228,9 @@ public void testSlice() throws Exception {
cut.add(new ExtensionSlice(3));
cut.add(new ExtensionSlice(106));
cut.add(new ExtensionSlice(126));
fop.cutoutToStream(cut, fileOutputStream);
final Cutout cutout = new Cutout();
cutout.pixelCutouts = cut;
fop.cutoutToStream(cutout, fileOutputStream);
}

final Fits resultsFits = new Fits(new RandomAccessFileExt(outputFile, "r"));
Expand All @@ -249,4 +252,29 @@ public void testSlice() throws Exception {
log.info("...");
}
}

@Test
public void testNoOverlap() throws Exception {
final RandomAccessDataObject randomAccessDataObject =
new RandomAccessFileExt(FileUtil.getFileFromResource("test-hst-mef.fits",
FitsOperationsTest.class), "r");
final FitsOperations fop = new FitsOperations(randomAccessDataObject);
final File outputFile = Files.createTempFile(
new File(System.getProperty("user.home")).toPath(),
"test-hst-mef-cutout-", ".fits").toFile();
outputFile.deleteOnExit();

// Extension 3 contains non-image Data, but should still be included.
try (final FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
List<ExtensionSlice> cut = new ArrayList<>();
// No such extension
cut.add(new ExtensionSlice(188));
final Cutout cutout = new Cutout();
cutout.pixelCutouts = cut;
fop.cutoutToStream(cutout, fileOutputStream);
Assert.fail("Should throw NoOverlapException");
} catch (NoOverlapException noOverlapException) {
// Good!
}
}
}
Loading