Skip to content
6 changes: 3 additions & 3 deletions imap_processing/lo/l1b/lo_l1b.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
cartesian_to_latitudinal,
frame_transform,
get_spacecraft_to_instrument_spin_phase_offset,
lo_instrument_pointing,
instrument_pointing,
)
from imap_processing.spice.repoint import (
get_pointing_mid_time,
Expand Down Expand Up @@ -1140,8 +1140,8 @@ def set_pointing_direction(l1b_de: xr.Dataset) -> xr.Dataset:
# Get the pointing bin for each DE
et = ttj2000ns_to_et(l1b_de["epoch"])
# get the direction in HAE coordinates
direction = lo_instrument_pointing(
et, l1b_de["pivot_angle"].values[0], SpiceFrame.IMAP_HAE, cartesian=True
direction = instrument_pointing(
et, SpiceFrame.IMAP_LO_INSTR, SpiceFrame.IMAP_HAE, cartesian=True
)

# TODO: Need to ask Lo what to do if a latitude is outside of the
Expand Down
18 changes: 14 additions & 4 deletions imap_processing/spice/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class SpiceFrame(IntEnum):
IMAP_LO_BASE = -43100
IMAP_LO = -43101
IMAP_LO_STAR_SENSOR = -43102
IMAP_LO_INSTR = -43103
IMAP_HI_45 = -43150
IMAP_HI_90 = -43151
IMAP_ULTRA_45 = -43200
Expand Down Expand Up @@ -96,6 +97,7 @@ class SpiceFrame(IntEnum):
SpiceFrame.IMAP_LO_BASE: np.array([0, -1, 0]),
SpiceFrame.IMAP_LO: np.array([0, -1, 0]),
SpiceFrame.IMAP_LO_STAR_SENSOR: np.array([0, -1, 0]),
SpiceFrame.IMAP_LO_INSTR: np.array([0, -1, 0]),
SpiceFrame.IMAP_HI_45: np.array([0, 1, 0]),
SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
Expand Down Expand Up @@ -146,7 +148,9 @@ def imap_state(
return np.asarray(state)


def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
def get_instrument_mounting_az_el(
instrument: SpiceFrame, et: float = 0.0
) -> np.ndarray:
"""
Calculate the azimuth and elevation angle of instrument mounting.

Expand All @@ -158,6 +162,11 @@ def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
----------
instrument : SpiceFrame
Instrument to get the azimuth and elevation angles for.
et : float
Ephemeris time at which to evaluate the mounting. Only matters for
frames that move relative to the spacecraft: IMAP_LO_INSTR and
IMAP_LO_STAR_SENSOR ride on the Lo pivot platform, so a Lo pivot CK
covering `et` must be loaded. Defaults to 0.

Returns
-------
Expand All @@ -172,6 +181,8 @@ def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
# Most of these vectors are the same as the instrument boresight vector.
mounting_normal_vector = {
SpiceFrame.IMAP_LO_BASE: np.array([0, 0, -1]),
SpiceFrame.IMAP_LO_INSTR: np.array([0, -1, 0]),
SpiceFrame.IMAP_LO_STAR_SENSOR: np.array([0, -1, 0]),
SpiceFrame.IMAP_HI_45: np.array([0, 1, 0]),
SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
Expand All @@ -187,9 +198,8 @@ def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
}

# Get the instrument mounting normal vector expressed in the spacecraft frame
# The reference frames are fixed, so the et argument can be fixed at 0
instrument_normal_sc = frame_transform(
0, mounting_normal_vector[instrument], instrument, SpiceFrame.IMAP_SPACECRAFT
et, mounting_normal_vector[instrument], instrument, SpiceFrame.IMAP_SPACECRAFT
)
# Convert the cartesian coordinate to azimuth/elevation angles in degrees
return np.rad2deg(
Expand Down Expand Up @@ -223,7 +233,7 @@ def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> fl
The spin phase offset from the spacecraft to the instrument.
"""
phase_offset_lookup = {
# Phase offset values based on imap_130.tf frame kernel
# Phase offset values based on imap_140.tf frame kernel
# See docstring notes for details on how these values were determined.
SpiceFrame.IMAP_LO: 60 / 360, # (330 + 90) % 360 = 60
SpiceFrame.IMAP_HI_45: 344.8264 / 360, # 255 + 90 = 345
Expand Down
60 changes: 57 additions & 3 deletions imap_processing/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@

from imap_processing import imap_module_directory
from imap_processing.cdf.utils import load_cdf
from imap_processing.spice import IMAP_SC_ID
from imap_processing.spice import config as spice_config
from imap_processing.spice.time import TTJ2000_EPOCH, met_to_ttj2000ns
from imap_processing.spice.geometry import SpiceFrame
from imap_processing.spice.time import (
TICK_DURATION,
TTJ2000_EPOCH,
met_to_ttj2000ns,
)
from imap_processing.tests.external_test_data_config import EXTERNAL_TEST_DATA


Expand Down Expand Up @@ -209,6 +215,54 @@ def furnish_kernels(kernels: list[Path]):
return furnish_kernels


@pytest.fixture
def lo_pivot_ck(tmp_path, spice_test_data_path):
"""
Return a function that writes a CK holding a constant IMAP-Lo pivot angle.

Examples
--------
>>> def test_lo_pointing(lo_pivot_ck, furnish_kernels):
>>> ck_path = lo_pivot_ck(pivot_angle=75.0)
>>> with furnish_kernels(["naif0012.tls", "imap_sclk_0036.tsc",
>>> "imap_140.tf", ck_path]):
>>> result = spicey_function()
"""

def write_lo_pivot_ck(
pivot_angle: float = 90.0,
start_utc: str = "2026-09-09T00:00:00",
end_utc: str = "2026-09-10T00:00:00",
) -> Path:
ck_path = tmp_path / f"imap_lo_pivot_{pivot_angle:g}deg.bc"
ck_path.unlink(missing_ok=True)
kernels = ["naif0012.tls", "imap_sclk_0036.tsc", "imap_140.tf"]
with spiceypy.KernelPool([str(spice_test_data_path / k) for k in kernels]):
start = spiceypy.sce2c(IMAP_SC_ID, spiceypy.str2et(start_utc))
end = spiceypy.sce2c(IMAP_SC_ID, spiceypy.str2et(end_utc))
# Rotate by pivot_angle along axis 1 (X axis)
quat = spiceypy.m2q(spiceypy.rotate(np.deg2rad(pivot_angle), 1))
handle = spiceypy.ckopn(str(ck_path), "IMAP-Lo pivot test CK", 0)
spiceypy.ckw02(
handle,
start,
end,
SpiceFrame.IMAP_LO.value,
SpiceFrame.IMAP_LO_BASE.name,
f"Constant pivot {pivot_angle:g} deg",
1, # one record covering the whole segment
np.array([start]), # record start
np.array([end]), # record stop
quat,
np.zeros(3), # no rotation during the record
np.array([TICK_DURATION]), # seconds per SCLK tick
)
spiceypy.ckcls(handle)
return ck_path

return write_lo_pivot_ck


@pytest.fixture
def use_test_spin_data_csv(monkeypatch):
"""Monkeypatches `spin._spin_table_paths` to the input Path."""
Expand Down Expand Up @@ -474,7 +528,7 @@ def imap_ena_sim_metakernel(furnish_kernels, _download_kernels):
"naif0012.tls",
"imap_spk_demo.bsp",
"sim_1yr_imap_attitude.bc",
"imap_130.tf",
"imap_140.tf",
"de440s.bsp",
"imap_science_120.tf",
"sim_1yr_imap_pointing_frame.bc",
Expand All @@ -485,7 +539,7 @@ def imap_ena_sim_metakernel(furnish_kernels, _download_kernels):

@pytest.fixture
def imap_ialirt_sim_metakernel(furnish_kernels):
kernels = ["imap_130.tf"]
kernels = ["imap_140.tf"]
with furnish_kernels(kernels) as k:
yield k

Expand Down
4 changes: 2 additions & 2 deletions imap_processing/tests/glows/test_glows_l1b.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ def test_hist_spice_output(
"naif0012.tls",
"de440s.bsp",
"imap_sclk_0000.tsc",
"imap_130.tf",
"imap_140.tf",
"imap_science_120.tf",
"sim_1yr_imap_attitude.bc",
"sim_1yr_imap_pointing_frame.bc",
Expand Down Expand Up @@ -738,7 +738,7 @@ def test_calculate_calculate_look_vectors_dps_uses_correct_azimuth_calculation(
furnish_kernels,
):
kernels = [
"imap_130.tf",
"imap_140.tf",
]
with furnish_kernels(kernels):
imap_spin_angle_bin_cntr = np.array([0, 90, 180, 270])
Expand Down
2 changes: 1 addition & 1 deletion imap_processing/tests/glows/test_glows_l2_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def test_ecliptic_coords_computation(furnish_kernels):
kernels = [
"naif0012.tls",
"imap_sclk_0000.tsc",
"imap_130.tf",
"imap_140.tf",
"imap_science_120.tf",
"sim_1yr_imap_pointing_frame.bc",
]
Expand Down
2 changes: 1 addition & 1 deletion imap_processing/tests/ialirt/unit/test_ialirt_spice.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_transform_instrument_vectors_to_inertial_single(furnish_kernels):

kernels = [
"imap_science_130.tf",
"imap_130.tf",
"imap_140.tf",
"naif0012.tls",
"de440s.bsp",
"imap_recon_od005_20250925_20251014_v01.bsp",
Expand Down
4 changes: 2 additions & 2 deletions imap_processing/tests/ialirt/unit/test_parse_mag.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ def test_transform_to_frames(furnish_kernels, spice_test_data_path):

kernels = [
"imap_science_130.tf",
"imap_130.tf",
"imap_140.tf",
"naif0012.tls",
"de440s.bsp",
"imap_spk_demo.bsp",
Expand Down Expand Up @@ -638,7 +638,7 @@ def test_process_packet(
"""Test the process_packet function."""
kernels = [
"imap_science_130.tf",
"imap_130.tf",
"imap_140.tf",
"naif0012.tls",
"de440s.bsp",
"imap_recon_od005_20250925_20251014_v01.bsp",
Expand Down
8 changes: 4 additions & 4 deletions imap_processing/tests/lo/test_lo_l1b.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def l1a_hist():
return_value=np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]),
)
@patch(
"imap_processing.lo.l1b.lo_l1b.lo_instrument_pointing",
"imap_processing.lo.l1b.lo_l1b.instrument_pointing",
return_value=np.zeros((2000, 3)),
)
@patch(
Expand All @@ -230,7 +230,7 @@ def l1a_hist():
def test_lo_l1b_de(
mock_interpolate_spin_data,
mock_frame_transform,
mock_lo_instrument_pointing,
mock_instrument_pointing,
mocked_get_pointing_times,
mock_spin_number,
mock_cartesian_to_latitudinal,
Expand Down Expand Up @@ -758,12 +758,12 @@ def test_identify_species(attr_mgr_l1b):


@patch(
"imap_processing.lo.l1b.lo_l1b.lo_instrument_pointing",
"imap_processing.lo.l1b.lo_l1b.instrument_pointing",
return_value=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]),
)
@pytest.mark.external_test_data
@pytest.mark.external_kernel
def test_set_direction(mock_lo_instrument_pointing, imap_ena_sim_metakernel):
def test_set_direction(mock_instrument_pointing, imap_ena_sim_metakernel):
# Arrange
l1b_de = xr.Dataset(
{
Expand Down
2 changes: 1 addition & 1 deletion imap_processing/tests/mag/test_mag_l1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_calculate_spin_offsets(
kernels = [
"naif0012.tls",
"imap_sclk_0000.tsc",
"imap_130.tf",
"imap_140.tf",
"imap_science_120.tf",
"sim_1yr_imap_attitude.bc",
"sim_1yr_imap_pointing_frame.bc",
Expand Down
Loading
Loading