예제 #1
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)
예제 #2
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)
예제 #3
0
class ResampleDataWidget(QpWidget):
    """
    Widget that lets you resample data onto a different grid
    """
    def __init__(self, **kwargs):
        super(ResampleDataWidget,
              self).__init__(name="Resample Data",
                             icon="resample.png",
                             desc="Resample data onto a different grid",
                             group="Utilities",
                             **kwargs)

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

        vbox.addWidget(TitleWidget(self))

        self.optbox = OptionBox("Resampling options")
        self.data = self.optbox.add("Data to resample",
                                    DataOption(self.ivm),
                                    key="data")
        self.resample_type = self.optbox.add(
            "Resampling method",
            ChoiceOption(
                ["On to grid from another data set", "Upsample", "Downsample"],
                ["data", "up", "down"]),
            key="type")
        self.grid_data = self.optbox.add("Use grid from",
                                         DataOption(self.ivm),
                                         key="grid")
        self.factor = self.optbox.add("Factor",
                                      NumericOption(default=2,
                                                    minval=2,
                                                    maxval=10,
                                                    intonly=True),
                                      key="factor")
        self.slicewise = self.optbox.add("2D only", BoolOption(), key="2d")
        self.order = self.optbox.add(
            "Interpolation",
            ChoiceOption(["Nearest neighbour", "Linear", "Quadratic", "Cubic"],
                         [0, 1, 2, 3],
                         default=1),
            key="order")
        self.output_name = self.optbox.add("Output name",
                                           OutputNameOption(src_data=self.data,
                                                            suffix="_res"),
                                           key="output-name")
        vbox.addWidget(self.optbox)
        self.resample_type.sig_changed.connect(self._resample_type_changed)

        self.run = RunButton("Resample", self._run)
        vbox.addWidget(self.run)
        vbox.addStretch(1)

        self._resample_type_changed()

    def _resample_type_changed(self):
        resample_type = self.resample_type.value
        self.optbox.set_visible("grid", resample_type == "data")
        self.optbox.set_visible("factor", resample_type != "data")
        self.optbox.set_visible("order", resample_type != "down")
        self.optbox.set_visible("2d", resample_type != "data")

    def batch_options(self):
        options = self.optbox.values()
        return "Resample", options

    def _run(self):
        _, options = self.batch_options()
        ResampleProcess(self.ivm).run(options)
예제 #4
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,
        }
예제 #5
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)
예제 #6
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
예제 #7
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
예제 #8
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}
예제 #9
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}
예제 #10
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")
예제 #11
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())
예제 #12
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
예제 #13
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)
예제 #14
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
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()