示例#1
0
class EELSConfig(t.HasTraits):
    eels_gos_files_path = t.Directory(guess_gos_path(),
        label = 'GOS directory',
        desc = 'The GOS files are required to create the EELS edge components')
    fine_structure_width = t.CFloat(30,
        label = 'Fine structure lenght',
        desc = 'The default lenght of the fine structure from the edge onset')
    fine_structure_active = t.CBool(False,
        label = 'Enable fine structure',
        desc = "If enabled, the regions of the EELS spectrum defined as fine "
               "structure will be fitted with a spline. Please note that it "
               "enabling this feature only makes sense when the model is "
               "convolved to account for multiple scattering")
    fine_structure_smoothing = t.Range(0., 1., value = 0.3,
        label = 'Fine structure smoothing factor',
        desc = 'The lower the value the smoother the fine structure spline fit')
    synchronize_cl_with_ll = t.CBool(False)
    preedge_safe_window_width = t.CFloat(2,
        label = 'Pre-onset region (in eV)',
        desc = 'Some functions needs to define the regions between two '
               'ionisation edges. Due to limited energy resolution or chemical '
               'shift, the region is limited on its higher energy side by '
               'the next ionisation edge onset minus an offset defined by this '
               'parameters')
    min_distance_between_edges_for_fine_structure = t.CFloat(0,
        label = 'Minimum distance between edges',
        desc = 'When automatically setting the fine structure energy regions, '
               'the fine structure of an EELS edge component is automatically '
               'disable if the next ionisation edge onset distance to the '
               'higher energy side of the fine structure region is lower that '
               'the value of this parameter')
示例#2
0
class PlotConfig(t.HasTraits):
    saturated_pixels = t.CFloat(
        0.,
        label='Saturated pixels (deprecated)',
        desc='Warning: this is deprecated and will be removed in HyperSpy v2.0'
    )
    # Don't use t.Enum to list all possible matplotlib colormap to
    # avoid importing matplotlib and building the list of colormap
    # when importing hyperpsy
    cmap_navigator = t.Str(
        'gray',
        label='Color map navigator',
        desc='Set the default color map for the navigator.',
    )
    cmap_signal = t.Str(
        'gray',
        label='Color map signal',
        desc='Set the default color map for the signal plot.',
    )
    dims_024_increase = t.Str('right', label='Navigate right')
    dims_024_decrease = t.Str(
        'left',
        label='Navigate left',
    )
    dims_135_increase = t.Str(
        'down',
        label='Navigate down',
    )
    dims_135_decrease = t.Str(
        'up',
        label='Navigate up',
    )
    modifier_dims_01 = t.Enum(
        [
            'ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 1st and 2nd dimensions')  # 0 elem is default
    modifier_dims_23 = t.Enum(
        [
            'shift', 'alt', 'ctrl', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 3rd and 4th dimensions')  # 0 elem is default
    modifier_dims_45 = t.Enum(
        [
            'alt', 'ctrl', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 5th and 6th dimensions')  # 0 elem is default
    pick_tolerance = t.CFloat(
        7.5,
        label='Pick tolerance',
        desc='The pick tolerance of ROIs in screen pixels.')
示例#3
0
class PlotConfig(t.HasTraits):
    saturated_pixels = t.CFloat(0.05,
                                label='Saturated pixels',
                                desc='Set the default saturated pixels value '
                                'for plotting images.'
                                )
    cmap_navigator = t.Enum(list(cmap_d.keys()),
                            label='Color map navigator',
                            desc='Set the default color map for the navigator.',
                            )
    cmap_signal = t.Enum(list(cmap_d.keys()),
                         label='Color map signal',
                         desc='Set the default color map for the signal plot.',
                         )
    dims_024_increase = t.Str('right',
                              label='Navigate right'
                              )
    dims_024_decrease = t.Str('left',
                              label='Navigate left',
                              )
    dims_135_increase = t.Str('down',
                              label='Navigate down',
                              )
    dims_135_decrease = t.Str('up',
                              label='Navigate up',
                              )
    modifier_dims_01 = t.Enum(['ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 1st and 2nd dimensions')  # 0 elem is default
    modifier_dims_23 = t.Enum(['shift', 'alt', 'ctrl', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 3rd and 4th dimensions')  # 0 elem is default
    modifier_dims_45 = t.Enum(['alt', 'ctrl', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 5th and 6th dimensions')  # 0 elem is default
示例#4
0
class EDSConfig(t.HasTraits):
    eds_mn_ka = t.CFloat(130.,
        label = 'Energy resolution at Mn Ka (eV)',
        desc = 'default value for FWHM of the Mn Ka peak in eV,'
                'This value is used as a first approximation'
                'of the energy resolution of the detector.')
    eds_tilt_stage = t.CFloat(0.,
        label = 'Stage tilt',
        desc = 'default value for the stage tilt in degree.')
    eds_detector_azimuth = t.CFloat(0.,
        label = 'Azimuth angle',
        desc = 'default value for the azimuth angle in degree. If the azimuth'
                ' is zero, the detector is perpendicular to the tilt axis.')
    eds_detector_elevation = t.CFloat(35.,
        label = 'Elevation angle',
        desc = 'default value for the elevation angle in degree.')  
示例#5
0
文件: roi.py 项目: realxnl/hyperspy
class Point2DROI(BasePointROI):
    """Selects a single point in a 2D space. The coordinates of the point in
    the 2D space are stored in the traits 'x' and 'y'.

    `Point2DROI` can be used in place of a tuple containing the coordinates
    of the point `(x, y)`.


    Example
    -------

    >>> roi = hs.roi.Point2DROI(3, 5)
    >>> x, y = roi
    >>> print(x, y)
    3 5

    """
    x, y = (t.CFloat(t.Undefined), ) * 2
    _ndim = 2

    def __init__(self, x, y):
        super(Point2DROI, self).__init__()
        self.x, self.y = x, y

    def __getitem__(self, *args, **kwargs):
        _tuple = (self.x, self.y)
        return _tuple.__getitem__(*args, **kwargs)

    def is_valid(self):
        return t.Undefined not in (self.x, self.y)

    def _x_changed(self, old, new):
        self.update()

    def _y_changed(self, old, new):
        self.update()

    def _get_ranges(self):
        ranges = (
            (self.x, ),
            (self.y, ),
        )
        return ranges

    def _set_from_widget(self, widget):
        self.x, self.y = widget.position

    def _apply_roi2widget(self, widget):
        widget.position = (self.x, self.y)

    def _get_widget_type(self, axes, signal):
        return widgets.SquareWidget

    def __repr__(self):
        return "%s(x=%g, y=%g)" % (self.__class__.__name__, self.x, self.y)
示例#6
0
文件: roi.py 项目: realxnl/hyperspy
class Point1DROI(BasePointROI):
    """Selects a single point in a 1D space. The coordinate of the point in the
    1D space is stored in the 'value' trait.

    `Point1DROI` can be used in place of a tuple containing the value of `value`.


    Example
    -------

    >>> roi = hs.roi.Point1DROI(0.5) 
    >>> value, = roi
    >>> print(value)
    0.5

    """
    value = t.CFloat(t.Undefined)
    _ndim = 1

    def __init__(self, value):
        super(Point1DROI, self).__init__()
        self.value = value

    def __getitem__(self, *args, **kwargs):
        _tuple = (self.value, )
        return _tuple.__getitem__(*args, **kwargs)

    def is_valid(self):
        return self.value != t.Undefined

    def _value_changed(self, old, new):
        self.update()

    def _get_ranges(self):
        ranges = ((self.value, ), )
        return ranges

    def _set_from_widget(self, widget):
        self.value = widget.position[0]

    def _apply_roi2widget(self, widget):
        widget.position = (self.value, )

    def _get_widget_type(self, axes, signal):
        direction = guess_vertical_or_horizontal(axes=axes, signal=signal)
        if direction == "vertical":
            return widgets.VerticalLineWidget
        elif direction == "horizontal":
            return widgets.HorizontalLineWidget
        else:
            raise ValueError("direction must be either horizontal or vertical")

    def __repr__(self):
        return "%s(value=%g)" % (self.__class__.__name__, self.value)
示例#7
0
class Point1DROI(BasePointROI):

    """Selects a single point in a 1D space. The coordinate of the point in the
    1D space is stored in the 'value' trait.
    """
    value = t.CFloat(t.Undefined)
    _ndim = 1

    def __init__(self, value):
        super(Point1DROI, self).__init__()
        self.value = value

    def is_valid(self):
        return self.value != t.Undefined

    def _value_changed(self, old, new):
        self.update()

    def _get_ranges(self):
        ranges = ((self.value,),)
        return ranges

    def _set_from_widget(self, widget):
        self.value = widget.position[0]

    def _apply_roi2widget(self, widget):
        widget.position = (self.value,)

    def _get_widget_type(self, axes, signal):
        # Figure out whether to use horizontal or veritcal line:
        if axes[0].navigate:
            plotdim = len(signal._plot.navigator_data_function().shape)
            axdim = signal.axes_manager.navigation_dimension
            idx = signal.axes_manager.navigation_axes.index(axes[0])
        else:
            plotdim = len(signal._plot.signal_data_function().shape)
            axdim = signal.axes_manager.signal_dimension
            idx = signal.axes_manager.signal_axes.index(axes[0])

        if plotdim == 2:  # Plot is an image
            # axdim == 1 and plotdim == 2 indicates "spectrum stack"
            if idx == 0 and axdim != 1:    # Axis is horizontal
                return widgets.VerticalLineWidget
            else:  # Axis is vertical
                return widgets.HorizontalLineWidget
        elif plotdim == 1:  # It is a spectrum
            return widgets.VerticalLineWidget
        else:
            raise ValueError("Could not find valid widget type")

    def __repr__(self):
        return "%s(value=%g)" % (
            self.__class__.__name__,
            self.value)
示例#8
0
文件: roi.py 项目: askorikov/hyperspy
class SpanROI(BaseInteractiveROI):
    """Selects a range in a 1D space. The coordinates of the range in
    the 1D space are stored in the traits 'left' and 'right'.
    """
    left, right = (t.CFloat(t.Undefined), ) * 2
    _ndim = 1

    def __init__(self, left, right):
        super(SpanROI, self).__init__()
        self._bounds_check = True  # Use reponsibly!
        self.left, self.right = left, right

    def is_valid(self):
        return (t.Undefined not in (self.left, self.right)
                and self.right >= self.left)

    def _right_changed(self, old, new):
        if self._bounds_check and \
                self.left is not t.Undefined and new <= self.left:
            self.right = old
        else:
            self.update()

    def _left_changed(self, old, new):
        if self._bounds_check and \
                self.right is not t.Undefined and new >= self.right:
            self.left = old
        else:
            self.update()

    def _get_ranges(self):
        ranges = ((self.left, self.right), )
        return ranges

    def _set_from_widget(self, widget):
        value = (widget.position[0], widget.position[0] + widget.size[0])
        self.left, self.right = value

    def _apply_roi2widget(self, widget):
        widget.set_bounds(left=self.left, right=self.right)

    def _get_widget_type(self, axes, signal):
        direction = guess_vertical_or_horizontal(axes=axes, signal=signal)
        if direction == "vertical":
            return partial(widgets.RangeWidget, direction="horizontal")
        elif direction == "horizontal":
            return partial(widgets.RangeWidget, direction="vertical")
        else:
            raise ValueError("direction must be either horizontal or vertical")

    def __repr__(self):
        return "%s(left=%g, right=%g)" % (self.__class__.__name__, self.left,
                                          self.right)
示例#9
0
class OdinEmbeddedMeta(Schema):
    """OdinEmbeddedMeta info that can be stored in a schema bundle."""
    #: Subject code
    subject = traits.CBytes(desc='subject code', maxlen=16)

    #: Time of creation
    timestamp = traits.CFloat(desc='unix timestamp')

    #: Number of embedded channels
    num_channels = traits.CInt(desc='number of channels')

    #: Number of classifiers
    num_classifiers = traits.CInt(desc='number of classifiers')
示例#10
0
class PlotConfig(t.HasTraits):
    saturated_pixels = t.CFloat(
        0.,
        label='Saturated pixels (deprecated)',
        desc='Warning: this is deprecated and will be removed in HyperSpy v2.0'
    )
    cmap_navigator = t.Enum(
        plt.colormaps(),
        label='Color map navigator',
        desc='Set the default color map for the navigator.',
    )
    cmap_signal = t.Enum(
        plt.colormaps(),
        label='Color map signal',
        desc='Set the default color map for the signal plot.',
    )
    dims_024_increase = t.Str('right', label='Navigate right')
    dims_024_decrease = t.Str(
        'left',
        label='Navigate left',
    )
    dims_135_increase = t.Str(
        'down',
        label='Navigate down',
    )
    dims_135_decrease = t.Str(
        'up',
        label='Navigate up',
    )
    modifier_dims_01 = t.Enum(
        [
            'ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 1st and 2nd dimensions')  # 0 elem is default
    modifier_dims_23 = t.Enum(
        [
            'shift', 'alt', 'ctrl', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 3rd and 4th dimensions')  # 0 elem is default
    modifier_dims_45 = t.Enum(
        [
            'alt', 'ctrl', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
            'ctrl+alt+shift'
        ],
        label='Modifier key for 5th and 6th dimensions')  # 0 elem is default
示例#11
0
文件: roi.py 项目: askorikov/hyperspy
class Point2DROI(BasePointROI):
    """Selects a single point in a 2D space. The coordinates of the point in
    the 2D space are stored in the traits 'x' and 'y'.
    """
    x, y = (t.CFloat(t.Undefined), ) * 2
    _ndim = 2

    def __init__(self, x, y):
        super(Point2DROI, self).__init__()
        self.x, self.y = x, y

    def is_valid(self):
        return t.Undefined not in (self.x, self.y)

    def _x_changed(self, old, new):
        self.update()

    def _y_changed(self, old, new):
        self.update()

    def _get_ranges(self):
        ranges = (
            (self.x, ),
            (self.y, ),
        )
        return ranges

    def _set_from_widget(self, widget):
        self.x, self.y = widget.position

    def _apply_roi2widget(self, widget):
        widget.position = (self.x, self.y)

    def _get_widget_type(self, axes, signal):
        return widgets.SquareWidget

    def __repr__(self):
        return "%s(x=%g, y=%g)" % (self.__class__.__name__, self.x, self.y)
示例#12
0
文件: roi.py 项目: askorikov/hyperspy
class Point1DROI(BasePointROI):
    """Selects a single point in a 1D space. The coordinate of the point in the
    1D space is stored in the 'value' trait.
    """
    value = t.CFloat(t.Undefined)
    _ndim = 1

    def __init__(self, value):
        super(Point1DROI, self).__init__()
        self.value = value

    def is_valid(self):
        return self.value != t.Undefined

    def _value_changed(self, old, new):
        self.update()

    def _get_ranges(self):
        ranges = ((self.value, ), )
        return ranges

    def _set_from_widget(self, widget):
        self.value = widget.position[0]

    def _apply_roi2widget(self, widget):
        widget.position = (self.value, )

    def _get_widget_type(self, axes, signal):
        direction = guess_vertical_or_horizontal(axes=axes, signal=signal)
        if direction == "vertical":
            return widgets.VerticalLineWidget
        elif direction == "horizontal":
            return widgets.HorizontalLineWidget
        else:
            raise ValueError("direction must be either horizontal or vertical")

    def __repr__(self):
        return "%s(value=%g)" % (self.__class__.__name__, self.value)
示例#13
0
class PlotConfig(t.HasTraits):
    saturated_pixels = t.CFloat(0.05,
                                label='Saturated pixels',
                                desc='Set the default saturated_pixels for '
                                'plotting images.'
                                )
    dims_024_increase = t.Str('right',
                              label='Navigate right'
                              )
    dims_024_decrease = t.Str('left',
                              label='Navigate left',
                              )
    dims_135_increase = t.Str('down',
                              label='Navigate down',
                              )
    dims_135_decrease = t.Str('up',
                              label='Navigate up',
                              )
    modifier_dims_01 = t.Enum(['ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 1st and 2nd dimensions')  # 0 elem is default
    modifier_dims_23 = t.Enum(['shift', 'alt', 'ctrl', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 3rd and 4th dimensions')  # 0 elem is default
    modifier_dims_45 = t.Enum(['alt', 'ctrl', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift',
                               'ctrl+alt+shift'], label='Modifier key for 5th and 6th dimensions')  # 0 elem is default
示例#14
0
文件: roi.py 项目: temcode/hyperspy
class Line2DROI(BaseInteractiveROI):

    x1, y1, x2, y2, linewidth = (t.CFloat(t.Undefined), ) * 5
    _ndim = 2

    def __init__(self, x1, y1, x2, y2, linewidth):
        super(Line2DROI, self).__init__()
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
        self.linewidth = linewidth

    def is_valid(self):
        return t.Undefined not in (self.x1, self.y1, self.x2, self.y2)

    def _x1_changed(self, old, new):
        self.update()

    def _x2_changed(self, old, new):
        self.update()

    def _y1_changed(self, old, new):
        self.update()

    def _y2_changed(self, old, new):
        self.update()

    def _linewidth_changed(self, old, new):
        self.update()

    def _set_from_widget(self, widget):
        """Sets the internal representation of the ROI from the passed widget,
        without doing anything to events.
        """
        c = widget.position
        s = widget.size[0]
        (self.x1, self.y1), (self.x2, self.y2) = c
        self.linewidth = s

    def _apply_roi2widget(self, widget):
        widget.position = (self.x1, self.y1), (self.x2, self.y2)
        widget.size = np.array([self.linewidth])

    def _get_widget_type(self, axes, signal):
        return widgets.Line2DWidget

    @staticmethod
    def _line_profile_coordinates(src, dst, linewidth=1):
        """Return the coordinates of the profile of an image along a scan line.
        Parameters
        ----------
        src : 2-tuple of numeric scalar (float or int)
            The start point of the scan line.
        dst : 2-tuple of numeric scalar (float or int)
            The end point of the scan line.
        linewidth : int, optional
            Width of the scan, perpendicular to the line
        Returns
        -------
        coords : array, shape (2, N, C), float
            The coordinates of the profile along the scan line. The length of
            the profile is the ceil of the computed length of the scan line.
        Notes
        -----
        This is a utility method meant to be used internally by skimage
        functions. The destination point is included in the profile, in
        contrast to standard numpy indexing.
        """
        src_row, src_col = src = np.asarray(src, dtype=float)
        dst_row, dst_col = dst = np.asarray(dst, dtype=float)
        d_row, d_col = dst - src
        theta = np.arctan2(d_row, d_col)

        length = np.ceil(np.hypot(d_row, d_col) + 1).astype(int)
        # we add one above because we include the last point in the profile
        # (in contrast to standard numpy indexing)
        line_col = np.linspace(src_col, dst_col, length)
        line_row = np.linspace(src_row, dst_row, length)

        data = np.zeros((2, length, int(linewidth)))
        data[0, :, :] = np.tile(line_col, [linewidth, 1]).T
        data[1, :, :] = np.tile(line_row, [linewidth, 1]).T

        if linewidth != 1:
            # we subtract 1 from linewidth to change from pixel-counting
            # (make this line 3 pixels wide) to point distances (the
            # distance between pixel centers)
            col_width = (linewidth - 1) * np.sin(-theta) / 2
            row_width = (linewidth - 1) * np.cos(theta) / 2
            row_off = np.linspace(-row_width, row_width, linewidth)
            col_off = np.linspace(-col_width, col_width, linewidth)
            data[0, :, :] += np.tile(col_off, [length, 1])
            data[1, :, :] += np.tile(row_off, [length, 1])
        return data

    @property
    def length(self):
        p0 = np.array((self.x1, self.y1), dtype=np.float)
        p1 = np.array((self.x2, self.y2), dtype=np.float)
        d_row, d_col = p1 - p0
        return np.hypot(d_row, d_col)

    @staticmethod
    def profile_line(img,
                     src,
                     dst,
                     axes,
                     linewidth=1,
                     order=1,
                     mode='constant',
                     cval=0.0):
        """Return the intensity profile of an image measured along a scan line.
        Parameters
        ----------
        img : numeric array, shape (M, N[, C])
            The image, either grayscale (2D array) or multichannel
            (3D array, where the final axis contains the channel
            information).
        src : 2-tuple of numeric scalar (float or int)
            The start point of the scan line.
        dst : 2-tuple of numeric scalar (float or int)
            The end point of the scan line.
        linewidth : int, optional
            Width of the scan, perpendicular to the line
        order : int in {0, 1, 2, 3, 4, 5}, optional
            The order of the spline interpolation to compute image values at
            non-integer coordinates. 0 means nearest-neighbor interpolation.
        mode : string, one of {'constant', 'nearest', 'reflect', 'wrap'},
                optional
            How to compute any values falling outside of the image.
        cval : float, optional
            If `mode` is 'constant', what constant value to use outside the
            image.
        Returns
        -------
        return_value : array
            The intensity profile along the scan line. The length of the
            profile is the ceil of the computed length of the scan line.
        Examples
        --------
        >>> x = np.array([[1, 1, 1, 2, 2, 2]])
        >>> img = np.vstack([np.zeros_like(x), x, x, x, np.zeros_like(x)])
        >>> img
        array([[0, 0, 0, 0, 0, 0],
               [1, 1, 1, 2, 2, 2],
               [1, 1, 1, 2, 2, 2],
               [1, 1, 1, 2, 2, 2],
               [0, 0, 0, 0, 0, 0]])
        >>> profile_line(img, (2, 1), (2, 4))
        array([ 1.,  1.,  2.,  2.])
        Notes
        -----
        The destination point is included in the profile, in contrast to
        standard numpy indexing.
        """

        import scipy.ndimage as nd
        p0 = ((src[0] - axes[0].offset) / axes[0].scale,
              (src[1] - axes[1].offset) / axes[1].scale)
        p1 = ((dst[0] - axes[0].offset) / axes[0].scale,
              (dst[1] - axes[1].offset) / axes[1].scale)
        perp_lines = Line2DROI._line_profile_coordinates(p0,
                                                         p1,
                                                         linewidth=linewidth)
        if img.ndim > 2:
            img = np.rollaxis(img, axes[0].index_in_array, 0)
            img = np.rollaxis(img, axes[1].index_in_array, 1)
            orig_shape = img.shape
            img = np.reshape(img,
                             orig_shape[0:2] + (np.product(orig_shape[2:]), ))
            pixels = [
                nd.map_coordinates(img[..., i],
                                   perp_lines,
                                   order=order,
                                   mode=mode,
                                   cval=cval) for i in xrange(img.shape[2])
            ]
            i0 = min(axes[0].index_in_array, axes[1].index_in_array)
            pixels = np.transpose(np.asarray(pixels), (1, 2, 0))
            intensities = pixels.mean(axis=1)
            intensities = np.rollaxis(
                np.reshape(intensities,
                           intensities.shape[0:1] + orig_shape[2:]), 0, i0)
        else:
            pixels = nd.map_coordinates(img,
                                        perp_lines,
                                        order=order,
                                        mode=mode,
                                        cval=cval)
            intensities = pixels.mean(axis=1)

        return intensities

    def __call__(self, signal, axes=None, order=0):
        """Slice the signal according to the ROI, and return it.

        Arguments
        ---------
        signal : Signal
            The signal to slice with the ROI.
        axes : specification of axes to use, default = None
            The axes argument specifies which axes the ROI will be applied on.
            The items in the collection can be either of the following:
                * a tuple of:
                    - DataAxis. These will not be checked with
                      signal.axes_manager.
                    - anything that will index signal.axes_manager
                * For any other value, it will check whether the navigation
                  space can fit the right number of axis, and use that if it
                  fits. If not, it will try the signal space.
        """
        if axes is None and signal in self.signal_map:
            axes = self.signal_map[signal][1]
        else:
            axes = self._parse_axes(axes, signal.axes_manager)
        linewidth = self.linewidth / np.min([ax.scale for ax in axes])
        profile = Line2DROI.profile_line(signal.data, (self.x1, self.y1),
                                         (self.x2, self.y2),
                                         axes=axes,
                                         linewidth=linewidth,
                                         order=order)
        length = np.linalg.norm(np.diff(np.array(
            ((self.x1, self.y1), (self.x2, self.y2))),
                                        axis=0),
                                axis=1)[0]
        axm = signal.axes_manager.deepcopy()
        idx = []
        for ax in axes:
            idx.append(ax.index_in_axes_manager)
        for i in reversed(sorted(idx)):  # Remove in reversed order
            axm.remove(i)
        axis = DataAxis(len(profile),
                        scale=length / len(profile),
                        units=axes[0].units,
                        navigate=axes[0].navigate)
        axis.axes_manager = axm
        i0 = min(axes[0].index_in_array, axes[1].index_in_array)
        axm._axes.insert(i0, axis)
        from hyperspy.signals import Signal
        roi = Signal(profile,
                     axes=axm._get_axes_dicts(),
                     metadata=signal.metadata.deepcopy().as_dictionary(),
                     original_metadata=signal.original_metadata.deepcopy().
                     as_dictionary())
        return roi

    def __repr__(self):
        return "%s(x1=%g, y1=%g, x2=%g, y2=%g, linewidth=%g)" % (
            self.__class__.__name__, self.x1, self.y1, self.x2, self.y2,
            self.linewidth)
示例#15
0
class Parameter(t.HasTraits):
    """Model parameter

    Attributes
    ----------
    value : float or array
        The value of the parameter for the current location. The value
        for other locations is stored in map.
    bmin, bmax: float
        Lower and upper bounds of the parameter value.
    twin : {None, Parameter}
        If it is not None, the value of the current parameter is
        a function of the given Parameter. The function is by default
        the identity function, but it can be defined by twin_function
    twin_function : function
        Function that, if selt.twin is not None, takes self.twin.value
        as its only argument and returns a float or array that is
        returned when getting Parameter.value
    twin_inverse_function : function
        The inverse of twin_function. If it is None then it is not
        possible to set the value of the parameter twin by setting
        the value of the current parameter.
    ext_force_positive : bool
        If True, the parameter value is set to be the absolute value
        of the input value i.e. if we set Parameter.value = -3, the
        value stored is 3 instead. This is useful to bound a value
        to be positive in an optimization without actually using an
        optimizer that supports bounding.
    ext_bounded : bool
        Similar to ext_force_positive, but in this case the bounds are
        defined by bmin and bmax. It is a better idea to use
        an optimizer that supports bounding though.

    Methods
    -------
    as_signal(field = 'values')
        Get a parameter map as a signal object
    plot()
        Plots the value of the Parameter at all locations.
    export(folder=None, name=None, format=None, save_std=False)
        Saves the value of the parameter map to the specified format
    connect, disconnect(function)
        Call the functions connected when the value attribute changes.

    """
    __number_of_elements = 1
    __value = 0
    __free = True
    _bounds = (None, None)
    __twin = None
    _axes_manager = None
    __ext_bounded = False
    __ext_force_positive = False

    # traitsui bugs out trying to make an editor for this, so always specify!
    # (it bugs out, because both editor shares the object, and Array editors
    # don't like non-sequence objects). TextEditor() works well, so does
    # RangeEditor() as it works with bmin/bmax.
    value = t.Property(t.Either([t.CFloat(0), Array()]))

    units = t.Str('')
    free = t.Property(t.CBool(True))

    bmin = t.Property(NoneFloat(), label="Lower bounds")
    bmax = t.Property(NoneFloat(), label="Upper bounds")

    def __init__(self):
        self._twins = set()
        self.connected_functions = list()
        self.twin_function = lambda x: x
        self.twin_inverse_function = lambda x: x
        self.std = None
        self.component = None
        self.grad = None
        self.name = ''
        self.map = None
        self.model = None

    def __repr__(self):
        text = ''
        text += 'Parameter %s' % self.name
        if self.component is not None:
            text += ' of %s' % self.component._get_short_description()
        text = '<' + text + '>'
        return text.encode('utf8')

    def __len__(self):
        return self._number_of_elements

    def connect(self, f):
        if f not in self.connected_functions:
            self.connected_functions.append(f)
            if self.twin:
                self.twin.connect(f)

    def disconnect(self, f):
        if f in self.connected_functions:
            self.connected_functions.remove(f)
            if self.twin:
                self.twin.disconnect(f)

    def _get_value(self):
        if self.twin is None:
            return self.__value
        else:
            return self.twin_function(self.twin.value)

    def _set_value(self, arg):
        try:
            # Use try/except instead of hasattr("__len__") because a numpy
            # memmap has a __len__ wrapper even for numbers that raises a
            # TypeError when calling. See issue #349.
            if len(arg) != self._number_of_elements:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
            else:
                if not isinstance(arg, tuple):
                    arg = tuple(arg)
        except TypeError:
            if self._number_of_elements != 1:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
        old_value = self.__value

        if self.twin is not None:
            if self.twin_inverse_function is not None:
                self.twin.value = self.twin_inverse_function(arg)
            return

        if self.ext_bounded is False:
            self.__value = arg
        else:
            if self.ext_force_positive is True:
                arg = np.abs(arg)
            if self._number_of_elements == 1:
                if self.bmin is not None and arg <= self.bmin:
                    self.__value = self.bmin
                elif self.bmax is not None and arg >= self.bmax:
                    self.__value = self.bmax
                else:
                    self.__value = arg
            else:
                bmin = (self.bmin if self.bmin is not None else -np.inf)
                bmax = (self.bmax if self.bmin is not None else np.inf)
                self.__value = np.clip(arg, bmin, bmax)

        if (self._number_of_elements != 1
                and not isinstance(self.__value, tuple)):
            self.__value = tuple(self.__value)
        if old_value != self.__value:
            for f in self.connected_functions:
                try:
                    f()
                except:
                    self.disconnect(f)
        self.trait_property_changed('value', old_value, self.__value)

    # Fix the parameter when coupled
    def _get_free(self):
        if self.twin is None:
            return self.__free
        else:
            return False

    def _set_free(self, arg):
        old_value = self.__free
        self.__free = arg
        if self.component is not None:
            self.component._update_free_parameters()
        self.trait_property_changed('free', old_value, self.__free)

    def _set_twin(self, arg):
        if arg is None:
            if self.twin is not None:
                # Store the value of the twin in order to set the
                # value of the parameter when it is uncoupled
                twin_value = self.value
                if self in self.twin._twins:
                    self.twin._twins.remove(self)
                    for f in self.connected_functions:
                        self.twin.disconnect(f)

                self.__twin = arg
                self.value = twin_value
        else:
            if self not in arg._twins:
                arg._twins.add(self)
                for f in self.connected_functions:
                    arg.connect(f)
            self.__twin = arg

        if self.component is not None:
            self.component._update_free_parameters()

    def _get_twin(self):
        return self.__twin

    twin = property(_get_twin, _set_twin)

    def _get_bmin(self):
        if self._number_of_elements == 1:
            return self._bounds[0]
        else:
            return self._bounds[0][0]

    def _set_bmin(self, arg):
        old_value = self.bmin
        if self._number_of_elements == 1:
            self._bounds = (arg, self.bmax)
        else:
            self._bounds = ((arg, self.bmax), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmin', old_value, arg)

    def _get_bmax(self):
        if self._number_of_elements == 1:
            return self._bounds[1]
        else:
            return self._bounds[0][1]

    def _set_bmax(self, arg):
        old_value = self.bmax
        if self._number_of_elements == 1:
            self._bounds = (self.bmin, arg)
        else:
            self._bounds = ((self.bmin, arg), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmax', old_value, arg)

    @property
    def _number_of_elements(self):
        return self.__number_of_elements

    @_number_of_elements.setter
    def _number_of_elements(self, arg):
        # Do nothing if the number of arguments stays the same
        if self.__number_of_elements == arg:
            return
        if arg <= 1:
            raise ValueError("Please provide an integer number equal "
                             "or greater to 1")
        self._bounds = ((self.bmin, self.bmax), ) * arg
        self.__number_of_elements = arg

        if arg == 1:
            self._Parameter__value = 0
        else:
            self._Parameter__value = (0, ) * arg
        if self.component is not None:
            self.component.update_number_parameters()

    @property
    def ext_bounded(self):
        return self.__ext_bounded

    @ext_bounded.setter
    def ext_bounded(self, arg):
        if arg is not self.__ext_bounded:
            self.__ext_bounded = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    @property
    def ext_force_positive(self):
        return self.__ext_force_positive

    @ext_force_positive.setter
    def ext_force_positive(self, arg):
        if arg is not self.__ext_force_positive:
            self.__ext_force_positive = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    def store_current_value_in_array(self):
        """Store the value and std attributes.

        See also
        --------
        fetch, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        self.map['values'][indices] = self.value
        self.map['is_set'][indices] = True
        if self.std is not None:
            self.map['std'][indices] = self.std

    def fetch(self):
        """Fetch the stored value and std attributes.


        See Also
        --------
        store_current_value_in_array, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        if self.map['is_set'][indices]:
            self.value = self.map['values'][indices]
            self.std = self.map['std'][indices]

    def assign_current_value_to_all(self, mask=None):
        '''Assign the current value attribute to all the  indices

        Parameters
        ----------
        mask: {None, boolean numpy array}
            Set only the indices that are not masked i.e. where
            mask is False.

        See Also
        --------
        store_current_value_in_array, fetch

        '''
        if mask is None:
            mask = np.zeros(self.map.shape, dtype='bool')
        self.map['values'][mask == False] = self.value
        self.map['is_set'][mask == False] = True

    def _create_array(self):
        """Create the map array to store the information in
        multidimensional datasets.

        """
        shape = self._axes_manager._navigation_shape_in_array
        if not shape:
            shape = [
                1,
            ]
        dtype_ = np.dtype([('values', 'float', self._number_of_elements),
                           ('std', 'float', self._number_of_elements),
                           ('is_set', 'bool', 1)])
        if (self.map is None or self.map.shape != shape
                or self.map.dtype != dtype_):
            self.map = np.zeros(shape, dtype_)
            self.map['std'].fill(np.nan)
            # TODO: in the future this class should have access to
            # axes manager and should be able to fetch its own
            # values. Until then, the next line is necessary to avoid
            # erros when self.std is defined and the shape is different
            # from the newly defined arrays
            self.std = None

    def as_signal(self, field='values'):
        """Get a parameter map as a signal object.

        Please note that this method only works when the navigation
        dimension is greater than 0.

        Parameters
        ----------
        field : {'values', 'std', 'is_set'}

        Raises
        ------

        NavigationDimensionError : if the navigation dimension is 0

        """
        from hyperspy.signal import Signal
        if self._axes_manager.navigation_dimension == 0:
            raise NavigationDimensionError(0, '>0')

        s = Signal(data=self.map[field],
                   axes=self._axes_manager._get_navigation_axes_dicts())
        if self.component.active_is_multidimensional:
            s.data[np.logical_not(self.component._active_array)] = np.nan
        s.metadata.General.title = ("%s parameter" %
                                    self.name if self.component is None else
                                    "%s parameter of %s component" %
                                    (self.name, self.component.name))
        for axis in s.axes_manager._axes:
            axis.navigate = False
        if self._number_of_elements > 1:
            s.axes_manager.append_axis(size=self._number_of_elements,
                                       name=self.name,
                                       navigate=True)
        return s

    def plot(self):
        self.as_signal().plot()

    def export(self, folder=None, name=None, format=None, save_std=False):
        '''Save the data to a file.

        All the arguments are optional.

        Parameters
        ----------
        folder : str or None
            The path to the folder where the file will be saved.
             If `None` the current folder is used by default.
        name : str or None
            The name of the file. If `None` the Components name followed
             by the Parameter `name` attributes will be used by default.
              If a file with the same name exists the name will be
              modified by appending a number to the file path.
        save_std : bool
            If True, also the standard deviation will be saved

        '''
        if format is None:
            format = preferences.General.default_export_format
        if name is None:
            name = self.component.name + '_' + self.name
        filename = incremental_filename(slugify(name) + '.' + format)
        if folder is not None:
            filename = os.path.join(folder, filename)
        self.as_signal().save(filename)
        if save_std is True:
            self.as_signal(field='std').save(append2pathname(filename, '_std'))

    def default_traits_view(self):
        # As mentioned above, the default editor for
        # value = t.Property(t.Either([t.CFloat(0), Array()]))
        # gives a ValueError. We therefore implement default_traits_view so
        # that configure/edit_traits will still work straight out of the box.
        # A whitelist controls which traits to include in this view.
        from traitsui.api import RangeEditor, View, Item
        whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value']
        editable_traits = [
            trait for trait in self.editable_traits() if trait in whitelist
        ]
        if 'value' in editable_traits:
            i = editable_traits.index('value')
            v = editable_traits.pop(i)
            editable_traits.insert(
                i,
                Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax')))
        view = View(editable_traits, buttons=['OK', 'Cancel'])
        return view
示例#16
0
文件: roi.py 项目: temcode/hyperspy
class CircleROI(BaseInteractiveROI):

    cx, cy, r, r_inner = (t.CFloat(t.Undefined), ) * 4
    _ndim = 2

    def __init__(self, cx, cy, r, r_inner=None):
        super(CircleROI, self).__init__()
        self._bounds_check = True  # Use reponsibly!
        self.cx, self.cy, self.r = cx, cy, r
        if r_inner:
            self.r_inner = r_inner

    def is_valid(self):
        return (t.Undefined not in (
            self.cx,
            self.cy,
            self.r,
        ) and self.r_inner == t.Undefined or self.r >= self.r_inner)

    def _cx_changed(self, old, new):
        self.update()

    def _cy_changed(self, old, new):
        self.update()

    def _r_changed(self, old, new):
        if self._bounds_check and \
                self.r_inner is not t.Undefined and new < self.r_inner:
            self.r = old
        else:
            self.update()

    def _r_inner_changed(self, old, new):
        if self._bounds_check and \
                self.r is not t.Undefined and new >= self.r:
            self.r_inner = old
        else:
            self.update()

    def _set_from_widget(self, widget):
        """Sets the internal representation of the ROI from the passed widget,
        without doing anything to events.
        """
        self.cx, self.cy = widget.position
        self.r, self.r_inner = widget.size

    def _apply_roi2widget(self, widget):
        widget.position = (self.cx, self.cy)
        inner = self.r_inner if self.r_inner != t.Undefined else 0.0
        widget.size = (self.r, inner)

    def _get_widget_type(self, axes, signal):
        return widgets.CircleWidget

    def navigate(self, signal):
        raise NotImplementedError("CircleROI does not support navigation.")

    def __call__(self, signal, axes=None):
        """Slice the signal according to the ROI, and return it.

        Arguments
        ---------
        signal : Signal
            The signal to slice with the ROI.
        axes : specification of axes to use, default = None
            The axes argument specifies which axes the ROI will be applied on.
            The items in the collection can be either of the following:
                * a tuple of:
                    - DataAxis. These will not be checked with
                      signal.axes_manager.
                    - anything that will index signal.axes_manager
                * For any other value, it will check whether the navigation
                  space can fit the right number of axis, and use that if it
                  fits. If not, it will try the signal space.
        """

        if axes is None and signal in self.signal_map:
            axes = self.signal_map[signal][1]
        else:
            axes = self._parse_axes(axes, signal.axes_manager)

        natax = signal.axes_manager._get_axes_in_natural_order()
        # Slice original data with a circumscribed rectangle
        cx = self.cx + 0.5 * axes[0].scale
        cy = self.cy + 0.5 * axes[1].scale
        ranges = [[cx - self.r, cx + self.r], [cy - self.r, cy + self.r]]
        slices = self._make_slices(natax, axes, ranges)
        ir = [slices[natax.index(axes[0])], slices[natax.index(axes[1])]]
        vx = axes[0].axis[ir[0]] - cx
        vy = axes[1].axis[ir[1]] - cy
        gx, gy = np.meshgrid(vx, vy)
        gr = gx**2 + gy**2
        mask = gr > self.r**2
        if self.r_inner != t.Undefined:
            mask |= gr < self.r_inner**2
        tiles = []
        shape = []
        for i in xrange(len(slices)):
            if i == natax.index(axes[0]):
                tiles.append(1)
                shape.append(mask.shape[0])
            elif i == natax.index(axes[1]):
                tiles.append(1)
                shape.append(mask.shape[1])
            else:
                tiles.append(signal.axes_manager.shape[i])
                shape.append(1)
        mask = mask.reshape(shape)
        mask = np.tile(mask, tiles)

        if axes[0].navigate:
            if len(axes) == 2 and not axes[1].navigate:
                # Special case, since we can no longer slice axes in different
                # spaces together.
                roi = signal.inav[slices[0]].isig[slices[1]]
            else:
                slicer = signal.inav.__getitem__
                slices = slices[0:signal.axes_manager.navigation_dimension]
                roi = slicer(slices)
        else:
            slicer = signal.isig.__getitem__
            slices = slices[signal.axes_manager.navigation_dimension:]
            roi = slicer(slices)
        roi.data = np.ma.masked_array(roi.data, mask, hard_mask=True)

        return roi

    def __repr__(self):
        if self.r_inner == t.Undefined:
            return "%s(cx=%g, cy=%g, r=%g" % (self.__class__.__name__, self.cx,
                                              self.cy, self.r)
        else:
            return "%s(cx=%g, cy=%g, r=%g, r_inner=%g)" % (
                self.__class__.__name__, self.cx, self.cy, self.r,
                self.r_inner)
示例#17
0
文件: roi.py 项目: askorikov/hyperspy
class Line2DROI(BaseInteractiveROI):

    x1, y1, x2, y2, linewidth = (t.CFloat(t.Undefined), ) * 5
    _ndim = 2

    def __init__(self, x1, y1, x2, y2, linewidth=0):
        super(Line2DROI, self).__init__()
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
        self.linewidth = linewidth

    def is_valid(self):
        return t.Undefined not in (self.x1, self.y1, self.x2, self.y2)

    def _x1_changed(self, old, new):
        self.update()

    def _x2_changed(self, old, new):
        self.update()

    def _y1_changed(self, old, new):
        self.update()

    def _y2_changed(self, old, new):
        self.update()

    def _linewidth_changed(self, old, new):
        self.update()

    def _set_from_widget(self, widget):
        """Sets the internal representation of the ROI from the passed widget,
        without doing anything to events.
        """
        c = widget.position
        s = widget.size[0]
        (self.x1, self.y1), (self.x2, self.y2) = c
        self.linewidth = s

    def _apply_roi2widget(self, widget):
        widget.position = (self.x1, self.y1), (self.x2, self.y2)
        widget.size = np.array([self.linewidth])

    def _get_widget_type(self, axes, signal):
        return widgets.Line2DWidget

    @staticmethod
    def _line_profile_coordinates(src, dst, linewidth=1):
        """Return the coordinates of the profile of an image along a scan line.

        Parameters
        ----------
        src : 2-tuple of numeric scalar (float or int)
            The start point of the scan line.
        dst : 2-tuple of numeric scalar (float or int)
            The end point of the scan line.
        linewidth : int, optional
            Width of the scan, perpendicular to the line
        Returns
        -------
        coords : array, shape (2, N, C), float
            The coordinates of the profile along the scan line. The length of
            the profile is the ceil of the computed length of the scan line.
        Notes
        -----
        This is a utility method meant to be used internally by skimage
        functions. The destination point is included in the profile, in
        contrast to standard numpy indexing.

        """
        src_row, src_col = src = np.asarray(src, dtype=float)
        dst_row, dst_col = dst = np.asarray(dst, dtype=float)
        d_row, d_col = dst - src
        theta = np.arctan2(d_row, d_col)

        length = np.ceil(np.hypot(d_row, d_col) + 1).astype(int)
        # we add one above because we include the last point in the profile
        # (in contrast to standard numpy indexing)
        line_col = np.linspace(src_col, dst_col, length)
        line_row = np.linspace(src_row, dst_row, length)
        data = np.zeros((2, length, linewidth))
        data[0, :, :] = np.tile(line_col, [linewidth, 1]).T
        data[1, :, :] = np.tile(line_row, [linewidth, 1]).T

        if linewidth != 1:
            # we subtract 1 from linewidth to change from pixel-counting
            # (make this line 3 pixels wide) to point distances (the
            # distance between pixel centers)
            col_width = (linewidth - 1) * np.sin(-theta) / 2
            row_width = (linewidth - 1) * np.cos(theta) / 2
            row_off = np.linspace(-row_width, row_width, linewidth)
            col_off = np.linspace(-col_width, col_width, linewidth)
            data[0, :, :] += np.tile(col_off, [length, 1])
            data[1, :, :] += np.tile(row_off, [length, 1])
        return data

    @property
    def length(self):
        p0 = np.array((self.x1, self.y1), dtype=np.float)
        p1 = np.array((self.x2, self.y2), dtype=np.float)
        d_row, d_col = p1 - p0
        return np.hypot(d_row, d_col)

    def angle(self, axis='horizontal', units='degrees'):
        """"Angle between ROI line and selected axis

        Parameters
        ----------
        axis : str, {'horizontal', 'vertical'}, optional
            Select axis against which the angle of the ROI line is measured.
            'x' is alias to 'horizontal' and 'y' is 'vertical'
            (Default: 'horizontal')
        units : str, {'degrees', 'radians'}
            The angle units of the output
            (Default: 'degrees')

        Returns
        -------
        angle : float

        Examples
        --------
        >>> import hyperspy.api as hs
        >>> hs.roi.Line2DROI(0., 0., 1., 2., 1)
        >>> r.angle()
        63.43494882292201
        """

        x = self.x2 - self.x1
        y = self.y2 - self.y1

        if units == 'degrees':
            conversation = 180. / np.pi
        elif units == 'radians':
            conversation = 1.
        else:
            raise ValueError(
                "Units are not recognized. Use  either 'degrees' or 'radians'."
            )

        if axis == 'horizontal':
            return np.arctan2(y, x) * conversation
        elif axis == 'vertical':
            return np.arctan2(x, y) * conversation
        else:
            raise ValueError("Axis is not recognized. "
                             "Use  either 'horizontal' or 'vertical'.")

    @staticmethod
    def profile_line(img,
                     src,
                     dst,
                     axes,
                     linewidth=1,
                     order=1,
                     mode='constant',
                     cval=0.0):
        """Return the intensity profile of an image measured along a scan line.

        Parameters
        ----------
        img : numeric array, shape (M, N[, C])
            The image, either grayscale (2D array) or multichannel
            (3D array, where the final axis contains the channel
            information).
        src : 2-tuple of numeric scalar (float or int)
            The start point of the scan line.
        dst : 2-tuple of numeric scalar (float or int)
            The end point of the scan line.
        linewidth : int, optional
            Width of the scan, perpendicular to the line
        order : int in {0, 1, 2, 3, 4, 5}, optional
            The order of the spline interpolation to compute image values at
            non-integer coordinates. 0 means nearest-neighbor interpolation.
        mode : string, one of {'constant', 'nearest', 'reflect', 'wrap'},
                optional
            How to compute any values falling outside of the image.
        cval : float, optional
            If `mode` is 'constant', what constant value to use outside the
            image.
        Returns
        -------
        return_value : array
            The intensity profile along the scan line. The length of the
            profile is the ceil of the computed length of the scan line.
        Examples
        --------
        >>> x = np.array([[1, 1, 1, 2, 2, 2]])
        >>> img = np.vstack([np.zeros_like(x), x, x, x, np.zeros_like(x)])
        >>> img
        array([[0, 0, 0, 0, 0, 0],
               [1, 1, 1, 2, 2, 2],
               [1, 1, 1, 2, 2, 2],
               [1, 1, 1, 2, 2, 2],
               [0, 0, 0, 0, 0, 0]])
        >>> profile_line(img, (2, 1), (2, 4))
        array([ 1.,  1.,  2.,  2.])
        Notes
        -----
        The destination point is included in the profile, in contrast to
        standard numpy indexing.

        """
        import scipy.ndimage as nd
        # Convert points coordinates from axes units to pixels
        p0 = ((src[0] - axes[0].offset) / axes[0].scale,
              (src[1] - axes[1].offset) / axes[1].scale)
        p1 = ((dst[0] - axes[0].offset) / axes[0].scale,
              (dst[1] - axes[1].offset) / axes[1].scale)
        if linewidth < 0:
            raise ValueError("linewidth must be positive number")
        linewidth_px = linewidth / np.min([ax.scale for ax in axes])
        linewidth_px = int(round(linewidth_px))
        # Minimum size 1 pixel
        linewidth_px = linewidth_px if linewidth_px >= 1 else 1
        perp_lines = Line2DROI._line_profile_coordinates(
            p0, p1, linewidth=linewidth_px)
        if img.ndim > 2:
            idx = [ax.index_in_array for ax in axes]
            if idx[0] < idx[1]:
                img = np.rollaxis(img, idx[0], 0)
                img = np.rollaxis(img, idx[1], 1)
            else:
                img = np.rollaxis(img, idx[1], 0)
                img = np.rollaxis(img, idx[0], 0)
            orig_shape = img.shape
            img = np.reshape(img,
                             orig_shape[0:2] + (np.product(orig_shape[2:]), ))
            pixels = [
                nd.map_coordinates(img[..., i].T,
                                   perp_lines,
                                   order=order,
                                   mode=mode,
                                   cval=cval) for i in range(img.shape[2])
            ]
            i0 = min(axes[0].index_in_array, axes[1].index_in_array)
            pixels = np.transpose(np.asarray(pixels), (1, 2, 0))
            intensities = pixels.mean(axis=1)
            intensities = np.rollaxis(
                np.reshape(intensities,
                           intensities.shape[0:1] + orig_shape[2:]), 0, i0 + 1)
        else:
            pixels = nd.map_coordinates(img,
                                        perp_lines,
                                        order=order,
                                        mode=mode,
                                        cval=cval)
            intensities = pixels.mean(axis=1)

        return intensities

    def __call__(self, signal, out=None, axes=None, order=0):
        """Slice the signal according to the ROI, and return it.

        Arguments
        ---------
        signal : Signal
            The signal to slice with the ROI.
        out : Signal, default = None
            If the 'out' argument is supplied, the sliced output will be put
            into this instead of returning a Signal. See Signal.__getitem__()
            for more details on 'out'.
        axes : specification of axes to use, default = None
            The axes argument specifies which axes the ROI will be applied on.
            The items in the collection can be either of the following:
                * a tuple of:
                    - DataAxis. These will not be checked with
                      signal.axes_manager.
                    - anything that will index signal.axes_manager
                * For any other value, it will check whether the navigation
                  space can fit the right number of axis, and use that if it
                  fits. If not, it will try the signal space.
        order : The spline interpolation order to use when extracting the line
            profile. 0 means nearest-neighbor interpolation, and is both the
            default and the fastest.
        """
        if axes is None and signal in self.signal_map:
            axes = self.signal_map[signal][1]
        else:
            axes = self._parse_axes(axes, signal.axes_manager)
        profile = Line2DROI.profile_line(signal.data, (self.x1, self.y1),
                                         (self.x2, self.y2),
                                         axes=axes,
                                         linewidth=self.linewidth,
                                         order=order)
        length = np.linalg.norm(np.diff(np.array(
            ((self.x1, self.y1), (self.x2, self.y2))),
                                        axis=0),
                                axis=1)[0]
        if out is None:
            axm = signal.axes_manager.deepcopy()
            i0 = min(axes[0].index_in_array, axes[1].index_in_array)
            axm.remove([ax.index_in_array + 3j for ax in axes])
            axis = DataAxis(profile.shape[i0],
                            scale=length / profile.shape[i0],
                            units=axes[0].units,
                            navigate=axes[0].navigate)
            axis.axes_manager = axm
            axm._axes.insert(i0, axis)
            from hyperspy.signals import BaseSignal
            roi = BaseSignal(
                profile,
                axes=axm._get_axes_dicts(),
                metadata=signal.metadata.deepcopy().as_dictionary(),
                original_metadata=signal.original_metadata.deepcopy(
                ).as_dictionary())
            return roi
        else:
            out.data = profile
            i0 = min(axes[0].index_in_array, axes[1].index_in_array)
            ax = out.axes_manager._axes[i0]
            size = len(profile)
            scale = length / len(profile)
            axchange = size != ax.size or scale != ax.scale
            if axchange:
                ax.size = len(profile)
                ax.scale = length / len(profile)
            out.events.data_changed.trigger(out)

    def __repr__(self):
        return "%s(x1=%g, y1=%g, x2=%g, y2=%g, linewidth=%g)" % (
            self.__class__.__name__, self.x1, self.y1, self.x2, self.y2,
            self.linewidth)
示例#18
0
文件: roi.py 项目: askorikov/hyperspy
class RectangularROI(BaseInteractiveROI):
    """Selects a range in a 2D space. The coordinates of the range in
    the 2D space are stored in the traits 'left', 'right', 'top' and 'bottom'.
    Convenience properties 'x', 'y', 'width' and 'height' are also available,
    but cannot be used for initialization.
    """
    top, bottom, left, right = (t.CFloat(t.Undefined), ) * 4
    _ndim = 2

    def __init__(self, left, top, right, bottom):
        super(RectangularROI, self).__init__()
        self._bounds_check = True  # Use reponsibly!
        self.top, self.bottom, self.left, self.right = top, bottom, left, right

    def is_valid(self):
        return (t.Undefined
                not in (self.top, self.bottom, self.left, self.right)
                and self.right >= self.left and self.bottom >= self.top)

    def _top_changed(self, old, new):
        if self._bounds_check and \
                self.bottom is not t.Undefined and new >= self.bottom:
            self.top = old
        else:
            self.update()

    @property
    def width(self):
        """Returns / sets the width of the ROI"""
        return self.right - self.left

    @width.setter
    def width(self, value):
        if value == self.width:
            return
        self.right -= self.width - value

    @property
    def height(self):
        """Returns / sets the height of the ROI"""
        return self.bottom - self.top

    @height.setter
    def height(self, value):
        if value == self.height:
            return
        self.bottom -= self.height - value

    @property
    def x(self):
        """Returns / sets the x coordinate of the ROI without changing its
        width"""
        return self.left

    @x.setter
    def x(self, value):
        if value != self.x:
            diff = value - self.x
            try:
                self._applying_widget_change = True
                self._bounds_check = False
                with self.events.changed.suppress():
                    self.right += diff
                    self.left += diff
            finally:
                self._applying_widget_change = False
                self._bounds_check = True
                self.update()

    @property
    def y(self):
        """Returns / sets the y coordinate of the ROI without changing its
        height"""
        return self.top

    @y.setter
    def y(self, value):
        if value != self.y:
            diff = value - self.y
            try:
                self._applying_widget_change = True
                self._bounds_check = False
                with self.events.changed.suppress():
                    self.top += diff
                    self.bottom += diff
            finally:
                self._applying_widget_change = False
                self._bounds_check = True
                self.update()

    def _bottom_changed(self, old, new):
        if self._bounds_check and \
                self.top is not t.Undefined and new <= self.top:
            self.bottom = old
        else:
            self.update()

    def _right_changed(self, old, new):
        if self._bounds_check and \
                self.left is not t.Undefined and new <= self.left:
            self.right = old
        else:
            self.update()

    def _left_changed(self, old, new):
        if self._bounds_check and \
                self.right is not t.Undefined and new >= self.right:
            self.left = old
        else:
            self.update()

    def _get_ranges(self):
        ranges = (
            (self.left, self.right),
            (self.top, self.bottom),
        )
        return ranges

    def _set_from_widget(self, widget):
        p = np.array(widget.position)
        s = np.array(widget.size)
        (self.left, self.top), (self.right, self.bottom) = (p, p + s)

    def _apply_roi2widget(self, widget):
        widget.set_bounds(left=self.left,
                          bottom=self.bottom,
                          right=self.right,
                          top=self.top)

    def _get_widget_type(self, axes, signal):
        return widgets.RectangleWidget

    def __repr__(self):
        return "%s(left=%g, top=%g, right=%g, bottom=%g)" % (
            self.__class__.__name__, self.left, self.top, self.right,
            self.bottom)
示例#19
0
文件: roi.py 项目: askorikov/hyperspy
class CircleROI(BaseInteractiveROI):

    cx, cy, r, r_inner = (t.CFloat(t.Undefined), ) * 4
    _ndim = 2

    def __init__(self, cx, cy, r, r_inner=None):
        super(CircleROI, self).__init__()
        self._bounds_check = True  # Use reponsibly!
        self.cx, self.cy, self.r = cx, cy, r
        if r_inner:
            self.r_inner = r_inner

    def is_valid(self):
        return (t.Undefined not in (
            self.cx,
            self.cy,
            self.r,
        ) and (self.r_inner is t.Undefined or t.Undefined not in
               (self.r, self.r_inner) and self.r >= self.r_inner))

    def _cx_changed(self, old, new):
        self.update()

    def _cy_changed(self, old, new):
        self.update()

    def _r_changed(self, old, new):
        if self._bounds_check and \
                self.r_inner is not t.Undefined and new < self.r_inner:
            self.r = old
        else:
            self.update()

    def _r_inner_changed(self, old, new):
        if self._bounds_check and \
                self.r is not t.Undefined and new >= self.r:
            self.r_inner = old
        else:
            self.update()

    def _set_from_widget(self, widget):
        """Sets the internal representation of the ROI from the passed widget,
        without doing anything to events.
        """
        self.cx, self.cy = widget.position
        self.r, self.r_inner = widget.size

    def _apply_roi2widget(self, widget):
        widget.position = (self.cx, self.cy)
        inner = self.r_inner if self.r_inner != t.Undefined else 0.0
        widget.size = (self.r, inner)

    def _get_widget_type(self, axes, signal):
        return widgets.CircleWidget

    def __call__(self, signal, out=None, axes=None):
        """Slice the signal according to the ROI, and return it.

        Arguments
        ---------
        signal : Signal
            The signal to slice with the ROI.
        out : Signal, default = None
            If the 'out' argument is supplied, the sliced output will be put
            into this instead of returning a Signal. See Signal.__getitem__()
            for more details on 'out'.
        axes : specification of axes to use, default = None
            The axes argument specifies which axes the ROI will be applied on.
            The items in the collection can be either of the following:
                * a tuple of:
                    - DataAxis. These will not be checked with
                      signal.axes_manager.
                    - anything that will index signal.axes_manager
                * For any other value, it will check whether the navigation
                  space can fit the right number of axis, and use that if it
                  fits. If not, it will try the signal space.
        """

        if axes is None and signal in self.signal_map:
            axes = self.signal_map[signal][1]
        else:
            axes = self._parse_axes(axes, signal.axes_manager)

        natax = signal.axes_manager._get_axes_in_natural_order()
        # Slice original data with a circumscribed rectangle
        cx = self.cx + 0.5001 * axes[0].scale
        cy = self.cy + 0.5001 * axes[1].scale
        ranges = [[cx - self.r, cx + self.r], [cy - self.r, cy + self.r]]
        slices = self._make_slices(natax, axes, ranges)
        ir = [slices[natax.index(axes[0])], slices[natax.index(axes[1])]]
        vx = axes[0].axis[ir[0]] - cx
        vy = axes[1].axis[ir[1]] - cy
        gx, gy = np.meshgrid(vx, vy)
        gr = gx**2 + gy**2
        mask = gr > self.r**2
        if self.r_inner != t.Undefined:
            mask |= gr < self.r_inner**2
        tiles = []
        shape = []
        chunks = []
        for i in range(len(slices)):
            if signal._lazy:
                chunks.append(signal.data.chunks[i][0])
            if i == natax.index(axes[0]):
                thisshape = mask.shape[0]
                tiles.append(thisshape)
                shape.append(thisshape)
            elif i == natax.index(axes[1]):
                thisshape = mask.shape[1]
                tiles.append(thisshape)
                shape.append(thisshape)
            else:
                tiles.append(signal.axes_manager._axes[i].size)
                shape.append(1)
        mask = mask.reshape(shape)

        nav_axes = [ax.navigate for ax in axes]
        nav_dim = signal.axes_manager.navigation_dimension
        if True in nav_axes:
            if False in nav_axes:

                slicer = signal.inav[slices[:nav_dim]].isig.__getitem__
                slices = slices[nav_dim:]
            else:
                slicer = signal.inav.__getitem__
                slices = slices[0:nav_dim]
        else:
            slicer = signal.isig.__getitem__
            slices = slices[nav_dim:]

        roi = slicer(slices, out=out)
        roi = out or roi
        if roi._lazy:
            import dask.array as da
            mask = da.from_array(mask, chunks=chunks)
            mask = da.broadcast_to(mask, tiles)
            # By default promotes dtype to float if required
            roi.data = da.where(mask, np.nan, roi.data)
        else:
            mask = np.broadcast_to(mask, tiles)
            roi.data = np.ma.masked_array(roi.data, mask, hard_mask=True)
        if out is None:
            return roi
        else:
            out.events.data_changed.trigger(out)

    def __repr__(self):
        if self.r_inner == t.Undefined:
            return "%s(cx=%g, cy=%g, r=%g)" % (self.__class__.__name__,
                                               self.cx, self.cy, self.r)
        else:
            return "%s(cx=%g, cy=%g, r=%g, r_inner=%g)" % (
                self.__class__.__name__, self.cx, self.cy, self.r,
                self.r_inner)
示例#20
0
class Parameter(t.HasTraits):
    """Model parameter

    Attributes
    ----------
    value : float or array
        The value of the parameter for the current location. The value
        for other locations is stored in map.
    bmin, bmax: float
        Lower and upper bounds of the parameter value.
    twin : {None, Parameter}
        If it is not None, the value of the current parameter is
        a function of the given Parameter. The function is by default
        the identity function, but it can be defined by twin_function
    twin_function : function
        Function that, if selt.twin is not None, takes self.twin.value
        as its only argument and returns a float or array that is
        returned when getting Parameter.value
    twin_inverse_function : function
        The inverse of twin_function. If it is None then it is not
        possible to set the value of the parameter twin by setting
        the value of the current parameter.
    ext_force_positive : bool
        If True, the parameter value is set to be the absolute value
        of the input value i.e. if we set Parameter.value = -3, the
        value stored is 3 instead. This is useful to bound a value
        to be positive in an optimization without actually using an
        optimizer that supports bounding.
    ext_bounded : bool
        Similar to ext_force_positive, but in this case the bounds are
        defined by bmin and bmax. It is a better idea to use
        an optimizer that supports bounding though.

    Methods
    -------
    as_signal(field = 'values')
        Get a parameter map as a signal object
    plot()
        Plots the value of the Parameter at all locations.
    export(folder=None, name=None, format=None, save_std=False)
        Saves the value of the parameter map to the specified format
    connect, disconnect(function)
        Call the functions connected when the value attribute changes.

    """
    __number_of_elements = 1
    __value = 0
    __free = True
    _bounds = (None, None)
    __twin = None
    _axes_manager = None
    __ext_bounded = False
    __ext_force_positive = False

    # traitsui bugs out trying to make an editor for this, so always specify!
    # (it bugs out, because both editor shares the object, and Array editors
    # don't like non-sequence objects). TextEditor() works well, so does
    # RangeEditor() as it works with bmin/bmax.
    value = t.Property(t.Either([t.CFloat(0), Array()]))

    units = t.Str('')
    free = t.Property(t.CBool(True))

    bmin = t.Property(NoneFloat(), label="Lower bounds")
    bmax = t.Property(NoneFloat(), label="Upper bounds")

    def __init__(self):
        self._twins = set()
        self.events = Events()
        self.events.value_changed = Event("""
            Event that triggers when the `Parameter.value` changes.

            The event triggers after the internal state of the `Parameter` has
            been updated.

            Arguments
            ---------
            obj : Parameter
                The `Parameter` that the event belongs to
            value : {float | array}
                The new value of the parameter
            """,
                                          arguments=["obj", 'value'])
        self.twin_function = lambda x: x
        self.twin_inverse_function = lambda x: x
        self.std = None
        self.component = None
        self.grad = None
        self.name = ''
        self.units = ''
        self.map = None
        self.model = None
        self._whitelist = {
            '_id_name': None,
            'value': None,
            'std': None,
            'free': None,
            'units': None,
            'map': None,
            '_bounds': None,
            'ext_bounded': None,
            'name': None,
            'ext_force_positive': None,
            'self': ('id', None),
            'twin_function': ('fn', None),
            'twin_inverse_function': ('fn', None),
        }
        self._slicing_whitelist = {'map': 'inav'}

    def _load_dictionary(self, dictionary):
        """Load data from dictionary

        Parameters
        ----------
        dict : dictionary
            A dictionary containing at least the following items:
            _id_name : string
                _id_name of the original parameter, used to create the
                dictionary. Has to match with the self._id_name
            _whitelist : dictionary
                a dictionary, which keys are used as keywords to match with the
                parameter attributes.  For more information see
                :meth:`hyperspy.misc.export_dictionary.load_from_dictionary`
            * any field from _whitelist.keys() *
        Returns
        -------
        id_value : int
            the ID value of the original parameter, to be later used for setting
            up the correct twins

        """
        if dictionary['_id_name'] == self._id_name:
            load_from_dictionary(self, dictionary)
            return dictionary['self']
        else:
            raise ValueError(
                "_id_name of parameter and dictionary do not match, \nparameter._id_name = %s\
                    \ndictionary['_id_name'] = %s" %
                (self._id_name, dictionary['_id_name']))

    def __repr__(self):
        text = ''
        text += 'Parameter %s' % self.name
        if self.component is not None:
            text += ' of %s' % self.component._get_short_description()
        text = '<' + text + '>'
        return text

    def __len__(self):
        return self._number_of_elements

    def _get_value(self):
        if self.twin is None:
            return self.__value
        else:
            return self.twin_function(self.twin.value)

    def _set_value(self, value):
        try:
            # Use try/except instead of hasattr("__len__") because a numpy
            # memmap has a __len__ wrapper even for numbers that raises a
            # TypeError when calling. See issue #349.
            if len(value) != self._number_of_elements:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
            else:
                if not isinstance(value, tuple):
                    value = tuple(value)
        except TypeError:
            if self._number_of_elements != 1:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
        old_value = self.__value

        if self.twin is not None:
            if self.twin_inverse_function is not None:
                self.twin.value = self.twin_inverse_function(value)
            return

        if self.ext_bounded is False:
            self.__value = value
        else:
            if self.ext_force_positive is True:
                value = np.abs(value)
            if self._number_of_elements == 1:
                if self.bmin is not None and value <= self.bmin:
                    self.__value = self.bmin
                elif self.bmax is not None and value >= self.bmax:
                    self.__value = self.bmax
                else:
                    self.__value = value
            else:
                bmin = (self.bmin if self.bmin is not None else -np.inf)
                bmax = (self.bmax if self.bmin is not None else np.inf)
                self.__value = np.clip(value, bmin, bmax)

        if (self._number_of_elements != 1
                and not isinstance(self.__value, tuple)):
            self.__value = tuple(self.__value)
        if old_value != self.__value:
            self.events.value_changed.trigger(value=self.__value, obj=self)
        self.trait_property_changed('value', old_value, self.__value)

    # Fix the parameter when coupled
    def _get_free(self):
        if self.twin is None:
            return self.__free
        else:
            return False

    def _set_free(self, arg):
        old_value = self.__free
        self.__free = arg
        if self.component is not None:
            self.component._update_free_parameters()
        self.trait_property_changed('free', old_value, self.__free)

    def _on_twin_update(self, value, twin=None):
        if (twin is not None and hasattr(twin, 'events')
                and hasattr(twin.events, 'value_changed')):
            with twin.events.value_changed.suppress_callback(
                    self._on_twin_update):
                self.events.value_changed.trigger(value=value, obj=self)
        else:
            self.events.value_changed.trigger(value=value, obj=self)

    def _set_twin(self, arg):
        if arg is None:
            if self.twin is not None:
                # Store the value of the twin in order to set the
                # value of the parameter when it is uncoupled
                twin_value = self.value
                if self in self.twin._twins:
                    self.twin._twins.remove(self)
                    self.twin.events.value_changed.disconnect(
                        self._on_twin_update)

                self.__twin = arg
                self.value = twin_value
        else:
            if self not in arg._twins:
                arg._twins.add(self)
                arg.events.value_changed.connect(self._on_twin_update,
                                                 ["value"])
            self.__twin = arg

        if self.component is not None:
            self.component._update_free_parameters()

    def _get_twin(self):
        return self.__twin

    twin = property(_get_twin, _set_twin)

    def _get_bmin(self):
        if self._number_of_elements == 1:
            return self._bounds[0]
        else:
            return self._bounds[0][0]

    def _set_bmin(self, arg):
        old_value = self.bmin
        if self._number_of_elements == 1:
            self._bounds = (arg, self.bmax)
        else:
            self._bounds = ((arg, self.bmax), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmin', old_value, arg)

    def _get_bmax(self):
        if self._number_of_elements == 1:
            return self._bounds[1]
        else:
            return self._bounds[0][1]

    def _set_bmax(self, arg):
        old_value = self.bmax
        if self._number_of_elements == 1:
            self._bounds = (self.bmin, arg)
        else:
            self._bounds = ((self.bmin, arg), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmax', old_value, arg)

    @property
    def _number_of_elements(self):
        return self.__number_of_elements

    @_number_of_elements.setter
    def _number_of_elements(self, arg):
        # Do nothing if the number of arguments stays the same
        if self.__number_of_elements == arg:
            return
        if arg <= 1:
            raise ValueError("Please provide an integer number equal "
                             "or greater to 1")
        self._bounds = ((self.bmin, self.bmax), ) * arg
        self.__number_of_elements = arg

        if arg == 1:
            self._Parameter__value = 0
        else:
            self._Parameter__value = (0, ) * arg
        if self.component is not None:
            self.component.update_number_parameters()

    @property
    def ext_bounded(self):
        return self.__ext_bounded

    @ext_bounded.setter
    def ext_bounded(self, arg):
        if arg is not self.__ext_bounded:
            self.__ext_bounded = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    @property
    def ext_force_positive(self):
        return self.__ext_force_positive

    @ext_force_positive.setter
    def ext_force_positive(self, arg):
        if arg is not self.__ext_force_positive:
            self.__ext_force_positive = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    def store_current_value_in_array(self):
        """Store the value and std attributes.

        See also
        --------
        fetch, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        self.map['values'][indices] = self.value
        self.map['is_set'][indices] = True
        if self.std is not None:
            self.map['std'][indices] = self.std

    def fetch(self):
        """Fetch the stored value and std attributes.


        See Also
        --------
        store_current_value_in_array, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        if self.map['is_set'][indices]:
            self.value = self.map['values'][indices]
            self.std = self.map['std'][indices]

    def assign_current_value_to_all(self, mask=None):
        """Assign the current value attribute to all the  indices

        Parameters
        ----------
        mask: {None, boolean numpy array}
            Set only the indices that are not masked i.e. where
            mask is False.

        See Also
        --------
        store_current_value_in_array, fetch

        """
        if mask is None:
            mask = np.zeros(self.map.shape, dtype='bool')
        self.map['values'][mask == False] = self.value
        self.map['is_set'][mask == False] = True

    def _create_array(self):
        """Create the map array to store the information in
        multidimensional datasets.

        """
        shape = self._axes_manager._navigation_shape_in_array
        if not shape:
            shape = [
                1,
            ]
        dtype_ = np.dtype([('values', 'float', self._number_of_elements),
                           ('std', 'float', self._number_of_elements),
                           ('is_set', 'bool', 1)])
        if (self.map is None or self.map.shape != shape
                or self.map.dtype != dtype_):
            self.map = np.zeros(shape, dtype_)
            self.map['std'].fill(np.nan)
            # TODO: in the future this class should have access to
            # axes manager and should be able to fetch its own
            # values. Until then, the next line is necessary to avoid
            # erros when self.std is defined and the shape is different
            # from the newly defined arrays
            self.std = None

    def as_signal(self, field='values'):
        """Get a parameter map as a signal object.

        Please note that this method only works when the navigation
        dimension is greater than 0.

        Parameters
        ----------
        field : {'values', 'std', 'is_set'}

        Raises
        ------

        NavigationDimensionError : if the navigation dimension is 0

        """
        from hyperspy.signal import BaseSignal

        s = BaseSignal(data=self.map[field],
                       axes=self._axes_manager._get_navigation_axes_dicts())
        if self.component is not None and \
                self.component.active_is_multidimensional:
            s.data[np.logical_not(self.component._active_array)] = np.nan

        s.metadata.General.title = ("%s parameter" %
                                    self.name if self.component is None else
                                    "%s parameter of %s component" %
                                    (self.name, self.component.name))
        for axis in s.axes_manager._axes:
            axis.navigate = False
        if self._number_of_elements > 1:
            s.axes_manager._append_axis(size=self._number_of_elements,
                                        name=self.name,
                                        navigate=True)
        s._assign_subclass()
        if field == "values":
            # Add the variance if available
            std = self.as_signal(field="std")
            if not np.isnan(std.data).all():
                std.data = std.data**2
                std.metadata.General.title = "Variance"
                s.metadata.set_item("Signal.Noise_properties.variance", std)
        return s

    def plot(self, **kwargs):
        """Plot parameter signal.

        Parameters
        ----------
        **kwargs
            Any extra keyword arguments are passed to the signal plot.

        Example
        -------
        >>> parameter.plot()

        Set the minimum and maximum displayed values

        >>> parameter.plot(vmin=0, vmax=1)
        """
        self.as_signal().plot(**kwargs)

    def export(self, folder=None, name=None, format=None, save_std=False):
        """Save the data to a file.

        All the arguments are optional.

        Parameters
        ----------
        folder : str or None
            The path to the folder where the file will be saved.
             If `None` the current folder is used by default.
        name : str or None
            The name of the file. If `None` the Components name followed
             by the Parameter `name` attributes will be used by default.
              If a file with the same name exists the name will be
              modified by appending a number to the file path.
        save_std : bool
            If True, also the standard deviation will be saved

        """
        if format is None:
            format = preferences.General.default_export_format
        if name is None:
            name = self.component.name + '_' + self.name
        filename = incremental_filename(slugify(name) + '.' + format)
        if folder is not None:
            filename = os.path.join(folder, filename)
        self.as_signal().save(filename)
        if save_std is True:
            self.as_signal(field='std').save(append2pathname(filename, '_std'))

    def as_dictionary(self, fullcopy=True):
        """Returns parameter as a dictionary, saving all attributes from
        self._whitelist.keys() For more information see
        :meth:`hyperspy.misc.export_dictionary.export_to_dictionary`

        Parameters
        ----------
        fullcopy : Bool (optional, False)
            Copies of objects are stored, not references. If any found,
            functions will be pickled and signals converted to dictionaries
        Returns
        -------
        dic : dictionary with the following keys:
            _id_name : string
                _id_name of the original parameter, used to create the
                dictionary. Has to match with the self._id_name
            _twins : list
                a list of ids of the twins of the parameter
            _whitelist : dictionary
                a dictionary, which keys are used as keywords to match with the
                parameter attributes.  For more information see
                :meth:`hyperspy.misc.export_dictionary.export_to_dictionary`
            * any field from _whitelist.keys() *

        """
        dic = {'_twins': [id(t) for t in self._twins]}
        export_to_dictionary(self, self._whitelist, dic, fullcopy)
        return dic

    def default_traits_view(self):
        # As mentioned above, the default editor for
        # value = t.Property(t.Either([t.CFloat(0), Array()]))
        # gives a ValueError. We therefore implement default_traits_view so
        # that configure/edit_traits will still work straight out of the box.
        # A whitelist controls which traits to include in this view.
        from traitsui.api import RangeEditor, View, Item
        whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value']
        editable_traits = [
            trait for trait in self.editable_traits() if trait in whitelist
        ]
        if 'value' in editable_traits:
            i = editable_traits.index('value')
            v = editable_traits.pop(i)
            editable_traits.insert(
                i,
                Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax')))
        view = View(editable_traits, buttons=['OK', 'Cancel'])
        return view

    def _interactive_slider_bounds(self, index=None):
        """Guesstimates the bounds for the slider. They will probably have to
        be changed later by the user.
        """
        fraction = 10.
        _min, _max, step = None, None, None
        value = self.value if index is None else self.value[index]
        if self.bmin is not None:
            _min = self.bmin
        if self.bmax is not None:
            _max = self.bmax
        if _max is None and _min is not None:
            _max = value + fraction * (value - _min)
        if _min is None and _max is not None:
            _min = value - fraction * (_max - value)
        if _min is None and _max is None:
            if self is self.component._position:
                axis = self._axes_manager.signal_axes[-1]
                _min = axis.axis.min()
                _max = axis.axis.max()
                step = np.abs(axis.scale)
            else:
                _max = value + np.abs(value * fraction)
                _min = value - np.abs(value * fraction)
        if step is None:
            step = (_max - _min) * 0.001
        return {'min': _min, 'max': _max, 'step': step}

    def _interactive_update(self, value=None, index=None):
        """Callback function for the widgets, to update the value
        """
        if value is not None:
            if index is None:
                self.value = value['new']
            else:
                self.value = self.value[:index] + (value['new'],) +\
                    self.value[index + 1:]

    def notebook_interaction(self, display=True):
        """Creates interactive notebook widgets for the parameter, if
        available.
        Requires `ipywidgets` to be installed.
        Parameters
        ----------
        display : bool
            if True (default), attempts to display the parameter widget.
            Otherwise returns the formatted widget object.
        """
        from ipywidgets import VBox
        from traitlets import TraitError as TraitletError
        from IPython.display import display as ip_display
        try:
            if self._number_of_elements == 1:
                container = self._create_notebook_widget()
            else:
                children = [
                    self._create_notebook_widget(index=i)
                    for i in range(self._number_of_elements)
                ]
                container = VBox(children)
            if not display:
                return container
            ip_display(container)
        except TraitletError:
            if display:
                _logger.info('This function is only avialable when running in'
                             ' a notebook')
            else:
                raise

    def _create_notebook_widget(self, index=None):

        from ipywidgets import (FloatSlider, FloatText, Layout, HBox)

        widget_bounds = self._interactive_slider_bounds(index=index)
        thismin = FloatText(
            value=widget_bounds['min'],
            description='min',
            layout=Layout(flex='0 1 auto', width='auto'),
        )
        thismax = FloatText(
            value=widget_bounds['max'],
            description='max',
            layout=Layout(flex='0 1 auto', width='auto'),
        )
        current_value = self.value if index is None else self.value[index]
        current_name = self.name
        if index is not None:
            current_name += '_{}'.format(index)
        widget = FloatSlider(value=current_value,
                             min=thismin.value,
                             max=thismax.value,
                             step=widget_bounds['step'],
                             description=current_name,
                             layout=Layout(flex='1 1 auto', width='auto'))

        def on_min_change(change):
            if widget.max > change['new']:
                widget.min = change['new']
                widget.step = np.abs(widget.max - widget.min) * 0.001

        def on_max_change(change):
            if widget.min < change['new']:
                widget.max = change['new']
                widget.step = np.abs(widget.max - widget.min) * 0.001

        thismin.observe(on_min_change, names='value')
        thismax.observe(on_max_change, names='value')

        this_observed = functools.partial(self._interactive_update,
                                          index=index)

        widget.observe(this_observed, names='value')
        container = HBox((thismin, widget, thismax))
        return container
示例#21
0
class Parameter(t.HasTraits):
    """Model parameter

    Attributes
    ----------
    value : float or array
        The value of the parameter for the current location. The value
        for other locations is stored in map.
    bmin, bmax: float
        Lower and upper bounds of the parameter value.
    twin : {None, Parameter}
        If it is not None, the value of the current parameter is
        a function of the given Parameter. The function is by default
        the identity function, but it can be defined by twin_function
    twin_function_expr: str
        Expression of the ``twin_function`` that enables setting a functional
        relationship between the parameter and its twin. If ``twin`` is not
        ``None``, the parameter value is calculated as the output of calling the
        twin function with the value of the twin parameter. The string is
        parsed using sympy, so permitted values are any valid sympy expressions
        of one variable. If the function is invertible the twin inverse function
        is set automatically.
    twin_inverse_function : str
        Expression of the ``twin_inverse_function`` that enables setting the
        value of the twin parameter. If ``twin`` is not
        ``None``, its value is set to the output of calling the
        twin inverse function with the value provided. The string is
        parsed using sympy, so permitted values are any valid sympy expressions
        of one variable.
    twin_function : function
        **Setting this attribute manually
        is deprecated in HyperSpy newer than 1.1.2. It will become private in
        HyperSpy 2.0. Please use ``twin_function_expr`` instead.**
    twin_inverse_function : function
        **Setting this attribute manually
        is deprecated in HyperSpy newer than 1.1.2. It will become private in
        HyperSpy 2.0. Please use ``twin_inverse_function_expr`` instead.**
    ext_force_positive : bool
        If True, the parameter value is set to be the absolute value
        of the input value i.e. if we set Parameter.value = -3, the
        value stored is 3 instead. This is useful to bound a value
        to be positive in an optimization without actually using an
        optimizer that supports bounding.
    ext_bounded : bool
        Similar to ext_force_positive, but in this case the bounds are
        defined by bmin and bmax. It is a better idea to use
        an optimizer that supports bounding though.

    Methods
    -------
    as_signal(field = 'values')
        Get a parameter map as a signal object
    plot()
        Plots the value of the Parameter at all locations.
    export(folder=None, name=None, format=None, save_std=False)
        Saves the value of the parameter map to the specified format
    connect, disconnect(function)
        Call the functions connected when the value attribute changes.

    """
    __number_of_elements = 1
    __value = 0
    __free = True
    _bounds = (None, None)
    __twin = None
    _axes_manager = None
    __ext_bounded = False
    __ext_force_positive = False

    # traitsui bugs out trying to make an editor for this, so always specify!
    # (it bugs out, because both editor shares the object, and Array editors
    # don't like non-sequence objects). TextEditor() works well, so does
    # RangeEditor() as it works with bmin/bmax.
    value = t.Property(t.Either([t.CFloat(0), Array()]))

    units = t.Str('')
    free = t.Property(t.CBool(True))

    bmin = t.Property(NoneFloat(), label="Lower bounds")
    bmax = t.Property(NoneFloat(), label="Upper bounds")
    _twin_function_expr = ""
    _twin_inverse_function_expr = ""
    twin_function = None
    _twin_inverse_function = None
    _twin_inverse_sympy = None

    def __init__(self):
        self._twins = set()
        self.events = Events()
        self.events.value_changed = Event("""
            Event that triggers when the `Parameter.value` changes.

            The event triggers after the internal state of the `Parameter` has
            been updated.

            Arguments
            ---------
            obj : Parameter
                The `Parameter` that the event belongs to
            value : {float | array}
                The new value of the parameter
            """,
                                          arguments=["obj", 'value'])
        self.std = None
        self.component = None
        self.grad = None
        self.name = ''
        self.units = ''
        self.map = None
        self.model = None
        self._whitelist = {
            '_id_name': None,
            'value': None,
            'std': None,
            'free': None,
            'units': None,
            'map': None,
            '_bounds': None,
            'ext_bounded': None,
            'name': None,
            'ext_force_positive': None,
            'twin_function_expr': None,
            'twin_inverse_function_expr': None,
            'self': ('id', None),
        }
        self._slicing_whitelist = {'map': 'inav'}

    def _load_dictionary(self, dictionary):
        """Load data from dictionary

        Parameters
        ----------
        dict : dictionary
            A dictionary containing at least the following items:
            _id_name : string
                _id_name of the original parameter, used to create the
                dictionary. Has to match with the self._id_name
            _whitelist : dictionary
                a dictionary, which keys are used as keywords to match with the
                parameter attributes.  For more information see
                :meth:`hyperspy.misc.export_dictionary.load_from_dictionary`
            * any field from _whitelist.keys() *
        Returns
        -------
        id_value : int
            the ID value of the original parameter, to be later used for setting
            up the correct twins

        """
        if dictionary['_id_name'] == self._id_name:
            load_from_dictionary(self, dictionary)
            return dictionary['self']
        else:
            raise ValueError(
                "_id_name of parameter and dictionary do not match, \nparameter._id_name = %s\
                    \ndictionary['_id_name'] = %s" %
                (self._id_name, dictionary['_id_name']))

    def __repr__(self):
        text = ''
        text += 'Parameter %s' % self.name
        if self.component is not None:
            text += ' of %s' % self.component._get_short_description()
        text = '<' + text + '>'
        return text

    def __len__(self):
        return self._number_of_elements

    @property
    def twin_function_expr(self):
        return self._twin_function_expr

    @twin_function_expr.setter
    def twin_function_expr(self, value):
        if not value:
            self.twin_function = None
            self.twin_inverse_function = None
            self._twin_function_expr = ""
            self._twin_inverse_sympy = None
            return
        expr = sympy.sympify(value)
        if len(expr.free_symbols) > 1:
            raise ValueError("The expression must contain only one variable.")
        elif len(expr.free_symbols) == 0:
            raise ValueError("The expression must contain one variable, "
                             "it contains none.")
        x = tuple(expr.free_symbols)[0]
        self.twin_function = lambdify(x, expr.evalf())
        self._twin_function_expr = value
        if not self.twin_inverse_function:
            y = sympy.Symbol(x.name + "2")
            try:
                inv = sympy.solveset(sympy.Eq(y, expr), x)
                self._twin_inverse_sympy = lambdify(y, inv)
                self._twin_inverse_function = None
            except BaseException:
                # Not all may have a suitable solution.
                self._twin_inverse_function = None
                self._twin_inverse_sympy = None
                _logger.warning(
                    "The function {} is not invertible. Setting the value of "
                    "{} will raise an AttributeError unless you set manually "
                    "``twin_inverse_function_expr``. Otherwise, set the "
                    "value of its twin parameter instead.".format(value, self))

    @property
    def twin_inverse_function_expr(self):
        if self.twin:
            return self._twin_inverse_function_expr
        else:
            return ""

    @twin_inverse_function_expr.setter
    def twin_inverse_function_expr(self, value):
        if not value:
            self.twin_inverse_function = None
            self._twin_inverse_function_expr = ""
            return
        expr = sympy.sympify(value)
        if len(expr.free_symbols) > 1:
            raise ValueError("The expression must contain only one variable.")
        elif len(expr.free_symbols) == 0:
            raise ValueError("The expression must contain one variable, "
                             "it contains none.")
        x = tuple(expr.free_symbols)[0]
        self._twin_inverse_function = lambdify(x, expr.evalf())
        self._twin_inverse_function_expr = value

    @property
    def twin_inverse_function(self):
        if (not self.twin_inverse_function_expr and self.twin_function_expr
                and self._twin_inverse_sympy):
            return lambda x: self._twin_inverse_sympy(x).pop()
        else:
            return self._twin_inverse_function

    @twin_inverse_function.setter
    def twin_inverse_function(self, value):
        self._twin_inverse_function = value

    def _get_value(self):
        if self.twin is None:
            return self.__value
        else:
            if self.twin_function:
                return self.twin_function(self.twin.value)
            else:
                return self.twin.value

    def _set_value(self, value):
        try:
            # Use try/except instead of hasattr("__len__") because a numpy
            # memmap has a __len__ wrapper even for numbers that raises a
            # TypeError when calling. See issue #349.
            if len(value) != self._number_of_elements:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
            else:
                if not isinstance(value, tuple):
                    value = tuple(value)
        except TypeError:
            if self._number_of_elements != 1:
                raise ValueError("The length of the parameter must be ",
                                 self._number_of_elements)
        old_value = self.__value

        if self.twin is not None:
            if self.twin_function is not None:
                if self.twin_inverse_function is not None:
                    self.twin.value = self.twin_inverse_function(value)
                    return
                else:
                    raise AttributeError(
                        "This parameter has a ``twin_function`` but"
                        "its ``twin_inverse_function`` is not defined.")
            else:
                self.twin.value = value
                return

        if self.ext_bounded is False:
            self.__value = value
        else:
            if self.ext_force_positive is True:
                value = np.abs(value)
            if self._number_of_elements == 1:
                if self.bmin is not None and value <= self.bmin:
                    self.__value = self.bmin
                elif self.bmax is not None and value >= self.bmax:
                    self.__value = self.bmax
                else:
                    self.__value = value
            else:
                bmin = (self.bmin if self.bmin is not None else -np.inf)
                bmax = (self.bmax if self.bmin is not None else np.inf)
                self.__value = np.clip(value, bmin, bmax)

        if (self._number_of_elements != 1
                and not isinstance(self.__value, tuple)):
            self.__value = tuple(self.__value)
        if old_value != self.__value:
            self.events.value_changed.trigger(value=self.__value, obj=self)
        self.trait_property_changed('value', old_value, self.__value)

    # Fix the parameter when coupled
    def _get_free(self):
        if self.twin is None:
            return self.__free
        else:
            return False

    def _set_free(self, arg):
        old_value = self.__free
        self.__free = arg
        if self.component is not None:
            self.component._update_free_parameters()
        self.trait_property_changed('free', old_value, self.__free)

    def _on_twin_update(self, value, twin=None):
        if (twin is not None and hasattr(twin, 'events')
                and hasattr(twin.events, 'value_changed')):
            with twin.events.value_changed.suppress_callback(
                    self._on_twin_update):
                self.events.value_changed.trigger(value=value, obj=self)
        else:
            self.events.value_changed.trigger(value=value, obj=self)

    def _set_twin(self, arg):
        if arg is None:
            if self.twin is not None:
                # Store the value of the twin in order to set the
                # value of the parameter when it is uncoupled
                twin_value = self.value
                if self in self.twin._twins:
                    self.twin._twins.remove(self)
                    self.twin.events.value_changed.disconnect(
                        self._on_twin_update)

                self.__twin = arg
                self.value = twin_value
        else:
            if self not in arg._twins:
                arg._twins.add(self)
                arg.events.value_changed.connect(self._on_twin_update,
                                                 ["value"])
            self.__twin = arg

        if self.component is not None:
            self.component._update_free_parameters()

    def _get_twin(self):
        return self.__twin

    twin = property(_get_twin, _set_twin)

    def _get_bmin(self):
        if self._number_of_elements == 1:
            return self._bounds[0]
        else:
            return self._bounds[0][0]

    def _set_bmin(self, arg):
        old_value = self.bmin
        if self._number_of_elements == 1:
            self._bounds = (arg, self.bmax)
        else:
            self._bounds = ((arg, self.bmax), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmin', old_value, arg)

    def _get_bmax(self):
        if self._number_of_elements == 1:
            return self._bounds[1]
        else:
            return self._bounds[0][1]

    def _set_bmax(self, arg):
        old_value = self.bmax
        if self._number_of_elements == 1:
            self._bounds = (self.bmin, arg)
        else:
            self._bounds = ((self.bmin, arg), ) * self._number_of_elements
        # Update the value to take into account the new bounds
        self.value = self.value
        self.trait_property_changed('bmax', old_value, arg)

    @property
    def _number_of_elements(self):
        return self.__number_of_elements

    @_number_of_elements.setter
    def _number_of_elements(self, arg):
        # Do nothing if the number of arguments stays the same
        if self.__number_of_elements == arg:
            return
        if arg <= 1:
            raise ValueError("Please provide an integer number equal "
                             "or greater to 1")
        self._bounds = ((self.bmin, self.bmax), ) * arg
        self.__number_of_elements = arg

        if arg == 1:
            self._Parameter__value = 0
        else:
            self._Parameter__value = (0, ) * arg
        if self.component is not None:
            self.component.update_number_parameters()

    @property
    def ext_bounded(self):
        return self.__ext_bounded

    @ext_bounded.setter
    def ext_bounded(self, arg):
        if arg is not self.__ext_bounded:
            self.__ext_bounded = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    @property
    def ext_force_positive(self):
        return self.__ext_force_positive

    @ext_force_positive.setter
    def ext_force_positive(self, arg):
        if arg is not self.__ext_force_positive:
            self.__ext_force_positive = arg
            # Update the value to take into account the new bounds
            self.value = self.value

    def store_current_value_in_array(self):
        """Store the value and std attributes.

        See also
        --------
        fetch, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        self.map['values'][indices] = self.value
        self.map['is_set'][indices] = True
        if self.std is not None:
            self.map['std'][indices] = self.std

    def fetch(self):
        """Fetch the stored value and std attributes.


        See Also
        --------
        store_current_value_in_array, assign_current_value_to_all

        """
        indices = self._axes_manager.indices[::-1]
        # If it is a single spectrum indices is ()
        if not indices:
            indices = (0, )
        if self.map['is_set'][indices]:
            value = self.map['values'][indices]
            std = self.map['std'][indices]
            if isinstance(value, dArray):
                value = value.compute()
            if isinstance(std, dArray):
                std = std.compute()
            self.value = value
            self.std = std

    def assign_current_value_to_all(self, mask=None):
        """Assign the current value attribute to all the  indices

        Parameters
        ----------
        mask: {None, boolean numpy array}
            Set only the indices that are not masked i.e. where
            mask is False.

        See Also
        --------
        store_current_value_in_array, fetch

        """
        if mask is None:
            mask = np.zeros(self.map.shape, dtype='bool')
        self.map['values'][mask == False] = self.value
        self.map['is_set'][mask == False] = True

    def _create_array(self):
        """Create the map array to store the information in
        multidimensional datasets.

        """
        shape = self._axes_manager._navigation_shape_in_array
        if not shape:
            shape = [
                1,
            ]
        # Shape-1 fields in dtypes won’t be collapsed to scalars in a future
        # numpy version (see release notes numpy 1.17.0)
        if self._number_of_elements > 1:
            dtype_ = np.dtype([('values', 'float', self._number_of_elements),
                               ('std', 'float', self._number_of_elements),
                               ('is_set', 'bool')])
        else:
            dtype_ = np.dtype([('values', 'float'), ('std', 'float'),
                               ('is_set', 'bool')])
        if (self.map is None or self.map.shape != shape
                or self.map.dtype != dtype_):
            self.map = np.zeros(shape, dtype_)
            self.map['std'].fill(np.nan)
            # TODO: in the future this class should have access to
            # axes manager and should be able to fetch its own
            # values. Until then, the next line is necessary to avoid
            # erros when self.std is defined and the shape is different
            # from the newly defined arrays
            self.std = None

    def as_signal(self, field='values'):
        """Get a parameter map as a signal object.

        Please note that this method only works when the navigation
        dimension is greater than 0.

        Parameters
        ----------
        field : {'values', 'std', 'is_set'}

        Raises
        ------

        NavigationDimensionError : if the navigation dimension is 0

        """
        from hyperspy.signal import BaseSignal

        s = BaseSignal(data=self.map[field],
                       axes=self._axes_manager._get_navigation_axes_dicts())
        if self.component is not None and \
                self.component.active_is_multidimensional:
            s.data[np.logical_not(self.component._active_array)] = np.nan

        s.metadata.General.title = ("%s parameter" %
                                    self.name if self.component is None else
                                    "%s parameter of %s component" %
                                    (self.name, self.component.name))
        for axis in s.axes_manager._axes:
            axis.navigate = False
        if self._number_of_elements > 1:
            s.axes_manager._append_axis(size=self._number_of_elements,
                                        name=self.name,
                                        navigate=True)
        s._assign_subclass()
        if field == "values":
            # Add the variance if available
            std = self.as_signal(field="std")
            if not np.isnan(std.data).all():
                std.data = std.data**2
                std.metadata.General.title = "Variance"
                s.metadata.set_item("Signal.Noise_properties.variance", std)
        return s

    def plot(self, **kwargs):
        """Plot parameter signal.

        Parameters
        ----------
        **kwargs
            Any extra keyword arguments are passed to the signal plot.

        Example
        -------
        >>> parameter.plot() #doctest: +SKIP

        Set the minimum and maximum displayed values

        >>> parameter.plot(vmin=0, vmax=1) #doctest: +SKIP
        """
        self.as_signal().plot(**kwargs)

    def export(self, folder=None, name=None, format="hspy", save_std=False):
        """Save the data to a file.

        All the arguments are optional.

        Parameters
        ----------
        folder : str or None
            The path to the folder where the file will be saved.
             If `None` the current folder is used by default.
        name : str or None
            The name of the file. If `None` the Components name followed
             by the Parameter `name` attributes will be used by default.
              If a file with the same name exists the name will be
              modified by appending a number to the file path.
        save_std : bool
            If True, also the standard deviation will be saved
        format: str
            The extension of any file format supported by HyperSpy, default hspy

        """
        if format is None:
            format = "hspy"
        if name is None:
            name = self.component.name + '_' + self.name
        filename = incremental_filename(slugify(name) + '.' + format)
        if folder is not None:
            filename = os.path.join(folder, filename)
        self.as_signal().save(filename)
        if save_std is True:
            self.as_signal(field='std').save(append2pathname(filename, '_std'))

    def as_dictionary(self, fullcopy=True):
        """Returns parameter as a dictionary, saving all attributes from
        self._whitelist.keys() For more information see
        :meth:`hyperspy.misc.export_dictionary.export_to_dictionary`

        Parameters
        ----------
        fullcopy : Bool (optional, False)
            Copies of objects are stored, not references. If any found,
            functions will be pickled and signals converted to dictionaries
        Returns
        -------
        dic : dictionary with the following keys:
            _id_name : string
                _id_name of the original parameter, used to create the
                dictionary. Has to match with the self._id_name
            _twins : list
                a list of ids of the twins of the parameter
            _whitelist : dictionary
                a dictionary, which keys are used as keywords to match with the
                parameter attributes.  For more information see
                :meth:`hyperspy.misc.export_dictionary.export_to_dictionary`
            * any field from _whitelist.keys() *

        """
        dic = {'_twins': [id(t) for t in self._twins]}
        export_to_dictionary(self, self._whitelist, dic, fullcopy)
        return dic

    def default_traits_view(self):
        # As mentioned above, the default editor for
        # value = t.Property(t.Either([t.CFloat(0), Array()]))
        # gives a ValueError. We therefore implement default_traits_view so
        # that configure/edit_traits will still work straight out of the box.
        # A whitelist controls which traits to include in this view.
        from traitsui.api import RangeEditor, View, Item
        whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value']
        editable_traits = [
            trait for trait in self.editable_traits() if trait in whitelist
        ]
        if 'value' in editable_traits:
            i = editable_traits.index('value')
            v = editable_traits.pop(i)
            editable_traits.insert(
                i,
                Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax')))
        view = View(editable_traits, buttons=['OK', 'Cancel'])
        return view
示例#22
0
文件: roi.py 项目: temcode/hyperspy
class RectangularROI(BaseInteractiveROI):
    """Selects a range in a 2D space. The coordinates of the range in
    the 2D space are stored in the traits 'left', 'right', 'top' and 'bottom'.
    """
    top, bottom, left, right = (t.CFloat(t.Undefined), ) * 4
    _ndim = 2

    def __init__(self, left, top, right, bottom):
        super(RectangularROI, self).__init__()
        self._bounds_check = True  # Use reponsibly!
        self.top, self.bottom, self.left, self.right = top, bottom, left, right

    def is_valid(self):
        return (t.Undefined
                not in (self.top, self.bottom, self.left, self.right)
                and self.right >= self.left and self.bottom >= self.top)

    def _top_changed(self, old, new):
        if self._bounds_check and \
                self.bottom is not t.Undefined and new >= self.bottom:
            self.top = old
        else:
            self.update()

    def _bottom_changed(self, old, new):
        if self._bounds_check and \
                self.top is not t.Undefined and new <= self.top:
            self.bottom = old
        else:
            self.update()

    def _right_changed(self, old, new):
        if self._bounds_check and \
                self.left is not t.Undefined and new <= self.left:
            self.right = old
        else:
            self.update()

    def _left_changed(self, old, new):
        if self._bounds_check and \
                self.right is not t.Undefined and new >= self.right:
            self.left = old
        else:
            self.update()

    def _get_ranges(self):
        ranges = (
            (self.left, self.right),
            (self.top, self.bottom),
        )
        return ranges

    def _set_from_widget(self, widget):
        p = np.array(widget.position)
        s = np.array(widget.size)
        (self.left, self.top), (self.right, self.bottom) = (p, p + s)

    def _apply_roi2widget(self, widget):
        widget.set_bounds(left=self.left,
                          bottom=self.bottom,
                          right=self.right,
                          top=self.top)

    def _get_widget_type(self, axes, signal):
        return widgets.RectangleWidget

    def __repr__(self):
        return "%s(left=%g, top=%g, right=%g, bottom=%g)" % (
            self.__class__.__name__, self.left, self.top, self.right,
            self.bottom)