Skip to content

Commit a14ca3b

Browse files
committed
Update for PSF datamodel changes
1 parent c23cae1 commit a14ca3b

4 files changed

Lines changed: 81 additions & 60 deletions

File tree

jwst/extract_1d/psf_profile.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,43 @@
1818
NOD_PAIR_PATTERN = ["ALONG-SLIT-NOD", "2-POINT-NOD"]
1919

2020

21-
def open_psf(psf_refname, exp_type):
21+
def open_psf(psf_refname, slit_name):
2222
"""
2323
Open the PSF reference file.
2424
2525
Parameters
2626
----------
2727
psf_refname : str
2828
The name of the psf reference file.
29-
exp_type : str
30-
The exposure type of the data.
29+
slit_name : str or None
30+
The slit name for the data.
3131
3232
Returns
3333
-------
3434
psf_model : SpecPsfModel
3535
Returns the EPSF model.
3636
"""
37-
if exp_type == "MIR_LRS-FIXEDSLIT":
38-
# The information we read in from PSF file is:
39-
# center_col: psf_model.meta.psf.center_col
40-
# super sample factor: psf_model.meta.psf.subpix)
41-
# psf : psf_model.data (2d)
42-
# wavelength of PSF planes: psf_model.wave
37+
try:
4338
psf_model = SpecPsfModel(psf_refname)
39+
except (ValueError, AttributeError):
40+
raise NotImplementedError(
41+
f"PSF file {psf_refname} could not be read as SpecPsfModel."
42+
) from None
4443

45-
else:
46-
# So far, only MIRI LRS has a PSF datamodel defined. For any other
47-
# exposure type, try to use the model MIRI LRS uses to open the input model
48-
try:
49-
psf_model = SpecPsfModel(psf_refname)
50-
except (ValueError, AttributeError):
51-
raise NotImplementedError(
52-
f"PSF file for EXP_TYPE {exp_type} could not be read as SpecPsfModel."
53-
) from None
54-
return psf_model
44+
# Get the right PSF aperture
45+
slit_name = str(slit_name).upper()
46+
psf_aperture = None
47+
for aperture in psf_model.apertures:
48+
aper_name = str(aperture.name).upper()
49+
if aper_name in ["NONE", "ANY"] or aper_name == slit_name:
50+
psf_aperture = aperture
51+
break
52+
psf_model.close()
53+
54+
if psf_aperture is None:
55+
raise ValueError(f"No matching aperture found in SpecPsfModel for slit={slit_name}")
56+
57+
return psf_aperture
5558

5659

5760
def _normalize_profile(profile, dispaxis):
@@ -253,9 +256,10 @@ def psf_profile(
253256
For PSF profiles, this is always set to the upper edge of the bounding box,
254257
since the full array may have non-zero weight.
255258
"""
256-
# Read in reference files
259+
# Read in reference file
257260
exp_type = input_model.meta.exposure.type
258-
psf_model = open_psf(psf_ref_name, exp_type)
261+
slit_name = getattr(input_model, "name", None)
262+
psf_model = open_psf(psf_ref_name, slit_name)
259263

260264
# Get the data cutout
261265
data_shape = input_model.data.shape[-2:]
@@ -343,14 +347,14 @@ def psf_profile(
343347

344348
# Scale the trace location to the subsampled psf and
345349
# add the wavelength and spatial shifts to the coordinates to map to
346-
psf_subpix = psf_model.meta.psf.subpix
350+
psf_subpix = psf_model.subpix
347351
psf_location = trace - bbox[0][0]
348352
if dispaxis == HORIZONTAL:
349-
psf_shift = psf_model.meta.psf.center_row - (psf_location * psf_subpix)
353+
psf_shift = psf_model.center_row - (psf_location * psf_subpix)
350354
xidx = wave_idx
351355
yidx = _y * psf_subpix + psf_shift
352356
else:
353-
psf_shift = psf_model.meta.psf.center_col - (psf_location * psf_subpix)
357+
psf_shift = psf_model.center_col - (psf_location * psf_subpix)
354358
xidx = _x * psf_subpix + psf_shift[:, None]
355359
yidx = wave_idx
356360

jwst/extract_1d/tests/conftest.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,12 @@ def psf_reference():
425425
The mock model.
426426
"""
427427
psf_model = dm.SpecPsfModel()
428-
psf_model.data = np.ones((50, 50), dtype=float)
429-
psf_model.wave = np.linspace(0, 10, 50)
430-
psf_model.meta.psf.subpix = 1.0
431-
psf_model.meta.psf.center_col = 25
432-
psf_model.meta.psf.center_row = 25
428+
psf_model.apertures.append({})
429+
psf_model.apertures[0].data = np.ones((50, 50), dtype=float)
430+
psf_model.apertures[0].wave = np.linspace(0, 10, 50)
431+
psf_model.apertures[0].subpix = 1.0
432+
psf_model.apertures[0].center_col = 25
433+
psf_model.apertures[0].center_row = 25
433434
yield psf_model
434435
psf_model.close()
435436

@@ -460,13 +461,14 @@ def psf_reference_with_source():
460461
The mock model.
461462
"""
462463
psf_model = dm.SpecPsfModel()
463-
psf_model.data = np.full((50, 50), 1e-6)
464-
psf_model.data[:, 24:27] += 1.0
465-
466-
psf_model.wave = np.linspace(0, 10, 50)
467-
psf_model.meta.psf.subpix = 1.0
468-
psf_model.meta.psf.center_col = 25
469-
psf_model.meta.psf.center_row = 25
464+
psf_model.apertures.append({})
465+
psf_model.apertures[0].data = np.full((50, 50), 1e-6)
466+
psf_model.apertures[0].data[:, 24:27] += 1.0
467+
468+
psf_model.apertures[0].wave = np.linspace(0, 10, 50)
469+
psf_model.apertures[0].subpix = 1.0
470+
psf_model.apertures[0].center_col = 25
471+
psf_model.apertures[0].center_row = 25
470472
yield psf_model
471473
psf_model.close()
472474

jwst/extract_1d/tests/test_psf_profile.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55
from jwst.extract_1d import psf_profile as pp
66

77

8-
@pytest.mark.parametrize("exp_type", ["MIR_LRS-FIXEDSLIT", "NRS_FIXEDSLIT", "UNKNOWN"])
9-
def test_open_psf(psf_reference_file, exp_type):
10-
# for any exptype, a model that can be read
11-
# as SpecPsfModel will be, since it's the only
12-
# one implemented so far
13-
with pp.open_psf(psf_reference_file, exp_type=exp_type) as model:
14-
assert isinstance(model, SpecPsfModel)
8+
@pytest.mark.parametrize("slit_name", [None, "ANY", "UNKNOWN"])
9+
def test_open_psf(psf_reference_file, slit_name):
10+
# for any slit name passed in, the first aperture will be matched,
11+
# since there's only one available and it has no specific slit name
12+
aperture = pp.open_psf(psf_reference_file, slit_name)
13+
assert aperture.data.size != 0
14+
assert aperture.wave.size != 0
1515

1616

1717
def test_open_psf_fail():
1818
with pytest.raises(NotImplementedError, match="could not be read"):
1919
pp.open_psf("bad_file", "UNKNOWN")
2020

2121

22+
def test_open_psf_no_aperture_found(psf_reference_file):
23+
# If no PSF aperture matches the slit, an error is raised
24+
with SpecPsfModel(psf_reference_file) as psf_model:
25+
psf_model.apertures[0].name = "GOOD_SLIT"
26+
with pytest.raises(ValueError, match="No matching aperture"):
27+
pp.open_psf(psf_model, "BAD_SLIT")
28+
29+
2230
@pytest.mark.parametrize("dispaxis", [1, 2])
2331
def test_normalize_profile(nod_profile, dispaxis):
2432
profile = 2 * nod_profile
@@ -42,11 +50,12 @@ def test_normalize_profile_with_nans(nod_profile, dispaxis):
4250

4351
@pytest.mark.parametrize("dispaxis", [1, 2])
4452
def test_make_cutout_profile_default(psf_reference, dispaxis):
45-
data_shape = psf_reference.data.shape
53+
psf_aper = psf_reference.apertures[0]
54+
data_shape = psf_aper.data.shape
4655
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
4756

48-
psf_subpix = psf_reference.meta.psf.subpix
49-
profiles = pp._make_cutout_profile(xidx, yidx, psf_subpix, psf_reference.data, dispaxis)
57+
psf_subpix = psf_aper.subpix
58+
profiles = pp._make_cutout_profile(xidx, yidx, psf_subpix, psf_aper.data, dispaxis)
5059
assert len(profiles) == 1
5160
assert profiles[0].shape == data_shape
5261

@@ -60,12 +69,13 @@ def test_make_cutout_profile_default(psf_reference, dispaxis):
6069
@pytest.mark.parametrize("dispaxis", [1, 2])
6170
@pytest.mark.parametrize("extra_shift", [1, 2])
6271
def test_make_cutout_profile_shift_down(psf_reference, dispaxis, extra_shift):
63-
data_shape = psf_reference.data.shape
72+
psf_aper = psf_reference.apertures[0]
73+
data_shape = psf_aper.data.shape
6474
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
65-
psf_subpix = psf_reference.meta.psf.subpix
75+
psf_subpix = psf_aper.subpix
6676

6777
profiles = pp._make_cutout_profile(
68-
xidx, yidx, psf_subpix, psf_reference.data, dispaxis, extra_shift=extra_shift
78+
xidx, yidx, psf_subpix, psf_aper.data, dispaxis, extra_shift=extra_shift
6979
)
7080
assert len(profiles) == 1
7181
assert profiles[0].shape == data_shape
@@ -84,12 +94,13 @@ def test_make_cutout_profile_shift_down(psf_reference, dispaxis, extra_shift):
8494
@pytest.mark.parametrize("dispaxis", [1, 2])
8595
@pytest.mark.parametrize("extra_shift", [-1, -2])
8696
def test_make_cutout_profile_shift_up(psf_reference, dispaxis, extra_shift):
87-
data_shape = psf_reference.data.shape
97+
psf_aper = psf_reference.apertures[0]
98+
data_shape = psf_aper.data.shape
8899
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
89-
psf_subpix = psf_reference.meta.psf.subpix
100+
psf_subpix = psf_aper.subpix
90101

91102
profiles = pp._make_cutout_profile(
92-
xidx, yidx, psf_subpix, psf_reference.data, dispaxis, extra_shift=extra_shift
103+
xidx, yidx, psf_subpix, psf_aper.data, dispaxis, extra_shift=extra_shift
93104
)
94105
assert len(profiles) == 1
95106
assert profiles[0].shape == data_shape
@@ -107,13 +118,14 @@ def test_make_cutout_profile_shift_up(psf_reference, dispaxis, extra_shift):
107118

108119
@pytest.mark.parametrize("dispaxis", [1, 2])
109120
def test_make_cutout_profile_with_nod(psf_reference, dispaxis):
110-
data_shape = psf_reference.data.shape
121+
psf_aper = psf_reference.apertures[0]
122+
data_shape = psf_aper.data.shape
111123
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
112-
psf_subpix = psf_reference.meta.psf.subpix
124+
psf_subpix = psf_aper.subpix
113125

114126
offset = 2
115127
profiles = pp._make_cutout_profile(
116-
xidx, yidx, psf_subpix, psf_reference.data, dispaxis, nod_offset=offset
128+
xidx, yidx, psf_subpix, psf_aper.data, dispaxis, nod_offset=offset
117129
)
118130
assert len(profiles) == 2
119131
source, nod = profiles
@@ -138,9 +150,10 @@ def test_make_cutout_profile_with_nod(psf_reference, dispaxis):
138150

139151
@pytest.mark.parametrize("dispaxis", [1, 2])
140152
def test_profile_residual(psf_reference, dispaxis):
153+
psf_aper = psf_reference.apertures[0]
141154
data_shape = (50, 50)
142155
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
143-
psf_subpix = psf_reference.meta.psf.subpix
156+
psf_subpix = psf_aper.subpix
144157

145158
# Set data to all ones, so residual should be zero
146159
# when background is not fit
@@ -149,16 +162,17 @@ def test_profile_residual(psf_reference, dispaxis):
149162

150163
param = [0, None]
151164
residual = pp._profile_residual(
152-
param, data, var, xidx, yidx, psf_subpix, psf_reference.data, dispaxis, fit_bkg=False
165+
param, data, var, xidx, yidx, psf_subpix, psf_aper.data, dispaxis, fit_bkg=False
153166
)
154167
assert np.isclose(residual, 0.0)
155168

156169

157170
@pytest.mark.parametrize("dispaxis", [1, 2])
158171
def test_profile_residual_with_bkg(psf_reference, dispaxis):
172+
psf_aper = psf_reference.apertures[0]
159173
data_shape = (50, 50)
160174
yidx, xidx = np.mgrid[: data_shape[0], : data_shape[1]]
161-
psf_subpix = psf_reference.meta.psf.subpix
175+
psf_subpix = psf_aper.subpix
162176

163177
# Set data to all ones, so it is all background - residual
164178
# should be all of the data
@@ -167,7 +181,7 @@ def test_profile_residual_with_bkg(psf_reference, dispaxis):
167181

168182
param = [0, None]
169183
residual = pp._profile_residual(
170-
param, data, var, xidx, yidx, psf_subpix, psf_reference.data, dispaxis, fit_bkg=True
184+
param, data, var, xidx, yidx, psf_subpix, psf_aper.data, dispaxis, fit_bkg=True
171185
)
172186
assert np.isclose(residual, np.sum(data**2 / var))
173187

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ dependencies = [
3030
"scikit-image>=0.21.0",
3131
"scipy>=1.14.1",
3232
# "stdatamodels>=5.0.1,<6",
33-
"stdatamodels @ git+https://github.com/spacetelescope/stdatamodels.git@main",
33+
# "stdatamodels @ git+https://github.com/spacetelescope/stdatamodels.git@main",
34+
"stdatamodels @ git+https://github.com/melanieclarke/stdatamodels.git@jp-4273",
3435
"stcal>=1.18,<2",
3536
"stpipe>=0.12,<2",
3637
"synphot>=1.3",

0 commit comments

Comments
 (0)