From 646459a098529595bea548bb9c219e2891108344 Mon Sep 17 00:00:00 2001 From: Dave Mehringer Date: Fri, 20 Mar 2026 16:44:02 -0400 Subject: [PATCH 1/7] #553 update direction frame determination --- .../image/_util/_casacore/xds_to_casacore.py | 18 ++++++- tests/unit/image/test_image.py | 49 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/xradio/image/_util/_casacore/xds_to_casacore.py b/src/xradio/image/_util/_casacore/xds_to_casacore.py index 48d7e741..cfb537b3 100644 --- a/src/xradio/image/_util/_casacore/xds_to_casacore.py +++ b/src/xradio/image/_util/_casacore/xds_to_casacore.py @@ -40,7 +40,23 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict: [xds.sizes[dim] for dim in ("l", "m")], dtype=np.int32 ) direction["_image_axes"] = np.array([2, 3], dtype=np.int32) - direction["system"] = xds_dir["reference_direction"]["attrs"]["equinox"].upper() + if "equinox" in xds_dir["reference_direction"]["attrs"]: + direction["system"] = xds_dir["reference_direction"]["attrs"]["equinox"].upper() + elif "frame" in xds_dir["reference_direction"]["attrs"]: + frame = xds_dir["reference_direction"]["attrs"]["frame"].upper() + direction["system"] = { + "FK5": "J2000", + "FK4": "B1950", + "ICRS": "ICRS", + "GALACTIC": "GALACTIC", + "J2000": "J2000", + "B1950": "B1950", + }.get(frame, frame) + else: + raise RuntimeError( + "Cannot determine direction coordinate system frame. " + f"direction metadata is {xds_dir}" + ) if direction["system"] == "J2000.0": direction["system"] = "J2000" direction["projection"] = xds_dir["projection"] diff --git a/tests/unit/image/test_image.py b/tests/unit/image/test_image.py index cd34bba5..e039fdab 100644 --- a/tests/unit/image/test_image.py +++ b/tests/unit/image/test_image.py @@ -57,6 +57,55 @@ def clean_path_logic(text: str) -> str: return text +def _make_test_sky_xds_for_casa_coord_write(): + xds = make_empty_sky_image( + phase_center=np.array([0.2, -0.5]), + image_size=np.array([4, 4]), + cell_size=np.array([1e-4, 1e-4]), + frequency_coords=np.array([1.4e9]), + pol_coords=np.array(["I"]), + time_coords=np.array([51544.0]), + do_sky_coords=False, + ) + xds["SKY"] = xr.DataArray( + np.zeros((1, 1, 1, 4, 4), dtype=np.float32), + dims=["time", "frequency", "polarization", "l", "m"], + ) + xds["SKY"].attrs["type"] = "sky" + xds.attrs["type"] = "image_dataset" + xds.attrs["data_groups"] = {"base": {"sky": "SKY"}} + return xds + + +def test_write_image_uses_reference_direction_frame_without_equinox(tmp_path): + xds = _make_test_sky_xds_for_casa_coord_write() + ref_dir_attrs = xds.attrs["coordinate_system_info"]["reference_direction"]["attrs"] + del ref_dir_attrs["equinox"] + outname = tmp_path / "frame_only.im" + + write_image(xds, str(outname), "casa") + + with open_image_ro(str(outname)) as im: + direction = im.coordinates().dict()["direction0"] + + assert direction["system"] == "J2000" + assert direction["conversionSystem"] == "J2000" + + +def test_write_image_requires_reference_direction_frame_or_equinox(tmp_path): + xds = _make_test_sky_xds_for_casa_coord_write() + ref_dir_attrs = xds.attrs["coordinate_system_info"]["reference_direction"]["attrs"] + del ref_dir_attrs["equinox"] + del ref_dir_attrs["frame"] + outname = tmp_path / "missing_frame.im" + + with pytest.raises( + RuntimeError, + match=r"Cannot determine direction coordinate system frame", + ): + write_image(xds, str(outname), "casa") + + @pytest.fixture(scope="module") def dask_client_module(): """Set up and tear down a Dask client for the test module. From 0520c914843b036599f7f07eed31bc48dce3c14c Mon Sep 17 00:00:00 2001 From: dmehring Date: Fri, 20 Mar 2026 17:12:38 -0400 Subject: [PATCH 2/7] Update tests/unit/image/test_image.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/unit/image/test_image.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit/image/test_image.py b/tests/unit/image/test_image.py index e039fdab..a3daa335 100644 --- a/tests/unit/image/test_image.py +++ b/tests/unit/image/test_image.py @@ -106,6 +106,36 @@ def test_write_image_requires_reference_direction_frame_or_equinox(tmp_path): write_image(xds, str(outname), "casa") +def test_write_image_uses_fk4_frame_to_set_b1950(tmp_path): + xds = _make_test_sky_xds_for_casa_coord_write() + ref_dir_attrs = xds.attrs["coordinate_system_info"]["reference_direction"]["attrs"] + # Ensure the frame is FK4 and that we rely on the frame (not equinox) mapping + ref_dir_attrs["frame"] = "FK4" + ref_dir_attrs.pop("equinox", None) + outname = tmp_path / "fk4_frame.im" + + write_image(xds, str(outname), "casa") + + with open_image_ro(str(outname)) as im: + direction = im.coordinates().dict()["direction0"] + + assert direction["system"] == "B1950" + assert direction["conversionSystem"] == "B1950" + + +def test_write_image_raises_for_unsupported_reference_direction_frame(tmp_path): + xds = _make_test_sky_xds_for_casa_coord_write() + ref_dir_attrs = xds.attrs["coordinate_system_info"]["reference_direction"]["attrs"] + # Set an unrecognized frame and remove equinox so that frame mapping is required + ref_dir_attrs["frame"] = "UNSUPPORTED" + ref_dir_attrs.pop("equinox", None) + outname = tmp_path / "unsupported_frame.im" + + with pytest.raises( + RuntimeError, + match=r"Unsupported direction coordinate system frame", + ): + write_image(xds, str(outname), "casa") @pytest.fixture(scope="module") def dask_client_module(): """Set up and tear down a Dask client for the test module. From 0ea717d8972622862a8fcbf754117e07e44e7f04 Mon Sep 17 00:00:00 2001 From: dmehring Date: Fri, 20 Mar 2026 17:14:28 -0400 Subject: [PATCH 3/7] Update src/xradio/image/_util/_casacore/xds_to_casacore.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../image/_util/_casacore/xds_to_casacore.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/xradio/image/_util/_casacore/xds_to_casacore.py b/src/xradio/image/_util/_casacore/xds_to_casacore.py index cfb537b3..26cbee13 100644 --- a/src/xradio/image/_util/_casacore/xds_to_casacore.py +++ b/src/xradio/image/_util/_casacore/xds_to_casacore.py @@ -42,16 +42,29 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict: direction["_image_axes"] = np.array([2, 3], dtype=np.int32) if "equinox" in xds_dir["reference_direction"]["attrs"]: direction["system"] = xds_dir["reference_direction"]["attrs"]["equinox"].upper() + # Allow J2000.0 for backward compatibility; it will be normalized below. + allowed_systems = {"FK5", "FK4", "ICRS", "GALACTIC", "J2000", "B1950", "J2000.0"} + if direction["system"] not in allowed_systems: + raise RuntimeError( + f"Unsupported direction equinox '{direction['system']}'. " + "Supported systems are: FK5, FK4, ICRS, GALACTIC, J2000, B1950." + ) elif "frame" in xds_dir["reference_direction"]["attrs"]: frame = xds_dir["reference_direction"]["attrs"]["frame"].upper() - direction["system"] = { + frame_to_system = { "FK5": "J2000", "FK4": "B1950", "ICRS": "ICRS", "GALACTIC": "GALACTIC", "J2000": "J2000", "B1950": "B1950", - }.get(frame, frame) + } + if frame not in frame_to_system: + raise RuntimeError( + f"Unsupported direction frame '{frame}'. " + "Supported frames are: FK5, FK4, ICRS, GALACTIC, J2000, B1950." + ) + direction["system"] = frame_to_system[frame] else: raise RuntimeError( "Cannot determine direction coordinate system frame. " From 96cae880108aeec79f93470c44e8ceff83c6f735 Mon Sep 17 00:00:00 2001 From: dmehring Date: Fri, 20 Mar 2026 17:15:29 -0400 Subject: [PATCH 4/7] Update src/xradio/image/_util/_casacore/xds_to_casacore.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/xradio/image/_util/_casacore/xds_to_casacore.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xradio/image/_util/_casacore/xds_to_casacore.py b/src/xradio/image/_util/_casacore/xds_to_casacore.py index 26cbee13..014383f7 100644 --- a/src/xradio/image/_util/_casacore/xds_to_casacore.py +++ b/src/xradio/image/_util/_casacore/xds_to_casacore.py @@ -72,6 +72,8 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict: ) if direction["system"] == "J2000.0": direction["system"] = "J2000" + elif direction["system"] == "B1950.0": + direction["system"] = "B1950" direction["projection"] = xds_dir["projection"] direction["projection_parameters"] = xds_dir["projection_parameters"] direction["units"] = [ From 75cb50d85653837be15e10dcd7fa74b12dccbb78 Mon Sep 17 00:00:00 2001 From: dmehring Date: Fri, 20 Mar 2026 17:18:10 -0400 Subject: [PATCH 5/7] Update tests/unit/image/test_image.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/unit/image/test_image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/image/test_image.py b/tests/unit/image/test_image.py index a3daa335..ff657e1d 100644 --- a/tests/unit/image/test_image.py +++ b/tests/unit/image/test_image.py @@ -66,6 +66,7 @@ def _make_test_sky_xds_for_casa_coord_write(): pol_coords=np.array(["I"]), time_coords=np.array([51544.0]), do_sky_coords=False, + direction_reference="fk5", ) xds["SKY"] = xr.DataArray( np.zeros((1, 1, 1, 4, 4), dtype=np.float32), From ba4d1df5c89e5baccf69f189ddb1601961733cd6 Mon Sep 17 00:00:00 2001 From: Dave Mehringer Date: Fri, 20 Mar 2026 17:50:14 -0400 Subject: [PATCH 6/7] adjust error message --- src/xradio/image/_util/_casacore/xds_to_casacore.py | 12 ++++++++++-- tests/unit/image/test_image.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/xradio/image/_util/_casacore/xds_to_casacore.py b/src/xradio/image/_util/_casacore/xds_to_casacore.py index 014383f7..7f91b439 100644 --- a/src/xradio/image/_util/_casacore/xds_to_casacore.py +++ b/src/xradio/image/_util/_casacore/xds_to_casacore.py @@ -43,7 +43,15 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict: if "equinox" in xds_dir["reference_direction"]["attrs"]: direction["system"] = xds_dir["reference_direction"]["attrs"]["equinox"].upper() # Allow J2000.0 for backward compatibility; it will be normalized below. - allowed_systems = {"FK5", "FK4", "ICRS", "GALACTIC", "J2000", "B1950", "J2000.0"} + allowed_systems = { + "FK5", + "FK4", + "ICRS", + "GALACTIC", + "J2000", + "B1950", + "J2000.0", + } if direction["system"] not in allowed_systems: raise RuntimeError( f"Unsupported direction equinox '{direction['system']}'. " @@ -61,7 +69,7 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict: } if frame not in frame_to_system: raise RuntimeError( - f"Unsupported direction frame '{frame}'. " + f"Unsupported direction coordinate system frame '{frame}'. " "Supported frames are: FK5, FK4, ICRS, GALACTIC, J2000, B1950." ) direction["system"] = frame_to_system[frame] diff --git a/tests/unit/image/test_image.py b/tests/unit/image/test_image.py index ff657e1d..9a549dec 100644 --- a/tests/unit/image/test_image.py +++ b/tests/unit/image/test_image.py @@ -137,6 +137,8 @@ def test_write_image_raises_for_unsupported_reference_direction_frame(tmp_path): match=r"Unsupported direction coordinate system frame", ): write_image(xds, str(outname), "casa") + + @pytest.fixture(scope="module") def dask_client_module(): """Set up and tear down a Dask client for the test module. From 06f4cf282bfbd22cbd9fb13a2192d74cff4568dc Mon Sep 17 00:00:00 2001 From: Dave Mehringer Date: Tue, 24 Mar 2026 10:58:13 -0400 Subject: [PATCH 7/7] black --- tests/unit/image/test_image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/image/test_image.py b/tests/unit/image/test_image.py index 3ed9ec1a..03344e7c 100644 --- a/tests/unit/image/test_image.py +++ b/tests/unit/image/test_image.py @@ -138,6 +138,8 @@ def test_write_image_raises_for_unsupported_reference_direction_frame(tmp_path): match=r"Unsupported direction coordinate system frame", ): write_image(xds, str(outname), "casa") + + def test_load_visibility_normalization_block_squeezes_spatial_axes(tmp_path): imagename = tmp_path / "synthetic.sumwt" data = np.arange(8, dtype=np.float32).reshape(4, 2, 1, 1)