Select Git revision
-
Kelvin Rodriguez authoredKelvin Rodriguez authored
base.py 12.16 KiB
from dateutil import parser
import numpy as np
import pvl
import spiceypy as spice
class Driver():
"""
Base class for all Drivers.
"""
def __init__(self, file):
self._file = file
def __str__(self):
return str(self.to_dict())
def is_valid(self):
try:
iid = self.instrument_id
return True
except Exception as e:
return False
def to_dict(self):
keys = set(dir(self)) & self.required_keys
return {p:getattr(self, p) for p in keys}
class LineScanner(Driver):
@property
def name_model(self):
return "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL"
@property
def t0_ephemeris(self):
return self.starting_ephemeris_time - self.center_ephemeris_time
@property
def t0_quaternion(self):
return self.starting_ephemeris_time - self.center_ephemeris_time
@property
def dt_ephemeris(self):
return (self.ending_ephemeris_time - self.starting_ephemeris_time) / self.number_of_ephemerides
@property
def dt_quaternion(self):
return (self.ending_ephemeris_time - self.starting_ephemeris_time) / self.number_of_ephemerides
@property
def line_scan_rate(self):
"""
In the form: [start_line, line_time, exposure_duration]
The form below is for a fixed rate line scanner.
"""
return [[float(self.starting_detector_line), self.t0_ephemeris, self.line_exposure_duration]]
@property
def number_of_ephemerides(self):
#TODO: Not make this hardcoded
return 909
@property
def number_of_quaternions(self):
#TODO: Not make this hardcoded
return 909
class Framer(Driver):
@property
def name_model(self):
return "USGS_ASTRO_FRAME_SENSOR_MODEL"
@property
def filter_number(self):
return self.label.get('FILTER_NUMBER', 0)
@property
def number_of_ephemerides(self):
# always one for framers
return 1
@property
def number_of_quaternions(self):
# always one for framers
return 1
class PDS3():
def _compute_ephemerides(self):
"""
Helper function to pull position and velocity in one pass
so that the results can then be cached in the associated
properties.
"""
eph = np.empty((self.number_of_ephemerides, 3))
eph_rates = np.empty(eph.shape)
current_et = self.starting_ephemeris_time
for i in range(self.number_of_ephemerides):
state, _ = spice.spkezr(self.spacecraft_name,
current_et,
self.reference_frame,
'NONE',
self.target_name,) # If this is the sensor, insufficient, if this is the spacecraft, it works? Huh?
eph[i] = state[:3]
eph_rates[i] = state[3:]
current_et += getattr(self, 'dt_ephemeris', 0)
# By default, spice works in km
eph *= 1000
eph_rates *= 1000
self._sensor_velocity = eph_rates
self._sensor_position = eph
@property
def focal_plane_tempature(self):
return self.label['FOCAL_PLANE_TEMPERATURE'].value
@property
def label(self):
if not hasattr(self, "_label"):
if isinstance(self._file, pvl.PVLModule):
self._label = label
try:
self._label = pvl.loads(self._file, strict=False)
except AttributeError:
self._label = pvl.load(self._file, strict=False)
except:
raise Exception("{} is not a valid label".format(label))
return self._label
@property
def line_exposure_duration(self):
try:
return self.label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds
except:
return np.nan
@property
def instrument_id(self):
pass
@property
def start_time(self):
return self.label['START_TIME']
@property
def image_lines(self):
return self.label['IMAGE']['LINES']
@property
def image_samples(self):
return self.label['IMAGE']['LINE_SAMPLES']
@property
def interpolation_method(self):
return 'lagrange'
@property
def target_name(self):
return self.label['TARGET_NAME']
@property
def _target_id(self):
return spice.bodn2c(self.label['TARGET_NAME'])
@property
def focal_epsilon(self):
return float(spice.gdpool('INS{}_FL_UNCERTAINTY'.format(self.ikid), 0, 1)[0])
@property
def starting_ephemeris_time(self):
if not hasattr(self, '_starting_ephemeris_time'):
sclock = self.label['SPACECRAFT_CLOCK_START_COUNT']
self._starting_ephemeris_time = spice.scs2e(self.spacecraft_id, sclock)
return self._starting_ephemeris_time
@property
def exposure_duration(self):
try:
return self.label['EXPOSURE_DURATION'].value * 0.001 # Scale to seconds
except:
return np.nan
@property
def spacecraft_clock_stop_count(self):
sc = self.label.get('SPACECRAFT_CLOCK_STOP_COUNT', None)
if sc == 'N/A':
sc = None
return sc
@property
def ending_ephemeris_time(self):
return (self.image_lines * self.line_exposure_duration) + self.starting_ephemeris_time
@property
def center_ephemeris_time(self):
return (self.starting_ephemeris_time + self.ending_ephemeris_time)/2
@property
def detector_center(self):
return list(spice.gdpool('INS{}_CCD_CENTER'.format(self.ikid), 0, 2))
@property
def spacecraft_name(self):
return self.label['MISSION_NAME']
@property
def starting_detector_line(self):
return 1
@property
def starting_detector_sample(self):
return 1
@property
def detector_sample_summing(self):
return 1
@property
def detector_line_summing(self):
return self.label.get('SAMPLING_FACTOR', 1)
class Spice():
@property
def metakernel(self):
pass
def __enter__(self):
"""
Called when the context is created. This is used
to get the kernels furnished.
"""
if self.metakernel:
spice.furnsh(self.metakernel)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Called when the context goes out of scope. Once
this is done, the object is out of scope and the
kernels can be unloaded.
"""
spice.unload(self.metakernel)
@property
def odtx(self):
return spice.gdpool('INS{}_OD_T_X'.format(self.ikid),0, 10)
@property
def odty(self):
return spice.gdpool('INS{}_OD_T_Y'.format(self.ikid), 0, 10)
@property
def odtk(self):
return spice.gdpool('INS{}_OD_K'.format(self.ikid),0, 3)
@property
def ikid(self):
return spice.bods2c(self.instrument_id)
@property
def spacecraft_id(self):
return spice.bods2c(self.spacecraft_name)
@property
def focal2pixel_lines(self):
return spice.gdpool('INS{}_ITRANSL'.format(self.fikid), 0, 3)
@property
def focal2pixel_samples(self):
return spice.gdpool('INS{}_ITRANSS'.format(self.fikid), 0, 3)
@property
def focal_length(self):
return float(spice.gdpool('INS{}_FOCAL_LENGTH'.format(self.ikid), 0, 1)[0])
@property
def semimajor(self):
rad = spice.bodvrd(self.target_name, 'RADII', 3)
return rad[1][1]
@property
def semiminor(self):
rad = spice.bodvrd(self.target_name, 'RADII', 3)
return rad[1][0]
@property
def reference_frame(self):
return 'IAU_{}'.format(self.target_name)
@property
def sun_position(self):
sun_state, _ = spice.spkezr("SUN",
self.center_ephemeris_time,
self.reference_frame,
'NONE',
self.target_name)
return [sun_state[:4].tolist()]
@property
def sun_velocity(self):
sun_state, lt = spice.spkezr("SUN",
self.center_ephemeris_time,
self.reference_frame,
'NONE',
self.target_name)
return [sun_state[3:6].tolist()]
@property
def sensor_position(self):
if not hasattr(self, '_sensor_position'):
self._compute_ephemerides()
return self._sensor_position.tolist()
@property
def sensor_velocity(self):
if not hasattr(self, '_sensor_velocity'):
self._compute_ephemerides()
return self._sensor_velocity.tolist()
@property
def sensor_orientation(self):
if not hasattr(self, '_sensor_orientation'):
current_et = self.starting_ephemeris_time
qua = np.empty((self.number_of_ephemerides, 4))
for i in range(self.number_of_quaternions):
# Find the rotation matrix
camera2bodyfixed = spice.pxform(self.instrument_id,
self.reference_frame,
current_et)
q = spice.m2q(camera2bodyfixed)
qua[i,:3] = q[1:]
qua[i,3] = q[0]
current_et += getattr(self, 'dt_quaternion', 0)
self._sensor_orientation = qua
return self._sensor_orientation.tolist()
@property
def reference_height(self):
# TODO: This should be a reasonable #
return 0, 100
@property
def detector_center(self):
if not hasattr(self, '_detector_center'):
center_line = float(spice.gdpool('INS{}_BORESIGHT_LINE'.format(self.ikid), 0, 1)[0])
center_sample = float(spice.gdpool('INS{}_BORESIGHT_SAMPLE'.format(self.ikid), 0, 1)[0])
self._detector_center = [center_line, center_sample]
return self._detector_center
@property
def center_ephemeris_time(self):
"""
The center ephemeris time for a fixed rate line scanner.
"""
if not hasattr(self, '_center_ephemeris_time'):
halflines = self.image_lines / 2
center_sclock = self.starting_ephemeris_time + halflines * self.line_exposure_duration
self._center_ephemeris_time = center_sclock
return self._center_ephemeris_time
@property
def fikid(self):
if isinstance(self, Framer):
fn = self.filter_number
if fn == 'N/A':
fn = 0
else:
fn = 0
return self.ikid - int(fn)
class Isis3():
@property
def label(self):
if not hasattr(self, "_label"):
if isinstance(self._file, pvl.PVLModule):
self._label = label
try:
self._label = pvl.loads(self._file, strict=False)
except AttributeError:
self._label = pvl.load(self._file, strict=False)
except:
raise Exception("{} is not a valid label".format(label))
return self._label
@property
def start_time(self):
return self.label['IsisCube']['Instrument']['StartTime']
@property
def spacecraft_name(self):
return self.label['IsisCube']['Instrument']['SpacecraftName']
@property
def image_lines(self):
return self.label['IsisCube']['Core']['Dimensions']['Lines']
@property
def image_samples(self):
return self.label['IsisCube']['Core']['Dimensions']['Samples']
@property
def _exposure_duration(self):
return self.label['IsisCube']['Instrument']['ExposureDuration'].value * 0.001 # Scale to seconds
@property
def target_name(self):
return self.label['IsisCube']['Instrument']['TargetName']
@property
def starting_ephemeris_time(self):
if not hasattr(self, '_starting_ephemeris_time'):
sclock = self.label['IsisCube']['Archive']['SpacecraftClockStartCount']
self._starting_ephemeris_time = spice.scs2e(self.spacecraft_id, sclock)
return self._starting_ephemeris_time