示例#1
0
def _get_actor_widget(actor: vtkActor) -> QWidget:
    widget = QWidget()
    layout = QVBoxLayout()

    prop = actor.GetProperty()

    # visibility
    visibility = QCheckBox("Visibility")
    visibility.setChecked(actor.GetVisibility())
    visibility.toggled.connect(actor.SetVisibility)
    layout.addWidget(visibility)

    if prop is not None:
        # opacity
        tmp_layout = QHBoxLayout()
        opacity = QDoubleSpinBox()
        opacity.setMaximum(1.0)
        opacity.setValue(prop.GetOpacity())
        opacity.valueChanged.connect(prop.SetOpacity)
        tmp_layout.addWidget(QLabel("Opacity"))
        tmp_layout.addWidget(opacity)
        layout.addLayout(tmp_layout)

    widget.setLayout(layout)
    return widget
示例#2
0
文件: _qt.py 项目: mh105/mne-python
 def _dock_add_spin_box(self,
                        name,
                        value,
                        rng,
                        callback,
                        *,
                        compact=True,
                        double=True,
                        step=None,
                        tooltip=None,
                        layout=None):
     layout = self._dock_named_layout(name=name,
                                      layout=layout,
                                      compact=compact)
     value = value if double else int(value)
     widget = QDoubleSpinBox() if double else QSpinBox()
     _set_widget_tooltip(widget, tooltip)
     widget.setAlignment(Qt.AlignCenter)
     widget.setMinimum(rng[0])
     widget.setMaximum(rng[1])
     widget.setKeyboardTracking(False)
     if step is None:
         inc = (rng[1] - rng[0]) / 20.
         inc = max(int(round(inc)), 1) if not double else inc
         widget.setSingleStep(inc)
     else:
         widget.setSingleStep(step)
     widget.setValue(value)
     widget.valueChanged.connect(callback)
     self._layout_add_widget(layout, widget)
     return _QtWidget(widget)
示例#3
0
 def __add_option(self, name: str, option: QDoubleSpinBox) -> None:
     """Add widgets for option."""
     layout = QHBoxLayout()
     label = QLabel(name, self)
     option.setValue(1)
     option.setMaximum(10000)
     option.setMinimum(0.01)
     layout.addWidget(label)
     layout.addWidget(option)
     self.main_layout.addLayout(layout)
示例#4
0
 def createDoubleSpinBox(self, variable_name, variable_value, variable_type, analysis_module_variables_model):
     spinner = QDoubleSpinBox()
     spinner.setDecimals(6)
     spinner.setMinimumWidth(75)
     spinner.setMaximum(analysis_module_variables_model.getVariableMaximumValue(variable_name))
     spinner.setMinimum(analysis_module_variables_model.getVariableMinimumValue(variable_name))
     spinner.setSingleStep(analysis_module_variables_model.getVariableStepValue(variable_name))
     spinner.setValue(variable_value)
     spinner.valueChanged.connect(partial(self.valueChanged, variable_name, variable_type, spinner))
     return spinner
示例#5
0
 def spinbox(v: float = 0.,
             *,
             minimum: float = 0.,
             maximum: float = 9999.,
             prefix: bool = False) -> QDoubleSpinBox:
     double_spinbox = QDoubleSpinBox()
     double_spinbox.setMinimum(minimum)
     double_spinbox.setMaximum(maximum)
     double_spinbox.setValue(v)
     if prefix:
         double_spinbox.setPrefix("±")
     return double_spinbox
示例#6
0
    def createEditor(self, parent, option, index):
        """Override.

        Create and editor based on the cell type
        """
        if index.column() == 2:
            editor = QDoubleSpinBox(parent)
            editor.setDecimals(3)
            editor.setMaximum(10)
            editor.setMinimum(0)
            editor.setSingleStep(0.001)
        return editor
def add_float_box(layout,
                  default,
                  minimum,
                  maximum,
                  label,
                  step,
                  row,
                  column=0):
    box = QDoubleSpinBox()
    box.setMinimum(minimum)
    box.setMaximum(maximum)
    box.setValue(default)
    box.setSingleStep(step)
    layout.addWidget(QLabel(label), row, column)
    layout.addWidget(box, row, column + 1)
    return box
示例#8
0
class PMGNumberSpinCtrl(BaseExtendedWidget):
    """
    利用spinbox的控制面板,当最大值、最小值、初始值和步长均为整数的时候,类型为整数;、反之只要有任意一个是float,
    类型就是浮点数了。
    """

    def __init__(self, layout_dir: str, title: str, initial_value: Union[int, float], unit: str = '',
                 val_range: Tuple[Union[float, int], Union[float, int]] = (None, None),
                 step: int = 1):
        super().__init__(layout_dir)
        self.on_check_callback = None

        self.prefix = QLabel(text=title)
        entryLayout = QHBoxLayout()
        entryLayout.setContentsMargins(0, 0, 0, 0)

        self.min, self.max = val_range
        self.step = step
        if isinstance(self.min, int) and isinstance(self.max, int) and isinstance(self.step, int) \
                and isinstance(initial_value, int):
            self.ctrl = QSpinBox()
        else:
            self.ctrl = QDoubleSpinBox()
        self.ctrl.valueChanged.connect(self.on_value_changed)
        self.postfix = QLabel(text=unit)

        # self.central_layout.addWidget(self.prefix)
        self.central_layout.addLayout(entryLayout)
        entryLayout.addWidget(self.prefix)
        entryLayout.addWidget(self.ctrl)
        entryLayout.addWidget(self.postfix)
        if self.min is not None:
            self.ctrl.setMinimum(self.min)
        if self.max is not None:
            self.ctrl.setMaximum(self.max)
        self.ctrl.setSingleStep(step)
        self.accury = initial_value
        self.set_value(initial_value)

    def set_value(self, value: Union[float, int]) -> None:
        self.ctrl.setValue(value)

    def get_value(self) -> Union[int, float]:
        return self.ctrl.value()

    def on_value_changed(self):
        self.signal_param_changed.emit(self.name)
示例#9
0
    def setupParameters(self, model):
        pBias = model.biasParameters()
        pSpecific = model.modelSpecificParameters()
        parameters = pBias + pSpecific
        self.setRowCount(len(parameters))
        self.siScale = {}
        self.rowMap = {}
        for row, parameter in enumerate(parameters):
            name, latex, unit, siScale, minimum, maximum, text = parameter
            guessSb = QDoubleSpinBox()
            if len(unit):
                guessSb.setSuffix(' %s' % unit)
            guessSb.setMinimum(minimum)
            guessSb.setMaximum(maximum)
            guessSb.setKeyboardTracking(False)
            item = QTableWidgetItem(name)
            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEditable
                          | Qt.ItemIsEnabled)
            item.setCheckState(Qt.Checked)
            item.setToolTip(text)
            self.setItem(row, 0, item)
            self.setCellWidget(row, 1, guessSb)
            minSb = QDoubleSpinBox()
            maxSb = QDoubleSpinBox()
            minSb.setMinimum(minimum)
            minSb.setMaximum(maximum)
            minSb.setValue(minimum)
            maxSb.setMinimum(minimum)
            maxSb.setMaximum(maximum)
            maxSb.setValue(maximum)
            minSb.setKeyboardTracking(False)
            maxSb.setKeyboardTracking(False)

            maxSb.valueChanged.connect(minSb.setMaximum)
            maxSb.valueChanged.connect(guessSb.setMaximum)
            minSb.valueChanged.connect(maxSb.setMinimum)
            minSb.valueChanged.connect(guessSb.setMinimum)
            self.setCellWidget(row, 2, minSb)
            self.setCellWidget(row, 3, maxSb)
            guessSb.valueChanged.connect(self.parameterGuessChanged)
            self.siScale[name] = siScale
            self.rowMap[name] = row
示例#10
0
class ImageRotationDialog(ExToolWindow):
    def __init__(self, signal, axes, parent, plugin):
        super(ImageRotationDialog, self).__init__(parent)
        self.ui = parent
        self.create_controls()
        self.accepted.connect(self.ok)
        self.rejected.connect(self.close_new)
        self.signal = signal
        self.plugin = plugin
        self.new_out = None
        self._connected_updates = False
        if isinstance(axes, str):
            axm = signal.signal.axes_manager
            if axes.startswith("nav"):
                axes = (axm._axes.index(axm.navigation_axes[0]),
                        axm._axes.index(axm.navigation_axes[1]))
            elif axes.startswith("sig"):
                axes = (axm._axes.index(axm.signal_axes[0]),
                        axm._axes.index(axm.signal_axes[1]))
        self.axes = axes
        self.setWindowTitle(tr("Rotate"))

        # TODO: TAG: Functionality check
        if not hasattr(signal.signal, 'events'):
            self.gbo_preview.setVisible(False)

        # TODO: Add dynamic rotation, e.g. one that rotates when source
        # signal's data_changed event triggers

    def connect(self):
        # TODO: Don't have to con/dis those in gbo
        self.opt_new.toggled.connect(self.close_new)
        self.num_angle.valueChanged.connect(self.update)
        self.chk_grid.toggled.connect(self.update)
        self.num_grid.valueChanged.connect(self.update)
        self.chk_reshape.toggled.connect(self.update)
        self.opt_new.toggled.connect(self.update)
        self.opt_replace.toggled.connect(self.update)

    def disconnect(self):
        self.num_angle.valueChanged.disconnect(self.update)
        self.chk_grid.toggled.disconnect(self.update)
        self.num_grid.valueChanged.disconnect(self.update)
        self.chk_reshape.toggled.disconnect(self.update)
        self.opt_new.toggled.disconnect(self.update)
        self.opt_replace.toggled.disconnect(self.update)

    def ok(self):
        # Draw figure if not already done
        # TODO: TAG: Functionality check
        if not hasattr(self.signal.signal, 'events') or \
                not self.gbo_preview.isChecked():
            self.update()
        angle = self.num_angle.value()
        reshape = self.chk_reshape.isChecked()
        self.plugin.record_code(
            r"<p>.rotate_signal({0}, reshape={1}, axes={2})".format(
                angle, reshape, self.axes))
        # Clean up event connections
        if self.new_out is not None:
            self.connect_update_plot(self.new_out.signal, disconnect=True)

    def close_new(self, value=False):
        if self.new_out is not None and not value:
            self.new_out.close()
            self.new_out = None
            self._connected_updates = False

    def set_preview(self, value):
        if not hasattr(self.signal.signal, 'events'):
            return
        if value:
            self.connect()
            self.update()
        else:
            self.disconnect()
            self.close_new()

    def _axes_in_nav(self):
        axm = self.signal.signal.axes_manager
        navidx = [axm._axes.index(ax) for ax in axm.navigation_axes]
        if self.axes[0] in navidx:
            return True
        return False

    def connect_update_plot(self, signal, disconnect=False):
        if self._connected_updates != disconnect:
            return  # Nothing to do, prevent double connections
        if self._axes_in_nav():
            f = signal._plot.navigator_plot.update
        else:
            f = signal._plot.signal_plot.update

        # TODO: TAG: Functionality check
        if hasattr(signal, 'events') and hasattr(signal.events,
                                                 'data_changed'):
            if disconnect:
                signal.events.data_changed.disconnect(f)
            else:
                signal.events.data_changed.connect(f, [])
        self._connected_updates = not disconnect

    def update(self):
        angle = self.num_angle.value()
        reshape = self.chk_reshape.isChecked()
        if self.opt_new.isChecked():
            if self.new_out is None:
                out = None
            else:
                out = self.new_out.signal
        elif self.opt_replace.isChecked():
            out = self.signal.signal
        else:
            return  # Indeterminate state, do nothing

        s = self.plugin.rotate_signal(angle,
                                      self.signal.signal,
                                      record=False,
                                      reshape=reshape,
                                      out=out,
                                      axes=self.axes)

        if out is None:
            s.metadata.General.title = self.signal.name + "[Rotated]"
            s.plot()
            self.connect_update_plot(s)
            if (self.gbo_preview.isChecked() and self.opt_new.isChecked()
                    and self.new_out is None):
                self.new_out = self.ui.lut_signalwrapper[s]
        else:
            s = out

        if self.chk_grid.isChecked() is True:
            pass  # TODO: Draw grid

    def create_controls(self):
        """
        Create UI controls.
        """
        vbox = QVBoxLayout()

        form = QFormLayout()
        self.num_angle = QDoubleSpinBox()
        self.num_angle.setValue(0.0)
        self.num_angle.setMinimum(-360)
        self.num_angle.setMaximum(360)
        form.addRow(tr("Angle:"), self.num_angle)
        vbox.addLayout(form)

        self.gbo_preview = QGroupBox(tr("Preview"))
        self.gbo_preview.setCheckable(True)
        self.gbo_preview.setChecked(False)
        gbo_vbox = QVBoxLayout()
        self.chk_grid = QCheckBox(tr("Grid"))
        self.chk_grid.setChecked(False)
        self.num_grid = QSpinBox()
        self.num_grid.setValue(4)
        self.num_grid.setMinimum(1)
        self.num_grid.setEnabled(False)
        self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled)
        gbo_vbox.addWidget(self.chk_grid)
        gbo_vbox.addWidget(self.num_grid)
        self.gbo_preview.setLayout(gbo_vbox)
        vbox.addWidget(self.gbo_preview)

        self.gbo_preview.toggled[bool].connect(self.set_preview)

        self.gbo_output = QGroupBox(tr("Output"))
        self.opt_new = QRadioButton(tr("New signal"))
        self.opt_replace = QRadioButton(tr("In place"))
        self.opt_new.setChecked(True)
        gbo_vbox2 = QVBoxLayout()
        gbo_vbox2.addWidget(self.opt_new)
        gbo_vbox2.addWidget(self.opt_replace)
        self.gbo_output.setLayout(gbo_vbox2)
        vbox.addWidget(self.gbo_output)

        self.chk_reshape = QCheckBox(tr("Resize to fit"))
        self.chk_reshape.setChecked(False)
        vbox.addWidget(self.chk_reshape)

        self.btn_ok = QPushButton(tr("&OK"))
        self.btn_ok.setDefault(True)
        self.btn_ok.clicked.connect(self.accept)
        self.btn_cancel = QPushButton(tr("&Cancel"))
        self.btn_cancel.clicked.connect(self.reject)
        hbox = QHBoxLayout()
        hbox.addWidget(self.btn_ok)
        hbox.addWidget(self.btn_cancel)
        vbox.addLayout(hbox)

        vbox.addStretch(1)
        self.setLayout(vbox)
示例#11
0
class QtVectorsControls(QtLayerControls):
    """Qt view and controls for the napari Vectors layer.

    Parameters
    ----------
    layer : napari.layers.Vectors
        An instance of a napari Vectors layer.

    Attributes
    ----------
    edge_color_label : qtpy.QtWidgets.QLabel
        Label for edgeColorSwatch
    edgeColorSwatch : qtpy.QtWidgets.QFrame
        Color swatch showing display color of vectors.
    edgeComboBox : qtpy.QtWidgets.QComboBox
        Dropdown widget to select display color for vectors.
    color_mode_comboBox : qtpy.QtWidgets.QComboBox
        Dropdown widget to select edge_color_mode for the vectors.
    color_prop_box : qtpy.QtWidgets.QComboBox
        Dropdown widget to select _edge_color_property for the vectors.
    edge_prop_label : qtpy.QtWidgets.QLabel
        Label for color_prop_box
    grid_layout : qtpy.QtWidgets.QGridLayout
        Layout of Qt widget controls for the layer.
    layer : napari.layers.Vectors
        An instance of a napari Vectors layer.
    lengthSpinBox : qtpy.QtWidgets.QDoubleSpinBox
        Spin box widget controlling line length of vectors.
        Multiplicative factor on projections for length of all vectors.
    widthSpinBox : qtpy.QtWidgets.QDoubleSpinBox
        Spin box widget controlling edge line width of vectors.
    """
    def __init__(self, layer):
        super().__init__(layer)

        self.layer.events.edge_width.connect(self._on_edge_width_change)
        self.layer.events.length.connect(self._on_length_change)
        self.layer.events.edge_color_mode.connect(
            self._on_edge_color_mode_change)
        self.layer.events.edge_color.connect(self._on_edge_color_change)

        # dropdown to select the property for mapping edge_color
        color_properties = self._get_property_values()
        color_prop_box = QComboBox(self)
        color_prop_box.activated[str].connect(self.change_edge_color_property)
        color_prop_box.addItems(color_properties)
        self.color_prop_box = color_prop_box
        self.edge_prop_label = QLabel(trans._('edge property:'))

        # vector direct color mode adjustment and widget
        self.edgeColorEdit = QColorSwatchEdit(
            initial_color=self.layer.edge_color,
            tooltip=trans._('click to set current edge color', ),
        )
        self.edgeColorEdit.color_changed.connect(self.change_edge_color_direct)
        self.edge_color_label = QLabel(trans._('edge color:'))
        self._on_edge_color_change()

        # dropdown to select the edge color mode
        colorModeComboBox = QComboBox(self)
        color_modes = [e.value for e in ColorMode]
        colorModeComboBox.addItems(color_modes)
        colorModeComboBox.activated[str].connect(self.change_edge_color_mode)
        self.color_mode_comboBox = colorModeComboBox
        self._on_edge_color_mode_change()

        # line width in pixels
        self.widthSpinBox = QDoubleSpinBox()
        self.widthSpinBox.setKeyboardTracking(False)
        self.widthSpinBox.setSingleStep(0.1)
        self.widthSpinBox.setMinimum(0.1)
        self.widthSpinBox.setMaximum(np.inf)
        self.widthSpinBox.setValue(self.layer.edge_width)
        self.widthSpinBox.valueChanged.connect(self.change_width)

        # line length
        self.lengthSpinBox = QDoubleSpinBox()
        self.lengthSpinBox.setKeyboardTracking(False)
        self.lengthSpinBox.setSingleStep(0.1)
        self.lengthSpinBox.setValue(self.layer.length)
        self.lengthSpinBox.setMinimum(0.1)
        self.lengthSpinBox.setMaximum(np.inf)
        self.lengthSpinBox.valueChanged.connect(self.change_length)

        # grid_layout created in QtLayerControls
        # addWidget(widget, row, column, [row_span, column_span])
        self.grid_layout.addWidget(QLabel(trans._('opacity:')), 0, 0)
        self.grid_layout.addWidget(self.opacitySlider, 0, 1, 1, 2)
        self.grid_layout.addWidget(QLabel(trans._('width:')), 1, 0)
        self.grid_layout.addWidget(self.widthSpinBox, 1, 1, 1, 2)
        self.grid_layout.addWidget(QLabel(trans._('length:')), 2, 0)
        self.grid_layout.addWidget(self.lengthSpinBox, 2, 1, 1, 2)
        self.grid_layout.addWidget(QLabel(trans._('blending:')), 3, 0)
        self.grid_layout.addWidget(self.blendComboBox, 3, 1, 1, 2)
        self.grid_layout.addWidget(QLabel(trans._('edge color mode:')), 4, 0)
        self.grid_layout.addWidget(self.color_mode_comboBox, 4, 1, 1, 2)
        self.grid_layout.addWidget(self.edge_color_label, 5, 0)
        self.grid_layout.addWidget(self.edgeColorEdit, 5, 1, 1, 2)
        self.grid_layout.addWidget(self.edge_prop_label, 6, 0)
        self.grid_layout.addWidget(self.color_prop_box, 6, 1, 1, 2)
        self.grid_layout.setRowStretch(7, 1)
        self.grid_layout.setColumnStretch(1, 1)
        self.grid_layout.setSpacing(4)

    def change_edge_color_property(self, property: str):
        """Change edge_color_property of vectors on the layer model.
        This property is the property the edge color is mapped to.

        Parameters
        ----------
        property : str
            property to map the edge color to
        """
        mode = self.layer.edge_color_mode
        try:
            self.layer.edge_color = property
            self.layer.edge_color_mode = mode
        except TypeError:
            # if the selected property is the wrong type for the current color mode
            # the color mode will be changed to the appropriate type, so we must update
            self._on_edge_color_mode_change()
            raise

    def change_edge_color_mode(self, mode: str):
        """Change edge color mode of vectors on the layer model.

        Parameters
        ----------
        mode : str
            Edge color for vectors. Must be: 'direct', 'cycle', or 'colormap'
        """
        old_mode = self.layer.edge_color_mode
        with self.layer.events.edge_color_mode.blocker():
            try:
                self.layer.edge_color_mode = mode
                self._update_edge_color_gui(mode)

            except ValueError:
                # if the color mode was invalid, revert to the old mode
                self.layer.edge_color_mode = old_mode
                raise

    def change_edge_color_direct(self, color: np.ndarray):
        """Change edge color of vectors on the layer model.

        Parameters
        ----------
        color : np.ndarray
            Edge color for vectors, in an RGBA array
        """
        self.layer.edge_color = color

    def change_width(self, value):
        """Change edge line width of vectors on the layer model.

        Parameters
        ----------
        value : float
            Line width of vectors.
        """
        self.layer.edge_width = value
        self.widthSpinBox.clearFocus()
        self.setFocus()

    def change_length(self, value):
        """Change length of vectors on the layer model.

        Multiplicative factor on projections for length of all vectors.

        Parameters
        ----------
        value : float
            Length of vectors.
        """
        self.layer.length = value
        self.lengthSpinBox.clearFocus()
        self.setFocus()

    def _update_edge_color_gui(self, mode: str):
        """Update the GUI element associated with edge_color.
        This is typically used when edge_color_mode changes

        Parameters
        ----------
        mode : str
            The new edge_color mode the GUI needs to be updated for.
            Should be: 'direct', 'cycle', 'colormap'
        """
        if mode in ('cycle', 'colormap'):
            self.edgeColorEdit.setHidden(True)
            self.edge_color_label.setHidden(True)
            self.color_prop_box.setHidden(False)
            self.edge_prop_label.setHidden(False)

        elif mode == 'direct':
            self.edgeColorEdit.setHidden(False)
            self.edge_color_label.setHidden(False)
            self.color_prop_box.setHidden(True)
            self.edge_prop_label.setHidden(True)

    def _get_property_values(self):
        """Get the current property values from the Vectors layer

        Returns
        -------
        property_values : np.ndarray
            array of all of the union of the property names (keys)
            in Vectors.properties and Vectors._property_choices

        """
        property_choices = [*self.layer._property_choices]
        properties = [*self.layer.properties]
        property_values = np.union1d(property_choices, properties)

        return property_values

    def _on_length_change(self, event=None):
        """Change length of vectors.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method, by default None.
        """
        with self.layer.events.length.blocker():
            self.lengthSpinBox.setValue(self.layer.length)

    def _on_edge_width_change(self, event=None):
        """Receive layer model width change event and update width spinbox.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method, by default None.
        """
        with self.layer.events.edge_width.blocker():
            self.widthSpinBox.setValue(self.layer.edge_width)

    def _on_edge_color_mode_change(self, event=None):
        """Receive layer model edge color mode change event & update dropdown.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method, by default None.
        """
        with qt_signals_blocked(self.color_mode_comboBox):
            mode = self.layer._edge.color_mode
            index = self.color_mode_comboBox.findText(mode,
                                                      Qt.MatchFixedString)
            self.color_mode_comboBox.setCurrentIndex(index)

            self._update_edge_color_gui(mode)

    def _on_edge_color_change(self, event=None):
        """Receive layer model edge color  change event & update dropdown.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method, by default None.
        """
        if self.layer._edge.color_mode == ColorMode.DIRECT:
            with qt_signals_blocked(self.edgeColorEdit):
                self.edgeColorEdit.setColor(self.layer.edge_color[0])
        elif self.layer._edge.color_mode in (
                ColorMode.CYCLE,
                ColorMode.COLORMAP,
        ):
            with qt_signals_blocked(self.color_prop_box):
                prop = self.layer._edge.color_properties.name
                index = self.color_prop_box.findText(prop, Qt.MatchFixedString)
                self.color_prop_box.setCurrentIndex(index)
示例#12
0
class CropDialog(QDialog):
    def __init__(self, parent, start, stop):
        super().__init__(parent)
        self.setWindowTitle("Crop data")
        vbox = QVBoxLayout(self)
        grid = QGridLayout()
        self.start_checkbox = QCheckBox("Start time:")
        self.start_checkbox.setChecked(True)
        self.start_checkbox.stateChanged.connect(self.toggle_start)
        grid.addWidget(self.start_checkbox, 0, 0)
        self._start = QDoubleSpinBox()
        self._start.setMaximum(999999)
        self._start.setValue(start)
        self._start.setDecimals(2)
        self._start.setSuffix(" s")
        grid.addWidget(self._start, 0, 1)

        self.stop_checkbox = QCheckBox("Stop time:")
        self.stop_checkbox.setChecked(True)
        self.stop_checkbox.stateChanged.connect(self.toggle_stop)
        grid.addWidget(self.stop_checkbox, 1, 0)
        self._stop = QDoubleSpinBox()
        self._stop.setMaximum(999999)
        self._stop.setValue(stop)
        self._stop.setDecimals(2)
        self._stop.setSuffix(" s")
        grid.addWidget(self._stop, 1, 1)
        vbox.addLayout(grid)
        buttonbox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Cancel)
        vbox.addWidget(buttonbox)
        buttonbox.accepted.connect(self.accept)
        buttonbox.rejected.connect(self.reject)
        vbox.setSizeConstraint(QVBoxLayout.SetFixedSize)

    @property
    def start(self):
        if self.start_checkbox.isChecked():
            return self._start.value()
        else:
            return None

    @property
    def stop(self):
        if self.stop_checkbox.isChecked():
            return self._stop.value()
        else:
            return None

    @Slot()
    def toggle_start(self):
        if self.start_checkbox.isChecked():
            self._start.setEnabled(True)
        else:
            self._start.setEnabled(False)

    @Slot()
    def toggle_stop(self):
        if self.stop_checkbox.isChecked():
            self._stop.setEnabled(True)
        else:
            self._stop.setEnabled(False)
示例#13
0
class ImageRotationDialog(ExToolWindow):

    def __init__(self, signal, axes, parent, plugin):
        super(ImageRotationDialog, self).__init__(parent)
        self.ui = parent
        self.create_controls()
        self.accepted.connect(self.ok)
        self.rejected.connect(self.close_new)
        self.signal = signal
        self.plugin = plugin
        self.new_out = None
        self._connected_updates = False
        if isinstance(axes, str):
            axm = signal.signal.axes_manager
            if axes.startswith("nav"):
                axes = (axm._axes.index(axm.navigation_axes[0]),
                        axm._axes.index(axm.navigation_axes[1]))
            elif axes.startswith("sig"):
                axes = (axm._axes.index(axm.signal_axes[0]),
                        axm._axes.index(axm.signal_axes[1]))
        self.axes = axes
        self.setWindowTitle(tr("Rotate"))

        # TODO: TAG: Functionality check
        if not hasattr(signal.signal, 'events'):
            self.gbo_preview.setVisible(False)

        # TODO: Add dynamic rotation, e.g. one that rotates when source
        # signal's data_changed event triggers

    def connect(self):
        # TODO: Don't have to con/dis those in gbo
        self.opt_new.toggled.connect(self.close_new)
        self.num_angle.valueChanged.connect(self.update)
        self.chk_grid.toggled.connect(self.update)
        self.num_grid.valueChanged.connect(self.update)
        self.chk_reshape.toggled.connect(self.update)
        self.opt_new.toggled.connect(self.update)
        self.opt_replace.toggled.connect(self.update)

    def disconnect(self):
        self.num_angle.valueChanged.disconnect(self.update)
        self.chk_grid.toggled.disconnect(self.update)
        self.num_grid.valueChanged.disconnect(self.update)
        self.chk_reshape.toggled.disconnect(self.update)
        self.opt_new.toggled.disconnect(self.update)
        self.opt_replace.toggled.disconnect(self.update)

    def ok(self):
        # Draw figure if not already done
        # TODO: TAG: Functionality check
        if not hasattr(self.signal.signal, 'events') or \
                not self.gbo_preview.isChecked():
            self.update()
        angle = self.num_angle.value()
        reshape = self.chk_reshape.isChecked()
        self.plugin.record_code(
            r"<p>.rotate_signal({0}, reshape={1}, axes={2})".format(
                angle, reshape, self.axes))
        # Clean up event connections
        if self.new_out is not None:
            self.connect_update_plot(self.new_out.signal, disconnect=True)

    def close_new(self, value=False):
        if self.new_out is not None and not value:
            self.new_out.close()
            self.new_out = None
            self._connected_updates = False

    def set_preview(self, value):
        if not hasattr(self.signal.signal, 'events'):
            return
        if value:
            self.connect()
            self.update()
        else:
            self.disconnect()
            self.close_new()

    def _axes_in_nav(self):
        axm = self.signal.signal.axes_manager
        navidx = [axm._axes.index(ax) for ax in axm.navigation_axes]
        if self.axes[0] in navidx:
            return True
        return False

    def connect_update_plot(self, signal, disconnect=False):
        if self._connected_updates != disconnect:
            return  # Nothing to do, prevent double connections
        if self._axes_in_nav():
            f = signal._plot.navigator_plot.update
        else:
            f = signal._plot.signal_plot.update

        # TODO: TAG: Functionality check
        if hasattr(signal, 'events') and hasattr(
                signal.events, 'data_changed'):
            if disconnect:
                signal.events.data_changed.disconnect(f)
            else:
                signal.events.data_changed.connect(f, [])
        self._connected_updates = not disconnect

    def update(self):
        angle = self.num_angle.value()
        reshape = self.chk_reshape.isChecked()
        if self.opt_new.isChecked():
            if self.new_out is None:
                out = None
            else:
                out = self.new_out.signal
        elif self.opt_replace.isChecked():
            out = self.signal.signal
        else:
            return  # Indeterminate state, do nothing

        s = self.plugin.rotate_signal(angle, self.signal.signal, record=False,
                                      reshape=reshape, out=out, axes=self.axes)

        if out is None:
            s.metadata.General.title = self.signal.name + "[Rotated]"
            s.plot()
            self.connect_update_plot(s)
            if (self.gbo_preview.isChecked() and self.opt_new.isChecked() and
                                                         self.new_out is None):
                self.new_out = self.ui.lut_signalwrapper[s]
        else:
            s = out

        if self.chk_grid.isChecked() is True:
            pass    # TODO: Draw grid

    def create_controls(self):
        """
        Create UI controls.
        """
        vbox = QVBoxLayout()

        form = QFormLayout()
        self.num_angle = QDoubleSpinBox()
        self.num_angle.setValue(0.0)
        self.num_angle.setMinimum(-360)
        self.num_angle.setMaximum(360)
        form.addRow(tr("Angle:"), self.num_angle)
        vbox.addLayout(form)

        self.gbo_preview = QGroupBox(tr("Preview"))
        self.gbo_preview.setCheckable(True)
        self.gbo_preview.setChecked(False)
        gbo_vbox = QVBoxLayout()
        self.chk_grid = QCheckBox(tr("Grid"))
        self.chk_grid.setChecked(False)
        self.num_grid = QSpinBox()
        self.num_grid.setValue(4)
        self.num_grid.setMinimum(1)
        self.num_grid.setEnabled(False)
        self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled)
        gbo_vbox.addWidget(self.chk_grid)
        gbo_vbox.addWidget(self.num_grid)
        self.gbo_preview.setLayout(gbo_vbox)
        vbox.addWidget(self.gbo_preview)

        self.gbo_preview.toggled[bool].connect(self.set_preview)

        self.gbo_output = QGroupBox(tr("Output"))
        self.opt_new = QRadioButton(tr("New signal"))
        self.opt_replace = QRadioButton(tr("In place"))
        self.opt_new.setChecked(True)
        gbo_vbox2 = QVBoxLayout()
        gbo_vbox2.addWidget(self.opt_new)
        gbo_vbox2.addWidget(self.opt_replace)
        self.gbo_output.setLayout(gbo_vbox2)
        vbox.addWidget(self.gbo_output)

        self.chk_reshape = QCheckBox(tr("Resize to fit"))
        self.chk_reshape.setChecked(False)
        vbox.addWidget(self.chk_reshape)

        self.btn_ok = QPushButton(tr("&OK"))
        self.btn_ok.setDefault(True)
        self.btn_ok.clicked.connect(self.accept)
        self.btn_cancel = QPushButton(tr("&Cancel"))
        self.btn_cancel.clicked.connect(self.reject)
        hbox = QHBoxLayout()
        hbox.addWidget(self.btn_ok)
        hbox.addWidget(self.btn_cancel)
        vbox.addLayout(hbox)

        vbox.addStretch(1)
        self.setLayout(vbox)
示例#14
0
class BPMIntlkLimSPWidget(BaseObject, QWidget):
    """BPM Orbit Interlock Limit Setpoint Widget."""
    def __init__(self, parent=None, metric='', prefix=''):
        """Init."""
        BaseObject.__init__(self, prefix)
        QWidget.__init__(self, parent)

        # initialize auxiliary attributes
        self.metric = metric.lower()
        if 'sum' in self.metric:
            self.lim_sp = [
                'IntlkLmtMinSum-SP',
            ]
            self.title = 'Min.Sum. Thresholds'
        elif 'trans' in self.metric:
            self.lim_sp = [
                'IntlkLmtTransMinX-SP', 'IntlkLmtTransMaxX-SP',
                'IntlkLmtTransMinY-SP', 'IntlkLmtTransMaxY-SP'
            ]
            self.title = 'Translation Thresholds'
        elif 'ang' in self.metric:
            self.lim_sp = [
                'IntlkLmtAngMinX-SP', 'IntlkLmtAngMaxX-SP',
                'IntlkLmtAngMinY-SP', 'IntlkLmtAngMaxY-SP'
            ]
            self.title = 'Angulation Thresholds'
        else:
            raise ValueError(metric)

        if 'sum' in self.metric:
            self._create_pvs('Sum-Mon')
            self._summon = _np.zeros(len(self.BPM_NAMES), dtype=float)
        else:
            self._set_ref_orb('ref_orb')

        self.prefix = prefix

        # initialize super
        self.setObjectName('SIApp')

        self._setupUi()
        self.setFocusPolicy(Qt.StrongFocus)

    def _setupUi(self):
        title = QLabel(self.title, self, alignment=Qt.AlignCenter)
        title.setStyleSheet("font-weight: bold;")
        title.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Maximum)

        # limit setpoints
        self._wid_lims = QGroupBox('Select Thresholds: ')
        lay_lims = QGridLayout(self._wid_lims)
        lay_lims.setAlignment(Qt.AlignTop)

        row = 0
        if 'sum' in self.metric:
            text = '\nThresholds must be set in Monit1 rate\n'\
                'counts. Here we make available you \n'\
                'to read Sum-Mon values, in Monit rate\n'\
                'counts, and convert them to Monit1\n'\
                'rate counts using calibration curves.\n'\
                'You can also apply a scale factor to\n'\
                'the values read.\n\n'
            self._label_help = QLabel(text, self)
            lay_lims.addWidget(self._label_help, row, 0, 1, 2)

            row += 1
            self._pb_get = QPushButton('Get current Sum-Mon', self)
            self._pb_get.released.connect(self._get_new_sum)
            lay_lims.addWidget(self._pb_get, row, 0, 1, 2)
            row += 1
            self._label_getsts = QLabel('\n', self)
            lay_lims.addWidget(self._label_getsts, row, 0, 1, 2)

            row += 1
            self._label_scl = QLabel('Scale: ', self)
            lay_lims.addWidget(self._label_scl, row, 0)
            self._spin_scl = QDoubleSpinBox(self)
            self._spin_scl.setDecimals(2)
            self._spin_scl.setValue(1.00)
            self._spin_scl.setMinimum(-100.00)
            self._spin_scl.setMaximum(+100.00)
            lay_lims.addWidget(self._spin_scl, row, 1)
        else:
            self._head_value = QLabel('Value', self, alignment=Qt.AlignCenter)
            lay_lims.addWidget(self._head_value, 0, 1)
            self._head_send = QLabel('Apply', self, alignment=Qt.AlignCenter)
            lay_lims.addWidget(self._head_send, 0, 2)

            self._spins, self._checks = dict(), dict()
            for lsp in self.lim_sp:
                row += 1
                text = lsp.split('-')[0].split('Lmt')[1] + ' [nm]: '
                label = QLabel(text, self, alignment=Qt.AlignRight)
                lay_lims.addWidget(label, row, 0)
                spin = QSpinBox(self)
                self._spins[lsp] = spin
                spin.setValue(0)
                spin.setMinimum(-1e9)
                spin.setMaximum(1e9)
                lay_lims.addWidget(spin, row, 1)
                check = QCheckBox(self)
                self._checks[lsp] = check
                lay_lims.addWidget(check, row, 2, alignment=Qt.AlignCenter)

            row += 1
            self._label_reforb = QLabel(
                '\nChoose Ref.Orb. to calculate reference\n' +
                self.title.lower() +
                ' around which we\napply the values above:')
            lay_lims.addWidget(self._label_reforb, row, 0, 1, 3)

            row += 1
            self._cb_reforb = RefOrbComboBox(self)
            self._cb_reforb.refOrbChanged.connect(self._set_ref_orb)
            lay_lims.addWidget(self._cb_reforb, row, 0, 1, 3)

            if 'trans' in self.metric:
                text = '\n\nBPM calculation consider the translation\n' \
                    'estimative:' \
                    '\n\n(pos@downstream+pos@upstream)/2\n\n' \
                    'where the pairs upstream/downstream\nfolow:\n' \
                    '    - M1/M2\n' \
                    '    - C1-1/C1-2\n' \
                    '    - C2/C3-1\n' \
                    '    - C3-2/C4\n'
            elif 'ang' in self.metric:
                text = '\n\nBPM calculation consider the angulation\n' \
                    'estimative:' \
                    '\n\n(pos@downstream - pos@upstream)\n\n' \
                    'where the pairs upstream/downstream\nfolow:\n' \
                    '    - M1/M2\n' \
                    '    - C1-1/C1-2\n' \
                    '    - C2/C3-1\n' \
                    '    - C3-2/C4\n'
            row += 1
            self._label_help = QLabel(text, self)
            lay_lims.addWidget(self._label_help, row, 0, 1, 3)

        # BPM selection
        groupsel = QGroupBox('Select BPMs:')
        groupsel.setObjectName('groupsel')
        groupsel.setStyleSheet('#groupsel{min-width: 27em; max-width: 27em;}')
        self._bpm_sel = _BPMSelectionWidget(self,
                                            show_toggle_all_false=False,
                                            toggle_all_true_text='Select All',
                                            prefix=self.prefix)
        lay_groupsel = QGridLayout(groupsel)
        lay_groupsel.addWidget(self._bpm_sel)

        # connect signals and slots
        self._bpm_sel.applyChangesClicked.connect(self.send_value)

        lay = QGridLayout(self)
        lay.addWidget(title, 0, 0, 1, 2)
        lay.addWidget(self._wid_lims, 1, 0)
        lay.addWidget(groupsel, 1, 1)

    def _set_ref_orb(self, text):
        self._reforb = dict()
        if text.lower() == 'zero':
            self._reforb['x'] = _np.zeros(len(self.BPM_NAMES), dtype=float)
            self._reforb['y'] = _np.zeros(len(self.BPM_NAMES), dtype=float)
        else:
            data = self.get_ref_orb(text)
            self._reforb['x'] = data['x']
            self._reforb['y'] = data['y']

    def _get_new_sum(self):
        self._summon = _np.array(self._get_values('Sum-Mon'), dtype=float)
        text = 'Read at ' + _time.strftime('%d/%m/%Y %H:%M:%S\n',
                                           _time.localtime(_time.time()))
        self._label_getsts.setText(text)

    def send_value(self):
        """Send new value."""
        idxsel, namesel = list(), list()
        for idx, wid in enumerate(self._bpm_sel.widgets):
            name = self.BPM_NAMES[idx]
            check = wid.findChild(QCheckBox)
            if check.isChecked():
                idxsel.append(idx)
                namesel.append(name)
                check.setChecked(False)
        if not namesel:
            return

        if 'sum' in self.metric:
            _av = self.CONV_POLY_MONIT1_2_MONIT[0, :]
            _bv = self.CONV_POLY_MONIT1_2_MONIT[1, :]
            allvals = self._spin_scl.value() * (self._summon - _bv) / _av
            values = allvals[_np.array(idxsel)]
            pvs = [b.substitute(propty=self.lim_sp[0]) for b in namesel]
        else:
            pvs, values = list(), list()
            for lsp in self.lim_sp:
                if not self._checks[lsp].isChecked():
                    continue
                lval = self._spins[lsp].value()
                plan = 'y' if 'y' in lsp.lower() else 'x'
                metric = self.calc_intlk_metric(self._reforb[plan],
                                                metric=self.metric)
                allvals = _np.round(_np.array(metric) + lval)
                allvals = allvals[_np.array(idxsel)]
                values.extend(allvals)
                pvs.extend([b.substitute(propty=lsp) for b in namesel])

        delays = [
            0.0,
        ] * len(pvs)

        self._items_success = list()
        self._items_fail = list()
        conn = EpicsConnector(pvs, parent=self)
        task_set = EpicsSetter(pvs, values, delays, parent=self)
        task_sleep = EpicsWait([
            None,
        ] * 10, wait_time=2.0, parent=self)
        task_check = EpicsChecker(pvs, values, delays, parent=self)
        task_check.itemChecked.connect(self._check_status)

        dlg = ProgressDialog(
            ['Connecting...', 'Setting...', 'Waiting...', 'Checking...'],
            [conn, task_set, task_sleep, task_check],
            parent=self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            return

        if self._items_fail:
            report = ReportDialog(self._items_fail, self)
            report.exec_()
            return

    @Slot(str, bool)
    def _check_status(self, item, status):
        if status:
            self._items_success.append((item, True))
        else:
            self._items_fail.append(item)
示例#15
0
class SliderSpinBox(QWidget):
    valueChanged = Signal(float)

    def __init__(self, parent=None, value_range=(0, 100), slider_steps=100, spinbox_steps=1000, decimals=None):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout(self)
        self.setLayout(layout)
        self.horizontalSlider = QSlider(Qt.Horizontal, self)
        self.doubleSpinBox = QDoubleSpinBox(self)
        self.decimals = decimals
        layout.setMargin(0)
        layout.addWidget(self.doubleSpinBox)
        layout.addWidget(self.horizontalSlider)

        self.doubleSpinBox.valueChanged.connect(self.spinbox_changed)
        self.horizontalSlider.valueChanged.connect(self.slider_changed)

        self.wt = []
        self.changer = None
        self.slider_steps = slider_steps
        self.horizontalSlider.setMaximum(slider_steps)
        self.spinbox_steps = spinbox_steps
        self.set_range(value_range)

    def set_value(self, value):
        self.doubleSpinBox.setValue(value)

    value = property(lambda self: self.doubleSpinBox.value(), set_value)

    def set_range(self, value_range):
        self.changer = "set_range"
        spinbox_step_decimal = np.ceil(np.log10(1. / (float(value_range[1] - value_range[0]) / self.spinbox_steps)))
        self.slider_decimals = int(np.ceil(np.log10(self.slider_steps) - np.log10(value_range[1] - value_range[0])))
        if self.decimals is None:
            self.doubleSpinBox.setDecimals(spinbox_step_decimal + 1)
        else:
            self.doubleSpinBox.setDecimals(self.decimals)
            spinbox_step_decimal = min(self.decimals, spinbox_step_decimal)
            self.slider_decimals = min(self.decimals, self.slider_decimals)
        self.doubleSpinBox.setSingleStep(10 ** -spinbox_step_decimal)


        self.value_range = value_range
        self.doubleSpinBox.setMinimum(value_range[0])
        self.doubleSpinBox.setMaximum(value_range[1])

        self.horizontalSlider.setValue(np.floor(value_range[0]))
        self.changer = None
        self.doubleSpinBox.setValue(value_range[0])

    def range(self):
        return self.doubleSpinBox.minimum(), self.doubleSpinBox.maximum()

    def slider_changed(self, value):
        if self.changer is None:
            self.changer = 'slider'
            value = np.round(value * (self.value_range[1] - self.value_range[0]) / self.slider_steps + self.value_range[0], self.slider_decimals)

            self.doubleSpinBox.setValue(value)
            self.changer = None
            self.value_changed(value)

    def spinbox_changed(self, value):
        if self.changer is None:
            self.changer = 'spinbox'

            slider_value = .5 + (value - self.value_range[0]) * self.slider_steps / (self.value_range[1] - self.value_range[0])
            self.horizontalSlider.setValue(slider_value)
            self.changer = None
            self.value_changed(value)

    @postpone_until_last_call_finishes
    def value_changed(self, value):
        self.valueChanged.emit(value)
示例#16
0
class _LinkLengthDialog(QDialog):
    """Link length dialog."""
    vlinks: Dict[str, FrozenSet[int]]

    def __init__(self, parent: MainWindowBase):
        super(_LinkLengthDialog, self).__init__(parent)
        self.setWindowTitle("Set Link Length")
        self.main_layout = QVBoxLayout(self)
        layout = QHBoxLayout()
        self.leader = QComboBox(self)
        self.follower = QComboBox(self)
        self.length = QDoubleSpinBox(self)
        layout.addWidget(self.leader)
        layout.addWidget(self.follower)
        layout.addWidget(self.length)
        self.main_layout.addLayout(layout)

        self.vpoints = parent.vpoint_list
        self.vlinks = {
            vlink.name: frozenset(vlink.points)
            for vlink in parent.vlink_list
        }
        self.leader.currentTextChanged.connect(self.__set_follower)
        self.follower.currentTextChanged.connect(self.__set_length)
        self.leader.addItems([f"P{i}" for i in range(len(self.vpoints))])
        self.leader.setCurrentIndex(0)
        self.length.setMaximum(100000)

        button_box = QDialogButtonBox(self)
        button_box.setStandardButtons(QDialogButtonBox.Ok
                                      | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        button_box.button(QDialogButtonBox.Ok).setEnabled(
            bool(parent.vpoint_list))
        self.main_layout.addWidget(button_box)

    @Slot(str)
    def __set_follower(self, leader: str) -> None:
        """Set follower options."""
        self.follower.clear()
        n = int(leader.replace('P', ''))
        options: Set[int] = set()
        for name, points in self.vlinks.items():
            if name == VLink.FRAME:
                continue
            if n in points:
                options.update(points)
        options.discard(n)
        self.follower.addItems([f"P{i}" for i in options])

    @Slot(str)
    def __set_length(self, follower: str) -> None:
        """Set the current length of two points."""
        if not follower:
            return
        n1 = self.get_leader()
        n2 = int(follower.replace('P', ''))
        self.length.setValue(self.vpoints[n1].distance(self.vpoints[n2]))

    def get_leader(self) -> int:
        """Get current leader."""
        return int(self.leader.currentText().replace('P', ''))

    def get_follower(self) -> int:
        """Get current follower."""
        return int(self.follower.currentText().replace('P', ''))

    def get_length(self) -> float:
        """Get current length."""
        return self.length.value()
示例#17
0
class DepthGUI(QMainWindow):
    def __init__(self):
        super(DepthGUI, self).__init__()

        self.display_string = None

        # Serial port config
        self.ser = serial.Serial()
        self.ser.baudrate = 19200

        self.depth_stream = None
        self._prev_port = None

        self.setup_ui()

    def setup_ui(self):
        self.setWindowTitle('Neuroport DBS - Electrodes Depth')
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.move(WINDOWDIMS_DEPTH[0], WINDOWDIMS_DEPTH[1])
        self.setFixedSize(WINDOWDIMS_DEPTH[2], WINDOWDIMS_DEPTH[3])

        self.show()

        self.plot_widget = QWidget()
        self.setCentralWidget(self.plot_widget)

        # define Qt GUI elements
        v_layout = QVBoxLayout()
        v_layout.setSpacing(0)
        v_layout.setContentsMargins(10, 0, 10, 10)

        h_layout = QHBoxLayout()

        self.comboBox_com_port = QComboBox()
        h_layout.addWidget(self.comboBox_com_port)

        self.pushButton_open = QPushButton("Open")
        h_layout.addWidget(self.pushButton_open)
        h_layout.addStretch()

        self.label_offset = QLabel("Offset: ")
        h_layout.addWidget(self.label_offset)

        self.doubleSpinBox_offset = QDoubleSpinBox()
        self.doubleSpinBox_offset.setMinimum(-100.00)
        self.doubleSpinBox_offset.setMaximum(100.00)
        self.doubleSpinBox_offset.setSingleStep(1.00)
        self.doubleSpinBox_offset.setDecimals(2)
        self.doubleSpinBox_offset.setValue(-10.00)
        self.doubleSpinBox_offset.setFixedWidth(60)
        h_layout.addWidget(self.doubleSpinBox_offset)

        h_layout.addStretch()

        h_layout.addWidget(QLabel("Stream to :"))

        self.chk_NSP = QCheckBox("NSP")
        self.chk_NSP.setChecked(True)
        h_layout.addWidget(self.chk_NSP)

        h_layout.addSpacing(5)

        self.chk_LSL = QCheckBox("LSL")
        self.chk_LSL.clicked.connect(self.on_chk_LSL_clicked)
        self.chk_LSL.click(
        )  # default is enabled, click call to trigger LSL stream creation.
        h_layout.addWidget(self.chk_LSL)

        h_layout.addSpacing(5)

        send_btn = QPushButton("Send")
        send_btn.clicked.connect(self.send)

        h_layout.addWidget(send_btn)
        h_layout.addSpacing(5)

        quit_btn = QPushButton('X')
        quit_btn.setMaximumWidth(20)
        quit_btn.clicked.connect(QApplication.instance().quit)

        quit_btn.setStyleSheet("QPushButton { color: white; "
                               "background-color : red; "
                               "border-color : red; "
                               "border-width: 2px}")

        h_layout.addWidget(quit_btn)

        v_layout.addLayout(h_layout)

        # define Qt GUI elements
        # add a frame for the LCD numbers
        self.lcd_frame = QFrame()
        self.lcd_frame.setFrameShape(1)
        lcd_layout = QGridLayout()
        self.lcd_frame.setLayout(lcd_layout)

        # RAW reading from DDU
        self.raw_ddu = QLCDNumber()
        self.raw_ddu.setDigitCount(7)
        self.raw_ddu.setFrameShape(0)
        self.raw_ddu.setSmallDecimalPoint(True)
        self.raw_ddu.setFixedHeight(50)
        self.raw_ddu.display("{0:.3f}".format(0))
        lcd_layout.addWidget(self.raw_ddu, 0, 3, 2, 3)

        self.offset_ddu = QLCDNumber()
        self.offset_ddu.setDigitCount(7)
        self.offset_ddu.setFixedHeight(150)
        self.offset_ddu.display("{0:.3f}".format(-10))
        self.offset_ddu.setFrameShape(0)
        lcd_layout.addWidget(self.offset_ddu, 2, 0, 5, 6)
        v_layout.addWidget(self.lcd_frame)

        self.plot_widget.setLayout(v_layout)

        # Populate control panel items
        for port in serial.tools.list_ports.comports():
            self.comboBox_com_port.addItem(port.device)
        self.comboBox_com_port.addItem("cbsdk playback")

        # Connect signals & slots
        self.pushButton_open.clicked.connect(self.on_open_clicked)
        self.comboBox_com_port.currentIndexChanged.connect(
            self.on_comboBox_com_port_changed)
        # TODO: Add signal for comboBox_com_port --> when cbsdk playback, uncheck NSP then re-open connection.

    def on_chk_LSL_clicked(self, state):
        print(f"LSL clicked state: {state}")
        if self.chk_LSL.isChecked():
            outlet_info = pylsl.StreamInfo(name='electrode_depth',
                                           type='depth',
                                           channel_count=1,
                                           nominal_srate=pylsl.IRREGULAR_RATE,
                                           channel_format=pylsl.cf_float32,
                                           source_id='depth1214')
            self.depth_stream = pylsl.StreamOutlet(outlet_info)
        else:
            self.depth_stream = None

    def _do_close(self, from_port):
        if from_port == "cbsdk playback":
            CbSdkConnection().disconnect()
        else:
            self.ser.close()
        self.pushButton_open.setText("Open")

    def on_comboBox_com_port_changed(self, new_ix):
        # If a connection was already open, close it before proceeding.
        if self.pushButton_open.text() == "Close":
            self._do_close(self._prev_port)

        # at this point the Open/Close pushbutton should show: Open
        # we will only enable/disable the Send to NSP button to leave the current checked status.
        # The default should be checked.
        if self.comboBox_com_port.currentText() == "cbsdk playback":
            # If switching _to_ cbsdk playback, disable sending out comments.
            self.chk_NSP.setEnabled(False)

        elif self._prev_port == "cbsdk playback":
            # If switching _from_ cbsdk playback, re-enable sending out comments.
            self.chk_NSP.setEnabled(True)

        self._prev_port = self.comboBox_com_port.currentText()

    def on_open_clicked(self):
        com_port = self.comboBox_com_port.currentText()
        cmd_text = self.pushButton_open.text()
        if cmd_text == 'Open':

            if com_port == "cbsdk playback":
                CbSdkConnection().connect()
                CbSdkConnection().cbsdk_config = {
                    'reset': True,
                    'get_events': False,
                    'get_comments': True,
                    'buffer_parameter': {
                        'comment_length': 10
                    }
                }
                self.pushButton_open.setText("Close")
            else:
                if self.chk_NSP.isEnabled() and self.chk_NSP.isChecked():
                    CbSdkConnection().connect()
                    CbSdkConnection().cbsdk_config = {
                        'reset': True,
                        'get_events': False,
                        'get_comments': False
                    }

                if not self.ser.is_open:
                    self.ser.port = com_port
                    try:
                        self.ser.open()  # TODO: Add timeout; Add error.
                        self.ser.write('AXON+\r'.encode())
                    except serial.serialutil.SerialException:
                        print("Could not open serial port")
                    finally:
                        self.pushButton_open.setText(
                            "Close" if self.ser.is_open else "Open")
        else:
            self._do_close(com_port)

    def update(self):
        # Added new_value handling for playback if we ever want to post-process depth
        # on previously recorded sessions.
        new_value = False
        out_value = None

        if self.comboBox_com_port.currentText() == "cbsdk playback":
            cbsdk_conn = CbSdkConnection()
            if cbsdk_conn.is_connected:
                comments = cbsdk_conn.get_comments()
                if comments:
                    comment_strings = [x[1].decode('utf8') for x in comments]
                else:
                    comment_strings = ""
                dtts = []
                for comm_str in comment_strings:
                    if 'DTT:' in comm_str:
                        dtts.append(float(comm_str[4:]))
                if len(dtts) > 0:
                    out_value = dtts[-1]
                    new_value = True
                    self.offset_ddu.display("{0:.3f}".format(out_value))
                    offset = self.doubleSpinBox_offset.value()
                    self.raw_ddu.display("{0:.3f}".format(out_value - offset))

        elif self.ser.is_open:
            in_str = self.ser.readline().decode('utf-8').strip()
            if in_str:
                try:
                    in_value = float(in_str)
                    # in_value /= DDUSCALEFACTOR  # Uncomment this for FHC DDU V2.

                    self.raw_ddu.display("{0:.3f}".format(in_value))

                    out_value = in_value + self.doubleSpinBox_offset.value()
                    display_string = "{0:.3f}".format(out_value)
                    self.offset_ddu.display(display_string)

                    # Check if new value
                    if display_string != self.display_string:
                        new_value = True
                        self.display_string = display_string

                    # Push to NSP
                    cbsdk_conn = CbSdkConnection()
                    if cbsdk_conn.is_connected:
                        if self.chk_NSP.isChecked() and self.chk_NSP.isEnabled(
                        ) and new_value:
                            cbsdk_conn.set_comments("DTT:" + display_string)
                    else:
                        # try connecting if not connected but button is active
                        if self.chk_NSP.isChecked() and self.chk_NSP.isEnabled(
                        ):
                            cbsdk_conn.connect()
                            cbsdk_conn.cbsdk_config = {
                                'reset': True,
                                'get_events': False,
                                'get_comments': False
                            }
                        # set button to connection status
                        self.chk_NSP.setChecked(cbsdk_conn.is_connected)

                except ValueError:
                    print("DDU result: {}".format(in_str))

        # Push to LSL
        if self.depth_stream is not None and new_value:
            self.depth_stream.push_sample([out_value])

    def send(self):
        self.display_string = None  # make sure the update function runs
        self.update()