Example #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)
Example #2
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
Example #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")
Example #4
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)