From 7dd136f11d61bda6621f6b9380a059e576f1b77b Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Tue, 9 Dec 2025 17:19:23 +0000 Subject: [PATCH 01/11] Change MAGo and MAGi frame to MAG BASE frame for I-ALiRT, L1D, and L2 --- imap_processing/ialirt/l0/parse_mag.py | 4 ++-- imap_processing/mag/l2/mag_l2_data.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/imap_processing/ialirt/l0/parse_mag.py b/imap_processing/ialirt/l0/parse_mag.py index de66b239b..31360bc76 100644 --- a/imap_processing/ialirt/l0/parse_mag.py +++ b/imap_processing/ialirt/l0/parse_mag.py @@ -703,7 +703,7 @@ def process_packet( attitude_time, time_data["primary_epoch"], mago_out, - SpiceFrame.IMAP_MAG_O, + SpiceFrame.IMAP_MAG_BASE, ) magi_inertial_vector = transform_to_inertial( sc_spin_phase_rad.values, @@ -712,7 +712,7 @@ def process_packet( attitude_time, time_data["secondary_epoch"], magi_out, - SpiceFrame.IMAP_MAG_I, + SpiceFrame.IMAP_MAG_BASE, ) met = grouped_data["met"][(grouped_data["group"] == group).values] diff --git a/imap_processing/mag/l2/mag_l2_data.py b/imap_processing/mag/l2/mag_l2_data.py index e7a8af0f4..fca2891ff 100644 --- a/imap_processing/mag/l2/mag_l2_data.py +++ b/imap_processing/mag/l2/mag_l2_data.py @@ -20,8 +20,8 @@ class ValidFrames(Enum): """SPICE reference frames for output.""" - MAGO = SpiceFrame.IMAP_MAG_O - MAGI = SpiceFrame.IMAP_MAG_I + MAGO = SpiceFrame.IMAP_MAG_BASE + MAGI = SpiceFrame.IMAP_MAG_BASE DSRF = SpiceFrame.IMAP_DPS SRF = SpiceFrame.IMAP_SPACECRAFT GSE = SpiceFrame.IMAP_GSE From 03d756609eb47e9b3c6dadc57f963f345b1a3501 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 11:47:14 +0000 Subject: [PATCH 02/11] Add more parameters to valid frames enum to capture new names and to adjust for MAGO and MAGI having the same spice frame --- imap_processing/mag/l1d/mag_l1d_data.py | 10 ++--- imap_processing/mag/l2/mag_l2_data.py | 54 +++++++++++++++---------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/imap_processing/mag/l1d/mag_l1d_data.py b/imap_processing/mag/l1d/mag_l1d_data.py index 6e3d73256..3cf022abd 100644 --- a/imap_processing/mag/l1d/mag_l1d_data.py +++ b/imap_processing/mag/l1d/mag_l1d_data.py @@ -283,7 +283,7 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: # Self.frame should refer to the main data in self.vectors, which is MAGO # data. For most frames, MAGO and MAGI are in the same frame, except the # instrument reference frame. - if ValidFrames.MAGI in (self.frame, end_frame): + if ValidFrames.MAGI is self.frame or ValidFrames.MAGI is end_frame: raise ValueError( "MAGL1d.frame should never be equal to MAGI frame. If the " "data is in the instrument frame, use MAGO." @@ -298,8 +298,8 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: self.vectors = frame_transform( self.epoch_et, self.vectors, - from_frame=start_frame.value, - to_frame=end_frame.value, + from_frame=start_frame.spice_frame, + to_frame=end_frame.spice_frame, allow_spice_noframeconnect=True, ) @@ -311,8 +311,8 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: self.magi_vectors = frame_transform( self.magi_epoch_et, self.magi_vectors, - from_frame=start_frame.value, - to_frame=end_frame.value, + from_frame=start_frame.spice_frame, + to_frame=end_frame.spice_frame, allow_spice_noframeconnect=True, ) diff --git a/imap_processing/mag/l2/mag_l2_data.py b/imap_processing/mag/l2/mag_l2_data.py index fca2891ff..98f3e6213 100644 --- a/imap_processing/mag/l2/mag_l2_data.py +++ b/imap_processing/mag/l2/mag_l2_data.py @@ -20,13 +20,33 @@ class ValidFrames(Enum): """SPICE reference frames for output.""" - MAGO = SpiceFrame.IMAP_MAG_BASE - MAGI = SpiceFrame.IMAP_MAG_BASE - DSRF = SpiceFrame.IMAP_DPS - SRF = SpiceFrame.IMAP_SPACECRAFT - GSE = SpiceFrame.IMAP_GSE - GSM = SpiceFrame.IMAP_GSM - RTN = SpiceFrame.IMAP_RTN + MAGO = ("MAGO", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") + MAGI = ("MAGi", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") + DSRF = ("DSRF", SpiceFrame.IMAP_DPS, "vector_attrs_dsrf", "b_dsrf") + SRF = ( "SRF", SpiceFrame.IMAP_SPACECRAFT, "vector_attrs_srf", "b_srf") + GSE = ( "GSE", SpiceFrame.IMAP_GSE,"vector_attrs_gse", "b_gse") + GSM = ( "GSM", SpiceFrame.IMAP_GSM, "vector_attrs_gsm", "b_gsm") + RTN = ( "RTN",SpiceFrame.IMAP_RTN, "vector_attrs_rtn", "b_rtn") + + def __new__(cls, value, spice_frame, attrs_name, var_name): + obj = object.__new__(cls) + obj._value_ = value + obj._spice_frame_ = spice_frame + obj._vector_attrs_name_ = attrs_name + obj._var_name_ = var_name + return obj + + @property + def spice_frame(self): + return self._spice_frame_ + + @property + def vector_attrs_name(self): + return self._vector_attrs_name_ + + @property + def var_name(self): + return self._var_name_ @dataclass(kw_only=True) @@ -109,16 +129,6 @@ def generate_dataset( f"{self.frame.name.lower()}" ) - # Select the appropriate vector attributes based on the frame - frame_to_vector_attrs = { - ValidFrames.SRF: "vector_attrs_srf", - ValidFrames.DSRF: "vector_attrs_dsrf", - ValidFrames.GSE: "vector_attrs_gse", - ValidFrames.RTN: "vector_attrs_rtn", - ValidFrames.GSM: "vector_attrs_gsm", # L2 Only - } - vector_attrs_name = frame_to_vector_attrs.get(self.frame, "vector_attrs") - direction = xr.DataArray( np.arange(3), name="direction", @@ -148,9 +158,9 @@ def generate_dataset( vectors = xr.DataArray( self.vectors, - name="vectors", + name=self.frame.var_name, dims=["epoch", "direction"], - attrs=attribute_manager.get_variable_attributes(vector_attrs_name), + attrs=attribute_manager.get_variable_attributes(self.frame.vector_attrs_name), ) quality_flags = xr.DataArray( @@ -195,7 +205,7 @@ def generate_dataset( attrs=global_attributes, ) - output["vectors"] = vectors + output[self.frame.var_name] = vectors output["quality_flags"] = quality_flags output["quality_bitmask"] = quality_bitmask output["range"] = rng @@ -332,8 +342,8 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: self.vectors = frame_transform( self.epoch_et, self.vectors, - from_frame=self.frame.value, - to_frame=end_frame.value, + from_frame=self.frame.spice_frame, + to_frame=end_frame.spice_frame, allow_spice_noframeconnect=True, ) self.frame = end_frame From 18301b53863e87242a6d4c3bf8f04deff0b9652c Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 11:48:30 +0000 Subject: [PATCH 03/11] Update test to use new spice_frame formulation, and reflect that MAGO and MAGI might share a spice frame --- imap_processing/tests/mag/test_mag_l1d.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/imap_processing/tests/mag/test_mag_l1d.py b/imap_processing/tests/mag/test_mag_l1d.py index de24640fe..95bb61105 100644 --- a/imap_processing/tests/mag/test_mag_l1d.py +++ b/imap_processing/tests/mag/test_mag_l1d.py @@ -569,7 +569,6 @@ def test_enhanced_gradiometry_with_quality_flags_detailed(): ) assert np.array_equal(grad_ds["quality_flags"].data, expected_flags) - def test_rotate_frames(mag_l1d_test_class): # Reset to initial MAGO frame for this test mag_l1d_test_class.frame = ValidFrames.MAGO @@ -581,10 +580,8 @@ def test_rotate_frames(mag_l1d_test_class): def mock_frame_transform( epoch_et, vectors, from_frame, to_frame, allow_spice_noframeconnect ): - if from_frame == ValidFrames.MAGO.value: + if from_frame == ValidFrames.MAGO.spice_frame or from_frame == ValidFrames.MAGI.spice_frame: return vectors + 100 - elif from_frame == ValidFrames.MAGI.value: - return vectors + 200 else: return vectors + 300 @@ -600,20 +597,22 @@ def mock_frame_transform( # First call should be for MAGO vectors first_call_args = mock_transform_l1d.call_args_list[0] - assert first_call_args[1]["from_frame"] == ValidFrames.MAGO.value - assert first_call_args[1]["to_frame"] == ValidFrames.SRF.value + assert first_call_args[1]["from_frame"] == ValidFrames.MAGO.spice_frame + assert first_call_args[1]["to_frame"] == ValidFrames.SRF.spice_frame # Second call should be for MAGI vectors second_call_args = mock_transform_l1d.call_args_list[1] - assert second_call_args[1]["from_frame"] == ValidFrames.MAGI.value - assert second_call_args[1]["to_frame"] == ValidFrames.SRF.value + assert second_call_args[1]["from_frame"] == ValidFrames.MAGI.spice_frame + assert second_call_args[1]["to_frame"] == ValidFrames.SRF.spice_frame + + # MAGO frame and MAGi frame not necessarily different (and are now the same) - # Check that MAGO vectors were transformed from MAGO frame (+100) + # Check that MAGO vectors were transformed (+100) expected_mago_vectors = initial_vectors + 100 np.testing.assert_array_equal(mag_l1d_test_class.vectors, expected_mago_vectors) - # Check that MAGI vectors were transformed from MAGI frame (+200) - expected_magi_vectors = initial_magi_vectors + 200 + # Check that MAGI vectors were transformed (+100) + expected_magi_vectors = initial_magi_vectors + 100 np.testing.assert_array_equal( mag_l1d_test_class.magi_vectors, expected_magi_vectors ) From b710ef966d368d36924fd5b3e5191afb42004c48 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 11:49:48 +0000 Subject: [PATCH 04/11] Update tests to use spice_frame, and assert that correct variable name is in dataset --- imap_processing/tests/mag/test_mag_l2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imap_processing/tests/mag/test_mag_l2.py b/imap_processing/tests/mag/test_mag_l2.py index c32bbaeac..5161d4d2f 100644 --- a/imap_processing/tests/mag/test_mag_l2.py +++ b/imap_processing/tests/mag/test_mag_l2.py @@ -105,7 +105,7 @@ def test_mag_l2(norm_dataset, mag_test_l2_data): ) for i, dataset in enumerate(l2): - assert "vectors" in dataset.data_vars + assert expected_frames[i].var_name in dataset.data_vars assert expected_frames[i].name in dataset.attrs["Data_type"] @@ -114,7 +114,7 @@ def return_some_nan_matrices_for_dsrf( et, from_frame, to_frame, allow_spice_noframeconnect ): matrices = np.tile(np.eye(3), (len(et), 1, 1)) - if to_frame == ValidFrames.DSRF.value: + if to_frame == ValidFrames.DSRF.spice_frame: for i in range(10, matrices.shape[0], 10): # every 10th matrix is NaN matrices[i] = np.full((3, 3), np.nan) return matrices From cc2ee66920e3a6546103bd569ff1f5f8ac6a3578 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 11:55:14 +0000 Subject: [PATCH 05/11] Restore this line as it no longer needs to be changed --- imap_processing/mag/l1d/mag_l1d_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imap_processing/mag/l1d/mag_l1d_data.py b/imap_processing/mag/l1d/mag_l1d_data.py index 3cf022abd..9688e381c 100644 --- a/imap_processing/mag/l1d/mag_l1d_data.py +++ b/imap_processing/mag/l1d/mag_l1d_data.py @@ -283,7 +283,7 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: # Self.frame should refer to the main data in self.vectors, which is MAGO # data. For most frames, MAGO and MAGI are in the same frame, except the # instrument reference frame. - if ValidFrames.MAGI is self.frame or ValidFrames.MAGI is end_frame: + if if ValidFrames.MAGI in (self.frame, end_frame): raise ValueError( "MAGL1d.frame should never be equal to MAGI frame. If the " "data is in the instrument frame, use MAGO." From 4cbc4555143bc721d1dca69e26a674cf70d487ba Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 11:56:28 +0000 Subject: [PATCH 06/11] fix: typo --- imap_processing/mag/l1d/mag_l1d_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imap_processing/mag/l1d/mag_l1d_data.py b/imap_processing/mag/l1d/mag_l1d_data.py index 9688e381c..add61b847 100644 --- a/imap_processing/mag/l1d/mag_l1d_data.py +++ b/imap_processing/mag/l1d/mag_l1d_data.py @@ -283,7 +283,7 @@ def rotate_frame(self, end_frame: ValidFrames) -> None: # Self.frame should refer to the main data in self.vectors, which is MAGO # data. For most frames, MAGO and MAGI are in the same frame, except the # instrument reference frame. - if if ValidFrames.MAGI in (self.frame, end_frame): + if ValidFrames.MAGI in (self.frame, end_frame): raise ValueError( "MAGL1d.frame should never be equal to MAGI frame. If the " "data is in the instrument frame, use MAGO." From 5deff0b657fd9d7740060c4726599f3f7268591f Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 13:49:50 +0000 Subject: [PATCH 07/11] pre-commit fixes --- imap_processing/mag/l2/mag_l2_data.py | 73 +++++++++++++++++++---- imap_processing/tests/mag/test_mag_l1d.py | 3 +- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/imap_processing/mag/l2/mag_l2_data.py b/imap_processing/mag/l2/mag_l2_data.py index 98f3e6213..11199c270 100644 --- a/imap_processing/mag/l2/mag_l2_data.py +++ b/imap_processing/mag/l2/mag_l2_data.py @@ -23,12 +23,37 @@ class ValidFrames(Enum): MAGO = ("MAGO", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") MAGI = ("MAGi", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") DSRF = ("DSRF", SpiceFrame.IMAP_DPS, "vector_attrs_dsrf", "b_dsrf") - SRF = ( "SRF", SpiceFrame.IMAP_SPACECRAFT, "vector_attrs_srf", "b_srf") - GSE = ( "GSE", SpiceFrame.IMAP_GSE,"vector_attrs_gse", "b_gse") - GSM = ( "GSM", SpiceFrame.IMAP_GSM, "vector_attrs_gsm", "b_gsm") - RTN = ( "RTN",SpiceFrame.IMAP_RTN, "vector_attrs_rtn", "b_rtn") + SRF = ("SRF", SpiceFrame.IMAP_SPACECRAFT, "vector_attrs_srf", "b_srf") + GSE = ("GSE", SpiceFrame.IMAP_GSE, "vector_attrs_gse", "b_gse") + GSM = ("GSM", SpiceFrame.IMAP_GSM, "vector_attrs_gsm", "b_gsm") + RTN = ("RTN", SpiceFrame.IMAP_RTN, "vector_attrs_rtn", "b_rtn") + + _spice_frame_: SpiceFrame + _vector_attrs_name_: str + _var_name_: str + + def __new__( + cls, value: str, spice_frame: SpiceFrame, attrs_name: str, var_name: str + ) -> "ValidFrames": + """ + Construct a new Valid Frame. + + Parameters + ---------- + value : str + Unique name of the frame. + spice_frame : str + The SPICE frame name corresponding to this frame. + attrs_name : str + The name of the variable attributes in the attribute manager for this frame. + var_name : str + The name of the variable in the output dataset for this frame. - def __new__(cls, value, spice_frame, attrs_name, var_name): + Returns + ------- + ValidFrame : ValidFrame + A ValidFrame enum member. + """ obj = object.__new__(cls) obj._value_ = value obj._spice_frame_ = spice_frame @@ -37,15 +62,39 @@ def __new__(cls, value, spice_frame, attrs_name, var_name): return obj @property - def spice_frame(self): + def spice_frame(self) -> SpiceFrame: + """ + Get the SPICE frame name for this ValidFrame. + + Returns + ------- + spice_frame : str + The frame's associated spice frame. + """ return self._spice_frame_ - + @property - def vector_attrs_name(self): + def vector_attrs_name(self) -> str: + """ + Get the vector attributes name for this valid frame. + + Returns + ------- + vector_attrs_name : str + The frame's associated vector attributes name. + """ return self._vector_attrs_name_ - + @property - def var_name(self): + def var_name(self) -> str: + """ + Get the vector variable name for this valid frame. + + Returns + ------- + var_name : str + The frame's associated vectors variable name. + """ return self._var_name_ @@ -160,7 +209,9 @@ def generate_dataset( self.vectors, name=self.frame.var_name, dims=["epoch", "direction"], - attrs=attribute_manager.get_variable_attributes(self.frame.vector_attrs_name), + attrs=attribute_manager.get_variable_attributes( + self.frame.vector_attrs_name + ), ) quality_flags = xr.DataArray( diff --git a/imap_processing/tests/mag/test_mag_l1d.py b/imap_processing/tests/mag/test_mag_l1d.py index 95bb61105..d6d5f6d59 100644 --- a/imap_processing/tests/mag/test_mag_l1d.py +++ b/imap_processing/tests/mag/test_mag_l1d.py @@ -569,6 +569,7 @@ def test_enhanced_gradiometry_with_quality_flags_detailed(): ) assert np.array_equal(grad_ds["quality_flags"].data, expected_flags) + def test_rotate_frames(mag_l1d_test_class): # Reset to initial MAGO frame for this test mag_l1d_test_class.frame = ValidFrames.MAGO @@ -580,7 +581,7 @@ def test_rotate_frames(mag_l1d_test_class): def mock_frame_transform( epoch_et, vectors, from_frame, to_frame, allow_spice_noframeconnect ): - if from_frame == ValidFrames.MAGO.spice_frame or from_frame == ValidFrames.MAGI.spice_frame: + if from_frame in [ValidFrames.MAGO.spice_frame, ValidFrames.MAGI.spice_frame]: return vectors + 100 else: return vectors + 300 From ec7ea300ca4d012a6be5f641864ab274b0a88d9e Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 13:58:40 +0000 Subject: [PATCH 08/11] Address comments from discussion with Alastair on future proofing this change --- imap_processing/mag/l2/mag_l2_data.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/imap_processing/mag/l2/mag_l2_data.py b/imap_processing/mag/l2/mag_l2_data.py index 11199c270..09d951e10 100644 --- a/imap_processing/mag/l2/mag_l2_data.py +++ b/imap_processing/mag/l2/mag_l2_data.py @@ -20,8 +20,31 @@ class ValidFrames(Enum): """SPICE reference frames for output.""" + """ + Default MAGO and MAGI L1D and L2 frames both map to the same SPICE frame. + This is because the idealised IMAP_MAG_BASE frame is used for both sensors, + as the MAG team provides a calibration matrix to convert from the real mechanical + mount as assessed in flight into the idealised frame. + + MAGO_GROUND_CAL and MAGI_GROUND_CAL additionally included for reference to the + ground assessed mount, and for future use if needed. + """ MAGO = ("MAGO", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") - MAGI = ("MAGi", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") + MAGI = ("MAGI", SpiceFrame.IMAP_MAG_BASE, "vector_attrs", "vectors") + + MAGO_GROUND_CAL = ( + "MAGO_GROUND_CAL", + SpiceFrame.IMAP_MAG_O, + "vector_attrs", + "vectors", + ) + MAGI_GROUND_CAL = ( + "MAGI_GROUND_CAL", + SpiceFrame.IMAP_MAG_I, + "vector_attrs", + "vectors", + ) + DSRF = ("DSRF", SpiceFrame.IMAP_DPS, "vector_attrs_dsrf", "b_dsrf") SRF = ("SRF", SpiceFrame.IMAP_SPACECRAFT, "vector_attrs_srf", "b_srf") GSE = ("GSE", SpiceFrame.IMAP_GSE, "vector_attrs_gse", "b_gse") From 1a827117b274d938951bf941dd2c68f15249cac4 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 14:25:44 +0000 Subject: [PATCH 09/11] Update tests to use new variable names --- imap_processing/tests/mag/test_mag_l1d.py | 13 +++++++------ imap_processing/tests/mag/test_mag_l2.py | 14 ++++++++------ imap_processing/tests/mag/test_mag_validation.py | 6 +++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/imap_processing/tests/mag/test_mag_l1d.py b/imap_processing/tests/mag/test_mag_l1d.py index d6d5f6d59..861ded966 100644 --- a/imap_processing/tests/mag/test_mag_l1d.py +++ b/imap_processing/tests/mag/test_mag_l1d.py @@ -94,8 +94,9 @@ def test_mag_l1d(mag_test_l1d_data, norm_dataset, furnish_kernels, fake_mag_spin ) # Should have: 4 norm frames + 4 burst frames + spin offsets + 2 gradiometry offsets + frame = l1d[0].attrs["Logical_source"].split("-")[-1].lower() assert len(l1d) == 11 - assert "vectors" in l1d[0].data_vars + assert f"b_{frame}" in l1d[0].data_vars # Check that expected logical sources are present logical_sources = [ds.attrs.get("Logical_source", "") for ds in l1d] @@ -181,11 +182,11 @@ def test_mag_l1d_attributes( f"got '{logical_source_parts[2]}'" ) - vectors_attrs = dataset["vectors"].attrs - assert "DICT_KEY" in vectors_attrs - frame = dataset.attrs["Logical_source"].split("-")[-1].upper() + vectors_attrs = dataset[f"b_{frame.lower()}"].attrs + assert "DICT_KEY" in vectors_attrs + assert f"CoordinateSystemName:{frame}" in vectors_attrs["DICT_KEY"] assert "magnitude" in dataset.data_vars @@ -482,7 +483,7 @@ def test_mago_magi_swap_functionality(mag_l1d_test_class): assert np.array_equal(mag_l1d_test_class.vectors, mago_vectors) assert np.array_equal(mag_l1d_test_class.epoch, mago_epoch) - assert np.array_equal(result["vectors"].data, magi_vectors) + assert np.array_equal(result[mag_l1d_test_class.frame.var_name].data, magi_vectors) assert np.array_equal(result["epoch"].data, magi_epoch) @@ -509,7 +510,7 @@ def test_mago_magi_no_swap_functionality(mag_l1d_test_class): assert np.array_equal(mag_l1d_test_class.vectors, mago_vectors) assert np.array_equal(mag_l1d_test_class.epoch, mago_epoch) - assert np.array_equal(result["vectors"].data, mago_vectors) + assert np.array_equal(result[mag_l1d_test_class.frame.var_name].data, mago_vectors) assert np.array_equal(result["epoch"].data, mago_epoch) diff --git a/imap_processing/tests/mag/test_mag_l2.py b/imap_processing/tests/mag/test_mag_l2.py index 5161d4d2f..3bba528df 100644 --- a/imap_processing/tests/mag/test_mag_l2.py +++ b/imap_processing/tests/mag/test_mag_l2.py @@ -59,14 +59,14 @@ def test_mag_l2_attributes(norm_dataset, mag_test_l2_data, data_mode): assert logical_source_parts[2] == "l2", ( f"Expected data_level 'l2' in Logical_source, " f"got '{logical_source_parts[2]}'" - ) - - vectors_attrs = dataset["vectors"].attrs - assert "DICT_KEY" in vectors_attrs + ) # Extract frame from logical source frame = dataset.attrs["Logical_source"].split("-")[-1].upper() + vectors_attrs = dataset[f"b_{frame.lower()}"].attrs + assert "DICT_KEY" in vectors_attrs + assert f"CoordinateSystemName:{frame}" in vectors_attrs["DICT_KEY"] assert "magnitude" in dataset.data_vars @@ -135,14 +135,16 @@ def return_some_nan_matrices_for_dsrf( assert len(l2) == 5, "L2 should produce 5 frames" + all_vars = ["b_srf", "b_gse", "b_gsm", "b_rtn", "b_dsrf"] + for dataset in l2: - assert "vectors" in dataset.data_vars + assert len(set(all_vars) & set(dataset.data_vars)) == 1, "Each dataset should have one of the expected vector variables" assert ( l2[-1].attrs["Data_type"] == "L2_norm-dsrf>Level 2 normal rate data in DSRF" ), "Last frame should be DSRF" - dsrf_vectors = l2[-1]["vectors"].data + dsrf_vectors = l2[-1]["b_dsrf"].data for i in range(10, len(dsrf_vectors), 10): assert np.isnan(dsrf_vectors[i]).all(), f"Vectors at index {i} should be NaN" diff --git a/imap_processing/tests/mag/test_mag_validation.py b/imap_processing/tests/mag/test_mag_validation.py index 8721ec14f..696383495 100644 --- a/imap_processing/tests/mag/test_mag_validation.py +++ b/imap_processing/tests/mag/test_mag_validation.py @@ -398,19 +398,19 @@ def test_mag_l2_validation(test_number, mode): assert np.allclose( expected_output["x"].iloc[index], - l2["vectors"].data[index][0], + l2["b_srf"].data[index][0], atol=1e-5, rtol=0, ) assert np.allclose( expected_output["y"].iloc[index], - l2["vectors"].data[index][1], + l2["b_srf"].data[index][1], atol=1e-5, rtol=0, ) assert np.allclose( expected_output["z"].iloc[index], - l2["vectors"].data[index][2], + l2["b_srf"].data[index][2], atol=1e-5, rtol=0, ) From 1ec703d6d4498e999e19955664383c344538f3f4 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Wed, 10 Dec 2025 14:28:45 +0000 Subject: [PATCH 10/11] fix: issues identified by precommit --- imap_processing/tests/mag/test_mag_l2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imap_processing/tests/mag/test_mag_l2.py b/imap_processing/tests/mag/test_mag_l2.py index 3bba528df..69f7268f3 100644 --- a/imap_processing/tests/mag/test_mag_l2.py +++ b/imap_processing/tests/mag/test_mag_l2.py @@ -59,7 +59,7 @@ def test_mag_l2_attributes(norm_dataset, mag_test_l2_data, data_mode): assert logical_source_parts[2] == "l2", ( f"Expected data_level 'l2' in Logical_source, " f"got '{logical_source_parts[2]}'" - ) + ) # Extract frame from logical source frame = dataset.attrs["Logical_source"].split("-")[-1].upper() @@ -138,7 +138,9 @@ def return_some_nan_matrices_for_dsrf( all_vars = ["b_srf", "b_gse", "b_gsm", "b_rtn", "b_dsrf"] for dataset in l2: - assert len(set(all_vars) & set(dataset.data_vars)) == 1, "Each dataset should have one of the expected vector variables" + assert len(set(all_vars) & set(dataset.data_vars)) == 1, ( + "Each dataset should have one of the expected vector variables" + ) assert ( l2[-1].attrs["Data_type"] == "L2_norm-dsrf>Level 2 normal rate data in DSRF" From 707cfa1c0e975a77b6f197d558a359514943f468 Mon Sep 17 00:00:00 2001 From: Mhairi Finlayson Date: Thu, 11 Dec 2025 17:04:32 +0000 Subject: [PATCH 11/11] Use spice frame abstraction in I-ALiRT --- imap_processing/ialirt/l0/parse_mag.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imap_processing/ialirt/l0/parse_mag.py b/imap_processing/ialirt/l0/parse_mag.py index 31360bc76..a0500c07d 100644 --- a/imap_processing/ialirt/l0/parse_mag.py +++ b/imap_processing/ialirt/l0/parse_mag.py @@ -24,7 +24,7 @@ shift_time, ) from imap_processing.mag.l1d.mag_l1d_data import MagL1d -from imap_processing.mag.l2.mag_l2_data import MagL2L1dBase +from imap_processing.mag.l2.mag_l2_data import MagL2L1dBase, ValidFrames from imap_processing.spice.geometry import ( SpiceFrame, cartesian_to_spherical, @@ -703,7 +703,7 @@ def process_packet( attitude_time, time_data["primary_epoch"], mago_out, - SpiceFrame.IMAP_MAG_BASE, + ValidFrames.MAGO.spice_frame, ) magi_inertial_vector = transform_to_inertial( sc_spin_phase_rad.values, @@ -712,7 +712,7 @@ def process_packet( attitude_time, time_data["secondary_epoch"], magi_out, - SpiceFrame.IMAP_MAG_BASE, + ValidFrames.MAGI.spice_frame, ) met = grouped_data["met"][(grouped_data["group"] == group).values]