diff --git a/ale/__init__.py b/ale/__init__.py index b649ee82414e20bfe6ebad469da86f654dae8f38..f2d46ca2575f049a9d174327036200f3c0d83b73 100644 --- a/ale/__init__.py +++ b/ale/__init__.py @@ -1,2 +1,2 @@ from . import drivers -from .drivers import load, loads +from .drivers import load diff --git a/ale/drivers/__init__.py b/ale/drivers/__init__.py index a0f027848f8b35437a862264999d046042530017..7f0056e8760e28b403b24646a3031f790bc94bed 100644 --- a/ale/drivers/__init__.py +++ b/ale/drivers/__init__.py @@ -20,6 +20,9 @@ __driver_modules__ = [importlib.import_module('.'+m, package='ale.drivers') for drivers = dict(chain.from_iterable(inspect.getmembers(dmod, lambda x: inspect.isclass(x) and "_driver" in x.__module__) for dmod in __driver_modules__)) def load(label): + """ + Load label from + """ for name, driver in drivers.items(): try: print("TRYING:", driver) @@ -32,19 +35,3 @@ def load(label): import traceback traceback.print_exc() raise Exception('No Such Driver for Label') - -def loads(label): - class NumpyEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.ndarray): - return obj.tolist() - return json.JSONEncoder.default(self, obj) - - def json_serial(obj): - """JSON serializer for objects not serializable by default json code""" - if isinstance(obj, (datetime, date)): - return obj.isoformat() - - with load(label) as o: - s = json.dumps(o.to_dict(), cls=NumpyEncoder, default=json_serial) - return s diff --git a/ale/drivers/base.py b/ale/drivers/base.py index 050ed24852cb85894f4c9ae2538caa5decbf8cb3..57e8d3758dde10b0ad6e1598fba943b019b3fd09 100644 --- a/ale/drivers/base.py +++ b/ale/drivers/base.py @@ -7,8 +7,18 @@ 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): + """ + Parameters + ---------- + file : str + path to file to be parsed + """ self._file = file def __str__(self): @@ -29,6 +39,15 @@ class Driver(): class LineScanner(Driver): @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 @@ -50,8 +69,10 @@ class LineScanner(Driver): @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. + 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]] @@ -69,6 +90,15 @@ class LineScanner(Driver): class Framer(Driver): @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 @@ -87,6 +117,15 @@ class Framer(Driver): class PDS3(): + """ + Mixin for reading from PDS3 Labels. + + Attributes + ---------- + _label : PVLModule + Dict-like object with PVL keys + + """ def _compute_ephemerides(self): """ Helper function to pull position and velocity in one pass @@ -117,6 +156,14 @@ class PDS3(): @property def label(self): + """ + Loads a PVL from from the _file attribute. + + Returns + ------- + : PVLModule + Dict-like object with PVL keys + """ if not hasattr(self, "_label"): if isinstance(self._file, pvl.PVLModule): self._label = label @@ -157,6 +204,17 @@ class PDS3(): @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 @@ -203,6 +261,15 @@ class PDS3(): @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 @@ -223,6 +290,7 @@ class PDS3(): class Spice(): + @property def metakernel(self): pass @@ -246,18 +314,42 @@ class Spice(): @property def odtx(self): - return spice.gdpool('INS{}_OD_T_X'.format(self.ikid),0, 10) + """ + Returns + ------- + : list + Optical distortion x coefficients + """ + return spice.gdpool('INS{}_OD_T_X'.format(self.ikid),0, 10).tolist() @property def odty(self): - return spice.gdpool('INS{}_OD_T_Y'.format(self.ikid), 0, 10) + """ + Returns + ------- + : list + Optical distortion y coefficients + """ + return spice.gdpool('INS{}_OD_T_Y'.format(self.ikid), 0, 10).tolist() @property def odtk(self): - return spice.gdpool('INS{}_OD_K'.format(self.ikid),0, 3) + """ + 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 @@ -266,11 +358,11 @@ class Spice(): @property def focal2pixel_lines(self): - return spice.gdpool('INS{}_ITRANSL'.format(self.fikid), 0, 3) + return list(spice.gdpool('INS{}_ITRANSL'.format(self.fikid), 0, 3)) @property def focal2pixel_samples(self): - return spice.gdpool('INS{}_ITRANSS'.format(self.fikid), 0, 3) + return list(spice.gdpool('INS{}_ITRANSS'.format(self.fikid), 0, 3)) @property def focal_length(self): @@ -278,11 +370,23 @@ class Spice(): @property def semimajor(self): + """ + Returns + ------- + : double + Semimajor axis of the target body + """ rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1][1] @property def semiminor(self): + """ + Returns + ------- + : double + Semiminor axis of the target body + """ rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1][0] @@ -312,6 +416,7 @@ class Spice(): @property def sensor_position(self): + if not hasattr(self, '_sensor_position'): self._compute_ephemerides() return self._sensor_position.tolist() @@ -395,14 +500,35 @@ class Isis3(): @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 @@ -411,6 +537,15 @@ class Isis3(): @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 diff --git a/ale/drivers/cassini_driver.py b/ale/drivers/cassini_driver.py index ebbb670fb19a8ebd17cecbda0253b34789cfeb19..c584bd197756f91b387ecb4d8e248f7007ab6c29 100644 --- a/ale/drivers/cassini_driver.py +++ b/ale/drivers/cassini_driver.py @@ -11,6 +11,9 @@ from ale.drivers import keys class CassiniISS(Framer): + """ + Cassini mixin class for defining snowflake Spice calls. + """ id_lookup = { "ISSNA" : "CASSINI_ISS_NAC", "ISSWA" : "CASSINI_ISS_WAC" @@ -20,6 +23,14 @@ class CassiniISS(Framer): @property def metakernel(self): + """ + Returns latest instrument metakernels + + Returns + ------- + : string + Path to latest metakernel file + """ metakernel_dir = config.cassini mks = sorted(glob(os.path.join(metakernel_dir,'*.tm'))) if not hasattr(self, '_metakernel'): @@ -30,6 +41,16 @@ class CassiniISS(Framer): @property 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 IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + + Returns + ------- + : str + instrument id + """ return self.id_lookup[self.label['INSTRUMENT_ID']] @property @@ -38,6 +59,10 @@ class CassiniISS(Framer): @property def spacecraft_name(self): + """ + Spacecraft name used in various Spice calls to acquire + ephemeris data. + """ return 'CASSINI' @property diff --git a/ale/drivers/lro_driver.py b/ale/drivers/lro_driver.py index 5654f672f993a8779a17326cf8af1713b90fc8a9..a97cab4507a1188c918ea3783ed8bd4851c73320 100644 --- a/ale/drivers/lro_driver.py +++ b/ale/drivers/lro_driver.py @@ -11,24 +11,58 @@ from ale.drivers import keys class LrocSpice(Spice, LineScanner): + """ + Lroc mixin class for defining snowflake Spice calls. + """ required_keys = keys.base | keys.linescanner @property def metakernel(self): + """ + Returns latest instrument metakernels + + Returns + ------- + : string + Path to latest metakernel file + """ metakernels = get_metakernels(years=self.start_time.year, missions='lro', versions='latest') self._metakernel = metakernels['data'][0]['path'] return self._metakernel @property def spacecraft_name(self): + """ + Spacecraft name used in various Spice calls to acquire + ephemeris data. + + Returns + ------- + : str + Spacecraft name + """ return "LRO" -class LroPds3Driver(PDS3, LrocSpice): +class LrocPds3Driver(PDS3, LrocSpice): + """ + Driver for reading Lroc labels. Requires a Spice mixin to acquire addtional + ephemeris and instrument data located exclusively in spice kernels. + """ + @property def instrument_id(self): """ + Returns an instrument id for uniquely identifying the instrument, but often + also used to be piped into Spice Kernels to acquire IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + Ignores Wide Angle for now + + Returns + ------- + : str + instrument id """ instrument = self.label.get("INSTRUMENT_ID") diff --git a/ale/drivers/mdis_driver.py b/ale/drivers/mdis_driver.py index 89770feb9eb99448e6acb01d409a3daa8e3f70d5..1091f8a6e01e2b9e040109387dbd29127b69a10d 100644 --- a/ale/drivers/mdis_driver.py +++ b/ale/drivers/mdis_driver.py @@ -11,6 +11,12 @@ from ale.drivers import keys class MdisSpice(Spice, Framer): + """ + MDIS mixin class for defining snowflake Spice calls. Since MDIS has unique + Spice keys, those are defined here as an intermediate mixin for MDIS drivers + that rely on Spice kernels. + """ + id_lookup = { 'MDIS-WAC': 'MSGR_MDIS_WAC', 'MDIS-NAC':'MSGR_MDIS_NAC', @@ -22,6 +28,14 @@ class MdisSpice(Spice, Framer): @property def metakernel(self): + """ + Returns latest instrument metakernels + + Returns + ------- + : string + Path to latest metakernel file + """ metakernel_dir = config.mdis mks = sorted(glob(os.path.join(metakernel_dir,'*.tm'))) if not hasattr(self, '_metakernel'): @@ -33,10 +47,20 @@ class MdisSpice(Spice, Framer): @property def focal_length(self): """ + Computes Focal Length from Kernels + + MDIS has tempature dependant focal lengh and coefficients need to + be acquired from IK Spice kernels (coeff describe focal length as a + function of tempature). Focal plane temps are acquired from a PDS3 label. + + Returns + ------- + : double + focal length in meters """ coeffs = spice.gdpool('INS{}_FL_TEMP_COEFFS '.format(self.fikid), 0, 5) - # reverse coeffs, mdis coeffs are listed a_0, a_1, a_2 ... a_n where + # reverse coeffs, MDIS coeffs are listed a_0, a_1, a_2 ... a_n where # numpy wants them a_n, a_n-1, a_n-2 ... a_0 f_t = np.poly1d(coeffs[::-1]) @@ -45,34 +69,78 @@ class MdisSpice(Spice, Framer): @property def starting_detector_sample(self): + """ + Returns starting detector sample quired from Spice Kernels. + + Returns + ------- + : int + starting detector sample + """ return int(spice.gdpool('INS{}_FPUBIN_START_SAMPLE'.format(self.ikid), 0, 1)[0]) @property def starting_detector_line(self): + """ + Returns starting detector sample acquired from Spice Kernels. + + Returns + ------- + : string + Path to latest metakernel file + """ return int(spice.gdpool('INS{}_FPUBIN_START_LINE'.format(self.ikid), 0, 1)[0]) class MdisPDS3Driver(PDS3, MdisSpice): + """ + Driver for reading MDIS PDS3 labels. Requires a Spice mixin to acquire addtional + ephemeris and instrument data located exclusively in spice kernels. + """ @property 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 IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + + Returns + ------- + : str + instrument id + """ return self.id_lookup[self.label['INSTRUMENT_ID']] 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 + """ + Driver for reading MDIS ISIS3 Labels. These are Labels that have been ingested + into ISIS from PDS EDR images but have not been spiceinit'd yet. + """ @property 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 IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + + Returns + ------- + : str + instrument id + """ return self.id_lookup[self.label['IsisCube']['Instrument']['InstrumentId']] @property def focal_plane_tempature(self): + """ + Acquires focal plane tempature from a PDS3 label. Used exclusively in + computing focal length. + + Returns + ------- + : double + focal plane tempature + """ return self.label['IsisCube']['Instrument']['FocalPlaneTemperature'].value diff --git a/ale/drivers/mro_driver.py b/ale/drivers/mro_driver.py index 870d3eca1d40398e2de673a972edbb7e78a2ade0..2cf539b564460845b2db631472e99c521975286e 100644 --- a/ale/drivers/mro_driver.py +++ b/ale/drivers/mro_driver.py @@ -11,6 +11,9 @@ from ale.drivers import keys class CtxSpice(Spice, LineScanner): + """ + Spice mixins that defines MRO CTX specific snowflake Spice calls. + """ id_lookup = { 'CONTEXT CAMERA':'MRO_CTX' } @@ -19,6 +22,14 @@ class CtxSpice(Spice, LineScanner): @property def metakernel(self): + """ + Returns latest instrument metakernels + + Returns + ------- + : string + Path to latest metakernel file + """ metakernel_dir = config.mro mks = sorted(glob(os.path.join(metakernel_dir,'*.tm'))) if not hasattr(self, '_metakernel'): @@ -30,12 +41,31 @@ class CtxSpice(Spice, LineScanner): class CtxPds3Driver(PDS3, CtxSpice): + """ + Driver for reading CTX PDS3 labels. Requires a Spice mixin to acquire addtional + ephemeris and instrument data located exclusively in spice kernels. + """ + @property def instrument_id(self): + """ + Returns an instrument id for uniquely identifying the instrument, but often + also used to be piped into Spice Kernels to acquire IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + + Returns + ------- + : str + instrument id + """ return self.id_lookup[self.label['INSTRUMENT_NAME']] @property def spacecraft_name(self): + """ + Spacecraft name used in various Spice calls to acquire + ephemeris data. + """ name_lookup = { 'MARS_RECONNAISSANCE_ORBITER': 'MRO' } @@ -43,4 +73,14 @@ class CtxPds3Driver(PDS3, CtxSpice): @property 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 IKIDs. Therefore they + the same ID the Spice expects in bods2c calls. + + Returns + ------- + : str + instrument id + """ return self.id_lookup[self.label['INSTRUMENT_NAME']] diff --git a/src/ale.cpp b/src/ale.cpp index 5427e9203f97a299706cf4f411d53816daeaddb7..5cf73cd190d8562f841b7500e94614449be3e2c0 100644 --- a/src/ale.cpp +++ b/src/ale.cpp @@ -355,11 +355,11 @@ namespace ale { PyObject *pDict = PyModule_GetDict(pModule); // Get the add method from the dictionary. - PyObject *pFunc = PyDict_GetItemString(pDict, "loads"); + PyObject *pFunc = PyDict_GetItemString(pDict, "load"); if(!pFunc) { // import errors do not set a PyError flag, need to use a custom // error message instead. - throw runtime_error("Failed to import ale.loads function from Python." + throw runtime_error("Failed to import ale.load function from Python." "This Usually indicates an error in the Ale Python Library." "Check if Installed correctly and the function ale.loads exists."); } diff --git a/tests/ctests/AleTest.cpp b/tests/ctests/AleTest.cpp index 6131c4e526b74b9331cbb87424338f89160f97a3..c7c5acae62fe36c5aaf6930e1d924d52395fd5b8 100644 --- a/tests/ctests/AleTest.cpp +++ b/tests/ctests/AleTest.cpp @@ -302,7 +302,6 @@ TEST(PyInterfaceTest, LoadInvalidLabel) { EXPECT_THROW(ale::load(label), invalid_argument); } - TEST(AngularVelocityInterpTest, ExampleGetRotation) { vector<double> times = {0, 1}; vector<vector<double>> rots({{0,0}, {1,0}, {0,1}, {0,0}}); diff --git a/tests/pytests/test_lro_drivers.py b/tests/pytests/test_lro_drivers.py index 05234b7d06ab00892a2707a55f1ddd6fd5faa469..56c81a783290db69d1d90f934a5ab1fe5d1afe85 100644 --- a/tests/pytests/test_lro_drivers.py +++ b/tests/pytests/test_lro_drivers.py @@ -5,7 +5,7 @@ import pytest import ale from ale.drivers import lro_driver, base -from ale.drivers.lro_driver import LroPds3Driver +from ale.drivers.lro_driver import LrocPds3Driver from ale import util # 'Mock' the spice module where it is imported @@ -15,7 +15,7 @@ simplespice = SimpleSpice() base.spice = simplespice lro_driver.spice = simplespice -LroPds3Driver.metakernel = get_mockkernels +LrocPds3Driver.metakernel = get_mockkernels @pytest.fixture def lro_lroclabel(): @@ -112,7 +112,7 @@ def lro_lroclabel(): """ def test_lro_creation(lro_lroclabel): - with LroPds3Driver(lro_lroclabel) as m: + with LrocPds3Driver(lro_lroclabel) as m: d = m.to_dict() assert isinstance(d, dict) assert(set(d.keys()) == m.required_keys)