From 02735422324a12dd94b29adc0972064c567e368f Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 7 Feb 2020 18:15:35 +0000 Subject: [PATCH] Improve behavior of PV slicing tool, and fix implementation of PVSlicedData.compute.fixed_resolution_buffer to ensure that multiple sliced datasets can be shown at the same time --- .../plugins/tools/pv_slicer/pv_sliced_data.py | 69 ++++++++++++++++++- glue/plugins/tools/pv_slicer/qt/pv_slicer.py | 57 ++++++++------- .../pv_slicer/tests/test_pv_sliced_data.py | 48 +++++++++++++ 3 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 glue/plugins/tools/pv_slicer/tests/test_pv_sliced_data.py diff --git a/glue/plugins/tools/pv_slicer/pv_sliced_data.py b/glue/plugins/tools/pv_slicer/pv_sliced_data.py index d3d6681b8..3980a6614 100644 --- a/glue/plugins/tools/pv_slicer/pv_sliced_data.py +++ b/glue/plugins/tools/pv_slicer/pv_sliced_data.py @@ -138,7 +138,70 @@ def compute_statistic(self, *args, view=None, **kwargs): def compute_histogram(self, *args, **kwargs): return self.original_data.compute_histogram(*args, **kwargs) - def compute_fixed_resolution_buffer(self, *args, **kwargs): + def compute_fixed_resolution_buffer(self, bounds, target_data=None, target_cid=None, + subset_state=None, broadcast=True, cache_id=None): + from glue.core.fixed_resolution_buffer import compute_fixed_resolution_buffer - print(args, kwargs) - return compute_fixed_resolution_buffer(self, *args, **kwargs) + + # First check that the target data is also a PVSlicedData + # TODO: also check it's actually for the same path + + if not isinstance(target_data, PVSlicedData): + raise TypeError('target_data has to be a PVSlicedData') + + if len(bounds) != len(self.shape): + raise ValueError('bounds should have {0} elements'.format(len(self.shape))) + + # Now translate the bounds so that we replace the path with the + # pixel coordinates in the target dataset + + + # The last item of bounds is the pixel offset in the target PV slice + path_pixel_offset_target = np.linspace(*bounds[-1]) + + # Translate this to a relative offset along the path + path_pixel_offset_target_relative = path_pixel_offset_target / self.shape[-1] + + # Find the pixel coordinates in the current dataset + x = np.interp(path_pixel_offset_target_relative, + np.linspace(0., 1., len(self.x)), + self.x) + y = np.interp(path_pixel_offset_target_relative, + np.linspace(0., 1., len(self.y)), + self.y) + + # Create new bouds list + + new_bounds = [] + idim_current = 0 + + slices = [] + + for idim in range(self.original_data.ndim): + + if idim == self.cid_x.axis: + ixmax = np.ceil(np.max(x)) + bound = (0, ixmax, ixmax + 1) + slices.append(np.round(x).astype(int)) + elif idim == self.cid_y.axis: + iymax = np.ceil(np.max(y)) + bound = (0, iymax, iymax + 1) + slices.append(np.round(x).astype(int)) + else: + bound = bounds[idim_current] + idim_current += 1 + slices.append(slice(None)) + + new_bounds.append(bound) + + # TODO: For now we extract a cube and then slice it, but it would be + # more efficient if bounds could include 1-d arrays. + + # Now compute the fixed resolution buffer using the original datasets + result = compute_fixed_resolution_buffer(self.original_data, new_bounds, + target_data=target_data.original_data, + target_cid=target_cid) + + result = result[slices] + + return result diff --git a/glue/plugins/tools/pv_slicer/qt/pv_slicer.py b/glue/plugins/tools/pv_slicer/qt/pv_slicer.py index affd619e6..35529692d 100644 --- a/glue/plugins/tools/pv_slicer/qt/pv_slicer.py +++ b/glue/plugins/tools/pv_slicer/qt/pv_slicer.py @@ -29,6 +29,7 @@ def __init__(self, viewer, **kwargs): self._roi_callback = self._extract_callback self._slice_widget = None self.viewer.state.add_callback('reference_data', self._on_reference_data_change) + # self._sliced_data = [] def _on_reference_data_change(self, reference_data): if reference_data is not None: @@ -47,31 +48,41 @@ def _extract_callback(self, mode): selected = self.viewer.session.application.selected_layers() - if len(selected) == 1 and isinstance(selected[0], PVSlicedData): - data = selected[0] - data.original_data = self.viewer.state.reference_data - data.x_att = self.viewer.state.x_att - data.y_att = self.viewer.state.y_att - data.set_xy(vx, vy) - open_viewer = True - for tab in self.viewer.session.application.viewers: - for viewer in tab: - if data in viewer._layer_artist_container: - open_viewer = False - break - if not open_viewer: - break - else: - data = PVSlicedData(self.viewer.state.reference_data, - self.viewer.state.x_att, vx, - self.viewer.state.y_att, vy, - label=self.viewer.state.reference_data.label + " [slice]") - data.parent_viewer = self.viewer - self.viewer.session.data_collection.append(data) - open_viewer = True + open_viewer = False + + all_pvdata = [] + + for data in self.viewer.state.layers_data: + if isinstance(data, Data): + + for pvdata in self.viewer.session.data_collection: + if isinstance(pvdata, PVSlicedData): + if pvdata.original_data is data: + break + else: + pvdata = None + + if pvdata is None: + pvdata = PVSlicedData(data, + self.viewer.state.x_att, vx, + self.viewer.state.y_att, vy, + label=data.label + " [slice]") + data.parent_viewer = self.viewer + self.viewer.session.data_collection.append(pvdata) + open_viewer = True + else: + data = pvdata + data.original_data = self.viewer.state.reference_data + data.x_att = self.viewer.state.x_att + data.y_att = self.viewer.state.y_att + data.set_xy(vx, vy) + + all_pvdata.append(pvdata) if open_viewer: - viewer = self.viewer.session.application.new_data_viewer(ImageViewer, data=data) + viewer = self.viewer.session.application.new_data_viewer(ImageViewer) + for pvdata in all_pvdata: + viewer.add_data(pvdata) viewer.state.aspect = 'auto' viewer.state.reset_limits() diff --git a/glue/plugins/tools/pv_slicer/tests/test_pv_sliced_data.py b/glue/plugins/tools/pv_slicer/tests/test_pv_sliced_data.py new file mode 100644 index 000000000..8b1771c71 --- /dev/null +++ b/glue/plugins/tools/pv_slicer/tests/test_pv_sliced_data.py @@ -0,0 +1,48 @@ +import numpy as np +from glue.core import Data, DataCollection +from glue.core.coordinates import AffineCoordinates, IdentityCoordinates +from glue.plugins.tools.pv_slicer.pv_sliced_data import PVSlicedData +from glue.core.link_helpers import LinkSame + + +class TestPVSlicedData: + + def setup_method(self, method): + matrix = np.array([[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]]) + + self.data1 = Data(x=np.arange(120).reshape((6, 5, 4)), coords=AffineCoordinates(matrix)) + self.data2 = Data(y=np.arange(120).reshape((6, 5, 4)), coords=IdentityCoordinates(n_dim=3)) + + self.dc = DataCollection([self.data1, self.data2]) + + self.dc.add_link(LinkSame(self.data1.world_component_ids[0], + self.data2.world_component_ids[0])) + + self.dc.add_link(LinkSame(self.data1.world_component_ids[1], + self.data2.world_component_ids[1])) + + self.dc.add_link(LinkSame(self.data1.world_component_ids[2], + self.data2.world_component_ids[2])) + + # TODO: the paths in the next two PVSlicedData objects are meant to + # be the same conceptually. We should make sure we formalize this with + # a UUID. Also should use proper links. + + x1 = [0, 2, 5] + y1 = [1, 2, 3] + + self.pvdata1 = PVSlicedData(self.data1, + self.data1.pixel_component_ids[1], y1, + self.data1.pixel_component_ids[2], x1) + + x2, y2, _ = self.data2.coords.world_to_pixel_values(*self.data1.coords.pixel_to_world_values(x1, y1, 0)) + + self.pvdata2 = PVSlicedData(self.data2, + self.data2.pixel_component_ids[1], y2, + self.data2.pixel_component_ids[2], x2) + + def test_fixed_resolution_buffer_linked(self): + result = self.pvdata1.compute_fixed_resolution_buffer(bounds=[(0, 5, 15), (0, 6, 20)], + target_data=self.pvdata2, + target_cid=self.data1.id['x']) + assert result.shape == (15, 20)