Skip to content

Commit bc009bd

Browse files
authored
Merge pull request #172 from rchwn/my-changes
blotting in lv3, flags for blotting + drizzling
2 parents 6186fcb + b89eddd commit bc009bd

2 files changed

Lines changed: 191 additions & 50 deletions

File tree

docs/steps/lv3.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@ For more details, see the `official documentation <https://jwst-pipeline.readthe
88
If you have background observations, you should ensure these are being correctly picked up by using ``bgr_check_type``
99
and ``bgr_background_name``.
1010

11+
After the main pipeline run, individual exposures can optionally be drizzled onto the
12+
mosaic WCS (boolean ``do_drizzle`` flag) and then blotted back to a reference detector frame
13+
(boolean ``do_blot`` flag). This is useful for per-exposure comparisons in pixel space. Resample
14+
parameters are read from ``jwst_parameters["resample"]``; blotting behaviour is
15+
controlled by ``blot_ref_index`` and ``blot_fillval``. In your config file:
16+
17+
Example 1: Just drizzle the individual exposures onto the mosaic WCS. No blotting.
18+
.. code-block:: toml
19+
20+
[parameters.lv3]
21+
do_drizzle = true
22+
do_blot = false
23+
24+
Example 2: Drizzle the individual exposures onto the mosaic WCS and then blot back to the reference detector frame
25+
.. code-block:: toml
26+
27+
[parameters.lv3]
28+
# You can also set do_drizzle = true here, but not necessary.
29+
# You can not set do_blot = true while setting do_drizzle = false (you'll get an error)
30+
do_blot = true
31+
blot_ref_index = 0
32+
blot_fillval = np.nan
33+
1134
---
1235
API
1336
---

pjpipe/lv3/lv3_step.py

Lines changed: 168 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import time
99

1010
import asdf
11+
import numpy as np
1112
import jwst
1213
from jwst.datamodels import ModelContainer
1314
from jwst.resample import ResampleStep
15+
from jwst.resample.resample import compute_image_pixel_area
16+
from stcal.outlier_detection.utils import gwcs_blot
1417

1518
# FIXME For newer JWST versions, import ModelLibrary
1619
try:
@@ -66,6 +69,10 @@ def __init__(
6669
bgr_background_name="off",
6770
process_bgr_like_science=False,
6871
jwst_parameters=None,
72+
do_drizzle=None,
73+
do_blot=False,
74+
blot_ref_index=0,
75+
blot_fillval=np.nan,
6976
overwrite=False,
7077
):
7178
"""Wrapper around the level 3 JWST pipeline
@@ -113,6 +120,22 @@ def __init__(
113120
jwst_parameters: Parameter dictionary to pass to
114121
the level 2 pipeline. Defaults to None,
115122
which will run the observatory defaults
123+
do_drizzle: If True, drizzle individual frames
124+
to the i2d mosaic WCS after the main
125+
pipeline run. Note: creates a lot of files.
126+
If not set (None) and do_blot is True,
127+
do_drizzle is automatically enabled. If
128+
explicitly set to False while do_blot is
129+
True, a ValueError is raised. Defaults to
130+
None.
131+
do_blot: If True, blot each resampled exposure
132+
back to a reference detector frame (requires
133+
drizzling). Defaults to False.
134+
blot_ref_index: Index into the alphabetically sorted CRF file list of
135+
the reference exposure whose detector frame
136+
is used for blotting. Defaults to 0.
137+
blot_fillval: Fill value for pixels outside the
138+
blotted footprint. Defaults to np.nan
116139
overwrite: Whether to overwrite or not. Defaults
117140
to False
118141
"""
@@ -152,6 +175,18 @@ def __init__(
152175
self.bgr_background_name = bgr_background_name
153176
self.process_bgr_like_science = process_bgr_like_science
154177
self.jwst_parameters = jwst_parameters
178+
if do_drizzle is False and do_blot:
179+
raise ValueError(
180+
"do_drizzle was explicitly set to False, but do_blot "
181+
"was set to True. Either set (do_drizzle, do_blot) to "
182+
"(True, True) or (True, False), or (None, True)."
183+
)
184+
if do_drizzle is None:
185+
do_drizzle = bool(do_blot)
186+
self.do_drizzle = do_drizzle
187+
self.do_blot = do_blot
188+
self.blot_ref_index = blot_ref_index
189+
self.blot_fillval = blot_fillval
155190
self.overwrite = overwrite
156191

157192
self.band_type = get_band_type(self.band)
@@ -608,60 +643,143 @@ def run_step(
608643
gc.collect()
609644

610645
# Drizzle individual frames to the common mosaic WCS
611-
i2d_files = glob.glob(os.path.join(self.out_dir, "*_i2d.fits"))
612-
crf_files = sorted(glob.glob(os.path.join(self.out_dir, "*_crf.fits")))
613-
614-
if i2d_files and crf_files:
615-
ref_wcs_file = os.path.join(self.out_dir, "resample_refwcs.asdf")
616-
with datamodels.open(i2d_files[0]) as i2d_model:
617-
asdf.AsdfFile({
618-
"wcs": i2d_model.meta.wcs,
619-
"array_shape": i2d_model.data.shape,
620-
}).write_to(ref_wcs_file)
621-
622-
resample_single = ResampleStep()
623-
resample_single.single = True
624-
resample_single.output_use_model = True
625-
resample_single.save_results = True
626-
resample_single.output_dir = self.out_dir
627-
resample_single.output_wcs = ref_wcs_file
628-
629-
try:
630-
resample_params = self.jwst_parameters["resample"]
631-
except KeyError:
632-
resample_params = {}
633-
634-
wcs_keys = {
635-
"output_wcs", "crval", "crpix", "rotation",
636-
"pixel_scale", "pixel_scale_ratio", "output_shape",
637-
}
638-
for resample_key in resample_params:
639-
if resample_key in wcs_keys:
640-
continue
641-
value = parse_parameter_dict(
642-
parameters=resample_params,
643-
key=resample_key,
644-
band=self.band,
645-
target=self.target,
646-
)
647-
if value == "VAL_NOT_FOUND":
648-
continue
649-
recursive_setattr(resample_single, resample_key, value)
646+
if self.do_drizzle:
647+
i2d_files = glob.glob(os.path.join(self.out_dir, "*_i2d.fits"))
648+
crf_files = sorted(glob.glob(os.path.join(self.out_dir, "*_crf.fits")))
649+
650+
if i2d_files and crf_files:
651+
ref_wcs_file = os.path.join(self.out_dir, "resample_refwcs.asdf")
652+
with datamodels.open(i2d_files[0]) as i2d_model:
653+
asdf.AsdfFile({
654+
"wcs": i2d_model.meta.wcs,
655+
"array_shape": i2d_model.data.shape,
656+
}).write_to(ref_wcs_file)
657+
658+
resample_single = ResampleStep()
659+
resample_single.single = True
660+
resample_single.output_use_model = True
661+
resample_single.save_results = True
662+
resample_single.output_dir = self.out_dir
663+
resample_single.output_wcs = ref_wcs_file
664+
665+
try:
666+
resample_params = self.jwst_parameters["resample"]
667+
except KeyError:
668+
resample_params = {}
669+
670+
wcs_keys = {
671+
"output_wcs", "crval", "crpix", "rotation",
672+
"pixel_scale", "pixel_scale_ratio", "output_shape",
673+
}
674+
for resample_key in resample_params:
675+
if resample_key in wcs_keys:
676+
continue
677+
value = parse_parameter_dict(
678+
parameters=resample_params,
679+
key=resample_key,
680+
band=self.band,
681+
target=self.target,
682+
)
683+
if value == "VAL_NOT_FOUND":
684+
continue
685+
recursive_setattr(resample_single, resample_key, value)
686+
687+
resample_single.run(crf_files)
688+
689+
del resample_single
690+
gc.collect()
691+
692+
# Notes:
693+
# - ResampleImage.resample_many_to_many() adds _outlier_resamplestep.fits
694+
# to the file names, but it's not an outlier image, so let's rename it.
695+
# - We'd have to edit ResampleStep for a proper fix.
696+
# - Can't have suffix ending in _i2d.fits, messes with anchoring pattern matching.
697+
for f in glob.glob(os.path.join(self.out_dir, "*_outlier_resamplestep.fits")):
698+
os.rename(f, f.replace("_outlier_resamplestep.fits", "_i2d_single.fits"))
699+
700+
if self.do_blot:
701+
self._blot_to_detector_frame(
702+
crf_files=crf_files,
703+
ref_index=self.blot_ref_index,
704+
)
705+
else:
706+
log.warning("do_drizzle is set but no i2d/crf files found")
650707

651-
resample_single.run(crf_files)
708+
return True
652709

653-
del resample_single
654-
gc.collect()
710+
def _blot_to_detector_frame(
711+
self,
712+
crf_files,
713+
ref_index=0,
714+
):
715+
"""Blot resampled single-exposure images back to a detector frame.
655716
656-
# Notes:
657-
# - ResampleImage.resample_many_to_many() adds _outlier_resamplestep.fits
658-
# to the file names, but it's not an outlier image, so let's rename it.
659-
# - We'd have to edit ResampleStep for a proper fix.
660-
# - Can't have suffix ending in _i2d.fits, messes with anchoring pattern matching.
661-
for f in glob.glob(os.path.join(self.out_dir, "*_outlier_resamplestep.fits")):
662-
os.rename(f, f.replace("_outlier_resamplestep.fits", "_i2d_single.fits"))
717+
Each ``*_i2d_single.fits`` file (one per exposure, on the common
718+
i2d WCS grid) is blotted onto the detector frame of the chosen
719+
reference exposure. The output files are written as
720+
``*_blot_det.fits`` alongside the other lv3 products.
663721
664-
return True
722+
Args:
723+
crf_files: Sorted list of CRF file paths (one per exposure).
724+
Used to obtain the detector-frame GWCS and shape.
725+
ref_index: Index into *crf_files* of the reference exposure
726+
whose detector frame will be adopted. Defaults to 0
727+
(the first exposure).
728+
"""
729+
730+
single_files = sorted(
731+
glob.glob(os.path.join(self.out_dir, "*_i2d_single.fits"))
732+
)
733+
if not single_files:
734+
log.warning("_blot_to_detector_frame: no *_i2d_single.fits files found")
735+
return
736+
737+
if ref_index >= len(crf_files):
738+
log.warning(
739+
"_blot_to_detector_frame: ref_index %d out of range (%d exposures)",
740+
ref_index, len(crf_files),
741+
)
742+
return
743+
744+
log.info(
745+
"Blotting %d resampled exposures to detector frame of %s",
746+
len(single_files), os.path.basename(crf_files[ref_index]),
747+
)
748+
749+
with datamodels.open(crf_files[ref_index]) as ref_model:
750+
blot_wcs = ref_model.meta.wcs
751+
blot_shape = ref_model.data.shape
752+
753+
ref_pixflux_area = ref_model.meta.photometry.pixelarea_steradians
754+
blot_wcs.array_shape = blot_shape
755+
ref_pixel_area = compute_image_pixel_area(blot_wcs)
756+
pix_ratio = np.sqrt(ref_pixflux_area / ref_pixel_area)
757+
758+
for single_file in single_files:
759+
with datamodels.open(single_file) as single_model:
760+
blotted = gwcs_blot(
761+
median_data=single_model.data.astype(np.float32),
762+
median_wcs=single_model.meta.wcs,
763+
blot_shape=blot_shape,
764+
blot_wcs=blot_wcs,
765+
pix_ratio=pix_ratio,
766+
fillval=self.blot_fillval,
767+
)
768+
769+
out_name = single_file.replace("_i2d_single.fits", "_blot_det.fits")
770+
blot_model = datamodels.ImageModel(data=blotted)
771+
blot_model.update(ref_model)
772+
blot_model.meta.wcs = copy.deepcopy(blot_wcs)
773+
save_file(blot_model, out_name=out_name, dr_version=self.dr_version)
774+
blot_model.close()
775+
776+
log.info(
777+
" %s -> %s",
778+
os.path.basename(single_file),
779+
os.path.basename(out_name),
780+
)
781+
782+
gc.collect()
665783

666784
def propagate_metadata(self,
667785
files):

0 commit comments

Comments
 (0)