From ed7ccf2e48188c7e6536d9841ca0f5962c31de5f Mon Sep 17 00:00:00 2001 From: Thomas Williams <32936518+thomaswilliamsastro@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:05:47 +0100 Subject: [PATCH] Skip linear mosaic targets in ImagingChunkedHandler Mosaic targets have no MS of their own, so ImagingChunkedHandler crashed on init. Check for a linmos target up front, log a warning, and make run_imaging and task_complete_gather_into_cubes no-ops, matching the behaviour of the standard ImagingHandler. Closes #350 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + phangsPipeline/handlerImagingChunked.py | 18 ++++++ tests/test_handlerImagingChunked.py | 75 +++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 tests/test_handlerImagingChunked.py diff --git a/CHANGELOG.md b/CHANGELOG.md index eb18198..30a8588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure fits import adds Stokes axis in feather routines (#407). - Fixed regression in get_ghz_range_for_line (#409). - Remove obsolete keywords so legacy singledish pipeline works again (#410). +- ``ImagingChunkedHandler`` skips linear mosaic targets rather than crashing (#413). ### Dependencies - Bump actions/upload-artifact from 6 to 7 (#313). diff --git a/phangsPipeline/handlerImagingChunked.py b/phangsPipeline/handlerImagingChunked.py index 2ab62ee..4c1c2db 100644 --- a/phangsPipeline/handlerImagingChunked.py +++ b/phangsPipeline/handlerImagingChunked.py @@ -112,6 +112,16 @@ def __init__( self.config = config self.product = product + # Linear mosaic targets have no MS of their own, so there is nothing to image. + # Skip them gracefully, as the standard ImagingHandler does + self.skip_target = self._kh.is_target_linmos(self.target) + if self.skip_target: + logger.warning(f"Target {self.target} is a linear mosaic, so has no visibilities to image. " + "Skipping.") + self.chunk_params = {} + self.nchunks = 0 + return + # Enforce that chunked imaging be used for cubes only. # Otherwise, there's no need for this approach: is_line_product = self.product in self._kh.get_line_products() @@ -419,6 +429,10 @@ def run_imaging( do_export_to_fits = True do_cleanup = True + if self.skip_target: + logger.warning(f"Target {self.target} is a linear mosaic. Skipping imaging.") + return + cwd = os.getcwd() # Create the imaging directory if it doesn't exist @@ -1485,6 +1499,10 @@ def task_complete_gather_into_cubes(self, root_name='all', Intended to create a final set of cubes. ''' + if self.skip_target: + logger.warning(f"Target {self.target} is a linear mosaic. Skipping gathering into cubes.") + return + accepted_root_names = ['dirty', 'singlescale', 'multiscale', None] if root_name == 'all': diff --git a/tests/test_handlerImagingChunked.py b/tests/test_handlerImagingChunked.py new file mode 100644 index 0000000..1f72769 --- /dev/null +++ b/tests/test_handlerImagingChunked.py @@ -0,0 +1,75 @@ +import importlib.util +import os + +import pytest + +# Load the key writer directly, since importing the phangsPipelineTests package +# pulls in the full (slow, side-effectful) legacy test suite +_spec = importlib.util.spec_from_file_location( + "minimal_key_writer", + os.path.join(os.path.dirname(__file__), "..", "phangsPipelineTests", "minimal_key_writer.py"), +) +_mkw = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_mkw) +MinimalKeyWriter = _mkw.MinimalKeyWriter + +_TARGET = "testgal_1" +_MOSAIC = "testgal" +_CONFIG = "C1" +_PRODUCT = "co21" + + +@pytest.fixture +def mosaic_key_handler(tmp_path): + """KeyHandler where _MOSAIC is a linear mosaic of _TARGET, with no MS of its own.""" + + from phangsPipeline import handlerKeys as kh + + writer = MinimalKeyWriter( + key_dir=tmp_path / "keys", + data_dir=tmp_path / "data", + ms_root=tmp_path / "uvdata", + ) + master_key = writer.write_all( + target=_TARGET, + config=_CONFIG, + product=_PRODUCT, + ms_filename="missing.ms", + restfreq_GHz=230.538, + ra="12h00m00.0s", + dec="-30d00m00.0s", + ) + + # Add the mosaic as a target, and define it as a linear mosaic + with open(os.path.join(writer.key_dir, "target_definitions.txt"), "a") as f: + f.write(f"{_MOSAIC}\t12h00m00.0s\t-30d00m00.0s\t0\t15\n") + with open(os.path.join(writer.key_dir, "linearmosaic_definitions.txt"), "a") as f: + f.write(f"{_MOSAIC}\t{_TARGET}\n") + + cwd = os.getcwd() + yield kh.KeyHandler(master_key=master_key) + os.chdir(cwd) + + +@pytest.mark.casa +def test_chunked_handler_skips_linmos_target(mosaic_key_handler): + """ImagingChunkedHandler should skip linear mosaic targets, rather than crash.""" + + from phangsPipeline.handlerImagingChunked import ImagingChunkedHandler + + assert mosaic_key_handler.is_target_linmos(_MOSAIC) + + im_handler = ImagingChunkedHandler( + target=_MOSAIC, + config=_CONFIG, + product=_PRODUCT, + key_handler=mosaic_key_handler, + make_temp_dir=False, + ) + + assert im_handler.skip_target + assert im_handler.nchunks == 0 + + # Neither of these should do anything, or raise + im_handler.run_imaging(do_all=True) + im_handler.task_complete_gather_into_cubes(root_name="all")