Ejemplo n.º 1
0
class ChooseDataDialog(QtGui.QDialog):
    def __init__(self, parent, ivm, used=[]):
        super(ChooseDataDialog, self).__init__(parent)
        self.setWindowTitle("Add VFA data set")
        vbox = QtGui.QVBoxLayout()

        grid = QtGui.QGridLayout()
        grid.addWidget(QtGui.QLabel("Data set"), 0, 0)
        self.data_combo = OverlayCombo(ivm, static_only=True)

        # Default to first data which hasn't been used
        data = [
            i for i in range(self.data_combo.count())
            if self.data_combo.itemText(i) not in used
        ]
        if len(data) > 0: self.data_combo.setCurrentIndex(data[0])

        self.data_combo.currentIndexChanged.connect(self._guess_fa)
        grid.addWidget(self.data_combo, 0, 1)

        grid.addWidget(QtGui.QLabel("Flip angle (\N{DEGREE SIGN})"), 1, 0)
        self.fa_edit = QtGui.QLineEdit()
        self.fa_edit.textChanged.connect(self._validate)
        grid.addWidget(self.fa_edit, 1, 1)

        vbox.addLayout(grid)

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

        self.setLayout(vbox)
        self._guess_fa()
        self._validate()

    def _guess_fa(self):
        name = self.data_combo.currentText()
        m = re.search(r".*?(\d+).*$", name)
        if m is not None:
            guess = m.group(1)
        else:
            guess = ""
        self.fa_edit.setText(guess)

    def _validate(self):
        valid = True
        try:
            fa = float(self.fa_edit.text())
            self.fa_edit.setStyleSheet("")
        except:
            self.fa_edit.setStyleSheet("QLineEdit {background-color: red}")
            valid = False
        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(valid)
class ImageOptionWidget(OptionWidget):
    """
    OptionWidget subclass which allows image options to be chosen
    from the current list of overlays
    """
    def __init__(self, opt, **kwargs):
        OptionWidget.__init__(self, opt, **kwargs)
        self.combo = OverlayCombo(self.ivm, static_only=True)
        self.widgets.append(self.combo)

    def get_value(self):
        return self.combo.currentText()

    def set_value(self, value):
        idx = self.combo.findText(value)
        self.combo.setCurrentIndex(idx)

    def add(self, grid, row):
        OptionWidget.add(self, grid, row)
        grid.addWidget(self.combo, row, 1)
Ejemplo n.º 3
0
class SmoothingWidget(QpWidget):
    """
    Gaussian smoothing widget
    """
    def __init__(self, **kwargs):
        super(SmoothingWidget, self).__init__(name="Smoothing",
                                              icon="smooth.png",
                                              desc="Gaussian smoothing",
                                              group="Processing",
                                              **kwargs)

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

        title = TitleWidget(self,
                            title="Data Smoothing",
                            subtitle="Smooth data using a Gaussian kernel",
                            help="smoothing")
        vbox.addWidget(title)

        hbox = QtGui.QHBoxLayout()
        gbox = QtGui.QGroupBox()
        gbox.setTitle("Options")
        grid = QtGui.QGridLayout()
        gbox.setLayout(grid)

        grid.addWidget(QtGui.QLabel("Data to smooth"), 0, 0)
        self.data_combo = OverlayCombo(self.ivm)
        self.data_combo.currentIndexChanged.connect(self.data_changed)
        grid.addWidget(self.data_combo, 0, 1)
        self.sigma = NumericOption("Sigma (mm)",
                                   grid,
                                   xpos=0,
                                   ypos=1,
                                   minval=0,
                                   step=0.1,
                                   default=1.0)
        grid.addWidget(QtGui.QLabel("Output name"), 2, 0)
        self.output_name = QtGui.QLineEdit()
        grid.addWidget(self.output_name, 2, 1)

        hbox.addWidget(gbox)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        self.run_btn = QtGui.QPushButton("Run")
        self.run_btn.clicked.connect(self.run)
        hbox.addWidget(self.run_btn)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addStretch(1)
        self.data_changed()

    def data_changed(self):
        self.output_name.setText("%s_smoothed" % self.data_combo.currentText())
        self.run_btn.setEnabled(self.data_combo.currentText() in self.ivm.data)

    def batch_options(self):
        return "Smooth", {
            "data": self.data_combo.currentText(),
            "sigma": self.sigma.spin.value(),
            "output-name": self.output_name.text()
        }

    def run(self):
        process = SmoothingProcess(self.ivm)
        process.run(self.batch_options()[1])
Ejemplo n.º 4
0
class CompareDataWidget(QpWidget):
    """
    Compare two data sets
    """
    def __init__(self, **kwargs):
        QpWidget.__init__(self,
                          name="Compare Data",
                          icon="compare.png",
                          desc="Compare two data sets",
                          group="Visualisation",
                          **kwargs)

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

        title = TitleWidget(self, batch_btn=False, help="compare")
        vbox.addWidget(title)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(QtGui.QLabel("Compare "))
        self.d1_combo = OverlayCombo(self.ivm)
        self.d1_combo.currentIndexChanged.connect(self._update_data)
        hbox.addWidget(self.d1_combo)
        hbox.addWidget(QtGui.QLabel(" with "))
        self.d2_combo = OverlayCombo(self.ivm)
        self.d2_combo.currentIndexChanged.connect(self._update_data)
        hbox.addWidget(self.d2_combo)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(QtGui.QLabel("Within ROI "))
        self.roi_combo = OverlayCombo(self.ivm,
                                      rois=True,
                                      data=False,
                                      none_option=True)
        self.roi_combo.currentIndexChanged.connect(self._update_data)
        hbox.addWidget(self.roi_combo)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        gbox = QtGui.QGroupBox("Options")
        grid = QtGui.QGridLayout()
        gbox.setLayout(grid)

        self.id_cb = QtGui.QCheckBox("Include identity line")
        grid.addWidget(self.id_cb, 0, 0)
        self.sample_cb = QtGui.QCheckBox("Sample values")
        self.sample_cb.setChecked(True)
        self.sample_cb.stateChanged.connect(self._update_data)
        self.sample_cb.stateChanged.connect(self._update_gui)
        grid.addWidget(self.sample_cb, 1, 0)
        self.sample_spin = QtGui.QSpinBox()
        self.sample_spin.setMinimum(10)
        self.sample_spin.setMaximum(10000000)
        self.sample_spin.setSingleStep(100)
        self.sample_spin.setValue(1000)
        self.sample_spin.valueChanged.connect(self._update_data)
        grid.addWidget(self.sample_spin, 1, 1)
        self.warn_label = QtGui.QLabel(
            "WARNING: plotting all values may take a long time")
        self.warn_label.setStyleSheet("QLabel { color : red; }")
        self.warn_label.setVisible(False)
        grid.addWidget(self.warn_label, 2, 0)
        self.plot_mode = ChoiceOption("Plot mode",
                                      grid,
                                      3,
                                      choices=["Scatter", "Heat map"])
        self.plot_mode.combo.currentIndexChanged.connect(
            self._plot_mode_changed)
        self.bins = NumericOption("Bins",
                                  grid,
                                  3,
                                  xpos=2,
                                  minval=20,
                                  default=50,
                                  intonly=True)
        self.bins.label.setVisible(False)
        self.bins.spin.setVisible(False)
        self.run_btn = QtGui.QPushButton("Update")
        self.run_btn.clicked.connect(self._run)
        grid.addWidget(self.run_btn, 4, 0)
        hbox.addWidget(gbox)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        win = pg.GraphicsLayoutWidget()
        win.setBackground(background=None)
        self.plot = win.addPlot(enableAutoRange=True)
        self.plot.clear()  # Unsure why this is necessary
        hbox.addWidget(win)

        self.hist = pg.HistogramLUTWidget()
        self.hist.setVisible(False)
        self.hist.setBackground(None)
        self.hist.gradient.loadPreset("thermal")
        hbox.addWidget(self.hist)
        vbox.addLayout(hbox)

        self._update_gui()
        self._update_data()

    def _plot_mode_changed(self, idx):
        self.bins.label.setVisible(idx == 1)
        self.bins.spin.setVisible(idx == 1)
        self.sample_cb.setChecked(idx == 0)
        self._update_gui()
        self._update_data()

    def _update_gui(self):
        self.sample_spin.setEnabled(self.sample_cb.isChecked())

    def _update_data(self):
        name1 = self.d1_combo.currentText()
        name2 = self.d2_combo.currentText()
        roi_name = self.roi_combo.currentText()
        qpd1 = self.ivm.data.get(name1, None)
        qpd2 = self.ivm.data.get(name2, None)
        roi = self.ivm.rois.get(roi_name, None)

        if qpd1 is not None and qpd2 is not None:
            current_vol = self.ivl.focus()[3]
            d1 = qpd1.volume(current_vol)
            d2 = qpd2.resample(qpd1.grid).volume(current_vol)

            if roi is not None:
                roi_data = roi.resample(qpd1.grid).raw()
                d1 = d1[roi_data > 0]
                d2 = d2[roi_data > 0]
            else:
                d1 = d1.reshape(-1)
                d2 = d2.reshape(-1)

            if self.sample_cb.isChecked():
                n_samples = self.sample_spin.value()
                idx = np.random.choice(np.arange(len(d1)), n_samples)
                d1 = np.take(d1, idx)
                d2 = np.take(d2, idx)
                self.warn_label.setVisible(False)
            else:
                # Warn about plotting all data in scatter mode
                self.warn_label.setVisible(
                    self.plot_mode.combo.currentIndex() == 0)

            self.d1 = d1
            self.d2 = d2
            self.run_btn.setEnabled(True)
        else:
            self.run_btn.setEnabled(False)

    def _run(self):
        self.plot.clear()
        self.plot.setLabel('bottom', self.d1_combo.currentText())
        self.plot.setLabel('left', self.d2_combo.currentText())
        if self.plot_mode.combo.currentIndex() == 0:
            self.plot.setAspectLocked(False)
            self.hist.setVisible(False)
            self.plot.plot(self.d1,
                           self.d2,
                           pen=None,
                           symbolBrush=(200, 200, 200),
                           symbolPen='k',
                           symbolSize=5.0)
        else:
            heatmap, xedges, yedges = np.histogram2d(self.d1,
                                                     self.d2,
                                                     bins=self.bins.value())
            rect = QtCore.QRectF(xedges[0], yedges[0], xedges[-1] - xedges[0],
                                 yedges[-1] - yedges[0])
            img = pg.ImageItem(heatmap)
            img.setRect(rect)
            #self.plot.setAspectLocked(True)
            self.hist.setVisible(True)
            self.hist.setImageItem(img)
            self.plot.addItem(img)

        if self.id_cb.isChecked():
            real_min = max(self.d1.min(), self.d2.min())
            real_max = min(self.d1.max(), self.d2.max())
            pen = pg.mkPen((255, 255, 255), style=QtCore.Qt.DashLine)
            self.plot.plot([real_min, real_max], [real_min, real_max],
                           pen=pen,
                           width=2.0)
Ejemplo n.º 5
0
class PerfSlicWidget(QpWidget):
    """
    Generates supervoxels using SLIC method
    """
    def __init__(self, **kwargs):
        super(PerfSlicWidget, self).__init__(name="Super Voxels", icon="sv", 
                                             desc="Generate supervoxel clusters", 
                                             group="Clustering", **kwargs)
        
    def init_ui(self):
        layout = QtGui.QVBoxLayout()
        self.setLayout(layout)

        title = TitleWidget(self, "Supervoxel Generation", help="sv")
        layout.addWidget(title)
        
        cite = Citation(CITE_TITLE, CITE_AUTHOR, CITE_JOURNAL)
        layout.addWidget(cite)

        hbox = QtGui.QHBoxLayout()
        optbox = QtGui.QGroupBox()
        optbox.setTitle("Options")
        grid = QtGui.QGridLayout()
        optbox.setLayout(grid)
        
        grid.addWidget(QtGui.QLabel("Data"), 0, 0)
        self.ovl = OverlayCombo(self.ivm)
        self.ovl.currentIndexChanged.connect(self._data_changed)
        grid.addWidget(self.ovl, 0, 1)
        grid.addWidget(QtGui.QLabel("ROI"), 1, 0)
        self.roi = RoiCombo(self.ivm)
        grid.addWidget(self.roi, 1, 1)

        self.n_comp = NumericOption("Number of components", grid, 2, minval=1, maxval=3, default=3, intonly=True)
        self.compactness = NumericOption("Compactness", grid, 3, minval=0.01, maxval=1, step=0.05, default=0.1, intonly=False)
        self.sigma = NumericOption("Smoothing", grid, 4, minval=0, maxval=5, step=0.1, default=1, intonly=False)
        self.n_supervoxels = NumericOption("Number of supervoxels", grid, 5, minval=2, maxval=1000, default=20, intonly=True)

        grid.addWidget(QtGui.QLabel("Output name"), 6, 0)
        self.output_name = QtGui.QLineEdit("supervoxels")
        grid.addWidget(self.output_name, 6, 1)

        self.gen_btn = QtGui.QPushButton('Generate', self)
        self.gen_btn.clicked.connect(self._generate)
        grid.addWidget(self.gen_btn, 7, 0)
        hbox.addWidget(optbox)
        hbox.addStretch(1)
        layout.addLayout(hbox)

        layout.addStretch(1)

    def _data_changed(self, _):
        name = self.ovl.currentText()
        if name:
            ovl = self.ivm.data[name]
            self.n_comp.label.setVisible(ovl.nvols > 1)
            self.n_comp.spin.setVisible(ovl.nvols > 1)

    def batch_options(self):
        options = {
            "data" : self.ovl.currentText(),
            "roi" : self.roi.currentText(),
            "n-components" : self.n_comp.spin.value(),
            "compactness" : self.compactness.spin.value(),
            "sigma" : self.sigma.spin.value(),
            "n-supervoxels" :  self.n_supervoxels.spin.value(),
            "output-name" :  self.output_name.text() 
        }
        return "Supervoxels", options

    def _generate(self):
        process = SupervoxelsProcess(self.ivm, sync=True)
        process.run(self.batch_options()[1])
Ejemplo n.º 6
0
class AslImageWidget(QtGui.QWidget, LogSource):
    """
    QWidget which allows an ASL data set to be described

    This is intended to be embedded into a QpWidget which supports processing of ASL data
    """

    # Signal emitted when the data or metadata is changed
    sig_changed = QtCore.Signal()

    def __init__(self, ivm, parent=None, **kwargs):
        LogSource.__init__(self)
        QtGui.QWidget.__init__(self, parent)
        self.ivm = ivm
        self.data = None
        self.default_md = kwargs.get("default_metadata", DEFAULT_METADATA)
        self.md = dict(self.default_md)
        self.aslimage = None
        self.valid = True

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

        grid = QtGui.QGridLayout()

        grid.addWidget(QtGui.QLabel("ASL data"), 0, 0)
        self.data_combo = OverlayCombo(self.ivm)
        self.data_combo.currentIndexChanged.connect(self._data_changed)
        grid.addWidget(self.data_combo, 0, 1)

        view_classes = [
            LabelType, RepeatsChoice, NumPhases, NumEncodings, BlockFormat,
            DataStructure, SignalView, Labelling, Readout, SliceTime,
            Multiband, Times, BolusDurations, VariableRepeats
        ]

        self.views = []
        for idx, view_class in enumerate(view_classes):
            if view_class in kwargs.get("ignore_views", ()):
                continue
            view = view_class(grid, ypos=idx + 2)
            view.ivm = self.ivm
            view.sig_md_changed.connect(self._metadata_changed)
            self.views.append(view)

        #grid.addWidget(QtGui.QLabel("Data order preview"), 5, 0)

        # Code below is for specific multiple phases
        #self.phases_lbl = QtGui.QLabel("Phases (\N{DEGREE SIGN})")
        #grid.addWidget(self.phases_lbl, 3, 0)
        #self.phases_lbl.setVisible(False)
        #self.phases = NumberList([float(x)*360/8 for x in range(8)])
        #grid.addWidget(self.phases, 3, 1)
        #self.phases.setVisible(False)

        grid.setColumnStretch(0, 0)
        grid.setColumnStretch(1, 1)
        grid.setColumnStretch(2, 0)
        grid.setRowStretch(len(view_classes) + 2, 1)

        self.grid = grid
        vbox.addLayout(grid)

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

        self._update_ui()
        self._data_changed()

    def set_data_name(self, name):
        """ Set the name of the data item being displayed """
        if name not in self.ivm.data:
            raise QpException("Data not found: %s" % name)
        else:
            idx = self.data_combo.findText(name)
            self.data_combo.setCurrentIndex(idx)

    def _data_changed(self):
        """
        New data selected - load any previously defined metadata, and validate it 
        """
        self.data = self.ivm.data.get(self.data_combo.currentText(), None)
        for idx in range(self.grid.count()):
            if idx > 1:
                w = self.grid.itemAt(idx).widget()
                if w is not None:
                    w.setEnabled(self.data is not None)

        if self.data is not None:
            self.md = self.data.metadata.get("AslData", None)
            if self.md is None:
                self.md = dict(self.default_md)
                if self.data.nvols % 2 == 0:
                    # Even number of volumes - guess TC pairs
                    # FIXME don't need block format for single TI
                    self.md["iaf"] = "tc"
                    self.md["ibf"] = "tis"
            for view in self.views:
                view.set_data(self.data, self.md)
            self._validate_metadata()
            self.sig_changed.emit()

    def _metadata_changed(self, sender):
        """
        Called when a view has changed the metadata
        """
        self.debug("Metadata changed %s", sender)
        if self.data is not None:
            self.debug("Current metadata: %s", self.md)
        self.debug("New metadata: %s", sender.md)
        self.md = sender.md
        self._validate_metadata()
        self._save_metadata()
        self.sig_changed.emit()

    def _save_metadata(self):
        """
        Set the metadata on the dataset
        """
        if self.data is not None:
            current_md = self.data.metadata.get("AslData", {})
            self.debug("Save: Current metadata: %s", current_md)
            if self.md != current_md:
                self.debug("Different!")
                self.data.metadata["AslData"] = dict(self.md)
                self.debug("Saved: %s ", self.md)
                self._update_ui()

    def _update_ui(self, ignore=()):
        """ 
        Update user interface from the current metadata 
        """
        try:
            for view in self.views:
                if view not in ignore:
                    view.set_data(self.data, self.md)
        finally:
            self.updating_ui = False

    def _validate_metadata(self):
        """
        Validate data against specified TIs, etc
        """
        try:
            if self.md and self.data is not None:
                self.debug("Validating metadata: %s", str(self.md))
                self.aslimage, _ = qpdata_to_aslimage(self.data,
                                                      metadata=self.md)
            self.warn_label.clear()
            self.valid = True
        except ValueError as e:
            self.debug("Failed: %s", str(e))
            self.aslimage = None
            self.warn_label.warn(str(e))
            self.valid = False

    def get_options(self):
        """ Get batch options """
        options = {}
        if self.data is not None:
            options["data"] = self.data.name
            options.update(self.md)
        return options
Ejemplo n.º 7
0
class MeanValuesWidget(QpWidget):
    """
    Convert a data + multi-level ROI into mean values data set
    """
    def __init__(self, **kwargs):
        super(MeanValuesWidget, self).__init__(
            name="Mean in ROI",
            icon="meanvals",
            desc="Replace data with its mean value within each ROI region",
            group="ROIs",
            **kwargs)

        layout = QtGui.QVBoxLayout()

        title = TitleWidget(self,
                            "Generate Mean Values Data",
                            help="mean_values")
        layout.addWidget(title)

        desc = QtGui.QLabel(
            "This widget will convert the current data set into a "
            "new data set in which each ROI region contains the mean "
            "value for that region.\n\nThis is generally only useful for "
            "multi-level ROIs such as clusters or supervoxels")
        desc.setWordWrap(True)
        layout.addWidget(desc)

        hbox = QtGui.QHBoxLayout()
        gbox = QtGui.QGroupBox()
        gbox.setTitle("Options")
        grid = QtGui.QGridLayout()
        gbox.setLayout(grid)

        grid.addWidget(QtGui.QLabel("Data"), 0, 0)
        self.ovl = OverlayCombo(self.ivm)
        self.ovl.currentIndexChanged.connect(self._data_changed)
        grid.addWidget(self.ovl, 0, 1)
        grid.addWidget(QtGui.QLabel("ROI regions"), 1, 0)
        self.roi = RoiCombo(self.ivm)
        grid.addWidget(self.roi, 1, 1)
        grid.addWidget(QtGui.QLabel("Output name"), 2, 0)
        self.output_name = QtGui.QLineEdit()
        grid.addWidget(self.output_name, 2, 1)

        btn = QtGui.QPushButton('Generate', self)
        btn.clicked.connect(self._generate)
        grid.addWidget(btn, 2, 0)
        hbox.addWidget(gbox)
        hbox.addStretch(1)
        layout.addLayout(hbox)
        layout.addStretch(1)
        self.setLayout(layout)

    def _data_changed(self):
        name = self.ovl.currentText()
        if name:
            self.output_name.setText(name + "_means")

    def batch_options(self):
        options = {
            "roi": self.roi.currentText(),
            "data": self.ovl.currentText(),
            "output-name": self.output_name.text()
        }
        return "MeanValues", options

    def _generate(self):
        options = self.batch_options()[1]

        if not options["data"]:
            QtGui.QMessageBox.warning(
                self, "No data selected",
                "Load data to generate mean values from",
                QtGui.QMessageBox.Close)
            return
        if not options["roi"]:
            QtGui.QMessageBox.warning(self, "No ROI selected",
                                      "Load an ROI for mean value regions",
                                      QtGui.QMessageBox.Close)
            return

        process = MeanValuesProcess(self.ivm)
        process.run(options)
Ejemplo n.º 8
0
class ClusteringWidget(QpWidget):
    """
    Widget for doing K-means clustering on 3D or 4D data
    """
    def __init__(self, **kwargs):
        super(ClusteringWidget,
              self).__init__(name="KMeans Clustering",
                             icon="clustering",
                             desc="Generate clusters from 3D or 4D data",
                             group="Clustering",
                             position=2,
                             **kwargs)
        self.curves = {}

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

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

        DESC = """
<i>Performs clustering of 3D or 4D data using the K-means algorithm.
PCA reduction is used on 4D data to extract representative curves
"""
        desc = QtGui.QLabel(DESC)
        desc.setWordWrap(True)
        layout.addWidget(desc)

        gbox = QtGui.QGroupBox()
        gbox.setTitle('Clustering options')

        grid = QtGui.QGridLayout()
        gbox.setLayout(grid)

        # Data to cluster
        grid.addWidget(QtGui.QLabel("Data"), 0, 0)
        self.data_combo = OverlayCombo(self.ivm)
        self.data_combo.currentIndexChanged.connect(self._data_changed)
        grid.addWidget(self.data_combo, 0, 1)

        grid.addWidget(QtGui.QLabel("ROI"), 1, 0)
        self.roi_combo = RoiCombo(self.ivm, none_option=True)
        grid.addWidget(self.roi_combo, 1, 1)

        # Number of clusters inside the ROI
        self.n_clusters = NumericOption("Number of clusters",
                                        grid,
                                        xpos=2,
                                        ypos=0,
                                        minval=2,
                                        maxval=20,
                                        default=4,
                                        intonly=True)
        self.n_clusters.spin.setToolTip("")

        # Number of PCA modes
        self.n_pca = NumericOption("Number of PCA modes",
                                   grid,
                                   xpos=2,
                                   ypos=1,
                                   minval=1,
                                   maxval=10,
                                   default=3,
                                   intonly=True)
        self.n_pca.spin.setToolTip("")

        # Output ROI name
        grid.addWidget(QtGui.QLabel("Output name"), 2, 0)
        self.output_name = QtGui.QLineEdit("clusters")
        grid.addWidget(self.output_name, 2, 1)
        layout.addWidget(gbox)

        # Run clustering button
        hbox = QtGui.QHBoxLayout()
        self.run_btn = QtGui.QPushButton('Run', self)
        self.run_btn.clicked.connect(self.run_clustering)
        hbox.addWidget(self.run_btn)
        hbox.addStretch(1)
        layout.addLayout(hbox)

        # Plot window, showing representative curves for 4D data
        self.show_curves_btn = QtGui.QPushButton('Show representative curves',
                                                 self)
        self.show_curves_btn.clicked.connect(self._show_curves)
        layout.addWidget(self.show_curves_btn)
        self.plotwin = pg.GraphicsLayoutWidget()
        self.plotwin.setBackground(background=None)
        self.plot = self.plotwin.addPlot(title="Cluster representative curves")
        self.plot.setLabel('left', "Signal Enhancement")
        self.plotwin.setVisible(False)
        layout.addWidget(self.plotwin)

        # Statistics
        self.show_count_btn = QtGui.QPushButton('Show voxel counts', self)
        self.show_count_btn.clicked.connect(self._show_counts)
        layout.addWidget(self.show_count_btn)
        self.stats_gbox = QtGui.QGroupBox()
        self.stats_gbox.setTitle('Voxel count')

        self.count_table = QtGui.QStandardItemModel()
        self.count_view = QtGui.QTableView()
        self.count_view.resizeColumnsToContents()
        self.count_view.setModel(self.count_table)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.count_view)
        self.stats_gbox.setLayout(hbox)
        self.stats_gbox.setVisible(False)
        layout.addWidget(self.stats_gbox)

        # Merge regions
        self.show_merge_btn = QtGui.QPushButton('Show merge options', self)
        self.show_merge_btn.clicked.connect(self._show_merge)
        layout.addWidget(self.show_merge_btn)

        self.merge_gbox = QtGui.QGroupBox()
        self.merge_gbox.setTitle('Merge regions')
        vbox = QtGui.QVBoxLayout()
        self.merge_gbox.setLayout(vbox)

        hbox = QtGui.QHBoxLayout()
        self.merge_btn = QtGui.QPushButton('Merge', self)
        self.merge_btn.clicked.connect(self._run_merge)
        hbox.addWidget(self.merge_btn)
        hbox.addWidget(QtGui.QLabel('Merge region '))
        self.merge_region1 = QtGui.QLineEdit('1', self)
        hbox.addWidget(self.merge_region1)
        hbox.addWidget(QtGui.QLabel(' with '))
        self.merge_region2 = QtGui.QLineEdit('2', self)
        hbox.addWidget(self.merge_region2)
        vbox.addLayout(hbox)

        hbox = QtGui.QHBoxLayout()
        self.auto_merge_btn = QtGui.QPushButton('AutoMerge', self)
        self.auto_merge_btn.clicked.connect(self._run_automerge)
        hbox.addWidget(self.auto_merge_btn)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        self.merge_gbox.setVisible(False)
        layout.addWidget(self.merge_gbox)

        layout.addStretch(1)

    def activate(self):
        self.ivl.sig_focus_changed.connect(self._focus_changed)
        self.ivm.sig_current_roi.connect(self._current_roi_changed)
        self.ivm.sig_main_data.connect(self._main_data_changed)
        self._data_changed()

    def deactivate(self):
        self.ivl.sig_focus_changed.disconnect(self._focus_changed)
        self.ivm.sig_current_roi.disconnect(self._current_roi_changed)
        self.ivm.sig_main_data.disconnect(self._main_data_changed)

    def _current_roi_changed(self, roi):
        if roi is not None and roi.name != self.output_name.text():
            self.roi_combo.setCurrentIndex(self.roi_combo.findText(roi.name))

    def _main_data_changed(self, data):
        if data is not None:
            idx = self.data_combo.findText(data.name)
        else:
            idx = 0
        self.data_combo.setCurrentIndex(idx)

    def _data_changed(self):
        data = self.ivm.data.get(self.data_combo.currentText(), None)
        if data is not None:
            is4d = data.nvols > 1
            self.debug("Number of vols: %i, 4d=%s", data.nvols, is4d)
            self.n_pca.label.setVisible(is4d)
            self.n_pca.spin.setVisible(is4d)
            self.show_curves_btn.setVisible(is4d)
            self.plotwin.setVisible(is4d and self.plotwin.isVisible())
            self.auto_merge_btn.setEnabled(is4d)
        self.run_btn.setEnabled(data is not None)

    def _focus_changed(self):
        self._update_voxel_count()

    def _show_curves(self):
        if self.plotwin.isVisible():
            self.plotwin.setVisible(False)
            self.show_curves_btn.setText("Show representative curves")
        else:
            self.plotwin.setVisible(True)
            self.show_curves_btn.setText("Hide representative curves")

    def _show_counts(self):
        if self.stats_gbox.isVisible():
            self.stats_gbox.setVisible(False)
            self.show_count_btn.setText("Show voxel counts")
        else:
            self.stats_gbox.setVisible(True)
            self.show_count_btn.setText("Hide voxel counts")

    def _show_merge(self):
        if self.merge_gbox.isVisible():
            self.merge_gbox.setVisible(False)
            self.show_merge_btn.setText("Show merge_options")
        else:
            self.merge_gbox.setVisible(True)
            self.show_merge_btn.setText("Hide merge options")

    def batch_options(self):
        options = {
            "data": self.data_combo.currentText(),
            "roi": self.roi_combo.currentText(),
            "n-clusters": self.n_clusters.spin.value(),
            "output-name": self.output_name.text(),
            "invert-roi": False,
        }

        if options["roi"] == "<none>":
            del options["roi"]

        if self.n_pca.label.isVisible():
            # 4D PCA options
            options["n-pca"] = self.n_pca.spin.value()
            options["reduction"] = "pca"
            options["norm-data"] = True

        return "KMeans", options

    def run_clustering(self):
        """
        Run kmeans clustering using normalised PCA modes
        """
        options = self.batch_options()[1]

        self.process.run(options)
        self._update_voxel_count()
        if self.n_pca.label.isVisible():
            self.update_plot()

    def _update_voxel_count(self):
        self.count_table.clear()
        self.count_table.setVerticalHeaderItem(
            0, QtGui.QStandardItem("Voxel count"))

        roi = self.ivm.rois.get(self.output_name.text(), None)
        if roi is not None:
            col_idx = 0
            for region, name in roi.regions.items():
                self.count_table.setHorizontalHeaderItem(
                    col_idx, QtGui.QStandardItem(name))

                # Volume count
                voxel_count = np.sum(roi.raw() == region)
                self.count_table.setItem(
                    0, col_idx,
                    QtGui.QStandardItem(str(np.around(voxel_count))))
                col_idx += 1

    def update_plot(self):
        """
        Plot the cluster curves
        :return:
        """
        # Clear graph
        self.plot.clear()
        self.plot.setLabel('bottom', "Volume", "")
        if self.plot.legend is not None:
            # Work around pyqtgraph legend bug - the legend is recreated multiple times!
            # https://stackoverflow.com/questions/42792858/pyqtgraph-delete-persisting-legend-in-pyqt4-gui
            self.plot.legend.scene().removeItem(self.plot.legend)
        data = self.ivm.data.get(self.data_combo.currentText(), None)
        roi = self.ivm.rois.get(self.output_name.text(), None)
        if roi is not None and data is not None and data.nvols > 1:
            # Generate mean curve for each cluster
            self._generate_cluster_means(roi, data)

            self.plot.addLegend()

            # Plotting using single or multiple plots
            for region, name in roi.regions.items():
                if np.sum(self.curves[region]) == 0:
                    continue

                pencol = get_pencol(roi, region)
                curve = self.plot.plot(pen=pencol, width=8.0, name=name)
                xx = np.arange(len(self.curves[region]))
                curve.setData(xx, self.curves[region])

    def _run_merge(self):
        m1 = int(self.merge_region1.text())
        m2 = int(self.merge_region2.text())
        self._merge(m1, m2)

    def _run_automerge(self):
        # Use PCA features or true curves?
        # Mean features from each cluster
        # Distance matrix between features
        if len(self.curves) < 2: return

        curvemat = np.zeros((len(self.curves), self.ivm.main.nvols))
        row_region = {}
        idx = 0
        for region, curve in self.curves.items():
            row_region[idx] = region
            curvemat[idx, :] = curve
            idx += 1
        distmat = pairwise.euclidean_distances(curvemat)
        distmat[distmat == 0] = np.inf
        loc1 = np.where(distmat == distmat.min())[0]
        self._merge(row_region[loc1[0]], row_region[loc1[1]])

    def _generate_cluster_means(self, roi, data):
        """
        Generate the mean curves for each cluster
        Returns:
        """
        self.curves = {}
        for region in roi.regions:
            roi_data = data.mask(roi, region=region, output_flat=True)
            mean = np.median(roi_data, axis=0)
            self.curves[region] = mean

    def _merge(self, m1, m2):
        roi = self.ivm.rois.get(self.output_name.text(), None)
        if roi is not None:
            roi_data = roi.raw()
            roi_data[roi_data == m1] = m2

            # signal the change
            self.ivm.add(NumpyData(roi_data,
                                   grid=roi.grid,
                                   name=self.output_name.text(),
                                   roi=True),
                         make_current=True)

            # replot
            self.update_plot()
            self._update_voxel_count()
Ejemplo n.º 9
0
class WalkerTool(Tool):
    """
    Tool which uses the random walker method to select a region

    FIXME this does not work properly in slice mode at the moment because the zaxis/pos 
    given by the picker does not necessarily correspond to the data axes.
    """
    def __init__(self):
        Tool.__init__(self, "Walker", "Automatic segmentation using the random walk algorithm")
        self.segmode = 0
        self.pickmode = 0

    def interface(self):
        grid = Tool.interface(self)

        grid.addWidget(QtGui.QLabel("Source data: "), 1, 0)
        self.ov_combo = OverlayCombo(self.ivm)
        grid.addWidget(self.ov_combo)

        grid.addWidget(QtGui.QLabel("Click to select points: "), 2, 0)
        self.pickmode_combo = QtGui.QComboBox()
        self.pickmode_combo.addItem("Inside the ROI")
        self.pickmode_combo.addItem("Outside the ROI")
        self.pickmode_combo.currentIndexChanged.connect(self._pick_mode_changed)
        grid.addWidget(self.pickmode_combo, 2, 1)
        
        grid.addWidget(QtGui.QLabel("Segmentation mode: "), 3, 0)
        self.segmode_combo = QtGui.QComboBox()
        self.segmode_combo.addItem("Slice")
        self.segmode_combo.addItem("3D")
        self.segmode_combo.currentIndexChanged.connect(self._seg_mode_changed)
        grid.addWidget(self.segmode_combo, 3, 1)

        self.beta = NumericOption("Diffusion difficulty", grid, 4, 0, intonly=True, maxval=20000, default=10000, step=1000)

        btn = QtGui.QPushButton("Segment")
        btn.clicked.connect(self._segment)
        grid.addWidget(btn, 5, 0)
        btn = QtGui.QPushButton("Clear points")
        btn.clicked.connect(self._init)
        grid.addWidget(btn, 5, 1)

        return grid

    def _pick_mode_changed(self, idx=None):
        self.pickmode = idx
        if self.pickmode == 0:
            self.ivl.picker.col = (255, 0, 0)
        else:
            self.ivl.picker.col = (255, 255, 255)

    def _seg_mode_changed(self, idx=None):
        self.segmode = idx
        self._init()

    def _init(self):
        if self.segmode == 1:
            self.ivl.set_picker(PickMode.MULTIPLE)
        else:
            self.ivl.set_picker(PickMode.SLICE_MULTIPLE)
            
        self.labels = np.zeros(self.builder.grid.shape, dtype=np.int)
        self._pick_mode_changed(self.pickmode)

    def selected(self):
        self.pickmode = 0
        self.segmode = 0
        self.ivl.sig_selection_changed.connect(self._points_changed)
        self._init()
        
    def deselected(self):
        self.ivl.sig_selection_changed.disconnect(self._points_changed)
        Tool.deselected(self)

    def _points_changed(self):
        for col, points in self.ivl.picker.selection(grid=self.builder.grid).items():
            if col == ((255, 0, 0)):
                label = 1
            else:
                label = 2

            for pos in points:
                pos = [int(p+0.5) for p in pos]
                # Clamp points to within range of ROI
                for dim in range(3):
                    pos[dim] = min(max(pos[dim], 0), self.labels.shape[dim]-1)
                self.labels[pos[0], pos[1], pos[2]] = label
                
    def _segment(self):
        data = self.ivm.data[self.ov_combo.currentText()].resample(self.builder.grid)
        labels = self.labels

        kwargs = {}
        # Use voxel size correctly
        spacing = [data.grid.spacing[0] / data.grid.spacing[0],
                   data.grid.spacing[0] / data.grid.spacing[1],
                   data.grid.spacing[0] / data.grid.spacing[2]]

        arr = data.raw()
        if arr.ndim > 3:
            # Reduce 4D data to PCA modes
            pca = PcaFeatReduce(n_components=5)
            arr = pca.get_training_features(arr, feature_volume=True)
            kwargs["multichannel"] = True
        else:
            # Normalize data
            arr = (arr / (np.max(arr)-np.min(arr))) + np.min(arr)
            kwargs["multichannel"] = False

        if self.segmode == 0:
            # Segment using 2D slice only
            zaxis = self.ivl.picker.zaxis
            zpos = int(self.ivl.picker.zpos + 0.5)
            #print(zaxis, zpos)
            sl = [slice(None)] * 3
            sl[zaxis] = zpos
            arr = arr[sl]
            labels = self.labels[sl]
            del spacing[zaxis] 
            #print(labels)
            #print(np.count_nonzero(labels))
            #print(np.count_nonzero(arr))

        seg = skimage.segmentation.random_walker(arr, labels, beta=self.beta.spin.value(), 
                                                 mode='cg_mg', spacing=spacing, **kwargs)

        if self.segmode == 0:
            # Create 3D volume using 2D slice
            seg_3d = np.zeros(self.builder.grid.shape, dtype=np.int)
            seg_3d[sl] = seg
            seg = seg_3d

        # Label 2 is used for 'outside region'
        seg[seg == 2] = 0
        
        self.builder.modify(vol=seg, mode=self.builder.ADD)
        self._init()
Ejemplo n.º 10
0
class FabberT1Widget(QpWidget):
    """
    T1 from VFA images, using the Fabber process
    """
    def __init__(self, **kwargs):
        QpWidget.__init__(
            self,
            name="Fabber T1",
            icon="t10_fabber",
            group="T1",
            desc="T1 mapping from VFA images using Bayesian inference",
            **kwargs)

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

        try:
            self.FabberProcess = get_plugins("processes", "FabberProcess")[0]
        except:
            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-t1",
            subtitle="T1 mapping from VFA images using the Fabber process %s" %
            __version__)
        vbox.addWidget(title)

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

        grid = QtGui.QGridLayout()
        self.multivol_choice = ChoiceOption(
            "VFA data in",
            grid,
            ypos=0,
            choices=["Single data set", "Multiple data sets"])
        self.multivol_choice.combo.currentIndexChanged.connect(self.update_ui)

        self.multivol_label = QtGui.QLabel("VFA data set")
        grid.addWidget(self.multivol_label, 1, 0)
        self.multivol_combo = OverlayCombo(self.ivm)
        grid.addWidget(self.multivol_combo, 1, 1)
        self.multivol_fas_label = QtGui.QLabel("FAs (\N{DEGREE SIGN})")
        grid.addWidget(self.multivol_fas_label, 2, 0)
        self.multivol_fas = NumberList(initial=[
            1,
        ])
        grid.addWidget(self.multivol_fas, 2, 1, 1, 2)

        self.singlevol_label = QtGui.QLabel("VFA data sets")
        grid.addWidget(self.singlevol_label, 3, 0)
        grid.setAlignment(self.singlevol_label, QtCore.Qt.AlignTop)
        self.singlevol_table = QtGui.QTableWidget()
        self.singlevol_table.setColumnCount(2)
        self.singlevol_table.setHorizontalHeaderLabels(
            ["Data set", "Flip angle"])
        self.singlevol_table.setEditTriggers(
            QtGui.QAbstractItemView.NoEditTriggers)
        grid.addWidget(self.singlevol_table, 3, 1)

        hbox = QtGui.QHBoxLayout()
        self.singlevol_add = QtGui.QPushButton("Add")
        self.singlevol_add.clicked.connect(self.add_vol)
        hbox.addWidget(self.singlevol_add)
        self.singlevol_clear = QtGui.QPushButton("Clear")
        self.singlevol_clear.clicked.connect(self.clear_vols)
        hbox.addWidget(self.singlevol_clear)
        grid.addLayout(hbox, 4, 1)

        self.tr = NumericOption("TR (ms)",
                                grid,
                                ypos=5,
                                default=4.108,
                                minval=0,
                                step=0.1,
                                decimals=3)

        grid.setColumnStretch(3, 1)

        vbox.addLayout(grid)

        self.run = RunBox(self.get_process, self.get_rundata)
        vbox.addWidget(self.run)

        vbox.addStretch(1)
        self.update_ui()

    def update_ui(self):
        multivol = self.multivol_choice.combo.currentIndex() == 0
        self.multivol_label.setVisible(multivol)
        self.multivol_combo.setVisible(multivol)
        self.multivol_fas_label.setVisible(multivol)
        self.multivol_fas.setVisible(multivol)

        self.singlevol_label.setVisible(not multivol)
        self.singlevol_table.setVisible(not multivol)
        self.singlevol_add.setVisible(not multivol)
        self.singlevol_clear.setVisible(not multivol)

    def add_vol(self):
        used = [
            self.singlevol_table.item(i, 0).text()
            for i in range(self.singlevol_table.rowCount())
        ]
        dlg = ChooseDataDialog(self, self.ivm, used)
        if dlg.exec_():
            nrows = self.singlevol_table.rowCount()
            self.singlevol_table.setRowCount(nrows + 1)
            self.singlevol_table.setItem(
                nrows, 0, QtGui.QTableWidgetItem(dlg.data_combo.currentText()))
            self.singlevol_table.setItem(
                nrows, 1, QtGui.QTableWidgetItem(dlg.fa_edit.text()))

    def clear_vols(self):
        self.singlevol_table.setRowCount(0)

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

    def batch_options(self):
        return "Fabber", self.get_rundata()

    def get_rundata(self):
        rundata = {}
        rundata["model-group"] = "t1"
        rundata["save-mean"] = ""
        rundata["save-model-fit"] = ""
        rundata["noise"] = "white"
        rundata["max-iterations"] = "20"
        rundata["model"] = "vfa"
        rundata["tr"] = self.tr.spin.value() / 1000

        multivol = self.multivol_choice.combo.currentIndex() == 0
        if multivol and self.multivol_combo.currentText() in self.ivm.data:
            rundata["data"] = self.multivol_combo.currentText()
            fas = self.multivol_fas.values()
            nvols = self.ivm.data[self.multivol_combo.currentText()].nvols
            if nvols != len(fas):
                raise QpException(
                    "Number of flip angles must match the number of volumes in the selected data (%i)"
                    % nvols)
            for idx, fa in enumerate(fas):
                rundata["fa%i" % (idx + 1)] = fa
        else:
            rundata["data"] = []
            for r in range(self.singlevol_table.rowCount()):
                rundata["data"].append(self.singlevol_table.item(r, 0).text())
                rundata["fa%i" % (r + 1)] = float(
                    self.singlevol_table.item(r, 1).text())

        return rundata