コード例 #1
0
    def __init__(self, canvas, toolbar_manager, current_peak_type):
        """
        Create an instance of FitInteractiveTool.
        :param canvas: A MPL canvas to draw on.
        :param toolbar_manager: A helper object that checks and manipulates
            the state of the plot toolbar. It is necessary to disable this
            tool's editing when zoom/pan is enabled by the user.
        :param current_peak_type: A name of a peak fit function to create by default.
        """
        super(FitInteractiveTool, self).__init__()
        self.canvas = canvas
        self.toolbar_manager = toolbar_manager
        ax = canvas.figure.get_axes()[0]
        self.ax = ax
        xlim = ax.get_xlim()
        dx = (xlim[1] - xlim[0]) / 20.
        # The fitting range: [StartX, EndX]
        start_x = xlim[0] + dx
        end_x = xlim[1] - dx
        # The interactive range marker drawn on the canvas as vertical lines that represent the fitting range.
        self.fit_range = RangeMarker(canvas, 'green', start_x, end_x,
                                     'XMinMax', '--')
        self.fit_range.range_changed.connect(self.fit_range_changed)

        # A list of interactive peak markers
        self.peak_markers = []
        # A reference to the currently selected peak marker
        self.selected_peak = None
        # A width to set to newly created peaks
        self.fwhm = dx
        # The name of the currently selected peak
        self.current_peak_type = current_peak_type
        # A cache for peak function names to use in the add function dialog
        self.peak_names = []
        # A cache for background function names to use in the add function dialog
        self.background_names = []
        # A cache for names of function that are neither peaks or backgrounds to use in the add function dialog
        self.other_names = []

        # Connect MPL events to callbacks and store connection ids in a cache
        self._cids = []
        self._cids.append(canvas.mpl_connect('draw_event', self.draw_callback))
        self._cids.append(
            canvas.mpl_connect('motion_notify_event',
                               self.motion_notify_callback))
        self._cids.append(
            canvas.mpl_connect('button_press_event',
                               self.button_press_callback))
        self._cids.append(
            canvas.mpl_connect('button_release_event',
                               self.button_release_callback))

        # The mouse state machine that handles responses to the mouse events.
        self.mouse_state = StateMachine(self)
コード例 #2
0
        def connect(self, ws, call_back, xmin=None, xmax=None,
                    range_min=None, range_max=None, x_title=None,
                    log_scale=False,
                    ws_output_base=None):
            if not IS_IN_MANTIDGUI:
                print("RangeSelector cannot be used output MantidPlot")
                return

            self._call_back = call_back
            self._ws_output_base = ws_output_base

            g = plotSpectrum(ws, [0], True)
            self.canvas = g.canvas
            g.suptitle(self._graph)
            l = g.axes[0]
            try:
                title = ws[0].replace("_", " ")
                title.strip()
            except:
                title = " "
            l.set_title(title)
            if log_scale:
                l.yscale('log')
                l.xscale('linear')
            if x_title is not None:
                l.set_xlabel(x_title)
            if xmin is not None and xmax is not None:
                l.set_xlim(xmin, xmax)

            if range_min is None or range_max is None:
                range_min, range_max = l.get_xlim()
                range_min = range_min + (range_max-range_min)/100.0
                range_max = range_max - (range_max-range_min)/100.0
            self.marker = RangeMarker(l.figure.canvas, 'green', range_min, range_max, line_style='--')
            self.marker.min_marker.set_name('Min Q')
            self.marker.max_marker.set_name('Max Q')

            def add_range(event):
                #self.marker.min_marker.add_name()
                #self.marker.max_marker.add_name()
                self.marker.redraw()

            self.marker.range_changed.connect(self._call_back)
            self._cids.append(g.canvas.mpl_connect('draw_event', add_range))
            self._cids.append(g.canvas.mpl_connect('button_press_event', self.on_mouse_button_press))
            self._cids.append(g.canvas.mpl_connect('motion_notify_event',self.motion_event))
            self._cids.append(g.canvas.mpl_connect('button_release_event', self.on_mouse_button_release))
コード例 #3
0
ファイル: interactive_tool.py プロジェクト: ethoeng/mantid
class FitInteractiveTool(QObject):
    """
    Peak editing tool. Peaks can be added by clicking on the plot. Peak parameters can be edited with the mouse.
    """

    fit_range_changed = Signal(list)
    peak_added = Signal(int, float, float, float)
    peak_moved = Signal(int, float, float)
    peak_fwhm_changed = Signal(int, float)
    peak_type_changed = Signal(str)
    add_background_requested = Signal(str)
    add_other_requested = Signal(str)

    default_background = 'LinearBackground'

    def __init__(self,
                 canvas,
                 toolbar_manager,
                 current_peak_type,
                 default_background=None):
        """
        Create an instance of FitInteractiveTool.
        :param canvas: A MPL canvas to draw on.
        :param toolbar_manager: A helper object that checks and manipulates
            the state of the plot toolbar. It is necessary to disable this
            tool's editing when zoom/pan is enabled by the user.
        :param current_peak_type: A name of a peak fit function to create by default.
        """
        super(FitInteractiveTool, self).__init__()
        self.canvas = canvas
        self.toolbar_manager = toolbar_manager
        ax = canvas.figure.get_axes()[0]
        self.ax = ax
        xlim = ax.get_xlim()
        dx = (xlim[1] - xlim[0]) / 20.
        # The fitting range: [StartX, EndX]
        start_x = xlim[0] + dx
        end_x = xlim[1] - dx
        # The interactive range marker drawn on the canvas as vertical lines that represent the fitting range.
        self.fit_range = RangeMarker(canvas, 'green', start_x, end_x,
                                     'XMinMax', '--')
        self.fit_range.range_changed.connect(self.fit_range_changed)

        # A list of interactive peak markers
        self.peak_markers = []
        # A reference to the currently selected peak marker
        self.selected_peak = None
        # A width to set to newly created peaks
        self.fwhm = dx
        # The name of the currently selected peak
        self.current_peak_type = current_peak_type
        # A cache for peak function names to use in the add function dialog
        self.peak_names = []
        # A cache for background function names to use in the add function dialog
        self.background_names = []
        # A cache for names of function that are neither peaks or backgrounds to use in the add function dialog
        self.other_names = []
        # The name of the default background type
        if default_background:
            self.default_background = default_background

        # Connect MPL events to callbacks and store connection ids in a cache
        self._cids = []
        self._cids.append(canvas.mpl_connect('draw_event', self.draw_callback))
        self._cids.append(
            canvas.mpl_connect('motion_notify_event',
                               self.motion_notify_callback))
        self._cids.append(
            canvas.mpl_connect('button_press_event',
                               self.button_press_callback))
        self._cids.append(
            canvas.mpl_connect('button_release_event',
                               self.button_release_callback))
        self._cids.append(
            canvas.mpl_connect('figure_leave_event', self.stop_add_peak))

        # The mouse state machine that handles responses to the mouse events.
        self.mouse_state = StateMachine(self)

    def set_visible(self, visible):
        self.fit_range.set_visible(visible)
        for marker in self.peak_markers:
            marker.set_visible(visible)

    def disconnect(self):
        """
        Disconnect the tool from everything
        """
        QObject.disconnect(self)
        for cid in self._cids:
            self.canvas.mpl_disconnect(cid)
        self.fit_range.remove()

    def draw_callback(self, event):
        """
        This is called at every canvas draw. Redraw the markers.
        :param event: Unused
        """
        self.fit_range.redraw()
        for pm in self.peak_markers:
            pm.redraw()

    def motion_notify_callback(self, event):
        """
        This is called when the mouse moves across the canvas
        :param event: An event object with information on the current mouse position
        """
        self.mouse_state.motion_notify_callback(event)

    def button_press_callback(self, event):
        """
        This is called when a mouse button is pressed inside the canvas
        :param event: An event object with information on the current mouse position
        """
        self.mouse_state.button_press_callback(event)

    def button_release_callback(self, event):
        """
        This is called when a mouse button is released inside the canvas
        :param event: An event object with information on the current mouse position
        """
        self.mouse_state.button_release_callback(event)

    def move_markers(self, event):
        """
        Move markers that need moving.
        :param event: A MPL mouse event.
        """
        x, y = event.xdata, event.ydata
        if x is None or y is None:
            return

        should_redraw = self.fit_range.mouse_move(x, y)
        for pm in self.peak_markers:
            should_redraw = pm.mouse_move(x, y) or should_redraw
        if should_redraw:
            self.canvas.draw()

    def start_move_markers(self, event):
        """
        Start moving markers under the mouse.
        :param event: A MPL mouse event.
        """
        x = event.xdata
        y = event.ydata
        if x is None or y is None:
            return
        self.fit_range.mouse_move_start(x, y)
        selected_peak = None
        for pm in self.peak_markers:
            pm.mouse_move_start(x, y)
            if pm.is_moving:
                selected_peak = pm
        if selected_peak is not None:
            self.select_peak(selected_peak)
            self.canvas.draw()

    def stop_move_markers(self, event):
        """
        Stop moving all markers.
        """
        self.fit_range.mouse_move_stop()
        for pm in self.peak_markers:
            pm.mouse_move_stop()

    def set_fit_range(self, start_x, end_x):
        """
        Change the fit range when it has been changed in the FitPropertyBrowser.
        :param start_x: New value of StartX
        :param end_x: New value of EndX
        """
        if start_x is not None and end_x is not None:
            self.fit_range.set_range(start_x, end_x)
            self.canvas.draw()

    def _make_peak_id(self):
        """
        Generate a new peak id. Ids of deleted markers can be reused.
        :return: An integer id that is unique among self.peak_markers.
        """
        ids = set([pm.peak_id for pm in self.peak_markers])
        n = 0
        for i in range(len(ids)):
            if i in ids:
                if i > n:
                    n = i
            else:
                return i
        return n + 1

    def add_default_peak(self):
        """
        A QAction callback. Start adding a new peak. The tool will expect the user to click on the canvas to
        where the peak should be placed.
        """
        self.mouse_state.transition_to('add_peak')

    def add_peak_dialog(self):
        """
        A QAction callback. Start a dialog to choose a peak function name. After that the tool will expect the user
        to click on the canvas to where the peak should be placed.
        """
        dialog = AddFunctionDialog(self.canvas, self.peak_names)
        dialog.view.ui.functionBox.lineEdit().setPlaceholderText(
            self.current_peak_type)
        dialog.view.function_added.connect(self.action_peak_added)
        dialog.view.open()

    def action_peak_added(self, function_name):
        self.peak_type_changed.emit(function_name)
        self.mouse_state.transition_to('add_peak')

    def add_background_dialog(self):
        """
        A QAction callback. Start a dialog to choose a background function name. The new function is added to the
        browser.
        """
        dialog = AddFunctionDialog(self.canvas, self.background_names)
        dialog.view.function_added.connect(self.add_background_requested)
        dialog.view.open()

    def add_other_dialog(self):
        """
        A QAction callback. Start a dialog to choose a name of a function except a peak or a background. The new
        function is added to the browser.
        """
        dialog = AddFunctionDialog(self.canvas, self.other_names)
        dialog.view.function_added.connect(self.add_other_requested)
        dialog.view.open()

    def add_peak_marker(self, x, y_top, y_bottom=0.0, fwhm=None):
        """
        Add a new peak marker. No signal is sent to the fit browser.
        :param x: The peak centre.
        :param y_top: The y coordinate of the top of the peak.
        :param y_bottom: The y coordinate of the bottom of the peak (background level).
        :param fwhm: A full width at half maximum. If None use the value of the FWHM of the last edited peak.
        :return: An instance of PeakMarker.
        """
        if fwhm is None:
            fwhm = self.fwhm
        peak_id = self._make_peak_id()
        peak = PeakMarker(self.canvas, peak_id, x, y_top, y_bottom, fwhm=fwhm)
        peak.peak_moved.connect(self.peak_moved)
        peak.fwhm_changed.connect(self.peak_fwhm_changed_slot)
        self.peak_markers.append(peak)
        return peak

    def add_peak(self, x, y_top, y_bottom=0.0):
        """
        Add a new peak marker and send a signal to the fit browser to add a new peak function.
        :param x: The peak centre.
        :param y_top: The y coordinate of the top of the peak.
        :param y_bottom: The y coordinate of the bottom of the peak (background level).
        """
        peak = self.add_peak_marker(x, y_top, y_bottom)
        self.select_peak(peak)
        self.canvas.draw()
        self.peak_added.emit(peak.peak_id, x, peak.height(), peak.fwhm())

    def stop_add_peak(self, event):
        self.mouse_state.state = self.mouse_state.state.transition()

    def update_peak(self, peak_id, centre, height, fwhm):
        """
        Update a peak marker.
        :param peak_id: An id of the marker to update.
        :param centre: A new peak centre.
        :param height: A new peak height.
        :param fwhm: A new peak width.
        """
        for pm in self.peak_markers:
            if pm.peak_id == peak_id:
                pm.update_peak(centre, height, fwhm)
        self.canvas.draw()

    def select_peak(self, peak):
        """
        Make a peak marker selected. Deselect all others.
        :param peak: An instance of PeakMarker to select.
        """
        self.selected_peak = None
        for pm in self.peak_markers:
            if peak == pm:
                pm.select()
                self.selected_peak = peak
            else:
                pm.deselect()

    def _get_default_height(self):
        """
        Calculate the value of the default peak height to set to peaks added by the user to the fit property browser
        directly.
        """
        ylim = self.ax.get_ylim()
        return (ylim[0] + ylim[1]) / 2

    def get_peak_list(self):
        """
        get a list of peak parameters as tuples of (id, centre, height, fwhm).
        """
        plist = []
        for pm in self.peak_markers:
            plist.append((pm.peak_id, pm.centre(), pm.height(), pm.fwhm()))
        return plist

    def update_peak_markers(self, peaks_to_keep, peaks_to_add):
        """
        Update the peak marker list.
        :param peaks_to_keep: A list of ids of the peaks that should be kept. Markers with ids not found in this list
            will be removed.
        :param peaks_to_add: Parameters of peaks to add as a list of tuples (prefix, centre, height, fwhm).
        :return: A tuple of: first item: {map of peak id -> prefix},
                             second item: a list of (prefix, centre, height, fwhm) for those added peaks that had
                                          their parameters changed and need to be updated in the fit browser.
                                          Parameters are changed if the added peak has zero height or width.
        """
        peaks_to_remove = []
        for i, pm in enumerate(self.peak_markers):
            if pm.peak_id not in peaks_to_keep:
                peaks_to_remove.append(i)
        peaks_to_remove.sort(reverse=True)
        for i in peaks_to_remove:
            self.peak_markers[i].remove()
            del self.peak_markers[i]
        peak_ids = {}
        peak_updates = []
        for prefix, c, h, w in peaks_to_add:
            do_updates = False
            if h == 0.0:
                h = self._get_default_height()
                do_updates = True
            if w <= 0:
                w = self.fwhm
                do_updates = True
            pm = self.add_peak_marker(c, h, fwhm=w)
            peak_ids[pm.peak_id] = prefix
            if do_updates:
                peak_updates.append((prefix, c, h, w))
        self.canvas.draw()
        return peak_ids, peak_updates

    @Slot(int, float)
    def peak_fwhm_changed_slot(self, peak_id, fwhm):
        """
        Respond to a peak marker changing its width.
        :param peak_id: Marker's peak id.
        :param fwhm: A new fwhm value.
        """
        self.fwhm = fwhm
        self.peak_fwhm_changed.emit(peak_id, fwhm)

    def get_transform(self):
        """
        Get the MPL transform object used to draw the markers. Used by the unit tests.
        """
        return self.fit_range.patch.get_transform()

    def add_to_menu(self, menu, peak_names, current_peak_type,
                    background_names, other_names):
        """
        Adds the fit tool menu actions to the given menu and returns the menu

        :param menu: A reference to a menu that will accept the actions
        :param peak_names: A list of registered fit function peak names to be offered to choose from by the "Add a peak"
            dialog.
        :param current_peak_type:
        :param background_names: A list of registered background fit functions to be offered to choose from by the
            "Add a background" dialog.
        :param other_names:  A list of other registered fit functions to be offered to choose from by the
            "Add other function" dialog.
        :returns: The menu reference passed in
        """
        self.peak_names = peak_names
        self.current_peak_type = current_peak_type
        self.background_names = background_names
        self.other_names = other_names
        if not self.toolbar_manager.is_tool_active():
            menu.addAction("Add peak", self.add_default_peak)
            menu.addAction("Select peak type", self.add_peak_dialog)
            menu.addAction("Add background", self.add_background_dialog)
            menu.addAction("Add other function", self.add_other_dialog)

        return menu
コード例 #4
0
    class _Selector(QObject):
        """
            Selector class for selecting ranges in Mantidplot
        """
        def __init__(self):
            super().__init__()
            self._call_back = None
            self._ws_output_base = None
            self._graph = "Range Selector"
            self._cids = []
            self.marker = None
            self.canvas = None

        def on_mouse_button_press(self, event):
            """Respond to a MouseEvent where a button was pressed"""
            # local variables to avoid constant self lookup
            x_pos = event.xdata
            y_pos = event.ydata
            if x_pos is None or y_pos is None:
                return

            # If left button clicked, start moving peaks
            if event.button == 1 and self.marker:
                self.marker.mouse_move_start(x_pos, y_pos)

        def stop_markers(self, x_pos, y_pos):
            """
            Stop all markers that are moving and draw the annotations
            """
            if self.marker:
                self.marker.mouse_move(x_pos, y_pos)
                self.marker.mouse_move_stop()
                self.marker.min_marker.add_all_annotations()
                self.marker.max_marker.add_all_annotations()

        def on_mouse_button_release(self, event):
            """ Stop moving the markers when the mouse button is released """
            x_pos = event.xdata
            y_pos = event.ydata

            if x_pos is None or y_pos is None:
                return

            self.stop_markers(x_pos, y_pos)

        def motion_event(self, event):
            """ Move the marker if the mouse is moving and in range """
            if event is None:
                return

            x = event.xdata
            y = event.ydata
            #self._set_hover_cursor(x, y)

            if self.canvas and self.marker.mouse_move(x, y):
                self.canvas.draw()

        def disconnect(self):
            if IS_IN_MANTIDGUI and self.canvas:
                if self.marker:
                    self.marker.range_changed.disconnect()
                for cid in self._cids:
                    self.canvas.mpl_disconnect(cid)

        def connect(self, ws, call_back, xmin=None, xmax=None,
                    range_min=None, range_max=None, x_title=None,
                    log_scale=False,
                    ws_output_base=None):
            if not IS_IN_MANTIDGUI:
                print("RangeSelector cannot be used output MantidPlot")
                return

            self._call_back = call_back
            self._ws_output_base = ws_output_base

            g = plotSpectrum(ws, [0], True)
            self.canvas = g.canvas
            g.suptitle(self._graph)
            l = g.axes[0]
            try:
                title = ws[0].replace("_", " ")
                title.strip()
            except:
                title = " "
            l.set_title(title)
            if log_scale:
                l.yscale('log')
                l.xscale('linear')
            if x_title is not None:
                l.set_xlabel(x_title)
            if xmin is not None and xmax is not None:
                l.set_xlim(xmin, xmax)

            if range_min is None or range_max is None:
                range_min, range_max = l.get_xlim()
                range_min = range_min + (range_max-range_min)/100.0
                range_max = range_max - (range_max-range_min)/100.0
            self.marker = RangeMarker(l.figure.canvas, 'green', range_min, range_max, line_style='--')
            self.marker.min_marker.set_name('Min Q')
            self.marker.max_marker.set_name('Max Q')

            def add_range(event):
                #self.marker.min_marker.add_name()
                #self.marker.max_marker.add_name()
                self.marker.redraw()

            self.marker.range_changed.connect(self._call_back)
            self._cids.append(g.canvas.mpl_connect('draw_event', add_range))
            self._cids.append(g.canvas.mpl_connect('button_press_event', self.on_mouse_button_press))
            self._cids.append(g.canvas.mpl_connect('motion_notify_event',self.motion_event))
            self._cids.append(g.canvas.mpl_connect('button_release_event', self.on_mouse_button_release))