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

RCAL-895: allow updating source catalog with tweaked WCS when running ELP #1373

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
0.16.2 (unreleased)
===================

exposure_pipeline
-----------------
- Update source catalog file with tweaked coordinates. [#1373]

zacharyburnett marked this conversation as resolved.
Show resolved Hide resolved
pipeline
--------

- Added ``suffix`` to the spec of ExposurePipeline with a
default value of ``cal``. Removed explicit setting of ``suffix``
so that it can be passed as an argument to ``strun``. [#1378]
Expand Down
2 changes: 2 additions & 0 deletions romancal/pipeline/exposure_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@

# make sure source_catalog returns the updated datamodel
self.source_catalog.return_updated_model = True
# make sure we update source catalog coordinates afer running TweakRegStep
self.tweakreg.update_source_catalog_coordinates = True

Check warning on line 75 in romancal/pipeline/exposure_pipeline.py

View check run for this annotation

Codecov / codecov/patch

romancal/pipeline/exposure_pipeline.py#L75

Added line #L75 was not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like this attribute is set here and only here. Patching the step object like this is not a good idea.
It should be added to the step spec with a default value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mairan, Nadia and I chatted briefly about this earlier today, and I had gotten confused with the related self.source_catalog.return_updated_model above. Let's make this default False for now, but it could go into the spec I think?


log.info("Starting Roman exposure calibration pipeline ...")
if isinstance(input, str):
Expand Down
125 changes: 125 additions & 0 deletions romancal/tweakreg/tests/test_tweakreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from gwcs import coordinate_frames as cf
from gwcs import wcs
from gwcs.geometry import CartesianToSpherical, SphericalToCartesian
from numpy.random import default_rng
from roman_datamodels import datamodels as rdm
from roman_datamodels import maker_utils

Expand Down Expand Up @@ -1185,3 +1186,127 @@ def test_parse_catfile_raises_error_on_invalid_content(tmp_path, catfile_line_co
trs._parse_catfile(catfile)

assert type(exec_info.value) == ValueError


def test_update_source_catalog_coordinates(tmp_path, base_image):
"""Test that TweakReg updates the catalog coordinates with the tweaked WCS."""

os.chdir(tmp_path)

img = base_image(shift_1=1000, shift_2=1000)
add_tweakreg_catalog_attribute(tmp_path, img, catalog_filename="img_1")

tweakreg = trs.TweakRegStep()

# create SourceCatalogModel
source_catalog_model = setup_source_catalog_model(img)

# save SourceCatalogModel
tweakreg.save_model(
source_catalog_model,
output_file="img_1.asdf",
suffix="cat",
force=True,
)

# update tweakreg catalog name
img.meta.source_detection.tweakreg_catalog_name = "img_1_cat.asdf"

# run TweakRegStep
res = trs.TweakRegStep.call([img])

# tweak the current WCS using TweakRegStep and save the updated cat file
with res:
dm = res.borrow(0)
assert dm.meta.source_detection.tweakreg_catalog_name == "img_1_cat.asdf"
tweakreg.update_catalog_coordinates(
dm.meta.source_detection.tweakreg_catalog_name, dm.meta.wcs
)
res.shelve(dm, 0)

# read in saved catalog coords
cat = rdm.open("img_1_cat.asdf")
cat_ra_centroid = cat.source_catalog["ra_centroid"]
cat_dec_centroid = cat.source_catalog["dec_centroid"]
cat_ra_psf = cat.source_catalog["ra_psf"]
cat_dec_psf = cat.source_catalog["dec_psf"]

# calculate world coords using tweaked WCS
expected_centroid = img.meta.wcs.pixel_to_world(
cat.source_catalog["xcentroid"], cat.source_catalog["ycentroid"]
)
expected_psf = img.meta.wcs.pixel_to_world(
cat.source_catalog["x_psf"], cat.source_catalog["y_psf"]
)
mairanteodoro marked this conversation as resolved.
Show resolved Hide resolved

# compare coordinates (make sure tweaked WCS was applied to cat file coords)
np.testing.assert_array_equal(cat_ra_centroid, expected_centroid.ra.value)
np.testing.assert_array_equal(cat_dec_centroid, expected_centroid.dec.value)
np.testing.assert_array_equal(cat_ra_psf, expected_psf.ra.value)
np.testing.assert_array_equal(cat_dec_psf, expected_psf.dec.value)
mairanteodoro marked this conversation as resolved.
Show resolved Hide resolved


def setup_source_catalog_model(img):
"""
Set up the source catalog model.

Notes
-----
This function reads the source catalog from a file, renames columns to match
expected names, adds mock PSF coordinates, applies random shifts to the centroid
and PSF coordinates, and calculates the world coordinates for the centroids.
"""
cat_model = rdm.SourceCatalogModel
source_catalog_model = maker_utils.mk_datamodel(cat_model)
# this will be the output filename
source_catalog_model.meta.filename = "img_1.asdf"

# read in the mock table
source_catalog = Table.read("img_1", format="ascii.ecsv")
mairanteodoro marked this conversation as resolved.
Show resolved Hide resolved
# rename columns to match expected column names
source_catalog.rename_columns(["x", "y"], ["xcentroid", "ycentroid"])
# add mock PSF coordinates
source_catalog["x_psf"] = source_catalog["xcentroid"]
source_catalog["y_psf"] = source_catalog["ycentroid"]

# add random fraction of a pixel shifts to the centroid coordinates
rng = default_rng()
shift_x = rng.uniform(-0.5, 0.5, size=len(source_catalog))
shift_y = rng.uniform(-0.5, 0.5, size=len(source_catalog))
source_catalog["xcentroid"] += shift_x
source_catalog["ycentroid"] += shift_y
# add random fraction of a pixel shifts to the PSF coordinates
shift_x = rng.uniform(-0.5, 0.5, size=len(source_catalog))
shift_y = rng.uniform(-0.5, 0.5, size=len(source_catalog))
source_catalog["x_psf"] += shift_x
source_catalog["y_psf"] += shift_y

# calculate centroid world coordinates
centroid = img.meta.wcs.pixel_to_world(
source_catalog["xcentroid"],
source_catalog["ycentroid"],
)
# calculate PSF world coordinates
psf = img.meta.wcs.pixel_to_world(
source_catalog["x_psf"],
source_catalog["y_psf"],
)
# add world coordinates to catalog
source_catalog["ra_centroid"], source_catalog["dec_centroid"] = (
centroid.ra.deg,
centroid.dec.deg,
)
source_catalog["ra_psf"], source_catalog["dec_psf"] = (
psf.ra.deg,
psf.dec.deg,
)
# add units
source_catalog["ra_centroid"].unit = u.deg
source_catalog["dec_centroid"].unit = u.deg
source_catalog["ra_psf"].unit = u.deg
source_catalog["dec_psf"].unit = u.deg

# add source catalog to SourceCatalogModel
source_catalog_model.source_catalog = source_catalog

return source_catalog_model
129 changes: 94 additions & 35 deletions romancal/tweakreg/tweakreg_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,41 +162,7 @@
images.shelve(image_model)
return image_model

if hasattr(image_model.meta, "source_detection"):
is_tweakreg_catalog_present = hasattr(
image_model.meta.source_detection, "tweakreg_catalog"
)
is_tweakreg_catalog_name_present = hasattr(
image_model.meta.source_detection, "tweakreg_catalog_name"
)
if is_tweakreg_catalog_present:
# read catalog from structured array
catalog = Table(
np.asarray(
image_model.meta.source_detection.tweakreg_catalog
)
)
elif is_tweakreg_catalog_name_present:
catalog = self.read_catalog(
image_model.meta.source_detection.tweakreg_catalog_name
)
else:
images.shelve(image_model, i, modify=False)
raise AttributeError(
"Attribute 'meta.source_detection.tweakreg_catalog' is missing."
"Please either run SourceDetectionStep or provide a"
"custom source catalog."
)
# remove 4D numpy array from meta.source_detection
if is_tweakreg_catalog_present:
del image_model.meta.source_detection["tweakreg_catalog"]
else:
images.shelve(image_model, i, modify=False)
raise AttributeError(
"Attribute 'meta.source_detection' is missing."
"Please either run SourceDetectionStep or provide a"
"custom source catalog."
)
catalog = self.get_tweakreg_catalog(images, i, image_model)

for axis in ["x", "y"]:
if axis not in catalog.colnames:
Expand Down Expand Up @@ -495,10 +461,103 @@
del image_model.meta["wcs_fit_results"][k]

image_model.meta.wcs = imcat.wcs

if getattr(self, "update_source_catalog_coordinates", False):
self.update_catalog_coordinates(

Check warning on line 466 in romancal/tweakreg/tweakreg_step.py

View check run for this annotation

Codecov / codecov/patch

romancal/tweakreg/tweakreg_step.py#L466

Added line #L466 was not covered by tests
image_model.meta.source_detection.tweakreg_catalog_name,
image_model.meta.wcs,
)
images.shelve(image_model, i)

return images

def get_tweakreg_catalog(self, images, i, image_model):
if hasattr(image_model.meta, "source_detection"):
is_tweakreg_catalog_present = hasattr(
image_model.meta.source_detection, "tweakreg_catalog"
)
is_tweakreg_catalog_name_present = hasattr(
image_model.meta.source_detection, "tweakreg_catalog_name"
)
if is_tweakreg_catalog_present:
# read catalog from structured array
catalog = Table(

Check warning on line 484 in romancal/tweakreg/tweakreg_step.py

View check run for this annotation

Codecov / codecov/patch

romancal/tweakreg/tweakreg_step.py#L484

Added line #L484 was not covered by tests
np.asarray(image_model.meta.source_detection.tweakreg_catalog)
)
elif is_tweakreg_catalog_name_present:
catalog = self.read_catalog(
image_model.meta.source_detection.tweakreg_catalog_name
)
else:
images.shelve(image_model, i, modify=False)
raise AttributeError(

Check warning on line 493 in romancal/tweakreg/tweakreg_step.py

View check run for this annotation

Codecov / codecov/patch

romancal/tweakreg/tweakreg_step.py#L492-L493

Added lines #L492 - L493 were not covered by tests
"Attribute 'meta.source_detection.tweakreg_catalog' is missing."
"Please either run SourceDetectionStep or provide a"
"custom source catalog."
)
# remove 4D numpy array from meta.source_detection
if is_tweakreg_catalog_present:
del image_model.meta.source_detection["tweakreg_catalog"]

Check warning on line 500 in romancal/tweakreg/tweakreg_step.py

View check run for this annotation

Codecov / codecov/patch

romancal/tweakreg/tweakreg_step.py#L500

Added line #L500 was not covered by tests
else:
images.shelve(image_model, i, modify=False)
raise AttributeError(
"Attribute 'meta.source_detection' is missing."
"Please either run SourceDetectionStep or provide a"
"custom source catalog."
)

return catalog

def update_catalog_coordinates(self, tweakreg_catalog_name, tweaked_wcs):
"""
Update the source catalog coordinates using the tweaked WCS.

Parameters
----------
tweakreg_catalog_name : str
The name of the TweakReg catalog file produced by `SourceCatalog`.
tweaked_wcs : `astropy.wcs.WCS`
mairanteodoro marked this conversation as resolved.
Show resolved Hide resolved
The tweaked World Coordinate System (WCS) object.

Returns
-------
None
"""
# read in cat file
with rdm.open(tweakreg_catalog_name) as source_catalog_model:
# get catalog
catalog = source_catalog_model.source_catalog

# define mapping between pixel and world coordinates
colname_mapping = {
("xcentroid", "ycentroid"): ("ra_centroid", "dec_centroid"),
("x_psf", "y_psf"): ("ra_psf", "dec_psf"),
}

for k, v in colname_mapping.items():
# get column names
x_colname, y_colname = k
ra_colname, dec_colname = v

# calculate new coordinates using tweaked WCS
tweaked_centroid = tweaked_wcs.pixel_to_world(
catalog[x_colname], catalog[y_colname]
)

# update catalog coordinates
catalog[ra_colname], catalog[dec_colname] = (
tweaked_centroid.ra.deg,
tweaked_centroid.dec.deg,
)
mairanteodoro marked this conversation as resolved.
Show resolved Hide resolved

# save updated catalog (overwrite cat file)
self.save_model(
source_catalog_model,
output_file=source_catalog_model.meta.filename,
suffix="cat",
force=True,
)

def read_catalog(self, catalog_name):
if catalog_name.endswith("asdf"):
with rdm.open(catalog_name) as source_catalog_model:
Expand Down