diff --git a/glue/core/state_objects.py b/glue/core/state_objects.py index 149ff43f9..a3750906f 100644 --- a/glue/core/state_objects.py +++ b/glue/core/state_objects.py @@ -8,6 +8,7 @@ from echo import (delay_callback, CallbackProperty, HasCallbackProperties, CallbackList) from glue.core.state import saver, loader +from glue.core.subset import SliceSubsetState from glue.core.component_id import PixelComponentID from glue.core.exceptions import IncompatibleAttribute @@ -288,7 +289,7 @@ class StateAttributeLimitsHelper(StateAttributeCacheHelper): """ values_names = ('lower', 'upper') - modifiers_names = ('log', 'percentile') + modifiers_names = ('log', 'percentile', 'subset_state') def __init__(self, state, attribute, random_subset=10000, margin=0, **kwargs): @@ -296,7 +297,7 @@ def __init__(self, state, attribute, random_subset=10000, margin=0, **kwargs): self.margin = margin self.random_subset = random_subset - self.subset_indices = None + self.subset_state = None if self.attribute is not None: @@ -341,16 +342,20 @@ def update_values(self, force=False, use_default_modifiers=False, **properties): if percentile == 100: lower = self.data.compute_statistic('minimum', cid=self.component_id, finite=True, positive=log, + subset_state = self.subset_state, random_subset=self.random_subset) upper = self.data.compute_statistic('maximum', cid=self.component_id, finite=True, positive=log, + subset_state = self.subset_state, random_subset=self.random_subset) else: lower = self.data.compute_statistic('percentile', cid=self.component_id, percentile=exclude, positive=log, + subset_state = self.subset_state, random_subset=self.random_subset) upper = self.data.compute_statistic('percentile', cid=self.component_id, percentile=100 - exclude, positive=log, + subset_state = self.subset_state, random_subset=self.random_subset) if not isinstance(lower, np.datetime64) and np.isnan(lower): @@ -375,6 +380,9 @@ def update_values(self, force=False, use_default_modifiers=False, **properties): def flip_limits(self): self.set(lower=self.upper, upper=self.lower) + def set_slice(self, slices): + self.set(subset_state=None if slices is None else SliceSubsetState(self.data, slices)) + class StateAttributeSingleValueHelper(StateAttributeCacheHelper): diff --git a/glue/core/tests/test_state_objects.py b/glue/core/tests/test_state_objects.py index 163d64f55..2b2ed2c09 100644 --- a/glue/core/tests/test_state_objects.py +++ b/glue/core/tests/test_state_objects.py @@ -4,6 +4,7 @@ from echo import CallbackProperty, ListCallbackProperty from glue.core import Data, DataCollection +from glue.core.subset import SliceSubsetState from .test_state import clone from ..state_objects import (State, StateAttributeLimitsHelper, @@ -182,6 +183,35 @@ def test_manual_edit(self): assert self.helper.upper == 234 assert self.helper.log + def test_subset(self): + + # Set subset to compute limits from + self.helper.subset_state = SliceSubsetState(self.helper.data, [slice(6000)]) + self.helper.percentile = 100 + # self.helper.update_values() + assert_allclose(self.helper.lower, -100) + assert_allclose(self.helper.upper, 19.992) + self.helper.percentile = 90 + assert_allclose(self.helper.lower, -94.0004) + assert_allclose(self.helper.upper, 13.9924) + + def test_slice(self): + + # Set subset to compute limits from slice + self.helper.set_slice([slice(2000, 8000)]) + self.helper.percentile = 100 + # self.helper.update_values() + assert_allclose(self.helper.lower, -59.996) + assert_allclose(self.helper.upper, 59.996) + self.helper.percentile = 90 + assert_allclose(self.helper.lower, -53.9964) + assert_allclose(self.helper.upper, 53.9964) + + self.helper.set_slice(None) + self.helper.percentile = 95 + assert_allclose(self.helper.lower, -95) + assert_allclose(self.helper.upper, 95) + class TestStateAttributeSingleValueHelper(): diff --git a/glue/viewers/image/state.py b/glue/viewers/image/state.py index 45f29dba0..e53c6719f 100644 --- a/glue/viewers/image/state.py +++ b/glue/viewers/image/state.py @@ -572,6 +572,19 @@ def flip_limits(self): """ self.attribute_lim_helper.flip_limits() + def set_slice(self, slices): + """ + Select a subset slice for determining image levels. + + Parameters + ---------- + slices : iterable of :class:`slice` or `None` + An iterable containing :class:`slice` objects that can instantiate + a :class:`SliceSubsetState` and has to be consistent with the + shape of `self.data`; `None` to unslice. + """ + self.attribute_lim_helper.set_slice(slices) + def reset_contrast_bias(self): with delay_callback(self, 'contrast', 'bias'): self.contrast = 1