Skip to content
Snippets Groups Projects
Unverified Commit 193f92fa authored by acpaquette's avatar acpaquette Committed by GitHub
Browse files

MSL Fixes/Landed Addition (#558)


* Some potential fixes for MSL

* Adjustment to signs in rotation matrix for MSM Cahvor

* Another incremental fix, with a temporary rover transform to help things along

* Added template property function for the final_inst_frame

* Updated frame chain with missing MSL frames

* Made the logic for adding the cahvor frame less round about

* Added MSL final_inst_frame property

* Tweak the frame id

Tweak the frame id

* Allowed cahvor drivers to produce local ISDs

* Fixed tests

* Removed unnecessary properties

* Added final_inst_frame test

---------

Co-authored-by: default avatarOleg Alexandrov <oleg.alexandrov@gmail.com>
parent 3ea165b1
No related branches found
No related tags found
No related merge requests found
......@@ -386,6 +386,22 @@ class Cahvor():
"""
raise NotImplementedError
@property
def final_inst_frame(self):
"""
Defines the final frame before cahvor frame in the frame chain
"""
raise NotImplementedError
@property
def sensor_position(self):
positions, velocities, times = super().sensor_position
positions += self.cahvor_camera_dict["C"]
if self._props.get("landed", False):
positions = np.array([[0, 0, 0]] * len(times))
velocities = np.array([[0, 0, 0]] * len(times))
return positions, velocities, times
def compute_h_c(self):
"""
Computes the h_c element of a cahvor model for the conversion
......@@ -451,7 +467,10 @@ class Cahvor():
v_s = self.compute_v_s()
H_prime = (self.cahvor_camera_dict['H'] - h_c * self.cahvor_camera_dict['A'])/h_s
V_prime = (self.cahvor_camera_dict['V'] - v_c * self.cahvor_camera_dict['A'])/v_s
if self._props.get("landed", False):
self._cahvor_rotation_matrix = np.array([H_prime, -V_prime, -self.cahvor_camera_dict['A']])
else:
self._cahvor_rotation_matrix = np.array([H_prime, V_prime, self.cahvor_camera_dict['A']])
return self._cahvor_rotation_matrix
@property
......@@ -466,13 +485,17 @@ class Cahvor():
A networkx frame chain object
"""
if not hasattr(self, '_frame_chain'):
self._frame_chain = FrameChain.from_spice(sensor_frame=self.spacecraft_id * 1000,
self._frame_chain = FrameChain.from_spice(sensor_frame=self.final_inst_frame,
target_frame=self.target_frame_id,
center_ephemeris_time=self.center_ephemeris_time,
ephemeris_times=self.ephemeris_time,
nadir=False, exact_ck_times=False)
cahvor_quats = Rotation.from_matrix(self.cahvor_rotation_matrix).as_quat()
cahvor_rotation = ConstantRotation(cahvor_quats, self.spacecraft_id * 1000, self.sensor_frame_id)
# If we are landed we only care about the final cahvor frame relative to the target
if self._props.get("landed", False):
cahvor_rotation = ConstantRotation(cahvor_quats, self.target_frame_id, self.sensor_frame_id)
else:
cahvor_rotation = ConstantRotation(cahvor_quats, self.final_inst_frame, self.sensor_frame_id)
self._frame_chain.add_edge(rotation = cahvor_rotation)
return self._frame_chain
......@@ -487,7 +510,9 @@ class Cahvor():
: float
The detector center line/boresight center line
"""
return self.compute_v_c()
# Add here 0.5 for consistency with the CSM convention that the
# upper-left image pixel is at (0.5, 0.5).
return self.compute_v_c() + 0.5
@property
def detector_center_sample(self):
......@@ -500,7 +525,9 @@ class Cahvor():
: float
The detector center sample/boresight center sample
"""
return self.compute_h_c()
# Add here 0.5 for consistency with the CSM convention that the
# upper-left image pixel is at (0.5, 0.5).
return self.compute_h_c() + 0.5
@property
def pixel_size(self):
......
......@@ -69,6 +69,18 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
self._cahvor_camera_params['R'] = np.array(camera_model_group["MODEL_COMPONENT_6"])
return self._cahvor_camera_params
@property
def final_inst_frame(self):
"""
Defines MSLs last naif frame before the cahvor model frame
Returns
-------
: int
Naif frame code for MSL_RSM_HEAD
"""
return spice.bods2c("MSL_RSM_HEAD")
@property
def sensor_frame_id(self):
"""
......@@ -96,7 +108,7 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
: list<double>
focal plane to detector lines
"""
return [0, 1/self.pixel_size, 0]
return [0, 0, 1/self.pixel_size]
@property
def focal2pixel_samples(self):
......@@ -108,23 +120,7 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
: list<double>
focal plane to detector samples
"""
return [0, 0, 1/self.pixel_size]
@property
def detector_center_line(self):
return self.label["INSTRUMENT_STATE_PARMS"]["DETECTOR_LINES"]/2
@property
def detector_center_sample(self):
return self.label["INSTRUMENT_STATE_PARMS"]["MSL:DETECTOR_SAMPLES"]/2
@property
def detector_start_line(self):
return self.label["IMAGE_REQUEST_PARMS"]["FIRST_LINE"]
@property
def detector_start_sample(self):
return self.label["IMAGE_REQUEST_PARMS"]["FIRST_LINE_SAMPLE"]
return [0, -1/self.pixel_size, 0]
@property
def sensor_model_version(self):
......
......@@ -118,7 +118,7 @@ def to_isd(driver):
sensor_frame = driver.sensor_frame_id
instrument_pointing = {}
source_frame, destination_frame, time_dependent_sensor_frame = frame_chain.last_time_dependent_frame_between(1, sensor_frame)
source_frame, destination_frame, _ = frame_chain.last_time_dependent_frame_between(1, sensor_frame)
# Reverse the frame order because ISIS orders frames as
# (destination, intermediate, ..., intermediate, source)
......
......@@ -67,6 +67,12 @@ def main():
action="store_true",
help="Only use drivers that generate fresh spice data"
)
parser.add_argument(
"-l", "--local",
action="store_true",
help="Generate local spice data, or image that is unaware of itself relative to "
"target body. This is largely used for landed/rover data."
)
parser.add_argument(
'--version',
action='version',
......@@ -97,7 +103,7 @@ def main():
if len(args.input) == 1:
try:
file_to_isd(args.input[0], args.out, kernels=k, log_level=log_level, only_isis_spice=args.only_isis_spice, only_naif_spice=args.only_naif_spice)
file_to_isd(args.input[0], args.out, kernels=k, log_level=log_level, only_isis_spice=args.only_isis_spice, only_naif_spice=args.only_naif_spice, local=args.local)
except Exception as err:
# Seriously, this just throws a generic Exception?
sys.exit(f"File {args.input[0]}: {err}")
......@@ -107,7 +113,11 @@ def main():
) as executor:
futures = {
executor.submit(
file_to_isd, f, **{"kernels": k, "log_level": log_level, "only_isis_spice": args.only_isis_spice, "only_naif_spice": args.only_naif_spice}
file_to_isd, f, **{"kernels": k,
"log_level": log_level,
"only_isis_spice": args.only_isis_spice,
"only_naif_spice": args.only_naif_spice,
"local": args.local}
): f for f in args.input
}
for f in concurrent.futures.as_completed(futures):
......@@ -127,7 +137,8 @@ def file_to_isd(
kernels: list = None,
log_level=logging.WARNING,
only_isis_spice=False,
only_naif_spice=False
only_naif_spice=False,
local=False
):
"""
Returns nothing, but acts as a thin wrapper to take the *file* and generate
......@@ -152,6 +163,10 @@ def file_to_isd(
logger.info(f"Reading: {file}")
props = {}
if local:
props['landed'] = local
if kernels is not None:
kernels = [str(PurePath(p)) for p in kernels]
props["kernels"] = kernels
......
......@@ -27,6 +27,7 @@ class test_cahvor_sensor(unittest.TestCase):
self.driver.target_frame_id = 10014
self.driver.center_ephemeris_time = 0
self.driver.ephemeris_time = [0]
self.driver._props = {}
@patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
def test_compute_functions(self, cahvor_camera_dict):
......@@ -39,26 +40,28 @@ class test_cahvor_sensor(unittest.TestCase):
def test_cahvor_model_elements(self, cahvor_camera_dict):
cahvor_matrix = self.driver.cahvor_rotation_matrix
np.testing.assert_allclose(cahvor_matrix, [[-0.82067034, -0.57129702, 0.01095033],
[-0.43920248, 0.6184257, -0.65165238],
[ 0.3655151, -0.5396012, -0.7584387 ]])
[0.43920248, -0.6184257, 0.65165238],
[ -0.3655151, 0.5396012, 0.7584387 ]])
@patch('ale.transformation.FrameChain.from_spice', return_value=ale.transformation.FrameChain())
@patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
def test_cahvor_frame_chain(self, cahvor_camera_dict, from_spice):
@patch("ale.base.type_sensor.Cahvor.final_inst_frame", new_callable=PropertyMock, return_value=-76000)
def test_cahvor_frame_chain(self, final_inst_frame, cahvor_camera_dict, from_spice):
frame_chain = self.driver.frame_chain
assert len(frame_chain.nodes()) == 2
assert -76000 in frame_chain.nodes()
assert -76562 in frame_chain.nodes()
from_spice.assert_called_with(center_ephemeris_time=0, ephemeris_times=[0], sensor_frame=-76000, target_frame=10014, nadir=False, exact_ck_times=False)
np.testing.assert_allclose(frame_chain[-76562][-76000]['rotation'].quat, [-0.28255205, 0.8940826, -0.33309383, 0.09914206])
print(frame_chain[-76562][-76000]['rotation'].quat[3])
np.testing.assert_allclose(frame_chain[-76562][-76000]['rotation'].quat, [-0.09914206260782343, 0.3330938313054434, 0.8940825953723198, -0.28255205470925904])
@patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
def test_cahvor_detector_center_line(self, cahvor_camera_dict):
np.testing.assert_almost_equal(self.driver.detector_center_line, 590.1933422831007)
np.testing.assert_almost_equal(self.driver.detector_center_line, 590.6933422831007)
@patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
def test_cahvor_detector_center_sample(self, cahvor_camera_dict):
np.testing.assert_almost_equal(self.driver.detector_center_sample, 673.4306859859296)
np.testing.assert_almost_equal(self.driver.detector_center_sample, 673.9306859859296)
@patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
def test_cahvor_pixel_size(self, cahvor_camera_dict):
......
......@@ -20,6 +20,11 @@ class test_mastcam_pds_naif(unittest.TestCase):
def test_exposure_duration(self):
np.testing.assert_almost_equal(self.driver.exposure_duration, 0.0102)
def test_final_inst_frame(self):
with patch('ale.drivers.msl_drivers.spice.bods2c', new_callable=PropertyMock, return_value=-76562) as bods2c:
assert self.driver.final_inst_frame == -76562
bods2c.assert_called_with("MSL_RSM_HEAD")
def test_cahvor_camera_dict(self):
cahvor_camera_dict = self.driver.cahvor_camera_dict
assert len(cahvor_camera_dict) == 4
......@@ -36,13 +41,13 @@ class test_mastcam_pds_naif(unittest.TestCase):
def test_focal2pixel_lines(self):
with patch('ale.drivers.msl_drivers.spice.bods2c', new_callable=PropertyMock, return_value=-76220) as bods2c, \
patch('ale.drivers.msl_drivers.spice.gdpool', new_callable=PropertyMock, return_value=[100]) as gdpool:
np.testing.assert_allclose(self.driver.focal2pixel_lines, [0, 137.96844341513602, 0])
np.testing.assert_allclose(self.driver.focal2pixel_lines, [0, 0, 137.96844341513602])
bods2c.assert_called_with('MSL_MASTCAM_RIGHT')
gdpool.assert_called_with('INS-76220_FOCAL_LENGTH', 0, 1)
def test_focal2pixel_samples(self):
with patch('ale.drivers.msl_drivers.spice.bods2c', new_callable=PropertyMock, return_value=-76220) as bods2c, \
patch('ale.drivers.msl_drivers.spice.gdpool', new_callable=PropertyMock, return_value=[100]) as gdpool:
np.testing.assert_allclose(self.driver.focal2pixel_samples, [0, 0, 137.96844341513602])
np.testing.assert_allclose(self.driver.focal2pixel_samples, [0, -137.96844341513602, 0])
bods2c.assert_called_with('MSL_MASTCAM_RIGHT')
gdpool.assert_called_with('INS-76220_FOCAL_LENGTH', 0, 1)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment