diff --git a/ale/base/data_naif.py b/ale/base/data_naif.py index e9801729cec5d72225312814719e125a65e654d2..4db795a9c6cdc3c516305affa65b63a721eaa745 100644 --- a/ale/base/data_naif.py +++ b/ale/base/data_naif.py @@ -1,6 +1,8 @@ import spiceypy as spice import numpy as np from ale.base.type_sensor import Framer +from ale.transformation import FrameNode +from ale.rotation import TimeDependentRotation class NaifSpice(): def __enter__(self): @@ -74,7 +76,10 @@ class NaifSpice(): @property def target_frame_id(self): - return spice.gipool('BODY_FRAME_CODE', 0, 1) + frame_id, frame_name, found = spice.cidfrm(self.target_id) + if not found: + raise ValueError("No reference frame could be found for target ID {}.".format(self.target_id)) + return frame_id @property def sensor_frame_id(self): @@ -150,10 +155,71 @@ class NaifSpice(): @property def frame_chain(self): - pass + """ + Return the root node of the rotation frame tree/chain. + + The root node is the J2000 reference frame. The other nodes in the + tree can be accessed via the methods in the FrameNode class. + + This property expects the ephemeris_time property/attribute to be defined. + It should be a list of the ephemeris seconds past the J2000 epoch for each + exposure in the image. + + Returns + ------- + FrameNode + The root node of the frame tree. This will always be the J2000 reference frame. + """ + if not hasattr(self, '_root_frame'): + j2000_id = 1 #J2000 is our root reference frame + self._root_frame = FrameNode(j2000_id) + + sensor_quats = np.zeros((len(self.ephemeris_time), 4)) + sensor_times = np.array(self.ephemeris_time) + body_quats = np.zeros((len(self.ephemeris_time), 4)) + body_times = np.array(self.ephemeris_time) + for i, time in enumerate(self.ephemeris_time): + sensor2j2000 = spice.pxform( + self.sensor_frame_id, + j2000_id, + time) + q_sensor = spice.m2q(sensor2j2000) + sensor_quats[i,:3] = q_sensor[1:] + sensor_quats[i,3] = q_sensor[0] + + body2j2000 = spice.pxform( + self.target_frame_id, + j2000_id, + time) + q_body = spice.m2q(body2j2000) + body_quats[i,:3] = q_body[1:] + body_quats[i,3] = q_body[0] + + sensor2j2000_rot = TimeDependentRotation( + sensor_quats, + sensor_times, + self.sensor_frame_id, + j2000_id + ) + sensor_node = FrameNode( + self.sensor_frame_id, + parent=self._root_frame, + rotation=sensor2j2000_rot) + + body2j2000_rot = TimeDependentRotation( + body_quats, + body_times, + self.target_frame_id, + j2000_id + ) + body_node = FrameNode( + self.target_frame_id, + parent=self._root_frame, + rotation=body2j2000_rot) + return self._root_frame @property - def _sensor_orientation(self): + def sensor_orientation(self): if not hasattr(self, '_orientation'): ephem = self.ephemeris_time diff --git a/tests/pytests/conftest.py b/tests/pytests/conftest.py index 20f9d811c5780ae52bccc3a2bb3531b5846a2584..f79988330ba1131e49405e95e81d9cca7a767c05 100644 --- a/tests/pytests/conftest.py +++ b/tests/pytests/conftest.py @@ -20,13 +20,15 @@ class SimpleSpice(): def unload(self, *args): return def pxform(self, *args): - return + return np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) def m2q(self, *args): return np.asarray([1,2,3,4]) def bodn2c(self, *args): return "SPACE" def sct2e(self, *args): return 0.1 + def cidfrm(self, *args): + return (2000, "Test_Body_Frame", True) def get_mockkernels(self, *args): return "some_metakernel" diff --git a/tests/pytests/test_data_naif.py b/tests/pytests/test_data_naif.py index 43eecc983aaf1496f73beb7f03fc6eb7e97f33d8..7dda5b7547eb36c4a2a96407c230e678a7fd43af 100644 --- a/tests/pytests/test_data_naif.py +++ b/tests/pytests/test_data_naif.py @@ -15,9 +15,20 @@ def test_naif_data(): naif_data = data_naif.NaifSpice() naif_data.instrument_id = "INSTRUMENT" naif_data.target_name = "TARGET" + naif_data.ephemeris_time = [0, 1] return naif_data +def test_frame_chain(test_naif_data): + j2000 = test_naif_data.frame_chain + assert j2000.parent == None + assert j2000.id == 1 + assert len(j2000.children) == 2 + for child in j2000.children: + assert child.parent == j2000 + np.testing.assert_equal(child.rotation.times, np.array([0, 1])) + assert child.rotation.quats.shape == (2, 4) + def test_target_id(test_naif_data): assert test_naif_data.target_id == -12345 @@ -29,10 +40,13 @@ def test_radii(test_naif_data): def test_naif_keywords(test_naif_data): np.testing.assert_equal(test_naif_data.isis_naif_keywords['BODY-12345_RADII'], np.ones(3)) - np.testing.assert_equal(test_naif_data.isis_naif_keywords['BODY_FRAME_CODE'], np.arange(1)) + np.testing.assert_equal(test_naif_data.isis_naif_keywords['BODY_FRAME_CODE'], 2000) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_PIXEL_SIZE'], (0.001)) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_ITRANSL'], np.ones(3)) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_ITRANSS'], np.ones(3)) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_FOCAL_LENGTH'], np.ones(1)) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_BORESIGHT_LINE'], np.ones(1)) np.testing.assert_equal(test_naif_data.isis_naif_keywords['INS-12345_BORESIGHT_SAMPLE'], np.ones(1)) + +def test_target_frame_id(test_naif_data): + assert test_naif_data.target_frame_id == 2000