Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
60 changes: 38 additions & 22 deletions nottcontrol/camera/macie/h2rg_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,25 @@ def acquire_archive_science_params(ctx: dict) -> dict:
}


def science_image_for_archive(
data,
header: dict | None,
params: dict,
):
"""Reduce a raw ramp cube with the acquire-time snapshot, not live GUI fields.

``_load_fits_from_path`` uses the current CDS/Fowler combo. The GUI is
re-enabled as soon as ZMQ returns, so that combo can change while FITS
are still being collected. Science products must ignore that change.
"""
return science_image_from_cube(
data,
header,
reduction=str(params.get("ramp_mode") or "CDS"),
fowler_pairs=int(params.get("fowler_pairs") or 2),
)


def path_is_directory(path: Path, timeout_s: float = FITS_DIR_CHECK_TIMEOUT_S) -> bool:
result: list[bool | None] = [None]

Expand Down Expand Up @@ -4073,6 +4092,14 @@ def _finish_acquire_archive(self, ctx: dict) -> None:
if frame is not None and preview_path is not None:
science_paths: list[Path] = []
params = acquire_archive_science_params(ctx)
# Display load uses the live combo; re-reduce from the raw cube
# so the last-ramp image matches the acquire-time snapshot.
if self._raw_fits_cube is not None:
frame = science_image_for_archive(
self._raw_fits_cube,
self._raw_fits_header,
params,
)
cards = self._acquisition_fits_header_cards(
ramp_mode=params["ramp_mode"],
tint_ms=params["tint_ms"],
Expand All @@ -4082,28 +4109,17 @@ def _finish_acquire_archive(self, ctx: dict) -> None:
# Always stamp DETMODE / cryo cards on the ramp archive,
# including when science FITS writes are disabled.
self._apply_cryo_temps_to_ramp(ramp_path, cards)
if (
ramp_path.name == preview_path.name
and self._raw_fits_header is not None
):
science_path = self._save_science_fits(
frame,
ramp_path,
reduction=params["ramp_mode"],
fowler_pairs=params["fowler_pairs"],
tint_ms=params["tint_ms"],
keep_files=params["keep_files"],
report=params["exposure_report"],
)
else:
science_path = self._save_science_fits_from_ramp(
ramp_path,
reduction=params["ramp_mode"],
fowler_pairs=params["fowler_pairs"],
tint_ms=params["tint_ms"],
keep_files=params["keep_files"],
report=params["exposure_report"],
)
# Reduce every ramp from disk with the snapshot. The last
# ramp used to reuse the display frame, which followed the
# live CDS/Fowler combo after ZMQ returned.
science_path = self._save_science_fits_from_ramp(
ramp_path,
reduction=params["ramp_mode"],
fowler_pairs=params["fowler_pairs"],
tint_ms=params["tint_ms"],
keep_files=params["keep_files"],
report=params["exposure_report"],
)
if science_path is not None:
science_paths.append(science_path)
preview_science = (
Expand Down
29 changes: 29 additions & 0 deletions nottcontrol/camera/macie/test_h2rg_fits_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
_centered_window,
_channel_window,
acquire_archive_science_params,
science_image_for_archive,
central_value_median,
fits_basename,
fits_frame_number_label,
Expand Down Expand Up @@ -448,6 +449,34 @@ def test_snapshot_is_not_the_live_gui_mode(self) -> None:
self.assertNotEqual(params["ramp_mode"], live_gui["ramp_mode"])
self.assertNotEqual(params["keep_files"], live_gui["keep_files"])

def test_last_ramp_science_uses_snapshot_not_live_combo(self) -> None:
"""Display load follows the live combo; archive must re-reduce as CDS.

Trigger: Acquire CDS, GUI re-enables when ZMQ returns, operator
switches to Fowler before the last ramp is written as science FITS.
"""
from nottcontrol.camera.macie.fits_science import science_image_from_cube

cube = numpy.array(
[
[[0.0, 0.0], [0.0, 0.0]],
[[10.0, 20.0], [30.0, 40.0]],
[[1.0, 2.0], [3.0, 4.0]],
[[11.0, 22.0], [33.0, 44.0]],
],
dtype=numpy.float32,
)
header = {"NAXIS": 3, "NAXIS3": 4}
params = acquire_archive_science_params(
{"ramp_mode": "CDS", "fowler_pairs": 2}
)
live = science_image_from_cube(
cube, header, reduction="Fowler", fowler_pairs=2
)
archived = science_image_for_archive(cube, header, params)
numpy.testing.assert_allclose(archived, [[11.0, 22.0], [33.0, 44.0]])
self.assertFalse(numpy.allclose(live, archived))

def test_invalid_pairs_and_missing_mode_fall_back(self) -> None:
params = acquire_archive_science_params(
{"fowler_pairs": "x", "tint_ms": "bad"}
Expand Down