diff --git a/.gitignore b/.gitignore
index e797cbaa7..bd76c5cec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,5 @@ glue/qt/glue_qt_resources.py
.vscode
# vscode plugin
.history
+
+.qt_for_python/
diff --git a/glue/app/qt/layer_tree_widget.py b/glue/app/qt/layer_tree_widget.py
index f170434ac..4a9d482f6 100644
--- a/glue/app/qt/layer_tree_widget.py
+++ b/glue/app/qt/layer_tree_widget.py
@@ -197,14 +197,14 @@ class DeleteAction(LayerAction):
def _can_trigger(self):
selection = self.selected_layers()
- return all(isinstance(s, (core.Data, core.SubsetGroup))
+ return all(isinstance(s, (core.BaseData, core.SubsetGroup))
for s in selection)
def _do_action(self):
assert self._can_trigger()
selection = self.selected_layers()
for s in selection:
- if isinstance(s, core.Data):
+ if isinstance(s, core.BaseData):
self._layer_tree.data_collection.remove(s)
else:
assert isinstance(s, core.SubsetGroup)
diff --git a/glue/core/data.py b/glue/core/data.py
index 92e7bdd4e..397066730 100644
--- a/glue/core/data.py
+++ b/glue/core/data.py
@@ -241,7 +241,7 @@ def add_subset(self, subset, label=None):
if subset.data is not self:
subset.do_broadcast(False)
subset.data = self
- subset.label = subset.label # hacky. disambiguates name if needed
+ subset.label = subset.label # hacky, disambiguates name if needed
if self.hub is not None:
msg = SubsetCreateMessage(subset)
@@ -1659,7 +1659,7 @@ def compute_statistic(self, statistic, cid, subset_state=None, axis=None,
# from glue.core.link_manager import pixel_cid_to_pixel_cid_matrix
# for att in subset_state.attributes:
# # TODO: in principle we cold still deal with non-pixel
- # # componnet IDs, so this should be fixed.
+ # # component IDs, so this should be fixed.
# if not isinstance(att, PixelComponentID):
# break
# matrix = pixel_cid_to_pixel_cid_matrix(att.parent, self)
diff --git a/glue/utils/wcs.py b/glue/utils/wcs.py
new file mode 100644
index 000000000..3c2c6d368
--- /dev/null
+++ b/glue/utils/wcs.py
@@ -0,0 +1,23 @@
+from astropy.wcs import WCS
+
+
+def get_identity_wcs(naxis):
+ """
+ Create a WCS object with an identity transform.
+
+ Parameters
+ ----------
+ naxis : float
+ The number of axes.
+
+ Returns
+ -------
+ `astropy.wcs.WCS`
+ A WCS object with an identity transform.
+ """
+ wcs = WCS(naxis=naxis)
+ wcs.wcs.ctype = ['X'] * naxis
+ wcs.wcs.crval = [0.] * naxis
+ wcs.wcs.crpix = [1.] * naxis
+ wcs.wcs.cdelt = [1.] * naxis
+ return wcs
diff --git a/glue/viewers/image/qt/options_widget.py b/glue/viewers/image/qt/options_widget.py
index 3a005f3bc..4d93d19a1 100644
--- a/glue/viewers/image/qt/options_widget.py
+++ b/glue/viewers/image/qt/options_widget.py
@@ -28,10 +28,12 @@ def __init__(self, viewer_state, session, parent=None):
self.viewer_state = viewer_state
+ self.session = session
+
self.slice_helper = MultiSliceWidgetHelper(viewer_state=self.viewer_state,
+ session=self.session,
layout=self.ui.layout_slices)
- self.session = session
self.ui.axes_editor.button_apply_all.clicked.connect(self._apply_all_viewers)
def _apply_all_viewers(self):
diff --git a/glue/viewers/image/qt/profile_viewer_tool.py b/glue/viewers/image/qt/profile_viewer_tool.py
index 48b110aab..9f6d5f37f 100644
--- a/glue/viewers/image/qt/profile_viewer_tool.py
+++ b/glue/viewers/image/qt/profile_viewer_tool.py
@@ -65,7 +65,6 @@ def activate(self):
# be a pixel attribute or world attribute depending on what information
# is available in the coordinates, so we need to be careful about that.
- x_att = profile_viewer.state.x_att
reference_data = self.viewer.state.reference_data
if isinstance(profile_viewer.state.x_att, PixelComponentID):
diff --git a/glue/viewers/image/qt/slice_widget.py b/glue/viewers/image/qt/slice_widget.py
index 3fc80910b..8223cae7c 100644
--- a/glue/viewers/image/qt/slice_widget.py
+++ b/glue/viewers/image/qt/slice_widget.py
@@ -11,7 +11,7 @@
class MultiSliceWidgetHelper(object):
- def __init__(self, viewer_state=None, layout=None):
+ def __init__(self, *args, viewer_state=None, layout=None, **kwargs):
self.viewer_state = viewer_state
@@ -56,6 +56,7 @@ def sync_state_from_sliders(self, *args):
else:
slices.append(self.viewer_state.slices[i])
self.viewer_state.slices = tuple(slices)
+ self._reference_data = self.viewer_state.reference_data
@avoid_circular
def sync_sliders_from_state(self, *args):
diff --git a/glue/viewers/image/viewer.py b/glue/viewers/image/viewer.py
index 70e01ce97..1aa58e086 100644
--- a/glue/viewers/image/viewer.py
+++ b/glue/viewers/image/viewer.py
@@ -1,7 +1,5 @@
import os
-from astropy.wcs import WCS
-
from glue.core.subset import roi_to_subset_state
from glue.core.coordinates import Coordinates, LegacyCoordinates
from glue.core.coordinate_helpers import dependent_axes
@@ -12,21 +10,11 @@
from glue.viewers.image.frb_artist import imshow
from glue.viewers.image.composite_array import CompositeArray
+from glue.utils.wcs import get_identity_wcs
__all__ = ['MatplotlibImageMixin']
-def get_identity_wcs(naxis):
-
- wcs = WCS(naxis=naxis)
- wcs.wcs.ctype = ['X'] * naxis
- wcs.wcs.crval = [0.] * naxis
- wcs.wcs.crpix = [1.] * naxis
- wcs.wcs.cdelt = [1.] * naxis
-
- return wcs
-
-
EXTRA_FOOTER = """
# Set tick label size - for now tick_params (called lower down) doesn't work
# properly, but these lines won't be needed in future.
diff --git a/glue/viewers/profile/layer_artist.py b/glue/viewers/profile/layer_artist.py
index 9402bae2d..6bda5776c 100644
--- a/glue/viewers/profile/layer_artist.py
+++ b/glue/viewers/profile/layer_artist.py
@@ -3,7 +3,6 @@
from matplotlib.lines import Line2D
-
from glue.core import BaseData
from glue.utils import defer_draw
from glue.viewers.profile.state import ProfileLayerState
@@ -27,9 +26,8 @@ def __init__(self, axes, viewer_state, layer_state=None, layer=None):
self._viewer_state.add_global_callback(self._update_profile)
self.state.add_global_callback(self._update_profile)
- drawstyle = 'steps-mid' if self.state.as_steps else 'default'
- self.plot_artist = self.axes.plot([1, 2, 3], [3, 4, 5], 'k-', drawstyle=drawstyle)[0]
-
+ self.plot_artist = self.axes.plot([1, 2, 3], [3, 4, 5], drawstyle='steps-mid',
+ color=self.state.layer.style.color)[0]
self.mpl_artists = [self.plot_artist]
@defer_draw
@@ -51,7 +49,7 @@ def _calculate_profile_thread(self, reset=False):
warnings.simplefilter("ignore")
if reset:
self.state.reset_cache()
- self.state.update_profile(update_limits=False)
+ self.state.update_profile(update_limits=True)
def _calculate_profile_postthread(self):
@@ -122,7 +120,7 @@ def _update_visual_attributes(self):
def _update_profile(self, force=False, **kwargs):
- if (self._viewer_state.x_att is None or
+ if (self._viewer_state.x_att_pixel is None or
self.state.attribute is None or
self.state.layer is None):
return
@@ -131,9 +129,10 @@ def _update_profile(self, force=False, **kwargs):
# of updated properties is up to date after this method has been called.
changed = self.pop_changed_properties()
- if force or any(prop in changed for prop in ('layer', 'x_att', 'attribute', 'function', 'normalize', 'v_min', 'v_max', 'visible')):
- self._calculate_profile(reset=force)
- force = True
+ if force or any(prop in changed for prop in ('layer', 'slices', 'x_att', 'x_att_pixel', 'attribute',
+ 'function', 'normalize', 'v_min', 'v_max', 'visible')):
+ self._update_visual_attributes()
+ self._calculate_profile(reset=True)
if force or any(prop in changed for prop in ('alpha', 'color', 'zorder', 'linewidth', 'as_steps')):
self._update_visual_attributes()
diff --git a/glue/viewers/profile/qt/data_viewer.py b/glue/viewers/profile/qt/data_viewer.py
index 38def7c94..73f4bcc28 100644
--- a/glue/viewers/profile/qt/data_viewer.py
+++ b/glue/viewers/profile/qt/data_viewer.py
@@ -29,5 +29,5 @@ class ProfileViewer(MatplotlibProfileMixin, MatplotlibDataViewer):
tools = ['select:xrange', 'profile-analysis']
def __init__(self, session, parent=None, state=None):
- MatplotlibDataViewer.__init__(self, session, parent=parent, state=state)
+ MatplotlibDataViewer.__init__(self, session, parent=parent, wcs=True, state=state)
MatplotlibProfileMixin.setup_callbacks(self)
diff --git a/glue/viewers/profile/qt/options_widget.py b/glue/viewers/profile/qt/options_widget.py
index 261f16cf3..64a1186dc 100644
--- a/glue/viewers/profile/qt/options_widget.py
+++ b/glue/viewers/profile/qt/options_widget.py
@@ -5,6 +5,8 @@
from glue.core.coordinate_helpers import dependent_axes
from echo.qt import autoconnect_callbacks_to_qt
from glue.utils.qt import load_ui, fix_tab_widget_fontsize
+from glue.viewers.profile.qt.slice_widget import ProfileMultiSliceWidgetHelper
+from glue.viewers.matplotlib.state import MatplotlibDataViewerState
__all__ = ['ProfileOptionsWidget']
@@ -33,24 +35,45 @@ def __init__(self, viewer_state, session, parent=None):
self.session = session
+ self.profile_slice_helper = None
+
+ self.viewer_state.add_callback('function', self._on_function_change)
self.viewer_state.add_callback('x_att', self._on_attribute_change)
self.ui.text_warning.hide()
+ self.ui.axes_editor.button_apply_all.clicked.connect(self._apply_all_viewers)
- def _on_attribute_change(self, *args):
+ def _on_function_change(self, *args):
+
+ if self.viewer_state.function == 'slice':
+ self.profile_slice_helper = ProfileMultiSliceWidgetHelper(viewer_state=self.viewer_state,
+ session=self.session,
+ layout=self.ui.layout_slices)
+ self.ui.text_warning.hide()
+ self.ui.text_warning.setText('')
+ else:
+ if self.profile_slice_helper:
+ self.profile_slice_helper.remove()
+ # Has to be set to None otherwise it will re-appear for other functions
+ self.profile_slice_helper = None
+ def _on_attribute_change(self, *args):
if (self.viewer_state.reference_data is None or
self.viewer_state.x_att_pixel is None or
self.viewer_state.x_att is self.viewer_state.x_att_pixel):
self.ui.text_warning.hide()
return
- world_warning = len(dependent_axes(self.viewer_state.reference_data.coords,
- self.viewer_state.x_att_pixel.axis)) > 1
-
- if world_warning:
- self.ui.text_warning.show()
- self.ui.text_warning.setText(WARNING_TEXT.format(label=self.viewer_state.x_att.label))
- else:
+ if self.viewer_state.function != 'slice':
+ world_warning = len(dependent_axes(self.viewer_state.reference_data.coords,
+ self.viewer_state.x_att_pixel.axis)) > 1
self.ui.text_warning.hide()
- self.ui.text_warning.setText('')
+ if world_warning:
+ self.ui.text_warning.show()
+ self.ui.text_warning.setText(WARNING_TEXT.format(label=self.viewer_state.x_att.label))
+
+ def _apply_all_viewers(self):
+ for tab in self.session.application.viewers:
+ for viewer in tab:
+ if isinstance(viewer.state, MatplotlibDataViewerState):
+ viewer.state.update_axes_settings_from(self.viewer_state)
diff --git a/glue/viewers/profile/qt/options_widget.ui b/glue/viewers/profile/qt/options_widget.ui
index cd9bafa99..6a9931133 100644
--- a/glue/viewers/profile/qt/options_widget.ui
+++ b/glue/viewers/profile/qt/options_widget.ui
@@ -167,6 +167,11 @@
+
+ -
+
+
+
-
@@ -174,6 +179,7 @@
+
-
diff --git a/glue/viewers/profile/qt/slice_widget.py b/glue/viewers/profile/qt/slice_widget.py
new file mode 100644
index 000000000..747fff8a4
--- /dev/null
+++ b/glue/viewers/profile/qt/slice_widget.py
@@ -0,0 +1,131 @@
+from glue.core.coordinate_helpers import dependent_axes, world_axis
+from glue.viewers.common.qt.data_slice_widget import SliceWidget
+from glue.utils.decorators import avoid_circular
+
+
+__all__ = ['ProfileMultiSliceWidgetHelper']
+
+
+class ProfileMultiSliceWidgetHelper(object):
+
+ def __init__(self, viewer_state=None, layout=None, session=None, *args, **kwargs):
+
+ self.viewer_state = viewer_state
+
+ self.session = session
+
+ self.profile_layout = layout
+ self.profile_layout.setSpacing(4)
+ self.profile_layout.setContentsMargins(0, 3, 0, 3)
+
+ self.viewer_state.add_callback('x_att', self.sync_sliders_from_state)
+ self.viewer_state.add_callback('slices', self.sync_sliders_from_state)
+ self.viewer_state.add_callback('reference_data', self.sync_sliders_from_state)
+
+ self._sliders = []
+
+ self._reference_data = None
+ self._x_att = None
+
+ self.sync_sliders_from_state()
+
+ @property
+ def data(self):
+ return self.viewer_state.reference_data
+
+ def _clear(self):
+
+ for _ in range(self.profile_layout.count()):
+ self.profile_layout.takeAt(0)
+
+ for s in self._sliders:
+ if s is not None:
+ s.close()
+
+ self._sliders = []
+
+ def remove(self):
+
+ for _ in range(self.profile_layout.count()):
+ self.profile_layout.takeAt(0)
+
+ for s in self._sliders:
+ if s is not None:
+ s.close()
+
+ self._sliders = []
+
+ @avoid_circular
+ def sync_state_from_sliders(self, *args):
+
+ slices = []
+ for i, slider in enumerate(self._sliders):
+ if slider is not None:
+ slices.append(slider.state.slice_center)
+ else:
+ slices.append(self.viewer_state.slices[i])
+ self.viewer_state.slices = tuple(slices)
+
+ if self.viewer_state.reference_data is not self._reference_data:
+ self._reference_data = self.viewer_state.reference_data
+
+ @avoid_circular
+ def sync_sliders_from_state(self, *args):
+ if self.data is None or self.viewer_state.x_att_pixel is None:
+ return
+
+ # Update sliders if needed
+
+ if (self.viewer_state.reference_data is not self._reference_data or
+ self.viewer_state.x_att_pixel is not self._x_att):
+
+ self._reference_data = self.viewer_state.reference_data
+ self._x_att = self.viewer_state.x_att_pixel
+
+ self._clear()
+
+ for i in range(self.data.ndim):
+
+ if i == self.viewer_state.x_att_pixel.axis:
+ self._sliders.append(None)
+ continue
+
+ # TODO: For now we simply pass a single set of world coordinates,
+ # but we will need to generalize this in future. We deliberately
+ # check the type of data.coords here since we want to treat
+ # subclasses differently.
+ if getattr(self.data, 'coords') is not None:
+ world = world_axis(self.data.coords, self.data,
+ pixel_axis=self.data.ndim - 1 - i,
+ world_axis=self.data.ndim - 1 - i)
+ world_unit = self.data.coords.world_axis_units[self.data.ndim - 1 - i]
+
+ world_warning = len(dependent_axes(self.data.coords, i)) > 1
+ world_label = self.data.world_component_ids[i].label
+ else:
+ world = None
+ world_unit = None
+ world_warning = False
+ world_label = self.data.pixel_component_ids[i].label
+
+ slider = SliceWidget(world_label,
+ hi=self.data.shape[i] - 1, world=world,
+ world_unit=world_unit, world_warning=world_warning)
+
+ self.slider_state = slider.state
+
+ self.slider_state.add_callback('slice_center', self.sync_state_from_sliders)
+ self._sliders.append(slider)
+ self.profile_layout.addWidget(slider)
+
+ slices = []
+ for i in range(self.data.ndim):
+ if self._sliders[i] is not None:
+ try:
+ slices.append(self._sliders[i].state.slice_center)
+ except Exception:
+ pass
+ elif self._sliders[i] is None:
+ slices.append(0)
+
+ self.viewer_state.slices = slices
diff --git a/glue/viewers/profile/qt/tests/test_python_export.py b/glue/viewers/profile/qt/tests/test_python_export.py
index fa9c3b0c0..4480b4769 100644
--- a/glue/viewers/profile/qt/tests/test_python_export.py
+++ b/glue/viewers/profile/qt/tests/test_python_export.py
@@ -64,6 +64,10 @@ def test_sum(self, tmpdir):
self.viewer.state.function = 'sum'
self.assert_same(tmpdir)
+ def test_slice(self, tmpdir):
+ self.viewer.state.function = 'slice'
+ self.assert_same(tmpdir)
+
def test_normalization(self, tmpdir):
self.viewer.state.normalize = True
self.assert_same(tmpdir)
diff --git a/glue/viewers/profile/state.py b/glue/viewers/profile/state.py
index 0d9cd6ba0..80267ca49 100644
--- a/glue/viewers/profile/state.py
+++ b/glue/viewers/profile/state.py
@@ -1,3 +1,4 @@
+import copy
from collections import OrderedDict
from glue.core.hub import HubListener
@@ -14,6 +15,8 @@
from glue.core.link_manager import is_convertible_to_single_pixel_cid
from glue.core.exceptions import IncompatibleDataException
from glue.core.message import SubsetUpdateMessage
+from glue.core.state_objects import StateAttributeLimitsHelper
+
__all__ = ['ProfileViewerState', 'ProfileLayerState']
@@ -22,7 +25,8 @@
('minimum', 'Minimum'),
('mean', 'Mean'),
('median', 'Median'),
- ('sum', 'Sum')])
+ ('sum', 'Sum'),
+ ('slice', 'Slice')])
class ProfileViewerState(MatplotlibDataViewerState):
@@ -31,7 +35,7 @@ class ProfileViewerState(MatplotlibDataViewerState):
"""
x_att_pixel = DDCProperty(docstring='The component ID giving the pixel component '
- 'shown on the x axis')
+ 'shown on the x axis')
x_att = DDSCProperty(docstring='The component ID giving the pixel or world component '
'shown on the x axis')
@@ -41,6 +45,8 @@ class ProfileViewerState(MatplotlibDataViewerState):
'which defines the coordinate frame in '
'which the images are shown')
+ slices = DDCProperty(docstring='The current slice along all dimensions')
+
function = DDSCProperty(docstring='The function to use for collapsing data')
normalize = DDCProperty(False, docstring='Whether to normalize all profiles '
@@ -56,7 +62,7 @@ def __init__(self, **kwargs):
self.add_callback('layers', self._layers_changed)
self.add_callback('reference_data', self._reference_data_changed, echo_old=True)
- self.add_callback('x_att', self._update_att)
+ self.add_callback('x_att', self._update_x_att)
self.add_callback('normalize', self._reset_y_limits)
self.add_callback('function', self._reset_y_limits)
@@ -82,7 +88,10 @@ def _display_world(self):
return getattr(self.reference_data, 'coords', None) is not None
@defer_draw
- def _update_att(self, *args):
+ def _update_x_att(self, *args):
+ """
+ Defines ``self.x_att_pixel`` in the viewer state.
+ """
if self.x_att is not None:
if self._display_world:
if self.x_att in self.reference_data.pixel_component_ids:
@@ -187,7 +196,7 @@ def _reference_data_changed(self, before=None, after=None):
self.x_att_helper.world_coord = False
self.x_att = self.reference_data.pixel_component_ids[0]
- self._update_att()
+ self._update_x_att()
self.reset_limits()
@@ -201,6 +210,39 @@ def _update_priority(self, name):
else:
return 1
+ @property
+ def wcsaxes_slice(self):
+ """
+ Returns slicing information usable by WCSAxes.
+
+ This returns an iterable of slices, and including ``'x'`` and ``'y'``
+ for the dimensions along which we are not slicing.
+ """
+ if self.reference_data is None:
+ return None
+ elif self.x_att_pixel is None:
+ # TODO: This should not be here.
+ # I can not work out how to get this to be set at initialisation.
+ self.x_att_helper.set_multiple_data([self.reference_data])
+ self.x_att_helper.world_coord = False
+ self.x_att = self.reference_data.pixel_component_ids[0]
+ self._update_x_att()
+
+ slices = []
+ for i in range(self.reference_data.ndim):
+ if self.x_att_pixel and i == self.x_att_pixel.axis:
+ slices.append('x')
+ else:
+ slices.append(0)
+ return slices[::-1]
+
+ def _set_default_slices(self):
+ # Need to make sure this gets called immediately when reference_data is changed
+ if self.reference_data is None:
+ self.slices = ()
+ else:
+ self.slices = tuple([0] * self.reference_data.ndim)
+
class ProfileLayerState(MatplotlibLayerState, HubListener):
"""
@@ -221,12 +263,19 @@ class ProfileLayerState(MatplotlibLayerState, HubListener):
_layer_subset_updates_subscribed = False
_profile_cache = None
- def __init__(self, layer=None, viewer_state=None, **kwargs):
+ def __init__(self, layer=None, viewer_state=None, session=None, **kwargs):
+
+ super(ProfileLayerState, self).__init__(layer=layer, viewer_state=viewer_state, session=session)
- super(ProfileLayerState, self).__init__(layer=layer, viewer_state=viewer_state)
+ self.viewer_state = viewer_state
- self.attribute_att_helper = ComponentIDComboHelper(self, 'attribute',
- numeric=True, categorical=False)
+ self.session = session
+
+ self.attribute_lim_helper = StateAttributeLimitsHelper(self, attribute='attribute',
+ percentile='percentile',
+ lower='v_min', upper='v_max')
+
+ self.attribute_att_helper = ComponentIDComboHelper(self, 'attribute')
percentile_display = {100: 'Min/Max',
99.5: '99.5%',
@@ -298,7 +347,9 @@ def update_profile(self, update_limits=True):
if not self._viewer_callbacks_set:
self.viewer_state.add_callback('x_att', self.reset_cache, priority=100000)
+ self.viewer_state.add_callback('x_att_pixel', self.reset_cache, priority=100000)
self.viewer_state.add_callback('function', self.reset_cache, priority=100000)
+ self.viewer_state.add_callback('slices', self.reset_cache, priority=100000)
if self.is_callback_property('attribute'):
self.add_callback('attribute', self.reset_cache, priority=100000)
self._viewer_callbacks_set = True
@@ -307,7 +358,7 @@ def update_profile(self, update_limits=True):
raise IncompatibleDataException()
# Check what pixel axis in the current dataset x_att corresponds to
- pix_cid = is_convertible_to_single_pixel_cid(self.layer, self.viewer_state.x_att_pixel)
+ pix_cid = is_convertible_to_single_pixel_cid(self.layer, self.viewer_state.x_att)
if pix_cid is None:
raise IncompatibleDataException()
@@ -329,7 +380,16 @@ def update_profile(self, update_limits=True):
data = self.layer
subset_state = None
- profile_values = data.compute_statistic(self.viewer_state.function, self.attribute, axis=axes, subset_state=subset_state)
+ if self.viewer_state.function == 'slice':
+ data_slice = list(copy.deepcopy(self.viewer_state.slices))
+ data_slice[pix_cid.axis] = slice(None)
+ profile_values = data.get_data(self.attribute, view=tuple(data_slice))
+ if subset_state:
+ subset_profile_mask = data.get_mask(subset_state=subset_state, view=tuple(data_slice))
+ profile_values = profile_values[subset_profile_mask]
+ else:
+ profile_values = data.compute_statistic(self.viewer_state.function, self.attribute, axis=axes,
+ subset_state=subset_state)
if np.all(np.isnan(profile_values)):
self._profile_cache = [], []
@@ -344,6 +404,7 @@ def update_profile(self, update_limits=True):
self.update_limits(update_profile=False)
def update_limits(self, update_profile=True):
+
with delay_callback(self, 'v_min', 'v_max'):
if update_profile:
self.update_profile(update_limits=False)
diff --git a/glue/viewers/profile/tests/test_state.py b/glue/viewers/profile/tests/test_state.py
index c574558db..314054127 100644
--- a/glue/viewers/profile/tests/test_state.py
+++ b/glue/viewers/profile/tests/test_state.py
@@ -46,17 +46,16 @@ def setup_method(self, method):
def test_basic(self):
x, y = self.layer_state.profile
- assert_allclose(x, [0, 2, 4])
+ assert_allclose(x, [0, 1, 2])
assert_allclose(y, [3.5, 11.5, 19.5])
def test_basic_world(self):
self.viewer_state.x_att = self.data.world_component_ids[0]
x, y = self.layer_state.profile
- assert_allclose(x, [0, 2, 4])
+ assert_allclose(x, [0, 1, 2])
assert_allclose(y, [3.5, 11.5, 19.5])
def test_x_att(self):
-
self.viewer_state.x_att = self.data.pixel_component_ids[0]
x, y = self.layer_state.profile
assert_allclose(x, [0, 1, 2])
@@ -94,6 +93,9 @@ def test_function(self):
x, y = self.layer_state.profile
assert_allclose(y, [3.5, 11.5, 19.5])
+ self.viewer_state.function = 'slice'
+ assert_allclose(y, [3.5, 11.5, 19.5])
+
def test_subset(self):
subset = self.data.new_subset()
@@ -102,7 +104,7 @@ def test_subset(self):
self.layer_state.layer = subset
x, y = self.layer_state.profile
- assert_allclose(x, [0, 2, 4])
+ assert_allclose(x, [0, 1, 2])
assert_allclose(y, [np.nan, 13., 19.5])
subset.subset_state = self.data.id['x'] > 100
@@ -159,5 +161,5 @@ def test_visible(self):
self.layer_state.visible = True
x, y = self.layer_state.profile
- assert_allclose(x, [0, 2, 4])
+ assert_allclose(x, [0, 1, 2])
assert_allclose(y, [3.5, 11.5, 19.5])
diff --git a/glue/viewers/profile/viewer.py b/glue/viewers/profile/viewer.py
index 3817a6480..a594da3de 100644
--- a/glue/viewers/profile/viewer.py
+++ b/glue/viewers/profile/viewer.py
@@ -1,4 +1,8 @@
from glue.core.subset import roi_to_subset_state
+from glue.core.coordinates import LegacyCoordinates
+from glue.utils.wcs import get_identity_wcs
+
+from astropy.visualization.wcsaxes.frame import RectangularFrame1D
__all__ = ['MatplotlibProfileMixin']
@@ -6,13 +10,22 @@
class MatplotlibProfileMixin(object):
def setup_callbacks(self):
- self.state.add_callback('x_att', self._update_axes)
- self.state.add_callback('normalize', self._update_axes)
- def _update_axes(self, *args):
+ self._changing_slice_requires_wcs_update = None
+ self.state.add_callback('normalize', self._set_wcs)
+ self.state.add_callback('x_att_pixel', self._set_wcs)
+ self.state.add_callback('reference_data', self._set_wcs)
+ self.state.add_callback('slices', self._set_wcs)
+
+ def update_x_ticklabel(self, *event):
+
+ # We need to overload this here for WCSAxes
+ axis = 0
- if self.state.x_att is not None:
- self.state.x_axislabel = self.state.x_att.label
+ self.axes.coords[axis].set_ticklabel(size=self.state.x_ticklabel_size)
+ self.redraw()
+
+ def _update_axes(self, *args):
if self.state.normalize:
self.state.y_axislabel = 'Normalized data values'
@@ -33,5 +46,30 @@ def apply_roi(self, roi, override_mode=None):
if len(self.layers) == 0:
return
- subset_state = roi_to_subset_state(roi, x_att=self.state.x_att)
+ subset_state = roi_to_subset_state(roi, x_att=self.state.x_att_pixel)
self.apply_subset_state(subset_state, override_mode=override_mode)
+
+ def _set_wcs(self, event=None, relim=True):
+ ref_coords = getattr(self.state.reference_data, 'coords', None)
+
+ self.axes.frame_class = RectangularFrame1D
+
+ if ref_coords is None or isinstance(ref_coords, LegacyCoordinates):
+ self.axes.reset_wcs(slices=self.state.wcsaxes_slice,
+ wcs=get_identity_wcs(self.state.reference_data.ndim))
+ else:
+ self.axes.reset_wcs(slices=self.state.wcsaxes_slice, wcs=ref_coords)
+
+ self.axes.yaxis.set_visible(True)
+
+ # Reset the axis labels to match the fact that the new axes have no labels
+ self.state.x_axislabel = ''
+ self.state.y_axislabel = 'Data values'
+
+ self._update_appearance_from_settings()
+ self._update_axes()
+
+ self.update_x_ticklabel()
+
+ if relim:
+ self.state.reset_limits()