Esempio n. 1
0
def PropertyDistribtionTable(dict_prop):
    """
    -i- dict_prop : dictionary, properties.
    -o- table : QTableWidget, for use in PyQt GUI.
    Return a table widget of properties data value distribution.
    """
    props = [prop for prop in dict_prop]
    nrow = len(props)
    columnNames = ['Name', 'Min', 'P10', 'P50', 'P90', 'Max']
    ncol = len(columnNames)
    table = QTableWidget()
    table.setRowCount(nrow)
    table.setColumnCount(ncol)
    table.setHorizontalHeaderLabels(columnNames)
    key = 'arrayPercentiles'
    for i in range(nrow):
        prop = props[i]
        if not key in dict_prop[prop]:
            add_array_percentile(dict_prop[prop])
        p0, p10, p50, p90, p100 = dict_prop[prop][key]
        state = [prop, p0, p10, p50, p90, p100]
        state = [str(a) for a in state]  # convert to string
        for j in range(ncol):
            table.setItem(i, j, QTableWidgetItem(state[j]))
    table.sortItems(0, Qt.AscendingOrder)
    return table
class SFeaturesViewer(QWidget):
    """Widget to display a the tracks features table

    Parameters
    ----------
    napari_viewer: QWidget
        The napari viewer

    """
    def __init__(self, napari_viewer):
        super().__init__()
        self.viewer = napari_viewer
        self.layer_name = ''

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        self.tableWidget = QTableWidget()
        self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        layout.addWidget(self.tableWidget)
        self.setLayout(layout)

    def reload(self):
        """Reload the tracks features from the layers to the table widget"""
        particles = self.viewer.layers[self.layer_name].data
        features = self.viewer.layers[self.layer_name].metadata
        headers = ['track_id']
        for key in features:
            if isinstance(features[key], dict):
                headers.append(key)
        self.tableWidget.setColumnCount(len(headers))
        self.tableWidget.setHorizontalHeaderLabels(headers)
        self.tableWidget.setRowCount(len(np.unique(particles[:, 0])))

        features_dict = dict()
        tracks_ids = np.unique(particles[:, 0])
        for id_ in tracks_ids:
            features_dict[id_] = list()

        for key in features:
            if isinstance(features[key], dict):
                feature_dict = features[key]
                for f_key in feature_dict:
                    if f_key in features_dict:
                        features_dict[f_key].append(str(feature_dict[f_key]))

        line = -1
        for key in features_dict:
            line += 1
            # track_id = key
            self.tableWidget.setItem(line, 0, QTableWidgetItem(str(key)))
            # add each feature per column
            col = 0
            for feature in features_dict[key]:
                col += 1
                self.tableWidget.setItem(line, col, QTableWidgetItem(feature))
Esempio n. 3
0
 def create_page_game(self):
     widget = QTableWidget()
     widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
     widget.setSortingEnabled(True)
     widget.setRowCount(1000)
     widget.setColumnCount(len(GAME_LIST))
     widget.setHorizontalHeaderLabels(GAME_LIST)
     header = widget.horizontalHeader()
     for i in range(len(GAME_LIST)):
         header.setSectionResizeMode(i, QHeaderView.ResizeToContents)
     return widget
Esempio n. 4
0
 def create_page_rank(self):
     widget = QTableWidget()
     widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
     widget.setSortingEnabled(True)
     widget.setRowCount(20)
     widget.setColumnCount(len(RANK_LIST))
     widget.setHorizontalHeaderLabels(RANK_LIST)
     header = widget.horizontalHeader()
     for i in range(len(RANK_LIST)):
         header.setSectionResizeMode(i, QHeaderView.ResizeToContents)
     widget.horizontalHeader().sortIndicatorChanged.connect(
         self.update_chart)
     return widget
Esempio n. 5
0
 def setup_ui(self):
     self.setLayout(QVBoxLayout())
     bpm_table = QTableWidget(self)
     col_labels = ["X Pos (mm)", "Y Pos (mm)"]
     bpm_table.setColumnCount(len(col_labels))
     bpm_table.setHorizontalHeaderLabels(col_labels)
     data = dev_list(self.start_marker, self.end_marker)
     bpm_names = [bpm['device'] for bpm in data]
     bpm_table.setRowCount(len(bpm_names))
     bpm_table.setVerticalHeaderLabels(bpm_names)
     for row, bpm_name in enumerate(bpm_names):
         for col, axis in enumerate(("X", "Y")):
             pos = PyDMLabel()
             pos.channel = "ca://{}:{}".format(bpm_name, axis)
             pos.showUnits = True
             bpm_table.setCellWidget(row, col, pos)
     self.layout().addWidget(bpm_table)
Esempio n. 6
0
    def initUI(self):
        self.w.setWindowTitle(u'回答結果')
        vbox = QVBoxLayout()
        ansBtn = QPushButton(u"閉じる")
        ansBtn.clicked.connect(self.closeWindow)

        self.tabs = QTabWidget()

        for i in range(self.numTab):
            tab = QTableWidget()
            tab.setRowCount(9)  # あとで9のところは変更してください。
            tab.setColumnCount(2)
            self.tabs.addTab(tab, "第" + str(i + 1) + "候補")
            self.tables.append(tab)

        vbox.addWidget(self.tabs)
        vbox.addWidget(ansBtn)
        self.w.setLayout(vbox)
Esempio n. 7
0
class D0(QGroupBox):
    def __init__(self, parent=None):
        self._parent = parent
        super().__init__(parent)
        self.setTitle("Define d₀")
        layout = QVBoxLayout()
        self.d0_grid_switch = QComboBox()
        self.d0_grid_switch.addItems(["Constant", "Field"])
        self.d0_grid_switch.currentTextChanged.connect(self.set_case)
        layout.addWidget(self.d0_grid_switch)
        self.d0_box = QWidget()
        d0_box_layout = QHBoxLayout()
        d0_box_layout.addWidget(QLabel("d₀"))
        validator = QDoubleValidator()
        validator.setBottom(0)
        self.d0 = QLineEdit()
        self.d0.setValidator(validator)
        self.d0.editingFinished.connect(self.update_d0)
        d0_box_layout.addWidget(self.d0)
        d0_box_layout.addWidget(QLabel("Δd₀"))
        self.d0e = QLineEdit()
        self.d0e.setValidator(validator)
        self.d0e.editingFinished.connect(self.update_d0)
        d0_box_layout.addWidget(self.d0e)
        self.d0_box.setLayout(d0_box_layout)
        layout.addWidget(self.d0_box)

        load_save = QWidget()
        load_save_layout = QHBoxLayout()
        self.load_grid = QPushButton("Load d₀ Grid")
        self.load_grid.clicked.connect(self.load_d0_field)
        load_save_layout.addWidget(self.load_grid)
        self.save_grid = QPushButton("Save d₀ Grid")
        self.save_grid.clicked.connect(self.save_d0_field)
        load_save_layout.addWidget(self.save_grid)
        load_save.setLayout(load_save_layout)
        layout.addWidget(load_save)
        self.d0_grid = QTableWidget()
        self.d0_grid.setColumnCount(5)
        self.d0_grid.setColumnWidth(0, 60)
        self.d0_grid.setColumnWidth(1, 60)
        self.d0_grid.setColumnWidth(2, 60)
        self.d0_grid.setColumnWidth(3, 60)
        self.d0_grid.verticalHeader().setVisible(False)
        self.d0_grid.horizontalHeader().setStretchLastSection(True)
        self.d0_grid.setHorizontalHeaderLabels(['vx', 'vy', 'vz', "d₀", "Δd₀"])
        spinBoxDelegate = SpinBoxDelegate()
        self.d0_grid.setItemDelegateForColumn(3, spinBoxDelegate)
        self.d0_grid.setItemDelegateForColumn(4, spinBoxDelegate)
        layout.addWidget(self.d0_grid)

        self.setLayout(layout)

        self.set_case('Constant')

    def set_case(self, case):
        if case == "Constant":
            self.d0_box.setEnabled(True)
            self.load_grid.setEnabled(False)
            self.save_grid.setEnabled(False)
            self.d0_grid.setEnabled(False)
        else:
            self.d0_box.setEnabled(False)
            self.load_grid.setEnabled(True)
            self.save_grid.setEnabled(True)
            self.d0_grid.setEnabled(True)

    def update_d0(self):
        self._parent.update_plot()

    def set_d0(self, d0, d0e):
        if d0 is None:
            self.d0.clear()
            self.d0e.clear()
        else:
            self.d0.setText(str(d0))
            self.d0e.setText(str(d0e))

    def set_d0_field(self, x, y, z, d0, d0e):
        if x is None:
            self.d0_grid.clearContents()
        else:
            self.d0_grid.setRowCount(len(x))

            for n in range(len(x)):
                x_item = QTableWidgetItem(f'{x[n]: 7.2f}')
                x_item.setFlags(x_item.flags() ^ Qt.ItemIsEditable)
                y_item = QTableWidgetItem(f'{y[n]: 7.2f}')
                y_item.setFlags(y_item.flags() ^ Qt.ItemIsEditable)
                z_item = QTableWidgetItem(f'{z[n]: 7.2f}')
                z_item.setFlags(z_item.flags() ^ Qt.ItemIsEditable)
                d0_item = QTableWidgetItem()
                d0_item.setData(Qt.EditRole, float(d0[n]))
                d0e_item = QTableWidgetItem()
                d0e_item.setData(Qt.EditRole, float(d0e[n]))
                self.d0_grid.setItem(n, 0, QTableWidgetItem(x_item))
                self.d0_grid.setItem(n, 1, QTableWidgetItem(y_item))
                self.d0_grid.setItem(n, 2, QTableWidgetItem(z_item))
                self.d0_grid.setItem(n, 3, QTableWidgetItem(d0_item))
                self.d0_grid.setItem(n, 4, QTableWidgetItem(d0e_item))

    def get_d0_field(self):
        if self.d0_grid.rowCount() == 0:
            return None
        else:
            x = [
                float(self.d0_grid.item(row, 0).text())
                for row in range(self.d0_grid.rowCount())
            ]
            y = [
                float(self.d0_grid.item(row, 1).text())
                for row in range(self.d0_grid.rowCount())
            ]
            z = [
                float(self.d0_grid.item(row, 2).text())
                for row in range(self.d0_grid.rowCount())
            ]
            d0 = [
                float(self.d0_grid.item(row, 3).text())
                for row in range(self.d0_grid.rowCount())
            ]
            d0e = [
                float(self.d0_grid.item(row, 4).text())
                for row in range(self.d0_grid.rowCount())
            ]
            return (d0, d0e, x, y, z)

    def save_d0_field(self):
        filename, _ = QFileDialog.getSaveFileName(
            self, "Save d0 Grid", "", "CSV (*.csv);;All Files (*)")
        if filename:
            d0, d0e, x, y, z = self.get_d0_field()
            np.savetxt(filename,
                       np.array([x, y, z, d0, d0e]).T,
                       fmt=['%.4g', '%.4g', '%.4g', '%.9g', '%.9g'],
                       header="vx, vy, vz, d0, d0_error",
                       delimiter=',')

    def load_d0_field(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Load d0 Grid", "", "CSV (*.csv);;All Files (*)")
        if filename:
            x, y, z, d0, d0e = np.loadtxt(filename, delimiter=',', unpack=True)
            self.set_d0_field(x, y, z, d0, d0e)

    def get_d0(self):
        if self.d0_grid_switch.currentText() == "Constant":
            try:
                return (float(self.d0.text()), float(self.d0e.text()))
            except ValueError:
                return None
        else:
            return self.get_d0_field()
Esempio n. 8
0
class WndDetailedFittingParams(SecondaryWindow):

    # Signal that is sent (to main window) to update global state of the program
    update_global_state = Signal()
    computations_complete = Signal(object)

    def __init__(self, *, window_title, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        # Reference to the main window. The main window will hold
        #   references to all non-modal windows that could be opened
        #   from multiple places in the program.
        self.ref_main_window = self.gui_vars["ref_main_window"]

        self.update_global_state.connect(
            self.ref_main_window.update_widget_state)

        self._enable_events = False

        self._dialog_data = {}

        self._load_dialog_data()
        self._selected_index = 0
        self._selected_eline = "-"

        self.setWindowTitle(window_title)
        self.setMinimumWidth(1100)
        self.setMinimumHeight(500)
        self.resize(1100, 500)

        hbox_el_select = self._setup_element_selection()
        self._setup_table()

        vbox = QVBoxLayout()
        vbox.addLayout(hbox_el_select)
        vbox.addWidget(self.table)

        self.setLayout(vbox)

        self.update_form_data()
        self._enable_events = True
        self._data_changed = False

    def _setup_element_selection(self):

        self.combo_element_sel = QComboBox()
        self.combo_element_sel.setMinimumWidth(200)
        self.combo_element_sel.currentIndexChanged.connect(
            self.combo_element_sel_current_index_changed)

        self.pb_apply = QPushButton("Apply")
        self.pb_apply.setEnabled(False)
        self.pb_apply.clicked.connect(self.pb_apply_clicked)
        self.pb_cancel = QPushButton("Cancel")
        self.pb_cancel.setEnabled(False)
        self.pb_cancel.clicked.connect(self.pb_cancel_clicked)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Select element:"))
        hbox.addWidget(self.combo_element_sel)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_apply)
        hbox.addWidget(self.pb_cancel)

        return hbox

    def _setup_table(self):

        self._value_keys = ("value", "min", "max")

        # Labels for horizontal header
        labels_presets = [
            fitting_preset_names[_] for _ in self._fit_strategy_list
        ]
        labels_values = [value_names[_] for _ in self._value_keys]
        self.tbl_labels = ["Name", "E, keV"] + labels_values + labels_presets
        # Labels for editable columns
        self.tbl_cols_editable = ("Value", "Min", "Max")
        # Labels for the columns that contain combo boxes
        self.tbl_cols_combobox = labels_presets
        # The list of columns with fixed size
        self.tbl_cols_stretch = ("Value", "Min", "Max")
        # Table item representation if different from default
        self.tbl_format = {
            "E, keV": ".4f",
            "Value": ".8g",
            "Min": ".8g",
            "Max": ".8g"
        }

        # Combobox items. All comboboxes in the table contain identical list of items.
        self.combo_items = self._bound_options

        self._combo_list = []

        self.table = QTableWidget()
        self.table.setColumnCount(len(self.tbl_labels))
        self.table.verticalHeader().hide()
        self.table.setHorizontalHeaderLabels(self.tbl_labels)

        self.table.setStyleSheet("QTableWidget::item{color: black;}")

        header = self.table.horizontalHeader()
        for n, lbl in enumerate(self.tbl_labels):
            # Set stretching for the columns
            if lbl in self.tbl_cols_stretch:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            else:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)

        self.table.itemChanged.connect(self.tbl_elines_item_changed)

    def _fill_table(self, table_contents):
        self._enable_events = False

        # Clear the list of combo boxes
        for item in self._combo_list:
            item.currentIndexChanged.disconnect(
                self.combo_strategy_current_index_changed)
        self._combo_list = []

        self.table.clearContents()

        self.table.setRowCount(len(table_contents))
        for nr, row in enumerate(table_contents):
            n_fit_strategy = 0
            row_name = row[0]
            for nc, entry in enumerate(row):
                label = self.tbl_labels[nc]

                # Set alternating background colors for the table rows
                #   Make background for editable items a little brighter
                brightness = 240 if label in self.tbl_cols_editable else 220
                if nr % 2:
                    rgb_bckg = (255, brightness, brightness)
                else:
                    rgb_bckg = (brightness, 255, brightness)

                if label not in self.tbl_cols_combobox:
                    if label in self.tbl_format and not isinstance(entry, str):
                        fmt = self.tbl_format[self.tbl_labels[nc]]
                        s = ("{:" + fmt + "}").format(entry)
                    else:
                        s = f"{entry}"

                    item = QTableWidgetItem(s)
                    if nc > 0:
                        item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)

                    # Set all columns not editable (unless needed)
                    if label not in self.tbl_cols_editable:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

                    # Make all items not selectable (we are not using selections)
                    item.setFlags(item.flags() & ~Qt.ItemIsSelectable)

                    # Note, that there is no way to set style sheet for QTableWidgetItem
                    item.setBackground(QBrush(QColor(*rgb_bckg)))

                    self.table.setItem(nr, nc, item)

                else:
                    if n_fit_strategy < len(self._fit_strategy_list):
                        combo_name = f"{row_name},{self._fit_strategy_list[n_fit_strategy]}"
                    else:
                        combo_name = ""
                    n_fit_strategy += 1

                    item = ComboBoxNamedNoWheel(name=combo_name)

                    # Set text color for QComboBox widget (necessary if the program is used with Dark theme)
                    pal = item.palette()
                    pal.setColor(QPalette.ButtonText, Qt.black)
                    item.setPalette(pal)
                    # Set text color for drop-down view (necessary if the program is used with Dark theme)
                    pal = item.view().palette()
                    pal.setColor(QPalette.Text, Qt.black)
                    item.view().setPalette(pal)

                    css1 = get_background_css(rgb_bckg,
                                              widget="QComboBox",
                                              editable=False)
                    css2 = get_background_css(rgb_bckg,
                                              widget="QWidget",
                                              editable=False)
                    item.setStyleSheet(css2 + css1)

                    item.addItems(self.combo_items)
                    if item.findText(entry) < 0:
                        logger.warning(
                            f"Text '{entry}' is not found. The ComboBox is not set properly."
                        )
                    item.setCurrentText(entry)  # Try selecting the item anyway
                    self.table.setCellWidget(nr, nc, item)

                    item.currentIndexChanged.connect(
                        self.combo_strategy_current_index_changed)
                    self._combo_list.append(item)

        self._enable_events = True

    def _set_tooltips(self):
        set_tooltip(self.pb_apply, "Save changes and <b>update plots</b>.")
        set_tooltip(self.pb_cancel, "<b>Discard</b> all changes.")
        set_tooltip(
            self.combo_element_sel,
            "Select K, L or M <b>emission line</b> to edit the optimization parameters "
            "used for the line during total spectrum fitting.",
        )
        set_tooltip(
            self.table,
            "Edit optimization parameters for the selected emission line. "
            "Processing presets may be configured by specifying optimization strategy "
            "for each parameter may be selected. A preset for each fitting step "
            "of the total spectrum fitting may be selected in <b>Model</b> tab.",
        )

    def combo_element_sel_current_index_changed(self, index):
        self._selected_index = index
        try:
            self._selected_eline = self._eline_list[index]
        except Exception:
            self._selected_eline = "-"
        self._update_table()

    def combo_strategy_current_index_changed(self, name, index):
        if self._enable_events:
            try:
                name_row, name_strategy = name.split(",")
                option = self._bound_options[index]
                self._param_dict[name_row][name_strategy] = option
            except Exception as ex:
                logger.error(
                    f"Error occurred while changing strategy options: {ex}")

            self._data_changed = True
            self._validate_all()

    def tbl_elines_item_changed(self, item):
        if self._enable_events:
            try:
                n_row, n_col = self.table.row(item), self.table.column(item)
                n_key = n_col - 2
                if n_key < 0 or n_key >= len(self._value_keys):
                    raise RuntimeError(f"Incorrect column {n_col}")
                value_key = self._value_keys[n_key]
                eline_key = self._table_contents[n_row][0]
                try:
                    value = float(item.text())
                    self._param_dict[eline_key][value_key] = value
                except Exception:
                    value = self._param_dict[eline_key][value_key]
                    item.setText(f"{value:.8g}")

                self._data_changed = True
                self._validate_all()

            except Exception as ex:
                logger.error(
                    f"Error occurred while setting edited value: {ex}")

    def pb_apply_clicked(self):
        """Save dialog data and update plots"""
        self.save_form_data()

    def pb_cancel_clicked(self):
        """Reload data (discard all changes)"""
        self.update_form_data()

    def _set_combo_element_sel_items(self):
        element_list = self._eline_list
        self.combo_element_sel.clear()
        self.combo_element_sel.addItems(element_list)
        # Deselect all (this should clear the table)
        self.select_eline(self._selected_eline)

    def _set_dialog_data(self, dialog_data):
        self._param_dict = dialog_data["param_dict"]
        self._eline_list = dialog_data["eline_list"]
        self._eline_key_dict = dialog_data["eline_key_dict"]
        self._eline_energy_dict = dialog_data["eline_energy_dict"]
        self._other_param_list = dialog_data["other_param_list"]
        self._fit_strategy_list = dialog_data["fit_strategy_list"]
        self._bound_options = dialog_data["bound_options"]

    def _show_all(self):
        selected_eline = self._selected_eline
        self._set_combo_element_sel_items()
        self.select_eline(selected_eline)
        self._update_table()

    def _update_table(self):
        self._enable_events = False

        eline_list = []
        if "shared" in self._selected_eline.lower():  # Shared parameters
            eline_list = self._other_param_list
            energy_list = [""] * len(eline_list)
        elif self._selected_eline == self._eline_list[
                self._selected_index]:  # Emission lines
            eline = self._selected_eline
            eline_list = self._eline_key_dict[eline]
            energy_list = self._eline_energy_dict[eline]

        self._table_contents = []
        for n, key in enumerate(eline_list):
            data = [
                key,
                energy_list[n],
                self._param_dict[key]["value"],
                self._param_dict[key]["min"],
                self._param_dict[key]["max"],
            ]
            for strategy in self._fit_strategy_list:
                data.append(self._param_dict[key][strategy])
            self._table_contents.append(data)

        self._fill_table(self._table_contents)
        self._enable_events = True

    def select_eline(self, eline):
        if eline in self._eline_list:
            index = self._eline_list.index(eline)
        elif self._eline_list:
            index = 0
            eline = self._eline_list[0]
        else:
            index = -1
            eline = "-"
        self._selected_eline = eline
        self._selected_index = index
        self.combo_element_sel.setCurrentIndex(index)
        self._update_table()

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        if condition == "tooltips":
            self._set_tooltips()

    def _validate_all(self):
        self.pb_apply.setEnabled(self._data_changed)
        self.pb_cancel.setEnabled(self._data_changed)

    def _load_dialog_data(self):
        ...

    def _save_dialog_data_function(self):
        ...

    def update_form_data(self):
        self._load_dialog_data()
        self._show_all()
        self._data_changed = False
        self._validate_all()

    def save_form_data(self):
        if self._data_changed:
            f_save_data = self._save_dialog_data_function()

            def cb(dialog_data):
                try:
                    f_save_data(dialog_data)
                    success, msg = True, ""
                except Exception as ex:
                    success, msg = False, str(ex)
                return {"success": success, "msg": msg}

            self._compute_in_background(cb,
                                        self.slot_save_form_data,
                                        dialog_data=self._dialog_data)

    @Slot(object)
    def slot_save_form_data(self, result):
        self._recover_after_compute(self.slot_save_form_data)

        if not result["success"]:
            msg = result["msg"]
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Failed to Apply Fit Parameters",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        else:
            self._data_changed = False
            self._validate_all()

        self.gui_vars["gui_state"]["state_model_fit_exists"] = False
        self.update_global_state.emit()

    def _compute_in_background(self, func, slot, *args, **kwargs):
        """
        Run function `func` in a background thread. Send the signal
        `self.computations_complete` once computation is finished.

        Parameters
        ----------
        func: function
            Reference to a function that is supposed to be executed at the background.
            The function return value is passed as a signal parameter once computation is
            complete.
        slot: qtpy.QtCore.Slot or None
            Reference to a slot. If not None, then the signal `self.computation_complete`
            is connected to this slot.
        args, kwargs
            arguments of the function `func`.
        """
        signal_complete = self.computations_complete

        def func_to_run(func, *args, **kwargs):
            class RunTask(QRunnable):
                def run(self):
                    result_dict = func(*args, **kwargs)
                    signal_complete.emit(result_dict)

            return RunTask()

        if slot is not None:
            self.computations_complete.connect(slot)
        self.gui_vars["gui_state"]["running_computations"] = True
        self.update_global_state.emit()
        QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs))

    def _recover_after_compute(self, slot):
        """
        The function should be called after the signal `self.computations_complete` is
        received. The slot should be the same as the one used when calling
        `self.compute_in_background`.
        """
        if slot is not None:
            self.computations_complete.disconnect(slot)
        self.gui_vars["gui_state"]["running_computations"] = False
        self.update_global_state.emit()
Esempio n. 9
0
class Extension2ReaderTable(QWidget):
    """Table showing extension to reader mappings with removal button.

    Widget presented in preferences-plugin dialog."""

    valueChanged = Signal(int)

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._table = QTableWidget()
        self._table.setShowGrid(False)
        self._populate_table()

        layout = QVBoxLayout()
        layout.addWidget(self._table)
        self.setLayout(layout)

    def _populate_table(self):
        """Add row for each extension to reader mapping in settings"""
        self._extension_col = 0
        self._reader_col = 1

        header_strs = [trans._('Extension'), trans._('Reader Plugin')]

        self._table.setColumnCount(2)
        self._table.setColumnWidth(self._extension_col, 100)
        self._table.setColumnWidth(self._reader_col, 150)
        self._table.verticalHeader().setVisible(False)
        self._table.setMinimumHeight(120)

        extension2reader = get_settings().plugins.extension2reader
        if len(extension2reader) > 0:
            self._table.setRowCount(len(extension2reader))
            self._table.horizontalHeader().setStretchLastSection(True)
            self._table.horizontalHeader().setStyleSheet(
                'border-bottom: 2px solid white;')
            self._table.setHorizontalHeaderLabels(header_strs)

            for row, (extension,
                      plugin_name) in enumerate(extension2reader.items()):
                item = QTableWidgetItem(extension)
                item.setFlags(Qt.NoItemFlags)
                self._table.setItem(row, self._extension_col, item)

                plugin_widg = QWidget()
                # need object name to easily find row
                plugin_widg.setObjectName(f'{extension}')
                plugin_widg.setLayout(QHBoxLayout())
                plugin_widg.layout().setContentsMargins(0, 0, 0, 0)

                plugin_label = QLabel(plugin_name)
                # need object name to easily work out which button was clicked
                remove_btn = QPushButton('x', objectName=f'{extension}')
                remove_btn.setFixedWidth(30)
                remove_btn.setStyleSheet('margin: 4px;')
                remove_btn.setToolTip(
                    trans._('Remove this extension to reader association'))
                remove_btn.clicked.connect(self._remove_extension_assignment)

                plugin_widg.layout().addWidget(plugin_label)
                plugin_widg.layout().addWidget(remove_btn)
                self._table.setCellWidget(row, self._reader_col, plugin_widg)
        else:
            # Display that there are no extensions with reader associations
            self._table.setRowCount(1)
            self._table.setHorizontalHeaderLabels(header_strs)

            self._table.setColumnHidden(self._reader_col, True)
            self._table.setColumnWidth(self._extension_col, 200)
            item = QTableWidgetItem(trans._('No extensions found.'))
            item.setFlags(Qt.NoItemFlags)
            self._table.setItem(0, 0, item)

    def _remove_extension_assignment(self, event):
        """Delete extension to reader mapping setting and remove table row"""
        extension_to_remove = self.sender().objectName()
        current_settings = get_settings().plugins.extension2reader
        # need explicit assignment to new object here for persistence
        get_settings().plugins.extension2reader = {
            k: v
            for k, v in current_settings.items() if k != extension_to_remove
        }

        for i in range(self._table.rowCount()):
            row_widg_name = self._table.cellWidget(
                i, self._reader_col).objectName()
            if row_widg_name == extension_to_remove:
                self._table.removeRow(i)
                return
Esempio n. 10
0
class SimpleMeasurements(QWidget):
    def __init__(self, settings: StackSettings, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.calculate_btn = QPushButton("Calculate")
        self.calculate_btn.clicked.connect(self.calculate)
        self.result_view = QTableWidget()
        self.channel_select = ChannelComboBox()
        self.units_select = QEnumComboBox(enum_class=Units)
        self.units_select.setCurrentEnum(
            self.settings.get("simple_measurements.units", Units.nm))
        self.units_select.currentIndexChanged.connect(self.change_units)
        self._shift = 2

        layout = QHBoxLayout()
        self.measurement_layout = QVBoxLayout()
        l1 = QHBoxLayout()
        l1.addWidget(QLabel("Units"))
        l1.addWidget(self.units_select)
        self.measurement_layout.addLayout(l1)
        l2 = QHBoxLayout()
        l2.addWidget(QLabel("Channel"))
        l2.addWidget(self.channel_select)
        self.measurement_layout.addLayout(l2)
        layout.addLayout(self.measurement_layout)
        result_layout = QVBoxLayout()
        result_layout.addWidget(self.result_view)
        result_layout.addWidget(self.calculate_btn)
        layout.addLayout(result_layout)
        self.setLayout(layout)
        self.setWindowTitle("Measurement")
        if self.window() == self:
            with suppress(KeyError):
                geometry = self.settings.get_from_profile(
                    "simple_measurement_window_geometry")
                self.restoreGeometry(
                    QByteArray.fromHex(bytes(geometry, "ascii")))

    def closeEvent(self, event: QCloseEvent) -> None:
        """
        Save geometry if widget is used as standalone window.
        """
        if self.window() == self:
            self.settings.set_in_profile(
                "simple_measurement_window_geometry",
                self.saveGeometry().toHex().data().decode("ascii"))
        super().closeEvent(event)

    def calculate(self):
        if self.settings.roi is None:
            QMessageBox.warning(self, "No segmentation",
                                "need segmentation to work")
            return
        to_calculate = []
        for i in range(self._shift, self.measurement_layout.count()):
            # noinspection PyTypeChecker
            chk: QCheckBox = self.measurement_layout.itemAt(i).widget()
            if chk.isChecked():
                leaf: Leaf = MEASUREMENT_DICT[chk.text()].get_starting_leaf()
                to_calculate.append(
                    leaf.replace_(per_component=PerComponent.Yes,
                                  area=AreaType.ROI))
        if not to_calculate:
            QMessageBox.warning(self, "No measurement",
                                "Select at least one measurement")
            return

        profile = MeasurementProfile(
            "", [MeasurementEntry(x.name, x) for x in to_calculate])

        dial = ExecuteFunctionDialog(
            profile.calculate,
            kwargs={
                "image": self.settings.image,
                "channel_num": self.channel_select.get_value(),
                "roi": self.settings.roi_info,
                "result_units": self.units_select.currentEnum(),
            },
        )
        dial.exec_()
        result: MeasurementResult = dial.get_result()
        values = result.get_separated()
        labels = result.get_labels()
        self.result_view.clear()
        self.result_view.setColumnCount(len(values) + 1)
        self.result_view.setRowCount(len(labels))
        for i, val in enumerate(labels):
            self.result_view.setItem(i, 0, QTableWidgetItem(val))
        for j, values_list in enumerate(values):
            for i, val in enumerate(values_list):
                self.result_view.setItem(i, j + 1, QTableWidgetItem(str(val)))

    def _clean_measurements(self):
        selected = set()
        for _ in range(self.measurement_layout.count() - self._shift):
            # noinspection PyTypeChecker
            chk: QCheckBox = self.measurement_layout.takeAt(
                self._shift).widget()
            if chk.isChecked():
                selected.add(chk.text())
            chk.deleteLater()
        return selected

    def refresh_measurements(self):
        selected = self._clean_measurements()
        for val in MEASUREMENT_DICT.values():
            area = val.get_starting_leaf().area
            pc = val.get_starting_leaf().per_component
            if (val.get_fields() or (area is not None and area != AreaType.ROI)
                    or (pc is not None and pc != PerComponent.Yes)):
                continue
            text = val.get_name()
            chk = QCheckBox(text)
            if text in selected:
                chk.setChecked(True)
            self.measurement_layout.addWidget(chk)

    def keyPressEvent(self, e: QKeyEvent):
        if not e.modifiers() & Qt.ControlModifier:
            return
        selected = self.result_view.selectedRanges()

        if e.key() == Qt.Key_C:  # copy
            s = ""

            for r in range(selected[0].topRow(), selected[0].bottomRow() + 1):
                for c in range(selected[0].leftColumn(),
                               selected[0].rightColumn() + 1):
                    try:
                        s += str(self.result_view.item(r, c).text()) + "\t"
                    except AttributeError:
                        s += "\t"
                s = s[:-1] + "\n"  # eliminate last '\t'
            QApplication.clipboard().setText(s)

    def event(self, event: QEvent) -> bool:
        if event.type() == QEvent.WindowActivate:
            if self.settings.image is not None:
                self.channel_select.change_channels_num(
                    self.settings.image.channels)
            self.refresh_measurements()

        return super().event(event)

    def change_units(self):
        self.settings.set("simple_measurements.units",
                          self.units_select.currentEnum())
Esempio n. 11
0
class PackagesDialog(DialogBase):
    """Package dependencies dialog."""

    sig_setup_ready = Signal()

    def __init__(
        self,
        parent=None,
        packages=None,
        pip_packages=None,
        remove_only=False,
        update_only=False,
    ):
        """About dialog."""
        super(PackagesDialog, self).__init__(parent=parent)

        # Variables
        self.api = AnacondaAPI()
        self.actions = None
        self.packages = packages or []
        self.pip_packages = pip_packages or []

        # Widgets
        self.stack = QStackedWidget()
        self.table = QTableWidget()
        self.text = QTextEdit()
        self.label_description = LabelBase()
        self.label_status = LabelBase()
        self.progress_bar = QProgressBar()
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')

        # Widget setup
        self.text.setReadOnly(True)
        self.stack.addWidget(self.table)
        self.stack.addWidget(self.text)
        if remove_only:
            text = 'The following packages will be removed:<br>'
        else:
            text = 'The following packages will be modified:<br>'
        self.label_description.setText(text)
        self.label_description.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_status.setWordWrap(True)
        self.table.horizontalScrollBar().setVisible(False)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setAlternatingRowColors(True)
        self.table.setSelectionMode(QAbstractItemView.NoSelection)
        self.table.setSortingEnabled(True)
        self._hheader = self.table.horizontalHeader()
        self._vheader = self.table.verticalHeader()
        self._hheader.setStretchLastSection(True)
        self._hheader.setDefaultAlignment(Qt.AlignLeft)
        self._hheader.setSectionResizeMode(self._hheader.Fixed)
        self._vheader.setSectionResizeMode(self._vheader.Fixed)
        self.button_ok.setMinimumWidth(70)
        self.button_ok.setDefault(True)
        self.base_minimum_width = 300 if remove_only else 420
        if remove_only:
            self.setWindowTitle("Remove Packages")
        elif update_only:
            self.setWindowTitle("Update Packages")
        else:
            self.setWindowTitle("Install Packages")

        self.setMinimumWidth(self.base_minimum_width)

        # Layouts
        layout_progress = QHBoxLayout()
        layout_progress.addWidget(self.label_status)
        layout_progress.addWidget(SpacerHorizontal())
        layout_progress.addWidget(self.progress_bar)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addWidget(self.label_description)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.stack)
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_progress)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)
        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_ok.setDisabled(True)

        # Setup
        self.table.setDisabled(True)
        self.update_status('Solving package specifications',
                           value=0,
                           max_value=0)

    def setup(self, worker, output, error):
        """Setup the widget to include the list of dependencies."""
        if not isinstance(output, dict):
            output = {}

        packages = sorted(pkg.split('==')[0] for pkg in self.packages)
        success = output.get('success')
        error = output.get('error', '')
        exception_name = output.get('exception_name', '')
        actions = output.get('actions', [])
        prefix = worker.prefix

        if exception_name:
            message = exception_name
        else:
            # All requested packages already installed
            message = output.get('message', ' ')

        navi_deps_error = self.api.check_navigator_dependencies(
            actions, prefix)
        description = self.label_description.text()

        if error:
            description = 'No packages will be modified.'
            self.stack.setCurrentIndex(1)
            self.button_ok.setDisabled(True)
            if self.api.is_offline():
                error = ("Some of the functionality of Anaconda Navigator "
                         "will be limited in <b>offline mode</b>. <br><br>"
                         "Installation and upgrade actions will be subject to "
                         "the packages currently available on your package "
                         "cache.")
            self.text.setText(error)
        elif navi_deps_error:
            description = 'No packages will be modified.'
            error = ('Downgrading/removing these packages will modify '
                     'Anaconda Navigator dependencies.')
            self.text.setText(error)
            self.stack.setCurrentIndex(1)
            message = 'NavigatorDependenciesError'
            self.button_ok.setDisabled(True)
        elif success and actions:
            self.stack.setCurrentIndex(0)
            # Conda 4.3.x
            if isinstance(actions, list):
                actions_link = actions[0].get('LINK', [])
                actions_unlink = actions[0].get('UNLINK', [])
            # Conda 4.4.x
            else:
                actions_link = actions.get('LINK', [])
                actions_unlink = actions.get('UNLINK', [])

            deps = set()
            deps = deps.union({p['name'] for p in actions_link})
            deps = deps.union({p['name'] for p in actions_unlink})
            deps = deps - set(packages)
            deps = sorted(list(deps))

            count_total_packages = len(packages) + len(deps)
            plural_total = 's' if count_total_packages != 1 else ''
            plural_selected = 's' if len(packages) != 1 else ''

            self.table.setRowCount(count_total_packages)
            self.table.setColumnCount(4)
            if actions_link:
                description = '{0} package{1} will be installed'.format(
                    count_total_packages, plural_total)
                self.table.showColumn(2)
                self.table.showColumn(3)
            elif actions_unlink and not actions_link:
                self.table.hideColumn(2)
                self.table.hideColumn(3)
                self.table.setHorizontalHeaderLabels(
                    ['Name', 'Unlink', 'Link', 'Channel'])
                description = '{0} package{1} will be removed'.format(
                    count_total_packages, plural_total)

            for row, pkg in enumerate(packages + deps):
                link_item = [p for p in actions_link if p['name'] == pkg]
                if not link_item:
                    link_item = {
                        'version': '-'.center(len('link')),
                        'channel': '-'.center(len('channel')),
                    }
                else:
                    link_item = link_item[0]

                unlink_item = [p for p in actions_unlink if p['name'] == pkg]
                if not unlink_item:
                    unlink_item = {
                        'version': '-'.center(len('link')),
                    }
                else:
                    unlink_item = unlink_item[0]

                unlink_version = str(unlink_item['version'])
                link_version = str(link_item['version'])

                item_unlink_v = QTableWidgetItem(unlink_version)
                item_link_v = QTableWidgetItem(link_version)
                item_link_c = QTableWidgetItem(link_item['channel'])
                if pkg in packages:
                    item_name = QTableWidgetItem(pkg)
                else:
                    item_name = QTableWidgetItem('*' + pkg)

                items = [item_name, item_unlink_v, item_link_v, item_link_c]
                for column, item in enumerate(items):
                    item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
                    self.table.setItem(row, column, item)

            if deps:
                message = (
                    '<b>*</b> indicates the package is a dependency of a '
                    'selected package{0}<br>').format(plural_selected)

            self.button_ok.setEnabled(True)
            self.table.resizeColumnsToContents()
            unlink_width = self.table.columnWidth(1)
            if unlink_width < 60:
                self.table.setColumnWidth(1, 60)
            self.table.setHorizontalHeaderLabels(
                ['Name  ', 'Unlink  ', 'Link  ', 'Channel  '])

        self.table.setEnabled(True)
        self.update_status(message=message)
        self.label_description.setText(description)

        # Adjust size after data has populated the table
        self.table.resizeColumnsToContents()
        width = sum(
            self.table.columnWidth(i) for i in range(self.table.columnCount()))
        delta = (self.width() - self.table.width() +
                 self.table.verticalHeader().width() + 10)

        new_width = width + delta

        if new_width < self.base_minimum_width:
            new_width = self.base_minimum_width

        self.setMinimumWidth(new_width)
        self.setMaximumWidth(new_width)

        self.sig_setup_ready.emit()

    def update_status(self, message='', value=None, max_value=None):
        """Update status of packages dialog."""
        self.label_status.setText(message)

        if max_value is None and value is None:
            self.progress_bar.setVisible(False)
        else:
            self.progress_bar.setVisible(True)
            self.progress_bar.setMaximum(max_value)
            self.progress_bar.setValue(value)
Esempio n. 12
0
class OpticsAdjustSettings(SiriusDialog):
    """Auxiliar window to optics adjust settings."""

    updateSettings = Signal(str, str)

    def __init__(self, tuneconfig_currname, chromconfig_currname, parent=None):
        """Initialize object."""
        super().__init__(parent)
        self.setWindowTitle('Optics Adjust Settings')
        self.setObjectName('BOApp')
        self.tuneconfig_currname = tuneconfig_currname
        self.chromconfig_currname = chromconfig_currname
        self.conn_tuneparams = _ConfigDBClient(
            config_type='bo_tunecorr_params')
        self.conn_chromparams = _ConfigDBClient(
            config_type='bo_chromcorr_params')
        self._setupUi()

    def _setupUi(self):
        self.tune_settings = QWidget(self)
        self.tune_settings.setLayout(self._setupTuneSettings())
        self.le_tuneconfig.setText(self.tuneconfig_currname)
        self.chrom_settings = QWidget(self)
        self.chrom_settings.setLayout(self._setupChromSettings())
        self.le_chromconfig.setText(self.chromconfig_currname)
        self.bt_apply = QPushButton('Apply Settings', self)
        self.bt_apply.setStyleSheet("""min-width:8em; max-width:8em;""")
        self.bt_apply.clicked.connect(self._emitSettings)
        self.bt_apply.setAutoDefault(False)
        self.bt_apply.setDefault(False)
        hlay_apply = QHBoxLayout()
        hlay_apply.addItem(
            QSpacerItem(20, 60, QSzPlcy.Expanding, QSzPlcy.Ignored))
        hlay_apply.addWidget(self.bt_apply)

        tabs = QTabWidget(self)
        tabs.addTab(self.tune_settings, 'Tune')
        tabs.addTab(self.chrom_settings, 'Chromaticity')

        lay = QVBoxLayout()
        lay.addWidget(tabs)
        lay.addLayout(hlay_apply)
        self.setLayout(lay)

    def _setupTuneSettings(self):
        l_tuneconfig = QLabel('<h3>Tune Variation Config</h3>', self)
        l_tuneconfig.setAlignment(Qt.AlignCenter)
        self.le_tuneconfig = _ConfigLineEdit(parent=self,
                                             config_type='bo_tunecorr_params')
        self.le_tuneconfig.textChanged.connect(self._showTuneConfigData)

        label_tunemat = QLabel('<h4>Matrix</h4>', self)
        label_tunemat.setAlignment(Qt.AlignCenter)
        self.table_tunemat = QTableWidget(self)
        self.table_tunemat.setObjectName('tunemat')
        self.table_tunemat.setStyleSheet("""
            #tunemat{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 6em; max-height: 6em;}""")
        self.table_tunemat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_tunemat.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_tunemat.setRowCount(2)
        self.table_tunemat.setColumnCount(2)
        self.table_tunemat.setVerticalHeaderLabels(['  X', '  Y'])
        self.table_tunemat.setHorizontalHeaderLabels(['QF', 'QD'])
        self.table_tunemat.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_tunemat.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_tunemat.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_tunemat.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_tunemat.setSizePolicy(QSzPlcy.MinimumExpanding,
                                         QSzPlcy.Preferred)

        label_nomKL = QLabel('<h4>Nominal KL</h4>')
        label_nomKL.setAlignment(Qt.AlignCenter)
        self.table_nomKL = QTableWidget(self)
        self.table_nomKL.setObjectName('nomKL')
        self.table_nomKL.setStyleSheet("""
            #nomKL{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 4em; max-height: 4em;}""")
        self.table_nomKL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_nomKL.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_nomKL.setRowCount(1)
        self.table_nomKL.setColumnCount(2)
        self.table_nomKL.setVerticalHeaderLabels(['KL'])
        self.table_nomKL.setHorizontalHeaderLabels(['QF', 'QD'])
        self.table_nomKL.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_nomKL.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_nomKL.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomKL.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomKL.setSizePolicy(QSzPlcy.MinimumExpanding,
                                       QSzPlcy.Preferred)

        lay = QVBoxLayout()
        lay.addWidget(l_tuneconfig)
        lay.addWidget(self.le_tuneconfig)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))
        lay.addWidget(label_tunemat)
        lay.addWidget(self.table_tunemat)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))
        lay.addWidget(label_nomKL)
        lay.addWidget(self.table_nomKL)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))

        return lay

    def _setupChromSettings(self):
        l_chromconfig = QLabel('<h3>Chromaticity Variation Config</h3>', self)
        l_chromconfig.setAlignment(Qt.AlignCenter)
        self.le_chromconfig = _ConfigLineEdit(
            parent=self, config_type='bo_chromcorr_params')
        self.le_chromconfig.textChanged.connect(self._showChromConfigData)

        l_chrommat = QLabel('<h4>Matrix</h4>', self)
        l_chrommat.setAlignment(Qt.AlignCenter)
        self.table_chrommat = QTableWidget(self)
        self.table_chrommat.setObjectName('chrommat')
        self.table_chrommat.setStyleSheet("""
            #chrommat{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 6em; max-height: 6em;}""")
        self.table_chrommat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_chrommat.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_chrommat.setRowCount(2)
        self.table_chrommat.setColumnCount(2)
        self.table_chrommat.setVerticalHeaderLabels(['  X', '  Y'])
        self.table_chrommat.setHorizontalHeaderLabels(['SF', 'SD'])
        self.table_chrommat.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_chrommat.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_chrommat.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_chrommat.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_chrommat.setSizePolicy(QSzPlcy.MinimumExpanding,
                                          QSzPlcy.Preferred)

        l_nomSL = QLabel('<h4>Nominal SL</h4>')
        l_nomSL.setAlignment(Qt.AlignCenter)
        self.table_nomSL = QTableWidget(self)
        self.table_nomSL.setObjectName('nomSL')
        self.table_nomSL.setStyleSheet("""
            #nomSL{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 4em; max-height: 4em;}""")
        self.table_nomSL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_nomSL.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_nomSL.setRowCount(1)
        self.table_nomSL.setColumnCount(2)
        self.table_nomSL.setVerticalHeaderLabels(['SL'])
        self.table_nomSL.setHorizontalHeaderLabels(['SF', 'SD'])
        self.table_nomSL.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_nomSL.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_nomSL.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomSL.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomSL.setSizePolicy(QSzPlcy.MinimumExpanding,
                                       QSzPlcy.Preferred)

        l_nomchrom = QLabel('<h4>Nominal Chrom</h4>')
        l_nomchrom.setAlignment(Qt.AlignCenter)
        self.label_nomchrom = QLabel()
        self.label_nomchrom.setAlignment(Qt.AlignCenter)

        lay = QVBoxLayout()
        lay.addWidget(l_chromconfig)
        lay.addWidget(self.le_chromconfig)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_chrommat)
        lay.addWidget(self.table_chrommat)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_nomSL)
        lay.addWidget(self.table_nomSL)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_nomchrom)
        lay.addWidget(self.label_nomchrom)

        return lay

    def _showTuneConfigData(self):
        try:
            name = self.le_tuneconfig.text()
            config = self.conn_tuneparams.get_config_value(name=name)
            mat = config['matrix']
            nomKL = config['nominal KLs']
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)
        else:
            self.tuneconfig_currname = name
            self.table_tunemat.setItem(0, 0, QTableWidgetItem(str(mat[0][0])))
            self.table_tunemat.setItem(0, 1, QTableWidgetItem(str(mat[0][1])))
            self.table_tunemat.setItem(1, 0, QTableWidgetItem(str(mat[1][0])))
            self.table_tunemat.setItem(1, 1, QTableWidgetItem(str(mat[1][1])))
            self.table_tunemat.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(1, 0).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(1, 1).setFlags(Qt.ItemIsEnabled)
            self.table_nomKL.setItem(0, 0, QTableWidgetItem(str(nomKL[0])))
            self.table_nomKL.setItem(0, 1, QTableWidgetItem(str(nomKL[1])))
            self.table_nomKL.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_nomKL.item(0, 1).setFlags(Qt.ItemIsEnabled)

    def _showChromConfigData(self):
        try:
            name = self.le_chromconfig.text()
            config = self.conn_chromparams.get_config_value(name=name)
            mat = config['matrix']
            nomSL = config['nominal SLs']
            nomChrom = config['nominal chrom']
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)
        else:
            self.chromconfig_currname = name
            self.table_chrommat.setItem(0, 0, QTableWidgetItem(str(mat[0][0])))
            self.table_chrommat.setItem(0, 1, QTableWidgetItem(str(mat[0][1])))
            self.table_chrommat.setItem(1, 0, QTableWidgetItem(str(mat[1][0])))
            self.table_chrommat.setItem(1, 1, QTableWidgetItem(str(mat[1][1])))
            self.table_chrommat.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(1, 0).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(1, 1).setFlags(Qt.ItemIsEnabled)
            self.table_nomSL.setItem(0, 0, QTableWidgetItem(str(nomSL[0])))
            self.table_nomSL.setItem(0, 1, QTableWidgetItem(str(nomSL[1])))
            self.table_nomSL.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_nomSL.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.label_nomchrom.setText(str(nomChrom))

    def _emitSettings(self):
        tuneconfig_name = self.le_tuneconfig.text()
        chromconfig_name = self.le_chromconfig.text()
        self.updateSettings.emit(tuneconfig_name, chromconfig_name)
        self.close()
Esempio n. 13
0
class Extension2ReaderTable(QWidget):
    """Table showing extension to reader mappings with removal button.

    Widget presented in preferences-plugin dialog."""

    valueChanged = Signal(int)

    def __init__(self, parent=None, npe2_readers=None, npe1_readers=None):
        super().__init__(parent=parent)

        npe2, npe1 = get_all_readers()
        if npe2_readers is None:
            npe2_readers = npe2
        if npe1_readers is None:
            npe1_readers = npe1

        self._npe2_readers = npe2_readers
        self._npe1_readers = npe1_readers

        self._table = QTableWidget()
        self._table.setShowGrid(False)
        self._set_up_table()
        self._edit_row = self._make_new_preference_row()
        self._populate_table()

        instructions = QLabel(
            trans.
            _('Enter a filename pattern to associate with a reader e.g. "*.tif" for all TIFF files.'
              ) + trans.
            _('Available readers will be filtered to those compatible with your pattern. Hover over a reader to see what patterns it accepts.'
              ) + trans.
            _('\n\nPreference saving for folder readers is not supported, so these readers are not shown.'
              ) + trans.
            _('\n\nFor documentation on valid filename patterns, see https://docs.python.org/3/library/fnmatch.html'
              ))
        instructions.setWordWrap(True)
        instructions.setOpenExternalLinks(True)

        layout = QVBoxLayout()
        instructions.setSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.Expanding)
        layout.addWidget(instructions)
        layout.addWidget(self._edit_row)
        layout.addWidget(self._table)
        self.setLayout(layout)

    def _set_up_table(self):
        """Add table columns and headers, define styling"""
        self._fn_pattern_col = 0
        self._reader_col = 1

        header_strs = [trans._('Filename Pattern'), trans._('Reader Plugin')]

        self._table.setColumnCount(2)
        self._table.setColumnWidth(self._fn_pattern_col, 200)
        self._table.setColumnWidth(self._reader_col, 200)
        self._table.verticalHeader().setVisible(False)
        self._table.setMinimumHeight(120)
        self._table.horizontalHeader().setStyleSheet(
            'border-bottom: 2px solid white;')
        self._table.setHorizontalHeaderLabels(header_strs)
        self._table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def _populate_table(self):
        """Add row for each extension to reader mapping in settings"""

        fnpattern2reader = get_settings().plugins.extension2reader
        if len(fnpattern2reader) > 0:
            for fn_pattern, plugin_name in fnpattern2reader.items():
                self._add_new_row(fn_pattern, plugin_name)
        else:
            # Display that there are no filename patterns with reader associations
            self._display_no_preferences_found()

    def _make_new_preference_row(self):
        """Make row for user to add a new filename pattern assignment"""
        edit_row_widget = QWidget()
        edit_row_widget.setLayout(QGridLayout())
        edit_row_widget.layout().setContentsMargins(0, 0, 0, 0)

        self._fn_pattern_edit = QLineEdit()
        self._fn_pattern_edit.setPlaceholderText(
            "Start typing filename pattern...")
        self._fn_pattern_edit.textChanged.connect(
            self._filter_compatible_readers)

        add_reader_widg = QWidget()
        add_reader_widg.setLayout(QHBoxLayout())
        add_reader_widg.layout().setContentsMargins(0, 0, 0, 0)

        self._new_reader_dropdown = QComboBox()
        for i, (plugin_name, display_name) in enumerate(
                sorted(dict(self._npe2_readers,
                            **self._npe1_readers).items())):
            self._add_reader_choice(i, plugin_name, display_name)

        add_btn = QPushButton('Add')
        add_btn.setToolTip(trans._('Save reader preference for pattern'))
        add_btn.clicked.connect(self._save_new_preference)

        add_reader_widg.layout().addWidget(self._new_reader_dropdown)
        add_reader_widg.layout().addWidget(add_btn)

        edit_row_widget.layout().addWidget(
            self._fn_pattern_edit,
            0,
            0,
        )
        edit_row_widget.layout().addWidget(add_reader_widg, 0, 1)

        return edit_row_widget

    def _display_no_preferences_found(self):
        self._table.setRowCount(1)
        item = QTableWidgetItem(trans._('No filename preferences found.'))
        item.setFlags(Qt.NoItemFlags)
        self._table.setItem(self._fn_pattern_col, 0, item)

    def _add_reader_choice(self, i, plugin_name, display_name):
        """Add dropdown item for plugin_name with reader pattern tooltip"""
        reader_patterns = get_filename_patterns_for_reader(plugin_name)
        # TODO: no reader_patterns means directory reader,
        # we don't support preference association yet
        if not reader_patterns:
            return

        self._new_reader_dropdown.addItem(display_name, plugin_name)
        if '*' in reader_patterns:
            tooltip_text = 'Accepts all'
        else:
            reader_patterns_formatted = ', '.join(sorted(
                list(reader_patterns)))
            tooltip_text = f'Accepts: {reader_patterns_formatted}'
        self._new_reader_dropdown.setItemData(i,
                                              tooltip_text,
                                              role=Qt.ToolTipRole)

    def _filter_compatible_readers(self, new_pattern):
        """Filter reader dropwdown items to those that accept `new_extension`"""
        self._new_reader_dropdown.clear()

        readers = self._npe2_readers.copy()
        to_delete = []

        compatible_readers = get_potential_readers(new_pattern)
        for plugin_name, display_name in readers.items():
            if plugin_name not in compatible_readers:
                to_delete.append(plugin_name)

        for reader in to_delete:
            del readers[reader]
        readers.update(self._npe1_readers)

        if not readers:
            self._new_reader_dropdown.addItem("None available")
        else:
            for i, (plugin_name,
                    display_name) in enumerate(sorted(readers.items())):
                self._add_reader_choice(i, plugin_name, display_name)

    def _save_new_preference(self, event):
        """Save current preference to settings and show in table"""
        fn_pattern = self._fn_pattern_edit.text()
        reader = self._new_reader_dropdown.currentData()

        if not fn_pattern or not reader:
            return

        # if user types pattern that starts with a . it's probably a file extension so prepend the *
        if fn_pattern.startswith('.'):
            fn_pattern = f'*{fn_pattern}'

        if fn_pattern in get_settings().plugins.extension2reader:
            self._edit_existing_preference(fn_pattern, reader)
        else:
            self._add_new_row(fn_pattern, reader)
        get_settings().plugins.extension2reader = {
            **get_settings().plugins.extension2reader,
            fn_pattern: reader,
        }

    def _edit_existing_preference(self, fn_pattern, reader):
        """Edit existing extension preference"""
        current_reader_label = self.findChild(QLabel, fn_pattern)
        if reader in self._npe2_readers:
            reader = self._npe2_readers[reader]
        current_reader_label.setText(reader)

    def _add_new_row(self, fn_pattern, reader):
        """Add new reader preference to table"""
        last_row = self._table.rowCount()

        if (last_row == 1 and 'No filename preferences found'
                in self._table.item(0, 0).text()):
            self._table.removeRow(0)
            last_row = 0

        self._table.insertRow(last_row)
        item = QTableWidgetItem(fn_pattern)
        item.setFlags(Qt.NoItemFlags)
        self._table.setItem(last_row, self._fn_pattern_col, item)

        plugin_widg = QWidget()
        # need object name to easily find row
        plugin_widg.setObjectName(f'{fn_pattern}')
        plugin_widg.setLayout(QHBoxLayout())
        plugin_widg.layout().setContentsMargins(0, 0, 0, 0)

        if reader in self._npe2_readers:
            reader = self._npe2_readers[reader]
        plugin_label = QLabel(reader, objectName=fn_pattern)
        # need object name to easily work out which button was clicked
        remove_btn = QPushButton('X', objectName=fn_pattern)
        remove_btn.setFixedWidth(30)
        remove_btn.setStyleSheet('margin: 4px;')
        remove_btn.setToolTip(
            trans._('Remove this filename pattern to reader association'))
        remove_btn.clicked.connect(self.remove_existing_preference)

        plugin_widg.layout().addWidget(plugin_label)
        plugin_widg.layout().addWidget(remove_btn)
        self._table.setCellWidget(last_row, self._reader_col, plugin_widg)

    def remove_existing_preference(self, event):
        """Delete extension to reader mapping setting and remove table row"""
        pattern_to_remove = self.sender().objectName()
        current_settings = get_settings().plugins.extension2reader
        # need explicit assignment to new object here for persistence
        get_settings().plugins.extension2reader = {
            k: v
            for k, v in current_settings.items() if k != pattern_to_remove
        }

        for i in range(self._table.rowCount()):
            row_widg_name = self._table.cellWidget(
                i, self._reader_col).objectName()
            if row_widg_name == pattern_to_remove:
                self._table.removeRow(i)
                break

        if self._table.rowCount() == 0:
            self._display_no_preferences_found()

    def value(self):
        """Return extension:reader mapping from settings.

        Returns
        -------
        Dict[str, str]
            mapping of extension to reader plugin display name
        """
        return get_settings().plugins.extension2reader
Esempio n. 14
0
class WndComputeRoiMaps(SecondaryWindow):

    # Signal that is sent (to main window) to update global state of the program
    update_global_state = Signal()
    computations_complete = Signal(object)

    signal_roi_computation_complete = Signal()
    signal_activate_tab_xrf_maps = Signal()

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        # Reference to the main window. The main window will hold
        #   references to all non-modal windows that could be opened
        #   from multiple places in the program.
        self.ref_main_window = self.gui_vars["ref_main_window"]

        self.update_global_state.connect(
            self.ref_main_window.update_widget_state)

        self.initialize()

    def initialize(self):
        self.setWindowTitle("PyXRF: Compute XRF Maps Based on ROIs")

        self.setMinimumWidth(600)
        self.setMinimumHeight(300)
        self.resize(600, 600)

        header_vbox = self._setup_header()
        self._setup_table()
        footer_hbox = self._setup_footer()

        vbox = QVBoxLayout()
        vbox.addLayout(header_vbox)
        vbox.addWidget(self.table)
        vbox.addLayout(footer_hbox)

        self.setLayout(vbox)

        self._set_tooltips()

    def _setup_header(self):
        self.pb_clear = QPushButton("Clear")
        self.pb_clear.clicked.connect(self.pb_clear_clicked)
        self.pb_use_lines_for_fitting = QPushButton(
            "Use Lines Selected For Fitting")
        self.pb_use_lines_for_fitting.clicked.connect(
            self.pb_use_lines_for_fitting_clicked)

        self.le_sel_emission_lines = LineEditExtended()
        self.le_sel_emission_lines.textChanged.connect(
            self.le_sel_emission_lines_text_changed)
        self.le_sel_emission_lines.editingFinished.connect(
            self.le_sel_emission_lines_editing_finished)

        sample_elements = ""
        self.le_sel_emission_lines.setText(sample_elements)

        vbox = QVBoxLayout()
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Enter emission lines, e.g. Fe_K, Gd_L  "))
        hbox.addStretch(1)
        hbox.addWidget(self.pb_clear)
        hbox.addWidget(self.pb_use_lines_for_fitting)
        vbox.addLayout(hbox)
        vbox.addWidget(self.le_sel_emission_lines)

        return vbox

    def _setup_table(self):

        # Labels for horizontal header
        self.tbl_labels = ["Line", "E, keV", "ROI, keV", "Show", "Reset"]

        # The list of columns that stretch with the table
        self.tbl_cols_stretch = ("E, keV", "ROI, keV")

        # Table item representation if different from default
        self.tbl_format = {"E, keV": ".3f"}

        # Editable items (highlighted with lighter background)
        self.tbl_cols_editable = {"ROI, keV"}

        # Columns that contain Range Manager
        self.tbl_cols_range_manager = ("ROI, keV", )

        self.table = QTableWidget()
        self.table.setColumnCount(len(self.tbl_labels))
        self.table.setHorizontalHeaderLabels(self.tbl_labels)
        self.table.verticalHeader().hide()
        self.table.setSelectionMode(QTableWidget.NoSelection)

        self.table.setStyleSheet("QTableWidget::item{color: black;}")

        header = self.table.horizontalHeader()
        for n, lbl in enumerate(self.tbl_labels):
            # Set stretching for the columns
            if lbl in self.tbl_cols_stretch:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            else:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)

        self._table_contents = []
        self.cb_list = []
        self.range_manager_list = []
        self.pb_default_list = []
        self.fill_table(self._table_contents)

    def fill_table(self, table_contents):

        self.table.clearContents()
        self._table_contents = table_contents  # Save new table contents

        for item in self.range_manager_list:
            item.selection_changed.disconnect(
                self.range_manager_selection_changed)
        self.range_manager_list = []

        for cb in self.cb_list:
            cb.stateChanged.disconnect(self.cb_state_changed)
        self.cb_list = []

        for pb in self.pb_default_list:
            pb.clicked.connect(self.pb_default_clicked)
        self.pb_default_list = []

        self.table.setRowCount(len(table_contents))
        for nr, row in enumerate(table_contents):
            eline_name = row["eline"] + "a1"
            energy = row["energy_center"]
            energy_left = row["energy_left"]
            energy_right = row["energy_right"]
            range_displayed = row["range_displayed"]
            table_row = [eline_name, energy, (energy_left, energy_right)]
            for nc, entry in enumerate(table_row):

                label = self.tbl_labels[nc]

                # Set alternating background colors for the table rows
                #   Make background for editable items a little brighter
                brightness = 240 if label in self.tbl_cols_editable else 220
                if nr % 2:
                    rgb_bckg = (255, brightness, brightness)
                else:
                    rgb_bckg = (brightness, 255, brightness)

                if self.tbl_labels[nc] not in self.tbl_cols_range_manager:
                    if self.tbl_labels[nc] in self.tbl_format:
                        fmt = self.tbl_format[self.tbl_labels[nc]]
                        s = ("{:" + fmt + "}").format(entry)
                    else:
                        s = f"{entry}"

                    item = QTableWidgetItem(s)
                    if nc > 0:
                        item.setTextAlignment(Qt.AlignCenter)
                    else:
                        item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)

                    # Set all columns not editable (unless needed)
                    item.setFlags(item.flags() & ~Qt.ItemIsEditable)

                    # Note, that there is no way to set style sheet for QTableWidgetItem
                    item.setBackground(QBrush(QColor(*rgb_bckg)))

                    self.table.setItem(nr, nc, item)
                else:
                    spin_name = f"{nr}"
                    item = RangeManager(name=spin_name,
                                        add_sliders=False,
                                        selection_to_range_min=0.0001)
                    item.set_range(
                        0.0,
                        100.0)  # The range is greater than needed (in keV)
                    item.set_selection(value_low=entry[0], value_high=entry[1])
                    item.setTextColor((0, 0, 0))  # In case of dark theme
                    item.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

                    self.range_manager_list.append(item)

                    item.selection_changed.connect(
                        self.range_manager_selection_changed)

                    color = (rgb_bckg[0], rgb_bckg[1], rgb_bckg[2])
                    item.setBackground(color)

                    self.table.setCellWidget(nr, nc, item)

            brightness = 220
            if nr % 2:
                rgb_bckg = (255, brightness, brightness)
            else:
                rgb_bckg = (brightness, 255, brightness)

            item = QWidget()
            cb = CheckBoxNamed(name=f"{nr}")
            cb.setChecked(Qt.Checked if range_displayed else Qt.Unchecked)
            self.cb_list.append(cb)
            cb.stateChanged.connect(self.cb_state_changed)

            item_hbox = QHBoxLayout(item)
            item_hbox.addWidget(cb)
            item_hbox.setAlignment(Qt.AlignCenter)
            item_hbox.setContentsMargins(0, 0, 0, 0)
            color_css = f"rgb({rgb_bckg[0]}, {rgb_bckg[1]}, {rgb_bckg[2]})"
            item.setStyleSheet(
                f"QWidget {{ background-color: {color_css}; }} "
                f"QCheckBox {{ color: black; background-color: white }}")
            self.table.setCellWidget(nr, nc + 1, item)

            item = PushButtonNamed("Reset", name=f"{nr}")
            item.clicked.connect(self.pb_default_clicked)
            self.pb_default_list.append(item)
            rgb_bckg = [_ - 35 if (_ < 255) else _ for _ in rgb_bckg]
            color_css = f"rgb({rgb_bckg[0]}, {rgb_bckg[1]}, {rgb_bckg[2]})"
            item.setStyleSheet(
                f"QPushButton {{ color: black; background-color: {color_css}; }}"
            )
            self.table.setCellWidget(nr, nc + 2, item)

    def _setup_footer(self):

        self.cb_subtract_baseline = QCheckBox("Subtract baseline")
        self.cb_subtract_baseline.setChecked(
            Qt.Checked if self.gpc.get_roi_subtract_background(
            ) else Qt.Unchecked)
        self.cb_subtract_baseline.toggled.connect(
            self.cb_subtract_baseline_toggled)

        self.pb_compute_roi = QPushButton("Compute ROIs")
        self.pb_compute_roi.clicked.connect(self.pb_compute_roi_clicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.cb_subtract_baseline)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_compute_roi)

        return hbox

    def _set_tooltips(self):
        set_tooltip(self.pb_clear, "<b>Clear</b> the list")
        set_tooltip(
            self.pb_use_lines_for_fitting,
            "Copy the contents of <b>the list of emission lines selected for fitting</b> to the list of ROIs",
        )
        set_tooltip(
            self.le_sel_emission_lines,
            "The list of <b>emission lines</b> selected for ROI computation.")

        set_tooltip(self.table, "The list of ROIs")

        set_tooltip(
            self.cb_subtract_baseline,
            "<b>Subtract baseline</b> from the pixel spectra before computing ROIs. "
            "Subtracting baseline slows down computations and usually have no benefit. "
            "In most cases it should remain <b>unchecked</b>.",
        )
        set_tooltip(
            self.pb_compute_roi,
            "<b>Run</b> computations of the ROIs. The resulting <b>ROI</b> dataset "
            "may be viewed in <b>XRF Maps</b> tab.",
        )

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        # Hide the window if required by the program state
        state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"]
        state_model_exist = self.gui_vars["gui_state"]["state_model_exists"]
        if not state_file_loaded or not state_model_exist:
            self.hide()

        if condition == "tooltips":
            self._set_tooltips()

    def pb_clear_clicked(self):
        self.gpc.clear_roi_element_list()
        self._update_displayed_element_list()
        self._validate_element_list()

    def pb_use_lines_for_fitting_clicked(self):
        self.gpc.load_roi_element_list_from_selected()
        self._update_displayed_element_list()
        self._validate_element_list()

    def le_sel_emission_lines_text_changed(self, text):
        self._validate_element_list(text)

    def le_sel_emission_lines_editing_finished(self):
        text = self.le_sel_emission_lines.text()
        if self._validate_element_list(text):
            self.gpc.set_roi_selected_element_list(text)
            self._update_table()
        else:
            element_list = self.gpc.get_roi_selected_element_list()
            self.le_sel_emission_lines.setText(element_list)

    def cb_subtract_baseline_toggled(self, state):
        self.gpc.set_roi_subtract_background(bool(state))

    def cb_state_changed(self, name, state):
        try:
            nr = int(name)  # Row number
            checked = state == Qt.Checked

            eline = self._table_contents[nr]["eline"]
            self._table_contents[nr]["range_displayed"] = checked
            self.gpc.show_roi(eline, checked)
        except Exception as ex:
            logger.error(
                f"Failed to process selection change. Exception occurred: {ex}."
            )

    def _find_spin_box(self, name):
        for item in self.spin_list:
            if item.getName() == name:
                return item
        return None

    def spin_value_changed(self, name, value):
        try:
            nr, side = name.split(",")
            nr = int(nr)
            keys = {"left": "energy_left", "right": "energy_right"}
            side = keys[side]
            eline = self._table_contents[nr]["eline"]
            if self._table_contents[nr][side] == value:
                return
            if side == "energy_left":  # Left boundary
                if value < self._table_contents[nr]["energy_right"]:
                    self._table_contents[nr][side] = value
            else:  # Right boundary
                if value > self._table_contents[nr]["energy_left"]:
                    self._table_contents[nr][side] = value

            # Update plot
            left, right = self._table_contents[nr][
                "energy_left"], self._table_contents[nr]["energy_right"]
            self.gpc.change_roi(eline, left, right)
        except Exception as ex:
            logger.error(
                f"Failed to change the ROI. Exception occurred: {ex}.")

    def range_manager_selection_changed(self, left, right, name):
        try:
            nr = int(name)
            eline = self._table_contents[nr]["eline"]
            self.gpc.change_roi(eline, left, right)
        except Exception as ex:
            logger.error(
                f"Failed to change the ROI. Exception occurred: {ex}.")

    def pb_default_clicked(self, name):
        try:
            nr = int(name)
            eline = self._table_contents[nr]["eline"]
            left = self._table_contents[nr]["energy_left_default"]
            right = self._table_contents[nr]["energy_right_default"]
            self.range_manager_list[nr].set_selection(value_low=left,
                                                      value_high=right)
            self.gpc.change_roi(eline, left, right)
        except Exception as ex:
            logger.error(
                f"Failed to change the ROI. Exception occurred: {ex}.")

    def pb_compute_roi_clicked(self):
        def cb():
            try:
                self.gpc.compute_rois()
                success, msg = True, ""
            except Exception as ex:
                success, msg = False, str(ex)

            return {"success": success, "msg": msg}

        self._compute_in_background(cb, self.slot_compute_roi_clicked)

    @Slot(object)
    def slot_compute_roi_clicked(self, result):
        self._recover_after_compute(self.slot_compute_roi_clicked)

        success = result["success"]
        if success:
            self.gui_vars["gui_state"]["state_xrf_map_exists"] = True
        else:
            msg = result["msg"]
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Failed to Compute ROIs",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()

        self.signal_roi_computation_complete.emit()
        self.update_global_state.emit()
        if success:
            self.signal_activate_tab_xrf_maps.emit()

    def _update_displayed_element_list(self):
        element_list = self.gpc.get_roi_selected_element_list()
        self.le_sel_emission_lines.setText(element_list)
        self._validate_element_list()
        self._update_table()

    def _update_table(self):
        table_contents = self.gpc.get_roi_settings()
        self.fill_table(table_contents)

    def _validate_element_list(self, text=None):
        if text is None:
            text = self.le_sel_emission_lines.text()
        el_list = text.split(",")
        el_list = [_.strip() for _ in el_list]
        if el_list == [""]:
            el_list = []
        valid = bool(len(el_list))
        for eline in el_list:
            if self.gpc.get_eline_name_category(eline) != "eline":
                valid = False

        self.le_sel_emission_lines.setValid(valid)
        self.pb_compute_roi.setEnabled(valid)

        return valid

    def _compute_in_background(self, func, slot, *args, **kwargs):
        """
        Run function `func` in a background thread. Send the signal
        `self.computations_complete` once computation is finished.

        Parameters
        ----------
        func: function
            Reference to a function that is supposed to be executed at the background.
            The function return value is passed as a signal parameter once computation is
            complete.
        slot: qtpy.QtCore.Slot or None
            Reference to a slot. If not None, then the signal `self.computation_complete`
            is connected to this slot.
        args, kwargs
            arguments of the function `func`.
        """
        signal_complete = self.computations_complete

        def func_to_run(func, *args, **kwargs):
            class LoadFile(QRunnable):
                def run(self):
                    result_dict = func(*args, **kwargs)
                    signal_complete.emit(result_dict)

            return LoadFile()

        if slot is not None:
            self.computations_complete.connect(slot)
        self.gui_vars["gui_state"]["running_computations"] = True
        self.update_global_state.emit()
        QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs))

    def _recover_after_compute(self, slot):
        """
        The function should be called after the signal `self.computations_complete` is
        received. The slot should be the same as the one used when calling
        `self.compute_in_background`.
        """
        if slot is not None:
            self.computations_complete.disconnect(slot)
        self.gui_vars["gui_state"]["running_computations"] = False
        self.update_global_state.emit()
Esempio n. 15
0
class PMGTableShow(BaseExtendedWidget):
    default_bg = QTableWidgetItem().background()
    default_fg = QTableWidgetItem().foreground()

    def __init__(self,
                 layout_dir: str,
                 title: List[str],
                 initial_value: List[List[Union[int, float, str]]],
                 size_restricted=False,
                 header_adaption_h=False,
                 header_adaption_v=False,
                 background_color: List[List[Union[str]]] = None,
                 foreground_color: List[List[Union[str]]] = None):
        super().__init__(layout_dir=layout_dir)

        self.maximum_rows = 100
        self.size_restricted = size_restricted
        self.header_adaption_h = header_adaption_h
        self.header_adaption_v = header_adaption_v
        self.background_color = background_color if background_color is not None else ''
        self.foreground_color = foreground_color if foreground_color is not None else ''
        self.char_width = 15
        self.on_check_callback = None
        self.title_list = title
        entryLayout = QHBoxLayout()
        entryLayout.setContentsMargins(0, 0, 0, 0)

        self.ctrl = QTableWidget()
        self.ctrl.verticalHeader().setVisible(False)
        self.set_params(size_restricted, header_adaption_h, header_adaption_v)
        self.ctrl.setColumnCount(len(title))

        self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        for i, text in enumerate(title):
            self.ctrl.setColumnWidth(i, len(text) * self.char_width + 10)
            self.ctrl.setHorizontalHeaderItem(i, QTableWidgetItem(text))

        self.central_layout.addLayout(entryLayout)
        entryLayout.addWidget(self.ctrl)

        if initial_value is not None:
            for sublist in initial_value:
                assert len(sublist) == len(title), \
                    'title is not as long as sublist,%s,%s' % (repr(title), sublist)
                self.ctrl.setRowCount(len(initial_value))
                self.set_value(initial_value)

    def set_params(self,
                   size_restricted=False,
                   header_adaption_h=False,
                   header_adaption_v=False):
        self.size_restricted = size_restricted
        self.header_adaption_h = header_adaption_h
        self.header_adaption_v = header_adaption_v
        if header_adaption_h:
            self.ctrl.horizontalHeader().setSectionResizeMode(
                QHeaderView.Stretch)
        if header_adaption_v:
            self.ctrl.verticalHeader().setSectionResizeMode(
                QHeaderView.Stretch)

    def check_data(self, value: List[List[Union[int, float, str]]]):
        for sublist in value:
            assert len(sublist) == len(self.title_list),\
                '%s,%s' % (repr(sublist), repr(self.title_list))

    def set_value(self, value: List[List[Union[int, float, str]]]):
        self.check_data(value)
        self.ctrl.setRowCount(len(value))
        cols = len(value[0])
        if isinstance(self.foreground_color, str):
            fg = [[self.foreground_color for i in range(cols)]
                  for j in range(len(value))]

        else:
            fg = self.foreground_color
        if isinstance(self.background_color, str):
            bg = [[self.background_color for i in range(cols)]
                  for j in range(len(value))]
        else:
            bg = self.background_color
        for row, row_list in enumerate(value):
            for col, content in enumerate(row_list):
                if len(str(content)) * self.char_width > self.ctrl.columnWidth(
                        col):
                    self.ctrl.setColumnWidth(
                        col,
                        len(str(content)) * self.char_width + 10)
                table_item = QTableWidgetItem(str(content))
                table_item.setTextAlignment(Qt.AlignCenter)
                # 字体颜色(红色)
                if fg[row][col] == '':
                    table_item.setForeground(self.default_fg)
                else:
                    table_item.setForeground(
                        QBrush(QColor(*color_str2tup(fg[row][col]))))

                # 背景颜色(红色)
                if bg[row][col] == '':
                    table_item.setBackground(self.default_bg)
                else:
                    table_item.setBackground(
                        QBrush(QColor(*color_str2tup(bg[row][col]))))
                self.ctrl.setItem(row, col, table_item)

        if self.size_restricted:
            if self.header_adaption_h:
                self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
                scrollbar_area_width = 0
            else:
                self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
                scrollbar_area_width = 10
            self.ctrl.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 +
                                       scrollbar_area_width)
            self.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 +
                                  scrollbar_area_width)

    def alert(self, alert_level: int):
        self.ctrl.alert(alert_level)

    def add_row(self, row: List):
        assert len(row) == self.ctrl.columnCount()
        rc = self.ctrl.rowCount()
        self.ctrl.setRowCount(rc + 1)
        for i, val in enumerate(row):
            self.ctrl.setItem(rc, i, QTableWidgetItem(str(val)))
        if self.ctrl.rowCount() > self.maximum_rows:
            self.ctrl.removeRow(0)
Esempio n. 16
0
class MeasurementWidget(QWidget):
    """
    :type settings: Settings
    :type segment: Segment
    """
    def __init__(self, settings: PartSettings, segment=None):
        super().__init__()
        self.settings = settings
        self.segment = segment
        self.measurements_storage = MeasurementsStorage()
        self.recalculate_button = QPushButton(
            "Recalculate and\n replace measurement", self)
        self.recalculate_button.clicked.connect(
            self.replace_measurement_result)
        self.recalculate_append_button = QPushButton(
            "Recalculate and\n append measurement", self)
        self.recalculate_append_button.clicked.connect(
            self.append_measurement_result)
        self.copy_button = QPushButton("Copy to clipboard", self)
        self.copy_button.setToolTip(
            "You cacn copy also with 'Ctrl+C'. To get raw copy copy with 'Ctrl+Shit+C'"
        )
        self.horizontal_measurement_present = QCheckBox(
            "Horizontal view", self)
        self.no_header = QCheckBox("No header", self)
        self.no_units = QCheckBox("No units", self)
        self.no_units.setChecked(True)
        self.no_units.clicked.connect(self.refresh_view)
        self.expand_mode = QCheckBox("Expand", self)
        self.expand_mode.setToolTip(
            "Shows results for each component in separate entry")
        self.file_names = EnumComboBox(FileNamesEnum)
        self.file_names_label = QLabel("Add file name:")
        self.file_names.currentIndexChanged.connect(self.refresh_view)
        self.horizontal_measurement_present.stateChanged.connect(
            self.refresh_view)
        self.expand_mode.stateChanged.connect(self.refresh_view)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.measurement_type = SearchCombBox(self)
        # noinspection PyUnresolvedReferences
        self.measurement_type.currentIndexChanged.connect(
            self.measurement_profile_selection_changed)
        self.measurement_type.addItem("<none>")
        self.measurement_type.addItems(
            list(sorted(self.settings.measurement_profiles.keys())))
        self.measurement_type.setToolTip(
            'You can create new measurement profile in advanced window, in tab "Measurement settings"'
        )
        self.channels_chose = ChannelComboBox()
        self.units_choose = EnumComboBox(Units)
        self.units_choose.set_value(self.settings.get("units_value", Units.nm))
        self.info_field = QTableWidget(self)
        self.info_field.setColumnCount(3)
        self.info_field.setHorizontalHeaderLabels(["Name", "Value", "Units"])
        self.measurement_add_shift = 0
        layout = QVBoxLayout()
        # layout.addWidget(self.recalculate_button)
        v_butt_layout = QVBoxLayout()
        v_butt_layout.setSpacing(1)
        self.up_butt_layout = QHBoxLayout()
        self.up_butt_layout.addWidget(self.recalculate_button)
        self.up_butt_layout.addWidget(self.recalculate_append_button)
        self.butt_layout = QHBoxLayout()
        # self.butt_layout.setMargin(0)
        # self.butt_layout.setSpacing(10)
        self.butt_layout.addWidget(self.horizontal_measurement_present, 1)
        self.butt_layout.addWidget(self.no_header, 1)
        self.butt_layout.addWidget(self.no_units, 1)
        self.butt_layout.addWidget(self.expand_mode, 1)
        self.butt_layout.addWidget(self.file_names_label)
        self.butt_layout.addWidget(self.file_names, 1)
        self.butt_layout.addWidget(self.copy_button, 2)
        self.butt_layout2 = QHBoxLayout()
        self.butt_layout3 = QHBoxLayout()
        self.butt_layout3.addWidget(QLabel("Channel:"))
        self.butt_layout3.addWidget(self.channels_chose)
        self.butt_layout3.addWidget(QLabel("Units:"))
        self.butt_layout3.addWidget(self.units_choose)
        # self.butt_layout3.addWidget(QLabel("Noise removal:"))
        # self.butt_layout3.addWidget(self.noise_removal_method)
        self.butt_layout3.addWidget(QLabel("Measurement set:"))
        self.butt_layout3.addWidget(self.measurement_type, 2)
        v_butt_layout.addLayout(self.up_butt_layout)
        v_butt_layout.addLayout(self.butt_layout)
        v_butt_layout.addLayout(self.butt_layout2)
        v_butt_layout.addLayout(self.butt_layout3)
        layout.addLayout(v_butt_layout)
        # layout.addLayout(self.butt_layout)
        layout.addWidget(self.info_field)
        self.setLayout(layout)
        # noinspection PyArgumentList
        self.clip = QApplication.clipboard()
        self.settings.image_changed[int].connect(self.image_changed)
        self.previous_profile = None

    def check_if_measurement_can_be_calculated(self, name):
        if name == "<none>":
            return "<none>"
        profile: MeasurementProfile = self.settings.measurement_profiles.get(
            name)
        if profile.is_any_mask_measurement() and self.settings.mask is None:
            QMessageBox.information(
                self, "Need mask",
                "To use this measurement set please use data with mask loaded",
                QMessageBox.Ok)
            self.measurement_type.setCurrentIndex(0)
            return "<none>"
        if self.settings.roi is None:
            QMessageBox.information(
                self,
                "Need segmentation",
                'Before calculating please create segmentation ("Execute" button)',
                QMessageBox.Ok,
            )
            self.measurement_type.setCurrentIndex(0)
            return "<none>"
        return name

    def image_changed(self, channels_num):
        self.channels_chose.change_channels_num(channels_num)

    def measurement_profile_selection_changed(self, index):
        text = self.measurement_type.itemText(index)
        text = self.check_if_measurement_can_be_calculated(text)
        try:
            stat = self.settings.measurement_profiles[text]
            is_mask = stat.is_any_mask_measurement()
            disable = is_mask and (self.settings.mask is None)
        except KeyError:
            disable = True
        self.recalculate_button.setDisabled(disable)
        self.recalculate_append_button.setDisabled(disable)
        if disable:
            self.recalculate_button.setToolTip(
                "Measurement profile contains mask measurements when mask is not loaded"
            )
            self.recalculate_append_button.setToolTip(
                "Measurement profile contains mask measurements when mask is not loaded"
            )
        else:
            self.recalculate_button.setToolTip("")
            self.recalculate_append_button.setToolTip("")

    def copy_to_clipboard(self):
        s = ""
        for r in range(self.info_field.rowCount()):
            for c in range(self.info_field.columnCount()):
                try:
                    s += str(self.info_field.item(r, c).text()) + "\t"
                except AttributeError:
                    s += "\t"
            s = s[:-1] + "\n"  # eliminate last '\t'
        self.clip.setText(s)

    def replace_measurement_result(self):
        self.measurements_storage.clear()
        self.previous_profile = ""
        self.append_measurement_result()

    def refresh_view(self):
        self.measurements_storage.set_expand(self.expand_mode.isChecked())
        self.measurements_storage.set_show_units(not self.no_units.isChecked())
        self.info_field.clear()
        save_orientation = self.horizontal_measurement_present.isChecked()
        columns, rows = self.measurements_storage.get_size(save_orientation)
        self.info_field.setColumnCount(columns)
        self.info_field.setRowCount(rows)
        self.info_field.setHorizontalHeaderLabels(
            self.measurements_storage.get_header(save_orientation))
        self.info_field.setVerticalHeaderLabels(
            self.measurements_storage.get_rows(save_orientation))
        for x in range(rows):
            for y in range(columns):
                self.info_field.setItem(
                    x, y,
                    QTableWidgetItem(
                        self.measurements_storage.get_val_as_str(
                            x, y, save_orientation)))
        if self.file_names.get_value() == FileNamesEnum.No:
            if save_orientation:
                self.info_field.removeColumn(0)
            else:
                self.info_field.removeRow(0)
        elif self.file_names.get_value() == FileNamesEnum.Short:
            if save_orientation:
                columns = 1
            else:
                rows = 1
            for x in range(rows):
                for y in range(columns):
                    item = self.info_field.item(x, y)
                    item.setText(os.path.basename(item.text()))

        self.info_field.setEditTriggers(QAbstractItemView.NoEditTriggers)

    def append_measurement_result(self):
        try:
            compute_class = self.settings.measurement_profiles[
                self.measurement_type.currentText()]
        except KeyError:
            QMessageBox.warning(
                self,
                "Measurement profile not found",
                f"Measurement profile '{self.measurement_type.currentText()}' not found'",
            )
            return

        if self.settings.roi is None:
            return
        units = self.units_choose.get_value()

        # FIXME find which errors should be displayed as warning
        # def exception_hook(exception):
        #    QMessageBox.warning(self, "Calculation error", f"Error during calculation: {exception}")

        for num in compute_class.get_channels_num():
            if num >= self.settings.image.channels:
                QMessageBox.warning(
                    self,
                    "Measurement error",
                    "Cannot calculate this measurement because "
                    f"image do not have channel {num+1}",
                )
                return

        thread = ExecuteFunctionThread(
            compute_class.calculate,
            [
                self.settings.image,
                self.channels_chose.currentIndex(), self.settings.roi_info,
                units
            ],
        )
        dial = WaitingDialog(
            thread,
            "Measurement calculation")  # , exception_hook=exception_hook)
        dial.exec()
        stat: MeasurementResult = thread.result
        if stat is None:
            return
        stat.set_filename(self.settings.image_path)
        self.measurements_storage.add_measurements(stat)
        self.previous_profile = compute_class.name
        self.refresh_view()

    def keyPressEvent(self, e: QKeyEvent):
        if e.modifiers() & Qt.ControlModifier:
            selected = self.info_field.selectedRanges()

            if e.key() == Qt.Key_C:  # copy
                s = ""

                for r in range(selected[0].topRow(),
                               selected[0].bottomRow() + 1):
                    for c in range(selected[0].leftColumn(),
                                   selected[0].rightColumn() + 1):
                        try:
                            s += str(self.info_field.item(r, c).text()) + "\t"
                        except AttributeError:
                            s += "\t"
                    s = s[:-1] + "\n"  # eliminate last '\t'
                self.clip.setText(s)

    def update_measurement_list(self):
        self.measurement_type.blockSignals(True)
        available = list(sorted(self.settings.measurement_profiles.keys()))
        text = self.measurement_type.currentText()
        try:
            index = available.index(text) + 1
        except ValueError:
            index = 0
        self.measurement_type.clear()
        self.measurement_type.addItem("<none>")
        self.measurement_type.addItems(available)
        self.measurement_type.setCurrentIndex(index)
        self.measurement_type.blockSignals(False)

    def showEvent(self, _):
        self.update_measurement_list()

    def event(self, event: QEvent):
        if event.type() == QEvent.WindowActivate:
            self.update_measurement_list()
        return super().event(event)

    @staticmethod
    def _move_widgets(widgets_list: List[Tuple[QWidget, int]],
                      layout1: QBoxLayout, layout2: QBoxLayout):
        for el in widgets_list:
            layout1.removeWidget(el[0])
            layout2.addWidget(el[0], el[1])

    def resizeEvent(self, _event: QResizeEvent) -> None:
        if self.width() < 800 and self.butt_layout2.count() == 0:
            self._move_widgets(
                [(self.file_names_label, 1), (self.file_names, 1),
                 (self.copy_button, 2)],
                self.butt_layout,
                self.butt_layout2,
            )
        elif self.width() > 800 and self.butt_layout2.count() != 0:
            self._move_widgets(
                [(self.file_names_label, 1), (self.file_names, 1),
                 (self.copy_button, 2)],
                self.butt_layout2,
                self.butt_layout,
            )
Esempio n. 17
0
class RgbSelectionWidget(QWidget):

    signal_update_map_selections = Signal()

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

        self._range_table = []
        self._limit_table = []
        self._rgb_keys = ["red", "green", "blue"]
        self._rgb_dict = {_: None for _ in self._rgb_keys}

        widget_layout = self._setup_rgb_widget()
        self.setLayout(widget_layout)

        sp = QSizePolicy()
        sp.setControlType(QSizePolicy.PushButton)
        sp.setHorizontalPolicy(QSizePolicy.Expanding)
        sp.setVerticalPolicy(QSizePolicy.Fixed)
        self.setSizePolicy(sp)

    def _setup_rgb_element(self, n_row, *, rb_check=0):
        """
        Parameters
        ----------
        rb_check: int
            The number of QRadioButton to check. Typically this would be the row number.
        """
        combo_elements = ComboBoxNamed(name=f"{n_row}")
        # combo_elements.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # Set text color for QComboBox widget (necessary if the program is used with Dark theme)
        pal = combo_elements.palette()
        pal.setColor(QPalette.ButtonText, Qt.black)
        combo_elements.setPalette(pal)
        # Set text color for drop-down view (necessary if the program is used with Dark theme)
        pal = combo_elements.view().palette()
        pal.setColor(QPalette.Text, Qt.black)
        combo_elements.view().setPalette(pal)

        btns = [QRadioButton(), QRadioButton(), QRadioButton()]
        if 0 <= rb_check < len(btns):
            btns[rb_check].setChecked(True)

        # Color is set for operation with Dark theme
        for btn in btns:
            pal = btn.palette()
            pal.setColor(QPalette.Text, Qt.black)
            btn.setPalette(pal)

        btn_group = QButtonGroup()
        for btn in btns:
            btn_group.addButton(btn)

        rng = RangeManager(name=f"{n_row}", add_sliders=True)
        rng.setTextColor([0, 0, 0])  # Set text color to 'black'
        # Set some text in edit boxes (just to demonstrate how the controls will look like)
        rng.le_min_value.setText("0.0")
        rng.le_max_value.setText("1.0")

        rng.setAlignment(Qt.AlignCenter)
        return combo_elements, btns, rng, btn_group

    def _enable_selection_events(self, enable):
        if enable:
            if not self.elements_btn_groups_events_enabled:
                for btn_group in self.elements_btn_groups:
                    btn_group.buttonToggled.connect(self.rb_toggled)
                for el_combo in self.elements_combo:
                    el_combo.currentIndexChanged.connect(
                        self.combo_element_current_index_changed)
                for el_range in self.elements_range:
                    el_range.selection_changed.connect(
                        self.range_selection_changed)
                self.elements_btn_groups_events_enabled = True
        else:
            if self.elements_btn_groups_events_enabled:
                for btn_group in self.elements_btn_groups:
                    btn_group.buttonToggled.disconnect(self.rb_toggled)
                for el_combo in self.elements_combo:
                    el_combo.currentIndexChanged.disconnect(
                        self.combo_element_current_index_changed)
                # Disconnecting the Range Manager signals is not necessary, but let's do it for consistency
                for el_range in self.elements_range:
                    el_range.selection_changed.disconnect(
                        self.range_selection_changed)
                self.elements_btn_groups_events_enabled = False

    def _setup_rgb_widget(self):

        self.elements_combo = []
        self.elements_rb_color = []
        self.elements_range = []
        self.elements_btn_groups = []
        self.elements_btn_groups_events_enabled = False
        self.row_colors = []

        self.table = QTableWidget()
        # Horizontal header entries
        tbl_labels = ["Element", "Red", "Green", "Blue", "Range"]
        # The list of columns that stretch with the table
        self.tbl_cols_stretch = ("Range", )

        self.table.setColumnCount(len(tbl_labels))
        self.table.setRowCount(3)
        self.table.setHorizontalHeaderLabels(tbl_labels)
        self.table.verticalHeader().hide()
        self.table.setSelectionMode(QTableWidget.NoSelection)

        header = self.table.horizontalHeader()
        for n, lbl in enumerate(tbl_labels):
            # Set stretching for the columns
            if lbl in self.tbl_cols_stretch:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            else:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)

        vheader = self.table.verticalHeader()
        vheader.setSectionResizeMode(QHeaderView.Stretch)  # ResizeToContents)

        for n_row in range(3):
            combo_elements, btns, rng, btn_group = self._setup_rgb_element(
                n_row, rb_check=n_row)

            combo_elements.setMinimumWidth(180)
            self.table.setCellWidget(n_row, 0, combo_elements)
            for i, btn in enumerate(btns):
                item = QWidget()
                item_hbox = QHBoxLayout(item)
                item_hbox.addWidget(btn)
                item_hbox.setAlignment(Qt.AlignCenter)
                item_hbox.setContentsMargins(0, 0, 0, 0)
                item.setMinimumWidth(70)

                self.table.setCellWidget(n_row, i + 1, item)

            rng.setMinimumWidth(200)
            rng.setMaximumWidth(400)
            self.table.setCellWidget(n_row, 4, rng)

            self.elements_combo.append(combo_elements)
            self.elements_rb_color.append(btns)
            self.elements_range.append(rng)
            self.elements_btn_groups.append(btn_group)
            self.row_colors.append(self._rgb_keys[n_row])

        # Colors that are used to paint rows of the table in RGB colors
        br = 150
        self._rgb_row_colors = {
            "red": (255, br, br),
            "green": (br, 255, br),
            "blue": (br, br, 255)
        }
        self._rgb_color_keys = ["red", "green", "blue"]

        # Set initial colors
        for n_row in range(self.table.rowCount()):
            self._set_row_color(n_row)

        self._enable_selection_events(True)

        self.table.resizeRowsToContents()

        # Table height is computed based on content. It doesn't seem
        #   to account for the height of custom widgets, but the table
        #   looks good enough
        table_height = 0
        for n_row in range(self.table.rowCount()):
            table_height += self.table.rowHeight(n_row)
        self.table.setMaximumHeight(table_height)

        table_width = 650
        self.table.setMinimumWidth(table_width)
        self.table.setMaximumWidth(800)

        hbox = QHBoxLayout()
        hbox.addWidget(self.table)

        return hbox

    def combo_element_current_index_changed(self, name, index):
        if index < 0 or index >= len(self._range_table):
            return
        n_row = int(name)
        sel_eline = self._range_table[index][0]
        row_color = self.row_colors[n_row]
        self._rgb_dict[row_color] = sel_eline

        self.elements_range[n_row].set_range(self._range_table[index][1],
                                             self._range_table[index][2])
        self.elements_range[n_row].set_selection(
            value_low=self._limit_table[index][1],
            value_high=self._limit_table[index][2])
        self._update_map_selections()

    def range_selection_changed(self, v_low, v_high, name):
        n_row = int(name)
        row_color = self.row_colors[n_row]
        sel_eline = self._rgb_dict[row_color]
        ind = None
        try:
            ind = [_[0] for _ in self._limit_table].index(sel_eline)
        except ValueError:
            pass
        if ind is not None:
            self._limit_table[ind][1] = v_low
            self._limit_table[ind][2] = v_high
            self._update_map_selections()
        # We are not preventing users to select the same emission line in to rows.
        #   Update the selected limits in other rows where the same element is selected.
        for nr, el_range in enumerate(self.elements_range):
            if (nr != n_row) and (self._rgb_dict[self.row_colors[nr]]
                                  == sel_eline):
                el_range.set_selection(value_low=v_low, value_high=v_high)

    def _get_selected_row_color(self, n_row):

        color_key = None

        btns = self.elements_rb_color[n_row]

        for n, btn in enumerate(btns):
            if btn.isChecked():
                color_key = self._rgb_color_keys[n]
                break

        return color_key

    def _set_row_color(self, n_row, *, color_key=None):
        """
        Parameters
        ----------
        n_row: int
            The row number that needs background color change (0..2 if table has 3 rows)
        color_key: int
            Color key: "red", "green" or "blue"
        """

        if color_key is None:
            color_key = self._get_selected_row_color(n_row)
        if color_key is None:
            return

        self.row_colors[n_row] = color_key
        rgb = self._rgb_row_colors[color_key]

        # The following code is based on the arrangement of the widgets in the table
        #   Modify the code if widgets are arranged differently or the table structure
        #   is changed
        for n_col in range(self.table.columnCount()):
            wd = self.table.cellWidget(n_row, n_col)
            if n_col == 0:
                # Combo box: update both QComboBox and QWidget backgrounds
                #   QWidget - background of the drop-down selection list
                css1 = get_background_css(rgb,
                                          widget="QComboBox",
                                          editable=False)
                css2 = get_background_css(rgb, widget="QWidget", editable=True)
                wd.setStyleSheet(css2 + css1)
            elif n_col <= 3:
                # 3 QRadioButton's. The buttons are inserted into QWidget objects,
                #   and we need to change backgrounds of QWidgets, not only buttons.
                wd.setStyleSheet(
                    get_background_css(rgb, widget="QWidget", editable=False))
            elif n_col == 4:
                # Custom RangeManager widget, color is updated using custom method
                wd.setBackground(rgb)

        n_col = self._rgb_color_keys.index(color_key)
        for n, n_btn in enumerate(self.elements_rb_color[n_row]):
            check_status = True if n == n_col else False
            n_btn.setChecked(check_status)

    def _fill_table(self):
        self._enable_selection_events(False)

        eline_list = [_[0] for _ in self._range_table]
        for n_row in range(self.table.rowCount()):
            self.elements_combo[n_row].clear()
            self.elements_combo[n_row].addItems(eline_list)

        for n_row, color in enumerate(self._rgb_color_keys):
            # Initially set colors in order
            self._set_row_color(n_row, color_key=color)
            eline_key = self._rgb_dict[color]
            if eline_key is not None:
                try:
                    ind = eline_list.index(eline_key)
                    self.elements_combo[n_row].setCurrentIndex(ind)
                    range_low, range_high = self._range_table[ind][1:]
                    self.elements_range[n_row].set_range(range_low, range_high)
                    sel_low, sel_high = self._limit_table[ind][1:]
                    self.elements_range[n_row].set_selection(
                        value_low=sel_low, value_high=sel_high)
                except ValueError:
                    pass
            else:
                self.elements_combo[n_row].setCurrentIndex(-1)  # Deselect all
                self.elements_range[n_row].set_range(0, 1)
                self.elements_range[n_row].set_selection(value_low=0,
                                                         value_high=1)

        self._enable_selection_events(True)

    def _find_rbutton(self, button):
        for nr, btns in enumerate(self.elements_rb_color):
            for nc, btn in enumerate(btns):
                if btn == button:
                    # Return tuple (nr, nc)
                    return nr, nc
        # Return None if the button is not found (this shouldn't happen)
        return None

    def rb_toggled(self, button, state):
        if state:  # Ignore signals from unchecked buttons
            nr, nc = self._find_rbutton(button)

            color_current = self.row_colors[nr]
            color_to_set = self._rgb_color_keys[nc]
            nr_switch = self.row_colors.index(color_to_set)

            self._enable_selection_events(False)

            self._set_row_color(nr, color_key=color_to_set)
            self._set_row_color(nr_switch, color_key=color_current)
            # Swap selected maps
            tmp = self._rgb_dict[color_to_set]
            self._rgb_dict[color_to_set] = self._rgb_dict[color_current]
            self._rgb_dict[color_current] = tmp

            self._enable_selection_events(True)

            self._update_map_selections()

    def set_ranges_and_limits(self,
                              *,
                              range_table=None,
                              limit_table=None,
                              rgb_dict=None):
        if range_table is not None:
            self._range_table = copy.deepcopy(range_table)
        if limit_table is not None:
            self._limit_table = copy.deepcopy(limit_table)
        if rgb_dict is not None:
            self._rgb_dict = rgb_dict.copy()
        self._fill_table()

    def _update_map_selections(self):
        """Upload the selections (limit table) and update plot"""
        self.signal_update_map_selections.emit()
Esempio n. 18
0
class DevicesFrame(QFrame):
    def __init__(self, *args, **kwargs):
        super(DevicesFrame, self).__init__(*args, **kwargs)
        self.setFrameStyle(QFrame.Panel | QFrame.Raised)
        self.contentLayout = QGridLayout()

        self.data: List[Device] = []

        # Current Action Status
        self.status = {}

        self.devicePrefixFilterLabel = QLabel(
            "Device filter (Ion Pump not the channel!)"
        )
        self.devicePrefixFilterInp = QLineEdit()
        self.devicePrefixFilterInp.setMaximumWidth(100)

        self.updateDeviceListButton = QPushButton("Apply Filter")
        self.updateDeviceListButton.clicked.connect(self.updateDeviceList)
        self.updateDeviceListButton.setToolTip("Filter the device prefix list.")

        self.contentLayout.addWidget(self.devicePrefixFilterLabel, 0, 0, 1, 2)
        self.contentLayout.addWidget(self.devicePrefixFilterInp, 1, 0, 1, 1)
        self.contentLayout.addWidget(self.updateDeviceListButton, 1, 1, 1, 1)

        self.deviceList = DeviceTreeView()
        self.contentLayout.addWidget(self.deviceList, 2, 0, 2, 2)

        self.deviceStatus = QTableWidget()
        self.deviceStatus.setColumnCount(2)
        # self.deviceStatus.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.deviceStatus.setHorizontalHeaderLabels(["Device", "Status"])
        self.deviceStatusLabel = QLabel("Status")

        self.contentLayout.addWidget(self.deviceStatusLabel, 0, 2, 1, 2)
        self.contentLayout.addWidget(self.deviceStatus, 1, 2, 4, 2)

        self.contentLayout.setRowStretch(2, 2)

        self.setLayout(self.contentLayout)
        self.reloadData()

    def clearStatus(self):
        self.status = {}
        self.deviceStatus.setRowCount(0)

    def updateStatus(self, param):
        self.status[param["device"]] = "{}".format(param["status"])
        idx = 0
        self.deviceStatus.setRowCount(len(self.status))
        for k, v in self.status.items():
            self.deviceStatus.setItem(idx, 0, QTableWidgetItem(k))
            self.deviceStatus.setItem(idx, 1, QTableWidgetItem(v))
            idx += 1

    def getSelectedDevices(self) -> List[DeviceTreeSelection]:
        return self.deviceList.getData()

    def highlightChecked(self, item):
        if item.checkState() == Qt.Checked:
            item.setBackground(QColor("#ffffb2"))
        else:
            item.setBackground(QColor("#ffffff"))

    def updateDeviceList(self):
        devs = []
        _filter = self.devicePrefixFilterInp.text()
        for d in self.data:
            if _filter == "" or _filter in d.prefix:
                devs.append(d)
        self.deviceList.importData(devs)

    def reloadData(self):
        self.data = getDevicesFromBeagles(getBeaglesFromList(getAgilent()))
        self.deviceList.importData(self.data)
Esempio n. 19
0
class WndLoadQuantitativeCalibration(SecondaryWindow):

    signal_quantitative_calibration_changed = Signal()

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        self.initialize()

    def initialize(self):
        self.table_header_display_names = False

        self.setWindowTitle("PyXRF: Load Quantitative Calibration")
        self.setMinimumWidth(750)
        self.setMinimumHeight(400)
        self.resize(750, 600)

        self.pb_load_calib = QPushButton("Load Calibration ...")
        self.pb_load_calib.clicked.connect(self.pb_load_calib_clicked)

        self._changes_exist = False
        self._auto_update = True
        self.cb_auto_update = QCheckBox("Auto")
        self.cb_auto_update.setCheckState(
            Qt.Checked if self._auto_update else Qt.Unchecked)
        self.cb_auto_update.stateChanged.connect(
            self.cb_auto_update_state_changed)

        self.pb_update_plots = QPushButton("Update Plots")
        self.pb_update_plots.clicked.connect(self.pb_update_plots_clicked)

        self.grp_current_scan = QGroupBox(
            "Parameters of Currently Processed Scan")

        self._distance_to_sample = 0.0
        self.le_distance_to_sample = LineEditExtended()
        le_dist_validator = QDoubleValidator()
        le_dist_validator.setBottom(0)
        self.le_distance_to_sample.setValidator(le_dist_validator)
        self._set_distance_to_sample()
        self.le_distance_to_sample.editingFinished.connect(
            self.le_distance_to_sample_editing_finished)
        self.le_distance_to_sample.focusOut.connect(
            self.le_distance_to_sample_focus_out)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Distance-to-sample:"))
        hbox.addWidget(self.le_distance_to_sample)
        hbox.addStretch(1)
        self.grp_current_scan.setLayout(hbox)

        self.eline_rb_exclusive = [
        ]  # Holds the list of groups of exclusive radio buttons
        self._setup_tab_widget()

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_load_calib)
        hbox.addStretch(1)
        hbox.addWidget(self.cb_auto_update)
        hbox.addWidget(self.pb_update_plots)
        vbox.addLayout(hbox)

        vbox.addWidget(self.tab_widget)

        vbox.addWidget(self.grp_current_scan)

        self.setLayout(vbox)

        # Display data
        self.update_all_data()

        self._set_tooltips()

    def _setup_tab_widget(self):

        self.tab_widget = QTabWidget()
        self.loaded_standards = QWidget()
        # self.display_loaded_standards()
        self.scroll = QScrollArea()
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidget(self.loaded_standards)
        self.tab_widget.addTab(self.scroll, "Loaded Standards")

        self.combo_set_table_header = QComboBox()
        self.combo_set_table_header.addItems(
            ["Standard Serial #", "Standard Name"])
        self.combo_set_table_header.currentIndexChanged.connect(
            self.combo_set_table_header_index_changed)

        vbox = QVBoxLayout()
        vbox.addSpacing(5)
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Display in table header:"))
        hbox.addWidget(self.combo_set_table_header)
        hbox.addStretch(1)
        vbox.addLayout(hbox)
        self.table = QTableWidget()
        self.table.verticalHeader().hide()
        self.table.setSelectionMode(QTableWidget.NoSelection)
        self.table.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.table.horizontalHeader().setMinimumSectionSize(150)
        vbox.addWidget(self.table)

        self.table.setStyleSheet("QTableWidget::item{color: black;}")

        frame = QFrame()
        vbox.setContentsMargins(0, 0, 0, 0)
        frame.setLayout(vbox)

        self.tab_widget.addTab(frame, "Selected Emission Lines")

    def display_loaded_standards(self):
        calib_data = self.gpc.get_quant_calibration_data()
        calib_settings = self.gpc.get_quant_calibration_settings()

        # Create the new widget (this deletes the old widget)
        self.loaded_standards = QWidget()
        self.loaded_standards.setMinimumWidth(700)

        # Also delete references to all components
        self.frames_calib_data = []
        self.pbs_view = []
        self.pbs_remove = []

        # All 'View' buttons are added to the group in order to be connected to the same slot
        self.group_view = QButtonGroup()
        self.group_view.setExclusive(False)
        self.group_view.buttonClicked.connect(self.pb_view_clicked)
        # The same for the 'Remove' buttons
        self.group_remove = QButtonGroup()
        self.group_remove.setExclusive(False)
        self.group_remove.buttonClicked.connect(self.pb_remove_clicked)

        vbox = QVBoxLayout()

        class _LabelBlack(QLabel):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.setStyleSheet("color: black")

        for cdata, csettings in zip(calib_data, calib_settings):
            frame = QFrame()
            frame.setFrameStyle(QFrame.StyledPanel)
            frame.setStyleSheet(
                get_background_css((200, 255, 200), widget="QFrame"))

            _vbox = QVBoxLayout()

            name = cdata["name"]  # Standard name (can be arbitrary string
            # If name is long, then print it in a separate line
            _name_is_long = len(name) > 30

            pb_view = QPushButton("View ...")
            self.group_view.addButton(pb_view)
            pb_remove = QPushButton("Remove")
            self.group_remove.addButton(pb_remove)

            # Row 1: serial, name
            serial = cdata["serial"]
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack(f"<b>Standard</b> #{serial}"))
            if not _name_is_long:
                _hbox.addWidget(_LabelBlack(f"'{name}'"))
            _hbox.addStretch(1)
            _hbox.addWidget(pb_view)
            _hbox.addWidget(pb_remove)
            _vbox.addLayout(_hbox)

            # Optional row
            if _name_is_long:
                # Wrap name if it is extemely long
                name = textwrap.fill(name, width=80)
                _hbox = QHBoxLayout()
                _hbox.addWidget(_LabelBlack("<b>Name:</b> "), 0, Qt.AlignTop)
                _hbox.addWidget(_LabelBlack(name), 0, Qt.AlignTop)
                _hbox.addStretch(1)
                _vbox.addLayout(_hbox)

            # Row 2: description
            description = textwrap.fill(cdata["description"], width=80)
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack("<b>Description:</b>"), 0, Qt.AlignTop)
            _hbox.addWidget(_LabelBlack(f"{description}"), 0, Qt.AlignTop)
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            # Row 3:
            incident_energy = cdata["incident_energy"]
            scaler = cdata["scaler_name"]
            detector_channel = cdata["detector_channel"]
            distance_to_sample = cdata["distance_to_sample"]
            _hbox = QHBoxLayout()
            _hbox.addWidget(
                _LabelBlack(f"<b>Incident energy, keV:</b> {incident_energy}"))
            _hbox.addWidget(_LabelBlack(f"  <b>Scaler:</b> {scaler}"))
            _hbox.addWidget(
                _LabelBlack(f"  <b>Detector channel:</b> {detector_channel}"))
            _hbox.addWidget(
                _LabelBlack(
                    f"  <b>Distance-to-sample:</b> {distance_to_sample}"))
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            # Row 4: file name
            fln = textwrap.fill(csettings["file_path"], width=80)
            _hbox = QHBoxLayout()
            _hbox.addWidget(_LabelBlack("<b>Source file:</b>"), 0, Qt.AlignTop)
            _hbox.addWidget(_LabelBlack(fln), 0, Qt.AlignTop)
            _hbox.addStretch(1)
            _vbox.addLayout(_hbox)

            frame.setLayout(_vbox)

            # Now the group box is added to the upper level layout
            vbox.addWidget(frame)
            vbox.addSpacing(5)
            self.frames_calib_data.append(frame)
            self.pbs_view.append(pb_view)
            self.pbs_remove.append(pb_remove)

        # Add the layout to the widget
        self.loaded_standards.setLayout(vbox)
        # ... and put the widget inside the scroll area. This will update the
        # contents of the scroll area.
        self.scroll.setWidget(self.loaded_standards)

    def display_table_header(self):
        calib_data = self.gpc.get_quant_calibration_data()
        header_by_name = self.table_header_display_names

        tbl_labels = ["Lines"]
        for n, cdata in enumerate(calib_data):
            if header_by_name:
                txt = cdata["name"]
            else:
                txt = cdata["serial"]
            txt = textwrap.fill(txt, width=20)
            tbl_labels.append(txt)

        self.table.setHorizontalHeaderLabels(tbl_labels)

    def display_standard_selection_table(self):
        calib_data = self.gpc.get_quant_calibration_data()
        self._quant_file_paths = self.gpc.get_quant_calibration_file_path_list(
        )

        brightness = 220
        table_colors = [(255, brightness, brightness),
                        (brightness, 255, brightness)]

        # Disconnect all radio button signals before clearing the table
        for bgroup in self.eline_rb_exclusive:
            bgroup.buttonToggled.disconnect(self.rb_selection_toggled)

        # This list will hold radio button groups for horizontal rows
        #   Those are exclusive groups. They are not going to be
        #   used directly, but they must be kept alive in order
        #   for the radiobuttons to work properly. Most of the groups
        #   will contain only 1 radiobutton, which will always remain checked.
        self.eline_rb_exclusive = []
        # The following list will contain the list of radio buttons for each
        #   row. If there is no radiobutton in a position, then the element is
        #   set to None.
        # N rows: the number of emission lines, N cols: the number of standards
        self.eline_rb_lists = []

        self.table.clear()

        if not calib_data:
            self.table.setRowCount(0)
            self.table.setColumnCount(0)
        else:
            # Create the sorted list of available element lines
            line_set = set()
            for cdata in calib_data:
                ks = list(cdata["element_lines"].keys())
                line_set.update(list(ks))
            self.eline_list = list(line_set)
            self.eline_list.sort()

            for n in range(len(self.eline_list)):
                self.eline_rb_exclusive.append(QButtonGroup())
                self.eline_rb_lists.append([None] * len(calib_data))

            self.table.setColumnCount(len(calib_data) + 1)
            self.table.setRowCount(len(self.eline_list))
            self.display_table_header()

            for n, eline in enumerate(self.eline_list):

                rgb = table_colors[n % 2]

                item = QTableWidgetItem(eline)
                item.setTextAlignment(Qt.AlignCenter)
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                item.setBackground(QBrush(QColor(*rgb)))
                self.table.setItem(n, 0, item)

                for ns, cdata in enumerate(calib_data):
                    q_file_path = self._quant_file_paths[
                        ns]  # Used to identify standard
                    if eline in cdata["element_lines"]:
                        rb = QRadioButton()
                        if self.gpc.get_quant_calibration_is_eline_selected(
                                eline, q_file_path):
                            rb.setChecked(True)

                        rb.setStyleSheet("color: black")

                        self.eline_rb_lists[n][ns] = rb
                        # self.eline_rb_by_standard[ns].addButton(rb)
                        self.eline_rb_exclusive[n].addButton(rb)

                        item = QWidget()
                        item_hbox = QHBoxLayout(item)
                        item_hbox.addWidget(rb)
                        item_hbox.setAlignment(Qt.AlignCenter)
                        item_hbox.setContentsMargins(0, 0, 0, 0)

                        item.setStyleSheet(get_background_css(rgb))

                        # Generate tooltip
                        density = cdata["element_lines"][eline]["density"]
                        fluorescence = cdata["element_lines"][eline][
                            "fluorescence"]
                        ttip = f"Fluorescence (F): {fluorescence:12g}\nDensity (D): {density:12g}\n"
                        # Avoid very small values of density (probably zero)
                        if abs(density) > 1e-30:
                            ttip += f"F/D: {fluorescence/density:12g}"

                        item.setToolTip(ttip)

                        self.table.setCellWidget(n, ns + 1, item)
                    else:
                        # There is no radio button, but we still need to fill the cell
                        item = QTableWidgetItem("")
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                        item.setBackground(QBrush(QColor(*rgb)))
                        self.table.setItem(n, ns + 1, item)

            # Now the table is set (specifically radio buttons).
            # So we can connect the button groups with the event processing function
            for bgroup in self.eline_rb_exclusive:
                bgroup.buttonToggled.connect(self.rb_selection_toggled)

    @Slot()
    def update_all_data(self):
        self.display_loaded_standards()
        self.display_standard_selection_table()
        self._set_distance_to_sample()

    def _set_distance_to_sample(self):
        """Set 'le_distance_to_sample` without updating maps"""
        distance_to_sample = self.gpc.get_quant_calibration_distance_to_sample(
        )
        if distance_to_sample is None:
            distance_to_sample = 0.0
        self._distance_to_sample = distance_to_sample
        self._set_le_distance_to_sample(distance_to_sample)

    def _set_tooltips(self):
        set_tooltip(self.pb_load_calib,
                    "Load <b>calibration data</b> from JSON file.")
        set_tooltip(
            self.cb_auto_update,
            "Automatically <b>update the plots</b> when changes are made. "
            "If unchecked, then button <b>Update Plots</b> must be pressed "
            "to update the plots. Automatic update is often undesirable "
            "when large maps are displayed and multiple changes to parameters "
            "are made.",
        )
        set_tooltip(
            self.pb_update_plots,
            "<b>Update plots</b> based on currently selected parameters.")
        set_tooltip(
            self.le_distance_to_sample,
            "Distance between <b>the sample and the detector</b>. The ratio between of the distances "
            "during calibration and measurement is used to scale computed concentrations. "
            "If distance-to-sample is 0 for calibration or measurement, then no scaling is performed.",
        )
        set_tooltip(
            self.combo_set_table_header,
            "Use <b>Serial Number</b> or <b>Name</b> of the calibration standard in the header of the table",
        )
        set_tooltip(
            self.table,
            "Use Radio Buttons to select the <b>source of calibration data</b> for each emission line. "
            "This feature is needed if multiple loaded calibration files have data on the same "
            "emission line.",
        )

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        # Hide the window if required by the program state
        state_xrf_map_exists = self.gui_vars["gui_state"][
            "state_xrf_map_exists"]
        if not state_xrf_map_exists:
            self.hide()

        if condition == "tooltips":
            self._set_tooltips()

    def cb_auto_update_state_changed(self, state):
        self._auto_update = state
        self.pb_update_plots.setEnabled(not state)
        # If changes were made, apply the changes while switching to 'auto' mode
        if state and self._changes_exist:
            self._update_maps_auto()

    def pb_update_plots_clicked(self):
        self._update_maps()

    def pb_load_calib_clicked(self):
        current_dir = self.gpc.get_current_working_directory()
        file_name = QFileDialog.getOpenFileName(
            self, "Select File with Quantitative Calibration Data",
            current_dir, "JSON (*.json);; All (*)")
        file_name = file_name[0]
        if file_name:
            try:
                logger.debug(
                    f"Loading quantitative calibration from file: '{file_name}'"
                )
                self.gpc.load_quantitative_calibration_data(file_name)
                self.update_all_data()
                self._update_maps_auto()
            except Exception:
                msg = "The selected JSON file has incorrect format. Select a different file."
                msgbox = QMessageBox(QMessageBox.Critical,
                                     "Data Loading Error",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

    def pb_view_clicked(self, button):
        try:
            n_standard = self.pbs_view.index(button)
            calib_settings = self.gpc.get_quant_calibration_settings()
            file_path = calib_settings[n_standard]["file_path"]
            calib_preview = self.gpc.get_quant_calibration_text_preview(
                file_path)
            dlg = DialogViewCalibStandard(None,
                                          file_path=file_path,
                                          calib_preview=calib_preview)
            dlg.exec()
        except ValueError:
            logger.error(
                "'View' button was pressed, but not found in the list of buttons"
            )

    def pb_remove_clicked(self, button):
        try:
            n_standard = self.pbs_remove.index(button)
            calib_settings = self.gpc.get_quant_calibration_settings()
            file_path = calib_settings[n_standard]["file_path"]
            self.gpc.quant_calibration_remove_entry(file_path)
            self.update_all_data()
            self._update_maps_auto()
        except ValueError:
            logger.error(
                "'Remove' button was pressed, but not found in the list of buttons"
            )

    def rb_selection_toggled(self, button, checked):
        if checked:
            # Find the button in 2D list 'self.eline_rb_lists'
            button_found = False
            for nr, rb_list in enumerate(self.eline_rb_lists):
                try:
                    nc = rb_list.index(button)
                    button_found = True
                    break
                except ValueError:
                    pass

            if button_found:
                eline = self.eline_list[nr]
                n_standard = nc
                file_path = self._quant_file_paths[n_standard]
                self.gpc.set_quant_calibration_select_eline(eline, file_path)
                self._update_maps_auto()
            else:
                # This should never happen
                logger.error(
                    "Selection radio button was pressed, but not found in the list"
                )

    def combo_set_table_header_index_changed(self, index):
        self.table_header_display_names = bool(index)
        self.display_table_header()

    def le_distance_to_sample_editing_finished(self):
        distance_to_sample = float(self.le_distance_to_sample.text())
        if distance_to_sample != self._distance_to_sample:
            self._distance_to_sample = distance_to_sample
            self.gpc.set_quant_calibration_distance_to_sample(
                distance_to_sample)
            self._update_maps_auto()

    def le_distance_to_sample_focus_out(self):
        try:
            float(self.le_distance_to_sample.text())
        except ValueError:
            # If the text can not be interpreted to float, then replace the text with the old value
            self._set_le_distance_to_sample(self._distance_to_sample)

    def _set_le_distance_to_sample(self, distance_to_sample):
        self.le_distance_to_sample.setText(f"{distance_to_sample:.12g}")

    def _update_maps_auto(self):
        """Update maps only if 'auto' update is ON. Used as a 'filter'
        to prevent extra plot updates."""
        self._changes_exist = True
        if self._auto_update:
            self._update_maps()

    def _update_maps(self):
        """Upload the selections (limit table) and update plot"""
        self._changes_exist = False
        self._redraw_maps()
        # Emit signal only after the maps are redrawn. This should change
        #   ranges in the respective controls for the plots
        self.signal_quantitative_calibration_changed.emit()

    def _redraw_maps(self):
        # We don't emit any signals here, but we don't really need to.
        logger.debug("Redrawing RGB XRF Maps")
        self.gpc.compute_map_ranges()
        self.gpc.redraw_maps()
        self.gpc.compute_rgb_map_ranges()
        self.gpc.redraw_rgb_maps()
Esempio n. 20
0
class SPropertiesViewer(QWidget):
    """Widget to display a the particles properties table

    Parameters
    ----------
    napari_viewer: QWidget
        The napari viewer

    """
    def __init__(self, napari_viewer):
        super().__init__()
        self.viewer = napari_viewer
        self.layer_name = ''

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        self.tableWidget = QTableWidget()
        self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        layout.addWidget(self.tableWidget)
        self.setLayout(layout)

    def reload(self):
        """Reload the particles properties from the layers to the table widget"""
        particles = self.viewer.layers[self.layer_name].data
        print(particles)
        properties = self.viewer.layers[self.layer_name].properties
        headers = []
        if particles.shape[1] == 3:
            headers = ['T', 'Y', 'X']
        elif particles.shape[1] == 4:
            headers = ['T', 'Z', 'Y', 'X']

        for key in properties:
            headers.append(key)
        self.tableWidget.setColumnCount(len(headers))
        self.tableWidget.setHorizontalHeaderLabels(headers)
        self.tableWidget.setRowCount(particles.shape[0])

        col = 0
        for line in range(particles.shape[0]):
            col = -1

            # T
            if particles.shape[1] == 4:
                col += 1
                self.tableWidget.setItem(line, col,
                                         QTableWidgetItem(
                                             str(particles[line, col])))
            # T or Z
            if particles.shape[1] >= 3:
                col += 1
                self.tableWidget.setItem(line, col,
                                         QTableWidgetItem(
                                             str(particles[line, col])))
            # Y
            col += 1
            self.tableWidget.setItem(line, col,
                                     QTableWidgetItem(
                                         str(particles[line, col])))
            # X
            col += 1
            self.tableWidget.setItem(line, col,
                                     QTableWidgetItem(
                                         str(particles[line, col])))
        # properties
        for key in properties:
            col += 1
            prop = properties[key]
            for line in range(len(prop)):
                self.tableWidget.setItem(line, col,
                                         QTableWidgetItem(str(prop[line])))
Esempio n. 21
0
class LogViewerDialog(DialogBase):
    """Logger widget."""

    def __init__(
        self,
        parent=None,
        log_folder=LOG_FOLDER,
        log_filename=LOG_FILENAME,
    ):
        """
        Logger widget.

        Parameters
        ----------
        log_folder: str
            Folder where logs are located
        log_filename: str
            Basic name for the rotating log files.
        """
        super(LogViewerDialog, self).__init__(parent=parent)

        self._data = None
        self._columns = ['level', 'time', 'module', 'method', 'message']
        self._headers = [c.capitalize() for c in self._columns]
        self._log_filename = log_filename
        self._log_folder = log_folder

        # Widgets
        self.label = QLabel('Select log file:')
        self.combobox = ComboBoxBase()
        self.table_logs = QTableWidget(self)
        self.button_copy = ButtonPrimary('Copy')
        self.text_search = LineEditSearch()

        # Widget setup
        self.table_logs.setAttribute(Qt.WA_LayoutUsesWidgetRect, True)
        horizontal_header = self.table_logs.horizontalHeader()
        vertical_header = self.table_logs.verticalHeader()
        horizontal_header.setStretchLastSection(True)
        horizontal_header.setSectionResizeMode(QHeaderView.Fixed)
        vertical_header.setSectionResizeMode(QHeaderView.Fixed)

        self.table_logs.setSelectionBehavior(QTableWidget.SelectRows)
        self.table_logs.setEditTriggers(QTableWidget.NoEditTriggers)

        self.setWindowTitle('Log Viewer')
        self.setMinimumWidth(800)
        self.setMinimumHeight(500)
        self.text_search.setPlaceholderText("Search...")

        # Layouts
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.label)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.combobox)
        top_layout.addStretch()
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.text_search)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.button_copy)
        layout = QVBoxLayout()
        layout.addLayout(top_layout)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.table_logs)
        self.setLayout(layout)

        # Signals
        self.combobox.currentIndexChanged.connect(self.update_text)
        self.button_copy.clicked.connect(self.copy_item)
        self.text_search.textChanged.connect(self.filter_text)

        # Setup()
        self.setup()
        self.update_style_sheet()

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.setStyleSheet(style_sheet)

    def setup(self):
        """Setup widget content."""
        self.combobox.clear()
        paths = log_files(
            log_folder=self._log_folder,
            log_filename=self._log_filename,
        )
        files = [os.path.basename(p) for p in paths]
        self.combobox.addItems(files)

    def filter_text(self):
        """Search for text in the selected log file."""
        search = self.text_search.text().lower()
        for i, data in enumerate(self._data):
            if any(search in str(d).lower() for d in data.values()):
                self.table_logs.showRow(i)
            else:
                self.table_logs.hideRow(i)

    def row_data(self, row):
        """Give the current row data concatenated with spaces."""
        data = {}
        if self._data:
            length = len(self._data)
            if 0 >= row < length:
                data = self._data[row]
        return data

    def update_text(self, index):
        """Update logs based on combobox selection."""
        path = os.path.join(self._log_folder, self.combobox.currentText())
        self._data = load_log(path)

        self.table_logs.clear()
        self.table_logs.setSortingEnabled(False)
        self.table_logs.setRowCount(len(self._data))
        self.table_logs.setColumnCount(len(self._columns))
        self.table_logs.setHorizontalHeaderLabels(self._headers)

        for row, data in enumerate(self._data):
            for col, col_key in enumerate(self._columns):
                item = QTableWidgetItem(data.get(col_key, ''))
                self.table_logs.setItem(row, col, item)

        for c in [0, 2, 3]:
            self.table_logs.resizeColumnToContents(c)

        self.table_logs.resizeRowsToContents()
        self.table_logs.setSortingEnabled(True)
        self.table_logs.scrollToBottom()
        self.table_logs.scrollToTop()
        self.table_logs.sortByColumn(1, Qt.AscendingOrder)

        # Make sure there is always a selected row
        self.table_logs.setCurrentCell(0, 0)

    def copy_item(self):
        """Copy selected item to clipboard in markdown format."""
        app = QApplication.instance()
        items = self.table_logs.selectedIndexes()
        if items:
            rows = set(sorted(i.row() for i in items))
            if self._data:
                all_data = [self._data[row] for row in rows]
                dump = json.dumps(all_data, sort_keys=True, indent=4)
                app.clipboard().setText('```json\n' + dump + '\n```')
Esempio n. 22
0
class WndManageEmissionLines(SecondaryWindow):

    signal_selected_element_changed = Signal(str)
    signal_update_element_selection_list = Signal()
    signal_update_add_remove_btn_state = Signal(bool, bool)
    signal_marker_state_changed = Signal(bool)

    signal_parameters_changed = Signal()

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        # Threshold used for peak removal (displayed in lineedit)
        self._remove_peak_threshold = self.gpc.get_peak_threshold()

        self._enable_events = False

        self._eline_list = [
        ]  # List of emission lines (used in the line selection combo)
        self._table_contents = [
        ]  # Keep a copy of table contents (list of dict)
        self._selected_eline = ""

        self.initialize()

        self._enable_events = True

        # Marker state is reported by Matplotlib plot in 'line_plot' model
        def cb(marker_state):
            self.signal_marker_state_changed.emit(marker_state)

        self.gpc.set_marker_reporter(cb)
        self.signal_marker_state_changed.connect(
            self.slot_marker_state_changed)

        # Update button states
        self._update_add_remove_btn_state()
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def initialize(self):
        self.setWindowTitle("PyXRF: Add/Remove Emission Lines")
        self.resize(600, 600)

        top_buttons = self._setup_select_elines()
        self._setup_elines_table()
        bottom_buttons = self._setup_action_buttons()

        vbox = QVBoxLayout()

        # Group of buttons above the table
        vbox.addLayout(top_buttons)

        # Tables
        hbox = QHBoxLayout()
        hbox.addWidget(self.tbl_elines)
        vbox.addLayout(hbox)

        vbox.addLayout(bottom_buttons)

        self.setLayout(vbox)

        self._set_tooltips()

    def _setup_select_elines(self):

        self.cb_select_all = QCheckBox("All")
        self.cb_select_all.setChecked(True)
        self.cb_select_all.toggled.connect(self.cb_select_all_toggled)

        self.element_selection = ElementSelection()

        # The following field should switched to 'editable' state from when needed
        self.le_peak_intensity = LineEditReadOnly()

        self.pb_add_eline = QPushButton("Add")
        self.pb_add_eline.clicked.connect(self.pb_add_eline_clicked)

        self.pb_remove_eline = QPushButton("Remove")
        self.pb_remove_eline.clicked.connect(self.pb_remove_eline_clicked)

        self.pb_user_peaks = QPushButton("New User Peak ...")
        self.pb_user_peaks.clicked.connect(self.pb_user_peaks_clicked)
        self.pb_pileup_peaks = QPushButton("New Pileup Peak ...")
        self.pb_pileup_peaks.clicked.connect(self.pb_pileup_peaks_clicked)

        self.element_selection.signal_current_item_changed.connect(
            self.element_selection_item_changed)

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(self.element_selection)
        hbox.addWidget(self.le_peak_intensity)
        hbox.addWidget(self.pb_add_eline)
        hbox.addWidget(self.pb_remove_eline)
        vbox.addLayout(hbox)

        hbox = QHBoxLayout()
        hbox.addWidget(self.cb_select_all)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_user_peaks)
        hbox.addWidget(self.pb_pileup_peaks)
        vbox.addLayout(hbox)

        # Wrap vbox into hbox, because it will be inserted into vbox
        hbox = QHBoxLayout()
        hbox.addLayout(vbox)
        hbox.addStretch(1)
        return hbox

    def _setup_elines_table(self):
        """The table has only functionality necessary to demonstrate how it is going
        to look. A lot more code is needed to actually make it run."""

        self._validator_peak_height = QDoubleValidator()
        self._validator_peak_height.setBottom(0.01)

        self.tbl_elines = QTableWidget()
        self.tbl_elines.setStyleSheet(
            "QTableWidget::item{color: black;}"
            "QTableWidget::item:selected{background-color: red;}"
            "QTableWidget::item:selected{color: white;}")

        self.tbl_labels = [
            "", "Z", "Line", "E, keV", "Peak Int.", "Rel. Int.(%)", "CS"
        ]
        self.tbl_cols_editable = ["Peak Int."]
        self.tbl_value_min = {"Rel. Int.(%)": 0.1}
        tbl_cols_resize_to_content = ["", "Z", "Line"]

        self.tbl_elines.setColumnCount(len(self.tbl_labels))
        self.tbl_elines.verticalHeader().hide()
        self.tbl_elines.setHorizontalHeaderLabels(self.tbl_labels)

        self.tbl_elines.setSelectionBehavior(QTableWidget.SelectRows)
        self.tbl_elines.setSelectionMode(QTableWidget.SingleSelection)
        self.tbl_elines.itemSelectionChanged.connect(
            self.tbl_elines_item_selection_changed)
        self.tbl_elines.itemChanged.connect(self.tbl_elines_item_changed)

        header = self.tbl_elines.horizontalHeader()
        for n, lbl in enumerate(self.tbl_labels):
            # Set stretching for the columns
            if lbl in tbl_cols_resize_to_content:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)
            else:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            # Set alignment for the columns headers (HEADERS only)
            if n == 0:
                header.setDefaultAlignment(Qt.AlignCenter)
            else:
                header.setDefaultAlignment(Qt.AlignRight)

        self.cb_sel_list = []  # List of checkboxes

    def _setup_action_buttons(self):
        self.pb_remove_rel = QPushButton("Remove Rel.Int.(%) <")
        self.pb_remove_rel.clicked.connect(self.pb_remove_rel_clicked)

        self.le_remove_rel = LineEditExtended("")
        self._validator_le_remove_rel = QDoubleValidator()
        self._validator_le_remove_rel.setBottom(0.01)  # Some small number
        self._validator_le_remove_rel.setTop(100.0)
        self.le_remove_rel.setText(
            self._format_threshold(self._remove_peak_threshold))
        self._update_le_remove_rel_state()
        self.le_remove_rel.textChanged.connect(self.le_remove_rel_text_changed)
        self.le_remove_rel.editingFinished.connect(
            self.le_remove_rel_editing_finished)

        self.pb_remove_unchecked = QPushButton("Remove Unchecked Lines")
        self.pb_remove_unchecked.clicked.connect(
            self.pb_remove_unchecked_clicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_remove_rel)
        hbox.addWidget(self.le_remove_rel)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_remove_unchecked)

        return hbox

    def _set_tooltips(self):
        set_tooltip(self.cb_select_all,
                    "<b>Select/Deselect All</b> emission lines in the list")
        set_tooltip(self.element_selection, "<b>Set active</b> emission line")
        set_tooltip(self.le_peak_intensity,
                    "Set or modify <b>intensity</b> of the active peak.")
        set_tooltip(self.pb_add_eline, "<b>Add</b> emission line to the list.")
        set_tooltip(self.pb_remove_eline,
                    "<b>Remove</b> emission line from the list.")
        set_tooltip(
            self.pb_user_peaks,
            "Open dialog box to add or modify parameters of the <b>user-defined peak</b>"
        )
        set_tooltip(
            self.pb_pileup_peaks,
            "Open dialog box to add or modify parameters of the <b>pileup peak</b>"
        )

        set_tooltip(self.tbl_elines,
                    "The list of the selected <b>emission lines</b>")

        # set_tooltip(self.pb_update,
        #             "Update the internally stored list of selected emission lines "
        #             "and their parameters. This button is <b>deprecated</b>, but still may be "
        #             "needed in some situations. In future releases it will be <b>removed</b> or replaced "
        #             "with 'Accept' button. Substantial changes to the computational code is needed before "
        #             "it happens.")
        # set_tooltip(self.pb_undo,
        #             "<b>Undo</b> changes to the table of selected emission lines. Doesn't always work.")
        set_tooltip(
            self.pb_remove_rel,
            "<b>Remove emission lines</b> from the list if their relative intensity is less "
            "then specified threshold.",
        )
        set_tooltip(
            self.le_remove_rel,
            "<b>Threshold</b> that controls which emission lines are removed "
            "when <b>Remove Rel.Int.(%)</b> button is pressed.",
        )
        set_tooltip(self.pb_remove_unchecked,
                    "Remove <b>unchecked</b> emission lines from the list.")

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        # Hide the window if required by the program state
        state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"]
        state_model_exist = self.gui_vars["gui_state"]["state_model_exists"]
        if not state_file_loaded or not state_model_exist:
            self.hide()

        if condition == "tooltips":
            self._set_tooltips()

    def fill_eline_table(self, table_contents):
        self._table_contents = copy.deepcopy(table_contents)

        self._enable_events = False

        self.tbl_elines.clearContents()

        # Clear the list of checkboxes
        for cb in self.cb_sel_list:
            cb.stateChanged.connect(self.cb_eline_state_changed)
        self.cb_sel_list = []

        self.tbl_elines.setRowCount(len(table_contents))
        for nr, row in enumerate(table_contents):
            sel_status = row["sel_status"]
            row_data = [
                None, row["z"], row["eline"], row["energy"], row["peak_int"],
                row["rel_int"], row["cs"]
            ]

            for nc, entry in enumerate(row_data):

                label = self.tbl_labels[nc]

                # Set alternating background colors for the table rows
                #   Make background for editable items a little brighter
                brightness = 240 if label in self.tbl_cols_editable else 220
                if nr % 2:
                    rgb_bckg = (255, brightness, brightness)  # Light-red
                else:
                    rgb_bckg = (brightness, 255, brightness)  # Light-green

                if nc == 0:
                    item = QWidget()
                    cb = CheckBoxNamed(name=f"{nr}")
                    item_hbox = QHBoxLayout(item)
                    item_hbox.addWidget(cb)
                    item_hbox.setAlignment(Qt.AlignCenter)
                    item_hbox.setContentsMargins(0, 0, 0, 0)

                    css1 = get_background_css(rgb_bckg,
                                              widget="QCheckbox",
                                              editable=False)
                    css2 = get_background_css(rgb_bckg,
                                              widget="QWidget",
                                              editable=False)
                    item.setStyleSheet(css2 + css1)

                    cb.setChecked(Qt.Checked if sel_status else Qt.Unchecked)
                    cb.stateChanged.connect(self.cb_eline_state_changed)
                    cb.setStyleSheet("QCheckBox {color: black;}")
                    self.cb_sel_list.append(cb)

                    self.tbl_elines.setCellWidget(nr, nc, item)
                else:

                    s = None
                    # The case when the value (Rel. Int.) is limited from the bottom
                    #   We don't want to print very small numbers here
                    if label in self.tbl_value_min:
                        v = self.tbl_value_min[label]
                        if isinstance(entry,
                                      (float, np.float64)) and (entry < v):
                            s = f"<{v:.2f}"
                    if s is None:
                        if isinstance(entry, (float, np.float64)):
                            s = f"{entry:.2f}" if entry else "-"
                        else:
                            s = f"{entry}" if entry else "-"

                    item = QTableWidgetItem(s)

                    item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)

                    # Set all columns not editable (unless needed)
                    if label not in self.tbl_cols_editable:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

                    brush = QBrush(QColor(*rgb_bckg))
                    item.setBackground(brush)

                    self.tbl_elines.setItem(nr, nc, item)

        self._enable_events = True
        # Update the rest of the widgets
        self._update_widgets_based_on_table_state()

    @Slot()
    def update_widget_data(self):
        # This is typically a new set of emission lines. Clear the selection both
        #   in the table and in the element selection tool.
        self.element_selection.set_current_item("")
        self.tbl_elines.clearSelection()
        self._set_selected_eline("")
        # Now update the tables
        self._update_eline_selection_list()
        self.update_eline_table()
        self._update_add_remove_btn_state()

    def pb_pileup_peaks_clicked(self):
        data = {}

        eline = self._selected_eline
        if self.gpc.get_eline_name_category(eline) == "pileup":
            logger.error(
                f"Attempt to add pileup peak '{eline}' while another pileup peak is selected."
            )
            return

        energy, marker_visible = self.gpc.get_suggested_manual_peak_energy()
        best_guess = self.gpc.get_guessed_pileup_peak_components(energy=energy,
                                                                 tolerance=0.1)
        if best_guess is not None:
            el1, el2, energy = best_guess
        else:
            # No peaks were found, enter peaks manually
            el1, el2, energy = "", "", 0
        data["element1"] = el1
        data["element2"] = el2
        data["energy"] = energy
        data["range_low"], data[
            "range_high"] = self.gpc.get_selected_energy_range()

        if not marker_visible:
            # We shouldn't end up here, but this will protect from crashing in case
            #   the button was not disabled (a bug).
            msg = "Select location of the new peak center (energy)\nby clicking on the plot in 'Fit Model' tab"
            msgbox = QMessageBox(QMessageBox.Information,
                                 "User Input Required",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        else:
            dlg = DialogPileupPeakParameters()

            def func():
                def f(e1, e2):
                    try:
                        name = self.gpc.generate_pileup_peak_name(e1, e2)
                        e = self.gpc.get_pileup_peak_energy(name)
                    except Exception:
                        e = 0
                    return e

                return f

            dlg.set_compute_energy_function(func())
            dlg.set_parameters(data)
            if dlg.exec():
                print("Pileup peak is added")
                try:
                    data = dlg.get_parameters()
                    eline1, eline2 = data["element1"], data["element2"]
                    eline = self.gpc.generate_pileup_peak_name(eline1, eline2)
                    self.gpc.add_peak_manual(eline)
                    self.update_eline_table()  # Update the table
                    self.tbl_elines_set_selection(
                        eline)  # Select new emission line
                    self._set_selected_eline(eline)
                    self._set_fit_status(False)
                    logger.info(f"New pileup peak {eline} was added")
                except RuntimeError as ex:
                    msg = str(ex)
                    msgbox = QMessageBox(QMessageBox.Critical,
                                         "Error",
                                         msg,
                                         QMessageBox.Ok,
                                         parent=self)
                    msgbox.exec()
                    # Reload the table anyway (nothing is going to be selected)
                    self.update_eline_table()

    def pb_user_peaks_clicked(self):
        eline = self._selected_eline
        # If current peak is user_defined peak
        is_userpeak = self.gpc.get_eline_name_category(eline) == "userpeak"

        if is_userpeak:
            data = {}
            data["enabled"] = True
            data["name"] = eline
            data["maxv"] = self.gpc.get_eline_intensity(eline)
            data["energy"], data[
                "fwhm"] = self.gpc.get_current_userpeak_energy_fwhm()

            dlg = DialogEditUserPeakParameters()
            dlg.set_parameters(data=data)
            if dlg.exec():
                print("Editing of user defined peak is completed")
                try:
                    eline = data["name"]
                    data = dlg.get_parameters()
                    self.gpc.update_userpeak(data["name"], data["energy"],
                                             data["maxv"], data["fwhm"])
                    self._set_fit_status(False)
                    logger.info(f"User defined peak {eline} was updated.")
                except Exception as ex:
                    msg = str(ex)
                    msgbox = QMessageBox(QMessageBox.Critical,
                                         "Error",
                                         msg,
                                         QMessageBox.Ok,
                                         parent=self)
                    msgbox.exec()
                # Reload the table anyway (nothing is going to be selected)
                self.update_eline_table()

        else:
            data = {}
            data["name"] = self.gpc.get_unused_userpeak_name()
            data[
                "energy"], marker_visible = self.gpc.get_suggested_manual_peak_energy(
                )
            if marker_visible:
                dlg = DialogNewUserPeak()
                dlg.set_parameters(data=data)
                if dlg.exec():
                    try:
                        eline = data["name"]
                        self.gpc.add_peak_manual(eline)
                        self.update_eline_table()  # Update the table
                        self.tbl_elines_set_selection(
                            eline)  # Select new emission line
                        self._set_selected_eline(eline)
                        self._set_fit_status(False)
                        logger.info(f"New user defined peak {eline} is added")
                    except RuntimeError as ex:
                        msg = str(ex)
                        msgbox = QMessageBox(QMessageBox.Critical,
                                             "Error",
                                             msg,
                                             QMessageBox.Ok,
                                             parent=self)
                        msgbox.exec()
                        # Reload the table anyway (nothing is going to be selected)
                        self.update_eline_table()
            else:
                msg = ("Select location of the new peak center (energy)\n"
                       "by clicking on the plot in 'Fit Model' tab")
                msgbox = QMessageBox(QMessageBox.Information,
                                     "User Input Required",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

    @Slot()
    def pb_add_eline_clicked(self):
        logger.debug("'Add line' clicked")
        # It is assumed that this button is active only if an element is selected from the list
        #   of available emission lines. It can't be used to add user-defined peaks or pileup peaks.
        eline = self._selected_eline
        if eline:
            try:
                self.gpc.add_peak_manual(eline)
                self.update_eline_table()  # Update the table
                self.tbl_elines_set_selection(
                    eline)  # Select new emission line
                self._set_fit_status(False)
            except RuntimeError as ex:
                msg = str(ex)
                msgbox = QMessageBox(QMessageBox.Critical,
                                     "Error",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()
                # Reload the table anyway (nothing is going to be selected)
                self.update_eline_table()

    @Slot()
    def pb_remove_eline_clicked(self):
        logger.debug("'Remove line' clicked")
        eline = self._selected_eline
        if eline:
            # If currently selected line is the emission line (like Ca_K), we want
            # it to remain selected after it is deleted. This means that nothing is selected
            # in the table. For other lines, nothing should remain selected.
            self.tbl_elines.clearSelection()
            self.gpc.remove_peak_manual(eline)
            self.update_eline_table()  # Update the table
            if self.gpc.get_eline_name_category(eline) != "eline":
                eline = ""
            # This will update widgets
            self._set_selected_eline(eline)
            self._set_fit_status(False)

    def cb_select_all_toggled(self, state):
        self._enable_events = False

        eline_list, state_list = [], []

        for n_row in range(self.tbl_elines.rowCount()):
            eline = self._table_contents[n_row]["eline"]
            # Do not deselect lines in category 'other'. They probably never to be deleted.
            # They also could be deselected manually.
            if self.gpc.get_eline_name_category(
                    eline) == "other" and not state:
                to_check = True
            else:
                to_check = state
            self.cb_sel_list[n_row].setChecked(
                Qt.Checked if to_check else Qt.Unchecked)
            eline_list.append(eline)
            state_list.append(to_check)

        self.gpc.set_checked_emission_lines(eline_list, state_list)
        self._set_fit_status(False)
        self._enable_events = True

    def cb_eline_state_changed(self, name, state):
        if self._enable_events:
            n_row = int(name)
            state = state == Qt.Checked
            eline = self._table_contents[n_row]["eline"]
            self.gpc.set_checked_emission_lines([eline], [state])
            self._set_fit_status(False)

    def tbl_elines_item_changed(self, item):
        if self._enable_events:
            n_row, n_col = self.tbl_elines.row(item), self.tbl_elines.column(
                item)
            # Value was changed
            if n_col == 4:
                text = item.text()
                eline = self._table_contents[n_row]["eline"]
                if self._validator_peak_height.validate(
                        text, 0)[0] != QDoubleValidator.Acceptable:
                    val = self._table_contents[n_row]["peak_int"]
                    self._enable_events = False
                    item.setText(f"{val:.2f}")
                    self._enable_events = True
                    self._set_fit_status(False)
                else:
                    self.gpc.update_eline_peak_height(eline, float(text))
                    self.update_eline_table()

    def tbl_elines_item_selection_changed(self):
        sel_ranges = self.tbl_elines.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            index = sel_ranges[0].topRow()
            eline = self._table_contents[index]["eline"]
            if self._enable_events:
                self._enable_events = False
                self._set_selected_eline(eline)
                self.element_selection.set_current_item(eline)
                self._enable_events = True

    def tbl_elines_set_selection(self, eline):
        """
        Select the row with emission line `eline` in the table. Deselect everything if
        the emission line does not exist.
        """
        index = self._get_eline_index_in_table(eline)
        self.tbl_elines.clearSelection()
        if index >= 0:
            self.tbl_elines.selectRow(index)

    def element_selection_item_changed(self, index, eline):
        self.signal_selected_element_changed.emit(eline)

        if self._enable_events:
            self._enable_events = False
            self._set_selected_eline(eline)
            self.tbl_elines_set_selection(eline)
            self._enable_events = True

    def pb_remove_rel_clicked(self):
        try:
            self.gpc.remove_peaks_below_threshold(self._remove_peak_threshold)
        except Exception as ex:
            msg = str(ex)
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        self.update_eline_table()
        # Update the displayed estimated peak amplitude value 'le_peak_intensity'
        self._set_selected_eline(self._selected_eline)
        self._set_fit_status(False)

    def le_remove_rel_text_changed(self, text):
        self._update_le_remove_rel_state(text)

    def le_remove_rel_editing_finished(self):
        text = self.le_remove_rel.text()
        if self._validator_le_remove_rel.validate(
                text, 0)[0] == QDoubleValidator.Acceptable:
            self._remove_peak_threshold = float(text)
        else:
            self.le_remove_rel.setText(
                self._format_threshold(self._remove_peak_threshold))

    def pb_remove_unchecked_clicked(self):
        try:
            self.gpc.remove_unchecked_peaks()
        except Exception as ex:
            msg = str(ex)
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        # Reload the table
        self.update_eline_table()
        # Update the displayed estimated peak amplitude value 'le_peak_intensity'
        self._set_selected_eline(self._selected_eline)
        self._set_fit_status(False)

    def _display_peak_intensity(self, eline):
        v = self.gpc.get_eline_intensity(eline)
        s = f"{v:.10g}" if v is not None else ""
        self.le_peak_intensity.setText(s)

    def _update_le_remove_rel_state(self, text=None):
        if text is None:
            text = self.le_remove_rel.text()
        state = self._validator_le_remove_rel.validate(
            text, 0)[0] == QDoubleValidator.Acceptable
        self.le_remove_rel.setValid(state)
        self.pb_remove_rel.setEnabled(state)

    @Slot(str)
    def slot_selection_item_changed(self, eline):
        self.element_selection.set_current_item(eline)

    @Slot(bool)
    def slot_marker_state_changed(self, state):
        # If userpeak is selected and plot is clicked (marker is set), then user
        #   should be allowed to add userpeak at a new location. So deselect the userpeak
        #   from the table (if it is selected)
        logger.debug(
            f"Vertical marker on the fit plot changed state to {state}.")
        if state:
            self._deselect_special_peak_in_table()
        # Now update state of all buttons
        self._update_add_remove_btn_state()
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _format_threshold(self, value):
        return f"{value:.2f}"

    def _deselect_special_peak_in_table(self):
        """Deselect userpeak if a userpeak is selected"""
        if self.gpc.get_eline_name_category(
                self._selected_eline) in ("userpeak", "pileup"):
            # Clear all selections
            self.tbl_elines_set_selection("")
            self._set_selected_eline("")
            # We also want to show marker at the new position
            self.gpc.show_marker_at_current_position()

    def _update_widgets_based_on_table_state(self):
        index, eline = self._get_current_index_in_table()
        if index >= 0:
            # Selection exists. Update the state of element selection widget.
            self.element_selection.set_current_item(eline)
        else:
            # No selection, update the state based on element selection widget.
            eline = self._selected_eline
            self.tbl_elines_set_selection(eline)
        self._update_add_remove_btn_state(eline)
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _update_eline_selection_list(self):
        self._eline_list = self.gpc.get_full_eline_list()
        self.element_selection.set_item_list(self._eline_list)
        self.signal_update_element_selection_list.emit()

    @Slot()
    def update_eline_table(self):
        """Update table of emission lines without changing anything else"""
        eline_table = self.gpc.get_selected_eline_table()
        self.fill_eline_table(eline_table)

    def _get_eline_index_in_table(self, eline):
        try:
            index = [_["eline"] for _ in self._table_contents].index(eline)
        except ValueError:
            index = -1
        return index

    def _get_eline_index_in_list(self, eline):
        try:
            index = self._eline_list.index(eline)
        except ValueError:
            index = -1
        return index

    def _get_current_index_in_table(self):
        sel_ranges = self.tbl_elines.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            index = sel_ranges[0].topRow()
            eline = self._table_contents[index]["eline"]
        else:
            index, eline = -1, ""
        return index, eline

    def _get_current_index_in_list(self):
        index, eline = self.element_selection.get_current_item()
        return index, eline

    def _update_add_remove_btn_state(self, eline=None):
        if eline is None:
            index_in_table, eline = self._get_current_index_in_table()
            index_in_list, eline = self._get_current_index_in_list()
        else:
            index_in_table = self._get_eline_index_in_table(eline)
            index_in_list = self._get_eline_index_in_list(eline)
        add_enabled, remove_enabled = True, True
        if index_in_list < 0 and index_in_table < 0:
            add_enabled, remove_enabled = False, False
        else:
            if index_in_table >= 0:
                if self.gpc.get_eline_name_category(eline) != "other":
                    add_enabled = False
                else:
                    add_enabled, remove_enabled = False, False
            else:
                remove_enabled = False
        self.pb_add_eline.setEnabled(add_enabled)
        self.pb_remove_eline.setEnabled(remove_enabled)
        self.signal_update_add_remove_btn_state.emit(add_enabled,
                                                     remove_enabled)

    def _update_add_edit_userpeak_btn_state(self):

        enabled = True
        add_peak = True
        if self.gpc.get_eline_name_category(
                self._selected_eline) == "userpeak":
            add_peak = False

        # Finally check if marker is set (you need it for adding peaks)
        _, marker_set = self.gpc.get_suggested_manual_peak_energy()

        if not marker_set and add_peak:
            enabled = False

        if add_peak:
            btn_text = "New User Peak ..."
        else:
            btn_text = "Edit User Peak ..."
        self.pb_user_peaks.setText(btn_text)
        self.pb_user_peaks.setEnabled(enabled)

    def _update_add_edit_pileup_peak_btn_state(self):

        enabled = True
        if self.gpc.get_eline_name_category(self._selected_eline) == "pileup":
            enabled = False

        # Finally check if marker is set (you need it for adding peaks)
        _, marker_set = self.gpc.get_suggested_manual_peak_energy()
        # Ignore set marker for userpeaks (marker is used to display location of userpeaks)
        if self.gpc.get_eline_name_category(
                self._selected_eline) == "userpeak":
            marker_set = False

        if not marker_set:
            enabled = False

        self.pb_pileup_peaks.setEnabled(enabled)

    def _set_selected_eline(self, eline):
        self._update_add_remove_btn_state(eline)
        if eline != self._selected_eline:
            self._selected_eline = eline
            self.gpc.set_selected_eline(eline)
            self._display_peak_intensity(eline)
        else:
            # Peak intensity may change in some circumstances, so renew the displayed value.
            self._display_peak_intensity(eline)
        # Update button states after 'self._selected_eline' is set
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _set_fit_status(self, status):
        self.gui_vars["gui_state"]["state_model_fit_exists"] = status
        self.signal_parameters_changed.emit()
Esempio n. 23
0
class ShortcutEditor(QWidget):
    """Widget to edit keybindings for napari."""

    valueChanged = Signal(dict)
    VIEWER_KEYBINDINGS = trans._('Viewer key bindings')

    def __init__(
        self,
        parent: QWidget = None,
        description: str = "",
        value: dict = None,
    ):

        super().__init__(parent=parent)

        # Flag to not run _set_keybinding method after setting special symbols.
        # When changing line edit to special symbols, the _set_keybinding
        # method will be called again (and breaks) and is not needed.
        self._skip = False

        layers = [
            Image,
            Labels,
            Points,
            Shapes,
            Surface,
            Vectors,
        ]

        self.key_bindings_strs = OrderedDict()

        # widgets
        self.layer_combo_box = QComboBox(self)
        self._label = QLabel(self)
        self._table = QTableWidget(self)
        self._table.setSelectionBehavior(QAbstractItemView.SelectItems)
        self._table.setSelectionMode(QAbstractItemView.SingleSelection)
        self._table.setShowGrid(False)
        self._restore_button = QPushButton(trans._("Reset All Keybindings"))

        # Set up dictionary for layers and associated actions.
        all_actions = action_manager._actions.copy()
        self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = []

        for layer in layers:
            if len(layer.class_keymap) == 0:
                actions = []
            else:
                actions = action_manager._get_layer_actions(layer)
                for name, action in actions.items():
                    all_actions.pop(name)
            self.key_bindings_strs[f"{layer.__name__} layer"] = actions

        # Left over actions can go here.
        self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = all_actions

        # Widget set up
        self.layer_combo_box.addItems(list(self.key_bindings_strs))
        self.layer_combo_box.activated[str].connect(self._set_table)
        self.layer_combo_box.setCurrentText(self.VIEWER_KEYBINDINGS)
        self._set_table()
        self._label.setText("Group")
        self._restore_button.clicked.connect(self.restore_defaults)

        # layout
        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self._label)
        hlayout1.addWidget(self.layer_combo_box)
        hlayout1.setContentsMargins(0, 0, 0, 0)
        hlayout1.setSpacing(20)
        hlayout1.addStretch(0)

        hlayout2 = QHBoxLayout()
        hlayout2.addLayout(hlayout1)
        hlayout2.addWidget(self._restore_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout2)
        layout.addWidget(self._table)

        self.setLayout(layout)

    def restore_defaults(self):
        """Launches dialog to confirm restore choice."""

        self._reset_dialog = ConfirmDialog(
            parent=self,
            text=trans._(
                "Are you sure you want to restore default shortcuts?"
            ),
        )
        self._reset_dialog.valueChanged.connect(self._reset_shortcuts)
        self._reset_dialog.exec_()

    def _reset_shortcuts(self, event=None):
        """Reset shortcuts to default settings.

        Parameters
        ----------
        event: Bool
            Event will indicate whether user confirmed resetting shortcuts.
        """

        # event is True if the user confirmed reset shortcuts
        if event is True:
            get_settings().reset(sections=['shortcuts'])
            for (
                action,
                shortcuts,
            ) in get_settings().shortcuts.shortcuts.items():
                action_manager.unbind_shortcut(action)
                for shortcut in shortcuts:
                    action_manager.bind_shortcut(action, shortcut)

            self._set_table(layer_str=self.layer_combo_box.currentText())

    def _set_table(self, layer_str=''):
        """Builds and populates keybindings table.

        Parameters
        ----------
        layer_str = str
            If layer_str is not empty, then show the specified layers'
            keybinding shortcut table.
        """

        # Keep track of what is in each column.
        self._action_name_col = 0
        self._icon_col = 1
        self._shortcut_col = 2
        self._action_col = 3

        # Set header strings for table.
        header_strs = ['', '', '', '']
        header_strs[self._action_name_col] = trans._('Action')
        header_strs[self._shortcut_col] = trans._('Keybinding')

        # If no layer_str, then set the page to the viewer keybindings page.
        if layer_str == '':
            layer_str = self.VIEWER_KEYBINDINGS

        # If rebuilding the table, then need to disconnect the connection made
        # previously as well as clear the table contents.
        try:
            self._table.cellChanged.disconnect(self._set_keybinding)
        except TypeError:
            # if building the first time, the cells are not yet connected so this would fail.
            pass
        except RuntimeError:
            # Needed to pass some tests.
            pass

        self._table.clearContents()

        # Table styling set up.
        self._table.horizontalHeader().setStretchLastSection(True)
        self._table.horizontalHeader().setStyleSheet(
            'border-bottom: 2px solid white;'
        )

        # Get all actions for the layer.
        actions = self.key_bindings_strs[layer_str]

        if len(actions) > 0:

            # Set up table based on number of actions and needed columns.
            self._table.setRowCount(len(actions))
            self._table.setColumnCount(4)
            # Set up delegate in order to capture keybindings.
            self._table.setItemDelegateForColumn(
                self._shortcut_col, ShortcutDelegate(self._table)
            )
            self._table.setHorizontalHeaderLabels(header_strs)
            self._table.verticalHeader().setVisible(False)

            # Hide the column with action names.  These are kept here for reference when needed.
            self._table.setColumnHidden(self._action_col, True)

            # Column set up.
            self._table.setColumnWidth(self._action_name_col, 250)
            self._table.setColumnWidth(self._shortcut_col, 200)
            self._table.setColumnWidth(self._icon_col, 50)

            # Go through all the actions in the layer and add them to the table.
            for row, (action_name, action) in enumerate(actions.items()):

                shortcuts = action_manager._shortcuts.get(action_name, [])
                # Set action description.  Make sure its not selectable/editable.
                item = QTableWidgetItem(action.description)
                item.setFlags(Qt.NoItemFlags)
                self._table.setItem(row, self._action_name_col, item)

                # Create empty item in order to make sure this column is not
                # selectable/editable.
                item = QTableWidgetItem("")
                item.setFlags(Qt.NoItemFlags)
                self._table.setItem(row, self._icon_col, item)

                # Set the shortcuts in table.
                item_shortcut = QTableWidgetItem(
                    Shortcut(list(shortcuts)[0]).platform if shortcuts else ""
                )
                self._table.setItem(row, self._shortcut_col, item_shortcut)

                # action_name is stored in the table to use later, but is not shown on dialog.
                item_action = QTableWidgetItem(action_name)
                self._table.setItem(row, self._action_col, item_action)

            # If a cell is changed, run .set_keybinding.
            self._table.cellChanged.connect(self._set_keybinding)
        else:
            # Display that there are no actions for this layer.
            self._table.setRowCount(1)
            self._table.setColumnCount(4)
            self._table.setHorizontalHeaderLabels(header_strs)
            self._table.verticalHeader().setVisible(False)

            self._table.setColumnHidden(self._action_col, True)
            item = QTableWidgetItem('No key bindings')
            item.setFlags(Qt.NoItemFlags)
            self._table.setItem(0, 0, item)

    def _set_keybinding(self, row, col):
        """Checks the new keybinding to determine if it can be set.

        Parameters
        ----------
        row: int
            Row in keybindings table that is being edited.
        col: int
            Column being edited (shortcut column).
        """

        if self._skip is True:
            # Do nothing if the text is setting to a symbol.
            # Its already been handled.
            self._skip = False
            return

        if col == self._shortcut_col:
            # Get all layer actions and viewer actions in order to determine
            # the new shortcut is not already set to an action.

            current_layer_text = self.layer_combo_box.currentText()
            layer_actions = self.key_bindings_strs[current_layer_text]
            actions_all = layer_actions.copy()
            if current_layer_text is not self.VIEWER_KEYBINDINGS:
                viewer_actions = self.key_bindings_strs[
                    self.VIEWER_KEYBINDINGS
                ]

                actions_all.update(viewer_actions)

            # get the current item from shortcuts column
            current_item = self._table.currentItem()
            new_shortcut = current_item.text()
            new_shortcut = new_shortcut[0].upper() + new_shortcut[1:]

            # get the current action name
            current_action = self._table.item(row, self._action_col).text()

            # get the original shortcutS
            current_shortcuts = list(
                action_manager._shortcuts.get(current_action, {})
            )

            # Flag to indicate whether to set the new shortcut.
            replace = True
            # Go through all layer actions to determine if the new shortcut is already here.
            for row1, (action_name, action) in enumerate(actions_all.items()):
                shortcuts = action_manager._shortcuts.get(action_name, [])

                if new_shortcut in shortcuts:
                    # Shortcut is here (either same action or not), don't replace in settings.
                    replace = False
                    if action_name != current_action:
                        # the shortcut is saved to a different action

                        # show warning symbols
                        self._show_warning_icons([row, row1])

                        # show warning message
                        message = trans._(
                            "The keybinding <b>{new_shortcut}</b>  "
                            + "is already assigned to <b>{action_description}</b>; change or clear "
                            + "that shortcut before assigning <b>{new_shortcut}</b> to this one.",
                            new_shortcut=new_shortcut,
                            action_description=action.description,
                        )
                        self._show_warning(new_shortcut, action, row, message)

                        if len(current_shortcuts) > 0:
                            # If there was a shortcut set originally, then format it and reset the text.
                            format_shortcut = Shortcut(
                                current_shortcuts[0]
                            ).platform

                            if format_shortcut != current_shortcuts[0]:
                                # only skip the next round if there are special symbols
                                self._skip = True

                            current_item.setText(format_shortcut)

                        else:
                            # There wasn't a shortcut here.
                            current_item.setText("")

                        self._cleanup_warning_icons([row, row1])

                        break

                    else:
                        # This shortcut was here.  Reformat and reset text.
                        format_shortcut = Shortcut(new_shortcut).platform
                        if format_shortcut != new_shortcut:
                            # Only skip the next round if there are special symbols in shortcut.
                            self._skip = True

                        current_item.setText(format_shortcut)

            if replace is True:
                # This shortcut is not taken.

                #  Unbind current action from shortcuts in action manager.
                action_manager.unbind_shortcut(current_action)

                if new_shortcut != "":
                    # Bind the new shortcut.
                    try:
                        action_manager.bind_shortcut(
                            current_action, new_shortcut
                        )
                    except TypeError:
                        # Shortcut is not valid.
                        action_manager._shortcuts[current_action] = set()
                        # need to rebind the old shortcut
                        action_manager.unbind_shortcut(current_action)
                        action_manager.bind_shortcut(
                            current_action, current_shortcuts[0]
                        )

                        # Show warning message to let user know this shortcut is invalid.
                        self._show_warning_icons([row])

                        message = trans._(
                            "<b>{new_shortcut}</b> is not a valid keybinding.",
                            new_shortcut=new_shortcut,
                        )
                        self._show_warning(new_shortcut, action, row, message)

                        self._cleanup_warning_icons([row])

                        format_shortcut = Shortcut(
                            current_shortcuts[0]
                        ).platform
                        if format_shortcut != current_shortcuts[0]:
                            # Skip the next round if there are special symbols.
                            self._skip = True

                        # Update text to formated shortcut.
                        current_item.setText(format_shortcut)
                        return

                    # The new shortcut is valid and can be displayed in widget.

                    # Keep track of what changed.
                    new_value_dict = {current_action: [new_shortcut]}

                    # Format new shortcut.
                    format_shortcut = Shortcut(new_shortcut).platform
                    if format_shortcut != new_shortcut:
                        # Skip the next round because there are special symbols.
                        self._skip = True

                    # Update text to formated shortcut.
                    current_item.setText(format_shortcut)

                else:
                    # There is not a new shortcut to bind.  Keep track of it.
                    if action_manager._shortcuts[current_action] != "":
                        new_value_dict = {current_action: [""]}

                if new_value_dict:
                    # Emit signal when new value set for shortcut.
                    self.valueChanged.emit(new_value_dict)

    def _show_warning_icons(self, rows):
        """Creates and displays the warning icons.

        Parameters
        ----------
        rows: list[int]
            List of row numbers that should have the icon.
        """

        for row in rows:
            self.warning_indicator = QLabel(self)
            self.warning_indicator.setObjectName("error_label")

            self._table.setCellWidget(
                row, self._icon_col, self.warning_indicator
            )

    def _cleanup_warning_icons(self, rows):
        """Remove the warning icons from the shortcut table.

        Paramters
        ---------
        rows: list[int]
            List of row numbers to remove warning icon from.

        """
        for row in rows:
            self._table.setCellWidget(row, self._icon_col, QLabel(""))

    def _show_warning(self, new_shortcut='', action=None, row=0, message=''):
        """Creates and displays warning message when shortcut is already assigned.

        Parameters
        ----------
        new_shortcut: str
            The new shortcut attempting to be set.
        action: Action
            Action that is already assigned with the shortcut.
        row: int
            Row in table where the shortcut is attempting to be set.
        message: str
            Message to be displayed in warning pop up.
        """

        # Determine placement of warning message.
        delta_y = 105
        delta_x = 10
        global_point = self.mapToGlobal(
            QPoint(
                self._table.columnViewportPosition(self._shortcut_col)
                + delta_x,
                self._table.rowViewportPosition(row) + delta_y,
            )
        )

        # Create warning pop up and move it to desired position.
        self._warn_dialog = KeyBindWarnPopup(
            text=message,
        )
        self._warn_dialog.move(global_point)

        # Styling adjustments.
        self._warn_dialog.resize(250, self._warn_dialog.sizeHint().height())

        self._warn_dialog._message.resize(
            200, self._warn_dialog._message.sizeHint().height()
        )

        self._warn_dialog.exec_()

    def value(self):
        """Return the actions and shortcuts currently assigned in action manager.

        Returns
        -------
        value: dict

            Dictionary of action names and shortcuts assigned to them.
        """

        value = {}

        for row, (action_name, action) in enumerate(
            action_manager._actions.items()
        ):
            shortcuts = action_manager._shortcuts.get(action_name, [])
            value[action_name] = list(shortcuts)

        return value
class UI(QWidget):
    def __init__(self):
        self.__problem = Problem()
        self.__controller = Controller(self.__problem)
        super().__init__()
        #self.validation()

        self.initUI()

    def initUI(self):
        self.l0 = QLabel(self)
        self.l0.setText("1.EA;2.HC;3.PSO")
        self.l0.move(0, 210)

        self.le0 = QLineEdit(self)
        self.le0.move(150, 210)

        self.l1 = QLabel(self)
        self.l1.setText("Probability of mutation")
        self.l1.move(0, 0)

        self.le1 = QLineEdit(self)
        self.le1.move(150, 0)
        #
        self.l2 = QLabel(self)
        self.l2.setText("Population size")
        self.l2.move(0, 30)

        self.le2 = QLineEdit(self)
        self.le2.move(150, 30)
        #
        self.l3 = QLabel(self)
        self.l3.setText("Number of iterations")
        self.l3.move(0, 60)

        self.le3 = QLineEdit(self)
        self.le3.move(150, 60)
        #
        self.l4 = QLabel(self)
        self.l4.setText("Number of neighbours")
        self.l4.move(0, 90)

        self.le4 = QLineEdit(self)
        self.le4.move(150, 90)
        #
        self.l5 = QLabel(self)
        self.l5.setText("w")
        self.l5.move(0, 120)

        self.le5 = QLineEdit(self)
        self.le5.move(150, 120)
        #
        self.l6 = QLabel(self)
        self.l6.setText("c1")
        self.l6.move(0, 150)

        self.le6 = QLineEdit(self)
        self.le6.move(150, 150)
        #
        self.l7 = QLabel(self)
        self.l7.setText("c2")
        self.l7.move(0, 180)

        self.le7 = QLineEdit(self)
        self.le7.move(150, 180)

        self.qtable = QTableWidget(self)
        self.qtable.move(200, 300)
        self.qtable.setGeometry(200, 300, 600, 600)

        self.setWindowTitle('Input dialog')

        self.btn = QPushButton('Take arguments', self)
        self.btn.move(200, 250)
        self.btn.clicked.connect(self.showDialog)

        self.show()

    def showDialog(self):
        if isinstance(self.le1.text(), str) and self.le1.text() != '':
            pM = float(self.le1.text())
        else:
            pM = 0
        if isinstance(self.le2.text(), str) and self.le2.text() != '':
            dimPopulation = int(self.le2.text())
        else:
            dimPopulation = 20
        if isinstance(self.le3.text(), str) and self.le3.text() != '':
            noIteratii = int(self.le3.text())
        else:
            noIteratii = 1000
        if isinstance(self.le4.text(), str) and self.le4.text() != '':
            sizeOfNeighborhood = int(self.le4.text())
        else:
            sizeOfNeighborhood = 2
        if isinstance(self.le5.text(), str) and self.le5.text() != '':
            w = float(self.le5.text())
        else:
            w = 1
        if isinstance(self.le6.text(), str) and self.le6.text() != '':
            c1 = float(self.le6.text())
        else:
            c1 = 1
        if isinstance(self.le7.text(), str) and self.le7.text() != '':
            c2 = float(self.le7.text())
        else:
            c2 = 2.5

        if isinstance(self.le0.text(), str) and self.le0.text() != '':
            a = self.le0.text()
        else:
            a = 0

        self.run(a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w, c1,
                 c2)

    def printMatrix(self, array):
        self.qtable.setColumnCount(len(array[0]))  # rows and columns of table
        self.qtable.setRowCount(len(array[0]))
        for row in range(len(
                array[0])):  # add items from array to QTableWidget
            for column in range(len(array[0])):
                item = (array[0][row][column], array[1][row][column]
                        )  # each item is a QTableWidgetItem
                self.qtable.setItem(row, column, QTableWidgetItem(str(item)))

        self.qtable.show()

    def case1(self, a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
              c1, c2):
        dimIndividual = 4
        P = self.__problem.population(dimPopulation, dimIndividual, 0, 0)
        for i in range(noIteratii):
            P = self.__controller.iteration(P, pM, 0, 0)

        # print the best individual
        graded = [(self.__problem.fitness(x), x) for x in P]
        graded = sorted(graded)
        result = graded[0]
        fitnessOptim = result[0]
        individualOptim = result[1]
        self.printMatrix(individualOptim)

    def case2(self, a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
              c1, c2):
        dimIndividual = 4
        ind = self.__problem.individual(dimIndividual, 0, 0)
        res = self.__controller.hillClimb(ind)
        self.printMatrix(res)

    def case3(self, a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
              c1, c2):
        noParticles = dimPopulation
        # individual size
        dimParticle = 4
        # the boundries of the search interval
        vmin = -100
        vmax = -10
        # specific parameters for PSO
        w = 1.0
        c1 = 1.
        c2 = 2.5
        # sizeOfNeighborhood = 2
        P = self.__problem.populationForParticles(noParticles, dimParticle,
                                                  vmin, vmax)
        # we establish the particles' neighbors
        neighborhoods = self.__problem.selectNeighbors(P, sizeOfNeighborhood)

        for i in range(noIteratii):
            P = self.__controller.iterationForParticles(
                P, neighborhoods, c1, c2, w / (i + 1))

        # print the best individual
        best = 0
        for i in range(1, len(P)):
            if (P[i].fitness < P[best].fitness):
                best = i

        fitnessOptim = P[best].fitness
        individualOptim = P[best].pozition
        self.printMatrix(individualOptim)

    def run(self, a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w, c1,
            c2):  #ui
        dimIndividual = 4
        vmin = 0
        vmax = 0
        if a == "1":
            self.case1(a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
                       c1, c2)
        if a == "2":
            self.case2(a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
                       c1, c2)
        if a == "3":
            self.case3(a, pM, dimPopulation, noIteratii, sizeOfNeighborhood, w,
                       c1, c2)

    def validation(self):
        fitnessOptimForEA = []
        for i in range(30):
            dimIndividual = 4
            dimPopulation = 40
            noIteratii = 1000
            pM = 0.01
            P = self.__problem.population(dimPopulation, dimIndividual, 0, 0)

            for i in range(noIteratii):
                P = self.__controller.iteration(P, pM, 0, 0)

            graded = [(self.__problem.fitness(x), x) for x in P]
            graded = sorted(graded)
            result = graded[0]
            fitnessOptimForEA.append(result[0])

        fitnessOptimForHC = []
        for i in range(30):
            dimIndividual = 4
            ind = self.__problem.individual(dimIndividual, 0, 0)
            res = self.__controller.hillClimb(ind)
            fitnessOptimForHC.append(self.__problem.fitness(res))

        fitnessOptimForPSO = []
        for i in range(3):
            noIteratii = 1000
            noParticles = 40
            dimParticle = 4
            # the boundries of the search interval
            vmin = -100
            vmax = -10
            # specific parameters for PSO
            w = 1.0
            c1 = 1.
            c2 = 2.5
            sizeOfNeighborhood = 2
            P = self.__problem.populationForParticles(noParticles, dimParticle,
                                                      vmin, vmax)
            neighborhoods = self.__problem.selectNeighbors(
                P, sizeOfNeighborhood)
            for i in range(noIteratii):
                P = self.__controller.iterationForParticles(
                    P, neighborhoods, c1, c2, w / (i + 1))
            best = 0
            for i in range(1, len(P)):
                if (P[i].fitness < P[best].fitness):
                    best = i
            fitnessOptim = P[best].fitness
            fitnessOptimForPSO.append(fitnessOptim)

        plt.plot(fitnessOptimForEA)  # plotting by columns
        np1 = np.array(fitnessOptimForEA)
        std1 = np.std(np1)
        m1 = np.mean(np1)
        plt.xlabel("standard deviation= " + str(std1) + ";mean= " + str(m1) +
                   "            Trials")
        plt.ylabel("EA algorithm" + "              Fitness")
        plt.show()

        plt.plot(fitnessOptimForHC)  # plotting by columns
        np2 = np.array(fitnessOptimForHC)
        std2 = np.std(np2)
        m2 = np.mean(np2)
        plt.xlabel("standard deviation= " + str(std2) + ";mean= " + str(m2) +
                   "            Trials")
        plt.ylabel("HC algorithm" + "              Fitness")
        plt.show()

        plt.plot(fitnessOptimForPSO)  # plotting by columns
        np3 = np.array(fitnessOptimForPSO)
        std3 = np.std(np3)
        m3 = np.mean(np3)
        plt.xlabel("standard deviation= " + str(std3) + ";mean= " + str(m3) +
                   "            Trials")
        plt.ylabel("PSO algorithm" + "              Fitness")
        plt.show()
Esempio n. 25
0
class Devices(QFrame):
    def __init__(self, *args, **kwargs):
        super(Devices, self).__init__(*args, **kwargs)
        self.setFrameStyle(QFrame.Panel | QFrame.Raised)
        self.contentLayout = QGridLayout()

        self.devices = []

        # Current Action Status
        self.status = {}

        self.devicePrefixFilterLabel = QLabel(
            "Device filter (Ion Pump not the channel!)")
        self.devicePrefixFilterInp = QLineEdit()
        self.devicePrefixFilterInp.setMaximumWidth(100)

        self.updateDeviceListButton = QPushButton("Apply Filter")
        self.updateDeviceListButton.clicked.connect(self.updateDeviceList)
        self.updateDeviceListButton.setToolTip(
            "Filter the device prefix list.")

        self.contentLayout.addWidget(self.devicePrefixFilterLabel, 0, 0, 1, 2)
        self.contentLayout.addWidget(self.devicePrefixFilterInp, 1, 0, 1, 1)
        self.contentLayout.addWidget(self.updateDeviceListButton, 1, 1, 1, 1)

        self.deviceList = QListWidget()
        self.contentLayout.addWidget(self.deviceList, 2, 0, 2, 2)

        self.deviceStatus = QTableWidget()
        self.deviceStatus.setColumnCount(2)
        self.deviceStatus.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.deviceStatus.setHorizontalHeaderLabels(["Device", "Status"])
        self.deviceStatusLabel = QLabel("Status")

        self.contentLayout.addWidget(self.deviceStatusLabel, 0, 2, 1, 2)
        self.contentLayout.addWidget(self.deviceStatus, 1, 2, 4, 2)

        self.contentLayout.setRowStretch(2, 2)

        self.setLayout(self.contentLayout)

        self.deviceList.itemChanged.connect(self.highlightChecked)
        self.reloadData()

    def updateStatus(self, param):
        self.status[param["dev"]] = "{}".format(param["status"])
        idx = 0
        self.deviceStatus.setRowCount(len(self.status))
        for k, v in self.status.items():
            self.deviceStatus.setItem(idx, 0, QTableWidgetItem(k))
            self.deviceStatus.setItem(idx, 1, QTableWidgetItem(v))
            idx += 1

    def getSelectedDevices(self):
        checked_devices = []

        count = 0
        while count < self.deviceList.count():
            item = self.deviceList.item(count)
            if item.checkState() == Qt.Checked:
                checked_devices.append(item.text())
            count += 1

        _devices = []
        for prefix in checked_devices:
            for device in self.devices:
                if device["prefix"] == prefix:
                    _devices.append(device)
                    break
        return _devices

    def highlightChecked(self, item):
        if item.checkState() == Qt.Checked:
            item.setBackground(QColor("#ffffb2"))
        else:
            item.setBackground(QColor("#ffffff"))

    def updateDeviceList(self):
        self.deviceList.clear()

        _filter = self.devicePrefixFilterInp.text()
        devs = []
        for d in self.devices:
            if _filter == "" or _filter in d["prefix"]:
                devs.append(d["prefix"])

        self.deviceList.addItems(devs)

        count = 0
        while count < self.deviceList.count():
            item = self.deviceList.item(count)
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Unchecked)
            count += 1

    def reloadData(self):
        data = getAgilent()
        self.devices = [d for d in getDevices(data)]
        self.updateDeviceList()
class SimpleMeasurements(QWidget):
    def __init__(self, settings: StackSettings, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.calculate_btn = QPushButton("Calculate")
        self.calculate_btn.clicked.connect(self.calculate)
        self.result_view = QTableWidget()
        self.channel_select = ChannelComboBox()
        self.units_select = EnumComboBox(Units)
        self.units_select.set_value(self.settings.get("simple_measurements.units", Units.nm))
        self.units_select.currentIndexChanged.connect(self.change_units)

        layout = QHBoxLayout()
        self.measurement_layout = QVBoxLayout()
        l1 = QHBoxLayout()
        l1.addWidget(QLabel("Units"))
        l1.addWidget(self.units_select)
        self.measurement_layout.addLayout(l1)
        l2 = QHBoxLayout()
        l2.addWidget(QLabel("Channel"))
        l2.addWidget(self.channel_select)
        self.measurement_layout.addLayout(l2)
        layout.addLayout(self.measurement_layout)
        result_layout = QVBoxLayout()
        result_layout.addWidget(self.result_view)
        result_layout.addWidget(self.calculate_btn)
        layout.addLayout(result_layout)
        self.setLayout(layout)
        self.setWindowTitle("Measurement")
        if self.window() == self:
            try:
                geometry = self.settings.get_from_profile("simple_measurement_window_geometry")
                self.restoreGeometry(QByteArray.fromHex(bytes(geometry, "ascii")))
            except KeyError:
                pass

    def closeEvent(self, event: QCloseEvent) -> None:
        """
        Save geometry if widget is used as standalone window.
        """
        if self.window() == self:
            self.settings.set_in_profile(
                "simple_measurement_window_geometry", self.saveGeometry().toHex().data().decode("ascii")
            )
        super().closeEvent(event)

    def calculate(self):
        if self.settings.segmentation is None:
            QMessageBox.warning(self, "No segmentation", "need segmentation to work")
            return
        to_calculate = []
        for i in range(2, self.measurement_layout.count()):
            # noinspection PyTypeChecker
            chk: QCheckBox = self.measurement_layout.itemAt(i).widget()
            if chk.isChecked():
                leaf: Leaf = MEASUREMENT_DICT[chk.text()].get_starting_leaf()
                to_calculate.append(leaf.replace_(per_component=PerComponent.Yes, area=AreaType.ROI))
        if not to_calculate:
            QMessageBox.warning(self, "No measurement", "Select at least one measurement")
            return

        profile = MeasurementProfile("", [MeasurementEntry(x.name, x) for x in to_calculate])

        dial = ExecuteFunctionDialog(
            profile.calculate,
            kwargs={
                "channel": self.settings.image.get_channel(self.channel_select.get_value()),
                "segmentation": self.settings.segmentation,
                "mask": None,
                "voxel_size": self.settings.image.spacing,
                "result_units": self.units_select.get_value(),
            },
        )
        dial.exec()
        result: MeasurementResult = dial.get_result()
        values = result.get_separated()
        labels = result.get_labels()
        self.result_view.clear()
        self.result_view.setColumnCount(len(values) + 1)
        self.result_view.setRowCount(len(labels))
        for i, val in enumerate(labels):
            self.result_view.setItem(i, 0, QTableWidgetItem(val))
        for j, values_list in enumerate(values):
            for i, val in enumerate(values_list):
                self.result_view.setItem(i, j + 1, QTableWidgetItem(str(val)))

    def _clean_measurements(self):
        selected = set()
        for _ in range(self.measurement_layout.count() - 2):
            # noinspection PyTypeChecker
            chk: QCheckBox = self.measurement_layout.takeAt(2).widget()
            if chk.isChecked():
                selected.add(chk.text())
            chk.deleteLater()
        return selected

    def event(self, event: QEvent) -> bool:
        if event.type() == QEvent.WindowActivate:
            self.channel_select.change_channels_num(self.settings.image.channels)
            selected = self._clean_measurements()
            for val in MEASUREMENT_DICT.values():
                area = val.get_starting_leaf().area
                pc = val.get_starting_leaf().per_component
                if (
                    val.get_fields()
                    or (area is not None and area != AreaType.ROI)
                    or (pc is not None and pc != PerComponent.Yes)
                ):
                    continue
                text = val.get_name()
                chk = QCheckBox(text)
                if text in selected:
                    chk.setChecked(True)
                self.measurement_layout.addWidget(chk)
        return super().event(event)

    def change_units(self):
        self.settings.set("simple_measurements.units", self.units_select.get_value())
Esempio n. 27
0
class DialogSelectQuantStandard(QDialog):
    def __init__(self, parent=None):

        super().__init__(parent)

        self._qe_param_built_in = []
        self._qe_param_custom = []
        self._qe_standard_selected = None
        self._qe_param = []  # The list of all standards
        self._custom_label = []  # The list of booleans: True - the standard is custom, False -built-in

        self.setWindowTitle("Load Quantitative Standard")

        self.setMinimumHeight(500)
        self.setMinimumWidth(600)
        self.resize(600, 500)

        self.selected_standard_index = -1

        labels = ("C", "Serial #", "Name", "Description")
        col_stretch = (
            QHeaderView.ResizeToContents,
            QHeaderView.ResizeToContents,
            QHeaderView.ResizeToContents,
            QHeaderView.Stretch,
        )
        self.table = QTableWidget()
        set_tooltip(
            self.table,
            "To <b> load the sta"
            "ndard</b>, double-click on the table row or "
            "select the table row and then click <b>Ok</b> button.",
        )
        self.table.setMinimumHeight(200)
        self.table.setColumnCount(len(labels))
        self.table.verticalHeader().hide()
        self.table.setHorizontalHeaderLabels(labels)
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setSelectionMode(QTableWidget.SingleSelection)
        self.table.itemSelectionChanged.connect(self.item_selection_changed)
        self.table.itemDoubleClicked.connect(self.item_double_clicked)

        self.table.setStyleSheet(
            "QTableWidget::item{color: black;}"
            "QTableWidget::item:selected{background-color: red;}"
            "QTableWidget::item:selected{color: white;}"
        )

        header = self.table.horizontalHeader()
        for n, col_stretch in enumerate(col_stretch):
            # Set stretching for the columns
            header.setSectionResizeMode(n, col_stretch)

        self.lb_info = QLabel()
        self.lb_info.setText("Column 'C': * means that the standard is user-defined.")

        button_box = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
        button_box.button(QDialogButtonBox.Cancel).setDefault(True)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

        self.pb_open = button_box.button(QDialogButtonBox.Open)
        self.pb_open.setEnabled(False)

        vbox = QVBoxLayout()
        vbox.addWidget(self.table)
        vbox.addWidget(self.lb_info)
        vbox.addWidget(button_box)
        self.setLayout(vbox)

    def _fill_table(self, table_contents):
        self.table.setRowCount(len(table_contents))

        for nr, row in enumerate(table_contents):
            for nc, entry in enumerate(row):
                s = textwrap.fill(entry, width=40)
                item = QTableWidgetItem(s)
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                if not nc:
                    item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                self.table.setItem(nr, nc, item)
        self.table.resizeRowsToContents()

        brightness = 220
        for nr in range(self.table.rowCount()):
            for nc in range(self.table.columnCount()):
                self.table.item(nr, nc)
                if nr % 2:
                    color = QColor(255, brightness, brightness)
                else:
                    color = QColor(brightness, 255, brightness)
                self.table.item(nr, nc).setBackground(QBrush(color))

        try:
            index = self._qe_param.index(self._qe_standard_selected)
            self.selected_standard_index = index
            n_columns = self.table.columnCount()
            self.table.setRangeSelected(QTableWidgetSelectionRange(index, 0, index, n_columns - 1), True)
        except ValueError:
            pass

    def item_selection_changed(self):
        sel_ranges = self.table.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            self.selected_standard_index = sel_ranges[0].topRow()
            self.pb_open.setEnabled(True)
        else:
            self.selected_standard_index = -1
            self.pb_open.setEnabled(False)

    def item_double_clicked(self):
        self.accept()

    def set_standards(self, qe_param_built_in, qe_param_custom, qe_standard_selected):
        self._qe_standard_selected = qe_standard_selected
        self._qe_param = qe_param_custom + qe_param_built_in
        custom_label = [True] * len(qe_param_custom) + [False] * len(qe_param_built_in)

        table_contents = []
        for n, param in enumerate(self._qe_param):
            custom = "*" if custom_label[n] else ""
            serial = param["serial"]
            name = param["name"]
            description = param["description"]
            table_contents.append([custom, serial, name, description])

        self._fill_table(table_contents)

    def get_selected_standard(self):
        if self.selected_standard_index >= 0:
            return self._qe_param[self.selected_standard_index]
        else:
            return None