Skip to content

Commit 4c4c107

Browse files
authored
MNT/ENH: Decommutate Ultra packets with derived values (IMAP-Science-Operations-Center#2323)
The Ultra packet definition has engineering unit conversions, so we can call the packet_file_to_datasets with `use_derived_value=True` to get the conversion. These are then l1b products to follow along with the mission convention. We handle that directly in the l1a processing by calling the routine again with a different flag. The metadata for l1a can be re-used, except that FILLVAL, MINVAL, and MAXVAL are all skipped for l1b because those values try to get converted to the datatype, but the datatype has changed with enumeration and conversions from uint8 to str or uint8 to float, etc. For ease of use, we just skip those metadata fields for now.
1 parent 2ccebf9 commit 4c4c107

3 files changed

Lines changed: 140 additions & 75 deletions

File tree

imap_processing/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ def do_processing(
14351435
f"Unexpected science_files found for ULTRA L1A:"
14361436
f"{science_files}. Expected only one dependency."
14371437
)
1438-
datasets = ultra_l1a.ultra_l1a(science_files[0])
1438+
datasets = ultra_l1a.ultra_l1a(science_files[0], create_derived_l1b=True)
14391439
elif self.data_level == "l1b":
14401440
science_files = dependencies.get_file_paths(source="ultra", data_type="l1a")
14411441
l1a_dict = {

imap_processing/tests/ultra/unit/test_ultra_l1a.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,13 @@ def test_cdf_rates(ccsds_path_theta_0):
178178

179179
def test_cdf_energy_rates(ccsds_path_functional):
180180
"""Tests that CDF file can be created."""
181-
test_data = ultra_l1a(ccsds_path_functional, apid_input=ULTRA_ENERGY_RATES.apid[0])
182-
test_data[0].attrs["Data_version"] = "999"
181+
test_data = ultra_l1a(
182+
ccsds_path_functional,
183+
apid_input=ULTRA_ENERGY_RATES.apid[0],
184+
create_derived_l1b=True,
185+
)
186+
assert len(test_data) == 2 # l1a and l1b
187+
183188
test_data[0].attrs["Repointing"] = "repoint99999"
184189
test_data_path = write_cdf(test_data[0], istp=True)
185190

@@ -189,6 +194,17 @@ def test_cdf_energy_rates(ccsds_path_functional):
189194
== "imap_ultra_l1a_45sensor-energy-rates_20240122-repoint99999_v999.cdf"
190195
)
191196

197+
# L1b dataset
198+
assert "1B" in test_data[1].attrs["Data_type"]
199+
test_data[1].attrs["Repointing"] = "repoint99999"
200+
test_data_path = write_cdf(test_data[1], istp=True)
201+
202+
assert test_data_path.exists()
203+
assert (
204+
test_data_path.name
205+
== "imap_ultra_l1b_45sensor-energy-rates_20240122-repoint99999_v999.cdf"
206+
)
207+
192208

193209
def test_cdf_macrodump(ccsds_path_functional):
194210
"""Tests that CDF file can be created."""

imap_processing/ultra/l1a/ultra_l1a.py

Lines changed: 121 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444

4545
def ultra_l1a( # noqa: PLR0912
46-
packet_file: str, apid_input: int | None = None
46+
packet_file: str, apid_input: int | None = None, create_derived_l1b: bool = False
4747
) -> list[xr.Dataset]:
4848
"""
4949
Will process ULTRA L0 data into L1A CDF files at output_filepath.
@@ -54,6 +54,8 @@ def ultra_l1a( # noqa: PLR0912
5454
Path to the CCSDS data packet file.
5555
apid_input : Optional[int]
5656
Optional apid.
57+
create_derived_l1b : bool
58+
Whether to create the l1b datasets with derived values.
5759
5860
Returns
5961
-------
@@ -64,7 +66,17 @@ def ultra_l1a( # noqa: PLR0912
6466
f"{imap_module_directory}/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml"
6567
)
6668

69+
# Keep a list to track the two versions, l1a and l1b with the derived values.
70+
decommutated_packet_datasets = []
6771
datasets_by_apid = packet_file_to_datasets(packet_file, xtce)
72+
decommutated_packet_datasets.append(datasets_by_apid)
73+
if create_derived_l1b:
74+
# For the housekeeping products, we can create the l1b at the same time
75+
# as the l1a since there is no additional processing needed.
76+
datasets_by_apid = packet_file_to_datasets(
77+
packet_file, xtce, use_derived_value=True
78+
)
79+
decommutated_packet_datasets.append(datasets_by_apid)
6880

6981
output_datasets = []
7082

@@ -109,77 +121,114 @@ def ultra_l1a( # noqa: PLR0912
109121
attr_mgr.add_instrument_global_attrs("ultra")
110122
attr_mgr.add_instrument_variable_attrs("ultra", "l1a")
111123

112-
for apid in apids:
113-
if apid in ULTRA_AUX.apid:
114-
decom_ultra_dataset = datasets_by_apid[apid]
115-
gattr_key = ULTRA_AUX.logical_source[ULTRA_AUX.apid.index(apid)]
116-
elif apid in all_l1a_image_apids:
117-
packet_props = all_l1a_image_apids[apid]
118-
decom_ultra_dataset = process_ultra_tof(
119-
datasets_by_apid[apid], packet_props
120-
)
121-
gattr_key = packet_props.logical_source[packet_props.apid.index(apid)]
122-
elif apid in ULTRA_RATES.apid:
123-
decom_ultra_dataset = process_ultra_rates(datasets_by_apid[apid])
124-
decom_ultra_dataset = decom_ultra_dataset.drop_vars("fastdata_00")
125-
gattr_key = ULTRA_RATES.logical_source[ULTRA_RATES.apid.index(apid)]
126-
elif apid in ULTRA_ENERGY_RATES.apid:
127-
decom_ultra_dataset = process_ultra_energy_rates(datasets_by_apid[apid])
128-
decom_ultra_dataset = decom_ultra_dataset.drop_vars("ratedata")
129-
gattr_key = ULTRA_ENERGY_RATES.logical_source[
130-
ULTRA_ENERGY_RATES.apid.index(apid)
131-
]
132-
elif apid in all_event_apids:
133-
decom_ultra_dataset = process_ultra_events(datasets_by_apid[apid], apid)
134-
gattr_key = all_event_apids[apid]
124+
for i, datasets_by_apid in enumerate(decommutated_packet_datasets):
125+
for apid in apids:
126+
if apid in ULTRA_AUX.apid:
127+
decom_ultra_dataset = datasets_by_apid[apid]
128+
gattr_key = ULTRA_AUX.logical_source[ULTRA_AUX.apid.index(apid)]
129+
elif apid in all_l1a_image_apids:
130+
packet_props = all_l1a_image_apids[apid]
131+
decom_ultra_dataset = process_ultra_tof(
132+
datasets_by_apid[apid], packet_props
133+
)
134+
gattr_key = packet_props.logical_source[packet_props.apid.index(apid)]
135+
elif apid in ULTRA_RATES.apid:
136+
decom_ultra_dataset = process_ultra_rates(datasets_by_apid[apid])
137+
decom_ultra_dataset = decom_ultra_dataset.drop_vars("fastdata_00")
138+
gattr_key = ULTRA_RATES.logical_source[ULTRA_RATES.apid.index(apid)]
139+
elif apid in ULTRA_ENERGY_RATES.apid:
140+
decom_ultra_dataset = process_ultra_energy_rates(datasets_by_apid[apid])
141+
decom_ultra_dataset = decom_ultra_dataset.drop_vars("ratedata")
142+
gattr_key = ULTRA_ENERGY_RATES.logical_source[
143+
ULTRA_ENERGY_RATES.apid.index(apid)
144+
]
145+
elif apid in all_event_apids:
146+
# We don't want to process the event l1b datasets since those l1b
147+
# products need more information
148+
if i == 1:
149+
continue
150+
decom_ultra_dataset = process_ultra_events(datasets_by_apid[apid], apid)
151+
gattr_key = all_event_apids[apid]
152+
# Add coordinate attributes
153+
attrs = attr_mgr.get_variable_attributes("event_id")
154+
decom_ultra_dataset.coords["event_id"].attrs.update(attrs)
155+
elif apid in ULTRA_ENERGY_SPECTRA.apid:
156+
decom_ultra_dataset = process_ultra_energy_spectra(
157+
datasets_by_apid[apid]
158+
)
159+
decom_ultra_dataset = decom_ultra_dataset.drop_vars("compdata")
160+
gattr_key = ULTRA_ENERGY_SPECTRA.logical_source[
161+
ULTRA_ENERGY_SPECTRA.apid.index(apid)
162+
]
163+
elif apid in ULTRA_MACROS_CHECKSUM.apid:
164+
decom_ultra_dataset = process_ultra_macros_checksum(
165+
datasets_by_apid[apid]
166+
)
167+
gattr_key = ULTRA_MACROS_CHECKSUM.logical_source[
168+
ULTRA_MACROS_CHECKSUM.apid.index(apid)
169+
]
170+
elif apid in ULTRA_HK.apid:
171+
decom_ultra_dataset = datasets_by_apid[apid]
172+
gattr_key = ULTRA_HK.logical_source[ULTRA_HK.apid.index(apid)]
173+
elif apid in ULTRA_CMD_TEXT.apid:
174+
decom_ultra_dataset = datasets_by_apid[apid]
175+
decoded_strings = [
176+
s.decode("ascii").rstrip("\x00")
177+
for s in decom_ultra_dataset["text"].values
178+
]
179+
decom_ultra_dataset = decom_ultra_dataset.drop_vars("text")
180+
decom_ultra_dataset["text"] = xr.DataArray(
181+
decoded_strings,
182+
dims=["epoch"],
183+
coords={"epoch": decom_ultra_dataset["epoch"]},
184+
)
185+
gattr_key = ULTRA_CMD_TEXT.logical_source[
186+
ULTRA_CMD_TEXT.apid.index(apid)
187+
]
188+
elif apid in ULTRA_CMD_ECHO.apid:
189+
decom_ultra_dataset = process_ultra_cmd_echo(datasets_by_apid[apid])
190+
gattr_key = ULTRA_CMD_ECHO.logical_source[
191+
ULTRA_CMD_ECHO.apid.index(apid)
192+
]
193+
else:
194+
logger.error(f"APID {apid} not recognized.")
195+
continue
196+
197+
decom_ultra_dataset.attrs.update(attr_mgr.get_global_attributes(gattr_key))
198+
199+
if i == 1:
200+
# Derived values dataset at l1b
201+
# We already have the l1a attributes, just update the l1a -> l1b
202+
# in the metadata.
203+
decom_ultra_dataset.attrs["Data_type"] = decom_ultra_dataset.attrs[
204+
"Data_type"
205+
].replace("1A", "1B")
206+
decom_ultra_dataset.attrs["Logical_source"] = decom_ultra_dataset.attrs[
207+
"Logical_source"
208+
].replace("l1a", "l1b")
209+
decom_ultra_dataset.attrs["Logical_source_description"] = (
210+
decom_ultra_dataset.attrs["Logical_source_description"].replace(
211+
"1A", "1B"
212+
)
213+
)
214+
215+
# Add data variable attributes
216+
for key in decom_ultra_dataset.data_vars:
217+
attrs = attr_mgr.get_variable_attributes(key.lower())
218+
decom_ultra_dataset.data_vars[key].attrs.update(attrs)
219+
if i == 1:
220+
# For l1b datasets, the FILLVAL and VALIDMIN/MAX may be
221+
# different datatypes, so we can't use them directly from l1a.
222+
# just remove them for now since we don't really have a need for
223+
# for them currently.
224+
for attr_key in ["FILLVAL", "VALIDMIN", "VALIDMAX"]:
225+
if attr_key in decom_ultra_dataset.data_vars[key].attrs:
226+
decom_ultra_dataset.data_vars[key].attrs.pop(attr_key)
227+
135228
# Add coordinate attributes
136-
attrs = attr_mgr.get_variable_attributes("event_id")
137-
decom_ultra_dataset.coords["event_id"].attrs.update(attrs)
138-
elif apid in ULTRA_ENERGY_SPECTRA.apid:
139-
decom_ultra_dataset = process_ultra_energy_spectra(datasets_by_apid[apid])
140-
decom_ultra_dataset = decom_ultra_dataset.drop_vars("compdata")
141-
gattr_key = ULTRA_ENERGY_SPECTRA.logical_source[
142-
ULTRA_ENERGY_SPECTRA.apid.index(apid)
143-
]
144-
elif apid in ULTRA_MACROS_CHECKSUM.apid:
145-
decom_ultra_dataset = process_ultra_macros_checksum(datasets_by_apid[apid])
146-
gattr_key = ULTRA_MACROS_CHECKSUM.logical_source[
147-
ULTRA_MACROS_CHECKSUM.apid.index(apid)
148-
]
149-
elif apid in ULTRA_HK.apid:
150-
decom_ultra_dataset = datasets_by_apid[apid]
151-
gattr_key = ULTRA_HK.logical_source[ULTRA_HK.apid.index(apid)]
152-
elif apid in ULTRA_CMD_TEXT.apid:
153-
decom_ultra_dataset = datasets_by_apid[apid]
154-
decoded_strings = [
155-
s.decode("ascii").rstrip("\x00")
156-
for s in decom_ultra_dataset["text"].values
157-
]
158-
decom_ultra_dataset = decom_ultra_dataset.drop_vars("text")
159-
decom_ultra_dataset["text"] = xr.DataArray(
160-
decoded_strings,
161-
dims=["epoch"],
162-
coords={"epoch": decom_ultra_dataset["epoch"]},
163-
)
164-
gattr_key = ULTRA_CMD_TEXT.logical_source[ULTRA_CMD_TEXT.apid.index(apid)]
165-
elif apid in ULTRA_CMD_ECHO.apid:
166-
decom_ultra_dataset = process_ultra_cmd_echo(datasets_by_apid[apid])
167-
gattr_key = ULTRA_CMD_ECHO.logical_source[ULTRA_CMD_ECHO.apid.index(apid)]
168-
else:
169-
logger.error(f"APID {apid} not recognized.")
170-
continue
171-
172-
decom_ultra_dataset.attrs.update(attr_mgr.get_global_attributes(gattr_key))
173-
174-
# Add data variable attributes
175-
for key in decom_ultra_dataset.data_vars:
176-
attrs = attr_mgr.get_variable_attributes(key.lower())
177-
decom_ultra_dataset.data_vars[key].attrs.update(attrs)
178-
179-
# Add coordinate attributes
180-
attrs = attr_mgr.get_variable_attributes("epoch", check_schema=False)
181-
decom_ultra_dataset.coords["epoch"].attrs.update(attrs)
182-
183-
output_datasets.append(decom_ultra_dataset)
229+
attrs = attr_mgr.get_variable_attributes("epoch", check_schema=False)
230+
decom_ultra_dataset.coords["epoch"].attrs.update(attrs)
231+
232+
output_datasets.append(decom_ultra_dataset)
184233

185234
return output_datasets

0 commit comments

Comments
 (0)