diff --git a/ale/base/__init__.py b/ale/base/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0312f47f49120369ec8b514ab92ab4305bb4f87a --- /dev/null +++ b/ale/base/__init__.py @@ -0,0 +1 @@ +from ale.base.base import Driver diff --git a/ale/base/base.py b/ale/base/base.py new file mode 100644 index 0000000000000000000000000000000000000000..80185a7e23208b0e8ae68628dc215d11e2f2efd0 --- /dev/null +++ b/ale/base/base.py @@ -0,0 +1,162 @@ +import pvl + +class Driver(): + """ + Base class for all Drivers. + + Attributes + ---------- + _file : str + Reference to file path to be used by mixins for opening. + """ + + def __init__(self, file, num_ephem=909, num_quats=909): + """ + Parameters + ---------- + file : str + path to file to be parsed + """ + self._num_quaternions = num_quats + self._num_ephem = num_ephem + self._file = file + + def __str__(self): + """ + Returns a string representation of the class + + Returns + ------- + str + String representation of all attributes and methods of the class + """ + return str(self.to_dict()) + + def is_valid(self): + """ + Checks if the driver has an intrument id associated with it + + Returns + ------- + bool + True if an instrument_id is defined, False otherwise + """ + try: + iid = self.instrument_id + return True + except Exception as e: + print(e) + return False + + def to_dict(self): + """ + Generates a dictionary of keys based on the attributes and methods assocated with + the driver and the required keys for the driver + + Returns + ------- + dict + Dictionary of key, attribute pairs + """ + keys = set() + return {p:getattr(self, p) for p in dir(self) if p[0] != "_" and isinstance(getattr(type(self), p), property)} + + + @property + def label(self): + if not hasattr(self, "_label"): + if isinstance(self._file, pvl.PVLModule): + self._label = self._file + try: + self._label = pvl.loads(self._file) + except Exception: + self._label = pvl.load(self._file) + except: + raise ValueError("{} is not a valid label".format(self._file)) + return self._label + + @property + def file(self): + return self._file + + @property + def interpolation_method(self): + return "lagrange" + + @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 1 + + @property + def name_platform(self): + return "Generic Platform" + + @property + def name_sensor(self): + return "Generic Sensor" + + @property + def radii(self): + return { + "semimajor" : self._semimajor, + "semiminor" : self._semiminor, + "unit" : "km" # default to KM + } + + @property + def reference_height(self): + # TODO: This should be a reasonable # + return { + "minheight" : 0, + "maxheight": 1000, + "unit": "m" + } + + @property + def focal_length_model(self): + return { + "focal_length" : self._focal_length + } + + @property + def detector_center(self): + if not hasattr(self, '_detector_center'): + self._detector_center = { + "line" : self._detector_center_line, + "sample" : self._detector_center_sample + } + return self._detector_center + + @property + def sensor_position(self): + return { + "positions" : self._sensor_position, + "velocities" : self._sensor_velocity, + "unit" : "m" + } + + @property + def sensor_orientation(self): + return { + "quaternions" : self._sensor_orientation + } + + @property + def sun_position(self): + return { + "positions" : self._sun_position, + "velocities" : self._sun_velocity, + "unit" : "m" + } diff --git a/ale/drivers/base.py b/ale/base/data_isis.py similarity index 59% rename from ale/drivers/base.py rename to ale/base/data_isis.py index 9f365fe750bb1ec85f3b3f3ed85b50ced9c5f2ea..fc941c8e4911c72e7e1ced9e989f2a037a132e7f 100644 --- a/ale/drivers/base.py +++ b/ale/base/data_isis.py @@ -10,11 +10,11 @@ import pvl import spiceypy as spice from ale import config - from scipy.interpolate import interp1d from scipy.spatial.transform import Slerp from scipy.spatial.transform import Rotation +from ale.base.label_isis import IsisLabel def read_table_data(table_label, cube): """ @@ -218,642 +218,6 @@ def parse_position_table(label, field_data): return results -class Driver(): - """ - Base class for all Drivers. - - Attributes - ---------- - _file : str - Reference to file path to be used by mixins for opening. - """ - - def __init__(self, file, num_ephem=909, num_quats=909): - """ - Parameters - ---------- - file : str - path to file to be parsed - """ - self._num_quaternions = num_quats - self._num_ephem = num_ephem - self._file = file - - def __str__(self): - """ - Returns a string representation of the class - - Returns - ------- - str - String representation of all attributes and methods of the class - """ - return str(self.to_dict()) - - def is_valid(self): - """ - Checks if the driver has an intrument id associated with it - - Returns - ------- - bool - True if an instrument_id is defined, False otherwise - """ - try: - iid = self.instrument_id - return True - except Exception as e: - print(e) - return False - - def to_dict(self): - """ - Generates a dictionary of keys based on the attributes and methods assocated with - the driver and the required keys for the driver - - Returns - ------- - dict - Dictionary of key, attribute pairs - """ - keys = set() - return {p:getattr(self, p) for p in dir(self) if p[0] != "_" and isinstance(getattr(type(self), p), property)} - - - @property - def label(self): - if not hasattr(self, "_label"): - if isinstance(self._file, pvl.PVLModule): - self._label = self._file - try: - self._label = pvl.loads(self._file) - except Exception: - self._label = pvl.load(self._file) - except: - raise ValueError("{} is not a valid label".format(self._file)) - return self._label - - @property - def file(self): - return self._file - - @property - def interpolation_method(self): - return "lagrange" - - @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 1 - - @property - def name_platform(self): - return "Generic Platform" - - @property - def name_sensor(self): - return "Generic Sensor" - - @property - def radii(self): - return { - "semimajor" : self._semimajor, - "semiminor" : self._semiminor, - "unit" : "km" # default to KM - } - - @property - def reference_height(self): - # TODO: This should be a reasonable # - return { - "minheight" : 0, - "maxheight": 1000, - "unit": "m" - } - - @property - def focal_length_model(self): - return { - "focal_length" : self._focal_length - } - - @property - def detector_center(self): - if not hasattr(self, '_detector_center'): - self._detector_center = { - "line" : self._detector_center_line, - "sample" : self._detector_center_sample - } - return self._detector_center - - @property - def sensor_position(self): - return { - "positions" : self._sensor_position, - "velocities" : self._sensor_velocity, - "unit" : "m" - } - - @property - def sensor_orientation(self): - return { - "quaternions" : self._sensor_orientation - } - - @property - def sun_position(self): - return { - "positions" : self._sun_position, - "velocities" : self._sun_velocity, - "unit" : "m" - } - - -class LineScanner(): - @property - def name_model(self): - """ - Returns Key used to define the sensor type. Primarily - used for generating camera models. - - Returns - ------- - : str - USGS Frame model - """ - 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_quaternions - - @property - def line_scan_rate(self): - """ - Returns - ------- - : list - 2d list of scan rates in the form: [[start_line, line_time, exposure_duration], ...] - """ - return [[float(self.starting_detector_line), self.t0_ephemeris, self.line_exposure_duration]] - - @property - def number_of_ephemerides(self): - return self._num_ephem - - @property - def number_of_quaternions(self): - #TODO: Not make this hardcoded - return self._num_quaternions - - @property - def ending_ephemeris_time(self): - return (self.image_lines * self.line_exposure_duration) + self.starting_ephemeris_time - - @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 line_exposure_duration(self): - return self.label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds - -class Framer(): - @property - def name_sensor(self): - return "Generic Framer" - - @property - def name_model(self): - """ - Returns Key used to define the sensor type. Primarily - used for generating camera models. - - Returns - ------- - : str - USGS Frame model - """ - 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 - - @property - def center_ephemeris_time(self): - """ - The center ephemeris time for a framer. - """ - center_time = self.starting_ephemeris_time + self.exposure_duration / 2 - return center_time - - @property - def exposure_duration(self): - return self._exposure_duration - - -class Pds3Label(): - """ - Mixin for reading from PDS3 Labels. - - Attributes - ---------- - _label : PVLModule - Dict-like object with PVL keys - - """ - - @property - def _focal_plane_tempature(self): - return self.label['FOCAL_PLANE_TEMPERATURE'].value - - @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): - """ - Returns an target name for unquely identifying the instrument, but often - piped into Spice Kernels to acquire Ephermis data from Spice. Therefore they - the same ID the Spice expects in bodvrd calls. - - Returns - ------- - : str - target name - """ - - return self.label['TARGET_NAME'] - - @property - def _target_id(self): - return spice.bodn2c(self.label['TARGET_NAME']) - - @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 spacecraft_clock_stop_count(self): - sc = self.label.get('SPACECRAFT_CLOCK_STOP_COUNT', None) - if sc == 'N/A': - sc = None - return sc - - @property - def _detector_center_line(self): - return spice.gdpool('INS{}_CCD_CENTER'.format(self.ikid), 0, 2)[0] - - @property - def _detector_center_sample(self): - return spice.gdpool('INS{}_CCD_CENTER'.format(self.ikid), 0, 2)[1] - - @property - def spacecraft_name(self): - """ - Spacecraft name used in various Spice calls to acquire - ephemeris data. - - Returns - ------- - : str - Spacecraft name - """ - return self.label['MISSION_NAME'] - - @property - def detector_line_summing(self): - return self.label.get('SAMPLING_FACTOR', 1) - - @property - def _exposure_duration(self): - # The EXPOSURE_DURATION may either be stored as a (value, unit) or just a value - try: - return self.label['EXPOSURE_DURATION'].value * 0.001 - except: - return self.label['EXPOSURE_DURATION'] * 0.001 - - -class NaifSpice(): - - 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 metakernel(self): - pass - - @property - def _odtx(self): - """ - Returns - ------- - : list - Optical distortion x coefficients - """ - return spice.gdpool('INS{}_OD_T_X'.format(self.ikid),0, 10).tolist() - - @property - def _odty(self): - """ - Returns - ------- - : list - Optical distortion y coefficients - """ - return spice.gdpool('INS{}_OD_T_Y'.format(self.ikid), 0, 10).tolist() - - @property - def _odtk(self): - """ - Returns - ------- - : list - Radial distortion coefficients - """ - return spice.gdpool('INS{}_OD_K'.format(self.ikid),0, 3).tolist() - - @property - def ikid(self): - """ - Returns - ------- - : int - Naif ID used to for indentifying the instrument in Spice kernels - """ - return spice.bods2c(self.instrument_id) - - @property - def spacecraft_id(self): - return spice.bods2c(self.spacecraft_name) - - @property - def focal2pixel_lines(self): - return list(spice.gdpool('INS{}_ITRANSL'.format(self.fikid), 0, 3)) - - @property - def focal2pixel_samples(self): - return list(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): - """ - Returns - ------- - : double - Semimajor axis of the target body - """ - rad = spice.bodvrd(self.target_name, 'RADII', 3) - return rad[1][0] - - @property - def _semiminor(self): - """ - Returns - ------- - : double - Semiminor axis of the target body - """ - rad = spice.bodvrd(self.target_name, 'RADII', 3) - return rad[1][2] - - @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, '_position'): - eph = [] - 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,) - eph.append(state[:3]) - current_et += getattr(self, "dt_ephemeris", 0) - # By default, spice works in km - self._position = [e * 1000 for e in eph] - return self._position - - @property - def _sensor_velocity(self): - if not hasattr(self, '_velocity'): - eph_rates = [] - 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,) - eph_rates.append(state[3:]) - current_et += getattr(self, "dt_ephemeris", 0) - # By default, spice works in km - self._velocity = [e*1000 for e in eph_rates] - return self._velocity - - @property - def _sensor_orientation(self): - if not hasattr(self, '_orientation'): - current_et = self.starting_ephemeris_time - qua = np.empty((self.number_of_quaternions, 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._orientation = qua - return self._orientation.tolist() - - @property - def _detector_center_sample(self): - return float(spice.gdpool('INS{}_BORESIGHT_SAMPLE'.format(self.ikid), 0, 1)[0]) - - - @property - def _detector_center_line(self): - return float(spice.gdpool('INS{}_BORESIGHT_LINE'.format(self.ikid), 0, 1)[0]) - - @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 IsisLabel(): - - @property - def start_time(self): - return self.label['IsisCube']['Instrument']['StartTime'] - - @property - def spacecraft_name(self): - """ - Spacecraft name used in various Spice calls to acquire - ephemeris data. - - Returns - ------- - : str - Spacecraft name - """ - return self.label['IsisCube']['Instrument']['SpacecraftName'] - - @property - def image_lines(self): - """ - Returns - ------- - : int - Number of lines in image - """ - return self.label['IsisCube']['Core']['Dimensions']['Lines'] - - @property - def image_samples(self): - """ - Returns - ------- - : int - Number of samples in image - """ - return self.label['IsisCube']['Core']['Dimensions']['Samples'] - - @property - def target_name(self): - """ - Target name used in various Spice calls to acquire - target specific ephemeris data. - - Returns - ------- - : str - Target name - """ - 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).value - return self._starting_ephemeris_time - - @property - def _exposure_duration(self): - return self.label['IsisCube']['Instrument']['ExposureDuration'].value * 0.001 class IsisSpice(IsisLabel): """Mixin class for reading from an ISIS cube that has been spiceinit'd @@ -1400,23 +764,3 @@ class IsisSpice(IsisLabel): rotation_mat = Rotation.from_dcm(body_const_rots)*rotation_mat return rotation_mat - - -class RadialDistortion(): - @property - def optical_distortion(self): - return { - "radial": { - "coefficients" : self._odtk - } - } - -class TransverseDistortion(): - @property - def optical_distortion(self): - return { - "transverse": { - "x" : self._odtx, - "y" : self._odty - } - } diff --git a/ale/base/data_naif.py b/ale/base/data_naif.py new file mode 100644 index 0000000000000000000000000000000000000000..a7142a3d9fa4de8b5548fdb03de94052d90b2d0c --- /dev/null +++ b/ale/base/data_naif.py @@ -0,0 +1,198 @@ +import spiceypy as spice +import numpy as np +from ale.base.type_sensor import Framer + +class NaifSpice(): + 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 metakernel(self): + pass + + @property + def _odtx(self): + """ + Returns + ------- + : list + Optical distortion x coefficients + """ + return spice.gdpool('INS{}_OD_T_X'.format(self.ikid),0, 10).tolist() + + @property + def _odty(self): + """ + Returns + ------- + : list + Optical distortion y coefficients + """ + return spice.gdpool('INS{}_OD_T_Y'.format(self.ikid), 0, 10).tolist() + + @property + def _odtk(self): + """ + Returns + ------- + : list + Radial distortion coefficients + """ + return spice.gdpool('INS{}_OD_K'.format(self.ikid),0, 3).tolist() + + @property + def ikid(self): + """ + Returns + ------- + : int + Naif ID used to for indentifying the instrument in Spice kernels + """ + return spice.bods2c(self.instrument_id) + + @property + def spacecraft_id(self): + return spice.bods2c(self.spacecraft_name) + + @property + def focal2pixel_lines(self): + return list(spice.gdpool('INS{}_ITRANSL'.format(self.fikid), 0, 3)) + + @property + def focal2pixel_samples(self): + return list(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): + """ + Returns + ------- + : double + Semimajor axis of the target body + """ + rad = spice.bodvrd(self.target_name, 'RADII', 3) + return rad[1][0] + + @property + def _semiminor(self): + """ + Returns + ------- + : double + Semiminor axis of the target body + """ + rad = spice.bodvrd(self.target_name, 'RADII', 3) + return rad[1][2] + + @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, '_position'): + eph = [] + 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,) + eph.append(state[:3]) + current_et += getattr(self, "dt_ephemeris", 0) + # By default, spice works in km + self._position = [e * 1000 for e in eph] + return self._position + + @property + def _sensor_velocity(self): + if not hasattr(self, '_velocity'): + eph_rates = [] + 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,) + eph_rates.append(state[3:]) + current_et += getattr(self, "dt_ephemeris", 0) + # By default, spice works in km + self._velocity = [e*1000 for e in eph_rates] + return self._velocity + + @property + def _sensor_orientation(self): + if not hasattr(self, '_orientation'): + current_et = self.starting_ephemeris_time + qua = np.empty((self.number_of_quaternions, 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._orientation = qua + return self._orientation.tolist() + + @property + def _detector_center_sample(self): + return float(spice.gdpool('INS{}_BORESIGHT_SAMPLE'.format(self.ikid), 0, 1)[0]) + + + @property + def _detector_center_line(self): + return float(spice.gdpool('INS{}_BORESIGHT_LINE'.format(self.ikid), 0, 1)[0]) + + @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) diff --git a/ale/base/label_isis.py b/ale/base/label_isis.py new file mode 100644 index 0000000000000000000000000000000000000000..a35c11064016f2aa2dd6aa3c37b23a1e2ef5faa8 --- /dev/null +++ b/ale/base/label_isis.py @@ -0,0 +1,63 @@ + +class IsisLabel(): + + @property + def start_time(self): + return self.label['IsisCube']['Instrument']['StartTime'] + + @property + def spacecraft_name(self): + """ + Spacecraft name used in various Spice calls to acquire + ephemeris data. + + Returns + ------- + : str + Spacecraft name + """ + return self.label['IsisCube']['Instrument']['SpacecraftName'] + + @property + def image_lines(self): + """ + Returns + ------- + : int + Number of lines in image + """ + return self.label['IsisCube']['Core']['Dimensions']['Lines'] + + @property + def image_samples(self): + """ + Returns + ------- + : int + Number of samples in image + """ + return self.label['IsisCube']['Core']['Dimensions']['Samples'] + + @property + def target_name(self): + """ + Target name used in various Spice calls to acquire + target specific ephemeris data. + + Returns + ------- + : str + Target name + """ + 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).value + return self._starting_ephemeris_time + + @property + def _exposure_duration(self): + return self.label['IsisCube']['Instrument']['ExposureDuration'].value * 0.001 diff --git a/ale/base/label_pds3.py b/ale/base/label_pds3.py new file mode 100644 index 0000000000000000000000000000000000000000..2f7c42f0a45bd7b8d44019e8a6ba1189b9d3d634 --- /dev/null +++ b/ale/base/label_pds3.py @@ -0,0 +1,103 @@ +import pvl +import spiceypy as spice + +class Pds3Label(): + """ + Mixin for reading from PDS3 Labels. + + Attributes + ---------- + _label : PVLModule + Dict-like object with PVL keys + + """ + + @property + def _focal_plane_tempature(self): + return self.label['FOCAL_PLANE_TEMPERATURE'].value + + @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): + """ + Returns an target name for unquely identifying the instrument, but often + piped into Spice Kernels to acquire Ephermis data from Spice. Therefore they + the same ID the Spice expects in bodvrd calls. + + Returns + ------- + : str + target name + """ + + return self.label['TARGET_NAME'] + + @property + def _target_id(self): + return spice.bodn2c(self.label['TARGET_NAME']) + + @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 spacecraft_clock_stop_count(self): + sc = self.label.get('SPACECRAFT_CLOCK_STOP_COUNT', None) + if sc == 'N/A': + sc = None + return sc + + @property + def _detector_center_line(self): + return spice.gdpool('INS{}_CCD_CENTER'.format(self.ikid), 0, 2)[0] + + @property + def _detector_center_sample(self): + return spice.gdpool('INS{}_CCD_CENTER'.format(self.ikid), 0, 2)[1] + + @property + def spacecraft_name(self): + """ + Spacecraft name used in various Spice calls to acquire + ephemeris data. + + Returns + ------- + : str + Spacecraft name + """ + return self.label['MISSION_NAME'] + + @property + def detector_line_summing(self): + return self.label.get('SAMPLING_FACTOR', 1) + + @property + def _exposure_duration(self): + # The EXPOSURE_DURATION may either be stored as a (value, unit) or just a value + try: + return self.label['EXPOSURE_DURATION'].value * 0.001 + except: + return self.label['EXPOSURE_DURATION'] * 0.001 diff --git a/ale/base/type_distortion.py b/ale/base/type_distortion.py new file mode 100644 index 0000000000000000000000000000000000000000..40aec329d0c16021d3970793b98b4db9ffc35019 --- /dev/null +++ b/ale/base/type_distortion.py @@ -0,0 +1,18 @@ +class RadialDistortion(): + @property + def optical_distortion(self): + return { + "radial": { + "coefficients" : self._odtk + } + } + +class TransverseDistortion(): + @property + def optical_distortion(self): + return { + "transverse": { + "x" : self._odtx, + "y" : self._odty + } + } diff --git a/ale/base/type_sensor.py b/ale/base/type_sensor.py new file mode 100644 index 0000000000000000000000000000000000000000..1945db8729cce17431dc273ffe1f7e7346147e21 --- /dev/null +++ b/ale/base/type_sensor.py @@ -0,0 +1,111 @@ +class LineScanner(): + @property + def name_model(self): + """ + Returns Key used to define the sensor type. Primarily + used for generating camera models. + + Returns + ------- + : str + USGS Frame model + """ + 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_quaternions + + @property + def line_scan_rate(self): + """ + Returns + ------- + : list + 2d list of scan rates in the form: [[start_line, line_time, exposure_duration], ...] + """ + return [[float(self.starting_detector_line), self.t0_ephemeris, self.line_exposure_duration]] + + @property + def number_of_ephemerides(self): + return self._num_ephem + + @property + def number_of_quaternions(self): + #TODO: Not make this hardcoded + return self._num_quaternions + + @property + def ending_ephemeris_time(self): + return (self.image_lines * self.line_exposure_duration) + self.starting_ephemeris_time + + @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 line_exposure_duration(self): + return self.label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds + +class Framer(): + @property + def name_sensor(self): + return "Generic Framer" + + @property + def name_model(self): + """ + Returns Key used to define the sensor type. Primarily + used for generating camera models. + + Returns + ------- + : str + USGS Frame model + """ + 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 + + @property + def center_ephemeris_time(self): + """ + The center ephemeris time for a framer. + """ + center_time = self.starting_ephemeris_time + self.exposure_duration / 2 + return center_time + + @property + def exposure_duration(self): + return self._exposure_duration diff --git a/ale/drivers/cassini_drivers.py b/ale/drivers/cassini_drivers.py index 12f42e49867a922f41632ba295b72f816b6bf666..9e49f14d19805eddbcd8af554822755257627605 100644 --- a/ale/drivers/cassini_drivers.py +++ b/ale/drivers/cassini_drivers.py @@ -1,12 +1,17 @@ -from glob import glob import os +from glob import glob -import pvl -import spiceypy as spice import numpy as np +import pvl +import spiceypy as spice from ale import config -from ale.drivers.base import Framer, RadialDistortion, Driver, Pds3Label, NaifSpice +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.type_distortion import RadialDistortion +from ale.base.type_sensor import Framer + class CassiniIssPds3LabelNaifSpiceDriver(Driver, Pds3Label, NaifSpice, Framer, RadialDistortion): """ @@ -40,7 +45,7 @@ class CassiniIssPds3LabelNaifSpiceDriver(Driver, Pds3Label, NaifSpice, Framer, R def instrument_id(self): """ Returns an instrument id for unquely identifying the instrument, but often - also used to be piped into Spice Kernels to acquire instrument kernel (IK) NAIF IDs. + also used to be piped into Spice Kernels to acquire instrument kernel (IK) NAIF IDs. Therefore they use the same NAIF ID asin bods2c calls. Returns diff --git a/ale/drivers/dawn_drivers.py b/ale/drivers/dawn_drivers.py index a8726471b94848768ac14f112771b2a785e0602f..e62f9acaa4ceb37651591ac6da965c1b22fc1ffe 100644 --- a/ale/drivers/dawn_drivers.py +++ b/ale/drivers/dawn_drivers.py @@ -1,8 +1,14 @@ +import pvl import ale -from ale.drivers.base import * from ale import config -import pvl + +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.type_distortion import RadialDistortion +from ale.base.type_sensor import Framer + class DawnFcNaifSpice(Driver, Framer, NaifSpice): """ diff --git a/ale/drivers/kaguya_drivers.py b/ale/drivers/kaguya_drivers.py index c7a98eb30e168471a25b302901df075a8b1fe226..87612af1eeed2527bcbf5a3ceef32d12a15ff17c 100644 --- a/ale/drivers/kaguya_drivers.py +++ b/ale/drivers/kaguya_drivers.py @@ -1,14 +1,18 @@ -import spiceypy as spice import os -import pvl -import ale - +from glob import glob import numpy as np -from glob import glob + +import pvl +import spiceypy as spice from ale import config -from ale.drivers.base import Driver, LineScanner, Pds3Label, NaifSpice, TransverseDistortion +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.type_distortion import TransverseDistortion +from ale.base.type_sensor import LineScanner + class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): """ diff --git a/ale/drivers/lro_drivers.py b/ale/drivers/lro_drivers.py index 410d00294ae1dabaa60d0466beeda691b630a3c6..00c88721e58a44b7cdfe994ee0110ff2f6110d1c 100644 --- a/ale/drivers/lro_drivers.py +++ b/ale/drivers/lro_drivers.py @@ -6,7 +6,11 @@ from glob import glob from ale import config from ale.util import get_metakernels -from ale.drivers.base import LineScanner, NaifSpice, Pds3Label, Driver +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.type_sensor import LineScanner + class LroLrocPds3LabelNaifSpiceDriver(Driver, NaifSpice, Pds3Label, LineScanner): """ @@ -97,4 +101,3 @@ class LroLrocPds3LabelNaifSpiceDriver(Driver, NaifSpice, Pds3Label, LineScanner) "coefficients": self._odtk, } } - diff --git a/ale/drivers/messenger_drivers.py b/ale/drivers/messenger_drivers.py index 2bc996375ae1eedf0a3d6a54eaebe2c837680349..e36d1284beab6827f307b68eca2619a0a97e2311 100644 --- a/ale/drivers/messenger_drivers.py +++ b/ale/drivers/messenger_drivers.py @@ -6,8 +6,11 @@ import spiceypy as spice import numpy as np from ale import config -from ale.drivers.base import Framer, NaifSpice, Pds3Label, IsisLabel, Driver - +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.label_isis import IsisLabel +from ale.base.type_sensor import Framer class MessengerMdisNaifSpice(Driver, NaifSpice, Framer): """ diff --git a/ale/drivers/mro_drivers.py b/ale/drivers/mro_drivers.py index b5f42545bd765e5d9cc1c23d25428b0567bcfc4d..e591fa14910448fc78519ddad92676af80e2cab6 100644 --- a/ale/drivers/mro_drivers.py +++ b/ale/drivers/mro_drivers.py @@ -4,9 +4,15 @@ import os import numpy as np import pvl import spiceypy as spice -from ale import config -from ale.drivers.base import LineScanner, NaifSpice, Pds3Label, IsisLabel, IsisSpice, Driver, RadialDistortion +from ale import config +from ale.base import Driver +from ale.base.data_naif import NaifSpice +from ale.base.data_isis import IsisSpice +from ale.base.label_pds3 import Pds3Label +from ale.base.label_isis import IsisLabel +from ale.base.type_distortion import RadialDistortion +from ale.base.type_sensor import LineScanner class MroCtxIsisLabelIsisSpiceDriver(Driver, IsisSpice, LineScanner, RadialDistortion): diff --git a/tests/pytests/test_dawn_driver.py b/tests/pytests/test_dawn_driver.py index 11de0eb2c12e3a508ffd0e2e2b3db548c84d655f..611ff8df616aa98681eaa6003d265a6c5f09e729 100644 --- a/tests/pytests/test_dawn_driver.py +++ b/tests/pytests/test_dawn_driver.py @@ -4,14 +4,16 @@ from unittest import mock import pytest import ale -from ale.drivers import dawn_drivers, base +from ale.drivers import dawn_drivers +from ale.base import data_naif + from ale.drivers.dawn_drivers import DawnFcPds3NaifSpiceDriver # 'Mock' the spice module where it is imported from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() -base.spice = simplespice +data_naif.spice = simplespice dawn_drivers.spice = simplespice DawnFcPds3NaifSpiceDriver.metakernel = get_mockkernels diff --git a/tests/pytests/test_isis_spice_drivers.py b/tests/pytests/test_isis_spice_drivers.py index 3d6980aebf55808dd0266e46efb8770ff673f07c..98780bf86809cefdd6619dcdf42fd80601ac4a33 100644 --- a/tests/pytests/test_isis_spice_drivers.py +++ b/tests/pytests/test_isis_spice_drivers.py @@ -3,7 +3,8 @@ from collections import namedtuple import pytest import ale -from ale.drivers import base +from ale.base import data_isis +from ale import base from ale import util import pvl @@ -430,13 +431,13 @@ End count = table_label['Records'] * len(table_label.getlist('Field')) doubles = list(range(count)) return struct.pack('d' * count, *doubles) - monkeypatch.setattr(base, 'read_table_data', test_table_data) + monkeypatch.setattr(data_isis, 'read_table_data', test_table_data) def test_label(file): return pvl.loads(label) monkeypatch.setattr(pvl, 'load', test_label) - test_image = type('TestCubeDriver', (base.Driver, base.IsisSpice), {})(label) + test_image = type('TestCubeDriver', (base.Driver, data_isis.IsisSpice), {})(label) # test_image._file = 'testfile.cub' return test_image diff --git a/tests/pytests/test_lro_drivers.py b/tests/pytests/test_lro_drivers.py index 92bda58bfe4d02f7341e8e56febb1d03e64d1a74..67c4eb50269d143e59f4fd94e0ebebaf84903064 100644 --- a/tests/pytests/test_lro_drivers.py +++ b/tests/pytests/test_lro_drivers.py @@ -4,7 +4,9 @@ from unittest import mock import pytest import ale -from ale.drivers import lro_drivers, base +from ale.drivers import lro_drivers +from ale.base import data_naif + from ale.drivers.lro_drivers import LroLrocPds3LabelNaifSpiceDriver from ale import util @@ -12,7 +14,7 @@ from ale import util from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() -base.spice = simplespice +data_naif.spice = simplespice lro_drivers.spice = simplespice LroLrocPds3LabelNaifSpiceDriver.metakernel = get_mockkernels diff --git a/tests/pytests/test_mdis_driver.py b/tests/pytests/test_mdis_driver.py index 67d5066bdf1cee0f07d088551723241a09c25e7e..3f8422f5c76a140285edc58966c76e9cddafc518 100644 --- a/tests/pytests/test_mdis_driver.py +++ b/tests/pytests/test_mdis_driver.py @@ -4,7 +4,9 @@ from unittest import mock import pytest import ale -from ale.drivers import messenger_drivers, base +from ale.drivers import messenger_drivers +from ale.base import data_naif + from ale.drivers.messenger_drivers import MessengerMdisPds3NaifSpiceDriver @@ -12,7 +14,7 @@ from ale.drivers.messenger_drivers import MessengerMdisPds3NaifSpiceDriver from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() -base.spice = simplespice +data_naif.spice = simplespice messenger_drivers.spice = simplespice MessengerMdisPds3NaifSpiceDriver.metakernel = get_mockkernels diff --git a/tests/pytests/test_mro_drivers.py b/tests/pytests/test_mro_drivers.py index cdc6428db95aed809b9284c587152d61d656c15e..3a6cb40e64c73f44e7ce739dac5db9ce1f265b53 100644 --- a/tests/pytests/test_mro_drivers.py +++ b/tests/pytests/test_mro_drivers.py @@ -1,15 +1,20 @@ import pytest import ale -from ale.drivers.mro_drivers import MroCtxPds3LabelNaifSpiceDriver -from ale.drivers import mro_drivers, base +from ale.drivers import mro_drivers +from ale.base import data_naif +from ale.base import label_pds3 # 'Mock' the spice module where it is imported from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() -base.spice = simplespice + +data_naif.spice = simplespice mro_drivers.spice = simplespice +label_pds3.spice = simplespice + +from ale.drivers.mro_drivers import MroCtxPds3LabelNaifSpiceDriver MroCtxPds3LabelNaifSpiceDriver.metakernel = get_mockkernels