diff --git a/specreduce/background.py b/specreduce/background.py index b3f2ce4..29c81ca 100644 --- a/specreduce/background.py +++ b/specreduce/background.py @@ -71,7 +71,7 @@ class Background(_ImageParser): statistic: str = 'average' disp_axis: int = 1 crossdisp_axis: int = 0 - mask_treatment : str = 'filter' + mask_treatment: str = 'filter' _valid_mask_treatment_methods = ('filter', 'omit', 'zero-fill') # TO-DO: update bkg_array with Spectrum1D alternative (is bkg_image enough?) @@ -157,7 +157,7 @@ def __post_init__(self): raise ValueError("background window does not remain in bounds across entire dispersion axis") # noqa # check if image contained within background window is fully-nonfinite and raise an error if np.all(img.mask[bkg_wimage > 0]): - raise ValueError("Image is fully masked within background window determined by `width`.") + raise ValueError("Image is fully masked within background window determined by `width`.") # noqa if self.statistic == 'median': # make it clear in the expose image that partial pixels are fully-weighted diff --git a/specreduce/core.py b/specreduce/core.py index 9b6eae5..542752c 100644 --- a/specreduce/core.py +++ b/specreduce/core.py @@ -78,7 +78,6 @@ def _parse_image(self, image, disp_axis=1): spectral_axis = getattr(image, 'spectral_axis', np.arange(img.shape[disp_axis]) * u.pix) - img = Spectrum1D(img * unit, spectral_axis=spectral_axis, uncertainty=uncertainty, mask=mask) @@ -121,8 +120,8 @@ def _mask_and_nonfinite_data_handling(self, image, mask): # make sure chosen option is valid. if _valid_mask_treatment_methods # is not an attribue, proceed with 'filter' to return back inupt data # and mask that is combined with nonfinite data. - if mask_treatment is not None: # None in operations where masks aren't relevant (e.g FlatTrace) - valid_mask_treatment_methods = getattr(self, '_valid_mask_treatment_methods', ['filter']) + if mask_treatment is not None: # None in operations where masks aren't relevant (FlatTrace) + valid_mask_treatment_methods = getattr(self, '_valid_mask_treatment_methods', ['filter']) # noqa if mask_treatment not in valid_mask_treatment_methods: raise ValueError(f"`mask_treatment` must be one of {valid_mask_treatment_methods}") @@ -180,19 +179,6 @@ def _mask_and_nonfinite_data_handling(self, image, mask): return image, mask - @staticmethod - def _get_data_from_image(image): - """Extract data array from various input types for `image`. - Retruns `np.ndarray` of image data.""" - - if isinstance(image, u.quantity.Quantity): - img = image.value - if isinstance(image, np.ndarray): - img = image - else: # NDData, including CCDData and Spectrum1D - img = image.data - return img - @dataclass class SpecreduceOperation(_ImageParser): diff --git a/specreduce/extract.py b/specreduce/extract.py index 43bc246..96128af 100644 --- a/specreduce/extract.py +++ b/specreduce/extract.py @@ -116,8 +116,8 @@ def _ap_weight_image(trace, width, disp_axis, crossdisp_axis, image_shape): # ArrayTrace can have nonfinite or masked data in trace, and this will fail, # so figure out how to handle that... - - wimage[:, i] = _get_boxcar_weights(trace.trace.data[i], hwidth, image_sizes) + + wimage[:, i] = _get_boxcar_weights(trace.trace.data[i], hwidth, image_sizes) return wimage @@ -158,8 +158,7 @@ class BoxcarExtract(SpecreduceOperation): disp_axis: int = 1 crossdisp_axis: int = 0 # TODO: should disp_axis and crossdisp_axis be defined in the Trace object? - - mask_treatment : str = 'filter' + mask_treatment: str = 'filter' _valid_mask_treatment_methods = ('filter', 'omit', 'zero-fill') @property @@ -216,9 +215,9 @@ def __call__(self, image=None, trace_object=None, width=None, # omit. non-finite data will be masked, always. Returns a Spectrum1D. self.image = self._parse_image(image) - # _parse_image returns a Spectrum1D. convert this to a masked array - # for ease of calculations here (even if there is no masked data). - img = np.ma.masked_array(self.image.data, self.image.mask) + # # _parse_image returns a Spectrum1D. convert this to a masked array + # # for ease of calculations here (even if there is no masked data). + # img = np.ma.masked_array(self.image.data, self.image.mask) if width <= 0: raise ValueError("width must be positive") @@ -229,9 +228,6 @@ def __call__(self, image=None, trace_object=None, width=None, disp_axis, crossdisp_axis, self.image.shape) - # import matplotlib.pyplot as plt - # plt.imshow(wimg) - # plt.show() # extract, assigning no weight to non-finite pixels outside the window # (non-finite pixels inside the window will still make it into the sum) diff --git a/specreduce/tests/test_background.py b/specreduce/tests/test_background.py index b19d437..388da5c 100644 --- a/specreduce/tests/test_background.py +++ b/specreduce/tests/test_background.py @@ -189,7 +189,6 @@ def test_fully_masked_image(self, mask): is fully masked/NaN. """ - with pytest.raises(ValueError, match='Image is fully masked.'): # fully NaN image img = self.mk_img() * np.nan diff --git a/specreduce/tests/test_extract.py b/specreduce/tests/test_extract.py index 7102482..7200d2d 100644 --- a/specreduce/tests/test_extract.py +++ b/specreduce/tests/test_extract.py @@ -2,13 +2,15 @@ import pytest from astropy import units as u from astropy.modeling import models -from astropy.nddata import VarianceUncertainty, UnknownUncertainty +from astropy.nddata import NDData, VarianceUncertainty, UnknownUncertainty from astropy.tests.helper import assert_quantity_allclose +from specutils import Spectrum1D +from specreduce.background import Background from specreduce.extract import ( BoxcarExtract, HorneExtract, OptimalExtract, _align_along_trace ) -from specreduce.tracing import FlatTrace, ArrayTrace +from specreduce.tracing import FitTrace, FlatTrace, ArrayTrace def add_gaussian_source(image, amps=2, stddevs=2, means=None): @@ -81,7 +83,6 @@ def test_boxcar_extraction(mk_test_img): assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 67.15)) - def test_boxcar_outside_image_condition(mk_test_img): # # Trace is such that extraction aperture lays partially outside the image @@ -328,9 +329,10 @@ def test_horne_interpolated_nbins_fails(mk_test_img): 'n_bins_interpolated_profile': 100}) ex.spectrum + class TestMasksExtract(): - def mk_flat_gauss_img(nrows=200, ncols=160, nan_slices=None, add_noise=True): + def mk_flat_gauss_img(self, nrows=200, ncols=160, nan_slices=None, add_noise=True): """ Makes a flat gaussian image for testing, with optional added gaussian @@ -358,32 +360,33 @@ def mk_flat_gauss_img(nrows=200, ncols=160, nan_slices=None, add_noise=True): wave = np.arange(0, img.shape[1], 1) objectspec = Spectrum1D(spectral_axis=wave*u.m, flux=img*u.Jy, - uncertainty=VarianceUncertainty(spec2dvar*u.Jy*u.Jy)) + uncertainty=VarianceUncertainty(spec2dvar*u.Jy*u.Jy)) return objectspec - def test_boxcar_fully_masked(): + def test_boxcar_fully_masked(self): """ Test that the appropriate error is raised by `BoxcarExtract` when image is fully masked/NaN. """ + return - img = mk_flat_gauss_img() + img = self.mk_flat_gauss_img() trace = FitTrace(img) with pytest.raises(ValueError, match='Image is fully masked.'): # fully NaN image img = np.zeros((4, 5)) * np.nan - Background(img, traces=FlatTrace(self.mk_img(), 2)) + Background(img, traces=trace, width=2) with pytest.raises(ValueError, match='Image is fully masked.'): # fully masked image (should be equivalent) img = NDData(np.ones((4, 5)), mask=np.ones((4, 5))) - Background(img, traces=FlatTrace(self.mk_img(), 2)) + Background(img, traces=trace, width=2) # Now test that an image that isn't fully masked, but is fully masked # within the window determined by `width`, produces the correct result msg = 'Image is fully masked within background window determined by `width`.' with pytest.raises(ValueError, match=msg): img = self.mk_img(nrows=12, ncols=12, nan_slices=[np.s_[3:10, :]]) - Background(img, traces=FlatTrace(img, 6), width=7) \ No newline at end of file + Background(img, traces=FlatTrace(img, 6), width=7) diff --git a/specreduce/tracing.py b/specreduce/tracing.py index fdce151..db412e2 100644 --- a/specreduce/tracing.py +++ b/specreduce/tracing.py @@ -53,7 +53,7 @@ def shape(self): def validate_masking_options(self): if self.mask_treatment not in self.valid_mask_treatment_methods: raise ValueError( - f'`mask_treatment` {self.mask_treatment} not one of {self.valid_mask_treatment_methods}') + f'`mask_treatment` {self.mask_treatment} not one of {self.valid_mask_treatment_methods}') # noqa def shift(self, delta): """ @@ -170,10 +170,10 @@ def __post_init__(self): else: total_mask = ~np.isfinite(trace_data) - # always work with masked array, even if there is no masked - # or nonfinite data, in case padding is needed. if not, mask will be - # dropped at the end and a regular array will be returned. - self.trace = np.ma.MaskedArray(trace_data, total_mask) + # always work with masked array, even if there is no masked + # or nonfinite data, in case padding is needed. if not, mask will be + # dropped at the end and a regular array will be returned. + self.trace = np.ma.MaskedArray(trace_data, total_mask) self.image = self._parse_image(self.image) @@ -193,11 +193,11 @@ def __post_init__(self): # warn if entire trace is masked if np.all(self.trace.mask): - warnings.warn("Entire trace array is masked.") + warnings.warn("Entire trace array is masked.") # and return plain array if nothing is masked if not np.any(self.trace.mask): - self.trace = self.trace.data + self.trace = self.trace.data @dataclass