Skip to content

Commit 67cf8d8

Browse files
Copilottmplummerpre-commit-ci[bot]
authored
Hi L1B DE: carry last_spin_num into output and add BADSPIN ccsds_qf flag (IMAP-Science-Operations-Center#2754)
* Initial plan * Hi L1B DE: carry last_spin_num into L1B and add SPIN_INVALID quality flag Co-authored-by: subagonsouth <16110870+subagonsouth@users.noreply.github.com> * Use np.testing assertions in SPIN_INVALID test methods Co-authored-by: subagonsouth <16110870+subagonsouth@users.noreply.github.com> * Use np.testing.assert_array_equal consistently in SPIN_INVALID tests Co-authored-by: subagonsouth <16110870+subagonsouth@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use ENAFlags.BADSPIN in ImapHiL1bDeFlags and update ccsds_qf VAR_NOTES Co-authored-by: subagonsouth <16110870+subagonsouth@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: subagonsouth <16110870+subagonsouth@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3af414b commit 67cf8d8

4 files changed

Lines changed: 85 additions & 6 deletions

File tree

imap_processing/cdf/config/imap_hi_variable_attrs.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,8 @@ hi_de_ccsds_qf:
446446
VALIDMAX: 255
447447
VAR_NOTES: >
448448
Bitwise quality flag for CCSDS packet. Bit 0 (value 1): packet was full
449-
(contained 664 events).
449+
(contained 664 events). Bit 2 (value 4): packet contained events from an
450+
invalid spin.
450451
451452
# ======= L1C PSET Section =======
452453

imap_processing/hi/hi_l1b.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ def annotate_direct_events(
140140
[
141141
"src_seq_ctr",
142142
"pkt_len",
143-
"last_spin_num",
144143
"spin_invalids",
145144
"esa_step_seconds",
146145
"esa_step_milliseconds",
@@ -603,7 +602,11 @@ def de_ccsds_qf(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
603602
# Filter out fill/out-of-range indices (e.g., uint16 FILLVAL 65535)
604603
valid_mask = (ccsds_indices >= 0) & (ccsds_indices < n_packets)
605604

606-
# If there are no valid events, all packets keep default quality flag 0
605+
# Set BADSPIN flag for packets with nonzero spin_invalids
606+
spin_invalid_mask = dataset["spin_invalids"].values != 0
607+
new_vars["ccsds_qf"].values[spin_invalid_mask] |= np.uint8(ImapHiL1bDeFlags.BADSPIN)
608+
609+
# If there are no valid events, skip the PACKET_FULL check
607610
if not np.any(valid_mask):
608611
return new_vars
609612

@@ -614,6 +617,8 @@ def de_ccsds_qf(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
614617
)
615618
# Set PACKET_FULL flag for packets with 664 events
616619
full_packet_mask = event_counts == max_events_per_packet
617-
new_vars["ccsds_qf"].values[full_packet_mask] = ImapHiL1bDeFlags.PACKET_FULL
620+
new_vars["ccsds_qf"].values[full_packet_mask] |= np.uint8(
621+
ImapHiL1bDeFlags.PACKET_FULL
622+
)
618623

619624
return new_vars

imap_processing/quality_flags.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,4 @@ class ImapHiL1bDeFlags(FlagNameMixin):
155155

156156
NONE = CommonFlags.NONE
157157
PACKET_FULL = 2**0 # bit 0, packet contained 664 events (max capacity)
158+
BADSPIN = ENAFlags.BADSPIN # bit 2, packet contained events from an invalid spin

imap_processing/tests/hi/test_hi_l1b.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_hi_annotate_direct_events(
6969
l1b_datasets = annotate_direct_events(l1a_dataset, xr.Dataset(), esa_energies_csv)
7070
assert len(l1b_datasets) == 1
7171
assert l1b_datasets[0].attrs["Logical_source"] == "imap_hi_l1b_45sensor-de"
72-
assert len(l1b_datasets[0].data_vars) == 17
72+
assert len(l1b_datasets[0].data_vars) == 18
7373

7474

7575
@pytest.mark.parametrize(
@@ -131,7 +131,7 @@ def test_annotate_direct_events_with_hk(
131131
l1b_datasets = annotate_direct_events(l1a_dataset, hk_dataset, esa_energies_csv)
132132
assert len(l1b_datasets) == 1
133133
assert l1b_datasets[0].attrs["Logical_source"] == "imap_hi_l1b_90sensor-de"
134-
assert len(l1b_datasets[0].data_vars) == 17
134+
assert len(l1b_datasets[0].data_vars) == 18
135135
# Verify new L1B variables exist
136136
assert "esa_step_met" in l1b_datasets[0].data_vars
137137
assert "ccsds_qf" in l1b_datasets[0].data_vars
@@ -700,6 +700,7 @@ def test_packet_full_flag_set(self):
700700
dims=["event_met"],
701701
attrs={"FILLVAL": 0},
702702
),
703+
"spin_invalids": (["epoch"], np.zeros(n_packets, dtype=np.uint8)),
703704
},
704705
)
705706
result = de_ccsds_qf(ds)
@@ -729,12 +730,81 @@ def test_no_full_packets(self):
729730
dims=["event_met"],
730731
attrs={"FILLVAL": 0},
731732
),
733+
"spin_invalids": (["epoch"], np.zeros(n_packets, dtype=np.uint8)),
732734
},
733735
)
734736
result = de_ccsds_qf(ds)
735737
assert result["ccsds_qf"].values[0] == 0
736738
assert result["ccsds_qf"].values[1] == 0
737739

740+
def test_spin_invalid_flag_set(self):
741+
"""Test that BADSPIN flag is set for packets with nonzero spin_invalids."""
742+
n_packets = 3
743+
ccsds_indices = np.concatenate(
744+
[
745+
np.zeros(10, dtype=np.uint16),
746+
np.ones(10, dtype=np.uint16),
747+
np.full(10, 2, dtype=np.uint16),
748+
]
749+
)
750+
ds = xr.Dataset(
751+
coords={
752+
"epoch": np.arange(n_packets),
753+
"event_met": np.arange(len(ccsds_indices), dtype=np.float64),
754+
},
755+
data_vars={
756+
"ccsds_index": (["event_met"], ccsds_indices),
757+
"trigger_id": xr.DataArray(
758+
np.ones(len(ccsds_indices), dtype=np.uint8),
759+
dims=["event_met"],
760+
attrs={"FILLVAL": 0},
761+
),
762+
# Packet 1 has an invalid spin, packets 0 and 2 do not
763+
"spin_invalids": (
764+
["epoch"],
765+
np.array([0, 1, 0], dtype=np.uint8),
766+
),
767+
},
768+
)
769+
result = de_ccsds_qf(ds)
770+
np.testing.assert_array_equal(
771+
result["ccsds_qf"].values, [0, ImapHiL1bDeFlags.BADSPIN, 0]
772+
)
773+
774+
def test_spin_invalid_and_packet_full_flags_combined(self):
775+
"""Test that BADSPIN and PACKET_FULL flags can be set together."""
776+
n_packets = 2
777+
ccsds_indices = np.concatenate(
778+
[
779+
np.zeros(664, dtype=np.uint16), # 664 events for packet 0
780+
np.ones(10, dtype=np.uint16),
781+
]
782+
)
783+
ds = xr.Dataset(
784+
coords={
785+
"epoch": np.arange(n_packets),
786+
"event_met": np.arange(len(ccsds_indices), dtype=np.float64),
787+
},
788+
data_vars={
789+
"ccsds_index": (["event_met"], ccsds_indices),
790+
"trigger_id": xr.DataArray(
791+
np.ones(len(ccsds_indices), dtype=np.uint8),
792+
dims=["event_met"],
793+
attrs={"FILLVAL": 0},
794+
),
795+
# Packet 0 is both full and has an invalid spin
796+
"spin_invalids": (
797+
["epoch"],
798+
np.array([1, 0], dtype=np.uint8),
799+
),
800+
},
801+
)
802+
result = de_ccsds_qf(ds)
803+
np.testing.assert_array_equal(
804+
result["ccsds_qf"].values,
805+
[ImapHiL1bDeFlags.PACKET_FULL | ImapHiL1bDeFlags.BADSPIN, 0],
806+
)
807+
738808
def test_no_valid_direct_events_all_fill_trigger_id(self):
739809
"""de_ccsds_qf returns all zeros when trigger_id is entirely FILLVAL."""
740810
n_packets = 3
@@ -756,6 +826,7 @@ def test_no_valid_direct_events_all_fill_trigger_id(self):
756826
dims=["event_met"],
757827
attrs={"FILLVAL": trigger_fillval},
758828
),
829+
"spin_invalids": (["epoch"], np.zeros(n_packets, dtype=np.uint8)),
759830
},
760831
)
761832
result = de_ccsds_qf(ds)
@@ -783,6 +854,7 @@ def test_ccsds_index_fillvals_ignored(self):
783854
dims=["event_met"],
784855
attrs={"FILLVAL": 0},
785856
),
857+
"spin_invalids": (["epoch"], np.zeros(n_packets, dtype=np.uint8)),
786858
},
787859
)
788860
result = de_ccsds_qf(ds)

0 commit comments

Comments
 (0)