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)
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
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")
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)