-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial rosetta virtis driver (#520)
* Initial rosetta virtis driver * Updated rosetta virtis driver * virtis test data * Added logic to allow quaternion inverse to pass * Updated rosetta virtis driver * rosetta virtis tests * Added missing docstrings * Updated changelog
- Loading branch information
1 parent
1e3fdea
commit 4ea98c6
Showing
20 changed files
with
771,134 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
import re | ||
import spiceypy as spice | ||
import math | ||
import numpy as np | ||
from scipy.spatial.transform import Rotation | ||
from scipy.interpolate import CubicSpline | ||
|
||
|
||
from ale.base import Driver | ||
from ale.base.data_naif import NaifSpice | ||
from ale.base.data_isis import read_table_data, parse_table | ||
from ale.base.label_isis import IsisLabel | ||
from ale.base.type_distortion import RadialDistortion | ||
from ale.base.type_sensor import LineScanner | ||
from ale.transformation import ConstantRotation, FrameChain, TimeDependentRotation | ||
|
||
|
||
class RosettaVirtisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, RadialDistortion, Driver): | ||
@property | ||
def instrument_id(self): | ||
""" | ||
Returns the instrument id for Rosetta Virtis | ||
Returns | ||
------- | ||
: str | ||
Frame Reference for Rosetta VIRTIS | ||
""" | ||
inst_id_lookup = { | ||
"VIRTIS_M_VIS" : "ROS_VIRTIS-M_VIS", | ||
"VIRTIS_M_IR" : "ROS_VIRTIS-M_IR", | ||
} | ||
return inst_id_lookup[self.label['IsisCube']['Instrument']['ChannelID']] | ||
|
||
|
||
@property | ||
def sensor_model_version(self): | ||
""" | ||
The ISIS Sensor model number for Rosetta Virtis in ISIS. | ||
Returns | ||
------- | ||
: int | ||
ISIS sensor model version | ||
""" | ||
return 1 | ||
|
||
|
||
@property | ||
def spacecraft_name(self): | ||
""" | ||
Return the spacecraft name. The label calls it "ROSETTA-ORBITER", NAIF calls it "ROSETTA" | ||
""" | ||
return "ROSETTA" | ||
|
||
@property | ||
def ephemeris_start_time(self): | ||
""" | ||
Returns the start ephemeris time for the image. | ||
Returns | ||
------- | ||
: float | ||
start time | ||
""" | ||
try: | ||
# first line's middle et - 1/2 exposure duration = cube start time | ||
return self.hk_ephemeris_time[0] - (self.line_exposure_duration/2) | ||
except: | ||
return spice.scs2e(self.spacecraft_id, self.label['IsisCube']['Instrument']['SpacecraftClockStartCount']) | ||
|
||
|
||
@property | ||
def ephemeris_stop_time(self): | ||
""" | ||
Returns the stop ephemeris time for the image. | ||
Returns | ||
------- | ||
: float | ||
stop time | ||
""" | ||
|
||
try: | ||
# last line's middle et + 1/2 exposure duration = cube start time | ||
return self.hk_ephemeris_time[-1] + (self.line_exposure_duration/2) | ||
except: | ||
return spice.scs2e(self.spacecraft_id, self.label['IsisCube']['Instrument']['SpacecraftClockStopCount']) | ||
|
||
@property | ||
def housekeeping(self): | ||
""" | ||
Read the housekeeping table from the cub and populate data. | ||
Returns | ||
------- | ||
None | ||
""" | ||
if not hasattr(self, "_housekeeping"): | ||
degs_per_rad = 57.2957795 | ||
binary = read_table_data(self.label['Table'], self._file) | ||
hk_table = parse_table(self.label['Table'], binary) | ||
data_scet = hk_table['dataSCET'] | ||
shutter_mode_list = hk_table['Data Type__Shutter state'] | ||
mirror_sin_list = hk_table['M_MIRROR_SIN_HK'] | ||
mirror_cos_list = hk_table['M_MIRROR_COS_HK'] | ||
|
||
opt_angles = [] | ||
x = np.array([]) | ||
y = np.array([]) | ||
for index, mirror_sin in enumerate(mirror_sin_list): | ||
shutter_mode = shutter_mode_list[index] | ||
is_dark = (shutter_mode == 1) | ||
|
||
mirror_cos = mirror_cos_list[index] | ||
|
||
scan_elec_deg = math.atan(mirror_sin/mirror_cos) * degs_per_rad | ||
opt_ang = ((scan_elec_deg - 3.7996979) * 0.25/0.257812) / 1000 | ||
|
||
if not is_dark: | ||
x = np.append(x, index + 1) | ||
y = np.append(y, opt_ang) | ||
|
||
if not self.is_calibrated: | ||
opt_angles.append(opt_ang) | ||
|
||
cs = CubicSpline(x, y, extrapolate="periodic") | ||
|
||
for i, opt_ang in enumerate(opt_angles): | ||
shutter_mode = shutter_mode_list[i] | ||
is_dark = (shutter_mode == 1) | ||
|
||
if (is_dark): | ||
if (i == 0): | ||
opt_angles[i] = opt_angles[i+1] | ||
elif (i == len(opt_angles) - 1): | ||
opt_angles[i] = opt_angles[i-1] | ||
else: | ||
opt_angles[i] = cs(i+1) | ||
|
||
line_mid_times = [spice.scs2e(self.spacecraft_id, str(round(i,5))) for i in data_scet] | ||
self._hk_ephemeris_time = line_mid_times | ||
self._optical_angle = opt_angles | ||
self._housekeeping= True | ||
|
||
@property | ||
def hk_ephemeris_time(self): | ||
""" | ||
Ephemeris times from the housekeeping table. | ||
Returns | ||
------- | ||
: list | ||
Ephemeris times from the housekeeping table | ||
""" | ||
if not hasattr(self, "_hk_ephemeris_time"): | ||
self.housekeeping | ||
return self._hk_ephemeris_time | ||
|
||
@property | ||
def optical_angle(self): | ||
""" | ||
Return optical angles from the housekeeping table. | ||
Returns | ||
------- | ||
: list | ||
Optical angles from the housekeeping table | ||
""" | ||
if not hasattr(self, "_optical_angle"): | ||
self.housekeeping | ||
return self._optical_angle | ||
|
||
|
||
@property | ||
def line_exposure_duration(self): | ||
""" | ||
Returns the exposure duration for each line. | ||
Returns | ||
------- | ||
: double | ||
Exposure duration for a line | ||
""" | ||
return self.label['IsisCube']['Instrument']['FrameParameter'][0] * 0.001 | ||
|
||
|
||
@property | ||
def line_summing(self): | ||
""" | ||
Returns | ||
------- | ||
: int | ||
Line summing | ||
""" | ||
return self.label['IsisCube']['Instrument']['FrameParameter'][1] | ||
|
||
@property | ||
def sample_summing(self): | ||
""" | ||
Returns | ||
------- | ||
: int | ||
Sample summing | ||
""" | ||
return self.label['IsisCube']['Instrument']['FrameParameter'][1] | ||
|
||
@property | ||
def scan_rate(self): | ||
""" | ||
Returns | ||
: Double | ||
Scan rate | ||
""" | ||
return self.label['IsisCube']['Instrument']['FrameParameter'][2] | ||
|
||
@property | ||
def odtk(self): | ||
""" | ||
The coefficients for the distortion model | ||
Defined in the IAK | ||
Returns | ||
------- | ||
: list | ||
Radial distortion coefficients | ||
""" | ||
return [0.0, .038, 0.38] | ||
|
||
|
||
@property | ||
def sensor_frame_id(self): | ||
""" | ||
Returns the sensor frame id. Depends on the instrument that was used to | ||
capture the image. | ||
Returns | ||
------- | ||
: int | ||
Sensor frame id | ||
""" | ||
|
||
if self.has_articulation_kernel: | ||
return super().sensor_frame_id | ||
else: | ||
return super().sensor_frame_id - 1 | ||
|
||
|
||
@property | ||
def is_calibrated(self): | ||
""" | ||
Determine if this image is calibrated. | ||
Returns | ||
------- | ||
: bool | ||
True if calibrated else False | ||
""" | ||
return self.label['IsisCube']['Instrument']['ProcessingLevelID'] == '3' | ||
|
||
@property | ||
def has_articulation_kernel(self): | ||
""" | ||
Determine if this image has an associated articulation kernel | ||
Returns | ||
------- | ||
: bool | ||
True if the image has an articulation kernel else False | ||
""" | ||
regex = re.compile('.*ROS_VIRTIS_M_[0-9]{4}_[0-9]{4}_V[0-9].BC') | ||
return any([re.match(regex, i) for i in self.kernels]) | ||
|
||
|
||
@property | ||
def frame_chain(self): | ||
""" | ||
Construct the frame chain. Use super to construct base chain and add edges via inst_pointing_rotation | ||
Returns | ||
------- | ||
: FrameChain | ||
""" | ||
frame_chain = super().frame_chain | ||
frame_chain.add_edge(rotation=self.inst_pointing_rotation) | ||
return frame_chain | ||
|
||
@property | ||
def inst_pointing_rotation(self): | ||
""" | ||
Returns a time dependent instrument pointing rotation for virtis frames | ||
Returns | ||
------- | ||
: TimeDependentRotation | ||
Instrument pointing rotation | ||
""" | ||
time_dep_quats = np.zeros((len(self.hk_ephemeris_time), 4)) | ||
avs = [] | ||
|
||
for i, time in enumerate(self.hk_ephemeris_time): | ||
try: | ||
state_matrix = spice.sxform("J2000", spice.frmnam(self.sensor_frame_id), time) | ||
except: | ||
rotation_matrix = spice.pxform("J2000", spice.frmnam(self.sensor_frame_id), time) | ||
state_matrix = spice.rav2xf(rotation_matrix, [0, 0, 0]) | ||
|
||
opt_angle = self.optical_angle[i] | ||
|
||
xform = spice.eul2xf([0, -opt_angle, 0, 0, 0, 0], 1, 2, 3) | ||
xform2 = spice.mxmg(xform, state_matrix) | ||
|
||
rot_mat, av = spice.xf2rav(xform2) | ||
avs.append(av) | ||
|
||
quat_from_rotation = spice.m2q(rot_mat) | ||
time_dep_quats[i,:3] = -quat_from_rotation[1:] | ||
time_dep_quats[i, 3] = -quat_from_rotation[0] | ||
|
||
time_dep_rot = TimeDependentRotation(time_dep_quats, self.hk_ephemeris_time, 1, self.sensor_frame_id, av=avs) | ||
|
||
return time_dep_rot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.