Ejemplo n.º 1
0
class HistogramWidget(QpWidget):
    """
    Widget which displays data histograms
    """
    def __init__(self, **kwargs):
        super(HistogramWidget, self).__init__(name="Histogram", icon="hist",
                                              desc="Display histograms from data", group="Visualisation", **kwargs)
        self._updating = False

    def init_ui(self):
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        title = TitleWidget(self)
        vbox.addWidget(title)

        self.options = OptionBox("Options")
        self.options.add("Data", DataOption(self.ivm, multi=True), key="data")
        self.options.add("Within ROI", DataOption(self.ivm, data=False, rois=True, none_option=True), key="roi")
        self.options.add("All volumes", BoolOption(default=False), key="allvols")
        self.options.add("Y-axis scale", ChoiceOption(["Count", "Probability"]), key="yscale")
        self.options.add("Number of bins", NumericOption(minval=5, maxval=500, default=100, intonly=True), key="bins")
        self.options.add("Min value", NumericOption(minval=0, maxval=100, default=0), key="min")
        self.options.add("Max value", NumericOption(minval=0, maxval=500, default=100), key="max")
        self.options.option("yscale").sig_changed.connect(self._yscale_changed)
        self.options.option("min").sig_changed.connect(self._min_changed)
        self.options.option("min").sig_changed.connect(self._max_changed)
        vbox.addWidget(self.options)

        self.plot = Plot(qpo=None, parent=self, title="Data histogram", display_mode=False)
        self.plot.set_xlabel("Data value")
        self.plot.set_ylabel("Count")
        vbox.addWidget(self.plot)

        vbox.addStretch(1)

    def activate(self):
        self.ivm.sig_all_data.connect(self._data_changed)
        self.options.option("data").sig_changed.connect(self._data_changed)
        self.options.sig_changed.connect(self._update)
        self._data_changed()

    def deactivate(self):
        self.ivm.sig_all_data.disconnect(self._data_changed)
        self.options.option("data").sig_changed.disconnect(self._data_changed)
        self.options.sig_changed.disconnect(self._update)

    def processes(self):
        opts = self.options.values()
        if not opts.pop("allvols", False):
            opts["vol"] = self.ivl.focus()[3]

        return {
            "Histogram" : opts
        }

    def _yscale_changed(self):
        self.plot.set_ylabel(self.options.option("yscale").value)

    def _min_changed(self):
        minval = self.options.option("min").value
        self.options.option("max").setLimits(minval=minval)

    def _max_changed(self):
        maxval = self.options.option("max").value
        self.options.option("min").setLimits(maxval=maxval)

    def _data_changed(self):
        if self._updating: return
        self._updating = True
        try:
            data_names = self.options.option("data").value
            vol = None
            if self.options.option("allvols").value:
                vol = self.ivl.focus()[3]
            dmin, dmax, multivol = None, None, False
            for data_name in data_names:
                qpdata = self.ivm.data[data_name]
                multivol = multivol or qpdata.nvols > 1
                _dmin, _dmax = qpdata.range(vol=vol)
                if dmin is None or dmin > _dmin:
                    dmin = _dmin
                if dmax is None or dmax > dmax:
                    dmax = _dmax
            
            if dmin is not None and dmax is not None:
                self.options.option("min").value = dmin
                self.options.option("min").setLimits(dmin, dmax)
                self.options.option("max").value = dmax
                self.options.option("max").setLimits(dmin, dmax)
                
            self.options.set_visible("allvols", multivol)
            self._update()
        finally:
            self._updating = False

    def _update(self):
        opts = self.processes()["Histogram"]
        if opts["data"]:
            HistogramProcess(self.ivm).run(opts)
            self.plot.clear()
            histogram = self.ivm.extras.get("histogram", None)
            if histogram is not None:
                for idx, name in enumerate(histogram.col_headers[3:]):
                    xvalues = [row[2] for row in histogram.arr]
                    yvalues = [row[idx+3] for row in histogram.arr]
                    self.plot.add_line(yvalues, name=name, xvalues=xvalues)
Ejemplo n.º 2
0
class PcaWidget(QpWidget):
    """
    PCA widget
    """
    def __init__(self, **kwargs):
        super(PcaWidget, self).__init__(name="PCA",
                                        icon="pca",
                                        desc="PCA reduction",
                                        group="Processing",
                                        **kwargs)

    def init_ui(self):
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        title = TitleWidget(
            self,
            title="PCA reduction",
            subtitle="Principal Component Analysis for 4D data")
        vbox.addWidget(title)

        self._options = OptionBox("Options")
        self._options.add("Data",
                          DataOption(self.ivm, include_3d=False),
                          key="data")
        self._options.add("ROI",
                          DataOption(self.ivm, data=False, rois=True),
                          key="roi")
        self._options.add("Number of components",
                          NumericOption(minval=1, intonly=True, default=4),
                          key="n-components")
        self._options.add("Output name",
                          OutputNameOption(
                              src_data=self._options.option("data"),
                              suffix="_pca"),
                          key="output-name")
        self._options.option("data").sig_changed.connect(self._data_changed)
        vbox.addWidget(self._options)

        self._run = RunWidget(self)
        self._run.sig_postrun.connect(self._postrun)
        vbox.addWidget(self._run)

        self.plot = Plot(qpo=None, parent=self, title="PCA modes")

        self.variance_model = QtGui.QStandardItemModel()
        variance_table = QtGui.QTableView()
        variance_table.verticalHeader().hide()
        variance_table.setModel(self.variance_model)

        tabs = QtGui.QTabWidget()
        tabs.addTab(self.plot, "PCA modes")
        tabs.addTab(variance_table, "Explained variance")
        tabs.setCurrentWidget(self.plot)
        vbox.addWidget(tabs)

        vbox.addStretch(1)
        self._data_changed()

    def processes(self):
        return {"PCA": self._options.values()}

    def _data_changed(self):
        self._run.setEnabled(
            self._options.option("data").value in self.ivm.data)

    def _postrun(self):
        self._update_plot()
        self._update_table()

    def _update_plot(self):
        self.plot.clear()
        extra = self.ivm.extras.get(
            self._options.option("output-name").value + "_modes", None)
        if extra is not None:
            arr = np.array(extra.arr)
            for idx in range(arr.shape[1] - 1):
                self.plot.add_line(arr[:, idx], name="Mode %i" % idx)
            self.plot.add_line(arr[:, -1],
                               name="Mean",
                               line_col=(255, 0, 0),
                               line_width=3.0)

    def _update_table(self):
        self.variance_model.clear()
        extra = self.ivm.extras.get(
            self._options.option("output-name").value + "_variance", None)
        if extra is not None:
            self.debug(str(extra))
            for idx, header in enumerate(extra.col_headers):
                self.variance_model.setHorizontalHeaderItem(
                    idx, QtGui.QStandardItem(header))

            for idx, variance in enumerate(extra.arr):
                self.variance_model.setItem(
                    idx, 0, QtGui.QStandardItem(str(variance[0])))
                self.variance_model.setItem(
                    idx, 1, QtGui.QStandardItem(sf(variance[1])))
                self.variance_model.setItem(
                    idx, 2, QtGui.QStandardItem(sf(variance[2])))
Ejemplo n.º 3
0
class AifWidget(QpWidget):
    """
    Widget which allows the user to define an arterial input function from signal data
    """
    def __init__(self, **kwargs):
        super(AifWidget, self).__init__(name="Arterial Input Function", icon="aif",
                                        desc="Tool for defining an AIF from measured data", group="Utilities", **kwargs)
        self._aif = []
        self._aif_points = []

    def init_ui(self):
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        title = TitleWidget(self)
        vbox.addWidget(title)

        self._options = OptionBox("Options")
        self._options.add("Signal data", DataOption(self.ivm), key="data")
        self._clear_btn = QtGui.QPushButton("Clear points")
        self._options.add("Method", ChoiceOption(["Pick points", "Use existing ROI"], ["points", "roi"]), self._clear_btn, key="method")
        self._options.add("ROI", DataOption(self.ivm, data=False, rois=True), key="roi")
        self._view_btn = QtGui.QPushButton("View")
        self._view_btn.setEnabled(False)
        self._save_btn = QtGui.QPushButton("Save")
        self._save_btn.setEnabled(False)
        self._options.add("AIF name", TextOption("aif"), self._view_btn, self._save_btn, key="output-name")
        self._options.option("method").sig_changed.connect(self._method_changed)
        self._options.option("data").sig_changed.connect(self._recalc_aif)
        self._options.option("roi").sig_changed.connect(self._recalc_aif)
        self._clear_btn.clicked.connect(self._clear_btn_clicked)
        self._save_btn.clicked.connect(self._save_btn_clicked)
        self._view_btn.clicked.connect(self._view_btn_clicked)
        vbox.addWidget(self._options)

        self._plot = Plot(qpo=None, parent=self, title="AIF", display_mode=False)
        self._plot.set_xlabel("Volume")
        self._plot.set_ylabel("Signal")
        vbox.addWidget(self._plot)

        vbox.addStretch(1)

    def activate(self):
        self.ivl.sig_selection_changed.connect(self._selection_changed)
        self._method_changed()

    def deactivate(self):
        self.ivl.sig_selection_changed.disconnect(self._selection_changed)
        self.ivl.set_picker(PickMode.SINGLE)

    @property
    def qpdata(self):
        return self.ivm.data.get(self._options.option("data").value, None)

    @property
    def roi(self):
        return self.ivm.data.get(self._options.option("roi").value, None)

    @property
    def method(self):
        return self._options.option("method").value

    def _method_changed(self):
        self._options.set_visible("roi", self.method == "roi")
        self._clear_btn.setVisible(self.method == "points")
        if self.method == "roi":
            self.ivl.set_picker(PickMode.SINGLE)
        else:
            self.ivl.set_picker(PickMode.MULTIPLE)
            self._selection_changed()
        self._recalc_aif()

    def _clear_btn_clicked(self):
        if self.method == "points":
            self.ivl.set_picker(PickMode.MULTIPLE)
            self._selection_changed() # FIXME should be signalled by picker

    def _save_btn_clicked(self):
        name = self._options.option("output-name").value
        extra = NumberListExtra(name, self._aif)
        self.ivm.add_extra(name, extra)
        self._save_btn.setEnabled(False)

    def _view_btn_clicked(self):
        aiftxt = ", ".join([str(v) for v in self._aif])
        TextViewerDialog(self, title="AIF data", text=aiftxt).exec_()

    def _selection_changed(self):
        if self.method == "roi":
            return
        
        self._aif_points = []
        for _col, points in self.ivl.picker.selection().items():
            self._aif_points += list(points)
        self._recalc_aif()

    def _recalc_aif(self):
        self._aif = []
        self._save_btn.setEnabled(True)
        self._view_btn.setEnabled(True)
        if self.qpdata is not None:
            if self.method == "roi":
                self._calc_aif_roi()
            else:
                self._calc_aif_points()
        self._update_plot()

    def _calc_aif_roi(self):
        if self.roi is None:
            return

        points = self.qpdata.raw()[self.roi.raw() > 0]
        if len(points) > 0:
            aif = None
            for sig in points:
                if aif is None:
                    aif = np.zeros([len(sig)], dtype=np.float32)
                aif += sig
            self._aif = aif / len(points)

    def _calc_aif_points(self):
        aif = None
        num_points = 0
        for point in self._aif_points:
            sig = self.qpdata.timeseries(point, grid=self.ivl.grid)
            self.debug("AIF signal: %s", sig)
            if aif is None:
                aif = np.zeros([len(sig)], dtype=np.float32)
            aif += sig
            num_points += 1

        if num_points > 0:
            self._aif = aif / num_points

    def _update_plot(self):
        self._plot.clear()
        self._plot.add_line(self._aif, name="AIF")
Ejemplo n.º 4
0
class VoxelAnalysis(QpWidget):
    """
    View original data and generated signal curves side by side
    """

    def __init__(self, **kwargs):
        super(VoxelAnalysis, self).__init__(name="Voxel analysis", desc="Display data at a voxel", 
                                            icon="curve_view", group="DEFAULT", **kwargs)
        self.data_enabled = {}
        self.updating = False

    def init_ui(self):
        main_vbox = QtGui.QVBoxLayout()
        self.setLayout(main_vbox)

        title = TitleWidget(self, title="Voxel analysis", help="modelfit", batch_btn=False)
        main_vbox.addWidget(title)

        self.plot = Plot(twoaxis=True)
        main_vbox.addWidget(self.plot)

        hbox = QtGui.QHBoxLayout()

        # Table showing RMS deviation
        rms_box = QtGui.QGroupBox()
        rms_box.setTitle('Timeseries data')
        vbox = QtGui.QVBoxLayout()
        self.rms_table = QtGui.QStandardItemModel()
        self.rms_table.itemChanged.connect(self._data_table_changed)
        tview = QtGui.QTableView()
        tview.resizeColumnsToContents()
        tview.setModel(self.rms_table)
        vbox.addWidget(tview)
        rms_box.setLayout(vbox)
        hbox.addWidget(rms_box)

        # Table showing value of model parameters
        params_box = QtGui.QGroupBox()
        params_box.setTitle('Non-timeseries data')
        vbox2 = QtGui.QVBoxLayout()
        self.values_table = QtGui.QStandardItemModel()
        tview = QtGui.QTableView()
        tview.resizeColumnsToContents()
        tview.setModel(self.values_table)
        vbox2.addWidget(tview)
        params_box.setLayout(vbox2)
        hbox.addWidget(params_box)

        main_vbox.addLayout(hbox)
    
    def activate(self):
        self.ivm.sig_all_data.connect(self._update)
        self.ivl.sig_focus_changed.connect(self._update)
        self._update()

    def deactivate(self):
        self.ivm.sig_all_data.disconnect(self._update)
        self.ivl.sig_focus_changed.disconnect(self._update)

    def _update(self, pos=None):
        self._update_table()
        self._update_rms_table()
        self._plot()

    def _update_table(self):
        """
        Set the data parameter values in the table based on the current point clicked
        """
        self.values_table.clear()
        self.values_table.setHorizontalHeaderItem(0, QtGui.QStandardItem("Value"))
        data_vals = self.ivm.values(self.ivl.focus(), self.ivl.grid)
        for ii, ovl in enumerate(sorted(data_vals.keys())):
            if self.ivm.data[ovl].ndim == 3:
                self.values_table.setVerticalHeaderItem(ii, QtGui.QStandardItem(ovl))
                self.values_table.setItem(ii, 0, QtGui.QStandardItem(sf(data_vals[ovl])))

    def _update_rms_table(self):
        try:
            self.updating = True # Hack to prevent plot being refreshed during table update
            self.rms_table.clear()
            self.rms_table.setHorizontalHeaderItem(0, QtGui.QStandardItem("Name"))
            self.rms_table.setHorizontalHeaderItem(1, QtGui.QStandardItem("RMS (Position)"))
            idx = 0
            pos = self.ivl.focus()
            sigs = self.ivm.timeseries(pos, self.ivl.grid)
            max_length = max([0,] + [len(sig) for sig in sigs.values()])

            if not self.ivm.main:
                return

            main_curve = self.ivm.main.timeseries(pos, grid=self.ivl.grid)
            main_curve.extend([0] * max_length)
            main_curve = main_curve[:max_length]

            for name in sorted(sigs.keys()):
                # Make sure data curve is correct length
                data_curve = sigs[name]
                data_curve.extend([0] * max_length)
                data_curve = data_curve[:max_length]

                data_rms = np.sqrt(np.mean(np.square([v1-v2 for v1, v2 in zip(main_curve, data_curve)])))

                name_item = QtGui.QStandardItem(name)
                name_item.setCheckable(True)
                name_item.setEditable(False)
                if name not in self.data_enabled:
                    self.data_enabled[name] = QtCore.Qt.Checked
                name_item.setCheckState(self.data_enabled[name])
                self.rms_table.setItem(idx, 0, name_item)

                item = QtGui.QStandardItem(sf(data_rms))
                item.setEditable(False)
                self.rms_table.setItem(idx, 1, item)
                idx += 1
        finally:
            self.updating = False

    def _data_table_changed(self, item):
        if not self.updating:
            # A checkbox has been toggled
            self.data_enabled[item.text()] = item.checkState()
            self._plot()

    def _plot(self):
        """
        Regenerate the plot
        """
        self.plot.clear() 

        # Get all timeseries signals
        pos = self.ivl.focus()
        sigs = self.ivm.timeseries(pos, self.ivl.grid)
        if not sigs:
            return
            
        # Get x scale
        self.plot.set_xlabel("Volume")
        
        # Set y labels
        #axis_labels = {0 : "Signal", 1 : "Signal enhancement", 2 : "Signal"}
        #self.plot.setLabel('left', axis_labels[self.plot_opts.mode])
        #if self.plot_opts.mode == 2:
        #    self.plot.showAxis('right')
        #    self.plot.getAxis('right').setLabel('Residual')
        #else:
        #    self.plot.hideAxis('right')

        #if not self.ivm.main:
        #    return
        #main_curve = self.ivm.main.timeseries(pos, grid=self.ivl.grid)

        idx, _ = 0, len(sigs)
        for ovl, sig_values in sigs.items():
            if self.data_enabled[ovl] == QtCore.Qt.Checked:
                col = get_kelly_col(idx)

                #if self.plot_opts.mode == 2 and ovl != self.ivm.main.name:
                #    # Show residuals on the right hand axis
                #    resid_values = [v1 - v2 for v1, v2 in zip(sig_values, main_curve)]
                #    self.plot_rightaxis.addItem(pg.PlotCurveItem(resid_values, pen=pg.mkPen(pen, style=QtCore.Qt.DashLine)))
                    
                self.plot.add_line(sig_values, name=ovl, line_col=col, point_brush=(200, 200, 200), point_col='k', point_size=5.0)
                idx += 1
Ejemplo n.º 5
0
class MultiVoxelAnalysis(QpWidget):
    """
    Plots timeseries for multiple selected points
    """
    
    def __init__(self, **kwargs):
        super(MultiVoxelAnalysis, self).__init__(name="Multi-Voxel", icon="voxel", desc="Compare signal curves at different voxels", group="Visualisation", position=2, **kwargs)

        self.activated = False
        self.colors = {'grey':(200, 200, 200), 'red':(255, 0, 0), 'green':(0, 255, 0), 'blue':(0, 0, 255),
                       'orange':(255, 140, 0), 'cyan':(0, 255, 255), 'brown':(139, 69, 19)}
        self.col = self.colors["red"]
        self.plots = {}
        self.mean_plots = {}

    def init_ui(self):
        self.setStatusTip("Click points on the 4D volume to see data curve")

        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        title = TitleWidget(self, "Multi-Voxel Visualisation", help="curve_compare", batch_btn=False)
        vbox.addWidget(title)

        self.plot = Plot(clear_btn=True)
        self.plot.clear_btn.clicked.connect(self._clear)
        self.plot.options.sig_options_changed.connect(self._options_changed)
        vbox.addWidget(self.plot)

        self.options = OptionBox("Options")
        self.options.add("Data set", DataOption(self.ivm, include_3d=False), key="data")
        col_names = [text for text in self.colors]
        cols = [col for text, col in self.colors.items()]
        self.options.add("Plot colour", ChoiceOption(col_names, cols, default="red"), key="col")
        self.options.add("Show individual curves", BoolOption(default=True), key="indiv")
        self.options.add("Show mean curve", BoolOption(), key="mean")

        self.options.option("data").sig_changed.connect(self._data_changed)
        self.options.option("indiv").sig_changed.connect(self._indiv_changed)
        self.options.option("mean").sig_changed.connect(self._mean_changed)
        self.options.option("col").sig_changed.connect(self._col_changed)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.options)
        hbox.addStretch()
        vbox.addLayout(hbox)

        vbox.addStretch(1)    
        self._options_changed()

    def activate(self):
        self.ivl.sig_selection_changed.connect(self._selection_changed)
        self.ivl.set_picker(PickMode.MULTIPLE)
        self.activated = True

    def deactivate(self):
        self.ivl.sig_selection_changed.disconnect(self._selection_changed)
        self.ivl.set_picker(PickMode.SINGLE)

    def _options_changed(self):
        if self.plot.options.sig_enh:
            self.plot.set_ylabel("Signal enhancement")
        else:
            self.plot.set_ylabel("Signal")

    def _data_changed(self):
        self._clear()
        data_name = self.options.option("data").value
        if data_name in self.ivm.data:
            # FIXME not clear whether to use metadata
            #data = self.ivm.data[data_name]
            #xlabel = data.metadata.get("vol_scale", "Volume")
            #xunits = data.metadata.get("vol_units", "")
            #if xunits:
            #    xlabel = "%s (%s)" % (xlabel, xunits)
            self.plot.set_xlabel("Volume")

    def _indiv_changed(self):
        show_indiv = self.options.option("indiv").value
        for plt in self.plots.values():
            if show_indiv:
                plt.show()
            else:
                plt.hide()

    def _mean_changed(self):
        show_mean = self.options.option("mean").value
        if show_mean:
            self._update_means()
            for plt in self.mean_plots.values():
                plt.show()
        else:
            for plt in self.mean_plots.values():
                plt.hide()

    def _clear(self):
        """
        Clear point data
        """
        self.plot.clear()
        self.plots, self.mean_plots = {}, {}
        # Reset the list of picked points
        self.ivl.set_picker(PickMode.MULTIPLE)
        self.ivl.picker.col = self.col

    def _add_point(self, point, col):
        """
        Add a selected point of the specified colour
        """
        data_name = self.options.option("data").value
        if data_name in self.ivm.data:
            data = self.ivm.data[data_name]
            sig = data.timeseries(point, grid=self.ivl.grid)
            if point in self.plots:
                self.plot.remove(self.plots[point])

            self.plots[point] = self.plot.add_line(sig, line_col=col)
            if not self.options.option("indiv").value:
                self.plots[point].hide()
            self._update_means()

    def _update_means(self):
        for col in self.colors.values():
            if col in self.mean_plots:
                self.plot.remove(self.mean_plots[col])
                del self.mean_plots[col]
            all_plts = [plt for plt in self.plots.values() if plt.line_col == col]
            if all_plts:
                mean_values = np.stack([plt.yvalues for plt in all_plts], axis=1)
                mean_values = np.squeeze(np.mean(mean_values, axis=1))
                self.mean_plots[col] = self.plot.add_line(mean_values, line_col=col, line_style=QtCore.Qt.DashLine, point_brush=col, point_col='k', point_size=10)
                if not self.options.option("mean").value:
                    self.mean_plots[col].hide()

    def _selection_changed(self, picker):
        """
        Point selection changed
        """
        # Add plots for points in the selection which we haven't plotted (or which have changed colour)
        allpoints = []
        for col, points in picker.selection().items():
            points = [tuple([int(p+0.5) for p in pos]) for pos in points]
            allpoints += points
            for point in points:
                if point not in self.plots or self.plots[point].line_col != col:
                    self._add_point(point, col)

        # Remove plots for points no longer in the selection
        for point in list(self.plots.keys()):
            if point not in allpoints:
                self.plots[point].hide()
                del self.plots[point]

    def _col_changed(self):
        self.col = self.options.option("col").value
        self.ivl.picker.col = self.col
Ejemplo n.º 6
0
class RadialProfileWidget(QpWidget):
    """
    Widget which displays radial profile of data
    """
    def __init__(self, **kwargs):
        super(RadialProfileWidget,
              self).__init__(name="Radial profile",
                             icon="rp",
                             desc="Display radial profile of data",
                             group="Visualisation",
                             **kwargs)
        self._updating = False

    def init_ui(self):
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        title = TitleWidget(self)
        vbox.addWidget(title)

        self.options = OptionBox("Options")
        self.options.add("Data", DataOption(self.ivm, multi=True), key="data")
        self.options.add("Within ROI",
                         DataOption(self.ivm,
                                    data=False,
                                    rois=True,
                                    none_option=True),
                         key="roi")
        self.options.add("All volumes",
                         BoolOption(default=False),
                         key="allvols")
        self.options.add("Number of bins",
                         NumericOption(minval=5,
                                       maxval=250,
                                       default=50,
                                       intonly=True),
                         key="bins")
        vbox.addWidget(self.options)

        self.plot = Plot(qpo=None,
                         parent=self,
                         title="Radial profile",
                         display_mode=False)
        self.plot.set_xlabel("Distance (mm)")
        self.plot.set_ylabel("Mean data value")
        vbox.addWidget(self.plot)

        vbox.addStretch(1)

    def activate(self):
        self.ivm.sig_all_data.connect(self._data_changed)
        self.ivl.sig_focus_changed.connect(self._update)
        self.options.option("data").sig_changed.connect(self._data_changed)
        self.options.sig_changed.connect(self._update)
        self._data_changed()

    def deactivate(self):
        self.ivm.sig_all_data.disconnect(self._data_changed)
        self.ivl.sig_focus_changed.disconnect(self._update)
        self.options.option("data").sig_changed.disconnect(self._data_changed)
        self.options.sig_changed.disconnect(self._update)

    def processes(self):
        opts = self.options.values()
        opts["centre"] = self.ivl.focus()
        if opts.pop("allvols", False):
            opts["centre"] = opts["centre"][:3]

        return {"RadialProfile": opts}

    def _data_changed(self):
        if self._updating: return
        self._updating = True
        try:
            data_names = self.options.option("data").value
            multivol = False
            for data_name in data_names:
                multivol = multivol or self.ivm.data[data_name].nvols > 1
            self.options.set_visible("allvols", multivol)
            self._update()
        finally:
            self._updating = False

    def _update(self):
        process = RadialProfileProcess(self.ivm)
        process.execute(self.processes()["RadialProfile"])
        self._update_plot()

    def _update_plot(self):
        self.plot.clear()
        rp = self.ivm.extras.get("radial-profile", None)
        if rp is not None:
            xvalues = rp.df.index
            for idx, name in enumerate(rp.df.columns):
                yvalues = rp.df.values[:, idx]
                self.plot.add_line(yvalues, name=name, xvalues=xvalues)