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

Compatibility with specutils 2.0 #260

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Bug Fixes
Other changes
^^^^^^^^^^^^^

- Compatibility with specutils 2.0. [#260]

1.5.1 (2024-03-08)
------------------
Expand Down
107 changes: 1 addition & 106 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
# This file is used to configure the behavior of pytest

import numpy as np
import pytest
from astropy import units as u
from astropy.io import fits
from astropy.nddata import CCDData, NDData, VarianceUncertainty
from astropy.utils.data import get_pkg_data_filename
from specutils import Spectrum1D, SpectralAxis
# Root level conftest.py is only for nice pytest header when using tox.

try:
from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS
Expand All @@ -15,103 +7,6 @@
ASTROPY_HEADER = False


# Test image is comprised of 30 rows with 10 columns each. Row content
# is row index itself. This makes it easy to predict what should be the
# value extracted from a region centered at any arbitrary Y position.
def _mk_test_data(imgtype, nrows=30, ncols=10):
image_ones = np.ones(shape=(nrows, ncols))
image = image_ones.copy()
for j in range(nrows):
image[j, ::] *= j
if imgtype == "raw":
pass # no extra processing
elif imgtype == "ccddata":
image = CCDData(image, unit=u.Jy)
else: # spectrum
flux = image * u.DN
uncert = VarianceUncertainty(image_ones)
if imgtype == "spec_no_axis":
image = Spectrum1D(flux, uncertainty=uncert)
else: # "spec"
image = Spectrum1D(flux, spectral_axis=np.arange(ncols) * u.um, uncertainty=uncert)
return image


@pytest.fixture
def mk_test_img_raw():
return _mk_test_data("raw")


@pytest.fixture
def mk_test_img():
return _mk_test_data("ccddata")


@pytest.fixture
def mk_test_spec_no_spectral_axis():
return _mk_test_data("spec_no_axis")


@pytest.fixture
def mk_test_spec_with_spectral_axis():
return _mk_test_data("spec")


# Test data file already transposed like this:
# fn = download_file('https://stsci.box.com/shared/static/exnkul627fcuhy5akf2gswytud5tazmw.fits', cache=True) # noqa: E501
# img = fits.getdata(fn).T
@pytest.fixture
def all_images():
np.random.seed(7)

filename = get_pkg_data_filename(
"data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits", package="specreduce.tests")
img = fits.getdata(filename)
flux = img * (u.MJy / u.sr)
sax = SpectralAxis(np.linspace(14.377, 3.677, flux.shape[-1]) * u.um)
unc = VarianceUncertainty(np.random.rand(*flux.shape))

all_images = {}
all_images['arr'] = img
all_images['s1d'] = Spectrum1D(flux, spectral_axis=sax, uncertainty=unc)
all_images['s1d_pix'] = Spectrum1D(flux, uncertainty=unc)
all_images['ccd'] = CCDData(img, uncertainty=unc, unit=flux.unit)
all_images['ndd'] = NDData(img, uncertainty=unc, unit=flux.unit)
all_images['qnt'] = img * flux.unit
return all_images


@pytest.fixture
def spec1d():
np.random.seed(7)
flux = np.random.random(50)*u.Jy
sa = np.arange(0, 50)*u.pix
spec = Spectrum1D(flux, spectral_axis=sa)
return spec


@pytest.fixture
def spec1d_with_emission_line():
np.random.seed(7)
sa = np.arange(0, 200)*u.pix
flux = (np.random.randn(200) +
10*np.exp(-0.01*((sa.value-130)**2)) +
sa.value/100) * u.Jy
spec = Spectrum1D(flux, spectral_axis=sa)
return spec


@pytest.fixture
def spec1d_with_absorption_line():
np.random.seed(7)
sa = np.arange(0, 200)*u.pix
flux = (np.random.randn(200) -
10*np.exp(-0.01*((sa.value-130)**2)) +
sa.value/100) * u.Jy
spec = Spectrum1D(flux, spectral_axis=sa)
return spec


def pytest_configure(config):

if ASTROPY_HEADER:
Expand Down
2 changes: 1 addition & 1 deletion notebook_sandbox/apo05.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"from astropy import units as u\n",
"\n",
"from astropy.nddata import CCDData, StdDevUncertainty\n",
"from specutils import Spectrum1D\n",
"from specreduce.compat import Spectrum as Spectrum1D\n",
"from ccdproc import trim_image, Combiner\n",
"\n",
"# need to get import to work in notebook w/o global package install\n",
Expand Down
2 changes: 1 addition & 1 deletion notebook_sandbox/fluxcal_demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"from ccdproc import trim_image\n",
"from astropy import units as u\n",
"\n",
"from specutils import Spectrum1D\n",
"from specreduce.compat import Spectrum as Spectrum1D\n",
"from astropy.nddata import StdDevUncertainty\n",
"\n",
"# need to get import to work in notebook w/o global package install\n",
Expand Down
2 changes: 1 addition & 1 deletion notebook_sandbox/wavecal_demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"from astropy.nddata import StdDevUncertainty\n",
"import astropy.units as u\n",
"\n",
"from specutils import Spectrum1D\n",
"from specreduce.compat import Spectrum as Spectrum1D\n",
"from specutils.fitting import fit_generic_continuum\n",
"\n",
"from specreduce.calibration_data import load_pypeit_calibration_lines\n",
Expand Down
30 changes: 19 additions & 11 deletions specreduce/background.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import numpy as np
from astropy import units as u
from astropy.utils.decorators import deprecated_attribute
from specutils import Spectrum1D

from specreduce.compat import SPECUTILS_LT_2, Spectrum
from specreduce.core import _ImageParser, MaskingOption, ImageLike
from specreduce.extract import _ap_weight_image
from specreduce.tracing import Trace, FlatTrace
Expand Down Expand Up @@ -84,7 +84,7 @@
"apply_nan_only",
)

# TO-DO: update bkg_array with Spectrum1D alternative (is bkg_image enough?)
# TO-DO: update bkg_array with Spectrum alternative (is bkg_image enough?)
bkg_array = deprecated_attribute("bkg_array", "1.3")

def __post_init__(self):
Expand Down Expand Up @@ -303,12 +303,18 @@

Returns
-------
`~specutils.Spectrum1D` object with same shape as ``image``.
spec : `~specutils.Spectrum1D`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Sphinx ref will have to be updated separately after specutils 2.0 stable is released and RTD picks up specutils 2.0. Might have to do a special requirements file for doc build or pin specutils>=2 here:

docs = [

But nothing we can do right now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ignore my other comment, then.

Spectrum object with same shape as ``image``.
"""
image = self._parse_image(image)
return Spectrum1D(
np.tile(self._bkg_array, (image.shape[0], 1)) * image.unit,
spectral_axis=image.spectral_axis,
arr = np.tile(self._bkg_array, (image.shape[0], 1))
if SPECUTILS_LT_2:
kwargs = {}
else:
kwargs = {"spectral_axis_index": arr.ndim - 1}

Check warning on line 314 in specreduce/background.py

View check run for this annotation

Codecov / codecov/patch

specreduce/background.py#L314

Added line #L314 was not covered by tests
return Spectrum(
arr * image.unit,
spectral_axis=image.spectral_axis, **kwargs
)

def bkg_spectrum(self, image=None, bkg_statistic="sum"):
Expand Down Expand Up @@ -355,7 +361,7 @@
# can't collapse with a spectral axis in pixels because
# SpectralCoord only allows frequency/wavelength equivalent units...
ext1d = statistic_function(bkg_image.flux, axis=self.crossdisp_axis)
return Spectrum1D(ext1d, bkg_image.spectral_axis)
return Spectrum(ext1d, bkg_image.spectral_axis)

def sub_image(self, image=None):
"""
Expand All @@ -369,15 +375,17 @@

Returns
-------
`~specutils.Spectrum1D` object with same shape as ``image``.
spec : `~specutils.Spectrum1D`
Spectrum object with same shape as ``image``.
"""
image = self._parse_image(image)

# a compare_wcs argument is needed for Spectrum1D.subtract() in order to
# a compare_wcs argument is needed for Spectrum.subtract() in order to
# avoid a TypeError from SpectralCoord when image's spectral axis is in
# pixels. it is not needed when image's spectral axis has physical units
kwargs = {"compare_wcs": None} if image.spectral_axis.unit == u.pix else {}

if not SPECUTILS_LT_2:
kwargs["spectral_axis_index"] = image.flux.ndim - 1

Check warning on line 388 in specreduce/background.py

View check run for this annotation

Codecov / codecov/patch

specreduce/background.py#L388

Added line #L388 was not covered by tests
# https://docs.astropy.org/en/stable/nddata/mixins/ndarithmetic.html
return image.subtract(self.bkg_image(image), **kwargs)

Expand Down Expand Up @@ -406,7 +414,7 @@
# can't collapse with a spectral axis in pixels because
# SpectralCoord only allows frequency/wavelength equivalent units...
ext1d = np.nansum(sub_image.flux, axis=self.crossdisp_axis)
return Spectrum1D(ext1d, spectral_axis=sub_image.spectral_axis)
return Spectrum(ext1d, spectral_axis=sub_image.spectral_axis)

Check warning on line 417 in specreduce/background.py

View check run for this annotation

Codecov / codecov/patch

specreduce/background.py#L417

Added line #L417 was not covered by tests

def __rsub__(self, image):
"""
Expand Down
16 changes: 8 additions & 8 deletions specreduce/calibration_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from astropy.utils.data import download_file
from astropy.utils.exceptions import AstropyUserWarning
from astropy.coordinates import SpectralCoord

from specutils import Spectrum1D
from specutils.utils.wcs_utils import vac_to_air

from specreduce.compat import Spectrum

__all__ = [
'get_available_line_catalogs',
'load_pypeit_calibration_lines',
Expand Down Expand Up @@ -187,7 +187,7 @@ def load_MAST_calspec(
filename: str | Path,
cache: bool | Literal['update'] = True,
show_progress: bool = False
) -> Spectrum1D | None:
) -> Spectrum | None:
"""
Load a standard star spectrum from the ``calspec`` database at MAST. These spectra are provided
in FITS format and are described in detail at:
Expand Down Expand Up @@ -238,7 +238,7 @@ def load_MAST_calspec(
# supported directly by astropy.units. mJy is chosen since it's the JWST
# standard and can easily be converted to/from AB magnitudes.
flux_mjy = synphot.units.convert_flux(wave, flux, u.mJy)
spectrum = Spectrum1D(spectral_axis=wave, flux=flux_mjy)
spectrum = Spectrum(spectral_axis=wave, flux=flux_mjy)
return spectrum


Expand All @@ -247,7 +247,7 @@ def load_onedstds(
specfile: str = "EG131.dat",
cache: bool | Literal['update'] = True,
show_progress: bool = False
) -> Spectrum1D | None:
) -> Spectrum | None:
"""
This is a convenience function for loading a standard star spectrum from the 'onedstds'
dataset in the ``specreduce_data`` package. They will be downloaded from the
Expand Down Expand Up @@ -290,11 +290,11 @@ def load_onedstds(
# the specreduce_data standard star spectra all provide fluxes in AB mag
flux = t['ABmag'].data * u.ABmag
flux = flux.to(u.mJy) # convert to linear flux units
spectrum = Spectrum1D(spectral_axis=spectral_axis, flux=flux)
spectrum = Spectrum(spectral_axis=spectral_axis, flux=flux)
return spectrum


class AtmosphericExtinction(Spectrum1D):
class AtmosphericExtinction(Spectrum):
"""
Spectrum container for atmospheric extinction in magnitudes as a function of wavelength.
If extinction and spectral_axis are provided, this will use them to build a custom model.
Expand Down Expand Up @@ -353,7 +353,7 @@ def __init__(
extinction = u.Magnitude(
extinction,
u.MagUnit(u.dimensionless_unscaled)
).to(u.dimensionless_unscaled) # Spectrum1D wants this to be linear
).to(u.dimensionless_unscaled) # Spectrum wants this to be linear
elif isinstance(extinction, (u.LogUnit, u.Magnitude)) or extinction.unit == u.mag:
# if in log or magnitudes, recast into Magnitude with dimensionless physical units
extinction = u.Magnitude(
Expand Down
11 changes: 11 additions & 0 deletions specreduce/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import specutils
from astropy.utils import minversion

__all__ = []

SPECUTILS_LT_2 = not minversion(specutils, "2.0.dev")

if SPECUTILS_LT_2:
from specutils import Spectrum1D as Spectrum
else:
from specutils import Spectrum # noqa: F401

Check warning on line 11 in specreduce/compat.py

View check run for this annotation

Codecov / codecov/patch

specreduce/compat.py#L11

Added line #L11 was not covered by tests
Loading
Loading