diff --git a/imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml b/imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml index d4e5eacbb3..983b496c10 100644 --- a/imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +++ b/imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml @@ -44,6 +44,18 @@ imap_ultra_l1a_90sensor-rates: Logical_source: imap_ultra_l1a_90sensor-rates Logical_source_description: IMAP-Ultra Instrument Level-1A Rates Data. +imap_ultra_l1a_45sensor-energy-rates: + <<: *instrument_base + Data_type: L1A_Energy_Rates>Level-1A Energy Rates + Logical_source: imap_ultra_l1a_45sensor-energy-rates + Logical_source_description: IMAP-Ultra Instrument Level-1A Energy Rates Data. + +imap_ultra_l1a_90sensor-energy-rates: + <<: *instrument_base + Data_type: L1A_Energy_Rates>Level-1A Energy Rates + Logical_source: imap_ultra_l1a_90sensor-energy-rates + Logical_source_description: IMAP-Ultra Instrument Level-1A Energy Rates Data. + imap_ultra_l1a_45sensor-de: <<: *instrument_base Data_type: L1A_DE>Level-1A Direct Event diff --git a/imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml b/imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml index 7d1dae9903..a5d41f6d95 100644 --- a/imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml @@ -83,6 +83,78 @@ sid: UNITS: " " DEPEND_0: epoch +ssd0_energy_led: + <<: *default_uint32 + CATDESC: SSD0 Energy LED. + FIELDNAM: ssd0_energy_led + LABLAXIS: ssd0 energy led + UNITS: " " + DEPEND_0: epoch + +ssd1_energy_led: + <<: *default_uint32 + CATDESC: SSD1 Energy LED. + FIELDNAM: ssd1_energy_led + LABLAXIS: ssd1 energy led + UNITS: " " + DEPEND_0: epoch + +ssd2_energy_led: + <<: *default_uint32 + CATDESC: SSD2 Energy LED. + FIELDNAM: ssd2_energy_led + LABLAXIS: ssd2 energy led + UNITS: " " + DEPEND_0: epoch + +ssd3_energy_led: + <<: *default_uint32 + CATDESC: SSD3 Energy LED. + FIELDNAM: ssd3_energy_led + LABLAXIS: ssd3 energy led + UNITS: " " + DEPEND_0: epoch + +ssd4_energy_led: + <<: *default_uint32 + CATDESC: SSD4 Energy LED. + FIELDNAM: ssd4_energy_led + LABLAXIS: ssd4 energy led + UNITS: " " + DEPEND_0: epoch + +ssd5_energy_led: + <<: *default_uint32 + CATDESC: SSD5 Energy LED. + FIELDNAM: ssd5_energy_led + LABLAXIS: ssd5 energy led + UNITS: " " + DEPEND_0: epoch + +ssd6_energy_led: + <<: *default_uint32 + CATDESC: SSD6 Energy LED. + FIELDNAM: ssd6_energy_led + LABLAXIS: ssd6 energy led + UNITS: " " + DEPEND_0: epoch + +ssd7_energy_led: + <<: *default_uint32 + CATDESC: SSD7 Energy LED. + FIELDNAM: ssd7_energy_led + LABLAXIS: ssd7 energy led + UNITS: " " + DEPEND_0: epoch + +processed_events: + <<: *default_uint32 + CATDESC: Processed Events. + FIELDNAM: processed_events + LABLAXIS: processed events + UNITS: " " + DEPEND_0: epoch + spin: <<: *default_uint32 CATDESC: Spin number. diff --git a/imap_processing/tests/external_test_data_config.py b/imap_processing/tests/external_test_data_config.py index 4d7577c2b6..d7aef5159a 100644 --- a/imap_processing/tests/external_test_data_config.py +++ b/imap_processing/tests/external_test_data_config.py @@ -63,6 +63,8 @@ ("IMAP-Ultra45_r1_L1_V0_shortened.csv", "ultra/data/l1/"), # Ultra + ("ultra45_raw_sc_ultranrgrates_FM45_UltraFM45_Functional_" + "2024-01-22T0105_20240122T010548.csv", "ultra/data/l0/"), ("imap_ultra_l0_raw_20260924_v001.pkts", "ultra/data/l0/"), ("imap_ultra_l1b_45sensor-de_20240207_v999.cdf", "ultra/data/l1/"), ("ultra-90_raw_event_data_shortened.csv", "ultra/data/l1/"), diff --git a/imap_processing/tests/ultra/unit/conftest.py b/imap_processing/tests/ultra/unit/conftest.py index 9705575d73..7e974c5b95 100644 --- a/imap_processing/tests/ultra/unit/conftest.py +++ b/imap_processing/tests/ultra/unit/conftest.py @@ -6,12 +6,14 @@ from imap_processing import imap_module_directory from imap_processing.ultra.l0.decom_ultra import ( + process_ultra_energy_rates, process_ultra_events, process_ultra_rates, process_ultra_tof, ) from imap_processing.ultra.l0.ultra_utils import ( ULTRA_AUX, + ULTRA_ENERGY_RATES, ULTRA_EVENTS, ULTRA_RATES, ULTRA_TOF, @@ -86,6 +88,19 @@ def ccsds_path_tof(): ) +@pytest.fixture +def ccsds_path_functional(): + """Returns the ccsds directory.""" + return ( + imap_module_directory + / "tests" + / "ultra" + / "data" + / "l0" + / "FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS" + ) + + @pytest.fixture def xtce_path(): """Returns the xtce image rates directory.""" @@ -107,6 +122,16 @@ def rates_test_path(): return imap_module_directory / "tests" / "ultra" / "data" / "l0" / filename +@pytest.fixture +def energy_rates_test_path(): + """Returns the xtce image rates test data directory.""" + filename = ( + "ultra45_raw_sc_ultranrgrates_FM45_UltraFM45_Functional" + "_2024-01-22T0105_20240122T010548.csv" + ) + return imap_module_directory / "tests" / "ultra" / "data" / "l0" / filename + + @pytest.fixture def aux_test_path(): """Returns the xtce auxiliary test data directory.""" @@ -149,9 +174,11 @@ def decom_test_data(request, xtce_path): ULTRA_TOF.apid[0]: process_ultra_tof, ULTRA_EVENTS.apid[0]: process_ultra_events, ULTRA_RATES.apid[0]: process_ultra_rates, + ULTRA_ENERGY_RATES.apid[0]: process_ultra_energy_rates, ULTRA_TOF.apid[1]: process_ultra_tof, ULTRA_EVENTS.apid[1]: process_ultra_events, ULTRA_RATES.apid[1]: process_ultra_rates, + ULTRA_ENERGY_RATES.apid[1]: process_ultra_energy_rates, } process_function = strategy_dict.get(apid, lambda *args: False) diff --git a/imap_processing/tests/ultra/unit/test_decom_apid_882.py b/imap_processing/tests/ultra/unit/test_decom_apid_882.py new file mode 100644 index 0000000000..416b088f90 --- /dev/null +++ b/imap_processing/tests/ultra/unit/test_decom_apid_882.py @@ -0,0 +1,51 @@ +import json + +import numpy as np +import pandas as pd +import pytest + +from imap_processing.ultra.l0.ultra_utils import ENERGY_RATES_KEYS, ULTRA_ENERGY_RATES + + +@pytest.mark.parametrize( + "decom_test_data", + [ + pytest.param( + { + "apid": ULTRA_ENERGY_RATES.apid[0], + "filename": "FM45_UltraFM45_Functional_" + "2024-01-22T0105_20240122T010548.CCSDS", + } + ) + ], + indirect=True, +) +@pytest.mark.external_test_data +def test_image_rate_decom(decom_test_data, energy_rates_test_path): + """This function reads validation data and checks that decom data + matches validation data for image rate packet""" + decom_ultra = decom_test_data + + df = pd.read_csv(energy_rates_test_path, index_col="MET") + total_packets = 315 + + np.testing.assert_array_equal(df.SID, decom_ultra["sid"]) + np.testing.assert_array_equal(df.Spin, decom_ultra["spin"]) + np.testing.assert_array_equal(df.AbortFlag, decom_ultra["abortflag"]) + np.testing.assert_array_equal(df.StartDelay, decom_ultra["startdelay"]) + + # Spot-check first packet + t0 = decom_ultra["shcoarse"][0] + expected_arr0 = json.loads(df.loc[int(t0)].Counts) + arr = [] + for name in ENERGY_RATES_KEYS: + arr.append(decom_ultra[name][0]) + assert expected_arr0 == arr + + # Spot-check last packet + tn = decom_ultra["shcoarse"][total_packets - 1] + expected_arrn = json.loads(df.loc[int(tn)].Counts) + arr = [] + for name in ENERGY_RATES_KEYS: + arr.append(decom_ultra[name][total_packets - 1]) + assert expected_arrn == arr diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1a.py b/imap_processing/tests/ultra/unit/test_ultra_l1a.py index 061d12a9a7..2d5ad677a5 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1a.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1a.py @@ -8,6 +8,7 @@ from imap_processing.ultra.l0.decom_ultra import get_event_id from imap_processing.ultra.l0.ultra_utils import ( ULTRA_AUX, + ULTRA_ENERGY_RATES, ULTRA_EVENTS, ULTRA_RATES, ULTRA_TOF, @@ -96,6 +97,20 @@ def test_cdf_rates(ccsds_path_theta_0): ) +def test_cdf_energy_rates(ccsds_path_functional): + """Tests that CDF file can be created.""" + test_data = ultra_l1a(ccsds_path_functional, apid_input=ULTRA_ENERGY_RATES.apid[0]) + test_data[0].attrs["Data_version"] = "999" + test_data[0].attrs["Repointing"] = "repoint99999" + test_data_path = write_cdf(test_data[0], istp=True) + + assert test_data_path.exists() + assert ( + test_data_path.name + == "imap_ultra_l1a_45sensor-energy-rates_20240122-repoint99999_v999.cdf" + ) + + def test_cdf_tof(ccsds_path_theta_0): """Tests that CDF file can be created.""" test_data = ultra_l1a(ccsds_path_theta_0, apid_input=ULTRA_TOF.apid[0]) diff --git a/imap_processing/ultra/l0/decom_ultra.py b/imap_processing/ultra/l0/decom_ultra.py index 477a4e53db..6193f3751d 100644 --- a/imap_processing/ultra/l0/decom_ultra.py +++ b/imap_processing/ultra/l0/decom_ultra.py @@ -15,8 +15,10 @@ read_image_raw_events_binary, ) from imap_processing.ultra.l0.ultra_utils import ( + ENERGY_RATES_KEYS, EVENT_FIELD_RANGES, RATES_KEYS, + ULTRA_ENERGY_RATES, ULTRA_RATES, ULTRA_TOF, ) @@ -242,3 +244,38 @@ def process_ultra_rates(ds: xr.Dataset) -> xr.Dataset: ds[key] = xr.DataArray(np.array(values), dims=["epoch"]) return ds + + +def process_ultra_energy_rates(ds: xr.Dataset) -> xr.Dataset: + """ + Unpack and decode Ultra ENERGY RATES packets. + + Parameters + ---------- + ds : xarray.Dataset + Energy rates dataset. + + Returns + ------- + dataset : xarray.Dataset + Dataset containing the decoded and decompressed data. + """ + decom_data = defaultdict(list) + + for rate in ds["ratedata"]: + raw_binary_string = convert_to_binary_string(rate.item()) + decompressed_data = decompress_binary( + raw_binary_string, + cast(int, ULTRA_ENERGY_RATES.width), + cast(int, ULTRA_ENERGY_RATES.block), + cast(int, ULTRA_ENERGY_RATES.len_array), + cast(int, ULTRA_ENERGY_RATES.mantissa_bit_length), + ) + + for index in range(cast(int, ULTRA_ENERGY_RATES.len_array)): + decom_data[ENERGY_RATES_KEYS[index]].append(decompressed_data[index]) + + for key, values in decom_data.items(): + ds[key] = xr.DataArray(np.array(values), dims=["epoch"]) + + return ds diff --git a/imap_processing/ultra/l0/ultra_utils.py b/imap_processing/ultra/l0/ultra_utils.py index adb215ec09..a26847b645 100644 --- a/imap_processing/ultra/l0/ultra_utils.py +++ b/imap_processing/ultra/l0/ultra_utils.py @@ -40,6 +40,18 @@ class PacketProperties(NamedTuple): len_array=48, mantissa_bit_length=12, ) +ULTRA_ENERGY_RATES = PacketProperties( + apid=[882, 946], + logical_source=[ + "imap_ultra_l1a_45sensor-energy-rates", + "imap_ultra_l1a_90sensor-energy-rates", + ], + addition_to_logical_desc="Image Rates", + width=5, + block=16, + len_array=11, + mantissa_bit_length=12, +) ULTRA_TOF = PacketProperties( apid=[883, 947], logical_source=[ @@ -324,6 +336,32 @@ class PacketProperties(NamedTuple): ] +ENERGY_RATES_KEYS = [ + # SSD0 Energy LED + "ssd0_energy_led", + # SSD1 Energy LED + "ssd1_energy_led", + # SSD2 Energy LED + "ssd2_energy_led", + # SSD3 Energy LED + "ssd3_energy_led", + # SSD4 Energy LED + "ssd4_energy_led", + # SSD5 Energy LED + "ssd5_energy_led", + # SSD6 Energy LED + "ssd6_energy_led", + # SSD7 Energy LED + "ssd7_energy_led", + # Event Active Time + "event_active_time", + # FIFO Valid Events + "fifo_valid_events", + # Processed Events + "processed_events", +] + + def parse_event(event_binary: str) -> dict: """ Parse a binary string representing a single event. diff --git a/imap_processing/ultra/l1a/ultra_l1a.py b/imap_processing/ultra/l1a/ultra_l1a.py index 8b7ce9e8cd..7310129abf 100644 --- a/imap_processing/ultra/l1a/ultra_l1a.py +++ b/imap_processing/ultra/l1a/ultra_l1a.py @@ -8,6 +8,7 @@ from imap_processing import imap_module_directory from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes from imap_processing.ultra.l0.decom_ultra import ( + process_ultra_energy_rates, process_ultra_events, process_ultra_rates, process_ultra_tof, @@ -15,6 +16,7 @@ from imap_processing.ultra.l0.ultra_utils import ( ULTRA_AUX, ULTRA_CMD_TEXT, + ULTRA_ENERGY_RATES, ULTRA_EVENTS, ULTRA_HK, ULTRA_RATES, @@ -75,6 +77,12 @@ def ultra_l1a(packet_file: str, apid_input: Optional[int] = None) -> list[xr.Dat decom_ultra_dataset = process_ultra_rates(datasets_by_apid[apid]) decom_ultra_dataset = decom_ultra_dataset.drop_vars("fastdata_00") gattr_key = ULTRA_RATES.logical_source[ULTRA_RATES.apid.index(apid)] + elif apid in ULTRA_ENERGY_RATES.apid: + decom_ultra_dataset = process_ultra_energy_rates(datasets_by_apid[apid]) + decom_ultra_dataset = decom_ultra_dataset.drop_vars("ratedata") + gattr_key = ULTRA_ENERGY_RATES.logical_source[ + ULTRA_ENERGY_RATES.apid.index(apid) + ] elif apid in ULTRA_EVENTS.apid: decom_ultra_dataset = process_ultra_events(datasets_by_apid[apid]) gattr_key = ULTRA_EVENTS.logical_source[ULTRA_EVENTS.apid.index(apid)]