示例#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
class CheckerboardModelView:
    """
    View for CheckerboardModel
    """
    def __init__(self, ivm):
        self.model = CheckerboardModel(ivm)
        self.gui = OptionBox()
        self.gui.add("Number of voxels per patch (approx)", NumericOption(minval=1, maxval=1000, default=20, intonly=True), key="voxels-per-patch")
        self.gui.sig_changed.connect(self._update_options)
        
    def _update_options(self):
        self.model.options.update(self.gui.values())
class FastStructureModelView:
    """
    View for FastStructureModel
    """
    def __init__(self, ivm):
        self.model = FastStructureModel(ivm)
        self.gui = OptionBox()
        self.gui.add("Structural image (brain extracted)", DataOption(self.model._ivm, explicit=True), key="struc")
        self.gui.add("Image type", ChoiceOption(["T1 weighted", "T2 weighted", "Proton Density"], return_values=[1, 2, 3]), key="type")
        self.gui.sig_changed.connect(self._update_options)
        
    def _update_options(self):
        self.model.options.update(self.gui.values())
示例#4
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
示例#5
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()
示例#6
0
class NoiseOptions(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("Add noise with SNR",
                         NumericOption(minval=0.1, maxval=100, default=10),
                         checked=True,
                         key="snr")
        self.options.sig_changed.connect(self.sig_changed.emit)
        main_vbox.addWidget(self.options)

        main_vbox.addStretch(1)
示例#7
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)
示例#8
0
class AddNoiseWidget(QpWidget):
    """
    Add noise to data
    """
    def __init__(self, **kwargs):
        super(AddNoiseWidget,
              self).__init__(name="Add noise",
                             icon="noise",
                             desc="Add random noise to a data set",
                             group="Simulation",
                             **kwargs)

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

        title = TitleWidget(self, title="Add Noise", help="noise")
        vbox.addWidget(title)

        self.option_box = OptionBox("Options")
        data = self.option_box.add("Data set",
                                   DataOption(self.ivm),
                                   key="data")
        self.option_box.add("Gaussian standard deviation",
                            NumericOption(minval=0, maxval=100, default=50),
                            key="std")
        self.option_box.add("Output name",
                            OutputNameOption(src_data=data, suffix="_noisy"),
                            key="output-name")
        vbox.addWidget(self.option_box)

        run_btn = QtGui.QPushButton('Run', self)
        run_btn.clicked.connect(self.run)
        vbox.addWidget(run_btn)

        vbox.addStretch(1)

    def batch_options(self):
        return "AddNoise", self.option_box.values()

    def run(self):
        options = self.batch_options()[1]
        process = AddNoiseProcess(self.ivm)
        process.execute(options)
示例#9
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,
            }
        }
示例#10
0
class SimMotionWidget(QpWidget):
    """
    Widget to simulate random motion on a 4D data set
    """
    def __init__(self, **kwargs):
        super(SimMotionWidget,
              self).__init__(name="Simulate motion",
                             icon="reg",
                             desc="Simulate random motion on a 4D data set",
                             group="Simulation",
                             **kwargs)

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

        title = TitleWidget(self, title="Simulate Motion", help="sim_motion")
        vbox.addWidget(title)

        self.option_box = OptionBox("Options")
        data = self.option_box.add("Data set",
                                   DataOption(self.ivm,
                                              include_4d=True,
                                              include_3d=False),
                                   key="data")
        self.option_box.add("Motion standard deviation (mm)",
                            NumericOption(minval=0,
                                          maxval=5,
                                          default=1,
                                          decimals=2),
                            key="std")
        self.option_box.add("Padding (mm)",
                            NumericOption(minval=0,
                                          maxval=10,
                                          default=5,
                                          decimals=1),
                            key="padding",
                            checked=True)
        self.option_box.add("Output name",
                            OutputNameOption(src_data=data, suffix="_moving"),
                            key="output-name")
        vbox.addWidget(self.option_box)

        run_btn = QtGui.QPushButton('Run', self)
        run_btn.clicked.connect(self.run)
        vbox.addWidget(run_btn)

        vbox.addStretch(1)

    def batch_options(self):
        return "SimMotion", self.option_box.values()

    def run(self):
        options = self.batch_options()[1]
        process = SimMotionProcess(self.ivm)
        process.execute(options)
示例#11
0
    def init_ui(self):
        vbox = QtGui.QVBoxLayout()
        self.setLayout(vbox)

        vbox.addWidget(TitleWidget(self))

        optbox = OptionBox("Resampling options")
        self.data = optbox.add("Data to resample", DataOption(self.ivm))
        self.grid_data = optbox.add("Resample onto grid from",
                                    DataOption(self.ivm))
        self.order = optbox.add(
            "Interpolation",
            ChoiceOption(["Nearest neighbour", "Linear", "Quadratic", "Cubic"],
                         [0, 1, 2, 3]))
        self.output_name = optbox.add(
            "Output name", OutputNameOption(src_data=self.data, suffix="_res"))
        vbox.addWidget(optbox)

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

        vbox.addStretch(1)
示例#12
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)
示例#13
0
class DscDataModelView:
    def __init__(self, ivm):
        self.model = DscDataModel(ivm)
        self.gui = OptionBox()
        self.gui.add("Time between volumes (s)",
                     NumericOption(minval=0, maxval=5, default=1.0),
                     key="delt")
        self.gui.add("TE (s)",
                     NumericOption(minval=0, maxval=5, default=1.0),
                     key="te")
        self.gui.add("AIF", NumberListOption(), key="aif")
        self.gui.sig_changed.connect(self._update_options)
        self._update_options()

    def _update_options(self):
        self.model.options.update(self.gui.values())
示例#14
0
class SpinEchoDataModelView:
    def __init__(self, ivm):
        self.model = SpinEchoDataModel(ivm)
        self.gui = OptionBox()
        self.gui.add("TR (s)",
                     NumericOption(minval=0, maxval=10, default=4.8),
                     key="tr")
        self.gui.add("TE (ms)",
                     NumericOption(minval=0, maxval=1000, default=0),
                     key="te")
        self.gui.add("M0",
                     NumericOption(minval=0, maxval=10000, default=1000),
                     key="m0")
        self.gui.sig_changed.connect(self._update_options)
        self._update_options()

    def _update_options(self):
        self.model.options.update(self.gui.values())
示例#15
0
class OutputOptions(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("Output name", TextOption("sim_data"), key="output")
        self.options.add("Output parameter maps",
                         BoolOption(),
                         default=False,
                         key="output-param-maps")
        self.options.add("Output clean data (no noise/motion)",
                         TextOption("sim_data_clean"),
                         checked=True,
                         default=True,
                         key="output-clean")
        main_vbox.addWidget(self.options)
        main_vbox.addStretch(1)
示例#16
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,
        }
示例#17
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)
示例#18
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)
示例#19
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}
示例#20
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
示例#21
0
class SimData(FabberWidget):
    """
    Widget which uses Fabber models to generate simulated data
    """
    def __init__(self, **kwargs):
        super(SimData, self).__init__(name="Simulated Fabber Data", icon="fabber", 
                                      desc="Generate test data sets from Fabber models", 
                                      group="Simulation", **kwargs)
        self._param_test_values = {}

    def init_ui(self):
        FabberWidget.init_ui(self)
        
        self.param_values_box = OptionBox("Parameter values")
        self.param_values_box.sig_changed.connect(self._param_values_changed)
        self.vbox.addWidget(self.param_values_box)

        run_btn = QtGui.QPushButton('Generate test data', self)
        run_btn.clicked.connect(self._run)
        self.vbox.addWidget(run_btn)
        
        self.vbox.addStretch(1)

        model_opts_btn = QtGui.QPushButton('Model Options')
        model_opts_btn.clicked.connect(self._show_model_options)

        self.options.add("Model group", ChoiceOption(), key="model-group")
        self.options.add("Model", ChoiceOption(), model_opts_btn, key="model")
        self.options.add("Number of volumes (time points)", NumericOption(intonly=True, minval=1, maxval=100, default=10), key="num-vols")
        self.options.add("Voxels per patch (approx)", NumericOption(intonly=True, minval=1, maxval=10000, default=1000), key="num-voxels")
        self.options.add("Noise (Gaussian std.dev)", NumericOption(intonly=True, minval=0, maxval=1000, default=0), key="noise")
        self.options.add("Output data name", OutputNameOption(initial="fabber_test_data"), key="output-name")
        self.options.add("Output noise-free data", BoolOption(), key="save-clean")
        self.options.add("Output parameter ROIs", BoolOption(), key="save-rois")
        self.options.option("model-group").sig_changed.connect(self._model_group_changed)

        model_groups = ["ALL"]
        for group in FabberProcess.api().get_model_groups():
            model_groups.append(group.upper())
        self.options.option("model-group").setChoices(model_groups)
        self.options.option("model-group").value = "ALL"
        self._model_group_changed()

        self.options.option("model").value = "poly"
        self._options_changed()

        # Start with something sensible for the polynomial model
        self._param_test_values = {"c0" : [-100, 0, 100], "c1" : [-10, 0, 10], "c2" : [-1, 0, 1]}
        self._update_params()
 
    def _update_params(self):
        FabberWidget._update_params(self)
        self.param_values_box.clear()
        for param in self._fabber_params:
            current_values = self._param_test_values.get(param, [1.0])
            self.param_values_box.add(param, NumberListOption(initial=current_values))
            self._param_test_values[param] = current_values

        # Remove references to parameters which no longer exist
        for param in list(self._param_test_values.keys()):
            if param not in self._fabber_params:
                del self._param_test_values[param]

    def _param_values_changed(self):
        self._param_test_values = self.param_values_box.values()
        num_variable = len([1 for v in self._param_test_values.values() if len(v) > 1])
        if num_variable > 3:
            self.warn("Cannot have more than 3 varying parameters")
        
    def get_options(self):
        """ Return a copy of current Fabber options and parameter test values """
        options = dict(self._fabber_options)
        options["param-test-values"] = self._param_test_values
        return options
        
    def _run(self):
        process = self.get_process()
        options = self.get_options()
        process.run(options)
        
    def get_process(self):
        return FabberTestDataProcess(self.ivm)
  
    def batch_options(self):
        return "FabberTestData", self.get_options()
示例#22
0
class FlirtRegMethod(RegMethod):
    """
    FLIRT/MCFLIRT registration method
    """
    def __init__(self, ivm):
        RegMethod.__init__(self, "flirt", ivm, "FLIRT/MCFLIRT")
        self.options_widget = None
        self.cost_models = [
            "Mutual information", "Woods", "Correlation ratio",
            "Normalized correlation", "Normalized mutual information",
            "Least squares"
        ]
        self.cost_model_options = [
            "mutualinfo", "woods", "corratio", "normcorr", "normmi", "leastsq"
        ]

    @classmethod
    def apply_transform(cls, reg_data, transform, options, queue):
        """
        Apply a previously calculated transformation to a data set

        We are not actually using FSL applyxfm for this although it would be
        an alternative option for the reference space output option. Instead
        we perform a non-lossy affine transformation and then resample onto
        the reference or registration spaces as required.
        """
        log = "Performing non-lossy affine transformation\n"
        order = options.pop("interp-order", 1)
        affine = transform.voxel_to_world(reg_data.grid)
        grid = DataGrid(reg_data.grid.shape, affine)
        qpdata = NumpyData(reg_data.raw(), grid=grid, name=reg_data.name)

        output_space = options.pop("output-space", "ref")
        if output_space == "ref":
            qpdata = qpdata.resample(transform.ref_grid,
                                     suffix="",
                                     order=order)
            log += "Resampling onto reference grid\n"
        elif output_space == "reg":
            qpdata = qpdata.resample(transform.reg_grid,
                                     suffix="",
                                     order=order)
            log += "Resampling onto input grid\n"

        return qpdata, log

    @classmethod
    def reg_3d(cls, reg_data, ref_data, options, queue):
        """
        Static function for performing 3D registration
        """
        from fsl import wrappers as fsl
        reg = qpdata_to_fslimage(reg_data)
        ref = qpdata_to_fslimage(ref_data)

        set_environ(options)

        output_space = options.pop("output-space", "ref")
        interp = _interp(options.pop("interp-order", 1))
        twod = reg_data.grid.shape[2] == 1
        logstream = six.StringIO()
        flirt_output = fsl.flirt(reg,
                                 ref,
                                 interp=interp,
                                 out=fsl.LOAD,
                                 omat=fsl.LOAD,
                                 twod=twod,
                                 log={
                                     "cmd": logstream,
                                     "stdout": logstream,
                                     "stderr": logstream
                                 },
                                 **options)
        transform = FlirtTransform(ref_data.grid,
                                   flirt_output["omat"],
                                   name="flirt_xfm")

        if output_space == "ref":
            qpdata = fslimage_to_qpdata(flirt_output["out"], reg_data.name)
        elif output_space == "reg":
            qpdata = fslimage_to_qpdata(flirt_output["out"],
                                        reg_data.name).resample(reg_data.grid,
                                                                suffix="")
            qpdata.name = reg_data.name
        elif output_space == "trans":
            trans_affine = transform.voxel_to_world(reg_data.grid)
            trans_grid = DataGrid(reg_data.grid.shape, trans_affine)
            qpdata = NumpyData(reg_data.raw(),
                               grid=trans_grid,
                               name=reg_data.name)

        return qpdata, transform, logstream.getvalue()

    @classmethod
    def moco(cls, moco_data, ref, options, queue):
        """
        Motion correction
        
        We use MCFLIRT to implement this
        
        :param moco_data: A single 4D QpData instance containing data to motion correct.
        :param ref: Either 3D QpData containing reference data, or integer giving 
                    the volume index of ``moco_data`` to use
        :param options: Method options as dictionary
        :param queue: Queue object which method may put progress information on to. Progress 
                      should be given as a number between 0 and 1.
        
        :return Tuple of three items. 
        
                First, motion corrected data as 4D QpData in the same space as ``moco_data``
        
                Second, if options contains ``output-transform : True``, sequence of transformations
                found, one for each volume in ``reg_data``. Each is either an affine matrix transformation 
                or a sequence of 3 warp images, the same shape as ``regdata`` If ``output-transform`` 
                is not given, returns None instead.

                Third, log information from the registration as a string.
        """
        from fsl import wrappers as fsl
        if moco_data.ndim != 4:
            raise QpException("Cannot motion correct 3D data")

        set_environ(options)

        reg = qpdata_to_fslimage(moco_data)

        if isinstance(ref, int):
            options["refvol"] = ref
            ref_grid = moco_data.grid
        elif isinstance(ref, QpData):
            options["reffile"] = qpdata_to_fslimage(ref)
            ref_grid = ref.grid
        else:
            raise QpException("invalid reference object type: %s" % type(ref))

        interp = _interp(options.pop("interp-order", 1))  # FIXME ignored
        twod = moco_data.grid.shape[2] == 1
        logstream = six.StringIO()
        result = fsl.mcflirt(reg,
                             out=fsl.LOAD,
                             mats=fsl.LOAD,
                             twod=twod,
                             log={
                                 "cmd": logstream,
                                 "stdout": logstream,
                                 "stderr": logstream
                             },
                             **options)
        qpdata = fslimage_to_qpdata(result["out"], moco_data.name)
        transforms = [
            FlirtTransform(ref_grid,
                           result[os.path.join("out.mat", "MAT_%04i" % vol)])
            for vol in range(moco_data.nvols)
        ]

        return qpdata, transforms, logstream.getvalue()

    def interface(self, generic_options=None):
        """
        :return: QWidget containing registration options
        """
        if generic_options is None:
            generic_options = {}

        if self.options_widget is None:
            self.options_widget = QtGui.QWidget()
            vbox = QtGui.QVBoxLayout()
            self.options_widget.setLayout(vbox)

            cite = Citation(CITE_TITLE, CITE_AUTHOR, CITE_JOURNAL)
            vbox.addWidget(cite)

            self.optbox = OptionBox()
            self.optbox.add("Cost Model",
                            ChoiceOption(self.cost_models,
                                         self.cost_model_options,
                                         default="normcorr"),
                            key="cost")
            #self.optbox.add("Number of search stages", ChoiceOption([1, 2, 3, 4]), key="nstages")
            #self.optbox.option("stages").value = 2
            #self.optbox.add("Final stage interpolation", ChoiceOption(["None", "Sinc", "Spline", "Nearest neighbour"], ["", "sinc_final", "spline_final", "nn_final"]), key="final")
            #self.optbox.add("Field of view (mm)", NumericOption(minval1, maxval=100, default=20), key="fov")
            self.optbox.add("Number of bins",
                            NumericOption(intonly=True,
                                          minval=1,
                                          maxval=1000,
                                          default=256),
                            key="bins")
            self.optbox.add("Degrees of freedom",
                            ChoiceOption([6, 9, 12]),
                            key="dof")
            #self.optbox.add("Scaling", NumericOption(minval=0.1, maxval=10, default=6), key="scaling")
            #self.optbox.add("Smoothing in cost function", NumericOption(minval=0.1, maxval=10, default=1), key="smoothing")
            #self.optbox.add("Scaling factor for rotation\noptimization tolerances", NumericOption(minval=0.1, maxval=10, default=1), key="rotscale")
            #self.optbox.add("Search on gradient images", BoolOption, key="grad")

            vbox.addWidget(self.optbox)
        return self.options_widget

    def options(self):
        """
        :return: Dictionary of registration options selected
        """
        self.interface()
        opts = self.optbox.values()
        for env_copy in ["FSLOUTPUTTYPE", "FSLDIR", "FSLDEVDIR"]:
            if env_copy in os.environ:
                opts[env_copy] = os.environ[env_copy]
            else:
                self.debug("%s is not in environment" % env_copy)

        for key, value in opts.items():
            self.debug("%s: %s", key, value)
        return opts
示例#23
0
class OrientDataWidget(QpWidget):
    """
    Widget that lets you tweak the orientation of data
    """
    def __init__(self, **kwargs):
        super(OrientDataWidget,
              self).__init__(name="Orient Data",
                             icon="inspect.png",
                             desc="Manipulate data orientation",
                             group="Utilities",
                             **kwargs)
        self._transform_cache = {}
        self.ivm.sig_all_data.connect(self._all_data_changed)

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

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

        hbox = QtGui.QHBoxLayout()
        self.options = OptionBox("Re-orient data")
        data = self.options.add("Data item", DataOption(self.ivm), key="data")
        data.sig_changed.connect(self._data_changed)
        self.trans, self.rot = {}, {}
        self.options.add("Translation")
        for axis, label in {2: "axial", 0: "sagittal", 1: "coronal"}.items():
            trans = self.options.add("  %s (mm)" % label.title(),
                                     NumericOption(minval=-100,
                                                   maxval=100,
                                                   default=0),
                                     key="trans-%s" % label)
            trans.sig_changed.connect(self._translate(axis, label))
            self.trans[axis] = trans
        self.options.add("Rotation")
        for axis, label in {2: "axial", 0: "sagittal", 1: "coronal"}.items():
            rot = self.options.add("  %s (degrees)" % label.title(),
                                   NumericOption(minval=-180,
                                                 maxval=180,
                                                 default=0),
                                   key="rot-%s" % label)
            rot.sig_changed.connect(self._rotate(axis, label))
            self.rot[axis] = rot
        hbox.addWidget(self.options)
        vbox.addLayout(hbox)

        self.gridview = GridView(self.ivm, self.ivl)
        vbox.addWidget(self.gridview)

        hbox = QtGui.QHBoxLayout()
        reset_btn = QtGui.QPushButton("Reset to original")
        reset_btn.clicked.connect(self._reset)
        hbox.addWidget(reset_btn)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addStretch(1)

    def activate(self):
        self._data_changed()

    def _all_data_changed(self, data):
        for name in list(self._transform_cache.keys()):
            if name not in data:
                del self._transform_cache[name]

    def _data_changed(self):
        name = self.options.values()["data"]
        qpdata = self.ivm.data.get(name, self.ivm.rois.get(name, None))
        self.gridview.set_data(qpdata)
        if qpdata is not None:
            if name not in self._transform_cache:
                self._transform_cache[name] = ([0, 0, 0], [0, 0, 0])
            translation, rotations = self._transform_cache[name]
            for axis in range(3):
                self.trans[axis].value = translation[axis]
                self.rot[axis].value = rotations[axis]
            self._set()

    def _translate(self, axis, label):
        def _trans():
            name = self.gridview.data.name
            trans = self.options.values()["trans-%s" % label]
            self._transform_cache[name][0][axis] = trans
            self._set()

        return _trans

    def _rotate(self, axis, label):
        def _rot():
            name = self.gridview.data.name
            angle = self.options.values()["rot-%s" % label]
            if axis == 1: angle = -angle
            self._transform_cache[name][1][axis] = angle
            self._set()

        return _rot

    def _reset(self):
        name = self.gridview.data.name
        del self._transform_cache[name]
        self._data_changed()

    def _set(self):
        name = self.gridview.data.name
        affine = self.gridview.data.grid.affine_orig
        grid_centre = [float(dim) / 2 for dim in self.gridview.data.grid.shape]
        world_centre = np.dot(affine[:3, :3], grid_centre)
        self.debug("Initial affine\n%s", affine)
        translation, rotations = self._transform_cache[name]

        R = np.identity(3)
        for axis in range(3):
            angle = rotations[axis]
            rot3d = self._rotmtx_3d(axis, angle)
            affine[:3, :3] = np.dot(rot3d, affine[:3, :3])
            R = np.dot(rot3d, R)

        origin_offset = world_centre - np.dot(R, world_centre)
        origin_offset += translation
        self.debug("Origin offset\n%s", origin_offset)
        affine[:3, 3] += origin_offset

        self.debug("Final affine\n%s", affine)
        self.gridview.data.grid.affine = affine
        self.gridview.update()
        if self.gridview.data == self.ivm.main:
            self.ivm.sig_main_data.emit(self.ivm.main)
        if self.gridview.data.view.visible == Visibility.SHOW or self.gridview.data == self.ivm.main:
            self.ivl.redraw()

    def _rotmtx_3d(self, axis, angle):
        # FIXME this is not quite right when rotating in a plane where
        # the basis vectors have different lengths
        c, s = math.cos(math.radians(angle)), math.sin(math.radians(angle))
        rot2d = np.array([[c, -s], [s, c]])
        rot3d = np.identity(3)
        if axis == 0:
            rot3d[1:, 1:] = rot2d
        elif axis == 1:
            rot3d[0, 0] = rot2d[0, 0]
            rot3d[0, 2] = rot2d[0, 1]
            rot3d[2, 0] = rot2d[1, 0]
            rot3d[2, 2] = rot2d[1, 1]
        elif axis == 2:
            rot3d[:2, :2] = rot2d
        self.debug("3d rotation matrix: %i %f", axis, angle)
        self.debug("\n%s", rot3d)
        return rot3d
示例#24
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])))
示例#25
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}
示例#26
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
示例#27
0
class FnirtRegMethod(RegMethod):
    """
    FNIRT registration method
    """
    def __init__(self, ivm):
        RegMethod.__init__(self, "fnirt", ivm, display_name="FNIRT")
        self.options_widget = None

    @classmethod
    def apply_transform(cls, reg_data, transform, options, queue):
        """
        Apply a previously calculated transformation to a data set
        """
        output_space = options.pop("output-space", "ref")
        if output_space not in ("ref", "reg"):
            raise QpException(
                "FNIRT does not support output in transformed space")

        from fsl import wrappers as fsl
        reg = qpdata_to_fslimage(reg_data)
        trans = qpdata_to_fslimage(transform)

        # Applywarp generates an output for each volume of reference image
        # for some reason. So use just the first volume of the transform
        # as the reference space
        ref = qpdata_to_fslimage(transform.volume(0, qpdata=True))

        log = six.StringIO()
        order = options.pop("interp-order", 1)
        interp = _interp(order)
        apply_output = fsl.applywarp(reg,
                                     ref,
                                     interp=interp,
                                     paddingsize=1,
                                     super=True,
                                     superlevel="a",
                                     out=fsl.LOAD,
                                     log={
                                         "cmd": log,
                                         "stdout": log,
                                         "stderr": log
                                     },
                                     warp=trans,
                                     rel=True)
        qpdata = fslimage_to_qpdata(apply_output["out"], name=reg_data.name)

        if output_space == "ref":
            # Default is to output in reference space
            pass
        else:
            qpdata = qpdata.resample(reg_data.grid, suffix="", order=order)
            log += "Resampling onto input grid\n"

        return qpdata, log.getvalue()

    @classmethod
    def reg_3d(cls, reg_data, ref_data, options, queue):
        """
        Static function for performing 3D registration

        FIXME return jacobian as part of xform?
        """
        output_space = options.pop("output-space", "ref")
        if output_space not in ("ref", "reg"):
            raise QpException(
                "FNIRT does not support output in transformed space")

        from fsl import wrappers as fsl
        reg = qpdata_to_fslimage(reg_data)
        ref = qpdata_to_fslimage(ref_data)

        log = six.StringIO()
        fnirt_output = fsl.fnirt(reg,
                                 ref=ref,
                                 iout=fsl.LOAD,
                                 fout=fsl.LOAD,
                                 log={
                                     "cmd": log,
                                     "stdout": log,
                                     "stderr": log
                                 },
                                 **options)
        transform = fslimage_to_qpdata(fnirt_output["fout"], name="fnirt_warp")
        transform.metadata["QpReg"] = "FNIRT"

        if output_space == "ref":
            qpdata = fslimage_to_qpdata(fnirt_output["iout"],
                                        name=reg_data.name)
        else:
            qpdata = fslimage_to_qpdata(fnirt_output["iout"],
                                        name=reg_data.name).resample(
                                            reg_data.grid, suffix="")

        return qpdata, transform, log.getvalue()

    def interface(self, generic_options=None):
        if generic_options is None:
            generic_options = {}

        if self.options_widget is None:
            self.options_widget = QtGui.QWidget()
            vbox = QtGui.QVBoxLayout()
            self.options_widget.setLayout(vbox)

            cite = Citation(CITE_TITLE, CITE_AUTHOR, CITE_JOURNAL)
            vbox.addWidget(cite)

            self.optbox = OptionBox()
            self.optbox.add("Mask for registration data",
                            DataOption(self.ivm, rois=True, data=False),
                            key="inmask",
                            checked=True)
            self.optbox.add("Mask for reference data",
                            DataOption(self.ivm, rois=True, data=False),
                            key="refmask",
                            checked=True)
            self.optbox.add("Spline order",
                            ChoiceOption([2, 3]),
                            key="splineorder",
                            checked=True)
            self.optbox.add("Use pre-defined configuration",
                            ChoiceOption(
                                ["T1_2_MNI152_2mm", "FA_2_FMRIB58_1mm"]),
                            key="config",
                            checked=True)
            vbox.addWidget(self.optbox)

        return self.options_widget

    def options(self):
        self.interface()
        return self.optbox.values()
示例#28
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")
示例#29
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)
示例#30
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)