From 94def9a1a88195a63732d61f5307485eaddf3118 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Fri, 27 Feb 2026 15:34:49 -0700 Subject: [PATCH 1/3] add quality flags --- imap_processing/quality_flags.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imap_processing/quality_flags.py b/imap_processing/quality_flags.py index e458c0b0a..7068281f7 100644 --- a/imap_processing/quality_flags.py +++ b/imap_processing/quality_flags.py @@ -150,6 +150,8 @@ class GLOWSL1bFlags(FlagNameMixin): NONE = CommonFlags.NONE IS_CLOSE_TO_UV_SOURCE = 2**0 # Is the bin close to a UV source. IS_INSIDE_EXCLUDED_REGION = 2**1 # Is the bin inside an excluded sky region. + IS_EXCLUDED_BY_INSTR_TEAM = 2**2 # Is the bin excluded by the instrument team. + IS_SUSPECTED_TRANSIENT = 2**3 # Is the bin a suspected transient. class ImapHiL1bDeFlags(FlagNameMixin): From 567f82500be8461e6c21b4450ec69e1cb810397e Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Fri, 27 Feb 2026 16:34:42 -0700 Subject: [PATCH 2/3] add tests --- imap_processing/glows/l1b/glows_l1b_data.py | 53 ++++++++++++++++--- imap_processing/tests/glows/conftest.py | 9 ++-- imap_processing/tests/glows/test_glows_l1b.py | 7 +++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/imap_processing/glows/l1b/glows_l1b_data.py b/imap_processing/glows/l1b/glows_l1b_data.py index 04e25fd89..b553b8b8c 100644 --- a/imap_processing/glows/l1b/glows_l1b_data.py +++ b/imap_processing/glows/l1b/glows_l1b_data.py @@ -863,13 +863,13 @@ def __post_init__( # get the data for the correct day day_exclusions = ancillary_exclusions.limit_by_day(day) + # Generate ISO datetime string using SPICE functions + datetime64_time = met_to_datetime64(self.imap_start_time) + self.unique_block_identifier = np.datetime_as_string(datetime64_time, "s") # Initialize histogram flag array: [is_close_to_uv_source, # is_inside_excluded_region, is_excluded_by_instr_team, # is_suspected_transient] x 3600 bins self.histogram_flag_array = self._compute_histogram_flag_array(day_exclusions) - # Generate ISO datetime string using SPICE functions - datetime64_time = met_to_datetime64(self.imap_start_time) - self.unique_block_identifier = np.datetime_as_string(datetime64_time, "s") self.flags = np.ones((FLAG_LENGTH,), dtype=np.uint8) def update_spice_parameters(self) -> None: @@ -1080,6 +1080,31 @@ def flag_uv_and_excluded(self, exclusions: AncillaryExclusions) -> tuple: return close_to_uv_source, inside_excluded_region + def flag_from_mask_dataset(self, mask_dataset: xr.Dataset) -> np.ndarray: + """ + Look up the per-bin boolean mask for this histogram block. + + Parameters + ---------- + mask_dataset : xr.Dataset + Dataset with ``l1b_unique_block_identifier`` and + ``histogram_mask_array`` variables indexed by ``time_block``. + + Returns + ------- + mask : np.ndarray + Boolean array of shape (n_bins,). True where the bin is flagged. + """ + identifiers = mask_dataset["l1b_unique_block_identifier"].values + match = np.where(identifiers == self.unique_block_identifier)[0] + if not match.size: + return np.zeros(len(self.histogram), dtype=bool) + mask_str = mask_dataset["histogram_mask_array"].values[match[0]] + + # Parse the "0"/"1" character string into a boolean array + mask = np.array(list(mask_str)) == "1" + return mask + def _compute_histogram_flag_array( self, exclusions: AncillaryExclusions ) -> np.ndarray: @@ -1089,8 +1114,8 @@ def _compute_histogram_flag_array( Creates a (4, 3600) array where each row represents a different flag type: - Row 0: is_close_to_uv_source - Row 1: is_inside_excluded_region - - Row 2: is_excluded_by_instr_team (TODO) - - Row 3: is_suspected_transient (TODO) + - Row 2: is_excluded_by_instr_team + - Row 3: is_suspected_transient Parameters ---------- @@ -1117,9 +1142,25 @@ def _compute_histogram_flag_array( GLOWSL1bFlags.IS_CLOSE_TO_UV_SOURCE.value ) - # inside if within half pixel size of any excluded region center + # inside if within half bin width of any excluded region center histogram_flags[1][inside_excluded_region] |= ( GLOWSL1bFlags.IS_INSIDE_EXCLUDED_REGION.value ) + # bins excluded by the instrument team for the matching histogram block + excluded_by_instr = self.flag_from_mask_dataset( + exclusions.exclusions_by_instr_team + ) + histogram_flags[2][excluded_by_instr] |= ( + GLOWSL1bFlags.IS_EXCLUDED_BY_INSTR_TEAM.value + ) + + # bins flagged as suspected transients for the matching histogram block + suspected_transient = self.flag_from_mask_dataset( + exclusions.suspected_transients + ) + histogram_flags[3][suspected_transient] |= ( + GLOWSL1bFlags.IS_SUSPECTED_TRANSIENT.value + ) + return histogram_flags diff --git a/imap_processing/tests/glows/conftest.py b/imap_processing/tests/glows/conftest.py index eb23211f5..c2d573f73 100644 --- a/imap_processing/tests/glows/conftest.py +++ b/imap_processing/tests/glows/conftest.py @@ -134,15 +134,16 @@ def mock_ancillary_exclusions(): coords={"epoch": epoch_range}, ) + # Mask array based on data in imap_glows_suspected-transients_20250923_v002.dat. mock_suspected_transients = xr.Dataset( { "l1b_unique_block_identifier": ( ["epoch", "time_block"], - [["block1", "block2"]] * len(epoch_range), + [["2026-01-01T15:00:00", "2026-01-01T15:01:00"]] * len(epoch_range), ), "histogram_mask_array": ( ["epoch", "time_block"], - [["mask1", "mask2"]] * len(epoch_range), + [["0" * 3600, "0" * 600 + "1" * 100 + "0" * 2900]] * len(epoch_range), ), }, coords={"epoch": epoch_range}, @@ -152,11 +153,11 @@ def mock_ancillary_exclusions(): { "l1b_unique_block_identifier": ( ["epoch", "time_block"], - [["block1", "block2"]] * len(epoch_range), + [["2026-01-01T15:00:00", "2026-01-01T15:01:00"]] * len(epoch_range), ), "histogram_mask_array": ( ["epoch", "time_block"], - [["mask1", "mask2"]] * len(epoch_range), + [["0" * 100 + "1" * 10 + "0" * 3490, "0" * 3600]] * len(epoch_range), ), }, coords={"epoch": epoch_range}, diff --git a/imap_processing/tests/glows/test_glows_l1b.py b/imap_processing/tests/glows/test_glows_l1b.py index 4808f5e75..d8a7a6a42 100644 --- a/imap_processing/tests/glows/test_glows_l1b.py +++ b/imap_processing/tests/glows/test_glows_l1b.py @@ -616,4 +616,11 @@ def test_hist_spice_output( # (since the 0.05° threshold is exactly half the 0.1° bin spacing. assert np.count_nonzero(region_mask) == 1 + # Test flag_from_mask_dataset using the fixture data + instr_mask = hist_data.flag_from_mask_dataset( + day_exclusions.exclusions_by_instr_team + ) + assert instr_mask.shape == (3600,) + assert np.count_nonzero(instr_mask) == 10 + # TODO: Maxine will validate actual data with GLOWS team From 1ee39ef3ad2c978ec47d52d7d98eca060dfa8345 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Tue, 3 Mar 2026 14:52:50 -0700 Subject: [PATCH 3/3] pass for flags when ck is not available --- imap_processing/glows/l1b/glows_l1b_data.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/imap_processing/glows/l1b/glows_l1b_data.py b/imap_processing/glows/l1b/glows_l1b_data.py index b553b8b8c..5300fa43c 100644 --- a/imap_processing/glows/l1b/glows_l1b_data.py +++ b/imap_processing/glows/l1b/glows_l1b_data.py @@ -1021,6 +1021,10 @@ def flag_uv_and_excluded(self, exclusions: AncillaryExclusions) -> tuple: look_vecs_dps, SpiceFrame.IMAP_DPS, SpiceFrame.ECLIPJ2000, + # This is for cases in which a histogram falls in a 2-min ck gap. + # DPS CK coverage intentionally doesn't include the + # repointing transition period. + allow_spice_noframeconnect=True, ) # UV source vectors.