Пример #1
0
class ParamLineEdit(ParamWidget):
    """
    QLineEdit for typed user entry control.

    Parameter
    ---------
    parameter : str
        Name of parameter this widget controls.

    _type : type
        Type to convert the text to before sending it to the function. All
        values are initially `QString`s and then they are converted to the
        specified type. If this raises a ``ValueError`` due to an improperly
        entered value a ``np.nan`` is returned.

    default : bool, optional
        Default text for the QLineEdit. This if automatically populated into
        the QLineEdit field and it is also set as the ``placeHolderText``.

    parent : QWidget, optional
    """
    def __init__(self, parameter, _type, default='', parent=None):
        super().__init__(parameter, default=default, parent=parent)
        # Store type information
        self._type = _type
        # Create our LineEdit
        # Set our default text
        self.param_edit = QLineEdit(parent=self)
        self.param_edit.setAlignment(Qt.AlignCenter)
        self.layout().addWidget(self.param_edit)
        # Configure default text of LineEdit
        # If there is no default, still give some placeholder text
        # to indicate the type of the command needed
        if default != inspect._empty:
            self.param_edit.setText(str(self.default))
            self.param_edit.setPlaceholderText(str(self.default))
        elif self._type in (int, float):
            self.param_edit.setPlaceholderText(str(self._type(0.0)))

    def get_param_value(self):
        """
        Return the current value of the QLineEdit converted to :attr:`._type`.
        """
        # Cast the current text into our type
        try:
            val = self._type(self.param_edit.text())
        # If not possible, capture the exception and report `np.nan`
        except ValueError:
            logger.exception("Could not convert text to %r",
                             self._type.__name__)
            val = np.nan
        return val
Пример #2
0
 def _create_axis_label_widget(self):
     """Create the axis label widget which accompanies its slider."""
     label = QLineEdit(self)
     label.setObjectName('axis_label')  # needed for _update_label
     label.setText(self.dims.axis_labels[self.axis])
     label.home(False)
     label.setToolTip('Edit to change axis label')
     label.setAcceptDrops(False)
     label.setEnabled(True)
     label.setAlignment(Qt.AlignRight)
     label.setContentsMargins(0, 0, 2, 0)
     label.textChanged.connect(self._update_label)
     label.editingFinished.connect(self._clear_label_focus)
     self.axis_label = label
Пример #3
0
    def createEditor(self, parent, option, index):
        """Create editor widget"""
        model = index.model()
        # TODO: dtype should be taken from the model instead (or even from the actual value?)
        value = model.get_value(index)
        if self.dtype.name == "bool":
            # toggle value
            value = not value
            model.setData(index, to_qvariant(value))
            return
        elif value is not np.ma.masked:
            minvalue, maxvalue = self.minvalue, self.maxvalue
            if minvalue is not None and maxvalue is not None:
                msg = "value must be between %s and %s" % (minvalue, maxvalue)
            elif minvalue is not None:
                msg = "value must be >= %s" % minvalue
            elif maxvalue is not None:
                msg = "value must be <= %s" % maxvalue
            else:
                msg = None

            # Not using a QSpinBox for integer inputs because I could not find
            # a way to prevent the spinbox/editor from closing if the value is
            # invalid. Using the builtin minimum/maximum of the spinbox works
            # but that provides no message so it is less clear.
            editor = QLineEdit(parent)
            if is_number(self.dtype):
                validator = QDoubleValidator(editor) if is_float(self.dtype) \
                    else QIntValidator(editor)
                if minvalue is not None:
                    validator.setBottom(minvalue)
                if maxvalue is not None:
                    validator.setTop(maxvalue)
                editor.setValidator(validator)

                def on_editor_text_edited():
                    if not editor.hasAcceptableInput():
                        QToolTip.showText(editor.mapToGlobal(QPoint()), msg)
                    else:
                        QToolTip.hideText()

                if msg is not None:
                    editor.textEdited.connect(on_editor_text_edited)

            editor.setFont(self.font)
            editor.setAlignment(Qt.AlignRight)
            editor.destroyed.connect(self.on_editor_destroyed)
            self.editor_count += 1
            return editor
Пример #4
0
 def createEditor(self, parent, option, index):
     """Create editor widget"""
     model = index.model()
     value = model.get_value(index)
     if model._data.dtype.name == "bool":
         value = not value
         model.setData(index, to_qvariant(value))
         return
     elif value is not np.ma.masked:
         editor = QLineEdit(parent)
         # editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
         editor.setAlignment(Qt.AlignCenter)
         if is_number(self.dtype):
             editor.setValidator(QDoubleValidator(editor))
         editor.returnPressed.connect(self.commitAndCloseEditor)
         return editor
Пример #5
0
 def createEditor(self, parent, option, index):
     """Create editor widget"""
     model = index.model()
     value = model.get_value(index)
     if model._data.dtype.name == "bool":
         value = not value
         model.setData(index, to_qvariant(value))
         return
     elif value is not np.ma.masked:
         editor = QLineEdit(parent)
         editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
         editor.setAlignment(Qt.AlignCenter)
         if is_number(self.dtype):
             editor.setValidator(QDoubleValidator(editor))
         editor.returnPressed.connect(self.commitAndCloseEditor)
         return editor
Пример #6
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setHighlightSections(True)
        self.setStretchLastSection(True)

        lineEdit = QLineEdit(self.viewport())
        lineEdit.setAlignment(Qt.AlignCenter)
        lineEdit.setHidden(True)
        lineEdit.blockSignals(True)
        lineEdit.editingFinished.connect(self.finishEdit)
        self.lineEdit = lineEdit

        self.sectionEdit = 0

        self.sectionDoubleClicked.connect(self.startEdit)
Пример #7
0
    def startEdit(self, tabIndex: int) -> None:
        self.editingTabIndex = tabIndex
        rect: QRect = self.tabRect(tabIndex)

        topMargin = 3
        leftMargin = 6

        lineEdit = QLineEdit(self)
        lineEdit.setAlignment(Qt.AlignCenter)
        lineEdit.move(rect.left() + leftMargin, rect.top() + topMargin)
        lineEdit.resize(rect.width() - 2 * leftMargin,
                        rect.height() - 2 * topMargin)
        lineEdit.setText(self.tabText(tabIndex))
        lineEdit.selectAll()
        lineEdit.setFocus()
        lineEdit.show()
        lineEdit.editingFinished.connect(self.finishEdit)
        self.lineEdit = lineEdit
Пример #8
0
 def createEditor(self, parent, option, index):
     """Create editor widget"""
     model = index.model()
     value = model.get_value(index)
     if type(value) == np.ndarray or model.readonly:
         # The editor currently cannot properly handle this case
         return
     elif model._data.dtype.name == "bool":
         value = not value
         model.setData(index, to_qvariant(value))
         return
     elif value is not np.ma.masked:
         editor = QLineEdit(parent)
         editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
         editor.setAlignment(Qt.AlignCenter)
         if is_number(self.dtype):
             validator = QDoubleValidator(editor)
             validator.setLocale(QLocale('C'))
             editor.setValidator(validator)
         editor.returnPressed.connect(self.commitAndCloseEditor)
         return editor
Пример #9
0
class BMWidget(QWidget):
    def __init__(self, points, *args, **kwargs):
        super(BMWidget, self).__init__()
        self.plot_nb = 0
        self.curve_nb = 0
        self.setup(points, *args, **kwargs)

    def params(self, *args, **kwargs):
        if kwargs.get("only_lines", False):
            return (("Lines", None), )
        else:
            return (
                ("Lines", None),
                ("Dots", None),
            )

    def setup(self, points, *args, **kwargs):
        x = np.linspace(0.001, 20.0, int(points))
        y = (np.sin(x) / x) * np.cos(20 * x)
        layout = QGridLayout()
        nbcol, col, row = 2, 0, 0
        for style, symbol in self.params(*args, **kwargs):
            plot = BMPlot(style,
                          x,
                          y,
                          getattr(QwtPlotCurve, style),
                          symbol=symbol)
            layout.addWidget(plot, row, col)
            self.plot_nb += 1
            self.curve_nb += plot.curve_nb
            col += 1
            if col >= nbcol:
                row += 1
                col = 0
        self.text = QLineEdit()
        self.text.setReadOnly(True)
        self.text.setAlignment(Qt.AlignCenter)
        self.text.setText("Rendering plot...")
        layout.addWidget(self.text, row + 1, 0, 1, 2)
        self.setLayout(layout)
Пример #10
0
    def createEditor(self, parent, option, index):
        # ToDo: set dec placed for IN and MM machines
        col = self._columns[index.column()]

        if col == 'R':
            editor = QLineEdit(parent)
            editor.setFrame(False)
            margins = editor.textMargins()
            padding = editor.fontMetrics().width(self._padding) + 1
            margins.setLeft(margins.left() + padding)
            editor.setTextMargins(margins)
            return editor

        elif col in 'TPQ':
            editor = QSpinBox(parent)
            editor.setFrame(False)
            editor.setAlignment(Qt.AlignCenter)
            if col == 'Q':
                editor.setMaximum(9)
            else:
                editor.setMaximum(99999)
            return editor

        elif col in 'XYZABCUVWD':
            editor = QDoubleSpinBox(parent)
            editor.setFrame(False)
            editor.setAlignment(Qt.AlignCenter)
            editor.setDecimals(4)
            # editor.setStepType(QSpinBox.AdaptiveDecimalStepType)
            editor.setProperty('stepType', 1)  # stepType was added in 5.12
            editor.setRange(-1000, 1000)
            return editor

        elif col in 'IJ':
            editor = QDoubleSpinBox(parent)
            editor.setFrame(False)
            editor.setAlignment(Qt.AlignCenter)
            editor.setMaximum(360.0)
            editor.setMinimum(0.0)
            editor.setDecimals(4)
            # editor.setStepType(QSpinBox.AdaptiveDecimalStepType)
            editor.setProperty('stepType', 1)  # stepType was added in 5.12
            return editor

        return None
Пример #11
0
class FileChoose(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, settings: PartSettings, batch_manager, parent=None):
        QWidget.__init__(self, parent)
        self.files_to_proceed = set()
        self.settings = settings
        self.batch_manager = batch_manager
        self.files_widget = AddFiles(settings, self)
        self.progress = ProgressView(self, batch_manager)
        self.run_button = QPushButton("Process")
        self.run_button.setDisabled(True)
        self.calculation_choose = QComboBox()
        self.calculation_choose.addItem("<no workflow>")
        self.calculation_choose.currentIndexChanged[str].connect(
            self.change_situation)
        self.result_file = QLineEdit(self)
        self.result_file.setAlignment(Qt.AlignRight)
        self.result_file.setReadOnly(True)
        self.chose_result = QPushButton("Save result as", self)
        self.chose_result.clicked.connect(self.chose_result_file)

        self.run_button.clicked.connect(self.prepare_calculation)
        self.files_widget.file_list_changed.connect(self.change_situation)

        layout = QVBoxLayout()
        layout.addWidget(self.files_widget)
        calc_layout = QHBoxLayout()
        calc_layout.addWidget(QLabel("Batch workflow:"))
        calc_layout.addWidget(self.calculation_choose)
        calc_layout.addWidget(self.result_file)
        calc_layout.addWidget(self.chose_result)
        calc_layout.addStretch()
        calc_layout.addWidget(self.run_button)
        layout.addLayout(calc_layout)
        layout.addWidget(self.progress)
        self.setLayout(layout)

    def prepare_calculation(self):
        plan = self.settings.batch_plans[str(
            self.calculation_choose.currentText())]
        dial = CalculationPrepare(self.files_widget.get_paths(), plan,
                                  str(self.result_file.text()), self.settings,
                                  self.batch_manager)
        if dial.exec_():
            self.batch_manager.add_calculation(dial.get_data())
            self.progress.new_task()

    def showEvent(self, _):
        current_calc = str(self.calculation_choose.currentText())
        new_list = ["<no calculation>"] + list(
            sorted(self.settings.batch_plans.keys()))
        try:
            index = new_list.index(current_calc)
        except ValueError:
            index = 0
        self.calculation_choose.clear()
        self.calculation_choose.addItems(new_list)
        self.calculation_choose.setCurrentIndex(index)

    def change_situation(self):
        if (str(self.calculation_choose.currentText()) != "<no calculation>"
                and len(self.files_widget.files_to_proceed) != 0
                and str(self.result_file.text()) != ""):
            self.run_button.setEnabled(True)
        else:
            self.run_button.setDisabled(True)

    def chose_result_file(self):
        dial = SaveDialog({SaveExcel.get_short_name(): SaveExcel},
                          system_widget=False,
                          history=self.settings.get_path_history())
        dial.setDirectory(
            self.settings.get(
                "io.save_directory",
                self.settings.get("io.open_directory", str(Path.home()))))
        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            self.settings.add_path_history(os.path.dirname(file_path))
            if os.path.splitext(file_path)[1] == "":
                file_path += ".xlsx"
            self.result_file.setText(file_path)
            self.change_situation()
Пример #12
0
class InteractionVolumeView(VolumeView):

    signalAxisChanged = Signal(int)

    def __init__(self, label=None, **kwargs):

        super(InteractionVolumeView, self).__init__(**kwargs)

        self.interactionFrame = QFrame(self)
        self.interactionFrame.setSizePolicy(
            QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))
        self.interactionFrame.setFixedHeight(30)
        self.interactionLayout = QHBoxLayout(self.interactionFrame)
        self.interactionLayout.setContentsMargins(5, 5, 5, 5)

        self.sliceLabel = QLabel(self.interactionFrame)
        self.sliceLabel.setSizePolicy(
            QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
        self.updateSliceLabel()

        self.textLabel = QLineEdit(self.interactionFrame)
        self.textLabel.setReadOnly(True)
        self.textLabel.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.textLabel.setAlignment(Qt.AlignCenter)
        stylesheet = \
            "QLineEdit {\n" \
            + "border: none;\n" \
            + "background-color: rgba(255, 255, 255, 0);\n" \
            + "}"
        self.textLabel.setStyleSheet(stylesheet)
        if label is not None:
            self.updateTextLabel(label)

        self.interactionLayout.addWidget(self.sliceLabel)
        self.interactionLayout.addWidget(self.textLabel)

        self.axisSelector = QComboBox(self.interactionFrame)
        self.axisSelector.setFixedWidth(40)
        self.axisSelector.insertItems(0, ["0", "1", "2"])
        self.interactionLayout.addWidget(self.axisSelector)

        self.layout.addWidget(self.interactionFrame)

        self.signalSliceChanged.connect(self.updateSliceLabel)
        self.axisSelector.currentIndexChanged.connect(self.setAxisAndEmit)

    def setAxisAndEmit(self, axis):
        self.setAxis(axis)
        self.signalAxisChanged.emit(axis)

    def setAxis(self, axis):
        super(InteractionVolumeView, self).setAxis(axis)
        self.axisSelector.setCurrentIndex(axis)
        self.updateSliceLabel()

    def setSlice(self, slice_):
        super(InteractionVolumeView, self).setSlice(slice_)
        self.updateSliceLabel()

    def updateSliceLabel(self):
        try:
            length = str(len(str(self.numberOfSlices)))
            self.sliceLabel.setText(
                ("{:0" + length + "d}/{:0" + length + "}").format(
                    self.currentSlice, self.numberOfSlices))
        except AttributeError:
            pass

    def updateTextLabel(self, text):
        try:
            self.textLabel.setText(text)
            self.textLabel.setCursorPosition(0)
        except AttributeError:
            pass
Пример #13
0
class QtHighlightSizePreviewWidget(QDialog):
    """Creates custom widget to set highlight size.

    Parameters
    ----------
    description : str
        Text to explain and display on widget.
    value : int
        Value of highlight size.
    min_value : int
        Minimum possible value of highlight size.
    max_value : int
        Maximum possible value of highlight size.
    unit : str
        Unit of highlight size.
    """

    valueChanged = Signal(int)

    def __init__(
        self,
        parent: QWidget = None,
        description: str = "",
        value: int = 1,
        min_value: int = 1,
        max_value: int = 10,
        unit: str = "px",
    ):
        super().__init__(parent)

        self.setGeometry(300, 300, 125, 110)
        self._value = value if value else self.fontMetrics().height()
        self._min_value = min_value
        self._max_value = max_value

        # Widget
        self._lineedit = QLineEdit()
        self._description = QLabel(self)
        self._unit = QLabel(self)
        self._slider = QSlider(Qt.Horizontal)
        self._triangle = QtTriangle(self)
        self._slider_min_label = QLabel(self)
        self._slider_max_label = QLabel(self)
        self._preview = QtStar(self)
        self._preview_label = QLabel(self)
        self._validator = QIntValidator(min_value, max_value, self)

        # Widgets setup
        self._description.setText(description)
        self._description.setWordWrap(True)
        self._unit.setText(unit)
        self._unit.setAlignment(Qt.AlignBottom)
        self._lineedit.setValidator(self._validator)
        self._lineedit.setAlignment(Qt.AlignRight)
        self._lineedit.setAlignment(Qt.AlignBottom)
        self._slider_min_label.setText(str(min_value))
        self._slider_min_label.setAlignment(Qt.AlignBottom)
        self._slider_max_label.setText(str(max_value))
        self._slider_max_label.setAlignment(Qt.AlignBottom)
        self._slider.setMinimum(min_value)
        self._slider.setMaximum(max_value)
        self._preview.setValue(value)
        self._triangle.setValue(value)
        self._triangle.setMinimum(min_value)
        self._triangle.setMaximum(max_value)
        self._preview_label.setText(trans._("Preview"))
        self._preview_label.setAlignment(Qt.AlignHCenter)
        self._preview_label.setAlignment(Qt.AlignBottom)
        self._preview.setStyleSheet('border: 1px solid white;')

        # Signals
        self._slider.valueChanged.connect(self._update_value)
        self._lineedit.textChanged.connect(self._update_value)
        self._triangle.valueChanged.connect(self._update_value)

        # Layout
        triangle_layout = QHBoxLayout()
        triangle_layout.addWidget(self._triangle)
        triangle_layout.setContentsMargins(6, 35, 6, 0)
        triangle_slider_layout = QVBoxLayout()
        triangle_slider_layout.addLayout(triangle_layout)
        triangle_slider_layout.setContentsMargins(0, 0, 0, 0)
        triangle_slider_layout.addWidget(self._slider)
        triangle_slider_layout.setAlignment(Qt.AlignVCenter)

        # Bottom row layout
        lineedit_layout = QHBoxLayout()
        lineedit_layout.addWidget(self._lineedit)
        lineedit_layout.setAlignment(Qt.AlignBottom)
        bottom_left_layout = QHBoxLayout()
        bottom_left_layout.addLayout(lineedit_layout)
        bottom_left_layout.addWidget(self._unit)
        bottom_left_layout.addWidget(self._slider_min_label)
        bottom_left_layout.addLayout(triangle_slider_layout)
        bottom_left_layout.addWidget(self._slider_max_label)
        bottom_left_layout.setAlignment(Qt.AlignBottom)

        left_layout = QVBoxLayout()
        left_layout.addWidget(self._description)
        left_layout.addLayout(bottom_left_layout)
        left_layout.setAlignment(Qt.AlignLeft)

        preview_label_layout = QHBoxLayout()
        preview_label_layout.addWidget(self._preview_label)
        preview_label_layout.setAlignment(Qt.AlignHCenter)

        preview_layout = QVBoxLayout()
        preview_layout.addWidget(self._preview)
        preview_layout.addLayout(preview_label_layout)
        preview_layout.setAlignment(Qt.AlignCenter)

        layout = QHBoxLayout()
        layout.addLayout(left_layout)
        layout.addLayout(preview_layout)

        self.setLayout(layout)

        self._refresh()

    def _update_value(self, value):
        """Update highlight value.

        Parameters
        ----------
        value: int
            Highlight value.
        """
        if value == "":
            value = int(self._value)

        value = int(value)

        if value > self._max_value:
            value = self._max_value
        elif value < self._min_value:
            value = self._min_value

        if value != self._value:
            self.valueChanged.emit(value)

        self._value = value
        self._refresh()

    def _refresh(self):
        """Set every widget value to the new set value."""
        self.blockSignals(True)
        self._lineedit.setText(str(self._value))
        self._slider.setValue(self._value)
        self._triangle.setValue(self._value)
        self._preview.setValue(self._value)
        self.blockSignals(False)
        self.valueChanged.emit(self._value)

    def value(self):
        """Return current value.

        Returns
        -------
        int
            Current value of highlight widget.
        """
        return self._value

    def setValue(self, value):
        """Set new value and update widget.

        Parameters
        ----------
        value: int
            Highlight value.
        """
        self._update_value(value)
        self._refresh()

    def description(self):
        """Return the description text.

        Returns
        -------
        str
            Current text in description.
        """
        return self._description.text()

    def setDescription(self, text):
        """Set the description text.

        Parameters
        ----------
        text: str
            Text to use in description box.
        """
        self._description.setText(text)

    def unit(self):
        """Return highlight value unit text.

        Returns
        -------
        str
            Current text in unit text.
        """
        return self._unit.text()

    def setUnit(self, text):
        """Set highlight value unit.

        Parameters
        ----------
        text: str
            Text used to describe units.
        """
        self._unit.setText(text)

    def setMinimum(self, value):
        """Set minimum highlight value for star, triangle, text and slider.

        Parameters
        ----------
        value: int
            Minimum highlight value.
        """
        value = int(value)
        if value < self._max_value:
            self._min_value = value
            self._slider_min_label.setText(str(value))
            self._slider.setMinimum(value)
            self._triangle.setMinimum(value)
            self._value = (self._min_value
                           if self._value < self._min_value else self._value)
            self._refresh()
        else:
            raise ValueError(
                f"Minimum value must be smaller than {self._max_value}")

    def minimum(self):
        """Return minimum highlight value.

        Returns
        -------
        int
            Minimum value of highlight widget.
        """
        return self._min_value

    def setMaximum(self, value):
        """Set maximum highlight value.

        Parameters
        ----------
        value: int
            Maximum highlight value.
        """
        value = int(value)
        if value > self._min_value:
            self._max_value = value
            self._slider_max_label.setText(str(value))
            self._slider.setMaximum(value)
            self._triangle.setMaximum(value)
            self._value = (self._max_value
                           if self._value > self._max_value else self._value)
            self._refresh()
        else:
            raise ValueError(
                f"Maximum value must be larger than {self._min_value}")

    def maximum(self):
        """Return maximum highlight value.

        Returns
        -------
        int
            Maximum value of highlight widget.
        """
        return self._max_value
Пример #14
0
class FileChoose(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, settings: PartSettings, batch_manager, parent=None):
        super().__init__(parent)
        self.files_to_proceed = set()
        self.settings = settings
        self.batch_manager = batch_manager
        self.files_widget = AddFiles(settings, self)
        self.progress = ProgressView(self, batch_manager)
        self.run_button = QPushButton("Process")
        self.run_button.setDisabled(True)
        self.calculation_choose = SearchComboBox()
        self.calculation_choose.addItem("<no calculation>")
        self.calculation_choose.currentIndexChanged[str].connect(
            self.change_situation)
        self.result_file = QLineEdit(self)
        self.result_file.setAlignment(Qt.AlignRight)
        self.result_file.setReadOnly(True)
        self.chose_result = QPushButton("Save result as", self)
        self.chose_result.clicked.connect(self.chose_result_file)

        self.run_button.clicked.connect(self.prepare_calculation)
        self.files_widget.file_list_changed.connect(self.change_situation)
        self.settings.batch_plans_changed.connect(self._refresh_batch_list)

        layout = QVBoxLayout()
        layout.addWidget(self.files_widget)
        calc_layout = QHBoxLayout()
        calc_layout.addWidget(QLabel("Batch workflow:"))
        calc_layout.addWidget(self.calculation_choose)
        calc_layout.addWidget(self.result_file)
        calc_layout.addWidget(self.chose_result)
        calc_layout.addStretch()
        calc_layout.addWidget(self.run_button)
        layout.addLayout(calc_layout)
        layout.addWidget(self.progress)
        self.setLayout(layout)

        self._refresh_batch_list()

    def prepare_calculation(self):
        plan = self.settings.batch_plans[str(
            self.calculation_choose.currentText())]
        dial = CalculationPrepare(self.files_widget.get_paths(), plan,
                                  str(self.result_file.text()), self.settings,
                                  self.batch_manager)
        if dial.exec_():
            try:
                self.batch_manager.add_calculation(dial.get_data())
                self.progress.new_task()
            except PicklingError as e:
                if state_store.develop:
                    QMessageBox.warning(self, "Pickle error",
                                        "Please restart PartSeg.")
                else:
                    raise e

    def _refresh_batch_list(self):
        current_calc = str(self.calculation_choose.currentText())
        new_list = ["<no calculation>"] + list(
            sorted(self.settings.batch_plans.keys()))
        try:
            index = new_list.index(current_calc)
        except ValueError:
            index = 0
        self.calculation_choose.clear()
        self.calculation_choose.addItems(new_list)
        self.calculation_choose.setCurrentIndex(index)

    def change_situation(self):
        if (str(self.calculation_choose.currentText()) != "<no calculation>"
                and len(self.files_widget.files_to_proceed) != 0
                and str(self.result_file.text()) != ""):
            self.run_button.setEnabled(True)
        else:
            self.run_button.setDisabled(True)

        if self.calculation_choose.currentText() in self.settings.batch_plans:
            plan = self.settings.batch_plans[str(
                self.calculation_choose.currentText())]
            self.files_widget.mask_list = plan.get_list_file_mask()
        else:
            self.files_widget.mask_list = []

    def chose_result_file(self):
        dial = PSaveDialog(SaveExcel,
                           system_widget=False,
                           settings=self.settings,
                           path=IO_SAVE_DIRECTORY)
        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            if os.path.splitext(file_path)[1] == "":
                file_path += ".xlsx"
            self.result_file.setText(file_path)
            self.change_situation()
Пример #15
0
class Classifier(QWidget):
	def __init__(self, viewer, metadata_levels, initial_classes, *args, **kwargs):
		super(Classifier,self).__init__(*args,**kwargs)

		self.viewer = viewer

		# open image if not already open
		if len(self.viewer.layers)<1:
			msg = QMessageBox()
			msg.setIcon(QMessageBox.Information)
			msg.setText("No image open, please select an image in the following dialog.")
			msg.exec()
			self.viewer.window.qt_viewer._open_files_dialog()

		if len(self.viewer.layers)<1:
			# still no image open
			raise ValueError("Could not find image layers.")

		# get image shape
		self.shape = self.viewer.layers[0].shape

		# check metadata levels
		if not metadata_levels:
			self.metadata_levels = [f'dim_{dim}' for dim in range(len(self.shape[:-2]))]
		elif len(metadata_levels)!=len(self.shape[:-2]):
			metadata_levels_warning = NapariNotification((f'Number of metadata_levels ({len(metadata_levels)}) does not match '
								f'number of leading image dimensions ({len(self.shape[:-2])}); will use default metadata_levels.'),
								severity='warning')
			metadata_levels_warning.show()
			self.metadata_levels = [f'dim_{dim}' for dim in range(len(self.shape[:-2]))]
		else:
			self.metadata_levels = metadata_levels

		# load metadata
		self.load_metadata()

		# initialize widget
		layout = QHBoxLayout()

		## io panel
		save_button = QPushButton('Save...',self)
		save_button.clicked.connect(self.save_results)
		io_panel = QWidget()
		io_layout = QVBoxLayout()
		io_layout.addWidget(save_button)
		io_panel.setLayout(io_layout)
		layout.addWidget(io_panel)

		## class panel
		classes_panel = QGroupBox('classes')
		classes_panel.setMinimumWidth(CLASS_PANEL_MINIMUM_WIDTH)

		### layout for adding classes
		add_classes_layout = QHBoxLayout()
		add_class_button = QPushButton('Add class',self)
		add_class_button.clicked.connect(self.click_add_class)
		self.new_class_text = QLineEdit(self)
		self.new_class_text.setAlignment(Qt.AlignLeft)
		add_classes_layout.addWidget(add_class_button)
		add_classes_layout.addWidget(self.new_class_text)

		### layout for class buttons
		self.class_button_layout = QGridLayout()

		### add sub layouts to class panel
		classes_layout = QVBoxLayout()
		classes_layout.addLayout(add_classes_layout)
		classes_layout.addLayout(self.class_button_layout)
		classes_panel.setLayout(classes_layout)
		layout.addWidget(classes_panel)

		## set widget layout
		layout.setAlignment(Qt.AlignTop)
		layout.setSpacing(4)
		self.setLayout(layout)
		self.setMaximumHeight(GUI_MAXIMUM_HEIGHT)
		self.setMaximumWidth(GUI_MAXIMUM_WIDTH)

		# initialize classes
		self.classes = []

		if initial_classes is not None:
			for initial_class in initial_classes:
				self.add_class(initial_class)

	def load_metadata(self):
		# msg = QMessageBox()
		# msg.setIcon(QMessageBox.Information)
		# msg.setText(("Use the following dialog to choose a metadata table to open, "
		# 				"otherwise click 'cancel' to use an automatically generated table."))
		# msg.exec()

		# filename = QFileDialog.getOpenFileName(self,'Open metadata table',DEFAULT_PATH,'Metadata table (*.csv *.hdf)')

		self.df_metadata = None
		# if filename[0]:
		# 	ext = filename[0].split('.')[-1]
		# 	if ext == 'csv':
		# 		self.df_metadata = pd.read_csv(filename[0])
		# 	elif ext == 'hdf':
		# 		self.df_metadata = pd.read_hdf(filename[0])
		# 	else:
		# 		print(f'filetype {ext} not recognized, creating default metadata table')

		if self.df_metadata is None:
			from itertools import product
			self.df_metadata = pd.DataFrame([{level:idx for level,idx in zip(self.metadata_levels,indices)} 
				for indices in product(*(range(dim) for dim in self.shape[:-2]))]
				)

		if 'annotated_class' not in self.df_metadata.columns:
			self.df_metadata = self.df_metadata.assign(annotated_class=None)

		self.df_metadata = self.df_metadata.set_index(self.metadata_levels)

	def click_add_class(self):
		self.add_class(self.new_class_text.text())
		self.new_class_text.clear()

	def add_class(self,new_class):
		self.classes.append(new_class)

		if len(self.classes)<10:
			# shortcut key binding available
			self.viewer.bind_key(key=str(len(self.classes)),
				func=partial(self.classify_frame,chosen_class=self.classes[-1]),
				overwrite=True)
			new_class_button = QPushButton('{class_name} [{num}]'.format(
				class_name=self.classes[-1], num=len(self.classes)),
				self)
		else:
			# shortcut key binding not available
			many_classes_notification = NapariNotification(
				('Shortcut key bindings not available with the 10th or further class'),
				severity='info')
			many_classes_notification.show()

			new_class_button = QPushButton(self.classes[-1],self)
	
		new_class_button.clicked.connect(partial(self.classify_frame,key_press=None,chosen_class=self.classes[-1]))

		self.class_button_layout.addWidget(new_class_button,
			((len(self.classes)-1)%MAXIMUM_CLASS_BUTTONS_PER_COLUMN),
			int((len(self.classes)-1)/MAXIMUM_CLASS_BUTTONS_PER_COLUMN)
			)


	def classify_frame(self,key_press,chosen_class):
		# TODO: create an annotation status bar that reports class of current slice
		coords = self.viewer.layers[0].coordinates[:-2]

		coords_string = ', '.join([f'{level}={val}' for level,val in zip(self.metadata_levels,coords)])
		if self.df_metadata.loc[(coords),('annotated_class')] is not None:
			previous_class = self.df_metadata.loc[(coords),('annotated_class')]
			if previous_class != chosen_class:
				overwrite_notification = NapariNotification((f'{coords_string} previously annotated as '
								f'`{previous_class}`, overwriting annotation as `{chosen_class}`'),
								severity='info')
				overwrite_notification.show()
		else:
			annotate_notification = NapariNotification((f'Annotating {coords_string} as `{chosen_class}`'),
									severity='info')
			annotate_notification.show()

		
		self.df_metadata.loc[(coords),('annotated_class')] = chosen_class

		if tuple(np.array(self.shape[:-2])-1)==coords:
			# last slice
			pass
		else:
			if coords[-1]<(self.shape[-3]-1):
				self.viewer.dims._increment_dims_right(axis=(-3))
			else:
				self.viewer.dims.set_current_step(axis=(-3),value=0)
				self.viewer.dims._increment_dims_right(axis=(-4))


	def save_results(self):
		filename = QFileDialog.getSaveFileName(self,
        	'Export classification data',
        	os.path.join(DEFAULT_PATH, self.viewer.layers[0].name+'.classified.csv'),
        	'Classification files (*.csv *.hdf)')

		if filename[0]:
			ext = filename[0].split('.')[-1]
			if ext == 'csv':
				self.df_metadata.to_csv(filename[0])
			elif ext == 'hdf':
				self.df_metadata.to_hdf(filename[0],'x',mode='w')
			else:
				print(f'filetype {ext} not recognized, cannot save')
		else:
			print('no file selected, did not save')
Пример #16
0
    def _create_bump(self):
        def _add_entry(index):
            cbox = self.sender()
            text = cbox.itemText(index)
            if not text.startswith('other'):
                return
            win = LoadConfigDialog(self._config_type, self)
            confname, status = win.exec_()
            if not status:
                cbox.setCurrentIndex(0)
                return
            cbox.insertItem(index, confname)
            cbox.setCurrentIndex(index)

        wid = QDialog(self)
        wid.setObjectName(self._csorb.acc + 'App')
        lay = QGridLayout()
        wid.setLayout(lay)

        row = 0
        lay.addWidget(QLabel('Base Orbit ', wid), row, 0)
        orbcombo = QComboBox(wid)
        orbcombo.addItems(['Register', 'ref_orb', 'bba_orb', 'other...'])
        orbcombo.setCurrentIndex(1)
        orbcombo.activated.connect(_add_entry)
        lay.addWidget(orbcombo, row, 1)

        row += 1
        lay.addWidget(QLabel('Subsection', wid), row, 0)
        sscombo = QComboBox(wid)
        sub = ['SA', 'SB', 'SP', 'SB']
        ssnames = [f'{d+1:02d}{sub[d%len(sub)]}' for d in range(20)]
        bcnames = [f'{d+1:02d}BC' for d in range(20)]
        names = []
        for aaa, bbb in zip(ssnames, bcnames):
            names.extend([aaa, bbb])
        sscombo.addItems(names)
        lay.addWidget(sscombo, row, 1)

        row += 1
        lay.addWidget(QLabel('\u03B8<sub>x</sub> [urad]', wid), row, 0)
        angx = QLineEdit(wid)
        angx.setValidator(QDoubleValidator())
        angx.setText('0.0')
        angx.setAlignment(Qt.AlignCenter)
        angx.setStyleSheet('max-width:5em;')
        lay.addWidget(angx, row, 1)

        row += 1
        lay.addWidget(QLabel('X [um] ', wid), row, 0)
        posx = QLineEdit(wid)
        posx.setValidator(QDoubleValidator())
        posx.setText('0.0')
        posx.setAlignment(Qt.AlignCenter)
        posx.setStyleSheet('max-width:5em;')
        lay.addWidget(posx, row, 1)

        row += 1
        lay.addWidget(QLabel('\u03B8<sub>y</sub> [urad]', wid), row, 0)
        angy = QLineEdit(wid)
        angy.setValidator(QDoubleValidator())
        angy.setText('0.0')
        angy.setAlignment(Qt.AlignCenter)
        angy.setStyleSheet('max-width:5em;')
        lay.addWidget(angy, row, 1)

        row += 1
        lay.addWidget(QLabel('Y [um] ', wid), row, 0)
        posy = QLineEdit(wid)
        posy.setValidator(QDoubleValidator())
        posy.setText('0.0')
        posy.setAlignment(Qt.AlignCenter)
        posy.setStyleSheet('max-width:5em;')
        lay.addWidget(posy, row, 1)

        row += 1
        hlay = QHBoxLayout()
        cancel = QPushButton('Cancel', wid)
        confirm = QPushButton('Ok', wid)
        confirm.setDefault(True)
        cancel.clicked.connect(wid.reject)
        confirm.clicked.connect(wid.accept)
        hlay.addStretch()
        hlay.addWidget(cancel)
        hlay.addStretch()
        hlay.addWidget(confirm)
        hlay.addStretch()
        wid.layout().addItem(hlay, row, 0, 1, 2)
        res = wid.exec_()
        if res != QDialog.Accepted:
            return

        index = orbcombo.currentIndex()
        confname = orbcombo.itemText(index)
        if not index:
            orbx = _np.array(self.orbx)
            orby = _np.array(self.orby)
        elif index == orbcombo.count() - 1:
            return
        else:
            orbs = self._client.get_config_value(confname)
            orbx = _np.array(orbs['x'])
            orby = _np.array(orbs['y'])

        agx = float(angx.text())
        agy = float(angy.text())
        psx = float(posx.text())
        psy = float(posy.text())
        sub = sscombo.currentText()
        orbx, orby = _calculate_bump(orbx, orby, sub, agx, agy, psx, psy)

        txt = f'Bump@{sub}: ref={confname}\n'
        txt += f'ax={agx:.1f} ay={agy:.1f} dx={psx:.1f} dy={psy:.1f}'
        self._update_and_emit(txt, orbx, orby)
Пример #17
0
 def createEditor(self, parent: QWidget, option: QStyleOptionViewItem,
                  index: QModelIndex) -> QWidget:
     editor = QLineEdit(parent=parent)
     editor.setAlignment(Qt.AlignCenter)
     editor.selectAll()
     return editor
Пример #18
0
    def _edit_orbit(self):
        orbx = self.orbx
        orby = self.orby

        wid = QDialog(self)
        wid.setObjectName(self._csorb.acc + 'App')
        wid.setLayout(QVBoxLayout())

        hbl = QHBoxLayout()
        wid.layout().addItem(hbl)
        hbl.addWidget(QLabel('X = ', wid))
        multx = QLineEdit(wid)
        multx.setValidator(QDoubleValidator())
        multx.setText('1.0')
        # multx.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        multx.setAlignment(Qt.AlignCenter)
        multx.setStyleSheet('max-width:5em;')
        hbl.addWidget(multx)
        hbl.addWidget(QLabel('*X   +   ', wid))
        addx = QLineEdit(wid)
        addx.setValidator(QDoubleValidator())
        addx.setText('0.0')
        addx.setAlignment(Qt.AlignCenter)
        addx.setStyleSheet('max-width:5em;')
        hbl.addWidget(addx)
        hbl.addWidget(QLabel(' [um]', wid))

        hbl = QHBoxLayout()
        wid.layout().addItem(hbl)
        hbl.addWidget(QLabel('Y = ', wid))
        multy = QLineEdit(wid)
        multy.setValidator(QDoubleValidator())
        multy.setText('1.0')
        # multy.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        multy.setAlignment(Qt.AlignCenter)
        multy.setStyleSheet('max-width:5em;')
        hbl.addWidget(multy)
        hbl.addWidget(QLabel('*Y   +   ', wid))
        addy = QLineEdit(wid)
        addy.setValidator(QDoubleValidator())
        addy.setText('0.0')
        addy.setAlignment(Qt.AlignCenter)
        addy.setStyleSheet('max-width:5em;')
        hbl.addWidget(addy)
        hbl.addWidget(QLabel(' [um]', wid))

        hlay = QHBoxLayout()
        cancel = QPushButton('Cancel', wid)
        confirm = QPushButton('Ok', wid)
        confirm.setDefault(True)
        cancel.clicked.connect(wid.reject)
        confirm.clicked.connect(wid.accept)
        hlay.addStretch()
        hlay.addWidget(cancel)
        hlay.addStretch()
        hlay.addWidget(confirm)
        hlay.addStretch()
        wid.layout().addItem(hlay)
        res = wid.exec_()

        if res != QDialog.Accepted:
            return
        mltx = float(multx.text())
        mlty = float(multy.text())
        plusx = float(addx.text())
        plusy = float(addy.text())
        orbx = mltx * orbx + plusx
        orby = mlty * orby + plusy
        txt = ''
        txt += f'multx = {mltx:5.1f} offx = {plusx:7.1f}\n'
        txt += f'multy = {mlty:5.1f} offy = {plusy:7.1f}'
        self._update_and_emit(txt, orbx, orby)
Пример #19
0
class MainWindow(QMainWindow):
    max_recent_files = 10

    def __init__(self):
        super().__init__()

        self.setWindowTitle("OkPlayer")

        icon = QIcon()
        icon.addPixmap(QPixmap("ok_64x64.ico"), QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)

        self.recent_file_acts = []
        self.init_menu()
        self.now = datetime.now()

        # Setting
        self.setting = {}
        self.load_setting()

        # Status bar
        self.learning_time_ms = 0
        self.learning_time_ms_total = self.setting.get(
            "learning_time_ms_total", 0)
        self.status_bar = self.statusBar()
        self.label_learning_time = QLabel(self)
        self.label_learning_time.setAlignment(Qt.AlignRight)

        self.status_bar.addPermanentWidget(self.label_learning_time)
        self.label_learning_time.setText(
            f"Learning time: 00:00"
            f" / total {ms2min_sec(self.learning_time_ms_total)}")

        # Timer for learning time
        self.timer_learning_time = QTimer(self)
        self.timer_learning_time.timeout.connect(self.update_learning_time)
        self.timer_learning_time.setInterval(1000)

        # Player
        self.player = QMediaPlayer(self)
        self.player.mediaStatusChanged.connect(self.qmp_status_changed)
        self.player.positionChanged.connect(self.qmp_position_changed)
        self.player.setNotifyInterval(50)
        self.player.setVolume(50)
        self.player_buf = QBuffer()
        self.path_media = ""
        self.music_data = None
        self.duration_ms = 0
        self.duration_str = ""

        # A/B Loop
        self.pos_loop_a = None
        self.pos_loop_b = None

        # Layout
        self.label_music = QLabel("No music", self)

        self.ico_play = qta.icon("fa.play")
        self.ico_pause = qta.icon("fa.pause")

        layout = QVBoxLayout()
        layout_volume = QHBoxLayout()
        layout_btn_progress = QVBoxLayout()
        layout_music_btns = QHBoxLayout()
        self.btn_rewind = QPushButton(qta.icon("fa.backward"), "", self)
        self.btn_rewind.clicked.connect(self.rewind)
        self.btn_play = QPushButton(self.ico_play, "", self)
        self.btn_play.clicked.connect(self.play)
        self.btn_fastforward = QPushButton(qta.icon("fa.forward"), "", self)
        self.btn_fastforward.clicked.connect(self.fastforward)

        self.btn_rewind.setFocusPolicy(Qt.NoFocus)
        self.btn_play.setFocusPolicy(Qt.NoFocus)
        self.btn_fastforward.setFocusPolicy(Qt.NoFocus)

        layout_music_btns.addWidget(self.btn_rewind)
        layout_music_btns.addWidget(self.btn_play)
        layout_music_btns.addWidget(self.btn_fastforward)

        layout_progress = QHBoxLayout()
        self.progressbar = MusicProgressBar(self)
        self.progressbar.sig_pb_pos.connect(self.set_media_position)
        self.elapsed_time = QLineEdit(f"00:00 / 00:00", self)
        self.elapsed_time.setReadOnly(True)
        self.elapsed_time.setAlignment(Qt.AlignHCenter)

        layout_progress.addWidget(self.progressbar)
        layout_progress.addWidget(self.elapsed_time)

        layout_btn_progress.addWidget(self.label_music)
        layout_btn_progress.addLayout(layout_music_btns)
        layout_btn_progress.addLayout(layout_progress)

        # Volume
        self.qdial_volume = QDial(self)
        self.qdial_volume.setMinimumWidth(110)
        self.qdial_volume.setWrapping(False)
        self.qdial_volume.setNotchesVisible(True)
        self.qdial_volume.setMinimum(0)
        self.qdial_volume.setMaximum(100)
        self.qdial_volume.setValue(self.player.volume())
        self.qdial_volume.valueChanged.connect(self.qdial_changed)

        layout_volume.addLayout(layout_btn_progress)
        layout_volume.addWidget(self.qdial_volume)

        # Lyrics
        self.display_lyrics = LyricsDisplay(self)
        layout.addLayout(layout_volume)
        layout.addWidget(self.display_lyrics)

        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

        # Auto Play
        self.update_recent_file_action()
        path = self.setting.get("LastPlayedPath", "")
        if osp.isfile(path):
            self.load_music_file(path)

        self.setFocus()

    def init_menu(self):
        """Init menu."""
        color_icon = "#87939A"
        menu_bar = self.menuBar()
        menu_bar.setNativeMenuBar(False)  # Don't use mac native menu bar

        # File
        file_menu = menu_bar.addMenu("&File")

        # Open
        open_action = QAction(qta.icon("ei.folder-open", color=color_icon),
                              "&Open", self)
        open_action.setShortcut("Ctrl+O")
        open_action.setStatusTip("Open file")
        open_action.triggered.connect(self.open_music_file)
        file_menu.addAction(open_action)
        file_menu.addSeparator()

        # Recent Files
        for i in range(MainWindow.max_recent_files):
            self.recent_file_acts.append(
                QAction(self, visible=False, triggered=self.load_recent_music))
        for i in range(MainWindow.max_recent_files):
            file_menu.addAction(self.recent_file_acts[i])

        file_menu.addSeparator()

        # Exit
        exit_action = QAction(qta.icon("mdi.exit-run", color=color_icon),
                              "&Exit", self)
        exit_action.setShortcut("Ctrl+Q")
        exit_action.setStatusTip("Exit App")
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # Help
        help_menu = menu_bar.addMenu("&Help")
        about_action = QAction(
            "&About",
            self,
            statusTip="Show the application's About box",
            triggered=self.about,
        )
        help_menu.addAction(about_action)

    def about(self):
        """Show messagebox for about."""
        QMessageBox.about(
            self,
            "About music player a/b loop",
            "The music player a/b loop is made by <b>ok97465</b>",
        )

    def update_recent_file_action(self):
        """Update recent file action."""
        files = self.setting.get("recent_files", [])

        num_recent_files = min(len(files), MainWindow.max_recent_files)

        for i in range(num_recent_files):
            text = osp.splitext(osp.basename(files[i]))[0]
            self.recent_file_acts[i].setText(text)
            self.recent_file_acts[i].setData(files[i])
            self.recent_file_acts[i].setVisible(True)

        for j in range(num_recent_files, MainWindow.max_recent_files):
            self.recent_file_acts[j].setVisible(False)

    def open_music_file(self):
        """Open music file."""
        self.stop()
        fname = QFileDialog.getOpenFileName(
            self,
            "Open music file",
            "/home/ok97465",
            filter="Music Files (*.mp3, *.m4a)",
        )
        self.load_music_file(fname[0])

    def load_music_file(self, path: str):
        """Load music file"""
        if not osp.isfile(path):
            return
        self.path_media = path

        path_lyrics = path[:-3] + "vtt"
        self.display_lyrics.read_vtt(path_lyrics)

        fp = io.BytesIO()
        self.music_data = AudioSegment.from_file(path)
        self.music_data.export(fp, format="wav")
        self.player_buf.setData(fp.getvalue())
        self.player_buf.open(QIODevice.ReadOnly)
        self.player.setMedia(QMediaContent(), self.player_buf)

    def load_recent_music(self):
        """Load recent music."""
        action = self.sender()
        if action:
            self.stop()
            self.load_music_file(action.data())

    def load_setting(self):
        """Load setting file."""
        try:
            with open("setting.json", "r") as fp:
                self.setting = json.load(fp)
        except FileNotFoundError:
            pass

    def keyPressEvent(self, event):
        key = event.key()
        shift = event.modifiers() & Qt.ShiftModifier
        if shift:
            if key == Qt.Key_O:
                self.adjust_ab_loop(-100)
        else:
            if key in [Qt.Key_H, Qt.Key_Left, Qt.Key_A]:
                self.rewind(ms=5000)
            elif key in [Qt.Key_L, Qt.Key_Right, Qt.Key_D]:
                self.fastforward(ms=5000)
            elif key in [Qt.Key_J]:
                self.rewind(ms=1000 * 38)
            elif key in [Qt.Key_K, Qt.Key_F]:
                self.fastforward(ms=1000 * 38)
            elif key == Qt.Key_Up:
                self.control_volume(5)
            elif key == Qt.Key_Down:
                self.control_volume(-5)
            elif key in [Qt.Key_I, Qt.Key_W, Qt.Key_Menu]:
                self.set_ab_loop()
            elif key == Qt.Key_O:
                self.adjust_ab_loop(500)
            elif key in [Qt.Key_Space, Qt.Key_Hangul_Hanja]:
                self.play()
            elif key in [Qt.Key_S]:
                self.save_ab_loop()
            elif key in [Qt.Key_Q, Qt.Key_U, Qt.Key_Slash]:
                self.send_AB_loop_lyrics_to_papago()

        super().keyPressEvent(event)

    def set_ab_loop(self):
        """Set A/B loop."""
        if self.pos_loop_b:
            self.pos_loop_b = None
            self.pos_loop_a = None
        elif self.pos_loop_a:
            self.pos_loop_b = self.player.position()
            self.player.setPosition(self.pos_loop_a)
        else:
            self.pos_loop_a = self.player.position()

        self.progressbar.pos_loop_a = self.pos_loop_a
        self.progressbar.pos_loop_b = self.pos_loop_b
        self.progressbar.repaint()

    def adjust_ab_loop(self, offset_ms):
        """Adjust A/B loop."""
        if self.pos_loop_b:
            self.pos_loop_b += offset_ms
            self.pos_loop_a += offset_ms

    def save_ab_loop(self):
        """Save A/B loop"""
        if self.pos_loop_b is None:
            return

        is_playing = False
        if self.player.state() == QMediaPlayer.PlayingState:
            is_playing = True

        if is_playing:
            self.player.pause()
        path_new = (self.path_media[:-4] +
                    f"{self.pos_loop_a}_{self.pos_loop_b}" +
                    self.path_media[-4:])
        seg = self.music_data[self.pos_loop_a:self.pos_loop_b]
        seg.export(path_new, format="mp3")

        if is_playing:
            self.player.play()

    def play(self):
        """Play music file."""
        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.pause()
            self.btn_play.setIcon(self.ico_play)
            self.timer_learning_time.stop()
        else:
            self.player.play()
            self.btn_play.setIcon(self.ico_pause)
            self.timer_learning_time.start()

    def stop(self):
        """Stop."""
        self.save_current_media_info()
        self.player.stop()
        self.player_buf.close()
        self.path_media = ""
        self.pos_loop_b = None
        self.pos_loop_a = None
        self.timer_learning_time.stop()
        self.label_music.setText("No music")
        self.btn_play.setIcon(self.ico_play)

    def control_volume(self, step: int):
        """Control volume."""
        volume = self.player.volume()
        if step < 0:
            new_volume = max([0, volume + step])
        else:
            new_volume = min([100, volume + step])
        self.qdial_volume.setValue(new_volume)

    def navigate_media(self, ms: int):
        """Navigate the position of media."""
        position_ms = self.player.position()
        if ms < 0:
            new_position_ms = max([0, position_ms + ms])
        else:
            new_position_ms = min([self.duration_ms, position_ms + ms])
        self.player.setPosition(new_position_ms)

    def rewind(self, ms: int = 5000):
        """Re-wind media of QMediaPlayer."""
        self.navigate_media(ms * -1)

    def fastforward(self, ms: int = 5000):
        """fastfoward media of QMediaPlayer."""
        self.navigate_media(ms)

    def qmp_status_changed(self):
        """Handle status of QMediaPlayer if the status is changed."""
        status = self.player.mediaStatus()
        if status == QMediaPlayer.LoadedMedia and self.path_media:
            duration_ms = self.player.duration()
            self.duration_ms = duration_ms
            self.duration_str = ms2min_sec(duration_ms)
            self.elapsed_time.setText(f"00:00 / {self.duration_str}")
            self.progressbar.setMaximum(duration_ms)
            music_basename = osp.splitext(osp.basename(self.path_media))[0]
            self.label_music.setText(music_basename)
            self.player.play()

            # read previous position
            path = self.path_media
            position = self.setting.get(path, 0)
            self.player.setPosition(position)

            # update recent files
            files = self.setting.get("recent_files", [])
            try:
                files.remove(path)
            except ValueError:
                pass
            files.insert(0, path)
            del files[MainWindow.max_recent_files:]
            self.setting["recent_files"] = files
            self.update_recent_file_action()

        # Player state
        state = self.player.state()
        if state in [QMediaPlayer.PausedState, QMediaPlayer.StoppedState]:
            self.btn_play.setIcon(self.ico_play)
            self.timer_learning_time.stop()
        elif state == QMediaPlayer.PlayingState:
            self.btn_play.setIcon(self.ico_pause)
            self.timer_learning_time.start()

    def qmp_position_changed(self, position_ms: int):
        """Handle position of qmedia if the position is changed."""
        if self.pos_loop_b:
            if (position_ms
                    == self.duration_ms) or (self.pos_loop_b < position_ms):
                self.player.setPosition(self.pos_loop_a)
        self.progressbar.setValue(position_ms)
        self.elapsed_time.setText(
            f"{ms2min_sec(position_ms)} / {self.duration_str}")
        self.display_lyrics.update_media_pos(position_ms)

    def qdial_changed(self, pos: int):
        """Handle Qdial position."""
        self.player.setVolume(pos)

    def send_AB_loop_lyrics_to_papago(self):
        """Send AB loop lyrics to papago."""
        if not self.pos_loop_b:
            return
        lyrics = self.display_lyrics.get_lyrics_in_range(
            self.pos_loop_a, self.pos_loop_b)
        lyrics = lyrics.replace("\n", "")
        webbrowser.open(f"https://papago.naver.com/?sk=en&tk=ko&st={lyrics}",
                        autoraise=False)

    @Slot(int)
    def set_media_position(self, position_ms: int):
        """Set the position of Qmedia."""
        self.player.setPosition(position_ms)

    def save_current_media_info(self):
        """Save current media info to setting file."""
        if not osp.isfile(self.path_media):
            return
        if self.path_media:
            position = self.player.position()
            self.setting[self.path_media] = position
            self.setting["LastPlayedPath"] = self.path_media

    def update_learning_time(self):
        """Update learning time."""
        self.learning_time_ms += 1000
        self.learning_time_ms_total += 1000
        self.label_learning_time.setText(
            f"Learning time : {ms2min_sec(self.learning_time_ms)}"
            f" / total : {ms2min_sec(self.learning_time_ms_total)}")

    def closeEvent(self, event):
        """Save setting."""
        self.stop()
        self.setting["learning_time_ms_total"] = self.learning_time_ms_total

        with open("setting.json", "w") as fp:
            json.dump(self.setting, fp, indent=2)

        now = self.now
        cur = sqlite3.connect("history.db")
        cur.execute("CREATE TABLE IF NOT EXISTS LearningTimeData("
                    "DayOfWeek INTEGER, "
                    "month  INTEGER, "
                    "day INTEGER,  "
                    "timestamp REAL, "
                    "LearningTime_ms INTEGER)")
        cur.execute(
            "insert into LearningTimeData Values (?,?,?,?,?)",
            (now.weekday(), now.month, now.day, now.timestamp(),
             self.learning_time_ms),
        )
        cur.commit()
        cur.close()
Пример #20
0
class QtSizeSliderPreviewWidget(QWidget):
    """
    Widget displaying a description, textedit and slider to adjust font size
    with preview.

    Parameters
    ----------
    parent : qtpy.QtWidgets.QWidget, optional
        Default is None.
    description : str, optional
        Default is "".
    preview_text : str, optional
        Default is "".
    value : int, optional
        Default is None.
    min_value : int, optional
        Default is 1.
    max_value : int, optional
        Default is 50.
    unit : str, optional
        Default is "px".
    """

    valueChanged = Signal(int)

    def __init__(
        self,
        parent: QWidget = None,
        description: str = None,
        preview_text: str = None,
        value: int = None,
        min_value: int = 1,
        max_value: int = 50,
        unit: str = "px",
    ):
        super().__init__(parent)

        description = description or ""
        preview_text = preview_text or ""
        self._value = value if value else self.fontMetrics().height()
        self._min_value = min_value
        self._max_value = max_value

        # Widget
        self._lineedit = QLineEdit()
        self._description_label = QLabel(self)
        self._unit_label = QLabel(self)
        self._slider = QSlider(Qt.Horizontal, self)
        self._slider_min_label = QLabel(self)
        self._slider_max_label = QLabel(self)
        self._preview = QtFontSizePreview(self)
        self._preview_label = QLabel(self)
        self._validator = None

        # Widgets setup
        self._description_label.setText(description)
        self._description_label.setWordWrap(True)
        self._unit_label.setText(unit)
        self._lineedit.setAlignment(Qt.AlignRight)
        self._slider_min_label.setText(str(min_value))
        self._slider_max_label.setText(str(max_value))
        self._slider.setMinimum(min_value)
        self._slider.setMaximum(max_value)
        self._preview.setText(preview_text)
        self._preview_label.setText(trans._("preview"))
        self._preview_label.setAlignment(Qt.AlignHCenter)
        self.setFocusProxy(self._lineedit)

        # Layout
        left_bottom_layout = QHBoxLayout()
        left_bottom_layout.addWidget(self._lineedit)
        left_bottom_layout.addWidget(self._unit_label)
        left_bottom_layout.addWidget(self._slider_min_label)
        left_bottom_layout.addWidget(self._slider)
        left_bottom_layout.addWidget(self._slider_max_label)

        left_layout = QVBoxLayout()
        left_layout.addWidget(self._description_label)
        left_layout.addLayout(left_bottom_layout)

        right_layout = QVBoxLayout()
        right_layout.addWidget(self._preview)
        right_layout.addWidget(self._preview_label)

        layout = QHBoxLayout()
        layout.addLayout(left_layout, 2)
        layout.addLayout(right_layout, 1)

        self.setLayout(layout)

        # Signals
        self._slider.valueChanged.connect(self._update_value)
        self._lineedit.textChanged.connect(self._update_value)

        self._update_line_width()
        self._update_validator()
        self._update_value(self._value)

    def _update_validator(self):
        self._validator = QIntValidator(self._min_value, self._max_value, self)
        self._lineedit.setValidator(self._validator)

    def _update_line_width(self):
        """Update width ofg line text edit."""
        size = self._lineedit.fontMetrics().horizontalAdvance(
            "m" * (1 + len(str(self._max_value))))
        self._lineedit.setMaximumWidth(size)
        self._lineedit.setMinimumWidth(size)

    def _update_value(self, value: int):
        """Update internal value and emit if changed."""
        if value == "":
            value = int(self._value)

        value = int(value)

        if value > self._max_value:
            value = self._max_value
        elif value < self._min_value:
            value = self._min_value

        if value != self._value:
            self.valueChanged.emit(value)

        self._value = value
        self._refresh(self._value)

    def _refresh(self, value: int = None):
        """Refresh the value on all subwidgets."""
        value = value if value else self._value
        self.blockSignals(True)
        self._lineedit.setText(str(value))
        self._slider.setValue(value)
        font = QFont()
        font.setPixelSize(value)
        self._preview.setFont(font)

        font = QFont()
        font.setPixelSize(self.fontMetrics().height() - 4)
        self._preview_label.setFont(font)

        self.blockSignals(False)

    def description(self) -> str:
        """Return the current widget description.

        Returns
        -------
        str
            The description text.
        """
        return self._description_label.text()

    def setDescription(self, text: str):
        """Set the current widget description.

        Parameters
        ----------
        text : str
            The description text.
        """
        self._description_label.setText(text)

    def previewText(self) -> str:
        """Return the current preview text.

        Returns
        -------
        str
            The current preview text.
        """
        return self._preview.text()

    def setPreviewText(self, text: str):
        """Set the current preview text.

        Parameters
        ----------
        text : str
            The current preview text.
        """
        self._preview.setText(text)

    def unit(self) -> str:
        """Return the current unit text.

        Returns
        -------
        str
            The current unit text.
        """
        return self._unit_label.text()

    def setUnit(self, text: str):
        """Set the current unit text.

        Parameters
        ----------
        text : str
            The current preview text.
        """
        self._unit_label.setText(text)

    def minimum(self) -> int:
        """Return the current minimum value for the slider and value in textbox.

        Returns
        -------
        int
            The minimum value for the slider.
        """
        return self._min_value

    def setMinimum(self, value: int):
        """Set the current minimum value for the slider and value in textbox.

        Parameters
        ----------
        value : int
            The minimum value for the slider.
        """
        value = int(value)
        if value < self._max_value:
            self._min_value = value
            self._value = (self._min_value
                           if self._value < self._min_value else self._value)
            self._slider_min_label.setText(str(value))
            self._slider.setMinimum(value)
            self._update_validator()
            self._refresh()
        else:
            raise ValueError(
                trans._("Minimum value must be smaller than {}".format(
                    self._max_value)))

    def maximum(self) -> int:
        """Return the maximum value for the slider and value in textbox.

        Returns
        -------
        int
            The maximum value for the slider.
        """
        return self._max_value

    def setMaximum(self, value: int):
        """Set the maximum value for the slider and value in textbox.

        Parameters
        ----------
        value : int
            The maximum value for the slider.
        """
        value = int(value)
        if value > self._min_value:
            self._max_value = value
            self._value = (self._max_value
                           if self._value > self._max_value else self._value)
            self._slider_max_label.setText(str(value))
            self._slider.setMaximum(value)
            self._update_validator()
            self._update_line_width()
            self._refresh()
        else:
            raise ValueError(
                trans._("Maximum value must be larger than {}".format(
                    self._min_value)))

    def value(self) -> int:
        """Return the current widget value.

        Returns
        -------
        int
            The current value.
        """
        return self._value

    def setValue(self, value: int):
        """Set the current widget value.

        Parameters
        ----------
        value : int
            The current value.
        """
        self._update_value(value)
Пример #21
0
class SelectArithmetic(QDialog):
    def __init__(self, data, data_collection, parent=None):
        super(SelectArithmetic, self).__init__(parent)

        # Get the data_components (e.g., FLUX, DQ, ERROR etc)
        # Using list comprehension to keep the order of the component_ids
        self.data_components = [
            str(x).strip() for x in data.component_ids()
            if not x in data.coordinate_components
        ]

        self.setWindowFlags(self.windowFlags() | Qt.Tool)
        self.title = "Arithmetic Calculation"
        self.data = data
        self.data_collection = data_collection
        self.parent = parent

        self.currentAxes = None
        self.currentKernel = None

        self.createUI()

    def createUI(self):
        """
        Create the popup box with the calculation input area and buttons.

        :return:
        """
        boldFont = QtGui.QFont()
        boldFont.setBold(True)

        # Create calculation label and input box
        self.calculation_label = QLabel("Calculation:")
        self.calculation_label.setFixedWidth(100)
        self.calculation_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.calculation_label.setFont(boldFont)

        self.calculation_text = QLineEdit()
        self.calculation_text.setMinimumWidth(200)
        self.calculation_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hbl1 = QHBoxLayout()
        hbl1.addWidget(self.calculation_label)
        hbl1.addWidget(self.calculation_text)

        # Create calculation label and input box
        self.error_label = QLabel("")
        self.error_label.setFixedWidth(100)

        self.error_label_text = QLabel("")
        self.error_label_text.setMinimumWidth(200)
        self.error_label_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hbl_error = QHBoxLayout()
        hbl_error.addWidget(self.error_label)
        hbl_error.addWidget(self.error_label_text)

        # Show the available data
        self.data_available_text_label = QLabel("Data available: ")
        self.data_available_text_label.setFixedWidth(100)
        self.data_available_text_label.setAlignment(
            (Qt.AlignRight | Qt.AlignTop))
        self.data_available_text_label.setFont(boldFont)

        self.data_available_label = QLabel(', '.join(self.data_components))
        self.data_available_label.setMinimumWidth(200)
        self.data_available_label.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hbl2 = QHBoxLayout()
        hbl2.addWidget(self.data_available_text_label)
        hbl2.addWidget(self.data_available_label)

        # Show the examples
        self.example_text_label = QLabel("Examples: ")
        self.example_text_label.setFixedWidth(100)
        self.example_text_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.example_text_label.setFont(boldFont)

        examples = """Assuming we have data available called FLUX and ERROR:

        - Subtract 1000 from {0}:  {0}new = {0} - 1000
        - Double the FLUX:  {0}new = {0} * 2
        - Scale FLUX between 0 and 1:  {0}norm = ({0} - min({0})) - (max({0})-min({0}))
        - Signal to noise: SNR = {0} / {1}
        - Masking: {0}new = {0} * ({1} < 0.1*mean({1}))
        """.format(self.data_components[0], self.data_components[1])

        self.examples_label = QLabel(examples)
        self.examples_label.setMinimumWidth(200)
        self.examples_label.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hbl_examples = QHBoxLayout()
        hbl_examples.addWidget(self.example_text_label)
        hbl_examples.addWidget(self.examples_label)

        # Create Calculate and Cancel buttons
        self.calculateButton = QPushButton("Calculate")
        self.calculateButton.clicked.connect(self.calculate_callback)
        self.calculateButton.setDefault(True)

        self.cancelButton = QPushButton("Cancel")
        self.cancelButton.clicked.connect(self.cancel_callback)

        hbl5 = QHBoxLayout()
        hbl5.addStretch(1)
        hbl5.addWidget(self.cancelButton)
        hbl5.addWidget(self.calculateButton)

        # Add calculation and buttons to popup box
        vbl = QVBoxLayout()
        vbl.addLayout(hbl1)
        vbl.addLayout(hbl_error)
        vbl.addLayout(hbl2)
        vbl.addLayout(hbl_examples)
        vbl.addLayout(hbl5)

        self.setLayout(vbl)
        self.setMaximumWidth(700)
        self.show()

    def calculate_callback(self):
        """
        Callback for when they hit calculate
        :return:
        """

        # Create the interpreter
        from asteval import Interpreter
        aeval = Interpreter()

        # Grab the calculation from the text box which the user wants to do
        calculation = str(self.calculation_text.text())

        lhs = calculation.split('=')[0].strip()

        # Use the package asteval to do the calculation, we are going to
        # assume here that the lhs of the equals sign is going to be the output named variable

        try:
            if lhs in self.data_components:
                raise KeyError(
                    '{} is already in the data components, use a different variable on the left hand side.'
                    .format(lhs))

            # Pull in the required data and run the calculation
            for dc in self.data_components:
                if dc in calculation:
                    aeval.symtable[dc] = self.data[dc]
            aeval(calculation)

            # Pull out the output data and add to the proper drop-downs
            out_data = aeval.symtable[lhs]
            self.data.add_component(out_data, lhs)

            # Add the new data to the list of available data for arithemitic operations
            self.data_components.append(lhs)

            self.close()

        except KeyError as e:
            self.calculation_text.setStyleSheet(
                "background-color: rgba(255, 0, 0, 128);")

            # Display the error in the Qt popup
            if aeval.error_msg:
                self.error_label_text.setText('{}'.format(aeval.error_msg))
            else:
                self.error_label_text.setText('{}'.format(e))

            self.error_label_text.setStyleSheet("color: rgba(255, 0, 0, 128)")

    def cancel_callback(self, caller=0):
        """
        Cancel callback when the person hits the cancel button

        :param caller:
        :return:
        """
        self.close()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.cancel_callback()
Пример #22
0
class QtDimSliderWidget(QWidget):
    """Compound widget to hold the label, slider and play button for an axis.

    These will usually be instantiated in the QtDims._create_sliders method.
    This widget *must* be instantiated with a parent QtDims.
    """

    axis_label_changed = Signal(int, str)  # axis, label
    fps_changed = Signal(float)
    mode_changed = Signal(str)
    range_changed = Signal(tuple)
    play_started = Signal()
    play_stopped = Signal()

    def __init__(self, parent: QWidget, axis: int):
        super().__init__(parent=parent)
        self.axis = axis
        self.qt_dims = parent
        self.dims = parent.dims
        self.axis_label = None
        self.slider = None
        self.play_button = None
        self.curslice_label = QLineEdit(self)
        self.curslice_label.setToolTip(f'Current slice for axis {axis}')
        # if we set the QIntValidator to actually reflect the range of the data
        # then an invalid (i.e. too large) index doesn't actually trigger the
        # editingFinished event (the user is expected to change the value)...
        # which is confusing to the user, so instead we use an IntValidator
        # that makes sure the user can only enter integers, but we do our own
        # value validation in self.change_slice
        self.curslice_label.setValidator(QIntValidator(0, 999999))

        self.curslice_label.editingFinished.connect(self._set_slice_from_label)
        self.totslice_label = QLabel(self)
        self.totslice_label.setToolTip(f'Total slices for axis {axis}')
        self.curslice_label.setObjectName('slice_label')
        self.totslice_label.setObjectName('slice_label')
        sep = QFrame(self)
        sep.setFixedSize(1, 14)
        sep.setObjectName('slice_label_sep')

        self._fps = 10
        self._minframe = None
        self._maxframe = None
        self._loop_mode = LoopMode.LOOP

        layout = QHBoxLayout()
        self._create_axis_label_widget()
        self._create_range_slider_widget()
        self._create_play_button_widget()

        layout.addWidget(self.axis_label)
        layout.addWidget(self.play_button)
        layout.addWidget(self.slider, stretch=1)
        layout.addWidget(self.curslice_label)
        layout.addWidget(sep)
        layout.addWidget(self.totslice_label)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        self.setLayout(layout)
        self.dims.events.axis_labels.connect(self._pull_label)

    def _set_slice_from_label(self):
        """Update the dims point based on the curslice_label."""
        val = int(self.curslice_label.text())
        max_allowed = self.dims.max_indices[self.axis]
        if val > max_allowed:
            val = max_allowed
            self.curslice_label.setText(str(val))
        self.curslice_label.clearFocus()
        self.qt_dims.setFocus()
        self.dims.set_point(self.axis, val)

    def _create_axis_label_widget(self):
        """Create the axis label widget which accompanies its slider."""
        label = QLineEdit(self)
        label.setObjectName('axis_label')  # needed for _update_label
        label.setText(self.dims.axis_labels[self.axis])
        label.home(False)
        label.setToolTip('Edit to change axis label')
        label.setAcceptDrops(False)
        label.setEnabled(True)
        label.setAlignment(Qt.AlignRight)
        label.setContentsMargins(0, 0, 2, 0)
        label.textChanged.connect(self._update_label)
        label.editingFinished.connect(self._clear_label_focus)
        self.axis_label = label

    def _create_range_slider_widget(self):
        """Creates a range slider widget for a given axis."""
        _range = self.dims.range[self.axis]
        # Set the maximum values of the range slider to be one step less than
        # the range of the layer as otherwise the slider can move beyond the
        # shape of the layer as the endpoint is included
        _range = (_range[0], _range[1] - _range[2], _range[2])
        point = self.dims.point[self.axis]

        slider = ModifiedScrollBar(Qt.Horizontal)
        slider.setFocusPolicy(Qt.NoFocus)
        slider.setMinimum(int(_range[0]))
        slider.setMaximum(int(_range[1]))
        slider.setSingleStep(int(_range[2]))
        slider.setPageStep(int(_range[2]))
        slider.setValue(point)

        # Listener to be used for sending events back to model:
        slider.valueChanged.connect(
            lambda value: self.dims.set_point(self.axis, value))

        def slider_focused_listener():
            self.qt_dims.last_used = self.axis

        # linking focus listener to the last used:
        slider.sliderPressed.connect(slider_focused_listener)
        self.slider = slider

    def _create_play_button_widget(self):
        """Creates the actual play button, which has the modal popup."""
        self.play_button = QtPlayButton(self.qt_dims, self.axis)
        self.play_button.mode_combo.activated[str].connect(
            lambda x: self.__class__.loop_mode.fset(
                self, LoopMode(x.replace(' ', '_'))))

        def fps_listener(*args):
            fps = self.play_button.fpsspin.value()
            fps *= -1 if self.play_button.reverse_check.isChecked() else 1
            self.__class__.fps.fset(self, fps)

        self.play_button.fpsspin.editingFinished.connect(fps_listener)
        self.play_button.reverse_check.stateChanged.connect(fps_listener)
        self.play_stopped.connect(self.play_button._handle_stop)
        self.play_started.connect(self.play_button._handle_start)

    def _pull_label(self, event):
        """Updates the label LineEdit from the dims model."""
        if event.axis == self.axis:
            label = self.dims.axis_labels[self.axis]
            self.axis_label.setText(label)
            self.axis_label_changed.emit(self.axis, label)

    def _update_label(self):
        """Update dimension slider label."""
        with self.dims.events.axis_labels.blocker():
            self.dims.set_axis_label(self.axis, self.axis_label.text())
        self.axis_label_changed.emit(self.axis, self.axis_label.text())

    def _clear_label_focus(self):
        """Clear focus from dimension slider label."""
        self.axis_label.clearFocus()
        self.qt_dims.setFocus()

    def _update_range(self):
        """Updates range for slider."""
        displayed_sliders = self.qt_dims._displayed_sliders

        _range = self.dims.range[self.axis]
        _range = (_range[0], _range[1] - _range[2], _range[2])
        if _range not in (None, (None, None, None)):
            if _range[1] == 0:
                displayed_sliders[self.axis] = False
                self.qt_dims.last_used = None
                self.hide()
            else:
                if (not displayed_sliders[self.axis]
                        and self.axis not in self.dims.displayed):
                    displayed_sliders[self.axis] = True
                    self.last_used = self.axis
                    self.show()
                self.slider.setMinimum(int(_range[0]))
                self.slider.setMaximum(int(_range[1]))
                self.slider.setSingleStep(int(_range[2]))
                self.slider.setPageStep(int(_range[2]))
                maxi = self.dims.max_indices[self.axis]
                self.totslice_label.setText(str(int(maxi)))
                self.totslice_label.setAlignment(Qt.AlignLeft)
                self._update_slice_labels()
        else:
            displayed_sliders[self.axis] = False
            self.hide()

    def _update_slider(self):
        """Update dimension slider."""
        mode = self.dims.mode[self.axis]
        if mode == DimsMode.POINT:
            self.slider.setValue(int(self.dims.point[self.axis]))
            self._update_slice_labels()

    def _update_slice_labels(self):
        """Update slice labels to match current dimension slider position."""
        step = self.dims.range[self.axis][2]
        self.curslice_label.setText(
            str(int(self.dims.point[self.axis] // step)))
        self.curslice_label.setAlignment(Qt.AlignRight)

    @property
    def fps(self):
        """Frames per second for animation."""
        return self._fps

    @fps.setter
    def fps(self, value):
        """Frames per second for animation.

        Paramters
        ---------
        value : float
            Frames per second for animation.
        """
        self._fps = value
        self.play_button.fpsspin.setValue(abs(value))
        self.play_button.reverse_check.setChecked(value < 0)
        self.fps_changed.emit(value)

    @property
    def loop_mode(self):
        """Loop mode for animation.

        Loop mode enumeration napari._qt._constants.LoopMode
        Available options for the loop mode string enumeration are:
        - LoopMode.ONCE
            Animation will stop once movie reaches the max frame
            (if fps > 0) or the first frame (if fps < 0).
        - LoopMode.LOOP
            Movie will return to the first frame after reaching
            the last frame, looping continuously until stopped.
        - LoopMode.BACK_AND_FORTH
            Movie will loop continuously until stopped,
            reversing direction when the maximum or minimum frame
            has been reached.
        """
        return self._loop_mode

    @loop_mode.setter
    def loop_mode(self, value):
        """Loop mode for animation.

        Paramters
        ---------
        value : napari._qt._constants.LoopMode
            Loop mode for animation.
            Available options for the loop mode string enumeration are:
            - LoopMode.ONCE
                Animation will stop once movie reaches the max frame
                (if fps > 0) or the first frame (if fps < 0).
            - LoopMode.LOOP
                Movie will return to the first frame after reaching
                the last frame, looping continuously until stopped.
            - LoopMode.BACK_AND_FORTH
                Movie will loop continuously until stopped,
                reversing direction when the maximum or minimum frame
                has been reached.
        """
        self._loop_mode = value
        self.play_button.mode_combo.setCurrentText(str(value))
        self.mode_changed.emit(str(value))

    @property
    def frame_range(self):
        """Frame range for animation, as (minimum_frame, maximum_frame)."""
        frame_range = (self._minframe, self._maxframe)
        frame_range = frame_range if any(frame_range) else None
        return frame_range

    @frame_range.setter
    def frame_range(self, value):
        """Frame range for animation, as (minimum_frame, maximum_frame).

        Paramters
        ---------
        value : tuple(int, int)
            Frame range as tuple/list with range (minimum_frame, maximum_frame)
        """
        if not isinstance(value, (tuple, list, type(None))):
            raise TypeError('frame_range value must be a list or tuple')
        if value and not len(value) == 2:
            raise ValueError('frame_range must have a length of 2')
        if value is None:
            value = (None, None)
        self._minframe, self._maxframe = value
        self.range_changed.emit(tuple(value))

    def _update_play_settings(self, fps, loop_mode, frame_range):
        """Update settings for animation.

        Parameters
        ----------
        fps : float
            Frames per second to play the animation.
        loop_mode : napari._qt._constants.LoopMode
            Loop mode for animation.
            Available options for the loop mode string enumeration are:
            - LoopMode.ONCE
                Animation will stop once movie reaches the max frame
                (if fps > 0) or the first frame (if fps < 0).
            - LoopMode.LOOP
                Movie will return to the first frame after reaching
                the last frame, looping continuously until stopped.
            - LoopMode.BACK_AND_FORTH
                Movie will loop continuously until stopped,
                reversing direction when the maximum or minimum frame
                has been reached.
        frame_range : tuple(int, int)
            Frame range as tuple/list with range (minimum_frame, maximum_frame)
        """
        if fps is not None:
            self.fps = fps
        if loop_mode is not None:
            self.loop_mode = loop_mode
        if frame_range is not None:
            self.frame_range = frame_range

    def _play(
        self,
        fps: Optional[float] = None,
        loop_mode: Optional[str] = None,
        frame_range: Optional[Tuple[int, int]] = None,
    ):
        """Animate (play) axis. Same API as QtDims.play()

        Putting the AnimationWorker logic here makes it easier to call
        QtDims.play(axis), or hit the keybinding, and have each axis remember
        it's own settings (fps, mode, etc...).

        Parameters
        ----------
        fps : float
            Frames per second for animation.
        loop_mode : napari._qt._constants.LoopMode
            Loop mode for animation.
            Available options for the loop mode string enumeration are:
            - LoopMode.ONCE
                Animation will stop once movie reaches the max frame
                (if fps > 0) or the first frame (if fps < 0).
            - LoopMode.LOOP
                Movie will return to the first frame after reaching
                the last frame, looping continuously until stopped.
            - LoopMode.BACK_AND_FORTH
                Movie will loop continuously until stopped,
                reversing direction when the maximum or minimum frame
                has been reached.
        frame_range : tuple(int, int)
            Frame range as tuple/list with range (minimum_frame, maximum_frame)
        """

        # having this here makes sure that using the QtDims.play() API
        # keeps the play preferences synchronized with the play_button.popup
        self._update_play_settings(fps, loop_mode, frame_range)

        # setting fps to 0 just stops the animation
        if fps == 0:
            return

        worker, thread = _new_worker_qthread(
            AnimationWorker,
            self,
            _start_thread=True,
            _connect={'frame_requested': self.qt_dims._set_frame},
        )
        worker.finished.connect(self.qt_dims.stop)
        thread.finished.connect(self.play_stopped.emit)
        self.play_started.emit()
        self.thread = thread
        return worker, thread
Пример #23
0
class CollapseCube(QDialog):
    def __init__(self,
                 data,
                 data_collection=[],
                 allow_preview=False,
                 parent=None):
        super(CollapseCube, self).__init__(parent)

        self.setWindowTitle("Collapse Cube Along Spectral Axis")

        # Get the data_components (e.g., FLUX, DQ, ERROR etc)
        # Using list comprehension to keep the order of the component_ids
        self.data_components = [
            str(x).strip() for x in data.component_ids()
            if not x in data.coordinate_components
        ]

        self.setWindowFlags(self.windowFlags() | Qt.Tool)
        self.title = "Cube Collapse"
        self.data = data
        self.data_collection = data_collection
        self.parent = parent

        self._general_description = "Collapse the data cube over the spectral range based on the mathematical operation.  The nearest index or wavelength will be chosen if a specified number is out of bounds"
        self._custom_description = "To use the spectral viewer to define a region to collapse over, cancel this, create an ROI and then select this Collapse Cube again."

        self.currentAxes = None
        self.currentKernel = None

        self.createUI()

    def createUI(self):
        """
        Create the popup box with the calculation input area and buttons.

        :return:
        """
        boldFont = QtGui.QFont()
        boldFont.setBold(True)

        # Create data component label and input box
        self.widget_desc = QLabel(self._general_description)
        self.widget_desc.setWordWrap(True)
        self.widget_desc.setFixedWidth(350)
        self.widget_desc.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hb_desc = QHBoxLayout()
        hb_desc.addWidget(self.widget_desc)

        # Create data component label and input box
        self.data_label = QLabel("Data:")
        self.data_label.setFixedWidth(100)
        self.data_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.data_label.setFont(boldFont)

        self.data_combobox = QComboBox()
        self.data_combobox.addItems([
            str(x).strip() for x in self.data.component_ids()
            if not x in self.data.coordinate_components
        ])
        self.data_combobox.setMinimumWidth(200)

        hb_data = QHBoxLayout()
        hb_data.addWidget(self.data_label)
        hb_data.addWidget(self.data_combobox)

        # Create operation label and input box
        self.operation_label = QLabel("Operation:")
        self.operation_label.setFixedWidth(100)
        self.operation_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.operation_label.setFont(boldFont)

        self.operation_combobox = QComboBox()
        self.operation_combobox.addItems(operations.keys())
        self.operation_combobox.setMinimumWidth(200)

        hb_operation = QHBoxLayout()
        hb_operation.addWidget(self.operation_label)
        hb_operation.addWidget(self.operation_combobox)

        # Create region label and input box
        self.region_label = QLabel("region:")
        self.region_label.setFixedWidth(100)
        self.region_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.region_label.setFont(boldFont)

        self.region_combobox = QComboBox()

        # Get the Specviz regions and add them in to the Combo box
        for roi in self.parent.specviz._widget.roi_bounds:
            self.region_combobox.addItem("Specviz ROI ({:.3}, {:.3})".format(
                roi[0], roi[1]))

        self.region_combobox.addItems(
            ["Custom (Wavelengths)", "Custom (Indices)"])
        self.region_combobox.setMinimumWidth(200)
        self.region_combobox.currentIndexChanged.connect(
            self._region_selection_change)

        hb_region = QHBoxLayout()
        hb_region.addWidget(self.region_label)
        hb_region.addWidget(self.region_combobox)

        # Create error label
        self.error_label = QLabel("")
        self.error_label.setFixedWidth(100)

        self.error_label_text = QLabel("")
        self.error_label_text.setMinimumWidth(200)
        self.error_label_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hbl_error = QHBoxLayout()
        hbl_error.addWidget(self.error_label)
        hbl_error.addWidget(self.error_label_text)

        # Create start label and input box
        self.start_label = QLabel("Start:")
        self.start_label.setFixedWidth(100)
        self.start_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.start_label.setFont(boldFont)

        self.start_text = QLineEdit()
        self.start_text.setMinimumWidth(200)
        self.start_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hb_start = QHBoxLayout()
        hb_start.addWidget(self.start_label)
        hb_start.addWidget(self.start_text)

        # Create end label and input box
        self.end_label = QLabel("End:")
        self.end_label.setFixedWidth(100)
        self.end_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.end_label.setFont(boldFont)

        self.end_text = QLineEdit()
        self.end_text.setMinimumWidth(200)
        self.end_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hb_end = QHBoxLayout()
        hb_end.addWidget(self.end_label)
        hb_end.addWidget(self.end_text)

        # Create Calculate and Cancel buttons
        self.calculateButton = QPushButton("Calculate")
        self.calculateButton.clicked.connect(self.calculate_callback)
        self.calculateButton.setDefault(True)

        self.cancelButton = QPushButton("Cancel")
        self.cancelButton.clicked.connect(self.cancel_callback)

        hb_buttons = QHBoxLayout()
        hb_buttons.addStretch(1)
        hb_buttons.addWidget(self.cancelButton)
        hb_buttons.addWidget(self.calculateButton)

        #
        #  Sigma clipping
        #
        vbox_sigma_clipping = QVBoxLayout()

        self.sigma_description = QLabel(
            "Sigma clipping is implemented using <a href='http://docs.astropy.org/en/stable/api/astropy.stats.sigma_clip.html'>astropy.stats.sigma_clip</a>. Empty values will use defaults listed on the webpage, <b>but</b> if the first sigma is empty, then no clipping will be done."
        )
        self.sigma_description.setWordWrap(True)
        hb_sigma = QHBoxLayout()
        hb_sigma.addWidget(self.sigma_description)
        vbox_sigma_clipping.addLayout(hb_sigma)

        # Create sigma
        self.sigma_label = QLabel("Sigma:")
        self.sigma_label.setFixedWidth(100)
        self.sigma_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.sigma_label.setFont(boldFont)
        self.sigma_text = QLineEdit()
        self.sigma_text.setMinimumWidth(200)
        self.sigma_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))
        hb_sigma = QHBoxLayout()
        hb_sigma.addWidget(self.sigma_label)
        hb_sigma.addWidget(self.sigma_text)
        vbox_sigma_clipping.addLayout(hb_sigma)

        # Create sigma_lower
        self.sigma_lower_label = QLabel("Sigma Lower:")
        self.sigma_lower_label.setFixedWidth(100)
        self.sigma_lower_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.sigma_lower_label.setFont(boldFont)
        self.sigma_lower_text = QLineEdit()
        self.sigma_lower_text.setMinimumWidth(200)
        self.sigma_lower_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))
        hb_sigma_lower = QHBoxLayout()
        hb_sigma_lower.addWidget(self.sigma_lower_label)
        hb_sigma_lower.addWidget(self.sigma_lower_text)
        vbox_sigma_clipping.addLayout(hb_sigma_lower)

        # Create sigma_upper
        self.sigma_upper_label = QLabel("Sigma Upper:")
        self.sigma_upper_label.setFixedWidth(100)
        self.sigma_upper_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.sigma_upper_label.setFont(boldFont)
        self.sigma_upper_text = QLineEdit()
        self.sigma_upper_text.setMinimumWidth(200)
        self.sigma_upper_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))
        hb_sigma_upper = QHBoxLayout()
        hb_sigma_upper.addWidget(self.sigma_upper_label)
        hb_sigma_upper.addWidget(self.sigma_upper_text)
        vbox_sigma_clipping.addLayout(hb_sigma_upper)

        # Create sigma_iters
        self.sigma_iters_label = QLabel("Sigma Iterations:")
        self.sigma_iters_label.setFixedWidth(100)
        self.sigma_iters_label.setAlignment((Qt.AlignRight | Qt.AlignTop))
        self.sigma_iters_label.setFont(boldFont)
        self.sigma_iters_text = QLineEdit()
        self.sigma_iters_text.setMinimumWidth(200)
        self.sigma_iters_text.setAlignment((Qt.AlignLeft | Qt.AlignTop))
        hb_sigma_iters = QHBoxLayout()
        hb_sigma_iters.addWidget(self.sigma_iters_label)
        hb_sigma_iters.addWidget(self.sigma_iters_text)
        vbox_sigma_clipping.addLayout(hb_sigma_iters)

        # Add calculation and buttons to popup box
        vbl = QVBoxLayout()
        vbl.addLayout(hb_desc)
        vbl.addLayout(hb_data)
        vbl.addLayout(hb_operation)
        vbl.addLayout(hb_region)
        vbl.addLayout(hb_start)
        vbl.addLayout(hb_end)
        vbl.addLayout(vbox_sigma_clipping)
        vbl.addLayout(hbl_error)
        vbl.addLayout(hb_buttons)

        self.setLayout(vbl)
        self.setMaximumWidth(700)
        self.show()

        # Fire the callback to set the default values for everything
        self._region_selection_change(0)

    def _region_selection_change(self, index):
        """
        Callback for a change on the region selection combo box.

        :param newvalue:
        :return:
        """

        newvalue = self.region_combobox.currentText()

        # First, let's see if this is one of the custom options
        if 'Custom' in newvalue and 'Wavelength' in newvalue:
            # Custom Wavelengths
            self.hide_start_end(False)
            self.start_label.setText("Start Wavelength:")
            self.end_label.setText("End Wavelength:")

        elif 'Custom' in newvalue and 'Indices' in newvalue:
            # Custom indices
            self.hide_start_end(False)
            self.start_label.setText("Start Index:")
            self.end_label.setText("End Index:")

        else:
            # Region defined in specviz
            self.hide_start_end(True)

            # We are going to store the start and end wavelengths in the text boxes even though
            # they are hidden. This way we can use the text boxes later as a hidden storage container.
            # TODO: Should probably save the ROIs so the start and end values are more accurate.
            regex = r"-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?"
            floating = re.findall(regex, newvalue)
            self.start_text.setText(floating[0])
            self.end_text.setText(floating[1])

        # Let's update the text on the widget
        if 'Custom' in newvalue:
            self.widget_desc.setText(self._general_description + "\n\n" +
                                     self._custom_description)
        else:
            self.widget_desc.setText(self._general_description)

    def hide_start_end(self, dohide):
        """
        Show or hide the start and end indices depending if the region
        is defined from the specviz plot OR if we are using custom limits.

        :param dohide:
        :return:
        """
        if dohide:
            self.start_label.hide()
            self.start_text.hide()
            self.end_label.hide()
            self.end_text.hide()
        else:
            self.start_label.show()
            self.start_text.show()
            self.end_label.show()
            self.end_text.show()

    def calculate_callback(self):
        """
        Callback for when they hit calculate
        :return:
        """

        # Grab the values of interest
        data_name = self.data_combobox.currentText()
        start_value = self.start_text.text().strip()
        end_value = self.end_text.text().strip()

        self.error_label_text.setText(' ')
        self.error_label_text.setStyleSheet("color: rgba(255, 0, 0, 128)")

        # Sanity checks first
        if not start_value and not end_value:
            self.start_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.error_label_text.setText(
                'Must set at least one of start or end value')
            return

        wavelengths = np.array(self.parent._wavelengths)

        # If indicies, get them and check to see if the inputs are good.
        if 'Indices' in self.region_combobox.currentText():
            if len(start_value) == 0:
                start_index = 0
            else:
                try:
                    start_index = int(start_value)
                except ValueError as e:
                    self.start_label.setStyleSheet(
                        "color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'Start value must be an integer')
                    return

                if start_index < 0:
                    start_index = 0

            if len(end_value) == 0:
                end_index = len(wavelengths) - 1
            else:
                try:
                    end_index = int(end_value)
                except ValueError as e:
                    self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'End value must be an integer')
                    return

                if end_index > len(wavelengths) - 1:
                    end_index = len(wavelengths) - 1
        else:
            # Wavelength inputs
            if len(start_value) == 0:
                start_index = 0
            else:
                # convert wavelength to float value
                try:
                    start_value = float(start_value)
                except ValueError as e:
                    self.start_label.setStyleSheet(
                        "color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'Start value must be a floating point number')
                    return

                # Look up index
                start_index = np.argsort(np.abs(wavelengths - start_value))[0]

            if len(end_value) == 0:
                end_index = len(wavelengths) - 1

            else:
                # convert wavelength to float value
                try:
                    end_value = float(end_value)
                except ValueError as e:
                    self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'End value must be a floating point number')
                    return

                # Look up index
                end_index = np.argsort(np.abs(wavelengths - end_value))[0]

        # Check to make sure at least one of start or end is within the range of the wavelengths.
        if (start_index < 0
                and end_index < 0) or (start_index > len(wavelengths)
                                       and end_index > len(wavelengths)):
            self.start_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.error_label_text.setText(
                'Can not have both start and end outside of the wavelength range.'
            )
            return

        if start_index > end_index:
            self.start_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.error_label_text.setText(
                'Start value must be less than end value')
            return

        # Check to see if the wavelength (indices) are the same.
        if start_index == end_index:
            self.start_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.end_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
            self.error_label_text.setText(
                'Can not have both start and end wavelengths be the same.')
            return

        # Set the start and end values in the text boxes -- in case they enter one way out of range then
        # we'll fix it.
        ts = start_index if 'Indices' in self.region_combobox.currentText(
        ) else wavelengths[start_index]
        self.start_text.setText('{}'.format(ts))

        te = end_index if 'Indices' in self.region_combobox.currentText(
        ) else wavelengths[end_index]
        self.end_text.setText('{}'.format(te))

        data_name = self.data_combobox.currentText()
        operation = self.operation_combobox.currentText()

        # Do calculation if we got this far
        wavelengths, new_component = collapse_cube(self.data[data_name],
                                                   data_name,
                                                   self.data.coords.wcs,
                                                   operation, start_index,
                                                   end_index)

        # Get the start and end wavelengths from the newly created spectral cube and use for labeling the cube.
        # Convert to the current units.
        start_wavelength = wavelengths[0].to(
            self.parent._units_controller._new_units)
        end_wavelength = wavelengths[-1].to(
            self.parent._units_controller._new_units)

        label = '{}-collapse-{} ({:0.3}, {:0.3})'.format(
            data_name, operation, start_wavelength, end_wavelength)

        # Apply sigma clipping
        sigma = self.sigma_text.text().strip()

        if len(sigma) > 0:
            try:
                sigma = float(sigma)
            except ValueError as e:
                self.sigma_label.setStyleSheet("color: rgba(255, 0, 0, 128)")
                self.error_label_text.setText(
                    'If sigma set, it must be a floating point number')
                return

            sigma_lower = self.sigma_lower_text.text().strip()
            if len(sigma_lower) > 0:
                try:
                    sigma_lower = float(sigma_lower)
                except ValueError as e:
                    self.sigma_lower_label.setStyleSheet(
                        "color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'If sigma lower set, it must be a floating point number'
                    )
                    return
            else:
                sigma_lower = None

            sigma_upper = self.sigma_upper_text.text().strip()
            if len(sigma_upper) > 0:
                try:
                    sigma_upper = float(sigma_upper)
                except ValueError as e:
                    self.sigma_upper_label.setStyleSheet(
                        "color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'If sigma upper set, it must be a floating point number'
                    )
                    return
            else:
                sigma_upper = None

            sigma_iters = self.sigma_iters_text.text().strip()
            if len(sigma_iters) > 0:
                try:
                    sigma_iters = float(sigma_iters)
                except ValueError as e:
                    self.sigma_iters_label.setStyleSheet(
                        "color: rgba(255, 0, 0, 128)")
                    self.error_label_text.setText(
                        'If sigma iters set, it must be a floating point number'
                    )
                    return
            else:
                sigma_iters = None

            new_component = sigma_clip(new_component,
                                       sigma=sigma,
                                       sigma_lower=sigma_lower,
                                       sigma_upper=sigma_upper,
                                       iters=sigma_iters)

            # Add to label so it is clear which overlay/component is which
            if sigma:
                label += ' sigma={}'.format(sigma)

            if sigma_lower:
                label += ' sigma_lower={}'.format(sigma_lower)

            if sigma_upper:
                label += ' sigma_upper={}'.format(sigma_upper)

            if sigma_iters:
                label += ' sigma_iters={}'.format(sigma_iters)

        # Add new overlay/component to cubeviz. We add this both to the 2D
        # container Data object and also as an overlay. In future we might be
        # able to use the 2D container Data object for the overlays directly.
        add_to_2d_container(self.parent, self.data, new_component, label)
        self.parent.add_overlay(new_component, label)

        self.close()

        # Show new dialog
        self.final_dialog(label)

    def final_dialog(self, label):
        """
        Final dialog that to show where the calculated collapsed cube was put.

        :param label:
        :return:
        """

        final_dialog = QDialog()

        # Create data component label and input box
        widget_desc = QLabel(
            'The collapsed cube was added as an overlay with label "{}"'.
            format(label))
        widget_desc.setWordWrap(True)
        widget_desc.setFixedWidth(350)
        widget_desc.setAlignment((Qt.AlignLeft | Qt.AlignTop))

        hb_desc = QHBoxLayout()
        hb_desc.addWidget(widget_desc)

        # Create Ok button
        okButton = QPushButton("Ok")
        okButton.clicked.connect(lambda: final_dialog.close())
        okButton.setDefault(True)

        hb_buttons = QHBoxLayout()
        hb_buttons.addStretch(1)
        hb_buttons.addWidget(okButton)

        # Add description and buttons to popup box
        vbl = QVBoxLayout()
        vbl.addLayout(hb_desc)
        vbl.addLayout(hb_buttons)

        final_dialog.setLayout(vbl)
        final_dialog.setMaximumWidth(400)
        final_dialog.show()

    def cancel_callback(self, caller=0):
        """
        Cancel callback when the person hits the cancel button

        :param caller:
        :return:
        """
        self.close()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.cancel_callback()