Esempio n. 1
0
    def test_base(self):
        d = Data(x=[1, 2, 3], coords=IdentityCoordinates(n_dim=1))
        assert dependent_axes(d.coords, 0) == (0,)

        d = Data(x=[[1, 2], [3, 4]], coords=IdentityCoordinates(n_dim=2))
        assert dependent_axes(d.coords, 0) == (0,)
        assert dependent_axes(d.coords, 1) == (1,)
Esempio n. 2
0
    def test_wcs_alma(self):
        header = self.header4()

        d = Data(label='D1')
        d.coords = coordinates_from_header(header)
        d.add_component(np.zeros((3, 2, 1, 1)), label='test')

        assert dependent_axes(d.coords, 0) == (0,)
        assert dependent_axes(d.coords, 1) == (1,)
        assert dependent_axes(d.coords, 2) == (2, 3)
        assert dependent_axes(d.coords, 3) == (2, 3)
Esempio n. 3
0
    def _set_wcs(self, before=None, after=None, relim=True):

        if self.state.x_att is None or self.state.y_att is None or self.state.reference_data is None:
            return

        # A callback event for reference_data is triggered if the choices change
        # but the actual selection doesn't - so we avoid resetting the WCS in
        # this case.
        if after is not None and before is after:
            return

        ref_coords = getattr(self.state.reference_data, 'coords', None)

        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)

        # Reset the axis labels to match the fact that the new axes have no labels
        self.state.x_axislabel = ''
        self.state.y_axislabel = ''

        self._update_appearance_from_settings()
        self._update_axes()

        self.update_x_ticklabel()
        self.update_y_ticklabel()

        if relim:
            self.state.reset_limits()

        # Determine whether changing slices requires changing the WCS
        if ref_coords is None or type(ref_coords) == Coordinates:
            self._changing_slice_requires_wcs_update = False
        else:
            ix = self.state.x_att.axis
            iy = self.state.y_att.axis
            x_dep = list(dependent_axes(ref_coords, ix))
            y_dep = list(dependent_axes(ref_coords, iy))
            if ix in x_dep:
                x_dep.remove(ix)
            if iy in x_dep:
                x_dep.remove(iy)
            if ix in y_dep:
                y_dep.remove(ix)
            if iy in y_dep:
                y_dep.remove(iy)
            self._changing_slice_requires_wcs_update = bool(x_dep or y_dep)

        self._wcs_set = True
Esempio n. 4
0
    def _set_wcs(self, event=None, relim=True):

        if self.state.x_att is None or self.state.y_att is None or self.state.reference_data is None:
            return

        ref_coords = getattr(self.state.reference_data, 'coords', None)

        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)

        # Reset the axis labels to match the fact that the new axes have no labels
        self.state.x_axislabel = ''
        self.state.y_axislabel = ''

        self._update_appearance_from_settings()
        self._update_axes()

        self.update_x_ticklabel()
        self.update_y_ticklabel()

        if relim:
            self.state.reset_limits()

        # Determine whether changing slices requires changing the WCS
        if ref_coords is None or type(ref_coords) == Coordinates:
            self._changing_slice_requires_wcs_update = False
        else:
            ix = self.state.x_att.axis
            iy = self.state.y_att.axis
            x_dep = list(dependent_axes(ref_coords, ix))
            y_dep = list(dependent_axes(ref_coords, iy))
            if ix in x_dep:
                x_dep.remove(ix)
            if iy in x_dep:
                x_dep.remove(iy)
            if ix in y_dep:
                y_dep.remove(ix)
            if iy in y_dep:
                y_dep.remove(iy)
            self._changing_slice_requires_wcs_update = bool(x_dep or y_dep)

        self._wcs_set = True
Esempio n. 5
0
def translate_pixel(data, pixel_coords, target_cid):
    """
    Given a dataset and pixel coordinates in that dataset, compute a
    corresponding pixel coordinate for another dataset.

    Parameters
    ----------
    data : `~glue.core.data.Data`
        The main data object in which the original pixel coordinates are defined.
    pixel_coords : tuple or list
        List of pixel coordinates in the original data object. This should contain
        as many Numpy arrays as there are dimensions in ``data``.
    target_cid : `~glue.core.component_id.ComponentID`
        The component to compute - this can be for a different dataset.

    Returns
    -------
    coords : `~numpy.ndarray`
        The values of the translated coordinates
    dimensions : tuple
        The dimensions in ``data`` for which pixel coordinates were used in the
        translation.
    """

    if not len(pixel_coords) == data.ndim:
        raise ValueError('The number of coordinates in pixel_coords does not '
                         'match the number of dimensions in data')

    if target_cid in data.pixel_component_ids:
        return pixel_coords[target_cid.axis], [target_cid.axis]

    # TODO: check that things are efficient if the PixelComponentID is in a
    # pixel-aligned dataset.

    component = data.get_component(target_cid)

    if hasattr(component, 'link'):
        link = component.link
        values_all = []
        dimensions_all = []
        for cid in link._from:
            values, dimensions = translate_pixel(data, pixel_coords, cid)
            values_all.append(values)
            dimensions_all.extend(dimensions)
        # Unbroadcast arrays to smallest common shape for performance
        if len(values_all) > 0:
            shape = values_all[0].shape
            values_all = broadcast_arrays_minimal(*values_all)
            results = link._using(*values_all)
            result = broadcast_to(results, shape)
        else:
            result = None
        return result, sorted(set(dimensions_all))
    elif isinstance(component, CoordinateComponent):
        # FIXME: Hack for now - if we pass arrays in the view, it's interpreted
        return component._calculate(view=pixel_coords), dependent_axes(
            data.coords, component.axis)
    else:
        raise Exception("Dependency on non-pixel component", target_cid)
Esempio n. 6
0
    def sync_sliders_from_state(self, *args):

        if self.data is None or self.viewer_state.x_att is None or self.viewer_state.y_att is None:
            return

        if self.viewer_state.x_att is self.viewer_state.y_att:
            return

        # Update sliders if needed

        if (self.viewer_state.reference_data is not self._reference_data or
                self.viewer_state.x_att is not self._x_att or
                self.viewer_state.y_att is not self._y_att):

            self._reference_data = self.viewer_state.reference_data
            self._x_att = self.viewer_state.x_att
            self._y_att = self.viewer_state.y_att

            self._clear()

            for i in range(self.data.ndim):

                if i == self.viewer_state.x_att.axis or i == self.viewer_state.y_att.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 and type(self.data.coords) != LegacyCoordinates:
                    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[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.layout.addWidget(slider)

        for i in range(self.data.ndim):
            if self._sliders[i] is not None:
                if isinstance(self.viewer_state.slices[i], AggregateSlice):
                    self._sliders[i].state.slice_center = self.viewer_state.slices[i].center
                else:
                    self._sliders[i].state.slice_center = self.viewer_state.slices[i]
Esempio n. 7
0
    def __init__(self, comp_from, comp_to, coords, index, pixel2world=True):
        self.coords = coords
        self.index = index
        self.pixel2world = pixel2world

        # Some coords don't need all pixel coords
        # to compute a given world coord, and vice versa
        # (e.g., spectral data cubes)
        self.ndim = len(comp_from)
        self.from_needed = dependent_axes(coords, index)
        self._from_all = comp_from

        comp_from = [comp_from[i] for i in self.from_needed]
        super(CoordinateComponentLink, self).__init__(comp_from, comp_to,
                                                      self.using)
Esempio n. 8
0
    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:
            self.ui.text_warning.hide()
            self.ui.text_warning.setText('')
Esempio n. 9
0
def is_convertible_to_single_pixel_cid(data, cid):
    """
    Given a dataset and a component ID, determine whether a pixel component
    exists in data such that the component ID can be derived solely from the
    pixel component. Returns `None` if no such pixel component ID can be found
    and returns the pixel component ID if one exists.

    Parameters
    ----------
    data : `~glue.core.data.Data`
        The data in which to check for pixel components IDs
    cid : `~glue.core.ComponentID`
        The component ID to search for
    """
    if isinstance(data, Subset):
        data = data.data
    if cid in data.pixel_component_ids:
        return cid
    else:
        try:
            target_comp = data.get_component(cid)
        except IncompatibleAttribute:
            return None
        if cid in data.world_component_ids:
            if len(dependent_axes(data.coords, target_comp.axis)) == 1:
                return data.pixel_component_ids[target_comp.axis]
            else:
                return None
        else:
            if isinstance(target_comp, DerivedComponent):
                from_ids = [
                    is_convertible_to_single_pixel_cid(data, c)
                    for c in target_comp.link.get_from_ids()
                ]
                if None in from_ids:
                    return None
                else:
                    # Use set to get rid of duplicates
                    from_ids = list(set(from_ids))
                    if len(from_ids) == 1:
                        return is_convertible_to_single_pixel_cid(
                            data, from_ids[0])
Esempio n. 10
0
    def _calculate(self, view=None):

        if self.world:

            # Calculating the world coordinates can be a bottleneck if we aren't
            # careful, so we need to make sure that if not all dimensions depend
            # on each other, we use smart broadcasting.

            # The unoptimized way to do this for an N-dimensional dataset would
            # be to construct N-dimensional arrays of pixel values for each
            # coordinate. However, if we are computing the coordinates for axis
            # i, and axis i is not dependent on any other axis, then the result
            # will be an N-dimensional array where the same 1D array of
            # coordinates will be repeated over and over.

            # To optimize this, we therefore essentially consider only the
            # dependent dimensions and then broacast the result to the full
            # array size at the very end.

            # view=None actually adds a dimension which is never what we really
            # mean, at least in glue.
            if view is None:
                view = Ellipsis

            # If the view is a tuple or list of arrays, we should actually just
            # convert these straight to world coordinates since the indices
            # of the pixel coordinates are the pixel coordinates themselves.
            if isinstance(view,
                          (tuple, list)) and isinstance(view[0], np.ndarray):
                axis = self._data.ndim - 1 - self.axis
                return pixel2world_single_axis(self._data.coords,
                                               *view[::-1],
                                               world_axis=axis)

            # For 1D arrays, slice can be given as a single slice but we need
            # to wrap it in a list to make the following code work correctly,
            # as it is then consistent with higher-dimensional cases.
            if isinstance(view, slice) or np.isscalar(view):
                view = [view]

            # Some views, e.g. with lists of integer arrays, can give arbitrarily
            # complex (copied) subsets of arrays, so in this case we don't do any
            # optimization
            if view is Ellipsis:
                optimize_view = False
            else:
                for v in view:
                    if not np.isscalar(v) and not isinstance(v, slice):
                        optimize_view = False
                        break
                else:
                    optimize_view = True

            pix_coords = []
            dep_coords = dependent_axes(self._data.coords, self.axis)

            final_slice = []
            final_shape = []

            for i in range(self._data.ndim):

                if optimize_view and i < len(view) and np.isscalar(view[i]):
                    final_slice.append(0)
                else:
                    final_slice.append(slice(None))

                # We set up a 1D pixel axis along that dimension.
                pix_coord = np.arange(self._data.shape[i])

                # If a view was specified, we need to take it into account for
                # that axis.
                if optimize_view and i < len(view):
                    pix_coord = pix_coord[view[i]]
                    if not np.isscalar(view[i]):
                        final_shape.append(len(pix_coord))
                else:
                    final_shape.append(self._data.shape[i])

                if i not in dep_coords:
                    # The axis is not dependent on this instance's axis, so we
                    # just compute the values once and broadcast along this
                    # dimension later.
                    pix_coord = 0

                pix_coords.append(pix_coord)

            # We build the list of N arrays, one for each pixel coordinate
            pix_coords = np.meshgrid(*pix_coords, indexing='ij', copy=False)

            # Finally we convert these to world coordinates
            axis = self._data.ndim - 1 - self.axis
            world_coords = pixel2world_single_axis(self._data.coords,
                                                   *pix_coords[::-1],
                                                   world_axis=axis)

            # We get rid of any dimension for which using the view should get
            # rid of that dimension.
            if optimize_view:
                world_coords = world_coords[tuple(final_slice)]

            # We then broadcast the final array back to what it should be
            world_coords = broadcast_to(world_coords, tuple(final_shape))

            # We apply the view if we weren't able to optimize before
            if optimize_view:
                return world_coords
            else:
                return world_coords[view]

        else:

            slices = [slice(0, s, 1) for s in self.shape]
            grids = np.broadcast_arrays(*np.ogrid[slices])
            if view is not None:
                grids = [g[view] for g in grids]
            return grids[self.axis]