diff --git a/ale/drivers/base.py b/ale/drivers/base.py index 205d7b6db5a01307a25f17aa38675ac1b9ba7791..4cacadee74839bd5d88f185c20e854814ba605d1 100644 --- a/ale/drivers/base.py +++ b/ale/drivers/base.py @@ -5,8 +5,6 @@ import numpy as np import pvl import spiceypy as spice -from ale.drivers import distortion - class Base(ABC): """ Abstract base class for all PDS label parsing. Implementations should override @@ -19,7 +17,12 @@ class Base(ABC): """ def __init__(self, label, *args, **kwargs): - self._label = pvl.loads(label, strict=False) + if isinstance(label, str): + self._label = pvl.loads(label, strict=False) + elif isinstance(label, pvl.PVLModule): + self._label = label + else: + self._label = pvl.load(label, strict=False) def __enter__(self): """ @@ -95,6 +98,60 @@ class Base(ABC): data['name_sensor'] = data['instrument_id'] return data + @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 + +class TransverseDistortion(ABC): + """ + Exposes the properties that are used to describe a transverse distortion model. + """ + @property + @abstractmethod + def odtx(self): + pass + + @property + @abstractmethod + def odty(self): + pass + + +class RadialDistortion(ABC): + """ + Exposes the properties that are used to describe a radial distortion model. + """ + @property + @abstractmethod + def odtk(self): + pass + + +class LineScanner(Base): + @property + def name_model(self): + return "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL" + + +class Framer(Base): + @property + def name_model(self): + return "USGS_ASTRO_FRAME_SENSOR_MODEL" + + +class PDS3(): def _compute_ephemerides(self): """ Helper function to pull position and velocity in one pass @@ -121,12 +178,13 @@ class Base(ABC): @property - @abstractmethod - def metakernel(self): - pass + def line_exposure_duration(self): + try: + return self._label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds + except: + return np.nan @property - @abstractmethod def instrument_id(self): pass @@ -166,8 +224,19 @@ class Base(ABC): return self._starting_ephemeris_time @property - def _exposure_duration(self): - return self._label['EXPOSURE_DURATION'].value * 0.001 # Scale to seconds + def exposure_duration(self): + try: + return self._label['EXPOSURE_DURATION'].value * 0.001 # Scale to seconds + except: + return np.nan + + @property + def number_of_ephemerides(self): + return 909 + + @property + def number_of_quaternions(self): + return 909 @property def spacecraft_clock_stop_count(self): @@ -179,7 +248,7 @@ class Base(ABC): @property def ending_ephemeris_time(self): if not hasattr(self, '_ending_ephemeris_time'): - self._ending_ephemeris_time = (self.image_lines * self._exposure_duration) + self.starting_ephemeris_time + self._ending_ephemeris_time = (self.image_lines * self.line_exposure_duration) + self.starting_ephemeris_time return self._ending_ephemeris_time @property @@ -196,13 +265,51 @@ class Base(ABC): 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) + + @property + def filter_number(self): + return self._label.get('FILTER_NUMBER', 0) + + +class Spice(): + @property + def metakernel(self): + pass + + @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 fikid(self): - fn = self._label.get('FILTER_NUMBER', 0) + fn = self.filter_number if fn == 'N/A': fn = 0 return self.ikid - int(fn) @@ -223,35 +330,19 @@ class Base(ABC): def focal_length(self): return float(spice.gdpool('INS{}_FOCAL_LENGTH'.format(self.ikid), 0, 1)[0]) - @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) - @property def semimajor(self): - rad = spice.bodvrd(self._label['TARGET_NAME'], 'RADII', 3) + rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1][1] @property def semiminor(self): - rad = spice.bodvrd(self._label['TARGET_NAME'], 'RADII', 3) + rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1][0] @property def reference_frame(self): - return 'IAU_{}'.format(self._label['TARGET_NAME']) + return 'IAU_{}'.format(self.target_name) @property def sun_position(self): @@ -259,7 +350,7 @@ class Base(ABC): self.center_ephemeris_time, self.reference_frame, 'NONE', - self._label['TARGET_NAME']) + self.target_name) return [sun_state[:4].tolist()] @@ -269,7 +360,7 @@ class Base(ABC): self.center_ephemeris_time, self.reference_frame, 'NONE', - self._label['TARGET_NAME']) + self.target_name) return [sun_state[3:6].tolist()] @@ -307,23 +398,13 @@ class Base(ABC): # TODO: This should be a reasonable # return 0, 100 -class LineScanner(Base): - - @property - def name_model(self): - return "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL" - - @property - def _exposure_duration(self): - return self._label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds - @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._exposure_duration]] + return [[float(self.starting_detector_line), self.t0_ephemeris, self.line_exposure_duration]] @property def detector_center(self): @@ -340,50 +421,39 @@ class LineScanner(Base): """ if not hasattr(self, '_center_ephemeris_time'): halflines = self.image_lines / 2 - center_sclock = self.starting_ephemeris_time + halflines * self._exposure_duration + center_sclock = self.starting_ephemeris_time + halflines * self.line_exposure_duration self._center_ephemeris_time = center_sclock return self._center_ephemeris_time - @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 +class Isis3(): @property - def dt_ephemeris(self): - return (self.ending_ephemeris_time - self.starting_ephemeris_time) / self.number_of_ephemerides + def start_time(self): + return self._label['IsisCube']['Instrument']['StartTime'] @property - def dt_quaternion(self): - return (self.ending_ephemeris_time - self.starting_ephemeris_time) / self.number_of_ephemerides + def spacecraft_name(self): + return self._label['IsisCube']['Instrument']['SpacecraftName'] @property - def number_of_ephemerides(self): - return 909 + def image_lines(self): + return self._label['IsisCube']['Core']['Dimensions']['Lines'] @property - def number_of_quaternions(self): - return 909 - - + def image_samples(self): + return self._label['IsisCube']['Core']['Dimensions']['Samples'] @property def _exposure_duration(self): - return self._label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds - -class Framer(Base): - - @property - def name_model(self): - return "USGS_ASTRO_FRAME_SENSOR_MODEL" + return self._label['IsisCube']['Instrument']['ExposureDuration'].value * 0.001 # Scale to seconds @property - def number_of_ephemerides(self): - return 1 + def target_name(self): + return self._label['IsisCube']['Instrument']['TargetName'] @property - def number_of_quaternions(self): - return 1 + 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 diff --git a/ale/drivers/cassini_driver.py b/ale/drivers/cassini_driver.py index e28822c8642447e3e3965568bd56871c874ca6ec..ba1926022f38e2d8ea51d70dbcfe387088c5d663 100644 --- a/ale/drivers/cassini_driver.py +++ b/ale/drivers/cassini_driver.py @@ -6,8 +6,7 @@ import spiceypy as spice import numpy as np from ale import config -from ale.drivers.base import Framer -from ale.drivers.distortion import RadialDistortion +from ale.drivers.base import Framer, RadialDistortion class CassiniISS(Framer, RadialDistortion): diff --git a/ale/drivers/distortion.py b/ale/drivers/distortion.py deleted file mode 100644 index 3a5bac4d443af3750f03d3b8c7120d6e572d799f..0000000000000000000000000000000000000000 --- a/ale/drivers/distortion.py +++ /dev/null @@ -1,23 +0,0 @@ -from abc import ABC -import spiceypy as spice - - -class TransverseDistortion(ABC): - """ - Exposes the properties that are used to describe a transverse distortion model. - """ - @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) - -class RadialDistortion(ABC): - """ - Exposes the properties that are used to describe a radial distortion model. - """ - @property - def odtk(self): - return spice.gdpool('INS{}_OD_K'.format(self.ikid),0, 3) diff --git a/ale/drivers/lro_driver.py b/ale/drivers/lro_driver.py index c912497e3f7842047a762dea344d945d4f57e4ed..39ab7beee491e3f01456368671c8be6050072f79 100644 --- a/ale/drivers/lro_driver.py +++ b/ale/drivers/lro_driver.py @@ -6,10 +6,9 @@ import pvl import spiceypy as spice from ale.util import get_metakernels -from ale.drivers.base import LineScanner -from ale.drivers.distortion import RadialDistortion +from ale.drivers.base import LineScanner, RadialDistortion, Spice, PDS3, Isis3 -class LRO_LROC(LineScanner, RadialDistortion): +class LrocSpice(Spice, LineScanner, RadialDistortion): @property def metakernel(self): @@ -17,6 +16,12 @@ class LRO_LROC(LineScanner, RadialDistortion): self._metakernel = metakernels['data'][0]['path'] return self._metakernel + @property + def spacecraft_name(self): + return "LRO" + + +class LroPds3Driver(PDS3, LrocSpice): @property def instrument_id(self): """ @@ -32,7 +37,3 @@ class LRO_LROC(LineScanner, RadialDistortion): return "LRO_LROCNACL" elif instrument == "LROC" and frame_id == "RIGHT": return "LRO_LROCNACR" - - @property - def spacecraft_name(self): - return "LRO" diff --git a/ale/drivers/mdis_driver.py b/ale/drivers/mdis_driver.py index 00c1dcf895409af960d0dfdfef4ef7e741c4c625..d8bbb96c86d4bfe8a7c528218178c4732e7444d0 100644 --- a/ale/drivers/mdis_driver.py +++ b/ale/drivers/mdis_driver.py @@ -6,11 +6,9 @@ import spiceypy as spice import numpy as np from ale import config -from ale.drivers.base import Framer -from ale.drivers.distortion import TransverseDistortion +from ale.drivers.base import Framer, TransverseDistortion, Spice, PDS3, Isis3 - -class Messenger(Framer, TransverseDistortion): +class MdisSpice(Spice, Framer, TransverseDistortion): id_lookup = { 'MDIS-WAC': 'MSGR_MDIS_WAC', 'MDIS-NAC':'MSGR_MDIS_NAC', @@ -28,10 +26,6 @@ class Messenger(Framer, TransverseDistortion): self._metakernel = mk return self._metakernel - @property - def instrument_id(self): - return self.id_lookup[self._label['INSTRUMENT_ID']] - @property def focal_length(self): """ @@ -43,7 +37,7 @@ class Messenger(Framer, TransverseDistortion): f_t = np.poly1d(coeffs[::-1]) # eval at the focal_plane_tempature - return f_t(self._label['FOCAL_PLANE_TEMPERATURE'].value) + return f_t(self.focal_plane_tempature) @property def focal_epsilon(self): @@ -56,3 +50,33 @@ class Messenger(Framer, TransverseDistortion): @property def starting_detector_line(self): return int(spice.gdpool('INS{}_FPUBIN_START_LINE'.format(self.ikid), 0, 1)[0]) + + +class MdisPDS3Driver(PDS3, MdisSpice): + @property + def instrument_id(self): + return self.id_lookup[self._label['INSTRUMENT_ID']] + + @property + def focal_plane_tempature(self): + return self._label['FOCAL_PLANE_TEMPERATURE'].value + + +class MdisIsis3Driver(Isis3, MdisSpice): + @property + def metakernel(self): + metakernel_dir = config.mdis + mks = sorted(glob(os.path.join(metakernel_dir,'*.tm'))) + if not hasattr(self, '_metakernel'): + for mk in mks: + if str(self.start_time.year) in os.path.basename(mk): + self._metakernel = mk + return self._metakernel + + @property + def instrument_id(self): + return self.id_lookup[self._label['IsisCube']['Instrument']['InstrumentId']] + + @property + def focal_plane_tempature(self): + return self._label['IsisCube']['Instrument']['FocalPlaneTemperature'].value diff --git a/ale/drivers/mro_driver.py b/ale/drivers/mro_driver.py index 20b28ab7c619b7b42f540f43e00f3144eeae40d8..0c5e843b5782dd7ce4fef093ba29e62bd7689c4c 100644 --- a/ale/drivers/mro_driver.py +++ b/ale/drivers/mro_driver.py @@ -6,10 +6,9 @@ import pvl import spiceypy as spice from ale import config -from ale.drivers.base import LineScanner -from ale.drivers.distortion import RadialDistortion +from ale.drivers.base import LineScanner, RadialDistortion, Spice, PDS3, Isis3 -class MRO_CTX(LineScanner, RadialDistortion): +class CtxSpice(Spice, LineScanner, RadialDistortion): id_lookup = { 'CONTEXT CAMERA':'MRO_CTX' } @@ -25,6 +24,9 @@ class MRO_CTX(LineScanner, RadialDistortion): self._metakernel = mk return self._metakernel + + +class CtxPds3Driver(PDS3, CtxSpice): @property def instrument_id(self): return self.id_lookup[self._label['INSTRUMENT_NAME']] @@ -35,3 +37,7 @@ class MRO_CTX(LineScanner, RadialDistortion): 'MARS_RECONNAISSANCE_ORBITER': 'MRO' } return name_lookup[self._label['SPACECRAFT_NAME']] + + @property + def instrument_id(self): + return self.id_lookup[self._label['INSTRUMENT_NAME']] diff --git a/tests/pytests/test_lro_drivers.py b/tests/pytests/test_lro_drivers.py index 2e0bd6dae3c09d148edc39a96b93679406378825..39fb43dc33d557a1868545a7b6a4d3d20e1a4a82 100644 --- a/tests/pytests/test_lro_drivers.py +++ b/tests/pytests/test_lro_drivers.py @@ -4,8 +4,8 @@ from unittest import mock import pytest import ale -from ale.drivers import lro_driver, base, distortion -from ale.drivers.lro_driver import LRO_LROC +from ale.drivers import lro_driver, base +from ale.drivers.lro_driver import LroPds3Driver from ale import util # 'Mock' the spice module where it is imported @@ -14,9 +14,8 @@ from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() base.spice = simplespice lro_driver.spice = simplespice -distortion.spice = simplespice -LRO_LROC.metakernel = get_mockkernels +LroPds3Driver.metakernel = get_mockkernels @pytest.fixture def lro_lroclabel(): @@ -113,6 +112,6 @@ def lro_lroclabel(): """ def test_lro_creation(lro_lroclabel): - with LRO_LROC(lro_lroclabel) as m: + with LroPds3Driver(lro_lroclabel) as m: d = m.to_dict() assert isinstance(d, dict) diff --git a/tests/pytests/test_mdis_driver.py b/tests/pytests/test_mdis_driver.py index 1084934187cca9d42fe3e69bd1d02bac3c1541fb..efcbd00a3425eeec72f5280a70e34fe900d14ad9 100644 --- a/tests/pytests/test_mdis_driver.py +++ b/tests/pytests/test_mdis_driver.py @@ -4,8 +4,8 @@ from unittest import mock import pytest import ale -from ale.drivers import mdis_driver, base, distortion -from ale.drivers.mdis_driver import Messenger +from ale.drivers import mdis_driver, base +from ale.drivers.mdis_driver import MdisPDS3Driver # 'Mock' the spice module where it is imported @@ -14,9 +14,8 @@ from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() base.spice = simplespice mdis_driver.spice = simplespice -distortion.spice = simplespice -Messenger.metakernel = get_mockkernels +MdisPDS3Driver.metakernel = get_mockkernels @pytest.fixture def mdislabel(): @@ -245,6 +244,6 @@ END """ def test_mdis_creation(mdislabel): - with Messenger(mdislabel) as m: + with MdisPDS3Driver(mdislabel) as m: d = m.to_dict() assert isinstance(d, dict) diff --git a/tests/pytests/test_mro_drivers.py b/tests/pytests/test_mro_drivers.py index 00b48f0d8a5616a372ce6c44b324cef6833f77a0..6f73701267efb77b56c7d93e3d26c61f69c744e3 100644 --- a/tests/pytests/test_mro_drivers.py +++ b/tests/pytests/test_mro_drivers.py @@ -1,8 +1,8 @@ import pytest import ale -from ale.drivers.mro_driver import MRO_CTX -from ale.drivers import mro_driver, base, distortion +from ale.drivers.mro_driver import CtxPds3Driver +from ale.drivers import mro_driver, base # 'Mock' the spice module where it is imported from conftest import SimpleSpice, get_mockkernels @@ -10,9 +10,8 @@ from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() base.spice = simplespice mro_driver.spice = simplespice -distortion.spice = simplespice -MRO_CTX.metakernel = get_mockkernels +CtxPds3Driver.metakernel = get_mockkernels @pytest.fixture def mroctx_label(): @@ -64,6 +63,6 @@ def mroctx_label(): def test_get_dict(mroctx_label): - with MRO_CTX(mroctx_label) as m: + with CtxPds3Driver(mroctx_label) as m: d = m.to_dict() assert isinstance(d, dict)