Ejemplo n.º 1
0
    def enable(self):

        self.nav_mode = NavigateMouseMode(self.viewer,
                                          press_callback=self._on_nav_activate)
        self.rng_mode = RangeMouseMode(self.viewer)

        self.nav_mode.state.add_callback('x', self._on_slider_change)

        self.ui.tabs.setCurrentIndex(0)

        self.ui.tabs.currentChanged.connect(self._on_tab_change)

        self.ui.button_settings.clicked.connect(self._on_settings)
        self.ui.button_fit.clicked.connect(self._on_fit)
        self.ui.button_clear.clicked.connect(self._on_clear)
        self.ui.button_collapse.clicked.connect(self._on_collapse)

        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.text_log.document().setDefaultFont(font)
        self.ui.text_log.setLineWrapMode(self.ui.text_log.NoWrap)

        self.axes = self.viewer.axes
        self.canvas = self.axes.figure.canvas

        self._fit_artists = []

        ProfileTools.fit_function.set_choices(self, list(fit_plugin))
        ProfileTools.fit_function.set_display_func(self,
                                                   lambda fitter: fitter.label)
        connect_combo_selection(self, 'fit_function',
                                self.ui.combosel_fit_function)

        ProfileTools.collapse_function.set_choices(self, list(COLLAPSE_FUNCS))
        ProfileTools.collapse_function.set_display_func(
            self, COLLAPSE_FUNCS.get)
        connect_combo_selection(self, 'collapse_function',
                                self.ui.combosel_collapse_function)

        self._toolbar_connected = False

        self.viewer.toolbar_added.connect(self._on_toolbar_added)

        self.viewer.state.add_callback('x_att', self._on_x_att_change)
Ejemplo n.º 2
0
    def enable(self):

        self.nav_mode = NavigateMouseMode(self.viewer,
                                          press_callback=self._on_nav_activate)
        self.rng_mode = RangeMouseMode(self.viewer)

        self.nav_mode.state.add_callback('x', self._on_slider_change)

        self.ui.tabs.setCurrentIndex(0)

        self.ui.tabs.currentChanged.connect(self._on_tab_change)

        self.ui.button_settings.clicked.connect(self._on_settings)
        self.ui.button_fit.clicked.connect(self._on_fit)
        self.ui.button_clear.clicked.connect(self._on_clear)
        self.ui.button_collapse.clicked.connect(self._on_collapse)

        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.text_log.document().setDefaultFont(font)
        self.ui.text_log.setLineWrapMode(self.ui.text_log.NoWrap)

        self.axes = self.viewer.axes
        self.canvas = self.axes.figure.canvas

        self._fit_artists = []

        ProfileTools.fit_function.set_choices(self, list(fit_plugin))
        ProfileTools.fit_function.set_display_func(self, lambda fitter: fitter.label)
        connect_combo_selection(self, 'fit_function', self.ui.combosel_fit_function)

        ProfileTools.collapse_function.set_choices(self, list(COLLAPSE_FUNCS))
        ProfileTools.collapse_function.set_display_func(self, COLLAPSE_FUNCS.get)
        connect_combo_selection(self, 'collapse_function', self.ui.combosel_collapse_function)

        self._toolbar_connected = False

        self.viewer.toolbar_added.connect(self._on_toolbar_added)

        self.viewer.state.add_callback('x_att', self._on_x_att_change)
Ejemplo n.º 3
0
class ProfileTools(QtWidgets.QWidget):

    fit_function = SelectionCallbackProperty()
    collapse_function = SelectionCallbackProperty()

    def __init__(self, parent=None):

        super(ProfileTools, self).__init__(parent=parent)

        self._viewer = weakref.ref(parent)

        self.ui = load_ui('profile_tools.ui', self,
                          directory=os.path.dirname(__file__))

        fix_tab_widget_fontsize(self.ui.tabs)

        self.image_viewer = None

    @property
    def viewer(self):
        return self._viewer()

    def show(self, *args):
        super(ProfileTools, self).show(*args)
        self._on_tab_change()

    def hide(self, *args):
        super(ProfileTools, self).hide(*args)
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def enable(self):

        self.nav_mode = NavigateMouseMode(self.viewer,
                                          press_callback=self._on_nav_activate)
        self.rng_mode = RangeMouseMode(self.viewer)

        self.nav_mode.state.add_callback('x', self._on_slider_change)

        self.ui.tabs.setCurrentIndex(0)

        self.ui.tabs.currentChanged.connect(self._on_tab_change)

        self.ui.button_settings.clicked.connect(self._on_settings)
        self.ui.button_fit.clicked.connect(self._on_fit)
        self.ui.button_clear.clicked.connect(self._on_clear)
        self.ui.button_collapse.clicked.connect(self._on_collapse)

        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.text_log.document().setDefaultFont(font)
        self.ui.text_log.setLineWrapMode(self.ui.text_log.NoWrap)

        self.axes = self.viewer.axes
        self.canvas = self.axes.figure.canvas

        self._fit_artists = []

        ProfileTools.fit_function.set_choices(self, list(fit_plugin))
        ProfileTools.fit_function.set_display_func(self, lambda fitter: fitter.label)
        self._connection_fit = connect_combo_selection(self, 'fit_function', self.ui.combosel_fit_function)

        ProfileTools.collapse_function.set_choices(self, list(COLLAPSE_FUNCS))
        ProfileTools.collapse_function.set_display_func(self, COLLAPSE_FUNCS.get)
        self._connection_collapse = connect_combo_selection(self, 'collapse_function', self.ui.combosel_collapse_function)

        self._toolbar_connected = False

        self.viewer.toolbar_added.connect(self._on_toolbar_added)

        self.viewer.state.add_callback('x_att', self._on_x_att_change)

    def _on_x_att_change(self, *event):
        self.nav_mode.clear()
        self.rng_mode.clear()

    def _on_nav_activate(self, *args):
        self._nav_data = self._visible_data()
        self._nav_viewers = {}
        for data in self._nav_data:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)
            self._nav_viewers[data] = self._viewers_with_data_slice(data, pix_cid)

    def _on_slider_change(self, *args):

        x = self.nav_mode.state.x

        if x is None:
            return

        for data in self._nav_data:

            axis, slc = self._get_axis_and_pixel_slice(data, x)

            for viewer in self._nav_viewers[data]:
                slices = list(viewer.state.slices)
                slices[axis] = slc
                viewer.state.slices = tuple(slices)

    def _get_axis_and_pixel_slice(self, data, x):

        if self.viewer.state.x_att in data.pixel_component_ids:
            axis = self.viewer.state.x_att.axis
            slc = int(round(x))
        else:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)
            axis = pix_cid.axis
            axis_view = [0] * data.ndim
            axis_view[pix_cid.axis] = slice(None)
            axis_values = data[self.viewer.state.x_att, axis_view]
            slc = int(np.argmin(np.abs(axis_values - x)))

        return axis, slc

    def _on_settings(self):
        d = FitSettingsWidget(self.fit_function())
        d.exec_()

    def _on_fit(self):
        """
        Fit a model to the data

        The fitting happens on a dedicated thread, to keep the UI
        responsive
        """

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        x_range = self.rng_mode.state.x_range
        fitter = self.fit_function()

        def on_success(result):
            fit_results, x, y = result
            report = ""
            normalize = {}
            for layer_artist in fit_results:
                report += ("<b><font color='{0}'>{1}</font>"
                           "</b>".format(color2hex(layer_artist.state.color),
                                         layer_artist.layer.label))
                report += "<pre>" + fitter.summarize(fit_results[layer_artist], x, y) + "</pre>"
                if self.viewer.state.normalize:
                    normalize[layer_artist] = layer_artist.state.normalize_values
            self._report_fit(report)
            self._plot_fit(fitter, fit_results, x, y, normalize)

        def on_fail(exc_info):
            exc = '\n'.join(traceback.format_exception(*exc_info))
            self._report_fit("Error during fitting:\n%s" % exc)

        def on_done():
            self.ui.button_fit.setText("Fit")
            self.ui.button_fit.setEnabled(True)
            self.canvas.draw_idle()

        self.ui.button_fit.setText("Running...")
        self.ui.button_fit.setEnabled(False)

        w = Worker(self._fit, fitter, xlim=x_range)
        w.result.connect(on_success)
        w.error.connect(on_fail)
        w.finished.connect(on_done)

        self._fit_worker = w  # hold onto a reference
        w.start()

    def wait_for_fit(self):
        self._fit_worker.wait()

    def _report_fit(self, report):
        self.ui.text_log.document().setHtml(report)

    def _on_clear(self):
        self.ui.text_log.document().setPlainText('')
        self._clear_fit()
        self.canvas.draw_idle()

    def _fit(self, fitter, xlim=None):

        # We cycle through all the visible layers and get the plotted data
        # for each one of them.

        results = {}
        for layer in self.viewer.layers:
            if layer.enabled and layer.visible:
                x, y = layer.state.profile
                x = np.asarray(x)
                y = np.asarray(y)
                keep = (x >= min(xlim)) & (x <= max(xlim))
                if len(x) > 0:
                    results[layer] = fitter.build_and_fit(x[keep], y[keep])

        return results, x, y

    def _clear_fit(self):
        for artist in self._fit_artists[:]:
            artist.remove()
            self._fit_artists.remove(artist)

    def _plot_fit(self, fitter, fit_result, x, y, normalize):

        self._clear_fit()

        for layer in fit_result:
            # y_model = fitter.predict(fit_result[layer], x)
            self._fit_artists.append(fitter.plot(fit_result[layer], self.axes, x,
                                                 alpha=layer.state.alpha,
                                                 linewidth=layer.state.linewidth * 0.5,
                                                 color=layer.state.color,
                                                 normalize=normalize.get(layer, None))[0])

        self.canvas.draw_idle()

    def _visible_data(self):
        datasets = set()
        for layer_artist in self.viewer.layers:
            if layer_artist.enabled and layer_artist.visible:
                if isinstance(layer_artist.state.layer, BaseData):
                    datasets.add(layer_artist.state.layer)
                elif isinstance(layer_artist.state.layer, Subset):
                    datasets.add(layer_artist.state.layer.data)
        return list(datasets)

    def _viewers_with_data_slice(self, data, xatt):

        if self.viewer.session.application is None:
            return []

        viewers = []
        for tab in self.viewer.session.application.viewers:
            for viewer in tab:
                if isinstance(viewer, ImageViewer):
                    for layer_artist in viewer._layer_artist_container[data]:
                        if layer_artist.enabled and layer_artist.visible:
                            if len(viewer.state.slices) >= xatt.axis:
                                viewers.append(viewer)
        return viewers

    def _on_collapse(self):

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        func = self.collapse_function
        x_range = self.rng_mode.state.x_range

        for data in self._visible_data():

            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)

            for viewer in self._viewers_with_data_slice(data, pix_cid):

                slices = list(viewer.state.slices)

                # TODO: don't need to fetch axis twice
                axis, imin = self._get_axis_and_pixel_slice(data, x_range[0])
                axis, imax = self._get_axis_and_pixel_slice(data, x_range[1])

                current_slice = slices[axis]

                if isinstance(current_slice, AggregateSlice):
                    current_slice = current_slice.center

                imin, imax = min(imin, imax), max(imin, imax)

                slices[axis] = AggregateSlice(slice(imin, imax),
                                              current_slice,
                                              func)

                viewer.state.slices = tuple(slices)

    @property
    def mode(self):
        return MODES[self.tabs.currentIndex()]

    def _on_toolbar_added(self, *event):
        self.viewer.toolbar.tool_activated.connect(self._on_toolbar_activate)
        self.viewer.toolbar.tool_deactivated.connect(self._on_tab_change)

    def _on_toolbar_activate(self, *event):
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def _on_tab_change(self, *event):
        mode = self.mode
        if mode == 'navigate':
            self.rng_mode.deactivate()
            self.nav_mode.activate()
        else:
            self.rng_mode.activate()
            self.nav_mode.deactivate()
Ejemplo n.º 4
0
class ProfileTools(QtWidgets.QWidget):

    fit_function = SelectionCallbackProperty()
    collapse_function = SelectionCallbackProperty()

    def __init__(self, parent=None):

        super(ProfileTools, self).__init__(parent=parent)

        self.ui = load_ui('profile_tools.ui', self,
                          directory=os.path.dirname(__file__))

        fix_tab_widget_fontsize(self.ui.tabs)

        self._viewer = weakref.ref(parent)
        self.image_viewer = None

    @property
    def viewer(self):
        return self._viewer()

    def show(self, *args):
        super(ProfileTools, self).show(*args)
        self._on_tab_change()

    def hide(self, *args):
        super(ProfileTools, self).hide(*args)
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def enable(self):

        self.nav_mode = NavigateMouseMode(self.viewer,
                                          press_callback=self._on_nav_activate)
        self.rng_mode = RangeMouseMode(self.viewer)

        self.nav_mode.state.add_callback('x', self._on_slider_change)

        self.ui.tabs.setCurrentIndex(0)

        self.ui.tabs.currentChanged.connect(self._on_tab_change)

        self.ui.button_settings.clicked.connect(self._on_settings)
        self.ui.button_fit.clicked.connect(self._on_fit)
        self.ui.button_clear.clicked.connect(self._on_clear)
        self.ui.button_collapse.clicked.connect(self._on_collapse)

        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.text_log.document().setDefaultFont(font)
        self.ui.text_log.setLineWrapMode(self.ui.text_log.NoWrap)

        self.axes = self.viewer.axes
        self.canvas = self.axes.figure.canvas

        self._fit_artists = []

        ProfileTools.fit_function.set_choices(self, list(fit_plugin))
        ProfileTools.fit_function.set_display_func(self, lambda fitter: fitter.label)
        connect_combo_selection(self, 'fit_function', self.ui.combosel_fit_function)

        ProfileTools.collapse_function.set_choices(self, list(COLLAPSE_FUNCS))
        ProfileTools.collapse_function.set_display_func(self, COLLAPSE_FUNCS.get)
        connect_combo_selection(self, 'collapse_function', self.ui.combosel_collapse_function)

        self._toolbar_connected = False

        self.viewer.toolbar_added.connect(self._on_toolbar_added)

        self.viewer.state.add_callback('x_att', self._on_x_att_change)

    def _on_x_att_change(self, *event):
        self.nav_mode.clear()
        self.rng_mode.clear()

    def _on_nav_activate(self, *args):
        self._nav_data = self._visible_data()
        self._nav_viewers = {}
        for data in self._nav_data:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att)
            self._nav_viewers[data] = self._viewers_with_data_slice(data, pix_cid)

    def _on_slider_change(self, *args):

        x = self.nav_mode.state.x

        if x is None:
            return

        for data in self._nav_data:

            axis, slc = self._get_axis_and_pixel_slice(data, x)

            for viewer in self._nav_viewers[data]:
                slices = list(viewer.state.slices)
                slices[axis] = slc
                viewer.state.slices = tuple(slices)

    def _get_axis_and_pixel_slice(self, data, x):

        if self.viewer.state.x_att in data.pixel_component_ids:
            axis = self.viewer.state.x_att.axis
            slc = int(round(x))
        else:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att)
            axis = pix_cid.axis
            axis_view = [0] * data.ndim
            axis_view[pix_cid.axis] = slice(None)
            axis_values = data[self.viewer.state.x_att, axis_view]
            slc = int(np.argmin(np.abs(axis_values - x)))

        return axis, slc

    def _on_settings(self):
        d = FitSettingsWidget(self.fit_function())
        d.exec_()

    def _on_fit(self):
        """
        Fit a model to the data

        The fitting happens on a dedicated thread, to keep the UI
        responsive
        """

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        x_range = self.rng_mode.state.x_range
        fitter = self.fit_function()

        def on_success(result):
            fit_results, x, y = result
            report = ""
            normalize = {}
            for layer_artist in fit_results:
                report += ("<b><font color='{0}'>{1}</font>"
                           "</b>".format(color2hex(layer_artist.state.color),
                                         layer_artist.layer.label))
                report += "<pre>" + fitter.summarize(fit_results[layer_artist], x, y) + "</pre>"
                if self.viewer.state.normalize:
                    normalize[layer_artist] = layer_artist.state.normalize_values
            self._report_fit(report)
            self._plot_fit(fitter, fit_results, x, y, normalize)

        def on_fail(exc_info):
            exc = '\n'.join(traceback.format_exception(*exc_info))
            self._report_fit("Error during fitting:\n%s" % exc)

        def on_done():
            self.ui.button_fit.setText("Fit")
            self.ui.button_fit.setEnabled(True)
            self.canvas.draw()

        self.ui.button_fit.setText("Running...")
        self.ui.button_fit.setEnabled(False)

        w = Worker(self._fit, fitter, xlim=x_range)
        w.result.connect(on_success)
        w.error.connect(on_fail)
        w.finished.connect(on_done)

        self._fit_worker = w  # hold onto a reference
        w.start()

    def wait_for_fit(self):
        self._fit_worker.wait()

    def _report_fit(self, report):
        self.ui.text_log.document().setHtml(report)

    def _on_clear(self):
        self.ui.text_log.document().setPlainText('')
        self._clear_fit()
        self.canvas.draw()

    def _fit(self, fitter, xlim=None):

        # We cycle through all the visible layers and get the plotted data
        # for each one of them.

        results = {}
        for layer in self.viewer.layers:
            if layer.enabled and layer.visible:
                x, y = layer.state.profile
                x = np.asarray(x)
                y = np.asarray(y)
                keep = (x >= min(xlim)) & (x <= max(xlim))
                if len(x) > 0:
                    results[layer] = fitter.build_and_fit(x[keep], y[keep])

        return results, x, y

    def _clear_fit(self):
        for artist in self._fit_artists[:]:
            artist.remove()
            self._fit_artists.remove(artist)

    def _plot_fit(self, fitter, fit_result, x, y, normalize):

        self._clear_fit()

        for layer in fit_result:
            # y_model = fitter.predict(fit_result[layer], x)
            self._fit_artists.append(fitter.plot(fit_result[layer], self.axes, x,
                                                 alpha=layer.state.alpha,
                                                 linewidth=layer.state.linewidth * 0.5,
                                                 color=layer.state.color,
                                                 normalize=normalize.get(layer, None))[0])

        self.canvas.draw()

    def _visible_data(self):
        datasets = set()
        for layer_artist in self.viewer.layers:
            if layer_artist.enabled and layer_artist.visible:
                if isinstance(layer_artist.state.layer, BaseData):
                    datasets.add(layer_artist.state.layer)
                elif isinstance(layer_artist.state.layer, Subset):
                    datasets.add(layer_artist.state.layer.data)
        return list(datasets)

    def _viewers_with_data_slice(self, data, xatt):

        if self.viewer.session.application is None:
            return []

        viewers = []
        for tab in self.viewer.session.application.viewers:
            for viewer in tab:
                if isinstance(viewer, ImageViewer):
                    for layer_artist in viewer._layer_artist_container[data]:
                        if layer_artist.enabled and layer_artist.visible:
                            if len(viewer.state.slices) >= xatt.axis:
                                viewers.append(viewer)
        return viewers

    def _on_collapse(self):

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        func = self.collapse_function
        x_range = self.rng_mode.state.x_range

        for data in self._visible_data():

            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att)

            for viewer in self._viewers_with_data_slice(data, pix_cid):

                slices = list(viewer.state.slices)

                # TODO: don't need to fetch axis twice
                axis, imin = self._get_axis_and_pixel_slice(data, x_range[0])
                axis, imax = self._get_axis_and_pixel_slice(data, x_range[1])

                current_slice = slices[axis]

                if isinstance(current_slice, AggregateSlice):
                    current_slice = current_slice.center

                slices[axis] = AggregateSlice(slice(imin, imax),
                                              current_slice,
                                              func)

                viewer.state.slices = tuple(slices)

    @property
    def mode(self):
        return MODES[self.tabs.currentIndex()]

    def _on_toolbar_added(self, *event):
        self.viewer.toolbar.tool_activated.connect(self._on_toolbar_activate)
        self.viewer.toolbar.tool_deactivated.connect(self._on_tab_change)

    def _on_toolbar_activate(self, *event):
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def _on_tab_change(self, *event):
        mode = self.mode
        if mode == 'navigate':
            self.rng_mode.deactivate()
            self.nav_mode.activate()
        else:
            self.rng_mode.activate()
            self.nav_mode.deactivate()