Exemplo n.º 1
0
    def to_mask(self, data, view=None):

        if view is None:
            view = Ellipsis
        elif isinstance(view, slice) or np.isscalar(view):
            view = [view]

        # Figure out the shape of the final mask given the requested view
        shape = view_shape(data.shape, view)

        if data is self.reference_data:

            slices = self.slices

        else:

            # Check if we can transform list of slices to match this dataset
            order = data.pixel_aligned_data.get(self.reference_data, None)

            if order is None:
                # We use broadcast_to for minimal memory usage
                return broadcast_to(False, shape)
            else:
                # Reorder slices
                slices = [self.slices[idx] for idx in order]

        if (isinstance(view, np.ndarray) or
                (isinstance(view, (tuple, list)) and isinstance(view[0], np.ndarray))):
            mask = np.zeros(data.shape, dtype=bool)
            mask[slices] = True
            return mask[view]

        # The original slices assume the full array, not the array with the view
        # applied, so we need to now adjust the slices accordingly.
        if view is Ellipsis:
            subslices = slices
        else:
            subslices = []
            for i in range(data.ndim):
                if i >= len(view):
                    subslices.append(slices[i])
                elif np.isscalar(view[i]):
                    beg, end, stp = slices[i].indices(data.shape[i])
                    if view[i] < beg or view[i] >= end or (view[i] - beg) % stp != 0:
                        return broadcast_to(False, shape)
                elif isinstance(view[i], slice):
                    if view[i].step is not None and view[i].step < 0:
                        beg, end, step = view[i].indices(data.shape[i])
                        v = slice(end + 1, beg + 1, -step)
                    else:
                        v = view[i]
                    subslices.append(combine_slices(v, slices[i], data.shape[i]))
                else:
                    raise TypeError("Unexpected view item: {0}".format(view[i]))

        # Create mask with final shape
        mask = np.zeros(shape, dtype=bool)
        mask[subslices] = True

        return mask
Exemplo n.º 2
0
    def compute_fixed_resolution_buffer(self, bounds=None):

        shape = [bound[2] for bound in bounds]

        if self.layer_artist is None or self.viewer_state is None:
            return broadcast_to(0, shape)

        if isinstance(self.layer_artist.layer, Subset):
            try:
                subset_state = self.layer_artist.layer.subset_state
                result = self.layer_artist.layer.data.compute_fixed_resolution_buffer(
                    target_data=self.layer_artist._viewer_state.reference_data,
                    bounds=bounds,
                    subset_state=subset_state,
                    cache_id=self.layer_artist.id)
            except IncompatibleAttribute:
                self.layer_artist.disable_incompatible_subset()
                return broadcast_to(0, shape)
            else:
                self.layer_artist.enable()
        else:
            try:
                result = self.layer_artist.layer.compute_fixed_resolution_buffer(
                    target_data=self.layer_artist._viewer_state.reference_data,
                    bounds=bounds,
                    target_cid=self.layer_artist.state.attribute,
                    cache_id=self.layer_artist.id)
            except IncompatibleAttribute:
                self.layer_artist.disable(
                    'Layer data is not fully linked to reference data')
                return broadcast_to(0, shape)
            else:
                self.layer_artist.enable()

        return result
Exemplo n.º 3
0
def test_compute_statistic_empty_subset():

    data = Data(x=np.empty((30, 20, 40)))

    # A default subset state should be empty
    subset_state = SubsetState()

    result = data.compute_statistic('mean',
                                    data.id['x'],
                                    subset_state=subset_state)
    assert_equal(result, np.nan)

    result = data.compute_statistic('maximum',
                                    data.id['x'],
                                    subset_state=subset_state,
                                    axis=1)
    assert_equal(result, broadcast_to(np.nan, (30, 40)))

    result = data.compute_statistic('median',
                                    data.id['x'],
                                    subset_state=subset_state,
                                    axis=(1, 2))
    assert_equal(result, broadcast_to(np.nan, (30)))

    result = data.compute_statistic('sum',
                                    data.id['x'],
                                    subset_state=subset_state,
                                    axis=(0, 1, 2))
    assert_equal(result, np.nan)
Exemplo n.º 4
0
    def to_mask(self, data, view=None):

        if view is None:
            view = Ellipsis
        elif isinstance(view, slice) or np.isscalar(view):
            view = [view]

        # Figure out the shape of the final mask given the requested view
        shape = view_shape(data.shape, view)

        if data is self.reference_data:

            slices = self.slices

        else:

            # Check if we can transform list of slices to match this dataset
            order = data.pixel_aligned_data.get(self.reference_data, None)

            if order is None:
                # We use broadcast_to for minimal memory usage
                return broadcast_to(False, shape)
            else:
                # Reorder slices
                slices = [self.slices[idx] for idx in order]

        if (isinstance(view, np.ndarray) or
                (isinstance(view, (tuple, list)) and isinstance(view[0], np.ndarray))):
            mask = np.zeros(data.shape, dtype=bool)
            mask[slices] = True
            return mask[view]

        # The original slices assume the full array, not the array with the view
        # applied, so we need to now adjust the slices accordingly.
        if view is Ellipsis:
            subslices = slices
        else:
            subslices = []
            for i in range(data.ndim):
                if i >= len(view):
                    subslices.append(slices[i])
                elif np.isscalar(view[i]):
                    beg, end, stp = slices[i].indices(data.shape[i])
                    if view[i] < beg or view[i] >= end or (view[i] - beg) % stp != 0:
                        return broadcast_to(False, shape)
                elif isinstance(view[i], slice):
                    if view[i].step is not None and view[i].step < 0:
                        beg, end, step = view[i].indices(data.shape[i])
                        v = slice(end + 1, beg + 1, -step)
                    else:
                        v = view[i]
                    subslices.append(combine_slices(v, slices[i], data.shape[i]))
                else:
                    raise TypeError("Unexpected view item: {0}".format(view[i]))

        # Create mask with final shape
        mask = np.zeros(shape, dtype=bool)
        mask[subslices] = True

        return mask
Exemplo n.º 5
0
    def __getitem__(self, view=None):

        if (self.layer_artist is None or
                self.viewer_state is None):
            return broadcast_to(0, self.shape)[view]

        try:
            mask = self.layer_artist.layer.to_mask(view=view)
        except IncompatibleAttribute:
            self.layer_artist.disable_incompatible_subset()
            return broadcast_to(0, self.shape)[view]
        else:
            self.layer_artist.enable()

        return mask
Exemplo n.º 6
0
    def using(self, *args):

        # NOTE: in the past, we set any non-specified arguemnts to 0 for the
        # input coordinates, but this caused issues because in astropy.wcs
        # if one specifies e.g. (0, 0, 3000.) for (ra, dec, velocity), and if
        # (0, 0) for RA/Dec would return (nan, nan) normally, the velocity
        # is also NaN even though it is decoupled from the other coordinates.
        default = default_world_coords(self.coords)

        args2 = [None] * self.ndim
        for f, a in zip(self.from_needed, args):
            args2[f] = a
        for i in range(self.ndim):
            if args2[i] is None:
                args2[i] = broadcast_to(default[self.ndim - 1 - i],
                                        args[0].shape)
        args2 = tuple(args2)

        if self.pixel2world:
            return pixel2world_single_axis(self.coords,
                                           *args2[::-1],
                                           world_axis=self.ndim - 1 -
                                           self.index)
        else:
            return world2pixel_single_axis(self.coords,
                                           *args2[::-1],
                                           pixel_axis=self.ndim - 1 -
                                           self.index)
Exemplo n.º 7
0
    def compute(self, data, view=None):
        """
        For a given data set, compute the component comp_to given the data
        associated with each comp_from and the ``using`` function

        This raises an :class:`glue.core.exceptions.IncompatibleAttribute` if the
        data set doesn't have all the ComponentIDs needed for the transformation

        Parameters
        ----------
        data : `~glue.core.data.Data`
            The data set to use
        view : `None` or `slice` or `tuple`
            Optional view (e.g. slice) through the data to use

        Returns
        -------
        result
            The data associated with comp_to component
        """

        # First we get the values of all the 'from' components.
        args = [data[join_component_view(f, view)] for f in self._from]

        # We keep track of the original shape of the arguments
        original_shape = args[0].shape
        logger.debug("shape of first argument: %s", original_shape)

        # We now unbroadcast the arrays to only compute the link with the
        # smallest number of values we can. This can help for cases where
        # the link depends only on e.g. pixel components or world coordinates
        # that themselves only depend on a subset of pixel components.
        # Unbroadcasting is the act of returning the smallest array that
        # contains all the information needed to be broadcasted back to its
        # full value
        args = [unbroadcast(arg) for arg in args]

        # We now broadcast these to the smallest common shape in case the
        # linking functions don't know how to broadcast arrays with different
        # shapes.
        args = np.broadcast_arrays(*args)

        # We call the actual linking function
        result = self._using(*args)

        # We call asarray since link functions may return Python scalars in some cases
        result = np.asarray(result)

        # In some cases, linking functions return ravelled arrays, so we
        # fix this here.
        logger.debug("shape of result: %s", result.shape)
        if result.shape != args[0].shape:
            logger.debug("ComponentLink function %s changed shape. Fixing",
                         self._using.__name__)
            result.shape = args[0].shape

        # Finally we broadcast the final result to desired shape
        result = broadcast_to(result, original_shape)

        return result
Exemplo n.º 8
0
 def clear(self):
     """
     Remove the layer artist from the visualization
     """
     # We don't want to deallocate here because this can be called if we
     # disable the layer due to incompatible attributes
     self._multivol.set_data(self.id, broadcast_to(0, self._multivol._vol_shape))
Exemplo n.º 9
0
    def compute(self, data, view=None):

        left = self._left
        right = self._right

        if not isinstance(self._left, numbers.Number):
            left = data[self._left, view]
        if not isinstance(self._right, numbers.Number):
            right = data[self._right, view]

        # As described in more detail in ComponentLink.compute, we can
        # 'unbroadcast' the arrays to ensure a minimal operation

        original_shape = None

        if isinstance(left, np.ndarray):
            original_shape = left.shape
            left = unbroadcast(left)

        if isinstance(right, np.ndarray):
            original_shape = right.shape
            right = unbroadcast(right)

        if original_shape is not None:
            left, right = np.broadcast_arrays(left, right)

        result = self._op(left, right)

        if original_shape is None:
            return result
        else:
            return broadcast_to(result, original_shape)
Exemplo n.º 10
0
    def compute(self, data, view=None):
        """
        For a given data set, compute the component comp_to given the data
        associated with each comp_from and the ``using`` function

        This raises an :class:`glue.core.exceptions.IncompatibleAttribute` if the
        data set doesn't have all the ComponentIDs needed for the transformation

        Parameters
        ----------
        data : `~glue.core.data.Data`
            The data set to use
        view : `None` or `slice` or `tuple`
            Optional view (e.g. slice) through the data to use

        Returns
        -------
        result
            The data associated with comp_to component
        """

        # First we get the values of all the 'from' components.
        args = [data[join_component_view(f, view)] for f in self._from]

        # We keep track of the original shape of the arguments
        original_shape = args[0].shape
        logger.debug("shape of first argument: %s", original_shape)

        # We now unbroadcast the arrays to only compute the link with the
        # smallest number of values we can. This can help for cases where
        # the link depends only on e.g. pixel components or world coordinates
        # that themselves only depend on a subset of pixel components.
        # Unbroadcasting is the act of returning the smallest array that
        # contains all the information needed to be broadcasted back to its
        # full value
        args = [unbroadcast(arg) for arg in args]

        # We now broadcast these to the smallest common shape in case the
        # linking functions don't know how to broadcast arrays with different
        # shapes.
        args = np.broadcast_arrays(*args)

        # We call the actual linking function
        result = self._using(*args)

        # We call asarray since link functions may return Python scalars in some cases
        result = np.asarray(result)

        # In some cases, linking functions return ravelled arrays, so we
        # fix this here.
        logger.debug("shape of result: %s", result.shape)
        if result.shape != args[0].shape:
            logger.debug("ComponentLink function %s changed shape. Fixing",
                         self._using.__name__)
            result.shape = args[0].shape

        # Finally we broadcast the final result to desired shape
        result = broadcast_to(result, original_shape)

        return result
Exemplo n.º 11
0
    def compute(self, data, view=None):

        left = self._left
        right = self._right

        if not isinstance(self._left, numbers.Number):
            left = data[self._left, view]
        if not isinstance(self._right, numbers.Number):
            right = data[self._right, view]

        # As described in more detail in ComponentLink.compute, we can
        # 'unbroadcast' the arrays to ensure a minimal operation

        original_shape = None

        if isinstance(left, np.ndarray):
            original_shape = left.shape
            left = unbroadcast(left)

        if isinstance(right, np.ndarray):
            original_shape = right.shape
            right = unbroadcast(right)

        if original_shape is not None:
            left, right = np.broadcast_arrays(left, right)

        result = self._op(left, right)

        if original_shape is None:
            return result
        else:
            return broadcast_to(result, original_shape)
Exemplo n.º 12
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)
Exemplo n.º 13
0
def test_compute_statistic_empty_subset():

    data = Data(x=np.empty((30, 20, 40)))

    # A default subset state should be empty
    subset_state = SubsetState()

    result = data.compute_statistic('mean', data.id['x'], subset_state=subset_state)
    assert_equal(result, np.nan)

    result = data.compute_statistic('maximum', data.id['x'], subset_state=subset_state, axis=1)
    assert_equal(result, broadcast_to(np.nan, (30, 40)))

    result = data.compute_statistic('median', data.id['x'], subset_state=subset_state, axis=(1, 2))
    assert_equal(result, broadcast_to(np.nan, (30)))

    result = data.compute_statistic('sum', data.id['x'], subset_state=subset_state, axis=(0, 1, 2))
    assert_equal(result, np.nan)
Exemplo n.º 14
0
    def to_mask(self, data, view=None):

        # TODO: make sure that pixel components don't actually take up much
        #       memory and are just views

        x = data[self.xatt, view]
        y = data[self.yatt, view]

        # We do the import here to avoid circular imports
        from glue.core.component_id import PixelComponentID

        if (x.ndim == data.ndim and self.xatt in data.pixel_component_ids
                and self.yatt in data.pixel_component_ids):

            # This is a special case - the ROI is defined in pixel space, so we
            # can apply it to a single slice and then broadcast it to all other
            # dimensions. We start off by extracting a slice which takes only
            # the first elements of all dimensions except the attributes in
            # question, for which we take all the elements. We need to preserve
            # the dimensionality of the array, hence the use of slice(0, 1).
            # Note that we can only do this if the view (if present) preserved
            # the dimensionality, which is why we checked that x.ndim == data.ndim

            subset = []
            for i in range(data.ndim):
                if i == self.xatt.axis or i == self.yatt.axis:
                    subset.append(slice(None))
                else:
                    subset.append(slice(0, 1))

            x_slice = x[subset]
            y_slice = y[subset]

            if self.roi.defined():
                result = self.roi.contains(x_slice, y_slice)
            else:
                result = np.zeros(x_slice.shape, dtype=bool)

            result = broadcast_to(result, x.shape)

        else:

            if self.roi.defined():
                result = self.roi.contains(x, y)
            else:
                result = np.zeros(x.shape, dtype=bool)

        if result.shape != x.shape:
            raise ValueError(
                "Unexpected error: boolean mask has incorrect dimensions")

        return result
Exemplo n.º 15
0
    def to_mask(self, data, view=None):

        # TODO: make sure that pixel components don't actually take up much
        #       memory and are just views

        x = data[self.xatt, view]
        y = data[self.yatt, view]

        # We do the import here to avoid circular imports
        from glue.core.component_id import PixelComponentID

        if (x.ndim == data.ndim and
            isinstance(self.xatt, PixelComponentID) and
            isinstance(self.yatt, PixelComponentID)):

            # This is a special case - the ROI is defined in pixel space, so we
            # can apply it to a single slice and then broadcast it to all other
            # dimensions. We start off by extracting a slice which takes only
            # the first elements of all dimensions except the attributes in
            # question, for which we take all the elements. We need to preserve
            # the dimensionality of the array, hence the use of slice(0, 1).
            # Note that we can only do this if the view (if present) preserved
            # the dimensionality, which is why we checked that x.ndim == data.ndim

            subset = []
            for i in range(data.ndim):
                if i == self.xatt.axis or i == self.yatt.axis:
                    subset.append(slice(None))
                else:
                    subset.append(slice(0, 1))

            x_slice = x[subset]
            y_slice = y[subset]

            if self.roi.defined():
                result = self.roi.contains(x_slice, y_slice)
            else:
                result = np.zeros(x_slice.shape, dtype=bool)

            result = broadcast_to(result, x.shape)

        else:

            if self.roi.defined():
                result = self.roi.contains(x, y)
            else:
                result = np.zeros(x.shape, dtype=bool)

        if result.shape != x.shape:
            raise ValueError("Unexpected error: boolean mask has incorrect dimensions")

        return result
Exemplo n.º 16
0
    def __call__(self, bounds):

        if (self.layer_artist is None or self.layer_state is None
                or self.viewer_state is None):
            return broadcast_to(np.nan, self.shape)

        # We should compute the mask even if the layer is not visible as we need
        # the layer to show up properly when it is made visible (which doesn't
        # trigger __getitem__)

        try:
            mask = self.layer_state.get_sliced_data(bounds=bounds)
        except IncompatibleAttribute:
            self.layer_artist.disable_incompatible_subset()
            return broadcast_to(np.nan, self.shape)
        else:
            self.layer_artist.enable(redraw=False)

        r, g, b = color2rgb(self.layer_state.color)
        mask = np.dstack((r * mask, g * mask, b * mask, mask * .5))
        mask = (255 * mask).astype(np.uint8)

        return mask
Exemplo n.º 17
0
def points_inside_poly(x, y, vx, vy):

    if x.dtype.kind == 'M' and vx.dtype.kind == 'M':
        vx = vx.astype(x.dtype).astype(float)
        x = x.astype(float)

    if y.dtype.kind == 'M' and vy.dtype.kind == 'M':
        vy = vy.astype(y.dtype).astype(float)
        y = y.astype(float)

    original_shape = x.shape

    x = unbroadcast(x)
    y = unbroadcast(y)

    x = x.astype(float)
    y = y.astype(float)

    x, y = np.broadcast_arrays(x, y)

    reduced_shape = x.shape

    x = x.flat
    y = y.flat

    from matplotlib.path import Path
    p = Path(np.column_stack((vx, vy)))

    keep = ((x >= np.min(vx)) &
            (x <= np.max(vx)) &
            (y >= np.min(vy)) &
            (y <= np.max(vy)))

    inside = np.zeros(len(x), bool)

    x = x[keep]
    y = y[keep]

    coords = np.column_stack((x, y))

    inside[keep] = p.contains_points(coords).astype(bool)

    good = np.isfinite(x) & np.isfinite(y)
    inside[keep][~good] = False

    inside = inside.reshape(reduced_shape)
    inside = broadcast_to(inside, original_shape)

    return inside
Exemplo n.º 18
0
def points_inside_poly(x, y, vx, vy):

    if x.dtype.kind == 'M' and vx.dtype.kind == 'M':
        vx = vx.astype(x.dtype).astype(float)
        x = x.astype(float)

    if y.dtype.kind == 'M' and vy.dtype.kind == 'M':
        vy = vy.astype(y.dtype).astype(float)
        y = y.astype(float)

    original_shape = x.shape

    x = unbroadcast(x)
    y = unbroadcast(y)

    x = x.astype(float)
    y = y.astype(float)

    x, y = np.broadcast_arrays(x, y)

    reduced_shape = x.shape

    x = x.flat
    y = y.flat

    from matplotlib.path import Path
    p = Path(np.column_stack((vx, vy)))

    keep = ((x >= np.min(vx)) &
            (x <= np.max(vx)) &
            (y >= np.min(vy)) &
            (y <= np.max(vy)))

    inside = np.zeros(len(x), bool)

    x = x[keep]
    y = y[keep]

    coords = np.column_stack((x, y))

    inside[keep] = p.contains_points(coords).astype(bool)

    good = np.isfinite(x) & np.isfinite(y)
    inside[keep][~good] = False

    inside = inside.reshape(reduced_shape)
    inside = broadcast_to(inside, original_shape)

    return inside
Exemplo n.º 19
0
    def __getitem__(self, view=None):

        if (self.layer_artist is None or
                self.layer_state is None or
                self.viewer_state is None):
            return broadcast_to(np.nan, self.shape)

        # We should compute the mask even if the layer is not visible as we need
        # the layer to show up properly when it is made visible (which doesn't
        # trigger __getitem__)

        try:
            mask = self.layer_state.get_sliced_data(view=view)
        except IncompatibleAttribute:
            self.layer_artist.disable_incompatible_subset()
            return broadcast_to(np.nan, self.shape)
        else:
            self.layer_artist.enable(redraw=False)

        r, g, b = color2rgb(self.layer_state.color)
        mask = np.dstack((r * mask, g * mask, b * mask, mask * .5))
        mask = (255 * mask).astype(np.uint8)

        return mask
Exemplo n.º 20
0
    def pixel2world_single_axis(self, *pixel, **kwargs):
        """
        Convert pixel to world coordinates, preserving input type/shape.

        This is a wrapper around pixel2world which returns the result for just
        one axis, and also determines whether the calculation can be sped up
        if broadcasting is present in the input arrays.

        Parameters
        ----------
        *pixel : scalars lists, or Numpy arrays
            The pixel coordinates (0-based) to convert
        axis : int, optional
            If only one axis is needed, it should be specified since the
            calculation will be much more efficient.

        Returns
        -------
        world : `numpy.ndarray`
            The world coordinates for the requested axis
        """

        # PY3: the following is needed for Python 2
        axis = kwargs.get('axis', None)

        if axis is None:
            raise ValueError("axis needs to be set")

        if np.size(pixel[0]) == 0:
            return np.array([], dtype=float)

        original_shape = pixel[0].shape
        pixel_new = []

        # NOTE: the axis passed to this function is the WCS axis not the Numpy
        # axis, so we need to convert it as needed.
        dep_axes = self.dependent_axes(len(pixel) - 1 - axis)
        for ip, p in enumerate(pixel):
            if (len(pixel) - 1 - ip) in dep_axes:
                pixel_new.append(unbroadcast(p))
            else:
                pixel_new.append(p.flat[0])
        pixel = np.broadcast_arrays(*pixel_new)

        result = self.pixel2world(*pixel)

        return broadcast_to(result[axis], original_shape)
Exemplo n.º 21
0
    def pixel2world_single_axis(self, *pixel, **kwargs):
        """
        Convert pixel to world coordinates, preserving input type/shape.

        This is a wrapper around pixel2world which returns the result for just
        one axis, and also determines whether the calculation can be sped up
        if broadcasting is present in the input arrays.

        Parameters
        ----------
        *pixel : scalars lists, or Numpy arrays
            The pixel coordinates (0-based) to convert
        axis : int, optional
            If only one axis is needed, it should be specified since the
            calculation will be much more efficient.

        Returns
        -------
        world : `numpy.ndarray`
            The world coordinates for the requested axis
        """

        # PY3: the following is needed for Python 2
        axis = kwargs.get('axis', None)

        if axis is None:
            raise ValueError("axis needs to be set")

        if np.size(pixel[0]) == 0:
            return np.array([], dtype=float)

        original_shape = pixel[0].shape
        pixel_new = []

        # NOTE: the axis passed to this function is the WCS axis not the Numpy
        # axis, so we need to convert it as needed.
        dep_axes = self.dependent_axes(len(pixel) - 1 - axis)
        for ip, p in enumerate(pixel):
            if (len(pixel) - 1 - ip) in dep_axes:
                pixel_new.append(unbroadcast(p))
            else:
                pixel_new.append(p.flat[0])
        pixel = np.broadcast_arrays(*pixel_new)

        result = self.pixel2world(*pixel)

        return broadcast_to(result[axis], original_shape)
Exemplo n.º 22
0
def pixel2world_single_axis(wcs, *pixel, world_axis=None):
    """
    Convert pixel to world coordinates, preserving input type/shape.

    This is a wrapper around pixel_to_world_values which returns the result for
    just one axis, and also determines whether the calculation can be sped up
    if broadcasting is present in the input arrays.

    Parameters
    ----------
    *pixel : scalars lists, or Numpy arrays
        The pixel coordinates (0-based) to convert
    world_axis : int, optional
        The index of the world coordinate that is needed.

    Returns
    -------
    world : `numpy.ndarray`
        The world coordinates for the requested axis
    """

    if world_axis is None:
        raise ValueError("world_axis needs to be set")

    if np.size(pixel[0]) == 0:
        return np.array([], dtype=float)

    original_shape = pixel[0].shape
    pixel_new = []

    # Now find all the pixel coordinates that are needed to calculate this
    # world coordinate, using the axis correlation matrix
    pixel_dep = wcs.axis_correlation_matrix[world_axis, :]

    for ip, p in enumerate(pixel):
        if pixel_dep[ip]:
            pixel_new.append(unbroadcast(p))
        else:
            pixel_new.append(p.flat[0])
    pixel = np.broadcast_arrays(*pixel_new)

    result = wcs.pixel_to_world_values(*pixel)

    return broadcast_to(result[world_axis], original_shape)
Exemplo n.º 23
0
def world2pixel_single_axis(wcs, *world, pixel_axis=None):
    """
    Convert world to pixel coordinates, preserving input type/shape.

    This is a wrapper around world_to_pixel_values which returns the result for
    just one axis, and also determines whether the calculation can be sped up
    if broadcasting is present in the input arrays.

    Parameters
    ----------
    *world : scalars lists, or Numpy arrays
        The world coordinates to convert
    pixel_axis : int, optional
        The index of the pixel coordinate that is needed.

    Returns
    -------
    pixel : `numpy.ndarray`
        The pixel coordinates for the requested axis
    """

    if pixel_axis is None:
        raise ValueError("pixel_axis needs to be set")

    if np.size(world[0]) == 0:
        return np.array([], dtype=float)

    original_shape = world[0].shape
    world_new = []

    # Now find all the world coordinates that are needed to calculate this
    # world coordinate, using the axis correlation matrix
    world_dep = wcs.axis_correlation_matrix[:, pixel_axis]

    for iw, w in enumerate(world):
        if world_dep[iw]:
            world_new.append(unbroadcast(w))
        else:
            world_new.append(w.flat[0])
    world = np.broadcast_arrays(*world_new)

    result = wcs.world_to_pixel_values(*world)

    return broadcast_to(result[pixel_axis], original_shape)
Exemplo n.º 24
0
Arquivo: wcs.py Projeto: sunn-e/glue
def efficient_pixel_to_pixel(wcs1, wcs2, *inputs):
    """
    Wrapper that performs a pixel -> world -> pixel transformation with two
    WCS instances, and un-broadcasting arrays whenever possible for efficiency.
    """

    # Shortcut for scalars
    if np.isscalar(inputs[0]):
        world_outputs = wcs1.pixel_to_world(*inputs)
        if not isinstance(world_outputs, (tuple, list)):
            world_outputs = (world_outputs, )
        return wcs2.world_to_pixel(*world_outputs)

    # Remember original shape
    original_shape = inputs[0].shape

    matrix = pixel_to_pixel_correlation_matrix(wcs1, wcs2)
    split_info = split_matrix(matrix)

    outputs = [None] * wcs2.pixel_n_dim

    for (pixel_in_indices, pixel_out_indices) in split_info:

        pixel_inputs = []
        for ipix in range(wcs1.pixel_n_dim):
            if ipix in pixel_in_indices:
                pixel_inputs.append(unbroadcast(inputs[ipix]))
            else:
                pixel_inputs.append(inputs[ipix].flat[0])

        pixel_inputs = np.broadcast_arrays(*pixel_inputs)

        world_outputs = wcs1.pixel_to_world(*pixel_inputs)
        if not isinstance(world_outputs, (tuple, list)):
            world_outputs = (world_outputs, )
        pixel_outputs = wcs2.world_to_pixel(*world_outputs)

        for ipix in range(wcs2.pixel_n_dim):
            if ipix in pixel_out_indices:
                outputs[ipix] = broadcast_to(pixel_outputs[ipix],
                                             original_shape)

    return outputs
Exemplo n.º 25
0
Arquivo: wcs.py Projeto: glue-viz/glue
def efficient_pixel_to_pixel(wcs1, wcs2, *inputs):
    """
    Wrapper that performs a pixel -> world -> pixel transformation with two
    WCS instances, and un-broadcasting arrays whenever possible for efficiency.
    """

    # Shortcut for scalars
    if np.isscalar(inputs[0]):
        world_outputs = wcs1.pixel_to_world(*inputs)
        if not isinstance(world_outputs, (tuple, list)):
            world_outputs = (world_outputs,)
        return wcs2.world_to_pixel(*world_outputs)

    # Remember original shape
    original_shape = inputs[0].shape

    matrix = pixel_to_pixel_correlation_matrix(wcs1, wcs2)
    split_info = split_matrix(matrix)

    outputs = [None] * wcs2.pixel_n_dim

    for (pixel_in_indices, pixel_out_indices) in split_info:

        pixel_inputs = []
        for ipix in range(wcs1.pixel_n_dim):
            if ipix in pixel_in_indices:
                pixel_inputs.append(unbroadcast(inputs[ipix]))
            else:
                pixel_inputs.append(inputs[ipix].flat[0])

        pixel_inputs = np.broadcast_arrays(*pixel_inputs)

        world_outputs = wcs1.pixel_to_world(*pixel_inputs)
        if not isinstance(world_outputs, (tuple, list)):
            world_outputs = (world_outputs,)
        pixel_outputs = wcs2.world_to_pixel(*world_outputs)

        for ipix in range(wcs2.pixel_n_dim):
            if ipix in pixel_out_indices:
                outputs[ipix] = broadcast_to(pixel_outputs[ipix], original_shape)

    return outputs
Exemplo n.º 26
0
    def world2pixel_single_axis(self, *world, **kwargs):
        """
        Convert world to pixel coordinates, preserving input type/shape.

        This is a wrapper around world2pixel which returns the result for just
        one axis, and also determines whether the calculation can be sped up
        if broadcasting is present in the input arrays.

        Parameters
        ----------
        *world : scalars lists, or Numpy arrays
            The world coordinates to convert
        axis : int, optional
            If only one axis is needed, it should be specified since the
            calculation will be much more efficient.

        Returns
        -------
        pixel : `numpy.ndarray`
            The pixel coordinates for the requested axis
        """

        # PY3: the following is needed for Python 2
        axis = kwargs.get('axis', None)

        if axis is None:
            raise ValueError("axis needs to be set")

        original_shape = world[0].shape
        world_new = []
        dep_axes = self.dependent_axes(axis)
        for iw, w in enumerate(world):
            if iw in dep_axes:
                world_new.append(unbroadcast(w))
            else:
                world_new.append(w.flat[0])
        world = np.broadcast_arrays(*world_new)

        result = self.world2pixel(*world)

        return broadcast_to(result[axis], original_shape)
Exemplo n.º 27
0
    def world2pixel_single_axis(self, *world, **kwargs):
        """
        Convert world to pixel coordinates, preserving input type/shape.

        This is a wrapper around world2pixel which returns the result for just
        one axis, and also determines whether the calculation can be sped up
        if broadcasting is present in the input arrays.

        Parameters
        ----------
        *world : scalars lists, or Numpy arrays
            The world coordinates to convert
        axis : int, optional
            If only one axis is needed, it should be specified since the
            calculation will be much more efficient.

        Returns
        -------
        pixel : `numpy.ndarray`
            The pixel coordinates for the requested axis
        """

        # PY3: the following is needed for Python 2
        axis = kwargs.get('axis', None)

        if axis is None:
            raise ValueError("axis needs to be set")

        original_shape = world[0].shape
        world_new = []
        dep_axes = self.dependent_axes(axis)
        for iw, w in enumerate(world):
            if iw in dep_axes:
                world_new.append(unbroadcast(w))
            else:
                world_new.append(w.flat[0])
        world = np.broadcast_arrays(*world_new)

        result = self.world2pixel(*world)

        return broadcast_to(result[axis], original_shape)
Exemplo n.º 28
0
    def using(self, *args):

        attr = 'pixel2world_single_axis' if self.pixel2world else 'world2pixel_single_axis'
        func = getattr(self.coords, attr)

        # NOTE: in the past, we set any non-specified arguemnts to 0 for the
        # input coordinates, but this caused issues because in astropy.wcs
        # if one specifies e.g. (0, 0, 3000.) for (ra, dec, velocity), and if
        # (0, 0) for RA/Dec would return (nan, nan) normally, the velocity
        # is also NaN even though it is decoupled from the other coordinates.
        default = self.coords.default_world_coords(self.ndim)

        args2 = [None] * self.ndim
        for f, a in zip(self.from_needed, args):
            args2[f] = a
        for i in range(self.ndim):
            if args2[i] is None:
                args2[i] = broadcast_to(default[self.ndim - 1 - i], args[0].shape)
        args2 = tuple(args2)

        return func(*args2[::-1], axis=self.ndim - 1 - self.index)
Exemplo n.º 29
0
    def _calculate(self, view=None):

        if self.world:

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

            pix_coords = []
            dep_coords = self._data.coords.dependent_axes(self.axis)
            for i in range(self._data.ndim):
                if i in dep_coords:
                    pix_coords.append(np.arange(self._data.shape[i]))
                else:
                    pix_coords.append(0)
            pix_coords = np.meshgrid(*pix_coords, indexing='ij', copy=False)

            world_coords = self._data.coords.pixel2world_single_axis(*pix_coords[::-1],
                                                               axis=self._data.ndim - 1 - self.axis)

            world_coords = broadcast_to(world_coords, self._data.shape)

            if view is None:
                view = Ellipsis

            # FIXME: this is inefficient if view is only a small fraction of the
            # whole array, and should be optimized.

            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]
Exemplo n.º 30
0
    def _calculate(self, view=None):

        if self.world:

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

            pix_coords = []
            dep_coords = self._data.coords.dependent_axes(self.axis)
            for i in range(self._data.ndim):
                if i in dep_coords:
                    pix_coords.append(np.arange(self._data.shape[i]))
                else:
                    pix_coords.append(0)
            pix_coords = np.meshgrid(*pix_coords, indexing='ij', copy=False)

            world_coords = self._data.coords.pixel2world_single_axis(
                *pix_coords[::-1], axis=self._data.ndim - 1 - self.axis)

            world_coords = broadcast_to(world_coords, self._data.shape)

            if view is None:
                view = Ellipsis

            # FIXME: this is inefficient if view is only a small fraction of the
            # whole array, and should be optimized.

            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]
Exemplo n.º 31
0
 def to_mask(self, data, view=None):
     shp = view_shape(data.shape, view)
     return broadcast_to(False, shp)
Exemplo n.º 32
0
def compute_fixed_resolution_buffer(data,
                                    bounds,
                                    target_data=None,
                                    target_cid=None,
                                    subset_state=None,
                                    broadcast=True,
                                    cache_id=None):
    """
    Get a fixed-resolution buffer for a dataset.

    Parameters
    ----------
    data : `~glue.core.Data`
        The dataset from which to extract a fixed resolution buffer
    bounds : list
        The list of bounds for the fixed resolution buffer. This list should
        have as many items as there are dimensions in ``target_data``. Each
        item should either be a scalar value, or a tuple of ``(min, max, nsteps)``.
    target_data : `~glue.core.Data`, optional
        The data in whose frame of reference the bounds are defined. Defaults
        to ``data``.
    target_cid : `~glue.core.component_id.ComponentID`, optional
        If specified, gives the component ID giving the component to use for the
        data values. Alternatively, use ``subset_state`` to get a subset mask.
    subset_state : `~glue.core.subset.SubsetState`, optional
        If specified, gives the subset state for which to compute a mask.
        Alternatively, use ``target_cid`` if you want to get data values.
    broadcast : bool, optional
        If `True`, then if a dimension in ``target_data`` for which ``bounds``
        is not a scalar does not affect any of the dimensions in ``data``,
        then the final array will be effectively broadcast along this
        dimension, otherwise an error will be raised.
    """

    if target_data is None:
        target_data = data

    if target_cid is None and subset_state is None:
        raise ValueError(
            "Either target_cid or subset_state should be specified")

    if target_cid is not None and subset_state is not None:
        raise ValueError(
            "Either target_cid or subset_state should be specified (not both)")

    # If cache_id is specified, we keep a cached version of the resulting array
    # indexed by cache_id as well as a hash formed of the call arguments to this
    # function. We then check if the resulting array already exists in the cache.

    if cache_id is not None:

        if subset_state is None:
            # Use uuid for component ID since otherwise component IDs don't return
            # False when comparing two different CIDs (instead they return a subset state).
            # For bounds we use a special wrapper that can identify wildcards.
            current_array_hash = (data, bounds, target_data, target_cid.uuid,
                                  broadcast)
        else:
            current_array_hash = (data, bounds, target_data, subset_state,
                                  broadcast)

        current_pixel_hash = (data, target_data)

        if cache_id in ARRAY_CACHE:
            if ARRAY_CACHE[cache_id]['hash'] == current_array_hash:
                return ARRAY_CACHE[cache_id]['array']

        # To save time later, if the pixel cache doesn't match at the level of the
        # data and target_data, we just reset the cache.
        if cache_id in PIXEL_CACHE:
            if PIXEL_CACHE[cache_id]['hash'] != current_pixel_hash:
                PIXEL_CACHE.pop(cache_id)

    # Start off by generating arrays of coordinates in the original dataset
    pixel_coords = [
        np.linspace(*bound) if isinstance(bound, tuple) else bound
        for bound in bounds
    ]
    pixel_coords = np.meshgrid(*pixel_coords, indexing='ij', copy=False)

    # Keep track of the original shape of these arrays
    original_shape = pixel_coords[0].shape

    # Now loop through the dimensions of 'data' to find the corresponding
    # coordinates in the frame of view of this dataset.

    translated_coords = []
    dimensions_all = []

    invalid_all = np.zeros(original_shape, dtype=bool)

    for ipix, pix in enumerate(data.pixel_component_ids):

        # At this point, if cache_id is in PIXEL_CACHE, we know that data and
        # target_data match so we just check the bounds. Note that the bounds
        # include the AnyScalar wildcard for any dimensions that don't impact
        # the pixel coordinates here. We do this so that we don't have to
        # recompute the pixel coordinates when e.g. slicing through cubes.

        if cache_id in PIXEL_CACHE and ipix in PIXEL_CACHE[
                cache_id] and PIXEL_CACHE[cache_id][ipix]['bounds'] == bounds:

            translated_coord = PIXEL_CACHE[cache_id][ipix]['translated_coord']
            dimensions = PIXEL_CACHE[cache_id][ipix]['dimensions']
            invalid = PIXEL_CACHE[cache_id][ipix]['invalid']

        else:

            translated_coord, dimensions = translate_pixel(
                target_data, pixel_coords, pix)

            # The returned coordinates may often be a broadcasted array. To convert
            # the coordinates to integers and check which ones are within bounds, we
            # thus operate on the un-broadcasted array, before broadcasting it back
            # to the original shape.
            translated_coord = np.round(
                unbroadcast(translated_coord)).astype(int)
            invalid = (translated_coord < 0) | (translated_coord >=
                                                data.shape[ipix])

            # Since we are going to be using these coordinates later on to index an
            # array, we need the coordinates to be within the array, so we reset
            # any invalid coordinates and keep track of which pixels are invalid
            # to reset them later.
            translated_coord[invalid] = 0

            # We now populate the cache
            if cache_id is not None:

                if cache_id not in PIXEL_CACHE:
                    PIXEL_CACHE[cache_id] = {'hash': current_pixel_hash}

                PIXEL_CACHE[cache_id][ipix] = {
                    'translated_coord': translated_coord,
                    'dimensions': dimensions,
                    'invalid': invalid,
                    'bounds': bounds_for_cache(bounds, dimensions)
                }

        invalid_all |= invalid

        # Broadcast back to the original shape and add to the list
        translated_coords.append(broadcast_to(translated_coord,
                                              original_shape))

        # Also keep track of all the dimensions that contributed to this coordinate
        dimensions_all.extend(dimensions)

    translated_coords = tuple(translated_coords)

    # If a dimension from the target data for which bounds was set to an interval
    # did not actually contribute to any of the coordinates in data, then if
    # broadcast is set to False we raise an error, otherwise we proceed and
    # implicitly broadcast values along that dimension of the target data.

    if data is not target_data and not broadcast:
        for i in range(target_data.ndim):
            if isinstance(bounds[i], tuple) and i not in dimensions_all:
                raise IncompatibleDataException()

    # PERF: optimize further - check if we can extract a sub-region that
    # contains all the valid values.

    # Take subset_state into account, if present
    if subset_state is None:
        array = data.get_data(target_cid, view=translated_coords).astype(float)
        invalid_value = -np.inf
    else:
        array = data.get_mask(subset_state, view=translated_coords)
        invalid_value = False

    if np.any(invalid_all):
        if not array.flags.writeable:
            array = np.array(array, dtype=type(invalid_value))
        array[invalid_all] = invalid_value

    # Drop dimensions for which bounds were scalars
    slices = []
    for bound in bounds:
        if isinstance(bound, tuple):
            slices.append(slice(None))
        else:
            slices.append(0)

    array = array[tuple(slices)]

    if cache_id is not None:

        # For the bounds, we use a special wildcard for bounds that don't affect
        # the result. This will allow the cache to match regardless of the
        # value for those bounds. However, we only do this for scalar bounds.

        cache_bounds = bounds_for_cache(bounds, dimensions_all)

        current_array_hash = current_array_hash[:1] + (
            cache_bounds, ) + current_array_hash[2:]

        if subset_state is None:
            ARRAY_CACHE[cache_id] = {
                'hash': current_array_hash,
                'array': array
            }
        else:
            ARRAY_CACHE[cache_id] = {
                'hash': current_array_hash,
                'array': array
            }

    return array
Exemplo n.º 33
0
    def _update_visual_attributes(self, changed, force=False):

        if not self.enabled:
            return

        if self.state.markers_visible:

            if self.state.density_map:

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        self.density_artist.set_color(self.state.color)
                        self.density_artist.set_clim(
                            self.density_auto_limits.min,
                            self.density_auto_limits.max)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = ensure_numerical(
                        self.layer[self.state.cmap_att].ravel())
                    set_mpl_artist_cmap(self.density_artist, c, self.state)

                if force or 'stretch' in changed:
                    self.density_artist.set_norm(
                        ImageNormalize(
                            stretch=STRETCHES[self.state.stretch]()))

                if force or 'dpi' in changed:
                    self.density_artist.set_dpi(self._viewer_state.dpi)

                if force or 'density_contrast' in changed:
                    self.density_auto_limits.contrast = self.state.density_contrast
                    self.density_artist.stale = True

            else:

                if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed':

                    if force or 'color' in changed or 'fill' in changed:
                        if self.state.fill:
                            self.plot_artist.set_markeredgecolor('none')
                            self.plot_artist.set_markerfacecolor(
                                self.state.color)
                        else:
                            self.plot_artist.set_markeredgecolor(
                                self.state.color)
                            self.plot_artist.set_markerfacecolor('none')

                    if force or 'size' in changed or 'size_scaling' in changed:
                        self.plot_artist.set_markersize(
                            self.state.size * self.state.size_scaling)

                else:

                    # TEMPORARY: Matplotlib has a bug that causes set_alpha to
                    # change the colors back: https://github.com/matplotlib/matplotlib/issues/8953
                    if 'alpha' in changed:
                        force = True

                    if self.state.cmap_mode == 'Fixed':
                        if force or 'color' in changed or 'cmap_mode' in changed or 'fill' in changed:
                            self.scatter_artist.set_array(None)
                            if self.state.fill:
                                self.scatter_artist.set_facecolors(
                                    self.state.color)
                                self.scatter_artist.set_edgecolors('none')
                            else:
                                self.scatter_artist.set_facecolors('none')
                                self.scatter_artist.set_edgecolors(
                                    self.state.color)
                    elif force or any(
                            prop in changed
                            for prop in CMAP_PROPERTIES) or 'fill' in changed:
                        self.scatter_artist.set_edgecolors(None)
                        self.scatter_artist.set_facecolors(None)
                        c = ensure_numerical(
                            self.layer[self.state.cmap_att].ravel())
                        set_mpl_artist_cmap(self.scatter_artist, c, self.state)
                        if self.state.fill:
                            self.scatter_artist.set_edgecolors('none')
                        else:
                            colors = self.scatter_artist.get_facecolors()
                            self.scatter_artist.set_facecolors('none')
                            self.scatter_artist.set_edgecolors(colors)

                    if force or any(prop in changed
                                    for prop in MARKER_PROPERTIES):

                        if self.state.size_mode == 'Fixed':
                            s = self.state.size * self.state.size_scaling
                            s = broadcast_to(
                                s,
                                self.scatter_artist.get_sizes().shape)
                        else:
                            s = ensure_numerical(
                                self.layer[self.state.size_att].ravel())
                            s = ((s - self.state.size_vmin) /
                                 (self.state.size_vmax - self.state.size_vmin))
                            # The following ensures that the sizes are in the
                            # range 3 to 30 before the final size_scaling.
                            np.clip(s, 0, 1, out=s)
                            s *= 0.95
                            s += 0.05
                            s *= (30 * self.state.size_scaling)

                        # Note, we need to square here because for scatter, s is actually
                        # proportional to the marker area, not radius.
                        self.scatter_artist.set_sizes(s**2)

        if self.state.line_visible:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.line_collection.set_linearcolor(
                        color=self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                # Higher up we oversampled the points in the line so that
                # half a segment on either side of each point has the right
                # color, so we need to also oversample the color here.
                c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                self.line_collection.set_linearcolor(data=c, state=self.state)

            if force or 'linewidth' in changed:
                self.line_collection.set_linewidth(self.state.linewidth)

            if force or 'linestyle' in changed:
                self.line_collection.set_linestyle(self.state.linestyle)

        if self.state.vector_visible and self.vector_artist is not None:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.vector_artist.set_array(None)
                    self.vector_artist.set_color(self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                set_mpl_artist_cmap(self.vector_artist, c, self.state)

        if self.state.xerr_visible or self.state.yerr_visible:

            for eartist in ravel_artists(self.errorbar_artist):

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        eartist.set_color(self.state.color)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = ensure_numerical(
                        self.layer[self.state.cmap_att].ravel()).copy()
                    c = c[self._errorbar_keep]
                    set_mpl_artist_cmap(eartist, c, self.state)

                if force or 'alpha' in changed:
                    eartist.set_alpha(self.state.alpha)

                if force or 'visible' in changed:
                    eartist.set_visible(self.state.visible)

                if force or 'zorder' in changed:
                    eartist.set_zorder(self.state.zorder)

        for artist in [
                self.scatter_artist, self.plot_artist, self.vector_artist,
                self.line_collection, self.density_artist
        ]:

            if artist is None:
                continue

            if force or 'alpha' in changed:
                artist.set_alpha(self.state.alpha)

            if force or 'zorder' in changed:
                artist.set_zorder(self.state.zorder)

            if force or 'visible' in changed:
                # We need to hide the density artist if it is not needed because
                # otherwise it might still show even if there is no data as the
                # neutral/zero color might not be white.
                if artist is self.density_artist:
                    artist.set_visible(self.state.visible
                                       and self.state.density_map
                                       and self.state.markers_visible)
                else:
                    artist.set_visible(self.state.visible)

        self.redraw()
Exemplo n.º 34
0
def compute_fixed_resolution_buffer(data, bounds, target_data=None, target_cid=None,
                                    subset_state=None, broadcast=True, cache_id=None):
    """
    Get a fixed-resolution buffer for a dataset.

    Parameters
    ----------
    data : `~glue.core.Data`
        The dataset from which to extract a fixed resolution buffer
    bounds : list
        The list of bounds for the fixed resolution buffer. This list should
        have as many items as there are dimensions in ``target_data``. Each
        item should either be a scalar value, or a tuple of ``(min, max, nsteps)``.
    target_data : `~glue.core.Data`, optional
        The data in whose frame of reference the bounds are defined. Defaults
        to ``data``.
    target_cid : `~glue.core.component_id.ComponentID`, optional
        If specified, gives the component ID giving the component to use for the
        data values. Alternatively, use ``subset_state`` to get a subset mask.
    subset_state : `~glue.core.subset.SubsetState`, optional
        If specified, gives the subset state for which to compute a mask.
        Alternatively, use ``target_cid`` if you want to get data values.
    broadcast : bool, optional
        If `True`, then if a dimension in ``target_data`` for which ``bounds``
        is not a scalar does not affect any of the dimensions in ``data``,
        then the final array will be effectively broadcast along this
        dimension, otherwise an error will be raised.
    """

    if target_data is None:
        target_data = data

    if target_cid is None and subset_state is None:
        raise ValueError("Either target_cid or subset_state should be specified")

    if target_cid is not None and subset_state is not None:
        raise ValueError("Either target_cid or subset_state should be specified (not both)")

    # If cache_id is specified, we keep a cached version of the resulting array
    # indexed by cache_id as well as a hash formed of the call arguments to this
    # function. We then check if the resulting array already exists in the cache.

    if cache_id is not None:

        if subset_state is None:
            # Use uuid for component ID since otherwise component IDs don't return
            # False when comparing two different CIDs (instead they return a subset state).
            # For bounds we use a special wrapper that can identify wildcards.
            current_array_hash = (data, bounds, target_data, target_cid.uuid, broadcast)
        else:
            current_array_hash = (data, bounds, target_data, subset_state, broadcast)

        current_pixel_hash = (data, target_data)

        if cache_id in ARRAY_CACHE:
            if ARRAY_CACHE[cache_id]['hash'] == current_array_hash:
                return ARRAY_CACHE[cache_id]['array']

        # To save time later, if the pixel cache doesn't match at the level of the
        # data and target_data, we just reset the cache.
        if cache_id in PIXEL_CACHE:
            if PIXEL_CACHE[cache_id]['hash'] != current_pixel_hash:
                PIXEL_CACHE.pop(cache_id)

    # Start off by generating arrays of coordinates in the original dataset
    pixel_coords = [np.linspace(*bound) if isinstance(bound, tuple) else bound for bound in bounds]
    pixel_coords = np.meshgrid(*pixel_coords, indexing='ij', copy=False)

    # Keep track of the original shape of these arrays
    original_shape = pixel_coords[0].shape

    # Now loop through the dimensions of 'data' to find the corresponding
    # coordinates in the frame of view of this dataset.

    translated_coords = []
    dimensions_all = []

    invalid_all = np.zeros(original_shape, dtype=bool)

    for ipix, pix in enumerate(data.pixel_component_ids):

        # At this point, if cache_id is in PIXEL_CACHE, we know that data and
        # target_data match so we just check the bounds. Note that the bounds
        # include the AnyScalar wildcard for any dimensions that don't impact
        # the pixel coordinates here. We do this so that we don't have to
        # recompute the pixel coordinates when e.g. slicing through cubes.

        if cache_id in PIXEL_CACHE and ipix in PIXEL_CACHE[cache_id] and PIXEL_CACHE[cache_id][ipix]['bounds'] == bounds:

            translated_coord = PIXEL_CACHE[cache_id][ipix]['translated_coord']
            dimensions = PIXEL_CACHE[cache_id][ipix]['dimensions']
            invalid = PIXEL_CACHE[cache_id][ipix]['invalid']

        else:

            translated_coord, dimensions = translate_pixel(target_data, pixel_coords, pix)

            # The returned coordinates may often be a broadcasted array. To convert
            # the coordinates to integers and check which ones are within bounds, we
            # thus operate on the un-broadcasted array, before broadcasting it back
            # to the original shape.
            translated_coord = np.round(unbroadcast(translated_coord)).astype(int)
            invalid = (translated_coord < 0) | (translated_coord >= data.shape[ipix])

            # Since we are going to be using these coordinates later on to index an
            # array, we need the coordinates to be within the array, so we reset
            # any invalid coordinates and keep track of which pixels are invalid
            # to reset them later.
            translated_coord[invalid] = 0

            # We now populate the cache
            if cache_id is not None:

                if cache_id not in PIXEL_CACHE:
                    PIXEL_CACHE[cache_id] = {'hash': current_pixel_hash}

                PIXEL_CACHE[cache_id][ipix] = {'translated_coord': translated_coord,
                                               'dimensions': dimensions,
                                               'invalid': invalid,
                                               'bounds': bounds_for_cache(bounds, dimensions)}

        invalid_all |= invalid

        # Broadcast back to the original shape and add to the list
        translated_coords.append(broadcast_to(translated_coord, original_shape))

        # Also keep track of all the dimensions that contributed to this coordinate
        dimensions_all.extend(dimensions)

    translated_coords = tuple(translated_coords)

    # If a dimension from the target data for which bounds was set to an interval
    # did not actually contribute to any of the coordinates in data, then if
    # broadcast is set to False we raise an error, otherwise we proceed and
    # implicitly broadcast values along that dimension of the target data.

    if data is not target_data and not broadcast:
        for i in range(target_data.ndim):
            if isinstance(bounds[i], tuple) and i not in dimensions_all:
                raise IncompatibleDataException()

    # PERF: optimize further - check if we can extract a sub-region that
    # contains all the valid values.

    # Take subset_state into account, if present
    if subset_state is None:
        array = data.get_data(target_cid, view=translated_coords).astype(float)
        invalid_value = -np.inf
    else:
        array = data.get_mask(subset_state, view=translated_coords)
        invalid_value = False

    if np.any(invalid_all):
        if not array.flags.writeable:
            array = np.array(array, dtype=type(invalid_value))
        array[invalid_all] = invalid_value

    # Drop dimensions for which bounds were scalars
    slices = []
    for bound in bounds:
        if isinstance(bound, tuple):
            slices.append(slice(None))
        else:
            slices.append(0)

    array = array[tuple(slices)]

    if cache_id is not None:

        # For the bounds, we use a special wildcard for bounds that don't affect
        # the result. This will allow the cache to match regardless of the
        # value for those bounds. However, we only do this for scalar bounds.

        cache_bounds = bounds_for_cache(bounds, dimensions_all)

        current_array_hash = current_array_hash[:1] + (cache_bounds,) + current_array_hash[2:]

        if subset_state is None:
            ARRAY_CACHE[cache_id] = {'hash': current_array_hash, 'array': array}
        else:
            ARRAY_CACHE[cache_id] = {'hash': current_array_hash, 'array': array}

    return array
Exemplo n.º 35
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):
                return np.array(self._data.coords.pixel2world(*view[::-1])[::-1])[self.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 = self._data.coords.dependent_axes(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 = self._data.coords.pixel2world_single_axis(*pix_coords[::-1],
                                                                     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]
Exemplo n.º 36
0
    def _update_visual_attributes(self, changed, force=False):

        if not self.enabled:
            return

        if self.state.style == 'Scatter':

            if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed':

                if force or 'color' in changed:
                    self.plot_artist.set_color(self.state.color)

                if force or 'size' in changed or 'size_scaling' in changed:
                    self.plot_artist.set_markersize(self.state.size *
                                                    self.state.size_scaling)

                artist = self.plot_artist

            else:

                # TEMPORARY: Matplotlib has a bug that causes set_alpha to
                # change the colors back: https://github.com/matplotlib/matplotlib/issues/8953
                if 'alpha' in changed:
                    force = True

                if force or any(prop in changed for prop in CMAP_PROPERTIES):

                    if self.state.cmap_mode == 'Fixed':
                        c = self.state.color
                        vmin = vmax = cmap = None
                    else:
                        c = self.layer[self.state.cmap_att].ravel()
                        vmin = self.state.cmap_vmin
                        vmax = self.state.cmap_vmax
                        cmap = self.state.cmap

                    if self.state.cmap_mode == 'Fixed':
                        self.scatter_artist.set_facecolors(c)
                    else:
                        self.scatter_artist.set_array(c)
                        self.scatter_artist.set_cmap(cmap)
                        if vmin > vmax:
                            self.scatter_artist.set_clim(vmax, vmin)
                            self.scatter_artist.set_norm(InvertedNormalize(vmax, vmin))
                        else:
                            self.scatter_artist.set_clim(vmin, vmax)
                            self.scatter_artist.set_norm(Normalize(vmin, vmax))

                    self.scatter_artist.set_edgecolor('none')

                if force or any(prop in changed for prop in SIZE_PROPERTIES):

                    if self.state.size_mode == 'Fixed':
                        s = self.state.size * self.state.size_scaling
                        s = broadcast_to(s, self.scatter_artist.get_sizes().shape)
                    else:
                        s = self.layer[self.state.size_att].ravel()
                        s = ((s - self.state.size_vmin) /
                             (self.state.size_vmax - self.state.size_vmin)) * 30
                        s *= self.state.size_scaling

                    # Note, we need to square here because for scatter, s is actually
                    # proportional to the marker area, not radius.
                    self.scatter_artist.set_sizes(s ** 2)

                artist = self.scatter_artist

            if self.state.xerr_visible or self.state.yerr_visible:

                for eartist in list(self.errorbar_artist[2]):

                    if eartist is None:
                        continue

                    if force or 'color' in changed:
                        eartist.set_color(self.state.color)

                    if force or 'alpha' in changed:
                        eartist.set_alpha(self.state.alpha)

                    if force or 'visible' in changed:
                        eartist.set_visible(self.state.visible)

                    if force or 'zorder' in changed:
                        eartist.set_zorder(self.state.zorder)

        elif self.state.style == 'Line':

            if force or 'color' in changed:
                self.line_artist.set_color(self.state.color)

            if force or 'linewidth' in changed:
                self.line_artist.set_linewidth(self.state.linewidth)

            if force or 'linestyle' in changed:
                self.line_artist.set_linestyle(self.state.linestyle)

            artist = self.line_artist

        else:

            raise NotImplementedError(self.state.style)  # pragma: nocover

        if force or 'alpha' in changed:
            artist.set_alpha(self.state.alpha)

        if force or 'zorder' in changed:
            artist.set_zorder(self.state.zorder)

        if force or 'visible' in changed:
            artist.set_visible(self.state.visible)

        self.redraw()
Exemplo n.º 37
0
    def _update_visual_attributes(self, changed, force=False):

        if not self.enabled:
            return

        if self.state.markers_visible:

            if self.state.density_map:

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        self.density_artist.set_color(self.state.color)
                        self.density_artist.set_clim(self.density_auto_limits.min,
                                                     self.density_auto_limits.max)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                    set_mpl_artist_cmap(self.density_artist, c, self.state)

                if force or 'stretch' in changed:
                    self.density_artist.set_norm(ImageNormalize(stretch=STRETCHES[self.state.stretch]()))

                if force or 'dpi' in changed:
                    self.density_artist.set_dpi(self._viewer_state.dpi)

                if force or 'density_contrast' in changed:
                    self.density_auto_limits.contrast = self.state.density_contrast
                    self.density_artist.stale = True

            else:

                if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed':

                    if force or 'color' in changed or 'fill' in changed:
                        if self.state.fill:
                            self.plot_artist.set_markeredgecolor('none')
                            self.plot_artist.set_markerfacecolor(self.state.color)
                        else:
                            self.plot_artist.set_markeredgecolor(self.state.color)
                            self.plot_artist.set_markerfacecolor('none')

                    if force or 'size' in changed or 'size_scaling' in changed:
                        self.plot_artist.set_markersize(self.state.size *
                                                        self.state.size_scaling)

                else:

                    # TEMPORARY: Matplotlib has a bug that causes set_alpha to
                    # change the colors back: https://github.com/matplotlib/matplotlib/issues/8953
                    if 'alpha' in changed:
                        force = True

                    if self.state.cmap_mode == 'Fixed':
                        if force or 'color' in changed or 'cmap_mode' in changed or 'fill' in changed:
                            self.scatter_artist.set_array(None)
                            if self.state.fill:
                                self.scatter_artist.set_facecolors(self.state.color)
                                self.scatter_artist.set_edgecolors('none')
                            else:
                                self.scatter_artist.set_facecolors('none')
                                self.scatter_artist.set_edgecolors(self.state.color)
                    elif force or any(prop in changed for prop in CMAP_PROPERTIES) or 'fill' in changed:
                        self.scatter_artist.set_edgecolors(None)
                        self.scatter_artist.set_facecolors(None)
                        c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                        set_mpl_artist_cmap(self.scatter_artist, c, self.state)
                        if self.state.fill:
                            self.scatter_artist.set_edgecolors('none')
                        else:
                            colors = self.scatter_artist.get_facecolors()
                            self.scatter_artist.set_facecolors('none')
                            self.scatter_artist.set_edgecolors(colors)

                    if force or any(prop in changed for prop in MARKER_PROPERTIES):

                        if self.state.size_mode == 'Fixed':
                            s = self.state.size * self.state.size_scaling
                            s = broadcast_to(s, self.scatter_artist.get_sizes().shape)
                        else:
                            s = ensure_numerical(self.layer[self.state.size_att].ravel())
                            s = ((s - self.state.size_vmin) /
                                 (self.state.size_vmax - self.state.size_vmin))
                            # The following ensures that the sizes are in the
                            # range 3 to 30 before the final size_scaling.
                            np.clip(s, 0, 1, out=s)
                            s *= 0.95
                            s += 0.05
                            s *= (30 * self.state.size_scaling)

                        # Note, we need to square here because for scatter, s is actually
                        # proportional to the marker area, not radius.
                        self.scatter_artist.set_sizes(s ** 2)

        if self.state.line_visible:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.line_collection.set_linearcolor(color=self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                # Higher up we oversampled the points in the line so that
                # half a segment on either side of each point has the right
                # color, so we need to also oversample the color here.
                c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                self.line_collection.set_linearcolor(data=c, state=self.state)

            if force or 'linewidth' in changed:
                self.line_collection.set_linewidth(self.state.linewidth)

            if force or 'linestyle' in changed:
                self.line_collection.set_linestyle(self.state.linestyle)

        if self.state.vector_visible and self.vector_artist is not None:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.vector_artist.set_array(None)
                    self.vector_artist.set_color(self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                set_mpl_artist_cmap(self.vector_artist, c, self.state)

        if self.state.xerr_visible or self.state.yerr_visible:

            for eartist in ravel_artists(self.errorbar_artist):

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        eartist.set_color(self.state.color)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = ensure_numerical(self.layer[self.state.cmap_att].ravel())
                    set_mpl_artist_cmap(eartist, c, self.state)

                if force or 'alpha' in changed:
                    eartist.set_alpha(self.state.alpha)

                if force or 'visible' in changed:
                    eartist.set_visible(self.state.visible)

                if force or 'zorder' in changed:
                    eartist.set_zorder(self.state.zorder)

        for artist in [self.scatter_artist, self.plot_artist,
                       self.vector_artist, self.line_collection,
                       self.density_artist]:

            if artist is None:
                continue

            if force or 'alpha' in changed:
                artist.set_alpha(self.state.alpha)

            if force or 'zorder' in changed:
                artist.set_zorder(self.state.zorder)

            if force or 'visible' in changed:
                artist.set_visible(self.state.visible)

        self.redraw()
Exemplo n.º 38
0
    def _update_visual_attributes(self, changed, force=False):

        if not self.enabled:
            return

        if self.state.markers_visible:

            if self.state.density_map:

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        self.density_artist.set_color(self.state.color)
                        self.density_artist.set_c(None)
                        self.density_artist.set_clim(
                            self.density_auto_limits.min,
                            self.density_auto_limits.max)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = self.layer[self.state.cmap_att].ravel()
                    set_mpl_artist_cmap(self.density_artist, c, self.state)

                if force or 'stretch' in changed:
                    self.density_artist.set_norm(
                        ImageNormalize(
                            stretch=STRETCHES[self.state.stretch]()))

                if force or 'dpi' in changed:
                    self.density_artist.set_dpi(self._viewer_state.dpi)

                if force or 'density_contrast' in changed:
                    self.density_auto_limits.contrast = self.state.density_contrast
                    self.density_artist.stale = True

            else:

                if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed':

                    if force or 'color' in changed:
                        self.plot_artist.set_color(self.state.color)

                    if force or 'size' in changed or 'size_scaling' in changed:
                        self.plot_artist.set_markersize(
                            self.state.size * self.state.size_scaling)

                else:

                    # TEMPORARY: Matplotlib has a bug that causes set_alpha to
                    # change the colors back: https://github.com/matplotlib/matplotlib/issues/8953
                    if 'alpha' in changed:
                        force = True

                    if self.state.cmap_mode == 'Fixed':
                        if force or 'color' in changed or 'cmap_mode' in changed:
                            self.scatter_artist.set_facecolors(
                                self.state.color)
                            self.scatter_artist.set_edgecolor('none')
                    elif force or any(prop in changed
                                      for prop in CMAP_PROPERTIES):
                        c = self.layer[self.state.cmap_att].ravel()
                        set_mpl_artist_cmap(self.scatter_artist, c, self.state)
                        self.scatter_artist.set_edgecolor('none')

                    if force or any(prop in changed
                                    for prop in MARKER_PROPERTIES):

                        if self.state.size_mode == 'Fixed':
                            s = self.state.size * self.state.size_scaling
                            s = broadcast_to(
                                s,
                                self.scatter_artist.get_sizes().shape)
                        else:
                            s = self.layer[self.state.size_att].ravel()
                            s = ((s - self.state.size_vmin) /
                                 (self.state.size_vmax -
                                  self.state.size_vmin)) * 30
                            s *= self.state.size_scaling

                        # Note, we need to square here because for scatter, s is actually
                        # proportional to the marker area, not radius.
                        self.scatter_artist.set_sizes(s**2)

        if self.state.line_visible:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.line_collection.set_array(None)
                    self.line_collection.set_color(self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                # Higher up we oversampled the points in the line so that
                # half a segment on either side of each point has the right
                # color, so we need to also oversample the color here.
                c = self.layer[self.state.cmap_att].ravel()
                cnew = np.zeros((len(c) - 1) * 2)
                cnew[::2] = c[:-1]
                cnew[1::2] = c[1:]
                set_mpl_artist_cmap(self.line_collection, cnew, self.state)

            if force or 'linewidth' in changed:
                self.line_collection.set_linewidth(self.state.linewidth)

            if force or 'linestyle' in changed:
                self.line_collection.set_linestyle(self.state.linestyle)

        if self.state.vector_visible and self.vector_artist is not None:

            if self.state.cmap_mode == 'Fixed':
                if force or 'color' in changed or 'cmap_mode' in changed:
                    self.vector_artist.set_array(None)
                    self.vector_artist.set_color(self.state.color)
            elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                c = self.layer[self.state.cmap_att].ravel()
                set_mpl_artist_cmap(self.vector_artist, c, self.state)

        if self.state.xerr_visible or self.state.yerr_visible:

            for eartist in list(self.errorbar_artist[2]):

                if eartist is None:
                    continue

                if self.state.cmap_mode == 'Fixed':
                    if force or 'color' in changed or 'cmap_mode' in changed:
                        eartist.set_color(self.state.color)
                elif force or any(prop in changed for prop in CMAP_PROPERTIES):
                    c = self.layer[self.state.cmap_att].ravel()
                    set_mpl_artist_cmap(eartist, c, self.state)

                if force or 'alpha' in changed:
                    eartist.set_alpha(self.state.alpha)

                if force or 'visible' in changed:
                    eartist.set_visible(self.state.visible)

                if force or 'zorder' in changed:
                    eartist.set_zorder(self.state.zorder)

        for artist in [
                self.scatter_artist, self.plot_artist, self.vector_artist,
                self.line_collection, self.density_artist
        ]:

            if artist is None:
                continue

            if force or 'alpha' in changed:
                artist.set_alpha(self.state.alpha)

            if force or 'zorder' in changed:
                artist.set_zorder(self.state.zorder)

            if force or 'visible' in changed:
                artist.set_visible(self.state.visible)

        self.redraw()