Skip to content

Commit b5ef5ca

Browse files
authored
Merge branch 'main' into update/pre-commit
2 parents b674b07 + a952a61 commit b5ef5ca

File tree

9 files changed

+390
-63
lines changed

9 files changed

+390
-63
lines changed

changes/9570.pipeline.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use ``read_metadata`` where possible in calwebb_image3; result is a few percent runtime improvement if in_memory=False

jwst/assign_mtwcs/moving_target_wcs.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,12 @@ def assign_moving_target_wcs(input_models):
5151
mt_valid = True
5252
with input_models:
5353
for i in ind:
54-
model = input_models.borrow(i)
55-
if model.meta.wcsinfo.mt_ra is None or model.meta.wcsinfo.mt_dec is None:
56-
mt_valid = False
57-
else:
58-
mt_ra[i] = model.meta.wcsinfo.mt_ra
59-
mt_dec[i] = model.meta.wcsinfo.mt_dec
60-
61-
if mt_valid and isinstance(model, datamodels.MultiSlitModel):
62-
for slit in model.slits:
63-
if slit.meta.wcsinfo.mt_ra is None or slit.meta.wcsinfo.mt_dec is None:
64-
mt_valid = False
65-
66-
input_models.shelve(model, i, modify=False)
54+
meta = input_models.read_metadata(i, flatten=False)
55+
mt_valid = _is_mt_meta_valid(meta)
56+
if not mt_valid:
57+
break
58+
mt_ra[i] = meta["meta"]["wcsinfo"]["mt_ra"]
59+
mt_dec[i] = meta["meta"]["wcsinfo"]["mt_dec"]
6760

6861
# Compute the mean MT RA/Dec over all exposures
6962
if not mt_valid:
@@ -148,3 +141,37 @@ def add_mt_frame(wcs, ra_average, dec_average, mt_ra, mt_dec):
148141
pipeline.append((mt, None))
149142
new_wcs = WCS(pipeline)
150143
return new_wcs
144+
145+
146+
def _is_mt_meta_valid(meta):
147+
"""
148+
Check if the metadata contains valid moving target RA/DEC.
149+
150+
Checks both the top-level wcsinfo as well as the wcsinfo for all
151+
the slits in a MultiSlitModel.
152+
153+
Parameters
154+
----------
155+
meta : dict
156+
Nested metadata dictionary from a data model, as output from `read_metadata`
157+
with `flatten=False`.
158+
159+
Returns
160+
-------
161+
bool
162+
True if valid, False otherwise.
163+
"""
164+
# check all the slits
165+
for slit in meta.get("slits", []):
166+
if (
167+
slit["meta"]["wcsinfo"].get("mt_ra", None) is None
168+
or slit["meta"]["wcsinfo"].get("mt_dec", None) is None
169+
):
170+
return False
171+
# check the top-level wcsinfo
172+
if (
173+
meta["meta"]["wcsinfo"].get("mt_ra", None) is None
174+
or meta["meta"]["wcsinfo"].get("mt_dec", None) is None
175+
):
176+
return False
177+
return True

jwst/datamodels/library.py

Lines changed: 160 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import warnings
22
from pathlib import Path
3+
import numpy as np
4+
from datetime import datetime
5+
from astropy.time import Time
6+
from asdf.tags.core import NDArrayType
7+
38
from stdatamodels.jwst.datamodels.util import open as datamodels_open
49
from stdatamodels.jwst.datamodels import read_metadata
5-
from stpipe.library import AbstractModelLibrary, NoGroupID
10+
from stpipe.library import AbstractModelLibrary, NoGroupID, BorrowError
611

712
from jwst.associations import AssociationNotValidError, load_asn
813
from jwst.datamodels.utils import attrs_to_group_id
@@ -218,25 +223,161 @@ def get_crds_parameters(self):
218223
)
219224
idx = 0
220225

221-
# find model in _loaded_models, temp_filenames, or asn_dir
222-
if self._on_disk:
223-
if idx in self._temp_filenames:
224-
# if model has been modified, find its temp filename
225-
filename = self._temp_filenames[idx]
226-
else:
227-
# otherwise, find the filename in the asn_dir
228-
member = self._members[idx]
229-
filename = Path(self._asn_dir) / member["expname"]
226+
return self.read_metadata(idx)
227+
228+
def read_metadata(self, idx, flatten=True):
229+
"""
230+
Read metadata for a model at the given index.
231+
232+
Parameters
233+
----------
234+
idx : int
235+
Index of the model in the library.
236+
237+
Returns
238+
-------
239+
dict
240+
The metadata dictionary for the model.
241+
"""
242+
# if model was already borrowed, calling code should just read the metadata
243+
# from the model itself, so we raise an error here
244+
if idx in self._ledger:
245+
raise BorrowError("Attempt to read metadata from model that is already borrowed")
246+
247+
# find model in _loaded_models, temp_filenames, or asn_dir
248+
if self._on_disk:
249+
if idx in self._temp_filenames:
250+
# if model has been modified, find its temp filename
251+
filename = self._temp_filenames[idx]
230252
else:
231-
if idx in self._loaded_models:
232-
# if this model is in memory, retrieve parameters from it directly
233-
model = self._loaded_models[idx]
234-
return model.get_crds_parameters()
235-
else:
236-
# otherwise, find the filename in the asn_dir
237-
member = self._members[idx]
238-
filename = Path(self._asn_dir) / member["expname"]
253+
# otherwise, find the filename in the asn_dir
254+
member = self._members[idx]
255+
filename = Path(self._asn_dir) / member["expname"]
256+
else:
257+
if idx in self._loaded_models:
258+
# if this model is in memory, retrieve parameters from it directly
259+
model = self._loaded_models[idx]
260+
return _read_meta_from_open_model(model, flatten=flatten)
261+
else:
262+
# otherwise, find the filename in the asn_dir
263+
member = self._members[idx]
264+
filename = Path(self._asn_dir) / member["expname"]
239265

240-
meta = read_metadata(filename, flatten=True)
266+
meta = read_metadata(filename, flatten=flatten)
267+
meta = self._assign_member_to_meta(meta, self._members[idx], flatten)
268+
return meta
241269

270+
def _assign_member_to_meta(self, meta, member, flatten):
271+
"""
272+
Update meta dict with asn-related attributes, similar to _assign_member_to_model.
273+
274+
Parameters
275+
----------
276+
meta : dict
277+
The metadata dictionary to update.
278+
member : dict
279+
The member dictionary containing association attributes.
280+
flatten : bool
281+
If True, the metadata will be flattened to a single level.
282+
If False, the metadata will be nested.
283+
284+
Returns
285+
-------
286+
dict
287+
The updated metadata dictionary with association attributes.
288+
"""
289+
if flatten:
290+
meta["meta.asn.exptype"] = member["exptype"]
291+
for attr in ("group_id", "tweakreg_catalog"):
292+
if attr in member:
293+
meta["meta." + attr] = member[attr]
294+
if "table_name" in self.asn.keys():
295+
meta["meta.asn.table_name"] = self.asn["table_name"]
296+
if "asn_pool" in self.asn.keys():
297+
meta["meta.asn.pool_name"] = self.asn["asn_pool"]
298+
else:
299+
meta["meta"].setdefault("asn", {})
300+
meta["meta"]["asn"]["exptype"] = member["exptype"]
301+
for attr in ("group_id", "tweakreg_catalog"):
302+
if attr in member:
303+
meta["meta"][attr] = member[attr]
304+
if "table_name" in self.asn.keys():
305+
meta["meta"]["asn"]["table_name"] = self.asn["table_name"]
306+
if "asn_pool" in self.asn.keys():
307+
meta["meta"]["asn"]["pool_name"] = self.asn["asn_pool"]
242308
return meta
309+
310+
311+
def _read_meta_from_open_model(model, flatten):
312+
"""
313+
Read metadata from an open model.
314+
315+
Parameters
316+
----------
317+
model : DataModel
318+
The model from which to read metadata.
319+
flatten : bool
320+
If True, the metadata will be flattened to a single level.
321+
If False, the metadata will be nested.
322+
323+
Returns
324+
-------
325+
dict
326+
The metadata dictionary for the model.
327+
"""
328+
if flatten:
329+
return model.get_crds_parameters()
330+
331+
def convert_val(val):
332+
if isinstance(val, datetime):
333+
return val.isoformat()
334+
elif isinstance(val, Time):
335+
return str(val)
336+
return val
337+
338+
def recurse(tree):
339+
"""
340+
Walk the tree and add metadata to a new dictionary.
341+
342+
Do all conversions and exclusions model.get_crds_parameters does,
343+
but without flattening.
344+
345+
Parameters
346+
----------
347+
tree : dict
348+
An asdf-like nested dictionary structure, e.g. model._instance.
349+
350+
Returns
351+
-------
352+
dict
353+
A new nested dictionary containing the metadata.
354+
"""
355+
new_tree = {}
356+
for key, val in tree.items():
357+
if key == "wcs":
358+
continue
359+
val = convert_val(val)
360+
if isinstance(val, dict):
361+
new_tree[key] = recurse(val)
362+
elif isinstance(val, list):
363+
new_list = []
364+
for item in val:
365+
if isinstance(item, dict):
366+
new_list.append(recurse(item))
367+
elif isinstance(item, list):
368+
# handle nested lists
369+
new_list.append(recurse({"_idx": item})["_idx"]) # placeholder key
370+
elif isinstance(item, (np.ndarray, NDArrayType)):
371+
continue
372+
else:
373+
new_list.append(convert_val(item))
374+
new_tree[key] = new_list
375+
elif isinstance(val, (np.ndarray, NDArrayType)):
376+
continue
377+
elif isinstance(val, (str, int, float, complex, bool)):
378+
new_tree[key] = val
379+
else:
380+
continue # skip unsupported types
381+
return new_tree
382+
383+
return recurse(model._instance) # noqa: SLF001

0 commit comments

Comments
 (0)