diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e482cce0..24cd6926f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,8 +48,10 @@ release. - ISIS-SPICE and NAIF-SPICE drivers: subtract 0.5 from the detector center (ISIS 0.5-based to CSM 0-based). - NAIF-SPICE driver: `focal_length` no longer raises `AttributeError` when `INS_FOCAL_LENGTH` is defined. - NAIF-SPICE driver: `ephemeris_start_time` uses the scalar from `utcToEt` (was a tuple). [#739](https://github.com/DOI-USGS/ale/pull/739) +- `isd_generate` now fails with an error instead of writing an ISD with null required fields. [#754](https://github.com/DOI-USGS/ale/pull/754) - Fixed kernel quality check to allow quality keys from different missions [#758](https://github.com/DOI-USGS/ale/pull/758) + ### Changed - `isd_generate`: default `--reduction` to `linear` (sampling every 10th line for line-scan images with >= 1000 lines). This avoids creating oversized ISDs (tens of thousands of pose samples) for long line-scan sensors such as LRO NAC, MRO CTX, and HRSC. Pass `--reduction none` to disable reduction and restore per-line sampling. [#744](https://github.com/DOI-USGS/ale/pull/744) diff --git a/ale/base/__init__.py b/ale/base/__init__.py index a03106b2f..fe4719a15 100644 --- a/ale/base/__init__.py +++ b/ale/base/__init__.py @@ -12,6 +12,14 @@ class WrongLabelTypeException(Exception): """ pass +class InvalidISDError(Exception): + """ + Exception raised when a driver matches the label but the generated ISD is + invalid, for example a required field is null. Unlike a wrong-driver + exception, this is not retried against other drivers. + """ + pass + spiceql_mission_map = { "APOLLO_METRIC" : "apollo15_metric", diff --git a/ale/drivers/__init__.py b/ale/drivers/__init__.py index b5cc32f68..8e440e5f8 100644 --- a/ale/drivers/__init__.py +++ b/ale/drivers/__init__.py @@ -11,7 +11,7 @@ import os import os.path import traceback -from ale.base import WrongInstrumentException, WrongLabelTypeException +from ale.base import WrongInstrumentException, WrongLabelTypeException, InvalidISDError import logging from ale.formatters.formatter import to_isd @@ -197,6 +197,7 @@ def load_from_label(label, props={}, formatter='ale', verbose=False, only_isis_s parsed_label = pre_parse_label(label) + invalid_isd_error = None for driver in drivers: if verbose: logger.info(f'Trying {driver.__name__}') @@ -220,6 +221,13 @@ def load_from_label(label, props={}, formatter='ale', verbose=False, only_isis_s except WrongInstrumentException as e: if verbose: logger.info(f"Wrong instrument id for driver {driver.__class__.__name__}: {e}. Skipping driver.") + except InvalidISDError as e: + # the driver produced an invalid ISD (a required field was null). + # Keep trying other drivers, but remember this so that if none + # succeed we surface this real error instead of a generic one. + invalid_isd_error = e + if verbose: + logger.info(f"Invalid ISD from driver {driver.__class__.__name__}: {e}. Skipping driver.") except Exception as e: if verbose: traceback.print_exc() @@ -227,6 +235,8 @@ def load_from_label(label, props={}, formatter='ale', verbose=False, only_isis_s logger.info(f'End {driver}\n') if verbose: logger.setLevel(logger_level) + if invalid_isd_error is not None: + raise invalid_isd_error raise Exception('No viable Driver for Label.') diff --git a/ale/formatters/formatter.py b/ale/formatters/formatter.py index 7c176aa34..00f0bfc4e 100644 --- a/ale/formatters/formatter.py +++ b/ale/formatters/formatter.py @@ -5,6 +5,7 @@ import logging from ale.base.type_sensor import LineScanner, Framer, Radar, PushFrame +from ale.base import InvalidISDError from ale import logger def to_isd(driver): @@ -170,6 +171,14 @@ def to_isd(driver): isd['starting_detector_line'] = driver_data["detector_start_line"] isd['starting_detector_sample'] = driver_data["detector_start_sample"] + # these interior-orientation fields are required for any CSM model + io_required = ['focal_length', 'detector_center_line', 'detector_center_sample', + 'focal2pixel_lines', 'focal2pixel_samples'] + io_null = [k for k in io_required if driver_data.get(k) is None] + if io_null: + raise InvalidISDError("Could not generate a valid ISD. Required field(s) " + "are null: " + ", ".join(io_null)) + j2000_rotation = frame_chain.compute_rotation(target_frame, J2000) instrument_position = {}