diff --git a/changes/9625.combine_1d.rst b/changes/9625.combine_1d.rst new file mode 100644 index 0000000000..f829b9fb75 --- /dev/null +++ b/changes/9625.combine_1d.rst @@ -0,0 +1 @@ +Allow all-NaN spectra to be skipped instead of causing crashes diff --git a/jwst/combine_1d/combine1d.py b/jwst/combine_1d/combine1d.py index 1aa06a376c..cc316fe38f 100644 --- a/jwst/combine_1d/combine1d.py +++ b/jwst/combine_1d/combine1d.py @@ -727,6 +727,12 @@ def _read_input_spectra(input_model, exptime_key, input_spectra): else: spectra = input_model.spec for in_spec in spectra: + if not np.any(np.isfinite(in_spec.spec_table.field("flux"))): + log.warning( + f"Input spectrum {in_spec.source_id} order {in_spec.spectral_order} " + f"from group_id {in_spec.meta.group_id} has no valid flux values; skipping." + ) + continue spectral_order = in_spec.spectral_order if spectral_order not in input_spectra: input_spectra[spectral_order] = [] @@ -768,6 +774,12 @@ def combine_1d_spectra(input_model, exptime_key, sigma_clip=None): else: _read_input_spectra(input_model, exptime_key, input_spectra) + if len(input_spectra) == 0: + log.error("No valid input spectra found for source. Skipping.") + result = input_model.copy() + result.meta.cal_step.combine_1d = "SKIPPED" + return result + for order in input_spectra: output_spectra[order] = OutputSpectrumModel() output_spectra[order].assign_wavelengths(input_spectra[order]) diff --git a/jwst/combine_1d/combine_1d_step.py b/jwst/combine_1d/combine_1d_step.py index c9ac222c65..1948c6b04d 100644 --- a/jwst/combine_1d/combine_1d_step.py +++ b/jwst/combine_1d/combine_1d_step.py @@ -71,7 +71,15 @@ def process(self, input_data): result = combine1d.combine_1d_spectra( model, self.exptime_key, sigma_clip=self.sigma_clip ) - results_list.append(result) + if not result.meta.cal_step.combine_1d == "SKIPPED": + results_list.append(result) + if not results_list: + self.log.error( + "No valid input spectra found in WFSSMultiSpecModel. Skipping." + ) + result = input_model.copy() + result.meta.cal_step.combine_1d = "SKIPPED" + return result result = make_wfss_multicombined(results_list) result.meta.cal_step.combine_1d = "COMPLETE" return result diff --git a/jwst/combine_1d/tests/test_combine1d.py b/jwst/combine_1d/tests/test_combine1d.py index d7ff2fd994..100913afba 100644 --- a/jwst/combine_1d/tests/test_combine1d.py +++ b/jwst/combine_1d/tests/test_combine1d.py @@ -2,6 +2,8 @@ import numpy as np import pytest +import logging +from jwst.tests.helpers import LogWatcher from jwst import datamodels @@ -227,3 +229,35 @@ def test_wfss_multi_input(wfss_multiexposure): assert np.all(tab["SOURCE_TYPE"] == "POINT") assert result.spec[0].dispersion_direction == 3 + + +def test_allnan_skip(wfss_multiexposure, monkeypatch): + """Test that all-nan spectra are skipped.""" + # Set all flux values to NaN + for spec in wfss_multiexposure.spec: + spec.spec_table["FLUX"][:] = np.nan + + # message when a single spectrum has no valid flux values + watcher0 = LogWatcher( + "Input spectrum 5 order 1 from group_id 1 has no valid flux values; skipping." + ) + monkeypatch.setattr(logging.getLogger("jwst.combine_1d.combine1d"), "warning", watcher0) + result = Combine1dStep.call(wfss_multiexposure) + assert result.meta.cal_step.combine_1d == "SKIPPED" + watcher0.assert_seen() + + # check for the other log messages + # these must be done one at a time because the watcher can only be monkeypatched once + # message when no valid input spectra are found for the source + watcher1 = LogWatcher("No valid input spectra found for source. Skipping.") + monkeypatch.setattr(logging.getLogger("jwst.combine_1d.combine1d"), "error", watcher1) + result = Combine1dStep.call(wfss_multiexposure) + assert result.meta.cal_step.combine_1d == "SKIPPED" + watcher1.assert_seen() + + # message when no valid input spectra at all are found + watcher2 = LogWatcher("No valid input spectra found in WFSSMultiSpecModel") + monkeypatch.setattr(logging.getLogger("stpipe.Combine1dStep"), "error", watcher2) + result = Combine1dStep.call(wfss_multiexposure) + assert result.meta.cal_step.combine_1d == "SKIPPED" + watcher2.assert_seen() diff --git a/jwst/pipeline/calwebb_spec3.py b/jwst/pipeline/calwebb_spec3.py index c7cec820d6..ed09baff6d 100644 --- a/jwst/pipeline/calwebb_spec3.py +++ b/jwst/pipeline/calwebb_spec3.py @@ -296,13 +296,17 @@ def process(self, input_data): extraction_complete = ( result is not None and result.meta.cal_step.extract_1d == "COMPLETE" ) - if extraction_complete: - # Combine the results for all sources - comb = self.combine_1d.run(result) - # add metadata that only WFSS wants - comb.spec[0].source_ra = result.spec[0].spec_table["SOURCE_RA"][0] - comb.spec[0].source_dec = result.spec[0].spec_table["SOURCE_DEC"][0] - wfss_comb.append(comb) + if not extraction_complete: + continue + # Combine the results for all sources + comb = self.combine_1d.run(result) + comb_complete = comb is not None and comb.meta.cal_step.combine_1d == "COMPLETE" + if not comb_complete: + continue + # add metadata that only WFSS wants + comb.spec[0].source_ra = result.spec[0].spec_table["SOURCE_RA"][0] + comb.spec[0].source_dec = result.spec[0].spec_table["SOURCE_DEC"][0] + wfss_comb.append(comb) elif resample_complete is not None and resample_complete.upper() == "COMPLETE": # If 2D data were resampled and combined, just do a 1D extraction