Skip to content

Commit 4d4a9fa

Browse files
authored
CoDICE l1a-l2 FSW updates 20260129 (IMAP-Science-Operations-Center#2679)
* codice de fsw stuff * update sci lut ot contain compression info * more fsw trickle down changes * clean up tests * remove print statement * remove duplicate code * copilot suggestiosn * carry product names through * add product_names to dataset * remove product name list * comment update * fix tests * fix ialirt test
1 parent c2cdc9d commit 4d4a9fa

27 files changed

Lines changed: 6167 additions & 225 deletions

imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ priority_label:
162162
FORMAT: A2
163163
VAR_TYPE: metadata
164164

165-
166165
# Common energy labels for species for omni and sectored data
167166
energy_species_label:
168167
CATDESC: Energy Table for {species}
@@ -214,6 +213,20 @@ data_quality:
214213
VALIDMAX: 1
215214
VAR_TYPE: data
216215

216+
packet_version:
217+
CATDESC: Packet version. Incremented each time the format of the packet changes.
218+
DEPEND_0: epoch
219+
DISPLAY_TYPE: time_series
220+
FIELDNAM: Packet Version
221+
FILLVAL: 65535
222+
FORMAT: I5
223+
LABLAXIS: Packet Version
224+
SCALETYP: linear
225+
UNITS: " "
226+
VALIDMIN: 0
227+
VALIDMAX: 65535
228+
VAR_TYPE: data
229+
217230
voltage_table:
218231
CATDESC: ElectroStatic Analyzer Voltage Values
219232
DEPEND_1: esa_step
@@ -242,30 +255,90 @@ nso_half_spin:
242255
CATDESC: When No Scan Operation (NSO) was activated
243256
DEPEND_0: epoch
244257
DISPLAY_TYPE: time_series
245-
FIELDNAM: NSO Mode
258+
FIELDNAM: NSO Half Spin
246259
FILLVAL: 255
247260
FORMAT: I3
248261
LABLAXIS: NSO Half Spin
249262
SCALETYP: linear
250263
UNITS: half spin number
251264
VALIDMIN: 0
252265
VALIDMAX: 255
253-
VAR_NOTES: Indicates the point when No Scan Operation (NSO) was activated. In NSO, the ESA voltage is set to the first step in the scan and remains fixed until the next cycle boundary.
266+
VAR_NOTES: Indicates the half spin when No Scan Operation (NSO) was activated. In NSO, the ESA voltage is set to the first step in the scan and remains fixed until the next cycle boundary.
267+
VAR_TYPE: data
268+
269+
nso_spin_sector:
270+
CATDESC: Spin Sector When No Scan Operation (NSO) was activated
271+
DEPEND_0: epoch
272+
DISPLAY_TYPE: time_series
273+
FIELDNAM: NSO Spin Sector
274+
FILLVAL: 255
275+
FORMAT: I3
276+
LABLAXIS: NSO Spin Sector
277+
SCALETYP: linear
278+
UNITS: spin sector
279+
VALIDMIN: 0
280+
VALIDMAX: 255
281+
VAR_NOTES: Indicates the spin sector when No Scan Operation (NSO) was activated. In NSO, the ESA voltage is set to the first step in the scan and remains fixed until the next cycle boundary.
282+
VAR_TYPE: data
283+
284+
nso_esa_step:
285+
CATDESC: Energy Step When No Scan Operation (NSO) was activated
286+
DEPEND_0: epoch
287+
DISPLAY_TYPE: time_series
288+
FIELDNAM: NSO Energy Step
289+
FILLVAL: 255
290+
FORMAT: I3
291+
LABLAXIS: NSO Energy Step
292+
SCALETYP: linear
293+
UNITS: energy step
294+
VALIDMIN: 0
295+
VALIDMAX: 255
296+
VAR_NOTES: Indicates the energy step when No Scan Operation (NSO) was activated. In NSO, the ESA voltage is set to the first step in the scan and remains fixed until the next cycle boundary.
254297
VAR_TYPE: data
255298

256299
rgfo_half_spin:
257-
CATDESC: When Reduced Gain Factor Operation (RGFO) was activated
300+
CATDESC: Half Spin When Reduced Gain Factor Operation (RGFO) was activated
258301
DEPEND_0: epoch
259302
DISPLAY_TYPE: time_series
260-
FIELDNAM: RGFO Mode
303+
FIELDNAM: RGFO Half Spin
261304
FILLVAL: 255
262305
FORMAT: I3
263306
LABLAXIS: RGFO Half Spin
264307
SCALETYP: linear
265308
UNITS: half spin number
266309
VALIDMIN: 0
267310
VALIDMAX: 255
268-
VAR_NOTES: Indicates the point when Reduced Gain Factor Operation (RGFO) was activated. In RGFO, the Entrance ESA voltage is reduced in order to limit the number of ions that reach the detectors.
311+
VAR_NOTES: Indicates the half spin when Reduced Gain Factor Operation (RGFO) was activated. In RGFO, the Entrance ESA voltage is reduced in order to limit the number of ions that reach the detectors.
312+
VAR_TYPE: data
313+
314+
rgfo_spin_sector:
315+
CATDESC: Spin Sector When Reduced Gain Factor Operation (RGFO) was activated
316+
DEPEND_0: epoch
317+
DISPLAY_TYPE: time_series
318+
FIELDNAM: RGFO Spin Sector
319+
FILLVAL: 255
320+
FORMAT: I3
321+
LABLAXIS: RGFO Spin Sector
322+
SCALETYP: linear
323+
UNITS: spin sector
324+
VALIDMIN: 0
325+
VALIDMAX: 255
326+
VAR_NOTES: Indicates the spin sector when Reduced Gain Factor Operation (RGFO) was activated. In RGFO, the Entrance ESA voltage is reduced in order to limit the number of ions that reach the detectors.
327+
VAR_TYPE: data
328+
329+
rgfo_esa_step:
330+
CATDESC: Energy Step When Reduced Gain Factor Operation (RGFO) was activated
331+
DEPEND_0: epoch
332+
DISPLAY_TYPE: time_series
333+
FIELDNAM: RGFO Energy Step
334+
FILLVAL: 255
335+
FORMAT: I3
336+
LABLAXIS: RGFO Energy Step
337+
SCALETYP: linear
338+
UNITS: energy step
339+
VALIDMIN: 0
340+
VALIDMAX: 255
341+
VAR_NOTES: Indicates the energy step when Reduced Gain Factor Operation (RGFO) was activated. In RGFO, the Entrance ESA voltage is reduced in order to limit the number of ions that reach the detectors.
269342
VAR_TYPE: data
270343

271344
spin_period:

imap_processing/codice/codice_l1a.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""CoDICE L1A processing functions."""
22

3+
import datetime
34
import logging
5+
import os
46

57
import xarray as xr
68
from imap_data_access import ProcessingInputCollection
@@ -52,16 +54,22 @@ def process_l1a( # noqa: PLR0912
5254
"""
5355
# Get science data which is L0 packet file
5456
science_file = dependency.get_file_paths(data_type="l0")[0]
57+
# TODO get the exact time the FSW changed on january 29 and relabel the xml file
58+
# On January 29, 2026, the CoDICE flight software was updated to a new version.
59+
# This update included changes to the packet definitions.
60+
start_date = datetime.datetime.strptime(
61+
os.path.basename(science_file).split("_")[4], "%Y%m%d"
62+
) # Extract the date from the filename
63+
path = imap_module_directory / "codice/packet_definitions/"
64+
if start_date >= datetime.datetime(2026, 1, 29):
65+
xtce_file = path / "imap_codice_packet-definition_20260129_v001.xml"
66+
else:
67+
xtce_file = path / "imap_codice_packet-definition_20250101_v001.xml"
5568

56-
xtce_file = (
57-
imap_module_directory / "codice/packet_definitions/codice_packet_definition.xml"
58-
)
59-
# Decom packet
6069
datasets_by_apid = packet_file_to_datasets(
6170
science_file,
6271
xtce_file,
6372
)
64-
6573
datasets = []
6674
for apid in datasets_by_apid:
6775
if apid not in [CODICEAPID.COD_LO_PHA, CODICEAPID.COD_HI_PHA]:

imap_processing/codice/codice_l1a_de.py

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ def extract_initial_items_from_combined_packets(
5656
spare_1 = np.zeros(n_packets, dtype=np.uint8)
5757
st_bias_gain_mode = np.zeros(n_packets, dtype=np.uint8)
5858
sw_bias_gain_mode = np.zeros(n_packets, dtype=np.uint8)
59-
priority = np.zeros(n_packets, dtype=np.uint8)
6059
suspect = np.zeros(n_packets, dtype=np.uint8)
60+
priority = np.zeros(n_packets, dtype=np.uint8)
6161
compressed = np.zeros(n_packets, dtype=np.uint8)
62+
rgfo_half_spin = np.zeros(n_packets, dtype=np.uint8)
63+
rgfo_esa_step = np.zeros(n_packets, dtype=np.uint8)
64+
rgfo_spin_sector = np.zeros(n_packets, dtype=np.uint8)
65+
nso_half_spin = np.zeros(n_packets, dtype=np.uint8)
66+
nso_spin_sector = np.zeros(n_packets, dtype=np.uint8)
67+
nso_esa_step = np.zeros(n_packets, dtype=np.uint8)
68+
spare_2 = np.zeros(n_packets, dtype=np.uint16)
6269
num_events = np.zeros(n_packets, dtype=np.uint32)
6370
byte_count = np.zeros(n_packets, dtype=np.uint32)
6471

@@ -89,20 +96,42 @@ def extract_initial_items_from_combined_packets(
8996
suspect[pkt_idx] = (mixed_bytes >> 1) & 0x1
9097
# compressed: 1 bit (LSB)
9198
compressed[pkt_idx] = mixed_bytes & 0x1
92-
93-
# Remaining byte-aligned fields
94-
num_events[pkt_idx] = int.from_bytes(event_data[12:16], byteorder="big")
95-
byte_count[pkt_idx] = int.from_bytes(event_data[16:20], byteorder="big")
96-
97-
# Remove the first 20 bytes from event_data (header fields from above)
99+
# After packet version 1, the fields below are present in event_data
100+
if packet_version[pkt_idx] > 1:
101+
# All of the fields below are single byte fields
102+
rgfo_half_spin[pkt_idx] = event_data[12]
103+
rgfo_spin_sector[pkt_idx] = event_data[13]
104+
rgfo_esa_step[pkt_idx] = event_data[14]
105+
nso_half_spin[pkt_idx] = event_data[15]
106+
nso_spin_sector[pkt_idx] = event_data[16]
107+
nso_esa_step[pkt_idx] = event_data[17]
108+
109+
# spare_2 is 16 bits
110+
spare_2[pkt_idx] = int.from_bytes(event_data[18:20], byteorder="big")
111+
# Remaining byte-aligned fields
112+
num_events[pkt_idx] = int.from_bytes(event_data[20:24], byteorder="big")
113+
byte_count[pkt_idx] = int.from_bytes(event_data[24:28], byteorder="big")
114+
# Header is 28 bytes total for version > 1
115+
len_header = 28
116+
else:
117+
# Remaining byte-aligned fields
118+
num_events[pkt_idx] = int.from_bytes(event_data[12:16], byteorder="big")
119+
byte_count[pkt_idx] = int.from_bytes(event_data[16:20], byteorder="big")
120+
# Header is 20 bytes total for version 1
121+
len_header = 20
122+
123+
# Remove the first len_header bytes from event_data (header fields from above)
98124
# Then trim to the number of bytes indicated by byte_count
99-
if byte_count[pkt_idx] > len(event_data) - 20:
125+
if byte_count[pkt_idx] > len(event_data) - len_header:
100126
raise ValueError(
101127
f"Byte count {byte_count[pkt_idx]} exceeds available "
102-
f"data length {len(event_data) - 20} for packet index {pkt_idx}."
128+
f"data length {len(event_data) - len_header} for packet index"
129+
f" {pkt_idx}."
103130
)
104-
packets.event_data.data[pkt_idx] = event_data[20 : 20 + byte_count[pkt_idx]]
105131

132+
packets.event_data.data[pkt_idx] = event_data[
133+
len_header : byte_count[pkt_idx] + len_header
134+
]
106135
if compressed[pkt_idx]:
107136
packets.event_data.data[pkt_idx] = decompress(
108137
packets.event_data.data[pkt_idx],
@@ -120,6 +149,13 @@ def extract_initial_items_from_combined_packets(
120149
packets["priority"] = xr.DataArray(priority, dims=["epoch"])
121150
packets["suspect"] = xr.DataArray(suspect, dims=["epoch"])
122151
packets["compressed"] = xr.DataArray(compressed, dims=["epoch"])
152+
packets["rgfo_half_spin"] = xr.DataArray(rgfo_half_spin, dims=["epoch"])
153+
packets["rgfo_spin_sector"] = xr.DataArray(rgfo_spin_sector, dims=["epoch"])
154+
packets["rgfo_esa_step"] = xr.DataArray(rgfo_esa_step, dims=["epoch"])
155+
packets["nso_half_spin"] = xr.DataArray(nso_half_spin, dims=["epoch"])
156+
packets["nso_spin_sector"] = xr.DataArray(nso_spin_sector, dims=["epoch"])
157+
packets["nso_esa_step"] = xr.DataArray(nso_esa_step, dims=["epoch"])
158+
packets["spare_2"] = xr.DataArray(spare_2, dims=["epoch"])
123159
packets["num_events"] = xr.DataArray(num_events, dims=["epoch"])
124160
packets["byte_count"] = xr.DataArray(byte_count, dims=["epoch"])
125161

@@ -203,6 +239,7 @@ def _create_dataset_coords(
203239
collapse_table=0,
204240
three_d_collapsed=0,
205241
view_id=0,
242+
compression=CoDICECompression.LOSSLESS.value, # DE data is always lossless
206243
)
207244
epochs, epochs_delta = get_codice_epoch_time(
208245
packets["acq_start_seconds"].isel(epoch=epoch_slice),
@@ -316,7 +353,6 @@ def _unpack_and_store_events(
316353
n_events = int(num_events_arr[pkt_idx])
317354
if n_events == 0:
318355
continue
319-
320356
# Extract and byte-reverse events for LSB unpacking
321357
pkt_bytes = np.asarray(event_data_arr[pkt_idx], dtype=np.uint8)
322358
pkt_bytes = pkt_bytes.reshape(n_events, 8)[:, ::-1]
@@ -450,7 +486,16 @@ def process_de_data(
450486

451487
# Add per-epoch metadata from first packet of each epoch
452488
epoch_slice = slice(None, None, num_priorities)
453-
for var in ["sw_bias_gain_mode", "st_bias_gain_mode"]:
489+
for var in [
490+
"sw_bias_gain_mode",
491+
"st_bias_gain_mode",
492+
"rgfo_esa_step",
493+
"rgfo_half_spin",
494+
"rgfo_spin_sector",
495+
"nso_esa_step",
496+
"nso_half_spin",
497+
"nso_spin_sector",
498+
]:
454499
de_data[var] = xr.DataArray(
455500
packets[var].isel(epoch=epoch_slice).values,
456501
dims=["epoch"],
@@ -482,7 +527,6 @@ def process_de_data(
482527
dims=["epoch", "priority"],
483528
attrs=cdf_attrs.get_variable_attributes("de_2d_attrs"),
484529
)
485-
486530
# Reshape packet arrays for validation and assignment
487531
priorities_2d = packets.priority.values.reshape(num_epochs, num_priorities)
488532
num_events_2d = packets.num_events.values.reshape(num_epochs, num_priorities)
@@ -534,6 +578,7 @@ def l1a_direct_event(unpacked_dataset: xr.Dataset, apid: int) -> xr.Dataset:
534578
packets = combine_segmented_packets(
535579
unpacked_dataset, binary_field_name="event_data"
536580
)
581+
537582
packets = extract_initial_items_from_combined_packets(packets)
538583

539584
# Gather the CDF attributes

imap_processing/codice/codice_l1a_hi_counters_aggregated.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from imap_processing.codice import constants
1111
from imap_processing.codice.decompress import decompress
1212
from imap_processing.codice.utils import (
13+
CoDICECompression,
1314
ViewTabInfo,
1415
get_codice_epoch_time,
1516
get_counters_aggregated_pattern,
@@ -61,6 +62,7 @@ def l1a_hi_counters_aggregated(
6162
sensor=view_tab_info["sensor"],
6263
three_d_collapsed=view_tab_info["3d_collapse"],
6364
collapse_table=view_tab_info["collapse_table"],
65+
compression=view_tab_info["compression"],
6466
)
6567

6668
if view_tab_obj.sensor != 1:
@@ -86,7 +88,7 @@ def l1a_hi_counters_aggregated(
8688
binary_data_list = unpacked_dataset["data"].values
8789
byte_count_list = unpacked_dataset["byte_count"].values
8890

89-
compression_algorithm = constants.HI_COMPRESSION_ID_LOOKUP[view_tab_obj.view_id]
91+
compression_algorithm = CoDICECompression(view_tab_obj.compression)
9092

9193
# The decompressed data in the shape of (epoch, n). Then reshape later.
9294
decompressed_data = [

imap_processing/codice/codice_l1a_hi_counters_singles.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from imap_processing.codice import constants
1111
from imap_processing.codice.decompress import decompress
1212
from imap_processing.codice.utils import (
13+
CoDICECompression,
1314
ViewTabInfo,
1415
get_codice_epoch_time,
1516
get_collapse_pattern_shape,
@@ -59,6 +60,7 @@ def l1a_hi_counters_singles(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.
5960
sensor=view_tab_info["sensor"],
6061
three_d_collapsed=view_tab_info["3d_collapse"],
6162
collapse_table=view_tab_info["collapse_table"],
63+
compression=view_tab_info["compression"],
6264
)
6365

6466
if view_tab_obj.sensor != 1:
@@ -78,7 +80,7 @@ def l1a_hi_counters_singles(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.
7880
# spin sector size is 1.
7981
inst_az = collapse_shape[1]
8082

81-
compression_algorithm = constants.HI_COMPRESSION_ID_LOOKUP[view_tab_obj.view_id]
83+
compression_algorithm = CoDICECompression(view_tab_obj.compression)
8284
# Decompress data using byte count information from decommed data
8385
binary_data_list = unpacked_dataset["data"].values
8486
byte_count_list = unpacked_dataset["byte_count"].values

imap_processing/codice/codice_l1a_hi_omni.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from imap_processing.codice import constants
1111
from imap_processing.codice.decompress import decompress
1212
from imap_processing.codice.utils import (
13+
CoDICECompression,
1314
ViewTabInfo,
1415
apply_replacements_to_attrs,
1516
get_codice_epoch_time,
@@ -61,6 +62,7 @@ def l1a_hi_omni(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.Dataset:
6162
sensor=view_tab_info["sensor"],
6263
three_d_collapsed=view_tab_info["3d_collapse"],
6364
collapse_table=view_tab_info["collapse_table"],
65+
compression=view_tab_info["compression"],
6466
)
6567

6668
if view_tab_obj.sensor != 1:
@@ -71,7 +73,7 @@ def l1a_hi_omni(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.Dataset:
7173
species_names = species_data.keys()
7274
logical_source_id = "imap_codice_l1a_hi-omni"
7375

74-
compression_algorithm = constants.HI_COMPRESSION_ID_LOOKUP[view_tab_obj.view_id]
76+
compression_algorithm = CoDICECompression(view_tab_obj.compression)
7577
# Decompress data using byte count information from decommed data
7678
binary_data_list = unpacked_dataset["data"].values
7779
byte_count_list = unpacked_dataset["byte_count"].values

0 commit comments

Comments
 (0)