Skip to content
Merged
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
57 changes: 51 additions & 6 deletions imap_processing/glows/l1b/glows_l1b_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1080,6 +1084,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:
Expand All @@ -1089,8 +1118,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
----------
Expand All @@ -1117,9 +1146,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
2 changes: 2 additions & 0 deletions imap_processing/quality_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
9 changes: 5 additions & 4 deletions imap_processing/tests/glows/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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},
Expand Down
7 changes: 7 additions & 0 deletions imap_processing/tests/glows/test_glows_l1b.py
Original file line number Diff line number Diff line change
Expand Up @@ -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