diff --git a/imap_processing/cdf/config/imap_codice_l2-hi-sectored_variable_attrs.yaml b/imap_processing/cdf/config/imap_codice_l2-hi-sectored_variable_attrs.yaml index 305450d02..6eb25485b 100644 --- a/imap_processing/cdf/config/imap_codice_l2-hi-sectored_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_codice_l2-hi-sectored_variable_attrs.yaml @@ -68,7 +68,7 @@ spin_sector: CATDESC: Spin Sector Index FIELDNAM: Spin Sector Index FILLVAL: *uint8_fillval - FORMAT: I2 + FORMAT: I3 LABLAXIS: " " SCALETYP: linear UNITS: " " diff --git a/imap_processing/cdf/config/imap_mag_l2_variable_attrs.yaml b/imap_processing/cdf/config/imap_mag_l2_variable_attrs.yaml index 0d869c751..bbd8658f8 100644 --- a/imap_processing/cdf/config/imap_mag_l2_variable_attrs.yaml +++ b/imap_processing/cdf/config/imap_mag_l2_variable_attrs.yaml @@ -141,9 +141,10 @@ pri_sens: qf_bitmask: <<: *support_default CATDESC: Bitmask indicating when spacecraft related activities influenced the measurement. + CDF_DATA_TYPE: CDF_UINT2 DICT_KEY: SPASE>Support>SupportQuantity:DataQuality FIELDNAM: Quality Bitmask - FILLVAL: 255 + FILLVAL: 65535 FORMAT: I3 LABLAXIS: QB VALIDMAX: 255 diff --git a/imap_processing/codice/codice_l2.py b/imap_processing/codice/codice_l2.py index f6e067ff0..a4ad81a41 100644 --- a/imap_processing/codice/codice_l2.py +++ b/imap_processing/codice/codice_l2.py @@ -1113,7 +1113,6 @@ def process_hi_sectored(dependencies: ProcessingInputCollection) -> xr.Dataset: # column remained same but spin angle incremented by 30 degrees for each # elevation angle. spin_angle = spin_angle.T - # Add spin angle variable using the new elevation_angle dimension l2_dataset["spin_angle"] = (("spin_sector", "elevation_angle"), spin_angle) l2_dataset["spin_angle"].attrs = cdf_attrs.get_variable_attributes( "spin_angle", check_schema=False diff --git a/imap_processing/mag/l2/mag_l2_data.py b/imap_processing/mag/l2/mag_l2_data.py index ea5bf6f80..beee1f2b4 100644 --- a/imap_processing/mag/l2/mag_l2_data.py +++ b/imap_processing/mag/l2/mag_l2_data.py @@ -212,7 +212,11 @@ def generate_dataset( ) direction_label = xr.DataArray( - np.array(["Bx", "By", "Bz"]), + np.array( + ["B_R", "B_T", "B_N"] + if self.frame == ValidFrames.RTN + else ["Bx", "By", "Bz"] + ), name="direction_label", dims=["direction_label"], attrs=attribute_manager.get_variable_attributes( @@ -246,7 +250,7 @@ def generate_dataset( ) quality_bitmask = xr.DataArray( - self.quality_bitmask, + self.quality_bitmask.astype(np.uint16), name="quality_bitmask", dims=["epoch"], attrs=attribute_manager.get_variable_attributes("qf_bitmask"), diff --git a/imap_processing/swapi/l2/swapi_l2.py b/imap_processing/swapi/l2/swapi_l2.py index 5440e8293..e6f8d9a60 100644 --- a/imap_processing/swapi/l2/swapi_l2.py +++ b/imap_processing/swapi/l2/swapi_l2.py @@ -290,6 +290,21 @@ def swapi_l2( "swp_coin_rate_stat_uncert_minus" ].attrs = cdf_manager.get_variable_attributes("swp_coin_rate_stat_uncert_minus") + depend_on_esa_energy = [ + "swp_l1a_flags", + "swp_pcem_rate", + "swp_scem_rate", + "swp_coin_rate", + "swp_pcem_rate_stat_uncert_plus", + "swp_pcem_rate_stat_uncert_minus", + "swp_scem_rate_stat_uncert_plus", + "swp_scem_rate_stat_uncert_minus", + "swp_coin_rate_stat_uncert_plus", + "swp_coin_rate_stat_uncert_minus", + ] + for variable in depend_on_esa_energy: + l2_dataset[variable].attrs["DEPEND_1"] = "esa_energy" + # TODO: add thruster firing flag # TODO: add other flags logger.info("SWAPI L2 processing complete") diff --git a/imap_processing/tests/codice/test_codice_hi_l2.py b/imap_processing/tests/codice/test_codice_hi_l2.py index 8bc712475..7713dda85 100644 --- a/imap_processing/tests/codice/test_codice_hi_l2.py +++ b/imap_processing/tests/codice/test_codice_hi_l2.py @@ -216,26 +216,23 @@ def test_l2_hi_sectored(mock_get_file_paths): if variable.startswith("unc_"): continue if variable == "spin_angle": - # The external validation file has outdated spin_angle values, but we - # still verify structure and basic numeric sanity to guard against - # regressions in the spin angle computation. - assert processed_l2[variable].dims == val_data[variable].dims, ( - f"Dimension mismatch in variable '{variable}'" + assert processed_l2[variable].dims == ( + "spin_sector", + "elevation_angle", ) spin_vals = processed_l2[variable].values - # All values should be finite and lie within a reasonable angular range. assert np.all(np.isfinite(spin_vals)), ( "spin_angle contains non-finite values" ) assert np.min(spin_vals) >= 0.0, "spin_angle has values below 0 degrees" assert np.max(spin_vals) <= 360.0, "spin_angle has values above 360 degrees" - continue - np.testing.assert_allclose( - processed_l2[variable].values, - val_data[variable].values, - rtol=1e-5, - err_msg=f"Mismatch in variable '{variable}'", - ) + else: + np.testing.assert_allclose( + processed_l2[variable].values, + val_data[variable].values, + rtol=1e-5, + err_msg=f"Mismatch in variable '{variable}'", + ) # Tests that dimensions match if variable in ["epoch_delta_plus", "epoch_delta_minus"]: continue @@ -285,7 +282,9 @@ def test_l2_hi_sectored(mock_get_file_paths): assert data_quality_attrs["VAR_TYPE"] == "data" assert data_quality_attrs["FORMAT"] == "I3" spin_sector_attrs = cdf_file.varattsget("spin_sector") - assert spin_sector_attrs["FORMAT"] == "I2" + assert spin_sector_attrs["FORMAT"] == "I3" + spin_angle_attrs = cdf_file.varattsget("spin_angle") + assert spin_angle_attrs["VAR_TYPE"] == "support_data" assert cdf_file.varattsget("energy_h")["FORMAT"] == "F12.6" assert cdf_file.varattsget("energy_h_minus")["FORMAT"] == "F12.6" assert cdf_file.varattsget("energy_h_plus")["FORMAT"] == "F12.6" diff --git a/imap_processing/tests/mag/test_mag_l1d.py b/imap_processing/tests/mag/test_mag_l1d.py index 2ebe5efe7..6550a0c86 100644 --- a/imap_processing/tests/mag/test_mag_l1d.py +++ b/imap_processing/tests/mag/test_mag_l1d.py @@ -1,6 +1,7 @@ import logging from unittest.mock import patch +import cdflib import numpy as np import pytest import xarray as xr @@ -548,6 +549,30 @@ def test_mago_magi_no_swap_functionality(mag_l1d_test_class): assert np.array_equal(result["epoch"].data, mago_epoch) +def test_mag_l1d_rtn_direction_label_written_cdf(mag_l1d_test_class): + """Test that shared MAG L1D metadata writes RTN component labels.""" + mag_l1d_test_class.frame = ValidFrames.RTN + + with patch( + "imap_processing.mag.l1d.mag_l1d_data.MagL2L1dBase.truncate_to_24h", + return_value=None, + ): + attributes = ImapCdfAttributes() + attributes.add_instrument_global_attrs("mag") + attributes.add_instrument_variable_attrs("mag", "l2") + + result = mag_l1d_test_class.generate_dataset( + attributes, np.datetime64("2000-01-01") + ) + + result.attrs["Data_version"] = "001" + cdf_filepath = write_cdf(result) + with cdflib.CDF(cdf_filepath) as cdf_file: + direction_label = cdf_file.varget("direction_label") + + np.testing.assert_array_equal(direction_label, np.array(["B_R", "B_T", "B_N"])) + + def test_enhanced_gradiometry_with_quality_flags_detailed(): """Test enhanced gradiometry calculation with quality flags and magnitude.""" # Test data with known differences diff --git a/imap_processing/tests/mag/test_mag_l2.py b/imap_processing/tests/mag/test_mag_l2.py index 03a0688e3..0520b62cf 100644 --- a/imap_processing/tests/mag/test_mag_l2.py +++ b/imap_processing/tests/mag/test_mag_l2.py @@ -130,9 +130,14 @@ def test_mag_l2_attributes( dataset["magnitude"].attrs["CATDESC"] == "Magnitude of the magnetic field" ) assert dataset["range"].attrs["CATDESC"] == "Range of the magnetometer sensor" + expected_direction_label = ( + np.array(["B_R", "B_T", "B_N"]) + if frame == "RTN" + else np.array(["Bx", "By", "Bz"]) + ) np.testing.assert_array_equal( dataset["direction_label"].data, - np.array(["Bx", "By", "Bz"]), + expected_direction_label, ) assert dataset["range"].attrs["DICT_KEY"] == ( "SPASE>Support>SupportQuantity:InstrumentMode" @@ -183,9 +188,14 @@ def test_mag_l2(norm_dataset, mag_test_l2_data): assert np.isclose(vector_attrs["VALIDMIN"], np.float32(-1.0e5)) assert np.isclose(vector_attrs["VALIDMAX"], np.float32(1.0e5)) assert vector_attrs["UNITS"] == "nT" + expected_direction_label = ( + np.array(["B_R", "B_T", "B_N"]) + if expected_frames[i] == ValidFrames.RTN + else np.array(["Bx", "By", "Bz"]) + ) np.testing.assert_array_equal( direction_label, - np.array(["Bx", "By", "Bz"]), + expected_direction_label, ) @@ -642,6 +652,7 @@ def test_qf(norm_dataset): ) assert output["quality_bitmask"].attrs["FIELDNAM"] == "Quality Bitmask" assert output["quality_bitmask"].attrs["LABLAXIS"] == "QB" + assert output["quality_bitmask"].dtype == np.uint16 assert output["quality_bitmask"].attrs["CATDESC"] == ( "Bitmask indicating when spacecraft related activities influenced " "the measurement." @@ -660,10 +671,14 @@ def test_qf(norm_dataset): with cdflib.CDF(cdf_filepath) as cdf_file: qf_attrs = cdf_file.varattsget("quality_flags") qf_bitmask_attrs = cdf_file.varattsget("quality_bitmask") + qf_bitmask_info = cdf_file.varinq("quality_bitmask") assert qf_attrs["FORMAT"] == "I1" assert int(qf_attrs["VALIDMAX"]) == 1 + assert qf_bitmask_info.Data_Type_Description == "CDF_UINT2" assert qf_bitmask_attrs["FORMAT"] == "I3" + assert int(qf_bitmask_attrs["FILLVAL"]) == 65535 + assert int(qf_bitmask_attrs["VALIDMIN"]) == 0 assert int(qf_bitmask_attrs["VALIDMAX"]) == 255 diff --git a/imap_processing/tests/swapi/test_swapi_l2.py b/imap_processing/tests/swapi/test_swapi_l2.py index af953a7b4..065f24764 100644 --- a/imap_processing/tests/swapi/test_swapi_l2.py +++ b/imap_processing/tests/swapi/test_swapi_l2.py @@ -141,6 +141,12 @@ def second_get_file_paths_side_effect(descriptor): assert esa_energy_attrs["VALIDMIN"] == np.float64(0.0) assert esa_energy_attrs["VAR_TYPE"] == "data" assert esa_energy_attrs["DEPEND_1"] == "esa_step" + assert cdf_file.varattsget("swp_l1a_flags")["DEPEND_1"] == "esa_energy" + assert cdf_file.varattsget("swp_pcem_rate")["DEPEND_1"] == "esa_energy" + assert ( + cdf_file.varattsget("swp_pcem_rate_stat_uncert_plus")["DEPEND_1"] + == "esa_energy" + ) assert esa_step_attrs["SCALETYP"] == "linear" assert "SCALE_TYP" not in esa_step_attrs assert esa_energy_attrs["CATDESC"] == (