From 17c7090cf73a2b66ce98e703e0e2683d2a4ef214 Mon Sep 17 00:00:00 2001 From: Luisa Coakley Date: Tue, 11 Aug 2026 12:33:27 -0600 Subject: [PATCH 1/4] update lo sw species depend 1 --- .../imap_codice_l1b_variable_attrs.yaml | 24 ++++++++++++++ ...p_codice_l2-lo-species_variable_attrs.yaml | 24 +++++++------- imap_processing/codice/codice_l1b.py | 32 +++++++++++++------ 3 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml diff --git a/imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml new file mode 100644 index 000000000..36c336058 --- /dev/null +++ b/imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml @@ -0,0 +1,24 @@ +# <=== Useful Variables ===> +real_fillval: &real_fillval -1.0E+31 + +# <=== Coordinates ===> +energy_per_charge: + CATDESC: Energy per charge + DEPEND_1: esa_step + FIELDNAM: Energy per charge + FILLVAL: *real_fillval + FORMAT: F12.6 + LABLAXIS: E/q + SCALETYP: log + UNITS: keV/e + VALIDMAX: 81.216 + VALIDMIN: 0.00288 + VAR_TYPE: support_data + +# <=== LABL_PTR_i Attributes ===> +energy_per_charge_label: + CATDESC: Energy per charge + DEPEND_1: esa_step + FIELDNAM: Energy per charge + FORMAT: A10 + VAR_TYPE: metadata diff --git a/imap_processing/cdf/config/imap_codice_l2-lo-species_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-lo-species_variable_attrs.yaml index e4048650a..e0ff5516f 100644 --- a/imap_processing/cdf/config/imap_codice_l2-lo-species_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_codice_l2-lo-species_variable_attrs.yaml @@ -66,14 +66,14 @@ lo-species-attrs: DELTA_MINUS_VAR: unc_{species} DELTA_PLUS_VAR: unc_{species} DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Differential DISPLAY_TYPE: spectrogram FIELDNAM: "Non-sunward - {species}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" @@ -86,14 +86,14 @@ lo-pui-species-attrs: DELTA_MINUS_VAR: unc_{species} DELTA_PLUS_VAR: unc_{species} DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Differential DISPLAY_TYPE: spectrogram FIELDNAM: "Sunward - {species}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" @@ -106,14 +106,14 @@ lo-sw-species-attrs: DELTA_MINUS_VAR: unc_{species} DELTA_PLUS_VAR: unc_{species} DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Differential DISPLAY_TYPE: spectrogram FIELDNAM: "Sunward Differential Intensity - {species_display}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" @@ -124,14 +124,14 @@ lo-sw-species-attrs: lo-species-unc-attrs: CATDESC: "{species} Non-sunward Species uncertainty" DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Uncertainty DISPLAY_TYPE: spectrogram FIELDNAM: "Non-sunward - {species}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" @@ -142,14 +142,14 @@ lo-species-unc-attrs: lo-pui-species-unc-attrs: CATDESC: "{species} Pickup Ion Sunward Species uncertainty" DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Uncertainty DISPLAY_TYPE: spectrogram FIELDNAM: "Sunward - {species}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" @@ -160,14 +160,14 @@ lo-pui-species-unc-attrs: lo-sw-species-unc-attrs: CATDESC: "Uncertainty in differential intensity from sunward-looking detectors measuring solar-wind {species_display}" DEPEND_0: epoch - DEPEND_1: esa_step + DEPEND_1: energy_per_charge DEPEND_2: spin_sector DICT_KEY: SPASE>Particle>ParticleType:Ion,ParticleQuantity:NumberFlux,Qualifier:Uncertainty DISPLAY_TYPE: spectrogram FIELDNAM: "Sunward Uncertainty - {species_display}" FILLVAL: *real_fillval FORMAT: F32.1 - LABL_PTR_1: esa_step_label + LABL_PTR_1: energy_per_charge_label LABL_PTR_2: spin_sector_label SCALETYP: log UNITS: "#/(cm^2-s-sr-keV/q)" diff --git a/imap_processing/codice/codice_l1b.py b/imap_processing/codice/codice_l1b.py index 644f59a3b..31d145e37 100644 --- a/imap_processing/codice/codice_l1b.py +++ b/imap_processing/codice/codice_l1b.py @@ -22,7 +22,9 @@ logger = logging.getLogger(__name__) -def convert_to_rates(dataset: xr.Dataset, descriptor: str) -> np.ndarray: +def convert_to_rates( + dataset: xr.Dataset, descriptor: str, cdf_attrs: ImapCdfAttributes +) -> np.ndarray: """ Apply a conversion from counts to rates. @@ -35,6 +37,8 @@ def convert_to_rates(dataset: xr.Dataset, descriptor: str) -> np.ndarray: The L1b dataset containing the data to convert. descriptor : str The descriptor of the data product of interest. + cdf_attrs : ImapCdfAttributes + The CDF attributes manager, with L1b variable attributes loaded. Returns ------- @@ -49,19 +53,27 @@ def convert_to_rates(dataset: xr.Dataset, descriptor: str) -> np.ndarray: ) if descriptor.startswith("lo-"): # Calculate energy_per_charge using voltage_table and k_factor - energy_attrs = dataset["voltage_table"].attrs | { - "UNITS": "keV/e", - "LABLAXIS": "E/q", - "CATDESC": "Energy per charge", - "FIELDNAM": "Energy per charge", - } # 1e3 is to convert eV to keV + energy_per_charge = ( + dataset["voltage_table"].values * dataset["k_factor"].values * 1e-3 + ) dataset["energy_per_charge"] = xr.DataArray( - dataset["voltage_table"].values * dataset["k_factor"].values * 1e-3, + energy_per_charge, + dims=[ + "esa_step", + ], + attrs=cdf_attrs.get_variable_attributes( + "energy_per_charge", check_schema=False + ), + ) + dataset["energy_per_charge_label"] = xr.DataArray( + np.array([f"{value:.3f}" for value in energy_per_charge]), dims=[ "esa_step", ], - attrs=energy_attrs, + attrs=cdf_attrs.get_variable_attributes( + "energy_per_charge_label", check_schema=False + ), ) if descriptor in [ @@ -180,6 +192,7 @@ def process_codice_l1b(file_path: Path) -> xr.Dataset: # Get the L1b CDF attributes cdf_attrs = ImapCdfAttributes() cdf_attrs.add_instrument_global_attrs("codice") + cdf_attrs.add_instrument_variable_attrs("codice", "l1b") # Use the L1a data product as a starting point for L1b l1b_dataset = l1a_dataset.copy(deep=True) @@ -190,4 +203,5 @@ def process_codice_l1b(file_path: Path) -> xr.Dataset: return convert_to_rates( l1b_dataset, descriptor, + cdf_attrs, ) From 3d0060e278729920182a1438808661f73929d60d Mon Sep 17 00:00:00 2001 From: Luisa Coakley Date: Wed, 12 Aug 2026 11:51:05 -0600 Subject: [PATCH 2/4] add cdf attrs --- imap_processing/ialirt/l0/process_codice.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/imap_processing/ialirt/l0/process_codice.py b/imap_processing/ialirt/l0/process_codice.py index 1675acd66..3865e8dd8 100644 --- a/imap_processing/ialirt/l0/process_codice.py +++ b/imap_processing/ialirt/l0/process_codice.py @@ -12,6 +12,7 @@ import xarray as xr from numpy.typing import NDArray +from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes from imap_processing.codice import constants from imap_processing.codice.codice_l1a_ialirt_hi import l1a_ialirt_hi from imap_processing.codice.codice_l1a_lo_species import l1a_lo_species @@ -439,6 +440,11 @@ def process_codice( codice_lo_data: list[dict[str, Any]] = [] codice_hi_data: list[dict[str, Any]] = [] + # Get the L1b CDF attributes + cdf_attrs = ImapCdfAttributes() + cdf_attrs.add_instrument_global_attrs("codice") + cdf_attrs.add_instrument_variable_attrs("codice", "l1b") + # Subsecond time conversion specified in 7516-9054 GSW-FSW ICD. # Value of SCLK subseconds, unsigned, (LSB = 1/256 sec) met = calculate_time(dataset["sc_sclk_sec"], dataset["sc_sclk_sub_sec"], 256) @@ -477,10 +483,7 @@ def process_codice( l1a_lo = process_by_table_id(cod_lo_dataset, l1a_lut_path, l1a_lo_species) l1b_lo = cast( xr.Dataset, - convert_to_rates( - l1a_lo, - "lo-ialirt", - ), + convert_to_rates(l1a_lo, "lo-ialirt", cdf_attrs), ) mid_measurement = int((l1b_lo["epoch"][0] + l1b_lo["epoch"][-1]) // 2) yyyymmdd = datetime.datetime.strptime( From 5c44f1a0acd9b1b0b2594ee68b6a4d34c8f0359f Mon Sep 17 00:00:00 2001 From: Luisa Coakley Date: Thu, 13 Aug 2026 11:51:40 -0600 Subject: [PATCH 3/4] make cdf attrs optional for ialirt --- imap_processing/codice/codice_l1b.py | 16 ++++++++++++---- imap_processing/ialirt/l0/process_codice.py | 8 +------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/imap_processing/codice/codice_l1b.py b/imap_processing/codice/codice_l1b.py index 31d145e37..e00850ab5 100644 --- a/imap_processing/codice/codice_l1b.py +++ b/imap_processing/codice/codice_l1b.py @@ -23,7 +23,9 @@ def convert_to_rates( - dataset: xr.Dataset, descriptor: str, cdf_attrs: ImapCdfAttributes + dataset: xr.Dataset, + descriptor: str, + cdf_attrs: ImapCdfAttributes | None = None, ) -> np.ndarray: """ Apply a conversion from counts to rates. @@ -37,8 +39,10 @@ def convert_to_rates( The L1b dataset containing the data to convert. descriptor : str The descriptor of the data product of interest. - cdf_attrs : ImapCdfAttributes + cdf_attrs : ImapCdfAttributes, optional The CDF attributes manager, with L1b variable attributes loaded. + Callers that only need the intermediate values (e.g. I-ALiRT, which + discards them after computing ratios) can omit this. Returns ------- @@ -64,7 +68,9 @@ def convert_to_rates( ], attrs=cdf_attrs.get_variable_attributes( "energy_per_charge", check_schema=False - ), + ) + if cdf_attrs is not None + else {}, ) dataset["energy_per_charge_label"] = xr.DataArray( np.array([f"{value:.3f}" for value in energy_per_charge]), @@ -73,7 +79,9 @@ def convert_to_rates( ], attrs=cdf_attrs.get_variable_attributes( "energy_per_charge_label", check_schema=False - ), + ) + if cdf_attrs is not None + else {}, ) if descriptor in [ diff --git a/imap_processing/ialirt/l0/process_codice.py b/imap_processing/ialirt/l0/process_codice.py index 3865e8dd8..8fd0684d5 100644 --- a/imap_processing/ialirt/l0/process_codice.py +++ b/imap_processing/ialirt/l0/process_codice.py @@ -12,7 +12,6 @@ import xarray as xr from numpy.typing import NDArray -from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes from imap_processing.codice import constants from imap_processing.codice.codice_l1a_ialirt_hi import l1a_ialirt_hi from imap_processing.codice.codice_l1a_lo_species import l1a_lo_species @@ -440,11 +439,6 @@ def process_codice( codice_lo_data: list[dict[str, Any]] = [] codice_hi_data: list[dict[str, Any]] = [] - # Get the L1b CDF attributes - cdf_attrs = ImapCdfAttributes() - cdf_attrs.add_instrument_global_attrs("codice") - cdf_attrs.add_instrument_variable_attrs("codice", "l1b") - # Subsecond time conversion specified in 7516-9054 GSW-FSW ICD. # Value of SCLK subseconds, unsigned, (LSB = 1/256 sec) met = calculate_time(dataset["sc_sclk_sec"], dataset["sc_sclk_sub_sec"], 256) @@ -483,7 +477,7 @@ def process_codice( l1a_lo = process_by_table_id(cod_lo_dataset, l1a_lut_path, l1a_lo_species) l1b_lo = cast( xr.Dataset, - convert_to_rates(l1a_lo, "lo-ialirt", cdf_attrs), + convert_to_rates(l1a_lo, "lo-ialirt"), ) mid_measurement = int((l1b_lo["epoch"][0] + l1b_lo["epoch"][-1]) // 2) yyyymmdd = datetime.datetime.strptime( From 2d5915e33f74edeece5459ee661da0c7c891289d Mon Sep 17 00:00:00 2001 From: Luisa Coakley Date: Thu, 13 Aug 2026 12:14:12 -0600 Subject: [PATCH 4/4] ruff --- imap_processing/codice/codice_l1b.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imap_processing/codice/codice_l1b.py b/imap_processing/codice/codice_l1b.py index e00850ab5..77a3cf2e8 100644 --- a/imap_processing/codice/codice_l1b.py +++ b/imap_processing/codice/codice_l1b.py @@ -39,7 +39,7 @@ def convert_to_rates( The L1b dataset containing the data to convert. descriptor : str The descriptor of the data product of interest. - cdf_attrs : ImapCdfAttributes, optional + cdf_attrs : ImapCdfAttributes The CDF attributes manager, with L1b variable attributes loaded. Callers that only need the intermediate values (e.g. I-ALiRT, which discards them after computing ratios) can omit this.