Beispiel #1
0
    def _new_roi(self):
        dialog = QtGui.QDialog(self)
        dialog.setWindowTitle("New ROI")
        vbox = QtGui.QVBoxLayout()
        dialog.setLayout(vbox)

        optbox = OptionBox()
        optbox.add("ROI name", TextOption(), key="name")
        optbox.add("Data space from", DataOption(self.ivm), key="grid")
        vbox.addWidget(optbox)

        buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok
                                         | QtGui.QDialogButtonBox.Cancel)
        buttons.accepted.connect(dialog.accept)
        buttons.rejected.connect(dialog.reject)
        vbox.addWidget(buttons)

        ok = dialog.exec_()
        if ok:
            roiname = optbox.option("name").value
            grid = self.ivm.data[optbox.option("grid").value].grid
            roidata = np.zeros(grid.shape, dtype=np.int)
            self.ivm.add(NumpyData(roidata, grid=grid, roi=True, name=roiname),
                         make_current=True)

            # Throw away old history. FIXME is this right, should we keep existing data and history?
            # Also should we cache old history in case we go back to this ROI?
            self._history = []
            self._undo_btn.setEnabled(False)
            self.options.option("roi").value = roiname
Beispiel #2
0
class ApplyTransform(QpWidget):
    """
    Widget for applying previously calculated transformations
    """
    def __init__(self, **kwargs):
        super(ApplyTransform, self).__init__(name="Apply Transform", icon="reg", 
                                             desc="Apply previously calculated transformations", 
                                             group="Registration", **kwargs)
        self.reg_methods = []
        for method in get_plugins("reg-methods"):
            try:
                self.reg_methods.append(method(self.ivm))
            except:
                traceback.print_exc()
                self.warn("Failed to create registration method: %s", method)

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

        title = TitleWidget(self, help="reg")
        layout.addWidget(title)

        if not self.reg_methods:
            layout.addWidget(QtGui.QLabel("No registration methods found"))
            layout.addStretch(1)
            return

        self.options = OptionBox("General Options")
        self.options.add("Transform", TransformOption(self.ivm), key="transform")
        self.options.add("Apply to data", DataOption(self.ivm), key="data")
        self.options.add("Interpolation", ChoiceOption(["Nearest neighbour", "Linear", "Spline"], [0, 1, 3], default=1), key="interp-order")
        self.options.add("Output name", OutputNameOption(src_data=self.options.option("data"), suffix="_reg"), key="output-name")
        self.options.option("transform").sig_changed.connect(self._transform_changed)
        layout.addWidget(self.options)

        self.details = TransformDetails()
        layout.addWidget(self.details)

        layout.addWidget(RunButton(self))
        layout.addStretch(1)
        self._transform_changed()

    def processes(self):
        return {
            "ApplyTransform" : self.options.values(),
        }

    def activate(self):
        self._transform_changed()

    def _transform_changed(self):
        trans_name = self.options.option("transform").value
        transform = self.ivm.data.get(trans_name, None)
        if transform is None or "QpReg" not in transform.metadata:
            transform = self.ivm.extras.get(trans_name, None)

        if transform is not None and "QpReg" in transform.metadata:
            self.details.transform = transform
Beispiel #3
0
class SimpleMathsWidget(QpWidget):
    """
    Widget which lets you run arbitrary Python/Numpy code on the data in the IVM
    """
    def __init__(self, **kwargs):
        super(SimpleMathsWidget,
              self).__init__(name="Simple Maths",
                             icon="maths",
                             desc="Simple mathematical operations on data",
                             group="Processing",
                             **kwargs)

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

        title = TitleWidget(self, help="simple_maths")
        layout.addWidget(title)

        info = QtGui.QLabel(MATHS_INFO)
        info.setWordWrap(True)
        layout.addWidget(info)

        self.optbox = OptionBox()
        self.optbox.add("Data space from", DataOption(self.ivm), key="grid")
        self.optbox.add("Command", TextOption(), key="cmd")
        self.optbox.add("Output name",
                        OutputNameOption(src_data=self.optbox.option("grid")),
                        key="output-name")
        self.optbox.add("Output is an ROI", BoolOption(), key="output-is-roi")
        layout.addWidget(self.optbox)

        hbox = QtGui.QHBoxLayout()
        self.go_btn = RunButton(self)
        hbox.addWidget(self.go_btn)
        hbox.addStretch(1)
        layout.addLayout(hbox)

        layout.addStretch(1)

    def processes(self):
        return {
            "Exec": {
                "grid":
                self.optbox.option("grid").value,
                "output-is-roi":
                self.optbox.option("output-is-roi").value,
                self.optbox.option("output-name").value:
                self.optbox.option("cmd").value,
            }
        }
Beispiel #4
0
class ModelOptions(OptionsWidget):
    def __init__(self, ivm, parent, model_type, abbrev, model_classes):
        OptionsWidget.__init__(self, ivm, parent)
        self._views = {}
        self.model = None
        self.view = None
        self._option_name = "%s-model" % abbrev
        for name, cls in model_classes.items():
            self._views[name] = get_view_class(cls)(ivm)

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

        self.options = OptionBox()
        self.options.add(
            "%s model" % model_type,
            ChoiceOption([v.model.display_name for v in self._views.values()],
                         self._views.keys()),
            key=self._option_name)
        self.options.option(self._option_name).sig_changed.connect(
            self._model_changed)
        main_vbox.addWidget(self.options)

        self._create_guis(main_vbox)
        main_vbox.addStretch(1)
        self._model_changed()

    def _create_guis(self, main_vbox):
        # Create the GUIs for models - only one visible at a time!
        for view in self._views.values():
            if view.gui is not None:
                view.gui.setVisible(False)
                if isinstance(view.gui, QtGui.QWidget):
                    main_vbox.addWidget(view.gui)
                else:
                    main_vbox.addLayout(view.gui)
                view.model.options.sig_changed.connect(
                    self._model_option_changed)

    def _model_changed(self):
        chosen_name = self.options.option(self._option_name).value
        self.view = self._views[chosen_name]
        self.model = self.view.model
        for name, view in self._views.items():
            view.gui.setVisible(chosen_name == name)
        self.sig_changed.emit()

    def _model_option_changed(self, _key, _value):
        self.sig_changed.emit()
Beispiel #5
0
class MotionOptions(OptionsWidget):
    def __init__(self, ivm, parent):
        OptionsWidget.__init__(self, ivm, parent)

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

        self.options = OptionBox()
        self.options.add("Simulate motion",
                         BoolOption(default=False),
                         key="motion")
        self.options.option("motion").sig_changed.connect(
            self._update_widget_visibility)
        self.options.add("Random translation standard deviation (mm)",
                         NumericOption(minval=0,
                                       maxval=5,
                                       default=1,
                                       decimals=2),
                         key="std")
        self.options.add(
            "Random rotation standard deviation (\N{DEGREE SIGN})",
            NumericOption(minval=0, maxval=10, default=1, decimals=2),
            key="std_rot")
        self.options.add("Padding (mm)",
                         NumericOption(minval=0,
                                       maxval=10,
                                       default=5,
                                       decimals=1),
                         key="padding",
                         checked=True)
        self.options.add(
            "Interpolation",
            ChoiceOption(["Nearest neighbour", "Linear", "Quadratic", "Cubic"],
                         return_values=range(4),
                         default=3),
            key="order")
        main_vbox.addWidget(self.options)

        main_vbox.addStretch(1)
        self._update_widget_visibility()

    def _update_widget_visibility(self):
        enabled = self.options.option("motion").value
        for option in ["std", "std_rot", "padding", "order"]:
            self.options.set_visible(option, enabled)
Beispiel #6
0
class AifWidget(QtGui.QWidget):
    """
    Widget allowing choice of AIF
    """
    def __init__(self, ivm):
        QtGui.QWidget.__init__(self)
        self.ivm = ivm

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

        self.optbox = OptionBox()
        self.optbox.add(
            "AIF source",
            ChoiceOption(["Global sequence of values", "Voxelwise image"],
                         ["global", "voxelwise"]),
            key="aif_source")
        self.optbox.option("aif_source").sig_changed.connect(
            self._aif_source_changed)
        self.optbox.add("AIF", NumberListOption(), key="aif")
        self.optbox.add("AIF image", DataOption(self.ivm), key="suppdata")
        self.optbox.add("AIF type",
                        ChoiceOption(["DSC signal", "Concentration"],
                                     [False, True]),
                        key="aifconc")
        vbox.addWidget(self.optbox)
        vbox.addStretch()
        self._aif_source_changed()

    def options(self):
        """ :return: Dictionary of options selected for the AIF"""
        opts = self.optbox.values()
        opts.pop("aif_source")
        return opts

    def _aif_source_changed(self):
        global_aif = self.optbox.option("aif_source").value == "global"
        self.optbox.set_visible("aif", global_aif)
        self.optbox.set_visible("suppdata", not global_aif)
Beispiel #7
0
class RoiBuilderWidget(QpWidget):
    """
    Widget for building ROIs
    """
    ADD = 1
    ERASE = 2
    MASK = 3

    def __init__(self, **kwargs):
        super(RoiBuilderWidget, self).__init__(name="ROI Builder",
                                               icon="roi_builder",
                                               desc=DESC,
                                               group="ROIs",
                                               **kwargs)
        self._history = collections.deque(maxlen=10)
        self._tool = None
        self.grid = None
        self.roi = None
        self.roiname = None

    def init_ui(self):
        layout = QtGui.QVBoxLayout()

        title = TitleWidget(self, help="roibuilder", batch_btn=False)
        layout.addWidget(title)

        self.options = OptionBox("Options")
        btn = QtGui.QPushButton("New")
        btn.clicked.connect(self._new_roi)
        self.options.add("ROI",
                         DataOption(self.ivm, rois=True, data=False),
                         btn,
                         key="roi")
        self.options.add("Current label",
                         NumericOption(minval=1, slider=False, intonly=True),
                         key="label")
        self.options.add("Label description", TextOption(), key="label_text")
        self.options.option("roi").sig_changed.connect(self._roi_changed)
        self.options.option("label").sig_changed.connect(self._label_changed)
        self.options.option("label_text").sig_changed.connect(
            self._label_text_changed)
        layout.addWidget(self.options)

        # Add toolbox buttons in a grid
        hbox = QtGui.QHBoxLayout()
        self._toolbox = QtGui.QGroupBox()
        self._toolbox.setTitle("Toolbox")
        self.tools_grid = QtGui.QGridLayout()
        self._toolbox.setLayout(self.tools_grid)

        x, y, cols = 0, 0, 4
        for tool in TOOLS:
            self._add_tool(tool, y, x)
            x += 1
            if x == cols:
                y += 1
                x = 0

        self._undo_btn = QtGui.QPushButton()
        self._undo_btn.clicked.connect(self.undo)
        self._undo_btn.setEnabled(False)
        undo_icon = QtGui.QIcon(get_icon("undo"))
        self._undo_btn.setIcon(undo_icon)
        self._undo_btn.setToolTip("Undo last action")
        self._undo_btn.setFixedSize(32, 32)
        self.tools_grid.addWidget(self._undo_btn, y, x)

        hbox.addWidget(self._toolbox)
        self._toolbox.setEnabled(False)
        hbox.addStretch(1)
        layout.addLayout(hbox)

        # Tool options box - initially invisible
        hbox = QtGui.QHBoxLayout()
        self._tool_options = QtGui.QGroupBox()
        self._tool_options.setVisible(False)

        hbox.addWidget(self._tool_options)
        hbox.addStretch(1)
        layout.addLayout(hbox)

        layout.addStretch(1)
        self.setLayout(layout)

    def activate(self):
        self._roi_changed()
        self.ivl.set_picker(PickMode.SINGLE)
        if self._tool is not None:
            self._tool.selected()

    def deactivate(self):
        self.ivl.set_picker(PickMode.SINGLE)
        if self._tool is not None:
            self._tool.deselected()

    def modify(self, vol=None, slice2d=None, points=None, mode=None):
        """
        Make a change to the ROI we are building

        :param roi_new: Numpy array containing the updated ROI data on the current base grid
        :param vol: 3D 3D selection specified as binary Numpy array same shape as ROI
        :param slice2d: 2D slice selection specified as tuple of (2D binary Numpy array, axis index, position)
        :param points: Selection specified as list of 3D co-ordinates
        :param mode: ``ADD`` to add selection to the ROI (using current label), ``ERASE`` to erase
                     selection from the ROI (set to 0), ``MASK`` to preserve ROI labels in
                     selection but zero everything outside selection
        """
        label = self.options.option("label").value
        self.debug("label=%i", label)

        # For undo functionality: selection is an object specifying which
        # points or ROI region were selected, data_orig is a corresponding
        # object which contains the data before the operation occurred.
        selection, data_orig = None, None

        if points is not None:
            selection = points
            data_orig = [
                self.roidata[point[0], point[1], point[2]] for point in points
            ]
            for point in points:
                if mode == self.ADD:
                    self.roidata[point[0], point[1], point[2]] = label
                elif mode == self.ERASE:
                    self.roidata[point[0], point[1], point[2]] = 0
                else:
                    raise ValueError("Invalid mode: %i" % mode)
        else:
            # change_subset is a Numpy selection of the points to be
            # affected, data_new is a corresponding binary array identifying
            # the selected points in this subset to modify.
            if vol is not None:
                # Selection left as None to indicate whole volume
                selected_points = vol
                change_subset = self.roidata
            elif slice2d is not None:
                selected_points, axis, pos = slice2d
                slices = [slice(None)] * 3
                slices[axis] = pos
                change_subset = self.roidata[slices]
                # Selection is the axis index and position
                selection = (axis, pos)
            else:
                raise ValueError(
                    "Neither volume nor slice nor points provided")

            data_orig = np.copy(change_subset)
            if mode == self.ADD:
                self.debug("Adding: %i", np.count_nonzero(selected_points))
                change_subset[selected_points > 0] = label
            elif mode == self.ERASE:
                self.debug("Erasing: %i", np.count_nonzero(selected_points))
                change_subset[selected_points > 0] = 0
            elif mode == self.MASK:
                self.debug("Masking: %i", np.count_nonzero(selected_points))
                change_subset[selected_points == 0] = 0
            else:
                raise ValueError("Invalid mode: %i" % mode)

        # Save the previous state of the data in the history list
        self._history.append((selection, data_orig))
        self._undo_btn.setEnabled(True)

        # Update the ROI - note that the regions may have been affected so make
        # sure they are regenerated
        self._update_regions()
        self.ivl.redraw()
        self.debug("Now have %i nonzero", np.count_nonzero(self.roidata))

    def _update_regions(self):
        """
        ROI regions may have been created or added so regenerate them but put 
        back existing label names
        """
        current_regions = self.ivm.data[self.roiname].metadata.pop(
            "roi_regions", {})
        new_regions = self.ivm.data[self.roiname].regions
        for label, desc in current_regions.items():
            if label in new_regions:
                new_regions[label] = desc

    def undo(self):
        """
        Undo the last change
        """
        self.debug("ROI undo: %i", len(self._history))
        if not self._history:
            return

        selection, data_orig = self._history.pop()

        # For selection, None indicates whole volume, tuple indicates
        # an (axis, pos) slice, otherwise we have a sequence of points
        if selection is None:
            self.roidata[:] = data_orig
        elif isinstance(selection, tuple):
            axis, pos = selection
            slices = [slice(None)] * 3
            slices[axis] = pos
            self.roidata[slices] = data_orig
        else:
            for point, orig_value in zip(selection, data_orig):
                self.roidata[point[0], point[1], point[2]] = orig_value

        self._update_regions()
        self.ivl.redraw()
        self.debug("Now have %i nonzero", np.count_nonzero(self.roidata))
        self._undo_btn.setEnabled(len(self._history) > 0)

    def _label_changed(self):
        self.debug("Label changed")
        roi = self.ivm.data.get(self.options.option("roi").value, None)
        if roi is not None:
            label = self.options.option("label").value
            self.debug(label)
            regions = roi.regions
            if label in regions:
                self.options.option("label_text").value = regions[label]
            else:
                self.options.option("label_text").value = "Region %i" % label

    def _label_text_changed(self):
        self.debug("Label text changed")
        roi = self.ivm.data.get(self.options.option("roi").value, None)
        if roi is not None:
            label = self.options.option("label").value
            label_text = self.options.option("label_text").value
            self.debug(label)
            self.debug(label_text)
            regions = roi.regions
            regions[label] = label_text

    def _roi_changed(self):
        roi = self.ivm.data.get(self.options.option("roi").value, None)
        self._toolbox.setEnabled(roi is not None)
        if roi is not None:
            # FIXME this will only work if ROI is NumpyData. Otherwise we are
            # manipulating a numpy array which may just be a proxy for the file
            # storage.
            regions = roi.regions
            current_label = self.options.option("label").value
            if self.roiname != roi.name or current_label not in regions.keys():
                self.options.option("label").value = min(
                    list(regions.keys()) + [
                        1,
                    ])
            self.roiname = roi.name
            self.grid = roi.grid
            self.roidata = roi.raw()

    def _new_roi(self):
        dialog = QtGui.QDialog(self)
        dialog.setWindowTitle("New ROI")
        vbox = QtGui.QVBoxLayout()
        dialog.setLayout(vbox)

        optbox = OptionBox()
        optbox.add("ROI name", TextOption(), key="name")
        optbox.add("Data space from", DataOption(self.ivm), key="grid")
        vbox.addWidget(optbox)

        buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok
                                         | QtGui.QDialogButtonBox.Cancel)
        buttons.accepted.connect(dialog.accept)
        buttons.rejected.connect(dialog.reject)
        vbox.addWidget(buttons)

        ok = dialog.exec_()
        if ok:
            roiname = optbox.option("name").value
            grid = self.ivm.data[optbox.option("grid").value].grid
            roidata = np.zeros(grid.shape, dtype=np.int)
            self.ivm.add(NumpyData(roidata, grid=grid, roi=True, name=roiname),
                         make_current=True)

            # Throw away old history. FIXME is this right, should we keep existing data and history?
            # Also should we cache old history in case we go back to this ROI?
            self._history = []
            self._undo_btn.setEnabled(False)
            self.options.option("roi").value = roiname

    def _tool_selected(self, tool):
        def _select():
            if self._tool is not None:
                self._tool.btn.setStyleSheet("")
                self._tool.deselected()

            self._tool = tool
            self._tool.btn.setStyleSheet(
                "border: 2px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);"
            )
            # Replace the old tool options with the new one. Need to reparent the
            # existing layout to a temporary widget which will then get deleted
            QtGui.QWidget().setLayout(self._tool_options.layout())
            self._tool_options.setLayout(self._tool.interface())
            self._tool_options.setTitle(tool.name)
            self._tool_options.setVisible(True)
            self._tool.selected()

        return _select

    def _add_tool(self, tool, x, y):
        tool.ivm = self.ivm
        tool.ivl = self.ivl
        tool.builder = self
        btn = QtGui.QPushButton()
        btn.setIcon(QtGui.QIcon(get_icon(tool.name.lower())))
        btn.setToolTip(tool.tooltip)
        btn.setFixedSize(32, 32)
        btn.clicked.connect(self._tool_selected(tool))
        tool.btn = btn
        self.tools_grid.addWidget(btn, x, y)
Beispiel #8
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])))
Beispiel #9
0
class CalibrationOptions(OxaslOptionWidget):
    """
    OXASL processing options related to calibration
    """
    def _init_ui(self):
        self.optbox.add("Calibration method",
                        ChoiceOption(["None", "Voxelwise", "Reference region"],
                                     [None, "voxelwise", "single"]),
                        key="calib_method")
        self.optbox.option("calib_method").sig_changed.connect(
            self._calib_method_changed)
        self.optbox.add("Calibration image",
                        DataOption(self.ivm, explicit=True),
                        key="calib")
        self.optbox.add("Sequence TR (s)",
                        NumericOption(minval=0,
                                      maxval=20,
                                      default=3.2,
                                      step=0.1,
                                      decimals=3),
                        key="tr",
                        checked=True)
        self.optbox.add("Sequence TE (ms)",
                        NumericOption(minval=0,
                                      maxval=100,
                                      default=0,
                                      step=5,
                                      decimals=3),
                        key="te",
                        checked=True)
        self.optbox.add("Calibration gain",
                        NumericOption(minval=0,
                                      maxval=5,
                                      default=1,
                                      step=0.05,
                                      decimals=3),
                        key="calib_gain",
                        checked=True)
        self.optbox.add("Inversion efficiency",
                        NumericOption(minval=0,
                                      maxval=1,
                                      default=0.85,
                                      step=0.05,
                                      decimals=3),
                        key="calib_alpha",
                        checked=True)

        self.voxelwise_opts = OptionBox("Voxelwise calibration")
        self.voxelwise_opts.add("Tissue T1",
                                NumericOption(minval=0,
                                              maxval=10,
                                              default=1.3,
                                              step=0.05,
                                              decimals=3),
                                key="t1t",
                                checked=True)
        self.voxelwise_opts.add("Tissue partition coefficient",
                                NumericOption(minval=0,
                                              maxval=5,
                                              default=0.9,
                                              step=0.05,
                                              decimals=3),
                                key="pct",
                                checked=True)
        self.vbox.addWidget(self.voxelwise_opts)

        self.refregion_opts = OptionBox("Reference region calibration")
        self.refregion_opts.add("Reference type",
                                ChoiceOption(["CSF", "WM", "GM", "Custom"],
                                             ["csf", "wm", "gm", None]),
                                key="tissref")
        self.refregion_opts.option("tissref").sig_changed.connect(
            self._ref_tiss_changed)
        self.refregion_opts.add("Custom reference ROI",
                                DataOption(self.ivm,
                                           rois=True,
                                           data=False,
                                           explicit=True),
                                key="refmask",
                                checked=True)
        # TODO pick specific region of ROI
        self.refregion_opts.add("Reference T1 (s)",
                                NumericOption(minval=0,
                                              maxval=10,
                                              default=4.3,
                                              step=0.1,
                                              decimals=3),
                                key="t1r",
                                checked=True)
        self.refregion_opts.add("Reference T2 (ms)",
                                NumericOption(minval=0,
                                              maxval=2000,
                                              default=750,
                                              step=50,
                                              decimals=3),
                                key="t2r",
                                checked=True)
        self.refregion_opts.add("Reference partition coefficient (ms)",
                                NumericOption(minval=0,
                                              maxval=5,
                                              default=1.15,
                                              step=0.05,
                                              decimals=3),
                                key="pcr",
                                checked=True)
        self.refregion_opts.add("Blood T2 (ms)",
                                NumericOption(minval=0,
                                              maxval=2000,
                                              default=150,
                                              step=50,
                                              decimals=3),
                                key="t2b",
                                checked=True)
        self.refregion_opts.setVisible(False)
        self.vbox.addWidget(self.refregion_opts)

        self._calib_method_changed()

    def _calib_method_changed(self):
        method = self.optbox.option("calib_method").value
        self.voxelwise_opts.setVisible(method == "voxelwise")
        self.refregion_opts.setVisible(method == "single")
        for opt in ("calib", "tr", "te", "calib_gain", "calib_alpha"):
            self.optbox.set_visible(opt, method is not None)

    def _ref_tiss_changed(self):
        tissref = self.refregion_opts.option("tissref").value
        if tissref is not None:
            from oxasl.calib import tissue_defaults
            t1, t2, t2star, pc = tissue_defaults(tissref)
            self.refregion_opts.option("t1r").value = t1
            self.refregion_opts.option("t2r").value = t2
            self.refregion_opts.option("pcr").value = pc
        else:
            # Do nothing - user must choose their own values
            pass

    def options(self):
        """ :return: Options as dictionary """
        opts = self.optbox.values()
        method = self.optbox.option("calib_method").value
        if method == "voxelwise":
            opts.update(self.voxelwise_opts.values())
        elif method == "single":
            opts.update(self.refregion_opts.values())
        return opts

    def set_wp_mode(self, wp_enabled):
        if wp_enabled:
            self.optbox.option("calib_method").value = "voxelwise"

    def set_asldata_metadata(self, md):
        """
        Set the inversion efficiency default to 0.98 for PASL, 0.85 for CASL
        unless the checkbox is ticked in which case the user has modified it
        manually - so leave it alone
        """
        casl = md.get("casl", True)
        alpha = 0.85 if casl else 0.98
        if "calib_alpha" not in self.optbox.values():
            self.optbox.option("calib_alpha").value = alpha
Beispiel #10
0
class FabberDceWidget(QpWidget):
    """
    DCE modelling, using the Fabber process
    """
    def __init__(self, **kwargs):
        QpWidget.__init__(self,
                          name="Bayesian DCE",
                          icon="dce",
                          group="DCE-MRI",
                          desc="DCE model fitting using Bayesian inference",
                          **kwargs)

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

        try:
            self.FabberProcess = get_plugins("processes", "FabberProcess")[0]
        except IndexError:
            self.FabberProcess = None

        if self.FabberProcess is None:
            vbox.addWidget(
                QtGui.QLabel(
                    "Fabber core library not found.\n\n You must install Fabber to use this widget"
                ))
            return

        title = TitleWidget(
            self,
            help="fabber-dsc",
            subtitle="DSC modelling using the Fabber process %s" % __version__)
        vbox.addWidget(title)

        cite = Citation(FAB_CITE_TITLE, FAB_CITE_AUTHOR, FAB_CITE_JOURNAL)
        vbox.addWidget(cite)

        self.input = OptionBox("Input data")
        self.input.add("DCE data",
                       DataOption(self.ivm, include_3d=False, include_4d=True),
                       key="data")
        self.input.add("ROI",
                       DataOption(self.ivm, data=False, rois=True),
                       key="roi",
                       checked=True)
        self.input.add("T1 map",
                       DataOption(self.ivm, include_3d=True, include_4d=False),
                       key="t1",
                       checked=True)
        self.input.option("t1").sig_changed.connect(self._t1_map_changed)
        vbox.addWidget(self.input)

        self.acquisition = OptionBox("Acquisition")
        self.acquisition.add("Contrast agent R1 relaxivity (l/mmol s)",
                             NumericOption(minval=0, maxval=10, default=3.7),
                             key="r1")
        self.acquisition.add("Flip angle (\N{DEGREE SIGN})",
                             NumericOption(minval=0, maxval=90, default=12),
                             key="fa")
        self.acquisition.add("TR (ms)",
                             NumericOption(minval=0, maxval=10, default=4.108),
                             key="tr")
        self.acquisition.add("Time between volumes (s)",
                             NumericOption(minval=0, maxval=30, default=12),
                             key="delt")
        vbox.addWidget(self.acquisition)

        self.model = OptionBox("Model options")
        self.model.add(
            "Model",
            ChoiceOption([
                "Standard Tofts model", "Extended Tofts model (ETM)",
                "2 Compartment exchange model",
                "Compartmental Tissue Update (CTU) model",
                "Adiabatic Approximation to Tissue Homogeneity (AATH) Model"
            ], ["dce_tofts", "dce_ETM", "dce_2CXM", "dce_CTU", "dce_AATH"]),
            key="model")
        self.model.add(
            "AIF",
            ChoiceOption([
                "Population (Orton 2008)", "Population (Parker)",
                "Measured DCE signal", "Measured concentration curve"
            ], ["orton", "parker", "signal", "conc"]),
            key="aif")
        self.model.add("Bolus injection time (s)",
                       NumericOption(minval=0, maxval=60, default=30),
                       key="tinj")
        self.model.add("AIF data values",
                       NumberListOption([
                           0,
                       ]),
                       key="aif-data")
        self.model.add("T1 (s)",
                       NumericOption(minval=0.0, maxval=5.0, default=1.0),
                       key="t10")
        self.model.add("Allow T1 to vary",
                       BoolOption(default=False),
                       key="infer-t10")
        self.model.add("Bolus arrival time (s)",
                       NumericOption(minval=0, maxval=2.0, default=0),
                       key="delay")
        self.model.add("Allow bolus arrival time to vary",
                       BoolOption(default=False),
                       key="infer-delay")
        self.model.add("Infer kep rather than ve",
                       BoolOption(default=False),
                       key="infer-kep")
        self.model.add("Infer flow", BoolOption(default=True), key="infer-fp")
        self.model.add("Infer permeability-surface area",
                       BoolOption(default=False),
                       key="infer-ps")
        self.model.add("Spatial regularization",
                       BoolOption(default=False),
                       key="spatial")
        self.model.option("model").sig_changed.connect(self._model_changed)
        self.model.option("aif").sig_changed.connect(self._aif_changed)
        vbox.addWidget(self.model)

        # Run button and progress
        vbox.addWidget(RunWidget(self, title="Run modelling"))
        vbox.addStretch(1)

        self._aif_changed()
        self._model_changed()

    def _t1_map_changed(self):
        self.model.set_visible("t10", "t1" not in self.input.values())

    def _aif_changed(self):
        aif_source = self.model.option("aif").value
        self.model.set_visible("tinj", aif_source not in ("signal", "conc"))
        self.model.set_visible("aif-data", aif_source in ("signal", "conc"))

    def _model_changed(self):
        self.model.set_visible(
            "infer-kep",
            self.model.option("model").value in ("dce_tofts", "dce_ETM"))
        self.model.set_visible("infer-fp",
                               self.model.option("model").value == "dce_AATH")
        self.model.set_visible("infer-ps",
                               self.model.option("model").value == "dce_AATH")

    def processes(self):
        options = {
            "model-group": "dce",
            "method": "vb",
            "noise": "white",
            "save-mean": True,
            "save-model-fit": True,
            "convergence": "trialmode",
            "max-trials": 20,
            "max-iterations": 50,
            "infer-sig0": True,
        }
        options.update(self.input.values())
        options.update(self.acquisition.values())
        options.update(self.model.values())

        # Extended Tofts model is the same model name but with inference of Vp
        if options["model"] == "dce_ETM":
            options["model"] = "dce_tofts"
            options["infer-vp"] = True

        # T1 map is an image prior
        if "t1" in options:
            options.update({
                "PSP_byname1": "t10",
                "PSP_byname1_type": "I",
                "PSP_byname1_image": options.pop("t1")
            })
            if not options["infer-t10"]:
                # To treat the image prior as ground truth need to put T10
                # into the model but give the image prior a high precision so
                # the parameter doesn't actually have any freedom to vary
                options["infer-t10"] = True
                options["PSP_byname1_prec"] = 1e6

        # Delay time to include injection time for population AIF
        if "tinj" in options:
            options["delay"] = options["delay"] + options.pop("tinj")

        # Times in minutes and TR in s
        options["delt"] = options["delt"] / 60
        options["delay"] = options["delay"] / 60
        options["tr"] = options["tr"] / 1000

        # Spatial mode
        if options.pop("spatial", False):
            options["method"] = "spatialvb"
            options["param-spatial-priors"] = "M+"

        return {"Fabber": options}
Beispiel #11
0
class DceWidget(QpWidget):
    """
    Widget for DCE Pharmacokinetic modelling
    """
    def __init__(self, **kwargs):
        super(DceWidget, self).__init__(name="DCE Modelling",
                                        desc="DCE kinetic modelling",
                                        icon="dce",
                                        group="DCE-MRI",
                                        **kwargs)

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

        title = TitleWidget(self, help="pk", batch_btn=True, opts_btn=False)
        vbox.addWidget(title)

        self.input = OptionBox("Input data")
        self.input.add("DCE data",
                       DataOption(self.ivm, include_3d=False, include_4d=True),
                       key="data")
        self.input.add("ROI",
                       DataOption(self.ivm, data=False, rois=True),
                       key="roi")
        self.input.add("T1 map",
                       DataOption(self.ivm, include_3d=True, include_4d=False),
                       key="t1")
        vbox.addWidget(self.input)

        self.options = OptionBox("Options")
        self.options.add("Contrast agent R1 relaxivity (l/mmol s)",
                         NumericOption(minval=0, maxval=10, default=3.7),
                         key="r1")
        self.options.add("Contrast agent R2 relaxivity (l/mmol s)",
                         NumericOption(minval=0, maxval=10, default=4.8),
                         key="r2")
        self.options.add("Flip angle (\N{DEGREE SIGN})",
                         NumericOption(minval=0, maxval=90, default=12),
                         key="fa")
        self.options.add("TR (ms)",
                         NumericOption(minval=0, maxval=10, default=4.108),
                         key="tr")
        self.options.add("TE (ms)",
                         NumericOption(minval=0, maxval=10, default=1.832),
                         key="te")
        self.options.add("Time between volumes (s)",
                         NumericOption(minval=0, maxval=30, default=12),
                         key="dt")
        self.options.add("Estimated injection time (s)",
                         NumericOption(minval=0, maxval=60, default=30),
                         key="tinj")
        self.options.add("Ktrans/kep percentile threshold",
                         NumericOption(minval=0, maxval=100, default=100),
                         key="ve-thresh")
        self.options.add("Dose (mM/kg) - preclinical only",
                         NumericOption(minval=0, maxval=5, default=0.6),
                         key="dose",
                         visible=False)

        models = [
            "Clinical: Toft / OrtonAIF (3rd) with offset",
            "Clinical: Toft / OrtonAIF (3rd) no offset",
            "Preclinical: Toft / BiexpAIF (Heilmann)",
            "Preclinical: Ext Toft / BiexpAIF (Heilmann)",
        ]
        self.options.add("Pharmacokinetic model choice",
                         ChoiceOption(models, [1, 2, 3, 4]),
                         key="model")
        self.options.option("model").sig_changed.connect(self._aif_changed)
        vbox.addWidget(self.options)

        # Run button and progress
        vbox.addWidget(RunWidget(self, title="Run modelling"))
        vbox.addStretch(1)
        self._aif_changed()

    def _aif_changed(self):
        self.options.set_visible("dose",
                                 self.options.option("model").value in (2, 3))

    def processes(self):
        options = self.input.values()
        options.update(self.options.values())
        return {"PkModelling": options}
Beispiel #12
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)
class AddEmbeddingDialog(QtGui.QDialog):
    """
    Dialog box enabling one item to be chosen from a list
    """

    def __init__(self, parent, ivm, existing_strucs):
        super(AddEmbeddingDialog, self).__init__(parent)
        self.ivm = ivm
        self.sel_text = None
        self.sel_data = None
        self.existing_names = [struc.name for struc in existing_strucs]

        self.setWindowTitle("Add embedding")
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        self._opts = OptionBox()
        pvmap = self._opts.add("PV map / mask", DataOption(ivm, data=True, rois=True), key="pvmap")
        pvmap.sig_changed.connect(self._pvmap_changed)
        self._opts.add("ROI region", ChoiceOption([]), key="region")
        name = self._opts.add("Name of embedded structure", TextOption(), key="name")
        name.textChanged.connect(self._name_changed)
        self._opts.add("Structure type", ChoiceOption(["Embedding", "Activation mask", "Additional PVE"], return_values=["embed", "act", "add"]), key="type")
        self._opts.add("Parent structure", ChoiceOption([s.display_name for s in existing_strucs], [s.name for s in existing_strucs]), key="parent")
        self._opts.add("Edge smoothing sigma (mm)", NumericOption(minval=0, maxval=10, default=1, decimals=2), checked=True, enabled=False, key="sigma")
        vbox.addWidget(self._opts)

        self._warning = QtWidgets.QLabel(parent=self)
        self._warning.setStyleSheet("color: red;")
        vbox.addWidget(self._warning)

        self.button_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)
        vbox.addWidget(self.button_box)
        self._pvmap_changed()

    def _pvmap_changed(self):
        if self.pvmap:
            qpdata = self.ivm.data[self.pvmap]
            if not self.name:
                self._opts.option("name").value = qpdata.name
            if qpdata.roi:
                regions = list(qpdata.regions.keys())
                region_names = list(qpdata.regions.values())
                self._opts.option("region").setChoices(region_names, regions)
            self._opts.set_visible("region", qpdata.roi and len(qpdata.regions) > 1)
            self._opts.set_visible("sigma", qpdata.roi)

    def _name_changed(self):
        accept = self.name != "" and self.name not in self.existing_names
        self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(accept)
        if self.name and not accept:
            self._warning.setText("Name already exists")
        else:
            self._warning.setText("")

    @property
    def pvmap(self):
        return self._opts.option("pvmap").value

    @property
    def region(self):
        return self._opts.values().get("region", None)

    @property
    def sigma(self):
        return self._opts.values().get("sigma", None)

    @property
    def name(self):
        return self._opts.option("name").value

    @property
    def struc_type(self):
        return self._opts.option("type").value

    @property
    def parent_struc(self):
        return self._opts.option("parent").value
Beispiel #14
0
class AnalysisOptions(QtGui.QWidget):
    """
    Widget allowing model and output options to be changed
    """
    def __init__(self, ivm=None):
        QtGui.QWidget.__init__(self)
        self._ivm = ivm
        self._poolvals_edited = False

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

        self.optbox = OptionBox()
        vbox.addWidget(self.optbox)

        self.optbox.add("<b>Output options</b>")
        self.optbox.add("CEST R*",
                        BoolOption(default=True),
                        key="save-model-extras")
        self.optbox.add("Parameter maps",
                        BoolOption(default=False),
                        key="save-mean")
        #self.optbox.add("Parameter variance", BoolOption(default=False), key="var")
        self.optbox.add("Model fit",
                        BoolOption(default=False),
                        key="save-model-fit")
        self.optbox.add("Prefix for output",
                        TextOption(),
                        checked=True,
                        key="output-prefix")

        self.optbox.add(" ")
        self.optbox.add("<b>Analysis options</b>")
        self.optbox.add("Spatial Regularization", BoolOption(), key="spatial")
        self.optbox.add("Allow uncertainty in T1/T2 values",
                        BoolOption(),
                        key="t12prior")
        self.optbox.add("Prior T1 map",
                        DataOption(self._ivm),
                        key="t1img",
                        checked=True)
        self.optbox.add("Prior T2 map",
                        DataOption(self._ivm),
                        key="t2img",
                        checked=True)
        self.optbox.add("Tissue PV map (GM+WM)",
                        DataOption(self._ivm),
                        key="pvimg",
                        checked=True)
        self.optbox.option("t12prior").sig_changed.connect(self._update_ui)
        self.optbox.add("Use steady state solution for MT bias reduction",
                        BoolOption(default=False),
                        key="new-ss")
        self.optbox.option("new-ss").sig_changed.connect(self._update_ui)
        self.optbox.add("TR (s)",
                        NumericOption(default=3.0,
                                      minval=0,
                                      maxval=5,
                                      digits=3,
                                      step=0.1),
                        key="tr")
        self.optbox.add("Excitation flip angle (\N{DEGREE SIGN})",
                        NumericOption(default=12.0,
                                      minval=0,
                                      maxval=25,
                                      digits=3,
                                      step=1.0),
                        key="fa")
        self.optbox.add(
            "MT pool Line shape",
            ChoiceOption(
                ["None", "Gaussian", "Lorentzian", "Super Lorentzian"],
                ["none", "gaussian", "lorentzian", "superlorentzian"]),
            key="lineshape")

        self.alexmt_cite = Citation(ALEXMT_CITE_TITLE, ALEXMT_CITE_AUTHOR,
                                    ALEXMT_CITE_JOURNAL)
        vbox.addWidget(self.alexmt_cite)

        vbox.addStretch(1)
        self._update_ui()

    def _update_ui(self):
        t12prior = self.optbox.option("t12prior").value
        self.optbox.set_visible("t1img", t12prior)
        self.optbox.set_visible("t2img", t12prior)
        newss = self.optbox.values().get("new-ss", False)
        self.optbox.set_visible("tr", newss)
        self.optbox.set_visible("fa", newss)
        self.optbox.set_visible("lineshape", newss)
        self.alexmt_cite.setVisible(newss)

    def set_pools(self, pools):
        self.optbox.set_visible("new-ss", "MT"
                                in [p.name for p in pools if p.enabled])
        self._update_ui()

    def options(self):
        options = self.optbox.values()

        if options.pop("spatial", False):
            options["method"] = "spatialvb"
            options["param-spatial-priors"] = "MN+"
        else:
            options["method"] = "vb"
            options.pop("param-spatial-priors", None)

        # The new MT model is automatically triggered when the TR and FA options are given
        options.pop("new-ss", None)

        prior_num = 1
        for idx in (1, 2):
            if "t%iimg" % idx in options:
                options["PSP_byname%i" % prior_num] = "T%ia" % idx
                options["PSP_byname%i_type" % prior_num] = "I"
                options["PSP_byname%i_image" % prior_num] = options.pop(
                    "t%iimg" % idx)
                prior_num += 1
        return options
Beispiel #15
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)
Beispiel #16
0
class FabberWidget(QpWidget):
    """
    Widget for running Fabber model fitting
    """
    def __init__(self, **kwargs):
        QpWidget.__init__(self, **kwargs)
        self._fabber_options = {
            "degree" : 2,
            "noise" : "white",
            "save-mean" : True,
            "save-model-fit" : True,
            "save-model-extras" : True,
        }
        self._fabber_params = []

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

        title = TitleWidget(self, subtitle="Plugin %s" % __version__, help="fabber")
        self.vbox.addWidget(title)
        
        cite = Citation(FAB_CITE_TITLE, FAB_CITE_AUTHOR, FAB_CITE_JOURNAL)
        self.vbox.addWidget(cite)

        self.options = OptionBox("Options")
        self.options.sig_changed.connect(self._options_changed)
        self.vbox.addWidget(self.options)

        self.warn_box = WarningBox("")
        self.warn_box.setVisible(False)
        self.vbox.addWidget(self.warn_box)

    def _model_group_changed(self):
        models = FabberProcess.api().get_models(model_group=self._fabber_options.get("model-group", None))
        self.debug("Models: %s", models)
        self.options.option("model").setChoices(models)
      
    def _options_changed(self):
        self._fabber_options.update(self.options.values())
        if self._fabber_options.get("model-group", None) == "ALL":
           self._fabber_options["model-group"] = None

        self.debug("Options changed:\n%s", self._fabber_options)
        self._update_params()

    def _fix_data_params(self, api):
        """
        Given a set of Fabber options, replace those that should be data items with a Numpy array
        """
        options = dict(self._fabber_options)
        known_options = api.get_options(generic=True, model=options.get("model", None), method=options.get("method", None))[0]
        for key in options:
            if api.is_data_option(key, known_options):
                # Just provide a placeholder
                options[key] = np.zeros((1, 1, 1))
        return options

    def _update_params(self):
        from fabber import FabberException
        try:
            api = FabberProcess.api()
            options = self._fix_data_params(api)
            self._fabber_params = api.get_model_params(options)
            self.warn_box.setVisible(False)
        except FabberException as exc:
            self._fabber_params = []
            self.warn_box.text.setText("Invalid model options:\n\n%s" % str(exc))
            self.warn_box.setVisible(True)

    def _show_model_options(self):
        model = self._fabber_options["model"]
        dlg = OptionsDialog(self, ivm=self.ivm, rundata=self._fabber_options, desc_first=True)
        opts, desc = FabberProcess.api().get_options(model=model)
        self.debug("Model options: %s", opts)
        dlg.set_title("Forward Model: %s" % model, desc)
        dlg.set_options(opts)
        dlg.exec_()
        self._update_params()

    def _show_method_options(self):
        method = self._fabber_options["method"]
        dlg = OptionsDialog(self, ivm=self.ivm, rundata=self._fabber_options, desc_first=True)
        opts, desc = FabberProcess.api().get_options(method=method)
        # Ignore prior options which have their own dialog
        opts = [o for o in opts if "PSP_byname" not in o["name"] and o["name"] != "param-spatial-priors"]
        dlg.set_title("Inference method: %s" % method, desc)
        self.debug("Method options: %s", opts)
        dlg.set_options(opts)
        dlg.fit_width()
        dlg.exec_()
        
    def _show_general_options(self):
        dlg = OptionsDialog(self, ivm=self.ivm, rundata=self._fabber_options, desc_first=True)
        dlg.ignore("model", "method", "output", "data", "mask", "data<n>", "overwrite", "help",
                   "listmodels", "listmethods", "link-to-latest", "data-order", "dump-param-names",
                   "loadmodels")
        opts, _ = FabberProcess.api().get_options()
        dlg.set_options(opts)
        dlg.fit_width()
        dlg.exec_()
        
    def _show_prior_options(self):
        dlg = PriorsDialog(self, ivm=self.ivm, rundata=self._fabber_options)
        try:
            api = FabberProcess.api()
            options = self._fix_data_params(api)
            params = api.get_model_params(options)
        except Exception as exc:
            raise QpException("Unable to get list of model parameters\n\n%s\n\nModel options must be set before parameters can be listed" % str(exc))
        dlg.set_params(params)
        dlg.fit_width()
        dlg.exec_()

    def get_options(self):
        """ Return a copy of current Fabber options """
        return dict(self._fabber_options)

    def get_process(self):
        return FabberProcess(self.ivm)

    def batch_options(self):
        return "Fabber", self.get_options()
Beispiel #17
0
class AcquisitionOptions(OptionsWidget):

    N_REGRESSORS = 3

    def __init__(self, ivm, parent):
        OptionsWidget.__init__(self, ivm, parent)

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

        self._optbox = OptionBox()
        self._optbox.add("<b>Data</b>")
        self._optbox.add("BOLD timeseries data",
                         DataOption(self.ivm),
                         key="data")
        self._optbox.add("ROI",
                         DataOption(self.ivm, rois=True, data=False),
                         key="roi")

        #self._optbox.add("Physiological data (CO<sub>2</sub>/O<sub>2</sub>)", FileOption(plot_btn=True), key="phys-data")
        #self._optbox.add("Sampling frequency (Hz)", NumericOption(minval=0, maxval=1000, default=100, intonly=True), key="samp-rate")
        self._optbox.add("TR for MRI timeseries (s)",
                         NumericOption(minval=0, maxval=5, default=1.0),
                         key="tr")
        self._optbox.add("Baseline period (s)",
                         NumericOption(minval=0,
                                       maxval=200,
                                       default=60,
                                       intonly=True),
                         key="baseline")
        self._optbox.add("MRI timeseries alignment",
                         ChoiceOption(["Automatic", "Manual"]),
                         key="mri-align")
        self._optbox.option("mri-align").sig_changed.connect(
            self._align_changed)
        self._optbox.add("MRI timeseries start time (s)",
                         NumericOption(minval=0, maxval=1000, default=0),
                         key="data-start-time")
        vbox.addWidget(self._optbox)

        self._optbox_reg = OptionBox()
        self._optbox_reg.add("<b>Regressors</b>")
        for idx in range(self.N_REGRESSORS):
            self._optbox_reg.add("Regressor %i" % (idx + 1),
                                 ChoiceOption([
                                     "Unprocessed CO2", "Preprocessed pETCO2",
                                     "Ramp (linear drift)", "Custom"
                                 ], ["co2", "petco2", "ramp", "custom"]),
                                 checked=True,
                                 default=True,
                                 key="type_%i" % (idx + 1))
            self._optbox_reg.option("type_%i" % (idx + 1)).sig_changed.connect(
                self._regressor_changed)
            self._optbox_reg.add("Data (CO<sub>2</sub>/O<sub>2</sub>)",
                                 FileOption(plot_btn=True),
                                 key="data_%i" % (idx + 1))
            self._optbox_reg.add("Time resolution (s)",
                                 NumericOption(minval=0, maxval=10, default=1),
                                 key="tr_%i" % (idx + 1))

        vbox.addWidget(self._optbox_reg)

        vbox.addStretch(1)
        self._regressor_changed()
        self._align_changed()

    def _regressor_changed(self):
        for idx in range(self.N_REGRESSORS):
            opts = self._optbox_reg.values()
            extras_visible = "type_%i" % (
                idx + 1) in opts and opts["type_%i" % (idx + 1)] != "ramp"
            self._optbox_reg.set_visible("data_%i" % (idx + 1), extras_visible)
            self._optbox_reg.set_visible("tr_%i" % (idx + 1), extras_visible)

    def _add_regressor_options(self, opts):
        regressors = []
        regressor_types = []
        regressor_trs = []
        reg_opts = self._optbox_reg.values()
        for idx in range(self.N_REGRESSORS):
            regressor_type = reg_opts.get("type_%i" % (idx + 1), None)
            if regressor_type is not None:
                if regressor_type != "ramp":
                    regressor_types.append(regressor_type)
                    regressors.append(reg_opts["data_%i" % (idx + 1)])
                    regressor_trs.append(reg_opts["tr_%i" % (idx + 1)])
                else:
                    # FIXME can't mix file regressors with Numpy array, need to write to tmp file
                    regressor_types.append("custom")
                    regressor_trs.append(opts["tr"])
                    regressors.append(
                        np.linspace(0, 1, self.ivm.data[opts["data"]].nvols))
        opts["regressors"] = ",".join(regressors)
        opts["regressor_trs"] = ",".join(["%.3f" % v for v in regressor_trs])
        opts["regressor_types"] = ",".join(regressor_types)

    def _align_changed(self):
        self._optbox.set_visible(
            "data-start-time",
            self._optbox.option("mri-align").value == "Manual")

    def options(self):
        opts = self._optbox.values()
        opts.pop("mri-align", None)
        self._add_regressor_options(opts)
        return opts
Beispiel #18
0
class ViewerOptions(QtGui.QDialog):
    """
    Dialog box which controls viewer options
    """
    def __init__(self, parent, ivl):
        super(ViewerOptions, self).__init__(parent)
        self.setWindowTitle("View Options")
        self._ivl = ivl

        vbox = QtGui.QVBoxLayout()
        label = QtGui.QLabel('<font size="5">View Options</font>')
        vbox.addWidget(label)

        self._optbox = OptionBox()
        self._optbox.add(
            "Orientation",
            ChoiceOption([
                "Radiological (Right is Left)", "Neurological (Left is Left)"
            ], [Orientation.RADIOLOGICAL, Orientation.NEUROLOGICAL],
                         default=self._ivl.opts.orientation),
            key="orient")
        self._optbox.add("Crosshairs",
                         ChoiceOption(["Show", "Hide"],
                                      [Visibility.SHOW, Visibility.HIDE],
                                      default=self._ivl.opts.crosshairs),
                         key="crosshairs")
        self._optbox.add("Orientation labels",
                         ChoiceOption(["Show", "Hide"],
                                      [Visibility.SHOW, Visibility.HIDE],
                                      default=self._ivl.opts.labels),
                         key="labels")
        self._optbox.add("Main data background",
                         ChoiceOption(["Show", "Hide"],
                                      [Visibility.SHOW, Visibility.HIDE],
                                      default=self._ivl.opts.main_data),
                         key="main_data")
        self._optbox.add(
            "Display order",
            ChoiceOption(
                ["User selected", "Data always on top", "ROIs always on top"],
                [
                    DisplayOrder.USER, DisplayOrder.DATA_ON_TOP,
                    DisplayOrder.ROI_ON_TOP
                ],
                default=self._ivl.opts.display_order),
            key="display_order")
        self._optbox.add(
            "View interpolation",
            ChoiceOption(
                ["Nearest neighbour (fast)", "Linear", "Cubic spline (slow)"],
                [0, 1, 3],
                default=self._ivl.opts.interp),
            key="interp")
        self._optbox.option("orient").sig_changed.connect(
            self._orientation_changed)
        self._optbox.option("crosshairs").sig_changed.connect(
            self._crosshairs_changed)
        self._optbox.option("labels").sig_changed.connect(self._labels_changed)
        self._optbox.option("main_data").sig_changed.connect(
            self._main_data_changed)
        self._optbox.option("display_order").sig_changed.connect(
            self._display_order_changed)
        self._optbox.option("interp").sig_changed.connect(self._interp_changed)
        vbox.addWidget(self._optbox)

        vbox.addStretch(1)
        self.setLayout(vbox)

    def _orientation_changed(self):
        self._ivl.opts.orientation = self._optbox.option("orient").value

    def _crosshairs_changed(self):
        self._ivl.opts.crosshairs = self._optbox.option("crosshairs").value

    def _labels_changed(self):
        self._ivl.opts.labels = self._optbox.option("labels").value

    def _main_data_changed(self):
        self._ivl.opts.main_data = self._optbox.option("main_data").value

    def _display_order_changed(self):
        self._ivl.opts._display_order = self._optbox.option(
            "display_order").value

    def _interp_changed(self):
        self._ivl.opts.interp = self._optbox.option("interp").value
Beispiel #19
0
class FslDirDialog(QtGui.QDialog):
    """
    Dialog box to choose FSLDIR
    """
    def __init__(self, fsldir, fsldevdir):
        super(FslDirDialog, self).__init__(quantiphyse.gui.dialogs.MAINWIN)
        self.setWindowTitle("Choose location of FSL installation")
        vbox = QtGui.QVBoxLayout()

        self.optbox = OptionBox()
        self.optbox.add("FSL installation",
                        FileOption(dirs=True, initial=fsldir),
                        key="fsldir")
        self.optbox.add("FSL development code",
                        FileOption(dirs=True, initial=fsldevdir),
                        checked=True,
                        enabled=bool(fsldevdir),
                        key="fsldevdir")
        self.optbox.option("fsldir").sig_changed.connect(self._fsldir_changed)
        self.optbox.option("fsldevdir").sig_changed.connect(
            self._fsldevdir_changed)
        vbox.addWidget(self.optbox)

        hbox = QtGui.QHBoxLayout()
        hbox.addStretch(1)
        self.button_box = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        hbox.addWidget(self.button_box)
        vbox.addLayout(hbox)
        self.setLayout(vbox)

    def _fsldir_changed(self):
        fsldir = self.optbox.option("fsldir").value
        self.optbox.option("fsldir").value = self._check_dir(fsldir)

    def _fsldevdir_changed(self):
        fsldevdir = self.optbox.option("fsldevdir").value
        self.optbox.option("fsldevdir").value = self._check_dir(fsldevdir)

    def _check_dir(self, dir):
        # Seems to be a bug in QFileDialog that returns UNC WSL filepath with forward not
        # back slashes which causes fslpy to fail
        if dir.startswith("//wsl$"):
            dir = dir.replace("//wsl$", "\\\\wsl$", 1)

        prefixes = [""]
        if sys.platform.startswith("win"):
            # On Windows, look for the dir under \\wsl$\ as well because sometimes QFileDialog
            # does not return this part of the path
            prefixes.append("\\\\wsl$")

        for prefix in prefixes:
            trial_dir = prefix + dir
            if os.path.exists(trial_dir):
                return trial_dir
        return dir

    @property
    def fsldir(self):
        return self.optbox.values()["fsldir"]

    @property
    def fsldevdir(self):
        return self.optbox.values().get("fsldevdir", None)
Beispiel #20
0
class AtlasDescription(QtGui.QWidget):
    """
    Displays atlas description
    """

    sig_selected = QtCore.Signal(object)

    def __init__(self, parent, registry):
        super(AtlasDescription, self).__init__(parent)
        self._registry = registry
        self.ivm = parent.ivm
        self._desc = None
        grid = QtGui.QGridLayout()
        self.setLayout(grid)

        grid.addWidget(QtGui.QLabel("Name"), 0, 0)
        self._name = QtGui.QLabel()
        grid.addWidget(self._name, 0, 1)
        grid.addWidget(QtGui.QLabel("Type"), 1, 0)
        self._type = QtGui.QLabel()
        grid.addWidget(self._type, 1, 1)
        grid.addWidget(QtGui.QLabel("Resolutions"), 2, 0)
        self._imgs = QtGui.QComboBox()
        grid.addWidget(self._imgs, 2, 1)

        self._label_table = QtGui.QTableView()
        self._label_model = QtGui.QStandardItemModel()
        self._label_table.setModel(self._label_model)
        self._label_table.setSelectionBehavior(
            QtGui.QAbstractItemView.SelectRows)
        self._label_table.setSelectionMode(
            QtGui.QAbstractItemView.SingleSelection)
        self._label_table.setEditTriggers(
            QtGui.QAbstractItemView.NoEditTriggers)
        self._label_table.selectionModel().selectionChanged.connect(
            self._region_changed)

        self._label_table.setStyleSheet(
            "font-size: 10px; alternate-background-color: #6c6c6c;")
        self._label_table.setShowGrid(False)
        self._label_table.setTextElideMode(QtCore.Qt.ElideLeft)
        self._label_table.setAlternatingRowColors(True)
        self._label_table.ensurePolished()
        fm = QtGui.QFontMetrics(self._label_table.font())
        self._label_table.verticalHeader().setVisible(False)
        self._label_table.verticalHeader().setSectionResizeMode(
            QtGui.QHeaderView.Fixed)
        self._label_table.verticalHeader().setDefaultSectionSize(fm.height() +
                                                                 2)

        grid.addWidget(self._label_table, 3, 0, 1, 2)
        grid.setRowStretch(3, 1)

        self._load_options = OptionBox()
        self._load_options.add("Regions",
                               ChoiceOption(["Selected region", "All regions"],
                                            ["sel", "all"]),
                               key="regions")
        self._load_options.add(
            "Load as",
            ChoiceOption(["New dataset", "Add to existing dataset"],
                         ["new", "add"]),
            key="add")
        self._load_options.add("Dataset name", TextOption("atlas"), key="name")
        self._load_options.add("Existing dataset",
                               DataOption(self.ivm),
                               key="data")
        self._load_options.option("regions").sig_changed.connect(
            self._load_regions_changed)
        self._load_options.option("add").sig_changed.connect(self._add_changed)
        grid.addWidget(self._load_options, 4, 0, 1, 2)

        hbox = QtGui.QHBoxLayout()
        btn = QtGui.QPushButton("Load")
        btn.clicked.connect(self._load)
        hbox.addWidget(btn)
        hbox.addStretch(1)
        grid.addLayout(hbox, 5, 0, 1, 2)
        self._add_changed()

    def _name_to_dataset_name(self, name):
        return name.replace(" ",
                            "_").replace("-", "_").replace(",", "").replace(
                                "(", "").replace(")", "").lower()

    def set_atlas(self, atlas_desc):
        self._desc = atlas_desc
        self._name.setText(atlas_desc.name)
        self._type.setText(atlas_desc.atlasType)
        self._load_options.option("name").value = self._name_to_dataset_name(
            atlas_desc.name)

        self._imgs.clear()
        for pixdim in atlas_desc.pixdims:
            pixdim_str = "%.2g mm x %.2g mm x %.2g mm" % pixdim
            self._imgs.addItem(pixdim_str, pixdim[0])

        self._label_model.clear()
        self._label_model.setColumnCount(2)
        self._label_model.setHorizontalHeaderLabels(["Index", "Name"])
        for label in atlas_desc.labels:
            index_item = QtGui.QStandardItem("%i" % label.index)
            name_item = QtGui.QStandardItem(label.name)
            self._label_model.appendRow([index_item, name_item])
        self._label_table.horizontalHeader().setResizeMode(
            0, QtGui.QHeaderView.ResizeToContents)
        self._label_table.horizontalHeader().setResizeMode(
            1, QtGui.QHeaderView.Stretch)
        self._load_options.option("regions").value = "all"

    def _load_regions_changed(self):
        if self._load_options.values()["regions"] == "sel":
            self._region_changed()
        else:
            self._load_options.option(
                "name").value = self._name_to_dataset_name(self._desc.name)

    def _region_changed(self):
        if self._load_options.values()["regions"] == "sel":
            indexes = self._label_table.selectionModel().selectedRows()
            if indexes:
                region_name = self._label_model.item(indexes[0].row(),
                                                     1).text()
                if region_name:
                    self._load_options.option(
                        "name").value = self._name_to_dataset_name(region_name)

    def _add_changed(self):
        add = self._load_options.values()["add"] == "add"
        self._load_options.set_visible("name", not add)
        self._load_options.set_visible("data", add)

    def _load(self):
        if self._desc is not None:
            res = self._imgs.itemData(self._imgs.currentIndex())
            atlas = self._registry.loadAtlas(self._desc.atlasID,
                                             loadSummary=False,
                                             resolution=res)
            is_roi = self._desc.atlasType == "label"

            new_name = self._load_options.option("name").value
            add_name = self._load_options.option("data").value
            add = self._load_options.option("add").value == "add"
            load_all = self._load_options.option("regions").value == "all"

            vol = None
            if not load_all:
                indexes = self._label_table.selectionModel().selectedRows()
                vol = int(self._label_model.item(indexes[0].row(), 0).text())
            new_data = fslimage_to_qpdata(atlas,
                                          vol=vol,
                                          name=new_name,
                                          roi=is_roi)

            if add and add_name in self.ivm.data:
                # User wants to add the region to an existing data set
                if load_all:
                    raise QpException(
                        "Cannot add data to existing data set when loading all regions"
                    )
                orig_data = self.ivm.data[add_name]
                if not orig_data.grid.matches(new_data.grid):
                    raise QpException(
                        "Can't add data to existing data set - grids do not match"
                    )
                if is_roi and not orig_data.roi:
                    raise QpException(
                        "Can't add data to existing data set - it is not an ROI"
                    )
                new_data = NumpyData(orig_data.raw() + new_data.raw(),
                                     grid=new_data.grid,
                                     name=add_name,
                                     roi=is_roi)

            self.ivm.add(new_data, make_current=True)
Beispiel #21
0
class SequenceOptions(QtGui.QWidget):
    """
    Widget containing options for the CEST sequence
    """

    sig_b0_changed = QtCore.Signal(float)

    def __init__(self, ivm=None):
        QtGui.QWidget.__init__(self)
        self._ivm = ivm

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

        self.optbox = OptionBox()
        vbox.addWidget(self.optbox)

        self.optbox.add("CEST data", DataOption(self._ivm), key="data")
        self.optbox.add("ROI",
                        DataOption(self._ivm, rois=True, data=False),
                        key="mask")
        self.optbox.add("Frequency offsets", NumberListOption(), key="freqs")
        self.optbox.add("B0", ChoiceOption(B0_DEFAULTS), key="b0")
        self.optbox.add("Custom B0 (T)",
                        NumericOption(minval=0.0,
                                      maxval=15,
                                      default=3.0,
                                      decimals=3),
                        key="b0_custom")
        # FIXME multiple B1 values
        self.optbox.add("B1 (\u03bcT)",
                        NumericOption(minval=0.0,
                                      maxval=2,
                                      default=0.55,
                                      decimals=6),
                        key="b1")
        self.optbox.add(
            "Saturation",
            ChoiceOption(["Continuous Saturation", "Pulsed Saturation"],
                         ["continuous", "pulsed"]),
            key="sat")
        self.optbox.add("Saturation time (s)",
                        NumericOption(minval=0.0,
                                      maxval=5,
                                      default=2,
                                      decimals=2),
                        key="sat_time")
        self.optbox.add("Pulse Magnitudes",
                        NumberListOption(),
                        key="pulse_mag")
        self.optbox.add("Pulse Durations (s)",
                        NumberListOption(),
                        key="pulse_dur")
        self.optbox.add("Pulse Repeats",
                        NumberListOption(),
                        key="pulse_repeats")

        self.optbox.option("b0").sig_changed.connect(self._b0_changed)
        self.optbox.option("b0_custom").sig_changed.connect(self._b0_changed)
        self.optbox.option("sat").sig_changed.connect(self._sat_changed)

        self.warn_box = WarningBox()
        vbox.addWidget(self.warn_box)

        # B1 field
        #hbox = QtGui.QHBoxLayout()
        #self.unsat_cb = QtGui.QCheckBox("Unsaturated")
        #self.unsat_cb.stateChanged.connect(self.update_ui)
        #hbox.addWidget(self.unsat_cb)
        #self.unsat_combo = QtGui.QComboBox()
        #self.unsat_combo.addItem("first")
        #self.unsat_combo.addItem("last")
        #self.unsat_combo.addItem("first and last  ")
        #hbox.addWidget(self.unsat_combo)
        #hbox.addStretch(1)
        #grid.addLayout(hbox, 2, 2)

        vbox.addStretch(1)
        self._sat_changed()
        self._b0_changed()

    def _sat_changed(self):
        pulsed = self.optbox.option("sat").value == "pulsed"
        self.optbox.set_visible("pulse_mag", pulsed)
        self.optbox.set_visible("pulse_dur", pulsed)
        self.optbox.set_visible("pulse_repeats", pulsed)

    def _b0_changed(self):
        b0_sel = self.optbox.option("b0").value
        if b0_sel == "Custom":
            self.optbox.set_visible("b0_custom", True)
            b0 = self.optbox.option("b0_custom").value
        else:
            self.optbox.set_visible("b0_custom", False)
            b0 = float(b0_sel[:-1])
        self.sig_b0_changed.emit(b0)

    def _get_dataspec(self, options):
        dataspec = []
        freqs = options.pop("freqs")
        b1 = options.pop("b1") / 1e6
        if options["sat"] == "pulsed":
            repeats = options.pop("pulse_repeats")
        else:
            repeats = 1
        for idx, freq in enumerate(freqs):
            #if self.unsat_cb.isChecked():
            #    self.debug("Unsat", idx, self.unsat_combo.currentIndex())
            #    if idx == 0 and self.unsat_combo.currentIndex() in (0, 2):
            #        b1 = 0
            #    elif idx == len(freqs)-1 and self.unsat_combo.currentIndex() in (1, 2):
            #        b1 = 0
            dataspec.append([freq, b1, repeats])
        #self.debug(dataspec)
        return dataspec

    def _get_ptrain(self, options):
        ptrain = []
        if options.pop("sat") == "pulsed":
            pms = options.pop("pulse_mag")
            pds = options.pop("pulse_dur")
            if len(pms) != len(pds):
                raise QpException(
                    "Pulse magnitude and duration must contain the same number of values"
                )
            for pm, pd in zip(pms, pds):
                ptrain.append([pm, pd])
        else:
            ptrain.append([1, options.pop("sat_time")])
        #self.debug(ptrain)
        return ptrain

    def options(self):
        options = self.optbox.values()
        options["spec"] = self._get_dataspec(options)
        options["ptrain"] = self._get_ptrain(options)
        options.pop("b0")
        options.pop("b0_custom", None)
        return options
Beispiel #22
0
class DscOptionsWidget(QtGui.QWidget):
    """
    Widget allowing choice of DSC options
    """
    def __init__(self, ivm):
        QtGui.QWidget.__init__(self)
        self.ivm = ivm

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

        self.optbox = OptionBox()

        self.optbox.add("DSC Data", DataOption(self.ivm), key="data")
        self.optbox.add("ROI",
                        DataOption(self.ivm, rois=True, data=False),
                        key="mask")
        self.optbox.add(
            "Model choice",
            ChoiceOption(["Standard", "Control point interpolation"],
                         ["dsc", "dsc_cpi"]),
            key="model")
        self.optbox.add("TE (s)",
                        NumericOption(minval=0, maxval=0.1, default=0.065),
                        key="te")
        self.optbox.add("Time interval between volumes (s)",
                        NumericOption(minval=0, maxval=10, default=1.5),
                        key="delt")
        self.optbox.add("Apply dispersion to AIF", BoolOption(), key="disp")
        self.optbox.add("Infer delay parameter",
                        BoolOption(default=True),
                        key="inferdelay")
        self.optbox.add("Infer arterial component",
                        BoolOption(),
                        key="inferart")
        self.optbox.add("Log transform on rCBF", BoolOption(), key="log-cbf")
        self.optbox.add("Output residue function",
                        BoolOption(),
                        key="save-model-extras")
        self.optbox.add("Spatial regularization",
                        ChoiceOption(("None", "Standard", "Full"),
                                     default="Standard"),
                        key="spatial")
        self.optbox.add("Output data suffix",
                        TextOption(),
                        checked=True,
                        key="output-suffix")
        self.optbox.option("model").sig_changed.connect(self._model_changed)

        vbox.addWidget(self.optbox)

        hbox = QtGui.QHBoxLayout()
        self.classic_options = OptionBox("Standard model")
        self.classic_options.add("Infer MTT",
                                 BoolOption(default=True),
                                 key="infermtt")
        self.classic_options.add("Infer lambda",
                                 BoolOption(default=True),
                                 key="inferlambda")
        hbox.addWidget(self.classic_options)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        self.cpi_options = OptionBox("CPI model")
        self.cpi_options.setVisible(False)
        self.cpi_options.add("Number of control points",
                             NumericOption(minval=3,
                                           maxval=20,
                                           default=5,
                                           intonly=True),
                             key="num-cps")
        self.cpi_options.add("Infer control point time position",
                             BoolOption(),
                             key="infer-cpt")
        hbox.addWidget(self.cpi_options)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addStretch()

    def options(self):
        """ :return: Dictionary of options selected for the DSC analysis"""
        opts = self.optbox.values()
        if opts["model"] == "dsc":
            opts.update(self.classic_options.values())
        elif opts["model"] == "dsc_cpi":
            opts.update(self.cpi_options.values())

        spatial = opts.pop("spatial", "None")
        if spatial == "Standard":
            opts["method"] = "spatialvb"
            opts["param-spatial-priors"] = "MN+"
        elif spatial == "Full":
            opts["method"] = "spatialvb"
            opts["param-spatial-priors"] = "M+"

        if opts.pop("log-cbf", False):
            opts["PSP_byname1"] = "cbf"
            opts["PSP_byname1_mean"] = 0.1
            opts["PSP_byname1_prec"] = 1e-4
            opts["PSP_byname1_transform"] = "L"

        return opts

    def _model_changed(self):
        classic = self.optbox.option("model").value == "dsc"
        self.classic_options.setVisible(classic)
        self.cpi_options.setVisible(not classic)
Beispiel #23
0
class NewPoolDialog(QtGui.QDialog):
    def __init__(self, parent):
        super(NewPoolDialog, self).__init__(parent)
        self.setWindowTitle("New Pool")
        vbox = QtGui.QVBoxLayout()

        self.optbox = OptionBox()
        vbox.addWidget(self.optbox)

        self.optbox.add("Name", TextOption(), key="name")
        self.optbox.add("PPM",
                        NumericOption(minval=0,
                                      maxval=20,
                                      default=0,
                                      decimals=3,
                                      slider=False),
                        key="ppm")
        self.optbox.add("Exchange rate",
                        NumericOption(minval=0,
                                      maxval=1000,
                                      default=0,
                                      decimals=1,
                                      slider=False),
                        key="exch")
        self.optbox.add("T1 (s)",
                        NumericOption(minval=0,
                                      maxval=10,
                                      default=1.0,
                                      decimals=2,
                                      slider=False),
                        key="t1")
        self.optbox.add("T2 (s)",
                        NumericOption(minval=0,
                                      maxval=1.0,
                                      default=0.07,
                                      decimals=6,
                                      slider=False),
                        key="t2")
        self.optbox.option("name").sig_changed.connect(self._validate)

        self.button_box = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        vbox.addWidget(self.button_box)

        self.setLayout(vbox)
        self._validate()

    def pool(self, b0):
        return Pool(self.optbox.option("name").value,
                    True,
                    vals={
                        b0: [
                            self.optbox.option(w).value
                            for w in ["ppm", "exch", "t1", "t2"]
                        ]
                    },
                    userdef=True)

    def _validate(self):
        valid = all(
            [self.optbox.option(w).valid for w in ["ppm", "exch", "t1", "t2"]])

        if self.optbox.option("name").value != "":
            self.optbox.option("name").setStyleSheet("")
        else:
            self.optbox.option("name").setStyleSheet(
                "QLineEdit {background-color: red}")
            valid = False

        self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(valid)
Beispiel #24
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
Beispiel #25
0
class RegWidget(QpWidget):
    """
    Generic registration / motion correction widget 
    """
    def __init__(self, **kwargs):
        super(RegWidget,
              self).__init__(name="Registration",
                             icon="reg",
                             desc="Registration and Motion Correction",
                             group="Registration",
                             **kwargs)
        self.reg_methods = []
        for method in get_plugins("reg-methods"):
            try:
                self.reg_methods.append(method(self.ivm))
            except:
                traceback.print_exc()
                self.warn("Failed to create registration method: %s", method)

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

        title = TitleWidget(self,
                            title="Registration and Motion Correction",
                            help="reg")
        layout.addWidget(title)

        if not self.reg_methods:
            layout.addWidget(QtGui.QLabel("No registration methods found"))
            layout.addStretch(1)
            return

        self.options = OptionBox("General Options")
        self.options.add("Mode",
                         ChoiceOption(["Registration", "Motion Correction"],
                                      ["reg", "moco"]),
                         key="mode")
        self.options.add(
            "Method",
            ChoiceOption([method.display_name for method in self.reg_methods],
                         self.reg_methods),
            key="method")
        self.options.add("Registration data", DataOption(self.ivm), key="reg")
        self.options.add("Reference data", DataOption(self.ivm), key="ref")
        self.options.add(
            "Reference volume",
            ChoiceOption(["Middle volume", "Mean volume", "Specified volume"],
                         ["median", "mean", "idx"]),
            key="ref-vol")
        self.options.add("Reference volume index",
                         NumericOption(intonly=True),
                         key="ref-idx")
        self.options.add(
            "Output space",
            ChoiceOption(["Reference", "Registration", "Transformed"],
                         ["ref", "reg", "trans"]),
            key="output-space")
        self.options.add("Output name",
                         OutputNameOption(src_data=self.options.option("reg"),
                                          suffix="_reg"),
                         key="output-name",
                         checked=True)
        self.options.add("Also apply transform to",
                         DataOption(self.ivm, multi=True),
                         key="add-reg")
        self.options.add("Save transformation",
                         TextOption(),
                         key="save-transform",
                         checked=True,
                         default=False)

        self.options.option("mode").sig_changed.connect(
            self._update_option_visibility)
        self.options.option("method").sig_changed.connect(self._method_changed)
        self.options.option("ref").sig_changed.connect(
            self._update_option_visibility)
        self.options.option("ref-vol").sig_changed.connect(
            self._update_option_visibility)
        self.options.option("reg").sig_changed.connect(
            self._update_option_visibility)
        layout.addWidget(self.options)

        # Create the options boxes for reg methods - only one visible at a time!
        self.opt_boxes = {}
        for method in self.reg_methods:
            hbox = QtGui.QHBoxLayout()
            opt_box = QtGui.QGroupBox()
            opt_box.setTitle(method.display_name)
            vbox = QtGui.QVBoxLayout()
            opt_box.setLayout(vbox)
            vbox.addWidget(method.interface())
            hbox.addWidget(opt_box)
            opt_box.setVisible(False)
            layout.addLayout(hbox)
            self.opt_boxes[method.name] = opt_box

        layout.addWidget(RunWidget(self))
        layout.addStretch(1)
        self._method_changed()

    def _method_changed(self):
        method = self.options.option("method").value
        for name, box in self.opt_boxes.items():
            box.setVisible(name == method.name)
        self.options.option("save-transform").value = "%s_trans" % method.name
        self._update_option_visibility()

    def _update_option_visibility(self):
        mode = self.options.option("mode").value
        regdata = self.ivm.data.get(self.options.option("reg").value, None)
        refdata = self.ivm.data.get(self.options.option("ref").value, None)
        refvol = self.options.option("ref-vol").value

        nvols_reg, nvols_ref = 1, 1
        if regdata is not None:
            nvols_reg = regdata.nvols

        if mode == "moco" and regdata is not None:
            nvols_ref = regdata.nvols
        elif mode == "reg" and refdata is not None:
            nvols_ref = refdata.nvols

        self.options.set_visible("ref", mode == "reg")
        self.options.set_visible("ref-vol", nvols_ref > 1)
        self.options.set_visible("ref-idx", nvols_ref > 1 and refvol == "idx")
        self.options.set_visible("add-reg", nvols_reg == 1 and mode == "reg")
        self.options.set_visible("output-space", mode == "reg")

        if nvols_ref > 1:
            self.options.option("ref-idx").setLimits(0, nvols_ref - 1)
            self.options.option("ref-idx").value = int(nvols_ref / 2)

    def processes(self):
        options = self.options.values()
        if options.get("ref-vol", None) == "idx":
            options["ref-vol"] = options.pop("ref-idx")

        method = options.pop("method")
        options["method"] = method.name
        options.update(method.options())

        return {
            "Reg": options,
        }
Beispiel #26
0
class DceDataModelView:
    def __init__(self, ivm):
        self.model = DceDataModel(ivm)
        self.gui = OptionBox()
        self.gui.add(
            "Model",
            ChoiceOption([
                "Standard Tofts model", "Extended Tofts model (ETM)",
                "2 Compartment exchange model",
                "Compartmental Tissue Update (CTU) model",
                "Adiabatic Approximation to Tissue Homogeneity (AATH) Model"
            ], ["dce_tofts", "dce_ETM", "dce_2CXM", "dce_CTU", "dce_AATH"]),
            key="model")
        self.gui.add("Contrast agent R1 relaxivity (l/mmol s)",
                     NumericOption(minval=0, maxval=10, default=3.7),
                     key="r1")
        self.gui.add("Flip angle (\N{DEGREE SIGN})",
                     NumericOption(minval=0, maxval=90, default=12),
                     key="fa")
        self.gui.add("TR (ms)",
                     NumericOption(minval=0, maxval=10, default=4.108),
                     key="tr")
        self.gui.add("Time between volumes (s)",
                     NumericOption(minval=0, maxval=30, default=12),
                     key="delt")
        self.gui.add("AIF",
                     ChoiceOption([
                         "Population (Orton 2008)", "Population (Parker)",
                         "Measured DCE signal", "Measured concentration curve"
                     ], ["orton", "parker", "signal", "conc"]),
                     key="aif")
        self.gui.add("Number of volumes",
                     NumericOption(minval=0,
                                   maxval=100,
                                   default=20,
                                   intonly=True),
                     key="nt")
        self.gui.add("Bolus injection time (s)",
                     NumericOption(minval=0, maxval=60, default=30),
                     key="tinj")
        self.gui.add("AIF data values",
                     NumberListOption([
                         0,
                     ]),
                     key="aif-data")
        self.gui.add("Arterial transit time (s)",
                     NumericOption(minval=0, maxval=1.0, default=0),
                     key="delay")
        self.gui.option("model").sig_changed.connect(self._model_changed)
        self.gui.option("aif").sig_changed.connect(self._aif_changed)
        self._aif_changed()
        self._model_changed()
        self.gui.sig_changed.connect(self._update_options)
        self._update_options()

    def _aif_changed(self):
        aif_source = self.gui.option("aif").value
        self.gui.set_visible("tinj", aif_source not in ("signal", "conc"))
        self.gui.set_visible("aif-data", aif_source in ("signal", "conc"))
        self.gui.set_visible("nt", aif_source not in ("signal", "conc"))

    def _model_changed(self):
        pass

    def _update_options(self):
        self.model.options.update(self.gui.values())
Beispiel #27
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")
class UserPvModelView:
    """
    View for UserPvModel - a structural model where user supplies partial volume maps
    """

    def __init__(self, ivm):
        self.ivm = ivm
        self.model = UserPvModel(ivm)
        self.model.options.update({
            "pvmaps" : {},
            "additional" : {},
        })
        self.gui = OptionBox()
        self.gui.sig_changed.connect(self._update_options)
        self._refresh_gui()

    def _refresh_gui(self):
        options = dict(self.model.options)
        self.gui.clear()
        for struc in self.model.default_strucs:
            data_opt = self.gui.add("%s map" % struc.name.upper(), DataOption(self.model._ivm, explicit=True), checked=True, enabled=struc.name in options["pvmaps"], key=struc.name)
            data_opt.value = options["pvmaps"].get(struc.name, None)

        for struc in options["additional"].values():
            del_btn = self._struc_delete_btn(struc)
            display_type = {"add" : "map", "embed" : "embedding", "act" : "mask"}.get(struc["struc_type"], "map")
            data_opt = self.gui.add("%s %s" % (struc["name"], display_type), DataOption(self.model._ivm, explicit=True, rois=True), del_btn, key=struc["name"])
            data_opt.value = struc.get("pvmap", None)

        res_opts = options.get("resampling", {})
        self.gui.add("Resampling", ChoiceOption(["Downsample", "From another data set", "Specified resolution"], ["down", "data", "res"], 
                     default=res_opts.get("type", "res")), checked=True, enabled="type" in res_opts, key="type")
        self.gui.add("Output space from", DataOption(self.ivm), key="grid")
        self.gui.add("Output resample factor", NumericOption(intonly=True, minval=1, maxval=10, default=2), key="factor")
        self.gui.add("Voxel sizes (mm)", NumberListOption(load_btn=False), key="voxel-sizes")
        for opt in ("grid", "factor", "voxel-sizes"):
            if opt in res_opts:
                self.gui.option(opt).value = res_opts[opt]

        self.gui.add(None, RunButton("Add user-defined structure", callback=self._add_embedding), key="add_embedding")
        self._update_resamp_visibility()

    def _update_resamp_visibility(self):
        resample_type = self.gui.values().pop("type", "")
        self.gui.set_visible("grid", resample_type == "data")
        self.gui.set_visible("factor", resample_type in ("up", "down"))
        #self.gui.set_visible("order", resample_type != "down")
        #self.gui.set_visible("2d", resample_type in ("up", "down"))
        self.gui.set_visible("voxel-sizes", resample_type == "res")

    def _update_options(self):
        self._update_resamp_visibility()
        opts = self.gui.values()
        pvmaps = {}
        for struc in self.model.default_strucs:
            if struc.name in opts:
                pvmaps[struc.name] = opts[struc.name]
        self.model.options["pvmaps"] = pvmaps

        resamp = {}
        for resamp_opt in ("type", "grid", "factor", "voxel-sizes"):
            if resamp_opt in opts:
                resamp[resamp_opt] = opts[resamp_opt]
        self.model.options["resampling"] = resamp

    def _struc_delete_btn(self, add_struc):
        def _del_cb():
            self._del_struc(add_struc["name"])

        btn = QtGui.QPushButton("Delete")
        btn.clicked.connect(_del_cb)
        return btn

    def _del_struc(self, name):
        self.model.options["additional"].pop(name, None)
        self._refresh_gui()

    def _add_embedding(self):
        dialog = AddEmbeddingDialog(self.gui, self.model._ivm, self.model.default_strucs)
        try:
            accept = dialog.exec_()
        except:
            import traceback
            traceback.print_exc()
        if accept:
            self.model.options["additional"][dialog.name] = {
                "name" : dialog.name,
                "struc_type" : dialog.struc_type,
                "parent_struc" : dialog.parent_struc,
                "pvmap" : dialog.pvmap,
            }
            if dialog.region:
                self.model.options["additional"][dialog.name]["region"] = dialog.region
            if dialog.sigma:
                self.model.options["additional"][dialog.name]["sigma"] = dialog.sigma

            self._refresh_gui()