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..e4da2e699 --- /dev/null +++ b/imap_processing/cdf/config/imap_codice_l2-hi-direct-events_variable_attrs.yaml @@ -0,0 +1,341 @@ +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:EnergyPerCharge + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:TimeOfFlight + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Positional + +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 + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:ArrivalDirection,Qualifier:DirectionAngle.ElevationAngle + +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 + DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:Energy + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other + +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 + DICT_KEY: SPASE>Support>SupportQuantity:Other +# ------------------------------- 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/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..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 @@ -72,6 +74,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 +90,7 @@ data_quality: VALIDMAX: 255 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality energy_step: CATDESC: Energy Step Index @@ -104,6 +108,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 +126,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 +144,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 +162,7 @@ gain: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other apd_id: CATDESC: APD Identifier @@ -172,6 +180,7 @@ apd_id: VALIDMAX: 31 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other position: CATDESC: Position Index @@ -189,6 +198,7 @@ position: VALIDMAX: 31 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other multi_flag: CATDESC: Multiple Event Flag @@ -206,6 +216,7 @@ multi_flag: VALIDMAX: 1 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:DataQuality type: CATDESC: Event Type @@ -223,6 +234,7 @@ type: VALIDMAX: 4 VALIDMIN: 0 VAR_TYPE: data + DICT_KEY: SPASE>Support>SupportQuantity:Other tof: CATDESC: Time of Flight @@ -240,6 +252,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 +270,7 @@ spin_sector: VALIDMIN: 0 VALIDMAX: 23 VAR_TYPE: support_data + DICT_KEY: SPASE>Support>SupportQuantity:Positional spin_angle: VAR_TYPE: data @@ -291,6 +305,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 +323,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 51d541536..91945fba0 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 @@ -136,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 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() + ) + # 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, @@ -1100,6 +1141,125 @@ 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") + + 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 + ssd_id = l2_dataset["ssd_id"].values + valid_ssd = (ssd_id <= 15) & (ssd_id >= 0) + + 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 = l2_dataset["gain"].values + ssd_energy = l2_dataset["ssd_energy"].values + valid_mask = ( + (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 * 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 = 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 = l2_dataset["tof"].values + # Get valid TOF indices for lookup + 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[valid_tof_mask], 0] + l2_dataset["tof"] = xr.DataArray( + data=tof_ns, + dims=l2_dataset["tof"].dims, + ).astype(np.float32) + + # Calculate energy per nuc + 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[valid_tof_mask], 1] + l2_dataset["energy_per_nuc"] = xr.DataArray( + data=energy_nuc, + 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), + ) + # Add labels + l2_dataset["event_num_label"] = xr.DataArray( + l2_dataset["event_num"].values.astype(str).astype(" xr.Dataset: @@ -1232,7 +1392,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..f3236b3b7 100644 --- a/imap_processing/codice/constants.py +++ b/imap_processing/codice/constants.py @@ -1140,3 +1140,52 @@ 13: 180, }, } + +# SSD ID to Elevation Angle +# 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 (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, + ] +) 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..91c32c1db 100644 --- a/imap_processing/tests/codice/test_codice_l2.py +++ b/imap_processing/tests/codice/test_codice_l2.py @@ -19,6 +19,7 @@ compute_geometric_factors, get_efficiency_lut, get_geometric_factor_lut, + get_hi_de_luts, get_mpq_calc_energy_conversion_vals, get_mpq_calc_tof_conversion_vals, process_codice_l2, @@ -48,7 +49,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 @@ -199,6 +203,13 @@ def test_get_energy_kev_from_mpq_lut(processing_dependencies, mock_get_file_path np.testing.assert_allclose(energy_kev, expected_e_kev, rtol=0.01) +def test_get_hi_de_luts(processing_dependencies, mock_get_file_paths): + # Mock get_file_paths to return specific files for hi-energy-table and hi-tof-table + energy_table, tof_table = get_hi_de_luts(processing_dependencies) + assert energy_table.shape == (2048, 48) + assert tof_table.shape == (1024, 2) + + def test_process_lo_species_intensity(mock_get_file_paths, codice_lut_path): mock_get_file_paths.side_effect = [ codice_lut_path(descriptor="lo-sw-species", data_type="l0"), @@ -563,3 +574,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/"),