From 4a8e73bc002967bdbb95368e6feadb339471e49b Mon Sep 17 00:00:00 2001 From: Luisa Date: Thu, 11 Dec 2025 10:21:41 -0700 Subject: [PATCH 1/7] hi de --- ...ce_l2-hi-direct-events_variable_attrs.yaml | 324 ++++++++++++++++++ imap_processing/codice/codice_l2.py | 154 ++++++++- imap_processing/codice/constants.py | 35 ++ imap_processing/tests/codice/conftest.py | 9 + .../tests/codice/test_codice_l2.py | 58 +++- .../tests/external_test_data_config.py | 2 + 6 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml diff --git a/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml new file mode 100644 index 000000000..8a584eadd --- /dev/null +++ b/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml @@ -0,0 +1,324 @@ +uint8_fillval: &uint8_fillval 255 +uint16_fillval: &uint16_fillval 65535 +real_fillval: &real_fillval -1.0e+31 +min_int: &min_int -9223372036854775808 +max_int: &max_int 9223372036854775807 + +# ------------------------------- Coordinates ------------------------------- +priority: + CATDESC: Priority Level + FIELDNAM: Priority Level + FILLVAL: *uint8_fillval + FORMAT: I1 + LABLAXIS: Priority + SCALETYP: linear + UNITS: " " + VALIDMIN: 0 + VALIDMAX: 7 + VAR_TYPE: support_data + +event_num: + CATDESC: Event Number + FIELDNAM: Event Number + FILLVAL: *uint16_fillval + FORMAT: I5 + LABLAXIS: Event Number + SCALETYP: linear + UNITS: " " + VALIDMIN: 0 + VALIDMAX: 10000 + VAR_TYPE: support_data + +epoch_delta_minus: + CATDESC: Time from acquisition start to acquisition center + FIELDNAM: epoch delta minus + DEPEND_0: epoch + FILLVAL: -9223372036854775808 + FORMAT: I18 + LABLAXIS: Epoch Delta Minus + SCALETYP: linear + UNITS: ns + VALIDMIN: *min_int + VALIDMAX: *max_int + VAR_TYPE: support_data + DISPLAY_TYPE: 'no_plot' + DICT_KEY: SPASE>Support>SupportQuantity:Temporal,Qualifier:Uncertainty + +epoch_delta_plus: + CATDESC: Time from acquisition center to acquisition end + FIELDNAM: epoch delta plus + DEPEND_0: epoch + FILLVAL: -9223372036854775808 + FORMAT: I18 + LABLAXIS: Epoch Delta Plus + SCALETYP: linear + UNITS: ns + VALIDMIN: *min_int + VALIDMAX: *max_int + VAR_TYPE: support_data + DISPLAY_TYPE: 'no_plot' + DICT_KEY: SPASE>Support>SupportQuantity:Temporal,Qualifier:Uncertainty + +# ------------------------------- Data Variables ------------------------------- +num_events: + CATDESC: Number of Events per Priority + DEPEND_0: epoch + DEPEND_1: priority + FIELDNAM: Number of Events + FILLVAL: *uint16_fillval + FORMAT: I5 + LABLAXIS: Number of Events + LABL_PTR_1: priority_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 10000 + VALIDMIN: 0 + VAR_TYPE: data + +data_quality: + CATDESC: Data Quality Flag per Priority + DEPEND_0: epoch + DEPEND_1: priority + FIELDNAM: Data Quality + FILLVAL: *uint8_fillval + FORMAT: I3 + LABLAXIS: Data Quality + LABL_PTR_1: priority_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 255 + VALIDMIN: 0 + VAR_TYPE: data + +energy_step: + CATDESC: Energy Step Index + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Energy Step + FILLVAL: *uint8_fillval + FORMAT: I3 + LABLAXIS: Energy Step + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 127 + VALIDMIN: 0 + VAR_TYPE: data + +energy_per_charge: + CATDESC: Energy per Charge + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Energy per Charge + FILLVAL: *real_fillval + FORMAT: F12.4 + LABLAXIS: Energy per Charge + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: keV/q + VALIDMAX: 200.0 + VALIDMIN: 0.0 + VAR_TYPE: data + +gain: + CATDESC: Gain Setting + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Gain + FILLVAL: *uint8_fillval + FORMAT: I1 + LABLAXIS: Gain + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 1 + VALIDMIN: 0 + VAR_TYPE: data + +multi_flag: + CATDESC: Multiple Event Flag + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Multi Flag + FILLVAL: *uint8_fillval + FORMAT: I1 + LABLAXIS: Multi Flag + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 1 + VALIDMIN: 0 + VAR_TYPE: data + +type: + CATDESC: Event Type + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Type + FILLVAL: *uint8_fillval + FORMAT: I1 + LABLAXIS: Type + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 4 + VALIDMIN: 0 + VAR_TYPE: data + +tof: + CATDESC: Time of Flight + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Time of Flight + FILLVAL: *real_fillval + FORMAT: F12.4 + LABLAXIS: TOF + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: ns + VALIDMAX: 1024.0 + VALIDMIN: 0.0 + VAR_TYPE: data + +spin_sector: + CATDESC: Spin Sector Index + FIELDNAM: Spin Sector Index + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + FILLVAL: *uint8_fillval + FORMAT: I2 + LABLAXIS: Spin Sector + SCALETYP: linear + UNITS: " " + VALIDMIN: 0 + VALIDMAX: 23 + VAR_TYPE: support_data + +spin_angle: + VAR_TYPE: data + CATDESC: Spin Angle + FIELDNAM: Spin Angle + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: degrees + FILLVAL: *real_fillval + VALIDMAX: 360.0 + VALIDMIN: 0.0 + FORMAT: F8.2 + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:ArrivalDirection,Qualifier:DirectionAngle.AzimuthAngle + +elevation_angle: + CATDESC: Elevation Angle + FIELDNAM: Elevation Angle + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + FORMAT: F8.2 + FILLVAL: *real_fillval + LABLAXIS: Elevation Angle + SCALETYP: linear + UNITS: degrees + VALIDMAX: 180.0 + VALIDMIN: 0.0 + VAR_TYPE: data +ssd_energy: + CATDESC: SSD Energy + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: SSD Energy + FILLVAL: *real_fillval + FORMAT: F12.4 + LABLAXIS: SSD Energy + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: keV + VALIDMAX: 512.0 + VALIDMIN: 0.0 + VAR_TYPE: data + +energy_per_nuc: + CATDESC: Energy per Nucleon + DEPEND_0: epoch + DEPEND_1: priority_label + DEPEND_2: event_num + FIELDNAM: Energy per Nucleon + FILLVAL: *real_fillval + FORMAT: F12.4 + LABLAXIS: Energy per Nucleon + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: MeV/nuc + VALIDMAX: 200.0 + VALIDMIN: 0.0 + VAR_TYPE: data + +ssd_id: + CATDESC: SSD Identifier + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: SSD ID + FILLVAL: *uint8_fillval + FORMAT: I2 + LABLAXIS: SSD ID + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 15 + VALIDMIN: 0 + VAR_TYPE: data + +spin_number: + CATDESC: Spin Number + DEPEND_0: epoch + DEPEND_1: priority + DEPEND_2: event_num + FIELDNAM: Spin Number + FILLVAL: *uint8_fillval + FORMAT: I10 + LABLAXIS: Spin Number + LABL_PTR_1: priority_label + LABL_PTR_2: event_num_label + SCALETYP: linear + UNITS: " " + VALIDMAX: 4294967295 + VALIDMIN: 0 + VAR_TYPE: support_data +# ------------------------------- labels ------------------------------- + +event_num_label: + CATDESC: Event Number Label + FIELDNAM: Event Number + FORMAT: A5 + VAR_TYPE: metadata + +priority_label: + CATDESC: Priority Level Label + FIELDNAM: Priority Level + FORMAT: A1 + VAR_TYPE: metadata diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index 51d541536..55970820d 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -21,6 +21,7 @@ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes from imap_processing.cdf.utils import load_cdf from imap_processing.codice.constants import ( + GAIN_ID_TO_STR, HALF_SPIN_LUT, HI_L2_ELEVATION_ANGLE, HI_OMNI_VARIABLE_NAMES, @@ -38,6 +39,8 @@ PIXEL_ORIENTATIONS, PUI_POSITIONS, SOLAR_WIND_POSITIONS, + SSD_ID_TO_ELEVATION, + SSD_ID_TO_SPIN_ANGLE, SW_POSITIONS, ) from imap_processing.codice.utils import apply_replacements_to_attrs @@ -1100,6 +1103,155 @@ def process_lo_direct_events(dependencies: ProcessingInputCollection) -> xr.Data return l2_dataset +def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Dataset: + """ + Process the hi-direct-events L1A dataset to convert variables to physical units. + + See section 11.2.1 of the CoDICE algorithm document for details. + + Parameters + ---------- + dependencies : ProcessingInputCollection + The collection of processing input files. + + Returns + ------- + xarray.Dataset + The updated L2 dataset with variables converted to physical units. + """ + file_path = dependencies.get_file_paths(descriptor="hi-direct-events")[0] + l1a_dataset = load_cdf(file_path) + + # Update global CDF attributes + cdf_attrs = ImapCdfAttributes() + cdf_attrs.add_instrument_global_attrs("codice") + cdf_attrs.add_instrument_variable_attrs("codice", "l2-hi-direct-events") + + # Convert from position to elevation angle in degrees relative to the spacecraft + # axis + l2_dataset = l1a_dataset.copy(deep=True) + # Load energy table and tof table needed for conversions + energy_table_file_path = dependencies.get_file_paths( + descriptor="l2-hi-energy-table" + )[0] + tof_table_file_path = dependencies.get_file_paths(descriptor="l2-hi-tof-table")[0] + # Read energy table CSV, skip first column which is an index + energy_table = ( + pd.read_csv(energy_table_file_path, header=None, skiprows=1) + .iloc[:, 1:] + .to_numpy() + ) + elevation_angle_shape = l2_dataset["ssd_id"].shape + # Read TOF table CSV, and get the second column which is TOF in ns + # Each row corresponds to a tof index + tof_table = ( + pd.read_csv(tof_table_file_path, header=None, skiprows=1).iloc[:, 1:].to_numpy() + ) + + ssd_id_flat = l2_dataset["ssd_id"].values.ravel() + elevation_angle = np.array( + [SSD_ID_TO_ELEVATION.get(id, np.nan) for id in ssd_id_flat] + ).reshape(elevation_angle_shape) + + l2_dataset["elevation_angle"] = ( + l2_dataset["ssd_id"].dims, + elevation_angle.astype(np.float32), + ) + # Calculate ssd energy in meV + gain_flat = l2_dataset["gain"].values.ravel() + ssd_energy_flat = l2_dataset["ssd_energy"].values.ravel() + # Set up array to hold calculated energies + ssd_energy = np.full(ssd_energy_flat.shape, np.nan) + valid_mask = ( + (np.isin(gain_flat, list(GAIN_ID_TO_STR.keys()))) + & (ssd_id_flat <= 15) + & (ssd_energy_flat != len(energy_table)) + ) + # The columns are organized in order of id and gains (LG, MG, HG) + cols = ssd_id_flat * 3 + (gain_flat - 1) + ssd_energy[valid_mask] = energy_table[ssd_energy_flat[valid_mask], cols[valid_mask]] + l2_dataset["ssd_energy"].data = ssd_energy.reshape( + l2_dataset["ssd_energy"].shape + ).astype(np.float32) + + # Convert spin_sector to spin_angle in degrees + theta_angles = np.array( + [SSD_ID_TO_SPIN_ANGLE.get(ssd_id, np.nan) for ssd_id in ssd_id_flat] + ) + spin_angles = ( + theta_angles + 15.0 * l2_dataset["spin_sector"].values.ravel() + ) % 360.0 + l2_dataset["spin_angle"] = xr.DataArray( + data=spin_angles.reshape(l2_dataset["spin_sector"].shape), + dims=l2_dataset["spin_sector"].dims, + ).astype(np.float32) + + # Calculate TOF in ns + tof_flat = l2_dataset["tof"].values.ravel() + # Get valid TOF indices for lookup + valid_tof_mask = tof_flat < tof_table.shape[0] + tof_ns = np.full(tof_flat.shape, np.nan) + # Get tof values in ns from first column of tof_table + tof_ns[valid_tof_mask] = tof_table[tof_flat[valid_tof_mask], 0] + l2_dataset["tof"] = xr.DataArray( + data=tof_ns.reshape(l2_dataset["tof"].shape), + dims=l2_dataset["tof"].dims, + ).astype(np.float32) + + # Calculate energy per nuc + energy_nuc = np.full(tof_flat.shape, np.nan) + # Get value from second column of tof_table (E/n (MeV/n)) + energy_nuc[valid_tof_mask] = tof_table[tof_flat[valid_tof_mask], 1] + l2_dataset["energy_per_nuc"] = xr.DataArray( + data=energy_nuc.reshape(l2_dataset["tof"].shape), + dims=l2_dataset["tof"].dims, + ).astype(np.float32) + # Drop unused variables + vars_to_drop = ["spare", "sw_bias_gain_mode", "st_bias_gain_mode"] + l2_dataset = l2_dataset.drop_vars(vars_to_drop) + # Update variable attributes + l2_dataset.attrs.update( + cdf_attrs.get_global_attributes("imap_codice_l2_hi-direct-events") + ) + for var in l2_dataset.data_vars: + l2_dataset[var].attrs.update(cdf_attrs.get_variable_attributes(var)) + # Update coord attributes + l2_dataset["priority"].attrs.update( + cdf_attrs.get_variable_attributes("priority", check_schema=False) + ) + l2_dataset["event_num"].attrs.update( + cdf_attrs.get_variable_attributes("event_num", check_schema=False) + ) + l2_dataset["epoch"] = xr.DataArray( + l2_dataset["epoch"].data, + dims="epoch", + attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False), + ) + l2_dataset["epoch_delta_minus"] = xr.DataArray( + data=l2_dataset["epoch_delta_minus"].data.astype(np.int64), + dims="epoch", + attrs=cdf_attrs.get_variable_attributes("epoch_delta_minus"), + ) + l2_dataset["epoch_delta_plus"] = xr.DataArray( + l2_dataset["epoch_delta_plus"].data.astype(np.int64), + dims="epoch", + attrs=cdf_attrs.get_variable_attributes("epoch_delta_plus"), + ) + # Add labels + l2_dataset["event_num_label"] = xr.DataArray( + l2_dataset["event_num"].values.astype(str).astype(" xr.Dataset: @@ -1232,7 +1384,7 @@ def process_codice_l2( # These converted variables are *in addition* to the existing L1 variables # The other data variables require no changes # See section 11.1.2 of algorithm document - pass + l2_dataset = process_hi_direct_events(dependencies) elif dataset_name == "imap_codice_l2_hi-sectored": # Convert the sectored count rates using equation described in section diff --git a/imap_processing/codice/constants.py b/imap_processing/codice/constants.py index a45bab2c4..a2aaa85f1 100644 --- a/imap_processing/codice/constants.py +++ b/imap_processing/codice/constants.py @@ -1140,3 +1140,38 @@ 13: 180, }, } + +# SSD ID to Elevation Angle +SSD_ID_TO_ELEVATION = { + 0: 150.0, + 1: 138.6, + 3: 115.7, + 4: 90.0, + 5: 64.3, + 7: 41.4, + 8: 30.0, + 9: 41.4, + 11: 64.3, + 12: 90.0, + 13: 115.7, + 15: 138.6, +} + +# gain lookup table +GAIN_ID_TO_STR = {1: "LG", 2: "MG", 3: "HG"} + +# SSD ID to Spin Angle +SSD_ID_TO_SPIN_ANGLE = { + 0: 277.50, + 1: 236.61, + 3: 221.19, + 4: 217.5, + 5: 221.19, + 7: 236.61, + 8: 277.50, + 9: 318.39, + 11: 333.81, + 12: 337.50, + 13: 333.81, + 15: 318.39, +} diff --git a/imap_processing/tests/codice/conftest.py b/imap_processing/tests/codice/conftest.py index 049ff16ba..7543608f1 100644 --- a/imap_processing/tests/codice/conftest.py +++ b/imap_processing/tests/codice/conftest.py @@ -271,6 +271,15 @@ def _side_effect(descriptor: str = None, data_type: str = None) -> list[Path]: TEST_DATA_PATH / "l2_lut/imap_codice_l2-lo-onboard-energy-table_20250101_v001.csv" ] + elif descriptor == "l2-hi-energy-table": + return [ + TEST_DATA_PATH + / "l2_lut/imap_codice_l2-hi-energy-table_20250101_v001.csv" + ] + elif descriptor == "l2-hi-tof-table": + return [ + TEST_DATA_PATH / "l2_lut/imap_codice_l2-hi-tof-table_20250101_v001.csv" + ] else: raise ValueError(f"Unknown descriptor: {descriptor}") diff --git a/imap_processing/tests/codice/test_codice_l2.py b/imap_processing/tests/codice/test_codice_l2.py index 8ef3fe014..1162c1518 100644 --- a/imap_processing/tests/codice/test_codice_l2.py +++ b/imap_processing/tests/codice/test_codice_l2.py @@ -48,7 +48,10 @@ def processing_dependencies(codice_lut_path): eff_file = "imap_codice_l2-lo-efficiency_20251008_v001.csv" gf_file = "imap_codice_l2-lo-gfactor_20251008_v001.csv" - return ProcessingInputCollection(AncillaryInput(gf_file), AncillaryInput(eff_file)) + mpq_file = "imap_codice_lo-mpq-cal_20250101_v001.csv" + return ProcessingInputCollection( + AncillaryInput(gf_file), AncillaryInput(eff_file), AncillaryInput(mpq_file) + ) @pytest.fixture @@ -563,3 +566,56 @@ def test_codice_l2_lo_de(mock_get_file_paths, codice_lut_path): errors = CDFValidator().validate(file) assert not errors load_cdf(file) + + +@patch("imap_data_access.processing_input.ProcessingInputCollection.get_file_paths") +def test_codice_l2_hi_de(mock_get_file_paths, codice_lut_path): + mock_get_file_paths.side_effect = [ + codice_lut_path(descriptor="hi-direct-events", data_type="l0") + ] + l1a_cdf = process_l1a(ProcessingInputCollection())[0] + + processed_l1a_file = write_cdf(l1a_cdf) + file_path = processed_l1a_file.as_posix() + # Mock get_files for l2 + mock_get_file_paths.side_effect = [ + [file_path], + [file_path], + codice_lut_path(descriptor="l2-hi-energy-table"), + codice_lut_path(descriptor="l2-hi-tof-table"), + ] + + processed_l2_ds = process_codice_l2("hi-direct-events", ProcessingInputCollection()) + l2_val_data = ( + imap_module_directory + / "tests" + / "codice" + / "data" + / "l2_validation" + / ( + f"imap_codice_l2_hi-direct-events_{VALIDATION_FILE_DATE}" + f"_{VALIDATION_FILE_VERSION}.cdf" + ) + ) + l2_val_data = load_cdf(l2_val_data) + for variable in l2_val_data.data_vars: + if "label" in variable: + np.testing.assert_array_equal( + processed_l2_ds[variable].values, + l2_val_data[variable].values, + err_msg=f"Mismatch in variable '{variable}'", + ) + else: + np.testing.assert_allclose( + processed_l2_ds[variable].values, + l2_val_data[variable].values, + rtol=5e-5, + err_msg=f"Mismatch in variable '{variable}'", + ) + + processed_l2_ds.attrs["Data_version"] = "001" + assert processed_l2_ds.attrs["Logical_source"] == "imap_codice_l2_hi-direct-events" + file = write_cdf(processed_l2_ds) + errors = CDFValidator().validate(file) + assert not errors + load_cdf(file) diff --git a/imap_processing/tests/external_test_data_config.py b/imap_processing/tests/external_test_data_config.py index 9a9c3bc0e..8b2d434c3 100644 --- a/imap_processing/tests/external_test_data_config.py +++ b/imap_processing/tests/external_test_data_config.py @@ -83,6 +83,8 @@ ("imap_codice_l2-hi-ialirt-efficiency_20251008_v001.csv", "codice/data/l2_lut/"), ("imap_codice_l2-lo-gfactor_20251008_v001.csv", "codice/data/l2_lut/"), ("imap_codice_l2-lo-efficiency_20251008_v001.csv", "codice/data/l2_lut/"), + ("imap_codice_l2-hi-tof-table_20250101_v001.csv", "codice/data/l2_lut/"), + ("imap_codice_l2-hi-energy-table_20250101_v001.csv", "codice/data/l2_lut/"), ("imap_codice_l2-lo-onboard-energy-bins_20250101_v001.csv", "codice/data/l2_lut/"), ("imap_codice_l2-lo-onboard-energy-table_20250101_v001.csv", "codice/data/l2_lut/"), ("imap_codice_l2-lo-onboard-mpq-cal_20250101_v001.csv", "codice/data/l2_lut/"), From 35276b538607978c224a0cd095bfd4f8dccf95a7 Mon Sep 17 00:00:00 2001 From: Luisa Date: Mon, 15 Dec 2025 12:31:28 -0700 Subject: [PATCH 2/7] codice hi de --- imap_processing/codice/codice_l2.py | 72 +++++++++++-------- .../tests/codice/test_codice_l2.py | 8 +++ 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index 55970820d..815586a28 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -139,6 +139,44 @@ def get_mpq_calc_tof_conversion_vals( return tof_ns +def get_hi_de_luts( + dependencies: ProcessingInputCollection | None, +) -> tuple[np.ndarray, np.ndarray]: + """ + Load lookup tables for hi direct-event processing. + + Parameters + ---------- + dependencies : ProcessingInputCollection + The collection of processing input files. + + Returns + ------- + energy_table : np.ndarray + 2D array of energy lookup table with shape (ssd_energy, col). + tof_table : np.ndarray + 2D array of tof lookup table with shape (tof_index, col). + """ + energy_table_file_path = dependencies.get_file_paths( + descriptor="l2-hi-energy-table" + )[0] + tof_table_file_path = dependencies.get_file_paths(descriptor="l2-hi-tof-table")[0] + # Read TOF table CSV, and get the second column which is TOF in ns + # Each row corresponds to a tof index and the columns are tof (ns) and E/n (MeV/n) + tof_table = ( + pd.read_csv(tof_table_file_path, header=None, skiprows=1).iloc[:, 1:].to_numpy() + ) + # Read energy table CSV, skip first column which is an index + # Each row corresponds to an ssd energy index and the columns map to a combination + # of gain and ssd id + energy_table = ( + pd.read_csv(energy_table_file_path, header=None, skiprows=1) + .iloc[:, 1:] + .to_numpy() + ) + return energy_table, tof_table + + def get_geometric_factor_lut( dependencies: ProcessingInputCollection | None, path: Path | None = None, @@ -1129,24 +1167,10 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data # Convert from position to elevation angle in degrees relative to the spacecraft # axis - l2_dataset = l1a_dataset.copy(deep=True) + l2_dataset = l1a_dataset # Load energy table and tof table needed for conversions - energy_table_file_path = dependencies.get_file_paths( - descriptor="l2-hi-energy-table" - )[0] - tof_table_file_path = dependencies.get_file_paths(descriptor="l2-hi-tof-table")[0] - # Read energy table CSV, skip first column which is an index - energy_table = ( - pd.read_csv(energy_table_file_path, header=None, skiprows=1) - .iloc[:, 1:] - .to_numpy() - ) + energy_table, tof_table = get_hi_de_luts(dependencies) elevation_angle_shape = l2_dataset["ssd_id"].shape - # Read TOF table CSV, and get the second column which is TOF in ns - # Each row corresponds to a tof index - tof_table = ( - pd.read_csv(tof_table_file_path, header=None, skiprows=1).iloc[:, 1:].to_numpy() - ) ssd_id_flat = l2_dataset["ssd_id"].values.ravel() elevation_angle = np.array( @@ -1160,14 +1184,15 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data # Calculate ssd energy in meV gain_flat = l2_dataset["gain"].values.ravel() ssd_energy_flat = l2_dataset["ssd_energy"].values.ravel() - # Set up array to hold calculated energies + # Initialize ssd_energy with nans ssd_energy = np.full(ssd_energy_flat.shape, np.nan) valid_mask = ( (np.isin(gain_flat, list(GAIN_ID_TO_STR.keys()))) & (ssd_id_flat <= 15) & (ssd_energy_flat != len(energy_table)) ) - # The columns are organized in order of id and gains (LG, MG, HG) + # The columns are organized in order of id and gains + # E.g. ssd 0 - LG, ssd 0 - MG, ssd 0 - HG, ssd 1 - LG, ssd 1 - MG, ssd 1 - HG, ... cols = ssd_id_flat * 3 + (gain_flat - 1) ssd_energy[valid_mask] = energy_table[ssd_energy_flat[valid_mask], cols[valid_mask]] l2_dataset["ssd_energy"].data = ssd_energy.reshape( @@ -1181,6 +1206,7 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data spin_angles = ( theta_angles + 15.0 * l2_dataset["spin_sector"].values.ravel() ) % 360.0 + l2_dataset["spin_angle"] = xr.DataArray( data=spin_angles.reshape(l2_dataset["spin_sector"].shape), dims=l2_dataset["spin_sector"].dims, @@ -1227,16 +1253,6 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data dims="epoch", attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False), ) - l2_dataset["epoch_delta_minus"] = xr.DataArray( - data=l2_dataset["epoch_delta_minus"].data.astype(np.int64), - dims="epoch", - attrs=cdf_attrs.get_variable_attributes("epoch_delta_minus"), - ) - l2_dataset["epoch_delta_plus"] = xr.DataArray( - l2_dataset["epoch_delta_plus"].data.astype(np.int64), - dims="epoch", - attrs=cdf_attrs.get_variable_attributes("epoch_delta_plus"), - ) # Add labels l2_dataset["event_num_label"] = xr.DataArray( l2_dataset["event_num"].values.astype(str).astype(" Date: Mon, 15 Dec 2025 12:36:32 -0700 Subject: [PATCH 3/7] fix comments --- imap_processing/codice/codice_l2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index 815586a28..cc0541008 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -161,7 +161,7 @@ def get_hi_de_luts( descriptor="l2-hi-energy-table" )[0] tof_table_file_path = dependencies.get_file_paths(descriptor="l2-hi-tof-table")[0] - # Read TOF table CSV, and get the second column which is TOF in ns + # Read TOF CSV, skip first column which is an index # Each row corresponds to a tof index and the columns are tof (ns) and E/n (MeV/n) tof_table = ( pd.read_csv(tof_table_file_path, header=None, skiprows=1).iloc[:, 1:].to_numpy() @@ -1165,13 +1165,13 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data cdf_attrs.add_instrument_global_attrs("codice") cdf_attrs.add_instrument_variable_attrs("codice", "l2-hi-direct-events") - # Convert from position to elevation angle in degrees relative to the spacecraft - # axis l2_dataset = l1a_dataset # Load energy table and tof table needed for conversions energy_table, tof_table = get_hi_de_luts(dependencies) - elevation_angle_shape = l2_dataset["ssd_id"].shape + # Convert from position to elevation angle in degrees relative to the spacecraft + # axis + elevation_angle_shape = l2_dataset["ssd_id"].shape ssd_id_flat = l2_dataset["ssd_id"].values.ravel() elevation_angle = np.array( [SSD_ID_TO_ELEVATION.get(id, np.nan) for id in ssd_id_flat] From 66d73bc963705cd44d70bb97e7e9318d508f7654 Mon Sep 17 00:00:00 2001 From: Luisa Date: Mon, 15 Dec 2025 13:47:00 -0700 Subject: [PATCH 4/7] get rid of reshaping --- imap_processing/codice/codice_l2.py | 70 +++++++++++++--------------- imap_processing/codice/constants.py | 72 +++++++++++++++++------------ 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index cc0541008..91945fba0 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -1168,68 +1168,60 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data l2_dataset = l1a_dataset # Load energy table and tof table needed for conversions energy_table, tof_table = get_hi_de_luts(dependencies) - + # Initialize nan array for calculations + nan_array = np.full(l2_dataset["ssd_id"].shape, np.nan) # Convert from position to elevation angle in degrees relative to the spacecraft # axis - elevation_angle_shape = l2_dataset["ssd_id"].shape - ssd_id_flat = l2_dataset["ssd_id"].values.ravel() - elevation_angle = np.array( - [SSD_ID_TO_ELEVATION.get(id, np.nan) for id in ssd_id_flat] - ).reshape(elevation_angle_shape) + ssd_id = l2_dataset["ssd_id"].values + valid_ssd = (ssd_id <= 15) & (ssd_id >= 0) - l2_dataset["elevation_angle"] = ( - l2_dataset["ssd_id"].dims, - elevation_angle.astype(np.float32), + elevation = nan_array.copy() + elevation[valid_ssd] = SSD_ID_TO_ELEVATION[ssd_id[valid_ssd]] + l2_dataset["elevation_angle"] = xr.DataArray( + data=elevation.astype(np.float32), dims=l2_dataset["ssd_id"].dims ) # Calculate ssd energy in meV - gain_flat = l2_dataset["gain"].values.ravel() - ssd_energy_flat = l2_dataset["ssd_energy"].values.ravel() - # Initialize ssd_energy with nans - ssd_energy = np.full(ssd_energy_flat.shape, np.nan) + gain = l2_dataset["gain"].values + ssd_energy = l2_dataset["ssd_energy"].values valid_mask = ( - (np.isin(gain_flat, list(GAIN_ID_TO_STR.keys()))) - & (ssd_id_flat <= 15) - & (ssd_energy_flat != len(energy_table)) + (np.isin(gain, list(GAIN_ID_TO_STR.keys()))) + & valid_ssd + & (ssd_energy != len(energy_table)) ) # The columns are organized in order of id and gains # E.g. ssd 0 - LG, ssd 0 - MG, ssd 0 - HG, ssd 1 - LG, ssd 1 - MG, ssd 1 - HG, ... - cols = ssd_id_flat * 3 + (gain_flat - 1) - ssd_energy[valid_mask] = energy_table[ssd_energy_flat[valid_mask], cols[valid_mask]] - l2_dataset["ssd_energy"].data = ssd_energy.reshape( - l2_dataset["ssd_energy"].shape - ).astype(np.float32) + cols = ssd_id * 3 + (gain - 1) + ssd_energy_converted = nan_array.copy() + ssd_energy_converted[valid_mask] = energy_table[ + ssd_energy[valid_mask], cols[valid_mask] + ] + l2_dataset["ssd_energy"].data = ssd_energy_converted.astype(np.float32) # Convert spin_sector to spin_angle in degrees - theta_angles = np.array( - [SSD_ID_TO_SPIN_ANGLE.get(ssd_id, np.nan) for ssd_id in ssd_id_flat] - ) - spin_angles = ( - theta_angles + 15.0 * l2_dataset["spin_sector"].values.ravel() - ) % 360.0 - - l2_dataset["spin_angle"] = xr.DataArray( - data=spin_angles.reshape(l2_dataset["spin_sector"].shape), - dims=l2_dataset["spin_sector"].dims, + theta_angles = nan_array.copy() + theta_angles[valid_ssd] = SSD_ID_TO_SPIN_ANGLE[ssd_id[valid_ssd]] + l2_dataset["spin_angle"] = ( + (theta_angles + 15.0 * l2_dataset["spin_sector"]) % 360.0 ).astype(np.float32) # Calculate TOF in ns - tof_flat = l2_dataset["tof"].values.ravel() + tof = l2_dataset["tof"].values # Get valid TOF indices for lookup - valid_tof_mask = tof_flat < tof_table.shape[0] - tof_ns = np.full(tof_flat.shape, np.nan) + valid_tof_mask = tof < tof_table.shape[0] + tof_ns = nan_array.copy() # Get tof values in ns from first column of tof_table - tof_ns[valid_tof_mask] = tof_table[tof_flat[valid_tof_mask], 0] + tof_ns[valid_tof_mask] = tof_table[tof[valid_tof_mask], 0] l2_dataset["tof"] = xr.DataArray( - data=tof_ns.reshape(l2_dataset["tof"].shape), + data=tof_ns, dims=l2_dataset["tof"].dims, ).astype(np.float32) # Calculate energy per nuc - energy_nuc = np.full(tof_flat.shape, np.nan) + energy_nuc = nan_array.copy() # Get value from second column of tof_table (E/n (MeV/n)) - energy_nuc[valid_tof_mask] = tof_table[tof_flat[valid_tof_mask], 1] + energy_nuc[valid_tof_mask] = tof_table[tof[valid_tof_mask], 1] l2_dataset["energy_per_nuc"] = xr.DataArray( - data=energy_nuc.reshape(l2_dataset["tof"].shape), + data=energy_nuc, dims=l2_dataset["tof"].dims, ).astype(np.float32) # Drop unused variables diff --git a/imap_processing/codice/constants.py b/imap_processing/codice/constants.py index a2aaa85f1..f3236b3b7 100644 --- a/imap_processing/codice/constants.py +++ b/imap_processing/codice/constants.py @@ -1142,36 +1142,50 @@ } # SSD ID to Elevation Angle -SSD_ID_TO_ELEVATION = { - 0: 150.0, - 1: 138.6, - 3: 115.7, - 4: 90.0, - 5: 64.3, - 7: 41.4, - 8: 30.0, - 9: 41.4, - 11: 64.3, - 12: 90.0, - 13: 115.7, - 15: 138.6, -} +# The index corresponds to the SSD ID. Missing SSD IDs are represented with np.nan. +SSD_ID_TO_ELEVATION = np.array( + [ + 150.0, + 138.6, + np.nan, + 115.7, + 90.0, + 64.3, + np.nan, + 41.4, + 30.0, + 41.4, + np.nan, + 64.3, + 90.0, + 115.7, + np.nan, + 138.6, + ] +) # gain lookup table GAIN_ID_TO_STR = {1: "LG", 2: "MG", 3: "HG"} -# SSD ID to Spin Angle -SSD_ID_TO_SPIN_ANGLE = { - 0: 277.50, - 1: 236.61, - 3: 221.19, - 4: 217.5, - 5: 221.19, - 7: 236.61, - 8: 277.50, - 9: 318.39, - 11: 333.81, - 12: 337.50, - 13: 333.81, - 15: 318.39, -} +# SSD ID to Spin Angle (degrees) +# The index corresponds to the SSD ID. Missing SSD IDs are represented with np.nan. +SSD_ID_TO_SPIN_ANGLE = np.array( + [ + 277.50, + 236.61, + np.nan, + 221.19, + 217.5, + 221.19, + np.nan, + 236.61, + 277.50, + 318.39, + np.nan, + 333.81, + 337.50, + 333.81, + np.nan, + 318.39, + ] +) From 982f21f0b164627175b534228d9374d029c5eecb Mon Sep 17 00:00:00 2001 From: Luisa Date: Wed, 17 Dec 2025 09:10:38 -0700 Subject: [PATCH 5/7] pr suggestion --- imap_processing/codice/codice_l2.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index 91945fba0..9211e518f 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -1234,17 +1234,10 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data for var in l2_dataset.data_vars: l2_dataset[var].attrs.update(cdf_attrs.get_variable_attributes(var)) # Update coord attributes - l2_dataset["priority"].attrs.update( - cdf_attrs.get_variable_attributes("priority", check_schema=False) - ) - l2_dataset["event_num"].attrs.update( - cdf_attrs.get_variable_attributes("event_num", check_schema=False) - ) - l2_dataset["epoch"] = xr.DataArray( - l2_dataset["epoch"].data, - dims="epoch", - attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False), - ) + for coord in l2_dataset.coords: + l2_dataset[coord].attrs.update( + cdf_attrs.get_variable_attributes(coord), check_schema=False + ) # Add labels l2_dataset["event_num_label"] = xr.DataArray( l2_dataset["event_num"].values.astype(str).astype(" Date: Wed, 17 Dec 2025 09:55:42 -0700 Subject: [PATCH 6/7] spase --- ...dice_l2-hi-direct-events_variable_attrs.yaml | 17 +++++++++++++++++ ...dice_l2-lo-direct-events_variable_attrs.yaml | 14 ++++++++++++++ imap_processing/codice/codice_l2.py | 15 +++++++++++---- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml index 8a584eadd..e4da2e699 100644 --- a/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml @@ -16,6 +16,7 @@ priority: VALIDMIN: 0 VALIDMAX: 7 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Other event_num: CATDESC: Event Number @@ -28,6 +29,7 @@ event_num: VALIDMIN: 0 VALIDMAX: 10000 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Other epoch_delta_minus: CATDESC: Time from acquisition start to acquisition center @@ -74,6 +76,7 @@ num_events: VALIDMAX: 10000 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other data_quality: CATDESC: Data Quality Flag per Priority @@ -89,6 +92,7 @@ data_quality: VALIDMAX: 255 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality energy_step: CATDESC: Energy Step Index @@ -106,6 +110,7 @@ energy_step: VALIDMAX: 127 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other energy_per_charge: CATDESC: Energy per Charge @@ -123,6 +128,7 @@ energy_per_charge: VALIDMAX: 200.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:EnergyPerCharge gain: CATDESC: Gain Setting @@ -140,6 +146,7 @@ gain: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other multi_flag: CATDESC: Multiple Event Flag @@ -157,6 +164,7 @@ multi_flag: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality type: CATDESC: Event Type @@ -174,6 +182,7 @@ type: VALIDMAX: 4 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other tof: CATDESC: Time of Flight @@ -191,6 +200,7 @@ tof: VALIDMAX: 1024.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:TimeOfFlight spin_sector: CATDESC: Spin Sector Index @@ -208,6 +218,7 @@ spin_sector: VALIDMIN: 0 VALIDMAX: 23 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Positional spin_angle: VAR_TYPE: data @@ -242,6 +253,8 @@ elevation_angle: VALIDMAX: 180.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:ArrivalDirection,Qualifier:DirectionAngle.ElevationAngle + ssd_energy: CATDESC: SSD Energy DEPEND_0: epoch @@ -258,6 +271,7 @@ ssd_energy: VALIDMAX: 512.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:Energy energy_per_nuc: CATDESC: Energy per Nucleon @@ -275,6 +289,7 @@ energy_per_nuc: VALIDMAX: 200.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other ssd_id: CATDESC: SSD Identifier @@ -292,6 +307,7 @@ ssd_id: VALIDMAX: 15 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other spin_number: CATDESC: Spin Number @@ -309,6 +325,7 @@ spin_number: VALIDMAX: 4294967295 VALIDMIN: 0 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Other # ------------------------------- labels ------------------------------- event_num_label: diff --git a/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml index 82cda9c07..4b11201f7 100644 --- a/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml @@ -72,6 +72,7 @@ num_events: VALIDMAX: 10000 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other data_quality: CATDESC: Data Quality Flag per Priority @@ -87,6 +88,7 @@ data_quality: VALIDMAX: 255 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality energy_step: CATDESC: Energy Step Index @@ -104,6 +106,7 @@ energy_step: VALIDMAX: 127 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other energy_per_charge: CATDESC: Energy per Charge @@ -121,6 +124,7 @@ energy_per_charge: VALIDMAX: 200.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:EnergyPerCharge apd_energy: CATDESC: APD Energy @@ -138,6 +142,7 @@ apd_energy: VALIDMAX: 512.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:Energy gain: CATDESC: Gain Setting @@ -155,6 +160,7 @@ gain: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other apd_id: CATDESC: APD Identifier @@ -172,6 +178,7 @@ apd_id: VALIDMAX: 31 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other position: CATDESC: Position Index @@ -189,6 +196,7 @@ position: VALIDMAX: 31 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other multi_flag: CATDESC: Multiple Event Flag @@ -206,6 +214,7 @@ multi_flag: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality type: CATDESC: Event Type @@ -223,6 +232,7 @@ type: VALIDMAX: 4 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other tof: CATDESC: Time of Flight @@ -240,6 +250,7 @@ tof: VALIDMAX: 1024.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:TimeOfFlight spin_sector: CATDESC: Spin Sector Index @@ -257,6 +268,7 @@ spin_sector: VALIDMIN: 0 VALIDMAX: 23 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Positional spin_angle: VAR_TYPE: data @@ -291,6 +303,7 @@ elevation_angle: VALIDMAX: 180.0 VALIDMIN: 0.0 VAR_TYPE: data + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:ArrivalDirection,Qualifier:DirectionAngle.ElevationAngle esa_step: CATDESC: Energy per charge (E/q) sweeping step @@ -308,6 +321,7 @@ esa_step: VALIDMIN: 0 VALIDMAX: 127 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other # ------------------------------- labels ------------------------------- event_num_label: diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index 9211e518f..91945fba0 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -1234,10 +1234,17 @@ def process_hi_direct_events(dependencies: ProcessingInputCollection) -> xr.Data for var in l2_dataset.data_vars: l2_dataset[var].attrs.update(cdf_attrs.get_variable_attributes(var)) # Update coord attributes - for coord in l2_dataset.coords: - l2_dataset[coord].attrs.update( - cdf_attrs.get_variable_attributes(coord), check_schema=False - ) + l2_dataset["priority"].attrs.update( + cdf_attrs.get_variable_attributes("priority", check_schema=False) + ) + l2_dataset["event_num"].attrs.update( + cdf_attrs.get_variable_attributes("event_num", check_schema=False) + ) + l2_dataset["epoch"] = xr.DataArray( + l2_dataset["epoch"].data, + dims="epoch", + attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False), + ) # Add labels l2_dataset["event_num_label"] = xr.DataArray( l2_dataset["event_num"].values.astype(str).astype(" Date: Wed, 17 Dec 2025 09:57:44 -0700 Subject: [PATCH 7/7] space update --- .../config/imap_codice_l2-lo-direct-events_variable_attrs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml index 4b11201f7..beb60d248 100644 --- a/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_codice_l2-lo-direct-events_variable_attrs.yaml @@ -16,6 +16,7 @@ priority: VALIDMIN: 0 VALIDMAX: 7 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Other event_num: CATDESC: Event Number @@ -28,6 +29,7 @@ event_num: VALIDMIN: 0 VALIDMAX: 10000 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Other epoch_delta_minus: CATDESC: Time from acquisition start to acquisition center