From 4c7ad530bffa009050a2b631fb9e63ac52e3e3ba Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 4 Apr 2019 09:49:33 -0700 Subject: [PATCH] added Kaguya TC driver --- ale/drivers/__init__.py | 28 +++-- ale/drivers/base.py | 26 ++--- ale/drivers/kaguya_driver.py | 96 ++++++++++++++++ ale/drivers/mdis_driver.py | 3 + tests/pytests/conftest.py | 2 + tests/pytests/test_kaguya_drivers.py | 163 +++++++++++++++++++++++++++ 6 files changed, 292 insertions(+), 26 deletions(-) create mode 100644 ale/drivers/kaguya_driver.py create mode 100644 tests/pytests/test_kaguya_drivers.py diff --git a/ale/drivers/__init__.py b/ale/drivers/__init__.py index 02614b2..b2eaf5b 100644 --- a/ale/drivers/__init__.py +++ b/ale/drivers/__init__.py @@ -22,6 +22,22 @@ __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__)) +class JsonEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + if isinstance(obj, np.int64): + return int(obj) + if isinstance(obj, datetime.datetime): + return obj.__str__() + if isinstance(obj, bytes): + return obj.decode("utf-8") + if isinstance(obj, pvl.PVLModule): + return pvl.dumps(obj) + if isinstance(obj, set): + return list(obj) + return json.JSONEncoder.default(self, obj) + def load(label): """ @@ -35,7 +51,7 @@ def load(label): for name, driver in drivers.items(): print("Trying:", name) try: - res = driver(label, *args, **kwargs) + res = driver(label) if res.is_valid(): with res as r: return res.to_dict() @@ -47,15 +63,5 @@ def load(label): def loads(label): - class JsonEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.ndarray): - return obj.tolist() - if isinstance(obj, np.int64): - return int(obj) - if isinstance(obj, datetime.datetime): - return obj.__str__() - return json.JSONEncoder.default(self, obj) - res = load(label) return json.dumps(res, cls=JsonEncoder) diff --git a/ale/drivers/base.py b/ale/drivers/base.py index 358ffa8..7e67877 100644 --- a/ale/drivers/base.py +++ b/ale/drivers/base.py @@ -480,10 +480,7 @@ class PDS3(): @property def line_exposure_duration(self): - try: - return self.label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds - except: - return np.nan + return self.label['LINE_EXPOSURE_DURATION'].value * 0.001 # Scale to seconds @property def instrument_id(self): @@ -531,13 +528,6 @@ class PDS3(): 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) @@ -818,10 +808,6 @@ class Isis3(): """ 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): """ @@ -1283,3 +1269,13 @@ class RadialDistortion(): "coefficients" : self._odtk } } + +class TransverseDistortion(): + @property + def optical_distortion(self): + return { + "Transverse": { + "x" : self._odtx, + "y" : self._odty + } + } diff --git a/ale/drivers/kaguya_driver.py b/ale/drivers/kaguya_driver.py new file mode 100644 index 0000000..64525ce --- /dev/null +++ b/ale/drivers/kaguya_driver.py @@ -0,0 +1,96 @@ +import spiceypy as spice +import os +import pvl +import ale + +import numpy as np +from ale.drivers.base import Driver, LineScanner, PDS3, Spice, TransverseDistortion + + +class TcPds3Driver(Driver, LineScanner, PDS3, Spice, TransverseDistortion): + @property + def instrument_id(self): + instrument = self.label.get("INSTRUMENT_ID") + if instrument == "TC1": + return "LISM_TC1" + elif instrument == "TC2": + return "LISM_TC2" + + + @property + def metakernel(self): + self._metakernels = 'mk_kaguyatc.tm' + return self._metakernels + + + @property + def starting_ephemeris_time(self): + if not hasattr(self, '_starting_ephemeris_time'): + self._starting_ephemeris_time = self.label.get('CORRECTED_SC_CLOCK_START_COUNT').value + self._starting_ephemeris_time = spice.sct2e(self.spacecraft_id, self._starting_ephemeris_time) + return self._starting_ephemeris_time + + + @property + def _detector_center_line(self): + return 1 + + @property + def _detector_center_sample(self): + return spice.gdpool('INS{}_CENTER'.format(self.ikid), 0, 2)[0] + + + @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("J2000", + 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 focal2pixel_lines(self): + """ + Calculated using pixel pitch and 1/pixel pitch + """ + return [0, 0, 142.857142857] + + + @property + def focal2pixel_samples(self): + """ + Calculated using pixel pitch and 1/pixel pitch + """ + return [0, 142.857142857, 0] + + + @property + def _odtx(self): + """ + Returns + ------- + : list + Optical distortion x coefficients + """ + return spice.gdpool('INS{}_DISTORTION_COEF_X'.format(self.ikid),0, 4).tolist() + + + @property + def _odty(self): + """ + Returns + ------- + : list + Optical distortion y coefficients + """ + return spice.gdpool('INS{}_DISTORTION_COEF_Y'.format(self.ikid), 0, 4).tolist() diff --git a/ale/drivers/mdis_driver.py b/ale/drivers/mdis_driver.py index 4453980..b97a92e 100644 --- a/ale/drivers/mdis_driver.py +++ b/ale/drivers/mdis_driver.py @@ -109,6 +109,9 @@ class MdisPDS3Driver(PDS3, MdisSpice): """ return self.id_lookup[self.label['INSTRUMENT_ID']] + @property + def line_exposure_duration(self): + return self.label["EXPOSURE_DURATION"].value * 0.001 class MdisIsis3Driver(Isis3, MdisSpice): """ diff --git a/tests/pytests/conftest.py b/tests/pytests/conftest.py index c3a3b1d..5a42b28 100644 --- a/tests/pytests/conftest.py +++ b/tests/pytests/conftest.py @@ -23,6 +23,8 @@ class SimpleSpice(): return np.asarray([1,2,3,4]) def bodn2c(self, *args): return "SPACE" + def sct2e(self, *args): + return 0.1 def get_mockkernels(self, *args): return "some_metakernel" diff --git a/tests/pytests/test_kaguya_drivers.py b/tests/pytests/test_kaguya_drivers.py new file mode 100644 index 0000000..026ccfd --- /dev/null +++ b/tests/pytests/test_kaguya_drivers.py @@ -0,0 +1,163 @@ +import pytest + +from ale.drivers import kaguya_driver +from ale.drivers.kaguya_driver import TcPds3Driver + +# 'Mock' the spice module where it is imported +from conftest import SimpleSpice, get_mockkernels + +simplespice = SimpleSpice() +kaguya_driver.spice = simplespice + +TcPds3Driver.metakernel = get_mockkernels + +@pytest.fixture +def kaguya_tclabel(): + return """ + PDS_VERSION_ID = "PDS3" + /*** FILE FORMAT ***/ + RECORD_TYPE = "UNDEFINED" + FILE_NAME = "TC1W2B0_01_00296N081E2387.img" + PRODUCT_ID = "TC1W2B0_01_00296N081E2387" + DATA_FORMAT = "PDS" + + /*** POINTERS TO START BYTE OFFSET OF OBJECTS IN FILE ***/ + ^IMAGE = 7609 + + /*** GENERAL DATA DESCRIPTION PARAMETERS ***/ + SOFTWARE_NAME = "RGC_TC_w_Level2B0 (based on RGC_TC_MI version 2.10.1)" + SOFTWARE_VERSION = "1.0.0" + PROCESS_VERSION_ID = "L2B" + PRODUCT_CREATION_TIME = 2013-07-16T20:12:53Z + PROGRAM_START_TIME = 2013-07-16T20:08:01Z + PRODUCER_ID = "LISM" + PRODUCT_SET_ID = "TC_w_Level2B0" + PRODUCT_VERSION_ID = "01" + REGISTERED_PRODUCT = "Y" + ILLUMINATION_CONDITION = "MORNING" + LEVEL2A_FILE_NAME = "TC1W2A0_02TSF00296_001_0022.img" + SPICE_METAKERNEL_FILE_NAME = "RGC_INF_TCv401IK_MIv200IK_SPv105IK_RISE100h_02_LongCK_D_V02_de421_110706.mk" + + /*** SCENE RELATED PARAMETERS ***/ + MISSION_NAME = "SELENE" + SPACECRAFT_NAME = "SELENE-M" + DATA_SET_ID = "TC1_Level2B" + INSTRUMENT_NAME = "Terrain Camera 1" + INSTRUMENT_ID = "TC1" + MISSION_PHASE_NAME = "InitialCheckout" + REVOLUTION_NUMBER = 296 + STRIP_SEQUENCE_NUMBER = 1 + SCENE_SEQUENCE_NUMBER = 22 + UPPER_LEFT_DAYTIME_FLAG = "Day" + UPPER_RIGHT_DAYTIME_FLAG = "Day" + LOWER_LEFT_DAYTIME_FLAG = "Day" + LOWER_RIGHT_DAYTIME_FLAG = "Day" + TARGET_NAME = "MOON" + OBSERVATION_MODE_ID = "NORMAL" + SENSOR_DESCRIPTION = "Imagery type:Pushbroom. ImageryMode:Mono,Stereo. ExposureTimeMode:Long,Middle,Short. CompressionMode:NonComp,DCT. Q-table:32 patterns. H-table:4 patterns. SwathMode:F(Full),N(Nominal),H(Half). First pixel number:1(F),297(N),1172(H)." + SENSOR_DESCRIPTION2 = "Pixel size:7x7[micron^2](TC1/TC2). Wavelength range:430-850[nm](TC1/TC2). A/D rate:10[bit](TC1/TC2). Slant angle:+/-15[degree] (from nadir to +x of S/C)(TC1/TC2). Focal length:72.45/72.63[mm](TC1/TC2). F number:3.97/3.98(TC1/TC2)." + DETECTOR_STATUS = {"TC1:ON","TC2:ON","MV:OFF","MN:OFF","SP:ON"} + EXPOSURE_MODE_ID = "SHORT" + LINE_EXPOSURE_DURATION = 1.625000 + SPACECRAFT_CLOCK_START_COUNT = 878074165.1875 + SPACECRAFT_CLOCK_STOP_COUNT = 878074195.4450 + CORRECTED_SC_CLOCK_START_COUNT = 878074165.186621 + CORRECTED_SC_CLOCK_STOP_COUNT = 878074195.443901 + START_TIME = 2007-11-02T21:29:27.123714Z + STOP_TIME = 2007-11-02T21:29:57.381214Z + CORRECTED_START_TIME = 2007-11-02T21:29:27.122835Z + CORRECTED_STOP_TIME = 2007-11-02T21:29:57.380115Z + LINE_SAMPLING_INTERVAL = 6.500000 + CORRECTED_SAMPLING_INTERVAL = 6.499953 + UPPER_LEFT_LATITUDE = 7.290785 + UPPER_LEFT_LONGITUDE = 238.410490 + UPPER_RIGHT_LATITUDE = 7.288232 + UPPER_RIGHT_LONGITUDE = 238.991705 + LOWER_LEFT_LATITUDE = 8.820028 + LOWER_LEFT_LONGITUDE = 238.417311 + LOWER_RIGHT_LATITUDE = 8.817605 + LOWER_RIGHT_LONGITUDE = 238.999370 + LOCATION_FLAG = "A" + ROLL_CANT = "NO" + SCENE_CENTER_LATITUDE = 8.053752 + SCENE_CENTER_LONGITUDE = 238.704621 + INCIDENCE_ANGLE = 28.687 + EMISSION_ANGLE = 17.950 + PHASE_ANGLE = 31.600 + SOLAR_AZIMUTH_ANGLE = 108.126 + FOCAL_PLANE_TEMPERATURE = 18.85 + TELESCOPE_TEMPERATURE = 18.59 + SATELLITE_MOVING_DIRECTION = "-1" + FIRST_SAMPLED_LINE_POSITION = "UPPERMOST" + FIRST_DETECTOR_ELEMENT_POSITION = "LEFT" + A_AXIS_RADIUS = 1737.400 + B_AXIS_RADIUS = 1737.400 + C_AXIS_RADIUS = 1737.400 + DEFECT_PIXEL_POSITION = "N/A" + + /*** CAMERA RELATED PARAMETERS ***/ + SWATH_MODE_ID = "FULL" + FIRST_PIXEL_NUMBER = 1 + LAST_PIXEL_NUMBER = 1600 + SPACECRAFT_ALTITUDE = 108.719 + SPACECRAFT_GROUND_SPEED = 1.530 + TC1_TELESCOPE_TEMPERATURE = 18.70 + TC2_TELESCOPE_TEMPERATURE = 18.70 + DPU_TEMPERATURE = 14.60 + TM_TEMPERATURE = 18.01 + TM_RADIATOR_TEMPERATURE = 17.67 + Q_TABLE_ID = "N/A" + HUFFMAN_TABLE_ID = "N/A" + DATA_COMPRESSION_PERCENT_MEAN = 100.0 + DATA_COMPRESSION_PERCENT_MAX = 100.0 + DATA_COMPRESSION_PERCENT_MIN = 100.0 + + /*** DESCRIPTION OF OBJECTS CONTAINED IN THE FILE ***/ + + OBJECT = IMAGE + ENCODING_TYPE = "N/A" + ENCODING_COMPRESSION_PERCENT = 100.0 + NOMINAL_LINE_NUMBER = 4088 + NOMINAL_OVERLAP_LINE_NUMBER = 568 + OVERLAP_LINE_NUMBER = 568 + LINES = 4656 + LINE_SAMPLES = 1600 + SAMPLE_TYPE = "MSB_INTEGER" + SAMPLE_BITS = 16 + IMAGE_VALUE_TYPE = "RADIANCE" + UNIT = "W/m**2/micron/sr" + SCALING_FACTOR = 1.30000e-02 + OFFSET = 0.00000e+00 + MIN_FOR_STATISTICAL_EVALUATION = 0 + MAX_FOR_STATISTICAL_EVALUATION = 32767 + SCENE_MAXIMUM_DN = 7602 + SCENE_MINIMUM_DN = 1993 + SCENE_AVERAGE_DN = 2888.6 + SCENE_STDEV_DN = 370.2 + SCENE_MODE_DN = 2682 + SHADOWED_AREA_MINIMUM = 0 + SHADOWED_AREA_MAXIMUM = 0 + SHADOWED_AREA_PERCENTAGE = 0 + INVALID_TYPE = ("SATURATION" , "MINUS" , "DUMMY_DEFECT" , "OTHER") + INVALID_VALUE = (-20000 , -21000 , -22000 , -23000) + INVALID_PIXELS = (0 , 0 , 0 , 0) + END_OBJECT = IMAGE + + OBJECT = PROCESSING_PARAMETERS + DARK_FILE_NAME = "TC1_DRK_00293_02951_S_N_b05.csv" + FLAT_FILE_NAME = "TC1_FLT_00293_04739_N_N_b05.csv" + EFFIC_FILE_NAME = "TC1_EFF_PRFLT_N_N_v01.csv" + NONLIN_FILE_NAME = "TC1_NLT_PRFLT_N_N_v01.csv" + RAD_CNV_COEF = 3.790009 + L2A_DEAD_PIXEL_THRESHOLD = 30 + L2A_SATURATION_THRESHOLD = 1023 + DARK_VALID_MINIMUM = -5 + RADIANCE_SATURATION_THRESHOLD = 425.971000 + END_OBJECT = PROCESSING_PARAMETERS + END + """ + +def test_kaguya_creation(kaguya_tclabel): + with TcPds3Driver(kaguya_tclabel) as m: + d = m.to_dict() + assert isinstance(d, dict) -- GitLab