Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ release.

### 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)

- Change the `linear` reduction option to apply to all drivers rather than just drivers built with the `LineScanner` mixin. [#752](https://github.com/DOI-USGS/ale/pull/752)
## [1.3.0] - 2026-08-25

### Added
Expand Down
64 changes: 58 additions & 6 deletions ale/base/data_naif.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@
from ale import util
from ale import ale_c

def reduce_ephem(ephem_times, sample_rate, minimum=101):
"""
Stand alone function to linearly reduce ephemeris times

Parameters
----------
ephem_times : list
A list of ephemeris times to reduce over

sample_rate : int
The rate of sampling between the first and last time in
ephem_times

minimum : int
The minimum number of ephemeris times to not reduce
below
"""
reduced = len(ephem_times) // sample_rate + 1
# Add check to avoid reducing below the minimum
if reduced < minimum:
logger.warning(f"Requested sampling of {sample_rate} would reduce total samples ({reduced}) below {minimum}. "
f"Setting minimum sample size to {minimum}")
reduced = minimum
reduced_times = np.linspace(ephem_times[0], ephem_times[-1], reduced)
return reduced_times

class NaifSpice():
"""
Mix-in for reading data from NAIF SPICE Kernels.
Expand Down Expand Up @@ -193,7 +219,28 @@ def search_kernels(self):
if self.use_web:
self._search_kernels = True
return self._search_kernels


@property
def reduction(self):
"""
Reduction property that reads a provided reduction method in the properties.

If no reduction is provided, or an unknown reduction is set reduction
will be set to 'none' (no reduction)

Returns
-------
: str
The checked and defined reduction
"""
if not hasattr(self, "_reduction"):
self._reduction = self._props.get('reduction', 'none').lower()
reduction_types = ["linear", "hermite", "none"]
if self._reduction not in reduction_types:
logger.warning(f"Unknown reduction value {self._reduction}, setting reduction to none")
self._reduction = "none"
return self._reduction

@property
def light_time_correction(self):
"""
Expand Down Expand Up @@ -543,7 +590,10 @@ def sensor_position(self):
not hasattr(self, '_velocity') or \
not hasattr(self, '_ephem'):
logger.debug("Generating Sensor Positions")
rate = self._props.get('ephem_sample_rate', 10)
ephem = self.ephemeris_time
if self.reduction == 'linear' and len(ephem) != 1:
ephemeris_time = reduce_ephem(ephem, rate)

pos = []
vel = []
Expand Down Expand Up @@ -652,8 +702,7 @@ def sensor_position(self):
self._velocity = np.asarray(vel)
self._ephem = ephem

reduction = self._props.get('reduction', 'none').lower()
if reduction == 'hermite' and len(self._position) > 3:
if self.reduction == 'hermite' and len(self._position) > 3:
logger.debug("Applying hermite reduction to positions")
state_list = [ale_c.State(i) for i in np.append(self._position, self._velocity, axis=1)]
states = ale_c.StatesFromStateVec(self._ephem, state_list, 1)
Expand Down Expand Up @@ -710,13 +759,16 @@ def frame_chain(self):
if not hasattr(self, '_frame_chain'):
nadir = self._props.get('nadir', False)
exact_ck_times = self._props.get('exact_ck_times', True)
reduction = self._props.get('reduction', 'none').lower()
rate = self._props.get('ephem_sample_rate', 10)
ephemeris_time = self.ephemeris_time
if self.reduction == 'linear' and len(ephemeris_time) != 1:
ephemeris_time = reduce_ephem(ephemeris_time, rate)
self._frame_chain = FrameChain.from_spice(sensor_frame=self.sensor_frame_id,
target_frame=self.target_frame_id,
center_ephemeris_time=self.center_ephemeris_time,
ephemeris_times=self.ephemeris_time,
ephemeris_times=ephemeris_time,
nadir=nadir, exact_ck_times=exact_ck_times,
reduction=reduction,
reduction=self.reduction,
inst_time_bias=self.instrument_time_bias,
mission=self.spiceql_mission,
use_web=self.use_web,
Expand Down
18 changes: 1 addition & 17 deletions ale/base/type_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,7 @@ def ephemeris_time(self):
ephemeris times split based on image lines
"""
if not hasattr(self, "_ephemeris_time"):
# Determine reduction mode. Default is "None" (No reduction).
# Set reduction=Linear via props to apply a linear reduction.
reduction = self._props.get('reduction', 'none').lower()

if reduction == 'linear':
# Sample at most every Nth line to keep ISD file size in check
# for large linescan images (e.g., 177k lines for Chandrayaan-2
# TMC). For images under ~1000 lines, keep one sample per line
# to avoid reducing below 100 samples.
rate = self._props.get('ephem_sample_rate', 10)
reduced = self.image_lines // rate + 1
num_samples = reduced if reduced >= 100 else self.image_lines + 1
else:
# No reduction
num_samples = self.image_lines + 1

self._ephemeris_time = np.linspace(self.ephemeris_start_time, self.ephemeris_stop_time, num_samples)
self._ephemeris_time = np.linspace(self.ephemeris_start_time, self.ephemeris_stop_time, self.image_lines + 1)
return self._ephemeris_time

@property
Expand Down
36 changes: 16 additions & 20 deletions ale/drivers/chandrayaan_drivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ def sensor_frame_id(self):
return self.original_naif_sensor_frame_id - 10

class Chandrayaan2OHRCIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):

@property
def instrument_id(self):
"""
Expand Down Expand Up @@ -726,29 +726,25 @@ def ephemeris_stop_time(self):
return self._ephemeris_stop_time

@property
def ephemeris_time(self):
def reduction(self):
"""
Forces a reduced set of ephemeris data for chandrayaan2 as
many of the images are nearly 200K lines long resulting in
ISDs that cannot be loaded correctly into the CSM
Reduction property override that sets the default reduction to linear.

Returns
-------
: ndarray
ephemeris times split based on image lines and ephem_sample_rate passed
through props
"""
if not hasattr(self, "_ephemeris_time"):
reduction = self._props.get('reduction', 'none').lower()
if (reduction == 'none'):
logger.warning("Chandrayaan-2 OHRC: per-line ephemeris ISDs are "
"very large. Defaulting reduction to 'linear'. Pass "
"--reduction linear --ephem_sample_rate N to control "
"the sampling. An explicit --reduction none cannot be "
"respected for this sensor.")
self._props['reduction'] = 'linear'
self._ephemeris_time = super().ephemeris_time
return self._ephemeris_time
: str
The checked and defined reduction
"""
if not hasattr(self, "_reduction"):
self._reduction = super().reduction
if self._reduction == "none":
self._reduction = "linear"
logger.warning("Chandrayaan-2 OHRC: per-line ephemeris ISDs are "
"very large. Defaulting reduction to 'linear'. Pass "
"--reduction linear --ephem_sample_rate N to control "
"the sampling. An explicit --reduction none cannot be "
"respected for this sensor.")
return self._reduction

@property
def detector_center_line(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/pytests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def compare_dicts(ldict, rdict):
differences.extend(compare_dicts(item, rdict[key]))
elif isinstance(item, np.ndarray) or isinstance(item, list):
if len(item) != len(rdict[key]):
differences.append(f'Array sizes of key {key} are not equal {item} : {rdict[key]}.')
differences.append(f'Array sizes of key {key} are not equal {len(item)} : {len(rdict[key])}.')
else:
list_item = np.array(item).flat[0]
# quaternion (q) and its additive inverse (qi) yield the same rotation. Equivalent quaternions should pass
Expand Down
9 changes: 3 additions & 6 deletions tests/pytests/test_chandrayaan_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ def setUp(self):
label = get_image_label('ch2_ohr_nrp_20200827T0226453039_d_img_d18', 'isis3')
self.driver = Chandrayaan2OHRCIsisLabelNaifSpiceDriver(label)

def test_default_reduction(self):
assert self.driver.reduction == "linear"

def test_short_mission_name(self):
assert self.driver.short_mission_name == 'chandrayaan'

Expand Down Expand Up @@ -285,12 +288,6 @@ def test_ephemeris_stop_time(self):
calls = [call(utc='2020-08-27 02:27:01.687500', searchKernels=False, useWeb=False)]
utcToEt.assert_has_calls(calls)

def test_ephemeris_time(self):
with patch("ale.drivers.chandrayaan_drivers.pyspiceql.utcToEt", return_value=[12345]) as utcToEt:
assert len(self.driver.ephemeris_time) == 10108
calls = [call(utc='2020-08-27 02:27:01.687500', searchKernels=False, useWeb=False)]
utcToEt.assert_has_calls(calls)

def test_detector_center_sample(self):
with patch.object(Chandrayaan2OHRCIsisLabelNaifSpiceDriver, 'naif_keywords', new_callable=PropertyMock) as naif_keywords_call:
naif_keywords_call.return_value = {"INS-152270_CENTER": [2003.1, 3003.2]}
Expand Down
3 changes: 3 additions & 0 deletions tests/pytests/test_data_naif.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def test_detector_center_line(self):
def test_swap_observer_target(self):
assert not self.driver.swap_observer_target

def test_reduction(self):
assert self.driver.reduction == "none"

def test_light_time_correction(self):
assert self.driver.light_time_correction == "LT+S"

Expand Down
Loading