class DialogPlotEscapePeak(QDialog): def __init__(self): super().__init__() self.setWindowTitle("Escape Peak Settings") self.grp_show_escape_peak = QGroupBox("Show Escape Peak") self.grp_show_escape_peak.setCheckable(True) self.grp_show_escape_peak.setChecked(False) # Set based on data !!! self.le_incident_energy = LineEditReadOnly() set_tooltip( self.le_incident_energy, "<b>Incident energy</b>. Use <b>General...</b> button of <b>Model</b> tab " "to change the value if needed.", ) self.combo_detector_type = QComboBox() set_tooltip(self.combo_detector_type, "Select <b>detector</b> material. The typical choice is <b>Si</b>") self._detector_types = ["Si", "Ge"] self.combo_detector_type.addItems(self._detector_types) grid = QGridLayout() grid.addWidget(QLabel("Incident energy, kev:"), 0, 0) grid.addWidget(self.le_incident_energy, 0, 1) grid.addWidget(QLabel("Detectory type:"), 1, 0) grid.addWidget(self.combo_detector_type, 1, 1) self.grp_show_escape_peak.setLayout(grid) # Yes/No button box button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addWidget(self.grp_show_escape_peak) vbox.addWidget(button_box) self.setLayout(vbox) def set_parameters(self, plot_escape_peak, incident_energy, detector_material): index_material = -1 if detector_material in self._detector_types: index_material = self._detector_types.index(detector_material) self.combo_detector_type.setCurrentIndex(index_material) self.le_incident_energy.setText(f"{incident_energy:.10g}") self.grp_show_escape_peak.setChecked(plot_escape_peak) def get_parameters(self): plot_escape_peak = self.grp_show_escape_peak.isChecked() index = self.combo_detector_type.currentIndex() if index >= 0: detector_material = self._detector_types[self.combo_detector_type.currentIndex()] else: detector_material = "" return plot_escape_peak, detector_material
def create_groupbox(parent, title=None, toggled=None, checked=None, flat=False, layout=None): """Create a QGroupBox""" if title is None: group = QGroupBox(parent) else: group = QGroupBox(title, parent) group.setFlat(flat) if toggled is not None: group.setCheckable(True) if checked is not None: group.setChecked(checked) group.toggled.connect(toggled) if layout is not None: group.setLayout(layout) return group
class DialogLoadMask(QDialog): """ Dialog box for selecting spatial ROI and mask. Typical use: default_dir = "/home/user/data" n_rows, n_columns = 15, 20 # Some values # Values that are changed by the dialog box roi = (2, 3, 11, 9) use_roi = True mask_f_path = "" use_mask = False dlg = DialogLoadMask() dlg.set_image_size(n_rows=n_rows, n_columns=n_columns) dlg.set_roi(row_start=roi[0], column_start=roi[1], row_end=roi[2], column_end=roi[3]) dlg.set_roi_active(use_roi) dlg.set_default_directory(default_dir) dlg.set_mask_file_path(mask_f_path) dlg.set_mask_file_active(use_mask) if dlg.exec() == QDialog.Accepted: # If success, then read the values back. Discard changes if rejected. roi = dlg.get_roi() use_roi = dlg.get_roi_active() mask_f_path = dlg.get_mask_file_path() use_mask = dlg.get_mask_file_active() """ def __init__(self): super().__init__() self._validation_enabled = False self.resize(500, 300) self.setWindowTitle("Load Mask or Select ROI") # Initialize variables used with ROI selection group self._n_rows = 0 self._n_columns = 0 self._roi_active = False self._row_start = -1 self._column_start = -1 self._row_end = -1 self._column_end = -1 # ... with Mask group self._mask_active = False self._mask_file_path = "" self._default_directory = "" # Fields for entering spatial ROI coordinates self.validator_rows = QIntValidator() self.validator_rows.setBottom(1) self.validator_cols = QIntValidator() self.validator_cols.setBottom(1) self.le_roi_start_row = LineEditExtended() self.le_roi_start_row.setValidator(self.validator_rows) self.le_roi_start_row.editingFinished.connect( self.le_roi_start_row_editing_finished) self.le_roi_start_row.textChanged.connect( self.le_roi_start_row_text_changed) self.le_roi_start_col = LineEditExtended() self.le_roi_start_col.setValidator(self.validator_cols) self.le_roi_start_col.editingFinished.connect( self.le_roi_start_col_editing_finished) self.le_roi_start_col.textChanged.connect( self.le_roi_start_col_text_changed) self.le_roi_end_row = LineEditExtended() self.le_roi_end_row.setValidator(self.validator_rows) self.le_roi_end_row.editingFinished.connect( self.le_roi_end_row_editing_finished) self.le_roi_end_row.textChanged.connect( self.le_roi_end_row_text_changed) self.le_roi_end_col = LineEditExtended() self.le_roi_end_col.setValidator(self.validator_cols) self.le_roi_end_col.editingFinished.connect( self.le_roi_end_col_editing_finished) self.le_roi_end_col.textChanged.connect( self.le_roi_end_col_text_changed) self._text_map_size_base = " * Map size: " self.label_map_size = QLabel(self._text_map_size_base + "not set") # Group box for spatial ROI selection self.gb_roi = QGroupBox("Select ROI (in pixels)") set_tooltip( self.gb_roi, "Select rectangular <b>spatial ROI</b>. If <b>mask</b> is " "loaded, then ROI is applied to the masked data.", ) self.gb_roi.setCheckable(True) self.gb_roi.toggled.connect(self.gb_roi_toggled) self.gb_roi.setChecked(self._roi_active) vbox = QVBoxLayout() grid = QGridLayout() grid.addWidget(QLabel("Start position(*):"), 0, 0) grid.addWidget(QLabel("row"), 0, 1) grid.addWidget(self.le_roi_start_row, 0, 2) grid.addWidget(QLabel("column"), 0, 3) grid.addWidget(self.le_roi_start_col, 0, 4) grid.addWidget(QLabel("End position(*):"), 1, 0) grid.addWidget(QLabel("row"), 1, 1) grid.addWidget(self.le_roi_end_row, 1, 2) grid.addWidget(QLabel("column"), 1, 3) grid.addWidget(self.le_roi_end_col, 1, 4) vbox.addLayout(grid) vbox.addWidget(self.label_map_size) self.gb_roi.setLayout(vbox) # Widgets for loading mask self.pb_load_mask = QPushButton("Load Mask ...") self.pb_load_mask.clicked.connect(self.pb_load_mask_clicked) self._le_load_mask_default_text = "select 'mask' file" self.le_load_mask = LineEditReadOnly(self._le_load_mask_default_text) # Group box for setting mask self.gb_mask = QGroupBox("Set mask") set_tooltip( self.gb_mask, "Load <b>mask</b> from file. Active pixels in the mask are " "represented by positive integers. If <b>spatial ROI</b> is " "selected, then it is applied to the masked data.", ) self.gb_mask.setCheckable(True) self.gb_mask.toggled.connect(self.gb_mask_toggled) self.gb_mask.setChecked(self._mask_active) hbox = QHBoxLayout() hbox.addWidget(self.pb_load_mask) hbox.addWidget(self.le_load_mask) self.gb_mask.setLayout(hbox) # Yes/No button box self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = self.button_box.button(QDialogButtonBox.Ok) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addWidget(self.gb_roi) vbox.addStretch(1) vbox.addWidget(self.gb_mask) vbox.addStretch(2) vbox.addWidget(self.button_box) self.setLayout(vbox) self._validation_enabled = True self._validate_all_widgets() def _compute_home_directory(self): dir_name = "." if self._default_directory: dir_name = self._default_directory if self._mask_file_path: d, _ = os.path.split(self._mask_file_path) dir_name = d if d else dir_name return dir_name def pb_load_mask_clicked(self): dir_name = self._compute_home_directory() file_name = QFileDialog.getOpenFileName(self, "Load Mask From File", dir_name, "All (*)") file_name = file_name[0] if file_name: self._mask_file_path = file_name self._show_mask_file_path() logger.debug(f"Mask file is selected: '{file_name}'") def gb_roi_toggled(self, state): self._roi_activate(state) def _read_le_roi_value(self, line_edit, v_default): """ Attempt to read value from line edit box as int, return `v_default` if not successful. Parameters ---------- line_edit: QLineEdit reference to QLineEdit object v_default: int default value returned if the value read from edit box is incorrect """ try: val = int(line_edit.text()) if val < 1: raise Exception() except Exception: val = v_default return val def le_roi_start_row_editing_finished(self): self._row_start = self._read_le_roi_value(self.le_roi_start_row, self._row_start + 1) - 1 def le_roi_end_row_editing_finished(self): self._row_end = self._read_le_roi_value(self.le_roi_end_row, self._row_end) def le_roi_start_col_editing_finished(self): self._column_start = self._read_le_roi_value( self.le_roi_start_col, self._column_start + 1) - 1 def le_roi_end_col_editing_finished(self): self._column_end = self._read_le_roi_value(self.le_roi_end_col, self._column_end) def le_roi_start_row_text_changed(self): self._validate_all_widgets() def le_roi_end_row_text_changed(self): self._validate_all_widgets() def le_roi_start_col_text_changed(self): self._validate_all_widgets() def le_roi_end_col_text_changed(self): self._validate_all_widgets() def gb_mask_toggled(self, state): self._mask_file_activate(state) def _validate_all_widgets(self): """ Validate the values and state of all widgets, update the 'valid' state of the widgets and enable/disable Ok button. """ if not self._validation_enabled: return # Check if all fields have valid input values def _check_valid_input(line_edit, flag_valid): val = self._read_le_roi_value(line_edit, -1) state = val > 0 line_edit.setValid(state) flag_valid = flag_valid if state else False return val, flag_valid # Set all line edits to 'valid' state self.le_roi_start_row.setValid(True) self.le_roi_end_row.setValid(True) self.le_roi_start_col.setValid(True) self.le_roi_end_col.setValid(True) self.le_load_mask.setValid(True) flag_valid = True if self._roi_active: # Perform the following checks only if ROI group is active rs, flag_valid = _check_valid_input(self.le_roi_start_row, flag_valid) re, flag_valid = _check_valid_input(self.le_roi_end_row, flag_valid) cs, flag_valid = _check_valid_input(self.le_roi_start_col, flag_valid) ce, flag_valid = _check_valid_input(self.le_roi_end_col, flag_valid) # Check if start if (rs > 0) and (re > 0) and (rs > re): self.le_roi_start_row.setValid(False) self.le_roi_end_row.setValid(False) flag_valid = False if (cs > 0) and (ce > 0) and (cs > ce): self.le_roi_start_col.setValid(False) self.le_roi_end_col.setValid(False) flag_valid = False if self._mask_active: if not self._mask_file_path: self.le_load_mask.setValid(False) flag_valid = False self.button_ok.setEnabled(flag_valid) def set_image_size(self, *, n_rows, n_columns): """ Set the image size. Image size is used to for input validation. When image size is set, the selection is reset to cover the whole image. Parameters ---------- n_rows: int The number of rows in the image: (1..) n_columns: int The number of columns in the image: (1..) """ if n_rows < 1 or n_columns < 1: raise ValueError( "DialogLoadMask: image size have zero rows or zero columns: " f"n_rows={n_rows} n_columns={n_columns}. " "Report the error to the development team.") self._n_rows = n_rows self._n_columns = n_columns self._row_start = 0 self._row_end = n_rows self._column_start = 0 self._column_end = n_columns self._show_selection(True) self._validate_all_widgets() self.validator_rows.setTop(n_rows) self.validator_cols.setTop(n_columns) # Set label self.label_map_size.setText( f"{self._text_map_size_base}{self._n_rows} rows, {self._n_columns} columns." ) def _show_selection(self, visible): """visible: True - show values, False - hide values""" def _show_number(l_edit, value): if value > 0: l_edit.setText(f"{value}") else: # This would typically mean incorrect initialization of the dialog box l_edit.setText("") _show_number(self.le_roi_start_row, self._row_start + 1) _show_number(self.le_roi_start_col, self._column_start + 1) _show_number(self.le_roi_end_row, self._row_end) _show_number(self.le_roi_end_col, self._column_end) def set_roi(self, *, row_start, column_start, row_end, column_end): """ Set the values of fields that define selection of the image region. First set the image size (`set_image_size()`) and then set the selection. Parameters ---------- row_start: int The row number of the first pixel in the selection (0..n_rows-1). Negative (-1) - resets value to 0. column_start: int The column number of the first pixel in the selection (0..n_columns-1). Negative (-1) - resets value to 0. row_end: int The row number following the last pixel in the selection (1..n_rows). This row is not included in the selection. Negative (-1) - resets value to n_rows. column_end: int The column number following the last pixel in the selection (1..n_columns). This column is not included in the selection. Negative (-1) - resets value to n_columns. """ # The variables holding the selected region are following Python conventions for the # selections: row_start, column_start are in the range 0..n_rows-1, 0..n_columns-1 # and row_end, column_end are in the range 1..n_rows, 1..n_columns. # The values displayed in the dialog box are just pixels numbers in the range # 1..n_rows, 1..n_columns that define the rectangle in the way intuitive to the user. self._row_start = int( np.clip(row_start, a_min=0, a_max=self._n_rows - 1)) self._column_start = int( np.clip(column_start, a_min=0, a_max=self._n_columns - 1)) def _adjust_last_index(index, n_elements): if index < 0: index = n_elements index = int(np.clip(index, 1, n_elements)) return index self._row_end = _adjust_last_index(row_end, self._n_rows) self._column_end = _adjust_last_index(column_end, self._n_columns) self._show_selection(self._roi_active) self._validate_all_widgets() def _roi_activate(self, state): self._roi_active = state self._show_selection(state) self._validate_all_widgets() def set_roi_active(self, state): self._roi_activate(state) self.gb_roi.setChecked(self._roi_active) def get_roi(self): return self._row_start, self._column_start, self._row_end, self._column_end def get_roi_active(self): return self._roi_active def _show_mask_file_path(self): fpath = self._mask_file_path if self._mask_file_path else self._le_load_mask_default_text self.le_load_mask.setText(fpath) self._validate_all_widgets() def _mask_file_activate(self, state): self._mask_active = state self._show_mask_file_path() self._validate_all_widgets() def set_mask_file_active(self, state): self._mask_file_activate(state) self.gb_mask.setChecked(self._mask_active) def get_mask_file_active(self): return self._mask_active def set_mask_file_path(self, fpath): self._mask_file_path = fpath self._show_mask_file_path() def get_mask_file_path(self): return self._mask_file_path def set_default_directory(self, dir_name): self._default_directory = dir_name def get_default_directory(self): return self._default_directory
class ImageRotationDialog(ExToolWindow): def __init__(self, signal, axes, parent, plugin): super(ImageRotationDialog, self).__init__(parent) self.ui = parent self.create_controls() self.accepted.connect(self.ok) self.rejected.connect(self.close_new) self.signal = signal self.plugin = plugin self.new_out = None self._connected_updates = False if isinstance(axes, str): axm = signal.signal.axes_manager if axes.startswith("nav"): axes = (axm._axes.index(axm.navigation_axes[0]), axm._axes.index(axm.navigation_axes[1])) elif axes.startswith("sig"): axes = (axm._axes.index(axm.signal_axes[0]), axm._axes.index(axm.signal_axes[1])) self.axes = axes self.setWindowTitle(tr("Rotate")) # TODO: TAG: Functionality check if not hasattr(signal.signal, 'events'): self.gbo_preview.setVisible(False) # TODO: Add dynamic rotation, e.g. one that rotates when source # signal's data_changed event triggers def connect(self): # TODO: Don't have to con/dis those in gbo self.opt_new.toggled.connect(self.close_new) self.num_angle.valueChanged.connect(self.update) self.chk_grid.toggled.connect(self.update) self.num_grid.valueChanged.connect(self.update) self.chk_reshape.toggled.connect(self.update) self.opt_new.toggled.connect(self.update) self.opt_replace.toggled.connect(self.update) def disconnect(self): self.num_angle.valueChanged.disconnect(self.update) self.chk_grid.toggled.disconnect(self.update) self.num_grid.valueChanged.disconnect(self.update) self.chk_reshape.toggled.disconnect(self.update) self.opt_new.toggled.disconnect(self.update) self.opt_replace.toggled.disconnect(self.update) def ok(self): # Draw figure if not already done # TODO: TAG: Functionality check if not hasattr(self.signal.signal, 'events') or \ not self.gbo_preview.isChecked(): self.update() angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() self.plugin.record_code( r"<p>.rotate_signal({0}, reshape={1}, axes={2})".format( angle, reshape, self.axes)) # Clean up event connections if self.new_out is not None: self.connect_update_plot(self.new_out.signal, disconnect=True) def close_new(self, value=False): if self.new_out is not None and not value: self.new_out.close() self.new_out = None self._connected_updates = False def set_preview(self, value): if not hasattr(self.signal.signal, 'events'): return if value: self.connect() self.update() else: self.disconnect() self.close_new() def _axes_in_nav(self): axm = self.signal.signal.axes_manager navidx = [axm._axes.index(ax) for ax in axm.navigation_axes] if self.axes[0] in navidx: return True return False def connect_update_plot(self, signal, disconnect=False): if self._connected_updates != disconnect: return # Nothing to do, prevent double connections if self._axes_in_nav(): f = signal._plot.navigator_plot.update else: f = signal._plot.signal_plot.update # TODO: TAG: Functionality check if hasattr(signal, 'events') and hasattr(signal.events, 'data_changed'): if disconnect: signal.events.data_changed.disconnect(f) else: signal.events.data_changed.connect(f, []) self._connected_updates = not disconnect def update(self): angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() if self.opt_new.isChecked(): if self.new_out is None: out = None else: out = self.new_out.signal elif self.opt_replace.isChecked(): out = self.signal.signal else: return # Indeterminate state, do nothing s = self.plugin.rotate_signal(angle, self.signal.signal, record=False, reshape=reshape, out=out, axes=self.axes) if out is None: s.metadata.General.title = self.signal.name + "[Rotated]" s.plot() self.connect_update_plot(s) if (self.gbo_preview.isChecked() and self.opt_new.isChecked() and self.new_out is None): self.new_out = self.ui.lut_signalwrapper[s] else: s = out if self.chk_grid.isChecked() is True: pass # TODO: Draw grid def create_controls(self): """ Create UI controls. """ vbox = QVBoxLayout() form = QFormLayout() self.num_angle = QDoubleSpinBox() self.num_angle.setValue(0.0) self.num_angle.setMinimum(-360) self.num_angle.setMaximum(360) form.addRow(tr("Angle:"), self.num_angle) vbox.addLayout(form) self.gbo_preview = QGroupBox(tr("Preview")) self.gbo_preview.setCheckable(True) self.gbo_preview.setChecked(False) gbo_vbox = QVBoxLayout() self.chk_grid = QCheckBox(tr("Grid")) self.chk_grid.setChecked(False) self.num_grid = QSpinBox() self.num_grid.setValue(4) self.num_grid.setMinimum(1) self.num_grid.setEnabled(False) self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled) gbo_vbox.addWidget(self.chk_grid) gbo_vbox.addWidget(self.num_grid) self.gbo_preview.setLayout(gbo_vbox) vbox.addWidget(self.gbo_preview) self.gbo_preview.toggled[bool].connect(self.set_preview) self.gbo_output = QGroupBox(tr("Output")) self.opt_new = QRadioButton(tr("New signal")) self.opt_replace = QRadioButton(tr("In place")) self.opt_new.setChecked(True) gbo_vbox2 = QVBoxLayout() gbo_vbox2.addWidget(self.opt_new) gbo_vbox2.addWidget(self.opt_replace) self.gbo_output.setLayout(gbo_vbox2) vbox.addWidget(self.gbo_output) self.chk_reshape = QCheckBox(tr("Resize to fit")) self.chk_reshape.setChecked(False) vbox.addWidget(self.chk_reshape) self.btn_ok = QPushButton(tr("&OK")) self.btn_ok.setDefault(True) self.btn_ok.clicked.connect(self.accept) self.btn_cancel = QPushButton(tr("&Cancel")) self.btn_cancel.clicked.connect(self.reject) hbox = QHBoxLayout() hbox.addWidget(self.btn_ok) hbox.addWidget(self.btn_cancel) vbox.addLayout(hbox) vbox.addStretch(1) self.setLayout(vbox)
class KernelConnectionDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(KernelConnectionDialog, self).__init__(parent) self.setWindowTitle(_('Connect to an existing kernel')) main_label = QLabel( _("<p>Please select the JSON connection file (<i>e.g.</i> " "<tt>kernel-1234.json</tt>) of the existing kernel, and enter " "the SSH information if connecting to a remote machine. " "To learn more about starting external kernels and connecting " "to them, see <a href=\"https://docs.spyder-ide.org/" "ipythonconsole.html#connect-to-an-external-kernel\">" "our documentation</a>.</p>")) main_label.setWordWrap(True) main_label.setAlignment(Qt.AlignJustify) main_label.setOpenExternalLinks(True) # Connection file cf_label = QLabel(_('Connection file:')) self.cf = QLineEdit() self.cf.setPlaceholderText(_('Kernel connection file path')) self.cf.setMinimumWidth(350) cf_open_btn = QPushButton(_('Browse')) cf_open_btn.clicked.connect(self.select_connection_file) cf_layout = QHBoxLayout() cf_layout.addWidget(cf_label) cf_layout.addWidget(self.cf) cf_layout.addWidget(cf_open_btn) # Remote kernel groupbox self.rm_group = QGroupBox(_("This is a remote kernel (via SSH)")) # SSH connection hn_label = QLabel(_('Hostname:')) self.hn = QLineEdit() pn_label = QLabel(_('Port:')) self.pn = QLineEdit() self.pn.setMaximumWidth(75) self.pn.setText('22') un_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.kf_radio.toggled.connect(self.pw.setDisabled) self.kf = QLineEdit() kf_open_btn = QPushButton(_('Browse')) kf_open_btn.clicked.connect(self.select_ssh_key) kf_layout = QHBoxLayout() kf_layout.addWidget(self.kf) kf_layout.addWidget(kf_open_btn) kfp_label = QLabel(_('Passphase:')) self.kfp = QLineEdit() self.kfp.setPlaceholderText(_('Optional')) self.kfp.setEchoMode(QLineEdit.Password) self.kf_radio.toggled.connect(self.kf.setEnabled) self.kf_radio.toggled.connect(self.kfp.setEnabled) self.kf_radio.toggled.connect(kf_open_btn.setEnabled) self.kf_radio.toggled.connect(kfp_label.setEnabled) self.pw_radio.toggled.connect(self.kf.setDisabled) self.pw_radio.toggled.connect(self.kfp.setDisabled) self.pw_radio.toggled.connect(kf_open_btn.setDisabled) self.pw_radio.toggled.connect(kfp_label.setDisabled) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(hn_label, 0, 0, 1, 2) ssh_layout.addWidget(self.hn, 0, 2) ssh_layout.addWidget(pn_label, 0, 3) ssh_layout.addWidget(self.pn, 0, 4) ssh_layout.addWidget(un_label, 1, 0, 1, 2) ssh_layout.addWidget(self.un, 1, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.kf_radio, 2, 0) auth_layout.addWidget(kf_label, 2, 1) auth_layout.addLayout(kf_layout, 2, 2) auth_layout.addWidget(kfp_label, 3, 1) auth_layout.addWidget(self.kfp, 3, 2) auth_group.setLayout(auth_layout) # Remote kernel layout rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(True) self.rm_group.setChecked(False) self.rm_group.toggled.connect(self.pw_radio.setChecked) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) # Dialog layout layout = QVBoxLayout(self) layout.addWidget(main_label) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) layout.addLayout(cf_layout) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addWidget(self.accept_btns) def select_connection_file(self): cf = getopenfilename(self, _('Select kernel connection file'), jupyter_runtime_dir(), '*.json;;*.*')[0] self.cf.setText(cf) def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.kf.setText(kf) @staticmethod def get_connection_parameters(parent=None, dialog=None): if not dialog: dialog = KernelConnectionDialog(parent) result = dialog.exec_() is_remote = bool(dialog.rm_group.isChecked()) accepted = result == QDialog.Accepted if is_remote: def falsy_to_none(arg): return arg if arg else None if dialog.hn.text() and dialog.un.text(): port = dialog.pn.text() if dialog.pn.text() else '22' hostname = "{0}@{1}:{2}".format(dialog.un.text(), dialog.hn.text(), port) else: hostname = None if dialog.pw_radio.isChecked(): password = falsy_to_none(dialog.pw.text()) keyfile = None elif dialog.kf_radio.isChecked(): keyfile = falsy_to_none(dialog.kf.text()) password = falsy_to_none(dialog.kfp.text()) else: # imposible? keyfile = None password = None return (dialog.cf.text(), hostname, keyfile, password, accepted) else: path = dialog.cf.text() _dir, filename = osp.dirname(path), osp.basename(path) if _dir == '' and not filename.endswith('.json'): path = osp.join(jupyter_runtime_dir(), 'kernel-' + path + '.json') return (path, None, None, None, accepted)
class KernelConnectionDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(KernelConnectionDialog, self).__init__(parent) self.setWindowTitle(_('Connect to an existing kernel')) main_label = QLabel(_( "<p>Please select the JSON connection file (<i>e.g.</i> " "<tt>kernel-1234.json</tt>) of the existing kernel, and enter " "the SSH information if connecting to a remote machine. " "To learn more about starting external kernels and connecting " "to them, see <a href=\"https://docs.spyder-ide.org/" "ipythonconsole.html#connect-to-an-external-kernel\">" "our documentation</a>.</p>")) main_label.setWordWrap(True) main_label.setAlignment(Qt.AlignJustify) main_label.setOpenExternalLinks(True) # Connection file cf_label = QLabel(_('Connection file:')) self.cf = QLineEdit() self.cf.setPlaceholderText(_('Kernel connection file path')) self.cf.setMinimumWidth(350) cf_open_btn = QPushButton(_('Browse')) cf_open_btn.clicked.connect(self.select_connection_file) cf_layout = QHBoxLayout() cf_layout.addWidget(cf_label) cf_layout.addWidget(self.cf) cf_layout.addWidget(cf_open_btn) # Remote kernel groupbox self.rm_group = QGroupBox(_("This is a remote kernel (via SSH)")) # SSH connection hn_label = QLabel(_('Hostname:')) self.hn = QLineEdit() pn_label = QLabel(_('Port:')) self.pn = QLineEdit() self.pn.setMaximumWidth(75) self.pn.setText('22') un_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.kf_radio.toggled.connect(self.pw.setDisabled) self.kf = QLineEdit() kf_open_btn = QPushButton(_('Browse')) kf_open_btn.clicked.connect(self.select_ssh_key) kf_layout = QHBoxLayout() kf_layout.addWidget(self.kf) kf_layout.addWidget(kf_open_btn) kfp_label = QLabel(_('Passphase:')) self.kfp = QLineEdit() self.kfp.setPlaceholderText(_('Optional')) self.kfp.setEchoMode(QLineEdit.Password) self.kf_radio.toggled.connect(self.kf.setEnabled) self.kf_radio.toggled.connect(self.kfp.setEnabled) self.kf_radio.toggled.connect(kf_open_btn.setEnabled) self.kf_radio.toggled.connect(kfp_label.setEnabled) self.pw_radio.toggled.connect(self.kf.setDisabled) self.pw_radio.toggled.connect(self.kfp.setDisabled) self.pw_radio.toggled.connect(kf_open_btn.setDisabled) self.pw_radio.toggled.connect(kfp_label.setDisabled) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(hn_label, 0, 0, 1, 2) ssh_layout.addWidget(self.hn, 0, 2) ssh_layout.addWidget(pn_label, 0, 3) ssh_layout.addWidget(self.pn, 0, 4) ssh_layout.addWidget(un_label, 1, 0, 1, 2) ssh_layout.addWidget(self.un, 1, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.kf_radio, 2, 0) auth_layout.addWidget(kf_label, 2, 1) auth_layout.addLayout(kf_layout, 2, 2) auth_layout.addWidget(kfp_label, 3, 1) auth_layout.addWidget(self.kfp, 3, 2) auth_group.setLayout(auth_layout) # Remote kernel layout rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(True) self.rm_group.setChecked(False) self.rm_group.toggled.connect(self.pw_radio.setChecked) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) # Dialog layout layout = QVBoxLayout(self) layout.addWidget(main_label) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) layout.addLayout(cf_layout) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addWidget(self.accept_btns) def select_connection_file(self): cf = getopenfilename(self, _('Select kernel connection file'), jupyter_runtime_dir(), '*.json;;*.*')[0] self.cf.setText(cf) def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.kf.setText(kf) @staticmethod def get_connection_parameters(parent=None, dialog=None): if not dialog: dialog = KernelConnectionDialog(parent) result = dialog.exec_() is_remote = bool(dialog.rm_group.isChecked()) accepted = result == QDialog.Accepted if is_remote: def falsy_to_none(arg): return arg if arg else None if dialog.hn.text() and dialog.un.text(): port = dialog.pn.text() if dialog.pn.text() else '22' hostname = "{0}@{1}:{2}".format(dialog.un.text(), dialog.hn.text(), port) else: hostname = None if dialog.pw_radio.isChecked(): password = falsy_to_none(dialog.pw.text()) keyfile = None elif dialog.kf_radio.isChecked(): keyfile = falsy_to_none(dialog.kf.text()) password = falsy_to_none(dialog.kfp.text()) else: # imposible? keyfile = None password = None return (dialog.cf.text(), hostname, keyfile, password, accepted) else: path = dialog.cf.text() _dir, filename = osp.dirname(path), osp.basename(path) if _dir == '' and not filename.endswith('.json'): path = osp.join(jupyter_runtime_dir(), 'kernel-'+path+'.json') return (path, None, None, None, accepted)
def uicreate_groupbox(self, idx): """.""" grpbx = QGroupBox(self.line_names[idx], self) grpbx.setCheckable(True) grpbx.setChecked(not idx) grpbx.toggled.connect(self.updater[idx].set_visible) vbl = QVBoxLayout(grpbx) gdl = QGridLayout() gdl.setSpacing(4) vbl.addLayout(gdl) if self.is_orb: lbl_orb = self.uicreate_label('Show', grpbx) lbl_ref = self.uicreate_label('as diff to:', grpbx) cbx_ref = self.uicreate_combobox(grpbx, 'ref', idx) cbx_orb = self.uicreate_combobox(grpbx, 'val', idx) gdl.addWidget(lbl_orb, 0, 0) gdl.addWidget(lbl_ref, 1, 0) gdl.addWidget(cbx_orb, 0, 1) gdl.addWidget(cbx_ref, 1, 1) pb_save = QPushButton('', grpbx) pb_save.clicked.connect(_part(self._save_difference, idx)) pb_save.setObjectName('butt') pb_save.setStyleSheet('#butt {max-width: 40px; icon-size: 35px;}') pb_save.setIcon(qta.icon('fa5.save')) pb_save.setToolTip('Save diff to file') gdl.addWidget(pb_save, 0, 2, 2, 1) unit = 'm' if self.is_orb else 'rad' for pln in ('x', 'y'): wid = QWidget(grpbx) vbl.addWidget(wid) vbl.setSpacing(2) hbl = QHBoxLayout(wid) hbl.setSpacing(0) cbx = QCheckBox('{0:s}:'.format(pln.upper()), wid) cbx.setObjectName(pln + 'checkbox') cbx.setChecked(False) hbl.addWidget(cbx) lab_avg = Label(unit, '-100.00 mrad', wid) self.updater[idx].ave[pln].connect(lab_avg.setFloat) lab_avg.setStyleSheet("""min-width:4.5em;""") lab_avg.setAlignment(Qt.AlignRight) hbl.addWidget(lab_avg) hbl.addWidget( QLabel(" <html><head/><body><p>±</p></body></html> ", wid)) lab_std = Label(unit, '100.00 mrad', wid) self.updater[idx].std[pln].connect(lab_std.setFloat) lab_std.setStyleSheet("""min-width:4.5em;""") lab_std.setAlignment(Qt.AlignLeft) hbl.addWidget(lab_std) hbl.addWidget(QLabel('(pp. ', wid)) lab_p2p = Label(unit, '100.00 mrad', wid) self.updater[idx].p2p[pln].connect(lab_p2p.setFloat) lab_p2p.setStyleSheet("""min-width:4.5em;""") lab_p2p.setAlignment(Qt.AlignLeft) hbl.addWidget(lab_p2p) hbl.addWidget(QLabel(')', wid)) return grpbx
class TOFTOFSetupWidget(BaseWidget): ''' The one and only tab page. ''' name = 'TOFTOF Reduction' class TofTofDataTableModel(DataTableModel): def _textToData(self, row, col, text): """ converts a displayable text back to stored data. """ if col == 2: return OptionalFloat(text) else: return text # just return the value, it is already str. # tooltips TIP_prefix = '' TIP_dataDir = '' TIP_saveDir = '' TIP_btnDataDir = '' TIP_btnSaveDir = '' TIP_vanRuns = '' TIP_vanCmnt = '' TIP_vanTemp = 'Temperature (K). Optional.' TIP_vanEcFactor = '' TIP_ecRuns = '' TIP_ecTemp = 'Temperature (K). Optional.' TIP_ecFactor = '' TIP_binEon = '' TIP_binEstart = '' TIP_binEstep = '' TIP_binEend = '' TIP_binQon = '' TIP_binQstart = '' TIP_binQstep = '' TIP_binQend = '' TIP_maskDetectors = '' TIP_dataRunsView = '' TIP_chkSubtractECVan = '' TIP_chkReplaceNaNs = 'Replace NaNs with 0' TIP_chkCreateDiff = '' TIP_chkKeepSteps = '' TIP_chkSofQW = '' TIP_chkSofTW = '' TIP_chkNxspe = 'Save for MSlice' TIP_chkNexus = 'Save for Mantid' TIP_chkAscii = 'Save as text' TIP_rbtNormaliseNone = '' TIP_rbtNormaliseMonitor = '' TIP_rbtNormaliseTime = '' TIP_rbtCorrectTOFNone = '' TIP_rbtCorrectTOFVan = '' TIP_rbtCorrectTOFSample = '' def dir_browse_dialog(self, default_dir=''): """ Pop up a directory dialog box. """ dirname = QFileDialog.getExistingDirectory( self, "Select Directory", default_dir, QFileDialog.DontUseNativeDialog) if isinstance(dirname, tuple): dirname = dirname[0] return dirname def __init__(self, settings): BaseWidget.__init__(self, settings=settings) inf = float('inf') def set_spin(spin, minVal=-inf, maxVal=+inf, decimals=3): spin.setRange(minVal, maxVal) spin.setDecimals(decimals) spin.setSingleStep(0.01) def tip(widget, text): if text: widget.setToolTip(text) return widget def setEnabled(widget, *widgets): """enables widget, when value of all widgets evaluates to true""" def setEnabled(): widget.setEnabled(all(w.isChecked() for w in widgets)) for w in widgets: w.toggled.connect(setEnabled) return widget def DoubleEdit(): edit = SmallQLineEdit() edit.setValidator(QDoubleValidator()) return edit # ui data elements self.prefix = tip(QLineEdit(), self.TIP_prefix) self.dataDir = tip(QLineEdit(), self.TIP_dataDir) self.saveDir = tip(QLineEdit(), self.TIP_saveDir) self.vanRuns = tip(QLineEdit(), self.TIP_vanRuns) self.vanCmnt = tip(QLineEdit(), self.TIP_vanCmnt) self.vanTemp = tip(DoubleEdit(), self.TIP_vanTemp) self.ecRuns = tip(SmallQLineEdit(), self.TIP_ecRuns) self.ecTemp = tip(DoubleEdit(), self.TIP_ecTemp) self.ecFactor = tip(QDoubleSpinBox(), self.TIP_ecFactor) set_spin(self.ecFactor, 0, 1) self.binEon = tip(QCheckBox(), self.TIP_binEon) self.binEstart = setEnabled(tip(QDoubleSpinBox(), self.TIP_binEstart), self.binEon) self.binEstep = setEnabled(tip(QDoubleSpinBox(), self.TIP_binEstep), self.binEon) self.binEend = setEnabled(tip(QDoubleSpinBox(), self.TIP_binEend), self.binEon) set_spin(self.binEstart) set_spin(self.binEstep, decimals=4) set_spin(self.binEend) self.binQon = setEnabled(tip(QCheckBox(), self.TIP_binQon), self.binEon) self.binQstart = setEnabled(tip(QDoubleSpinBox(), self.TIP_binQstart), self.binEon, self.binQon) self.binQstep = setEnabled(tip(QDoubleSpinBox(), self.TIP_binQstep), self.binEon, self.binQon) self.binQend = setEnabled(tip(QDoubleSpinBox(), self.TIP_binQend), self.binEon, self.binQon) set_spin(self.binQstart) set_spin(self.binQstep) set_spin(self.binQend) self.maskDetectors = tip(QLineEdit(), self.TIP_maskDetectors) headers = ('Data runs', 'Comment', 'T (K)') self.dataRunsView = tip( DataTableView(self, headers, TOFTOFSetupWidget.TofTofDataTableModel), self.TIP_dataRunsView) self.dataRunsView.horizontalHeader().setStretchLastSection(True) self.dataRunsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.runDataModel = self.dataRunsView.model() # ui controls self.btnDataDir = tip(QPushButton('Browse'), self.TIP_btnDataDir) self.btnSaveDir = tip(QPushButton('Browse'), self.TIP_btnSaveDir) self.chkSubtractECVan = tip( QCheckBox('Subtract empty can from vanadium'), self.TIP_chkSubtractECVan) self.vanEcFactor = setEnabled( tip(QDoubleSpinBox(), self.TIP_vanEcFactor), self.chkSubtractECVan) set_spin(self.vanEcFactor, 0, 1) self.chkReplaceNaNs = setEnabled( tip(QCheckBox(u'Replace special values in S(Q, ω) with 0'), self.TIP_chkReplaceNaNs), self.binEon) self.chkCreateDiff = setEnabled( tip(QCheckBox('Create diffractograms'), self.TIP_chkCreateDiff), self.binEon) self.chkKeepSteps = tip(QCheckBox('Keep intermediate steps'), self.TIP_chkKeepSteps) self.chkSofTWNxspe = setEnabled( tip(QCheckBox('NXSPE'), self.TIP_chkNxspe), self.binEon) self.chkSofTWNexus = tip(QCheckBox('NeXus'), self.TIP_chkNexus) self.chkSofTWAscii = tip(QCheckBox('Ascii'), self.TIP_chkAscii) self.chkSofQWNexus = setEnabled( tip(QCheckBox('NeXus'), self.TIP_chkNexus), self.binEon, self.binQon) self.chkSofQWAscii = setEnabled( tip(QCheckBox('Ascii'), self.TIP_chkAscii), self.binEon, self.binQon) self.rbtNormaliseNone = tip(QRadioButton('none'), self.TIP_rbtNormaliseNone) self.rbtNormaliseMonitor = tip(QRadioButton('to monitor'), self.TIP_rbtNormaliseMonitor) self.rbtNormaliseTime = tip(QRadioButton('to time'), self.TIP_rbtNormaliseTime) self.rbtCorrectTOFNone = tip(QRadioButton('none'), self.TIP_rbtCorrectTOFNone) self.rbtCorrectTOFVan = tip(QRadioButton('vanadium'), self.TIP_rbtCorrectTOFVan) self.rbtCorrectTOFSample = tip(QRadioButton('sample'), self.TIP_rbtCorrectTOFSample) # ui layout def _box(cls, widgets): box = cls() for wgt in widgets: if isinstance(wgt, QLayout): box.addLayout(wgt) elif isinstance(wgt, QWidget): box.addWidget(wgt) else: box.addStretch(wgt) return box def hbox(*widgets): return _box(QHBoxLayout, widgets) def vbox(*widgets): return _box(QVBoxLayout, widgets) def label(text, tip): label = QLabel(text) if tip: label.setToolTip(tip) return label self.gbSave = QGroupBox('Save reduced data') self.gbSave.setCheckable(True) gbDataDir = QGroupBox('Data search directory') gbPrefix = QGroupBox('Workspace prefix') gbOptions = QGroupBox('Options') gbInputs = QGroupBox('Inputs') gbBinning = QGroupBox('Binning') gbData = QGroupBox('Data') box = QVBoxLayout() self._layout.addLayout(box) box.addLayout( hbox(vbox(gbDataDir, gbInputs, gbBinning, gbOptions, 1), vbox(gbPrefix, gbData, self.gbSave))) gbDataDir.setLayout(hbox(self.dataDir, self.btnDataDir)) gbPrefix.setLayout(hbox(self.prefix, )) grid = QGridLayout() grid.addWidget(self.chkSubtractECVan, 0, 0, 1, 4) grid.addWidget(label('Normalise', 'tip'), 1, 0) grid.addWidget(self.rbtNormaliseNone, 1, 1) grid.addWidget(self.rbtNormaliseMonitor, 1, 2) grid.addWidget(self.rbtNormaliseTime, 1, 3) grid.addWidget(QLabel('Correct TOF'), 2, 0) grid.addWidget(self.rbtCorrectTOFNone, 2, 1) grid.addWidget(self.rbtCorrectTOFVan, 2, 2) grid.addWidget(self.rbtCorrectTOFSample, 2, 3) grid.addWidget(self.chkReplaceNaNs, 3, 0, 1, 4) grid.addWidget(self.chkCreateDiff, 4, 0, 1, 4) grid.addWidget(self.chkKeepSteps, 5, 0, 1, 4) grid.setColumnStretch(4, 1) gbOptions.setLayout(grid) btnGroup = QButtonGroup(self) btnGroup.addButton(self.rbtNormaliseNone) btnGroup.addButton(self.rbtNormaliseMonitor) btnGroup.addButton(self.rbtNormaliseTime) btnGroup = QButtonGroup(self) btnGroup.addButton(self.rbtCorrectTOFNone) btnGroup.addButton(self.rbtCorrectTOFVan) btnGroup.addButton(self.rbtCorrectTOFSample) grid = QGridLayout() grid.addWidget(QLabel('Vanadium runs'), 0, 0) grid.addWidget(self.vanRuns, 0, 1, 1, 3) grid.addWidget(QLabel('Van. comment'), 1, 0) grid.addWidget(self.vanCmnt, 1, 1, 1, 1) grid.addLayout(hbox(QLabel('EC factor'), self.vanEcFactor), 1, 2, 1, 1) grid.addLayout(hbox(QLabel('T (K)'), self.vanTemp), 1, 3) grid.addWidget(QLabel('Empty can runs'), 2, 0) grid.addWidget(self.ecRuns, 2, 1, 1, 1) grid.addLayout(hbox(QLabel('EC factor'), self.ecFactor), 2, 2, 1, 1) grid.addLayout(hbox(QLabel('T (K)'), self.ecTemp), 2, 3) grid.addWidget(QLabel('Mask detectors'), 3, 0) grid.addWidget(self.maskDetectors, 3, 1, 1, 3) gbInputs.setLayout(grid) grid = QGridLayout() grid.addWidget(QLabel('on'), 0, 1) grid.addWidget(QLabel('start'), 0, 2) grid.addWidget(QLabel('step'), 0, 3) grid.addWidget(QLabel('end'), 0, 4) grid.addWidget(QLabel('Energy'), 1, 0) grid.addWidget(self.binEon, 1, 1) grid.addWidget(self.binEstart, 1, 2) grid.addWidget(self.binEstep, 1, 3) grid.addWidget(self.binEend, 1, 4) grid.addWidget(QLabel('Q'), 2, 0) grid.addWidget(self.binQon, 2, 1) grid.addWidget(self.binQstart, 2, 2) grid.addWidget(self.binQstep, 2, 3) grid.addWidget(self.binQend, 2, 4) for col in (0, 2, 3, 4): grid.setColumnStretch(col, 1) gbBinning.setLayout(grid) gbData.setLayout(hbox(self.dataRunsView)) grid = QGridLayout() saveDirGroup = hbox(self.saveDir, self.btnSaveDir) grid.addWidget(QLabel('Directory'), 0, 0) grid.addLayout(saveDirGroup, 0, 1, 1, 4) grid.addWidget(setEnabled(QLabel(u'S(Q, ω):'), self.binEon), 1, 0) grid.addWidget(self.chkSofQWNexus, 1, 1) grid.addWidget(self.chkSofQWAscii, 1, 2) grid.addItem(QSpacerItem(5, 5, hPolicy=QSizePolicy.Expanding), 1, 4) grid.addWidget(QLabel(u'S(2θ, ω):'), 2, 0) grid.addWidget(self.chkSofTWNexus, 2, 1) grid.addWidget(self.chkSofTWAscii, 2, 2) grid.addWidget(self.chkSofTWNxspe, 2, 3) self.gbSave.setLayout(grid) # handle signals self.btnDataDir.clicked.connect(self._onDataDir) self.btnSaveDir.clicked.connect(self._onSaveDir) self.runDataModel.selectCell.connect(self._onSelectedCell) def _onDataDir(self): dirname = self.dir_browse_dialog(self.dataDir.text()) if dirname: self.dataDir.setText(dirname) def _onSaveDir(self): dirname = self.dir_browse_dialog(self.saveDir.text()) if dirname: self.saveDir.setText(dirname) def _onSelectedCell(self, index): self.dataRunsView.setCurrentIndex(index) self.dataRunsView.setFocus() def get_state(self): elem = TOFTOFScriptElement() def line_text(lineEdit): return str(lineEdit.text()).strip() def is_checked(checkBox): return checkBox.isChecked() and checkBox.isEnabled() elem.facility_name = self._settings.facility_name elem.instrument_name = self._settings.instrument_name elem.prefix = line_text(self.prefix) elem.dataDir = line_text(self.dataDir) elem.vanRuns = line_text(self.vanRuns) elem.vanCmnt = line_text(self.vanCmnt) elem.vanTemp = OptionalFloat(line_text(self.vanTemp)) elem.vanEcFactor = self.vanEcFactor.value() elem.ecRuns = line_text(self.ecRuns) elem.ecTemp = OptionalFloat(line_text(self.ecTemp)) elem.ecFactor = self.ecFactor.value() elem.dataRuns = self.runDataModel.tableData elem.binEon = is_checked(self.binEon) elem.binEstart = self.binEstart.value() elem.binEstep = self.binEstep.value() elem.binEend = self.binEend.value() elem.binQon = is_checked(self.binQon) elem.binQstart = self.binQstart.value() elem.binQstep = self.binQstep.value() elem.binQend = self.binQend.value() elem.maskDetectors = line_text(self.maskDetectors) elem.subtractECVan = is_checked(self.chkSubtractECVan) elem.replaceNaNs = is_checked(self.chkReplaceNaNs) elem.createDiff = is_checked(self.chkCreateDiff) elem.keepSteps = is_checked(self.chkKeepSteps) elem.saveDir = line_text(self.saveDir) elem.saveSofTWNxspe = is_checked(self.chkSofTWNxspe) elem.saveSofTWNexus = is_checked(self.chkSofTWNexus) elem.saveSofTWAscii = is_checked(self.chkSofTWAscii) elem.saveSofQWNexus = is_checked(self.chkSofQWNexus) elem.saveSofQWAscii = is_checked(self.chkSofQWAscii) elem.normalise = elem.NORM_MONITOR if self.rbtNormaliseMonitor.isChecked() else \ elem.NORM_TIME if self.rbtNormaliseTime.isChecked() else \ elem.NORM_NONE elem.correctTof = elem.CORR_TOF_VAN if self.rbtCorrectTOFVan.isChecked() else \ elem.CORR_TOF_SAMPLE if self.rbtCorrectTOFSample.isChecked() else \ elem.CORR_TOF_NONE return elem def set_state(self, toftofScriptElement): elem = toftofScriptElement self.prefix.setText(elem.prefix) self.dataDir.setText(elem.dataDir) self.vanRuns.setText(elem.vanRuns) self.vanCmnt.setText(elem.vanCmnt) self.vanTemp.setText(str(elem.vanTemp)) self.vanEcFactor.setValue(elem.vanEcFactor) self.ecRuns.setText(elem.ecRuns) self.ecTemp.setText(str(elem.ecTemp)) self.ecFactor.setValue(elem.ecFactor) self.runDataModel.beginResetModel() self.runDataModel.tableData = elem.dataRuns self.runDataModel.endResetModel() self.binEon.setChecked(elem.binEon) self.binEstart.setValue(elem.binEstart) self.binEstep.setValue(elem.binEstep) self.binEend.setValue(elem.binEend) self.binQon.setChecked(elem.binQon) self.binQstart.setValue(elem.binQstart) self.binQstep.setValue(elem.binQstep) self.binQend.setValue(elem.binQend) self.maskDetectors.setText(elem.maskDetectors) self.chkSubtractECVan.setChecked(elem.subtractECVan) self.chkReplaceNaNs.setChecked(elem.replaceNaNs) self.chkCreateDiff.setChecked(elem.createDiff) self.chkKeepSteps.setChecked(elem.keepSteps) self.saveDir.setText(elem.saveDir) self.chkSofTWNxspe.setChecked(elem.saveSofTWNxspe) self.chkSofTWNexus.setChecked(elem.saveSofTWNexus) self.chkSofTWAscii.setChecked(elem.saveSofTWAscii) self.chkSofQWNexus.setChecked(elem.saveSofQWNexus) self.chkSofQWAscii.setChecked(elem.saveSofQWAscii) self.gbSave.setChecked( any((elem.saveSofTWNxspe, elem.saveSofTWNexus, elem.saveSofTWAscii, elem.saveSofQWNexus, elem.saveSofQWAscii))) if elem.normalise == elem.NORM_MONITOR: self.rbtNormaliseMonitor.setChecked(True) elif elem.normalise == elem.NORM_TIME: self.rbtNormaliseTime.setChecked(True) else: self.rbtNormaliseNone.setChecked(True) if elem.correctTof == elem.CORR_TOF_VAN: self.rbtCorrectTOFVan.setChecked(True) elif elem.correctTof == elem.CORR_TOF_SAMPLE: self.rbtCorrectTOFSample.setChecked(True) else: self.rbtCorrectTOFNone.setChecked(True)
class WidgetGallery(QWidget): def __init__(self, parent=None): super(WidgetGallery, self).__init__(parent) self.originalPalette = QApplication.palette() styleComboBox = QComboBox() styleComboBox.addItems(QStyleFactory.keys()) styleLabel = QLabel("&Style:") styleLabel.setBuddy(styleComboBox) self.useStylePaletteCheckBox = QCheckBox( "&Use style's standard palette") self.useStylePaletteCheckBox.setChecked(True) disableWidgetsCheckBox = QCheckBox("&Disable widgets") self.createTopLeftGroupBox() self.createTopRightGroupBox() self.createBottomLeftTabWidget() self.createBottomRightGroupBox() self.createProgressBar() styleComboBox.activated[str].connect(self.changeStyle) self.useStylePaletteCheckBox.toggled.connect(self.changePalette) disableWidgetsCheckBox.toggled.connect( self.topLeftGroupBox.setDisabled) disableWidgetsCheckBox.toggled.connect( self.topRightGroupBox.setDisabled) disableWidgetsCheckBox.toggled.connect( self.bottomLeftTabWidget.setDisabled) disableWidgetsCheckBox.toggled.connect( self.bottomRightGroupBox.setDisabled) topLayout = QHBoxLayout() topLayout.addWidget(styleLabel) topLayout.addWidget(styleComboBox) topLayout.addStretch(1) topLayout.addWidget(self.useStylePaletteCheckBox) topLayout.addWidget(disableWidgetsCheckBox) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0, 1, 2) mainLayout.addWidget(self.topLeftGroupBox, 1, 0) mainLayout.addWidget(self.topRightGroupBox, 1, 1) mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0) mainLayout.addWidget(self.bottomRightGroupBox, 2, 1) mainLayout.addWidget(self.progressBar, 3, 0, 1, 2) mainLayout.setRowStretch(1, 1) mainLayout.setRowStretch(2, 1) mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(1, 1) self.setLayout(mainLayout) self.changeStyle('Windows') self.val = 0 def changeStyle(self, styleName): QApplication.setStyle(QStyleFactory.create(styleName)) self.changePalette() def changePalette(self): if (self.useStylePaletteCheckBox.isChecked()): QApplication.setPalette(QApplication.style().standardPalette()) else: QApplication.setPalette(self.originalPalette) def advanceProgressBar(self): self.progressBar.setMaximum(100) self.val += 1 self.progressBar.setValue(self.val) def createTopLeftGroupBox(self): self.topLeftGroupBox = QGroupBox("Group 1") radioButton1 = QRadioButton("Radio button 1") radioButton2 = QRadioButton("Radio button 2") radioButton3 = QRadioButton("Radio button 3") radioButton1.setChecked(True) checkBox = QCheckBox("Tri-state check box") checkBox.setTristate(True) checkBox.setCheckState(Qt.PartiallyChecked) layout = QVBoxLayout() layout.addWidget(radioButton1) layout.addWidget(radioButton2) layout.addWidget(radioButton3) layout.addWidget(checkBox) layout.addStretch(1) self.topLeftGroupBox.setLayout(layout) def createTopRightGroupBox(self): self.topRightGroupBox = QGroupBox("Group 2") defaultPushButton = QPushButton("Default Push Button") defaultPushButton.setDefault(True) togglePushButton = QPushButton("Toggle Push Button") togglePushButton.setCheckable(True) togglePushButton.setChecked(True) flatPushButton = QPushButton("Flat Push Button") flatPushButton.setFlat(True) layout = QVBoxLayout() layout.addWidget(defaultPushButton) layout.addWidget(togglePushButton) layout.addWidget(flatPushButton) layout.addStretch(1) self.topRightGroupBox.setLayout(layout) def createBottomLeftTabWidget(self): self.bottomLeftTabWidget = QTabWidget() self.bottomLeftTabWidget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Ignored) tab1 = QWidget() tableWidget = QTableWidget(10, 10) tab1hbox = QHBoxLayout() tab1hbox.setContentsMargins(5, 5, 5, 5) tab1hbox.addWidget(tableWidget) tab1.setLayout(tab1hbox) tab2 = QWidget() textEdit = QTextEdit() textEdit.setPlainText("Twinkle, twinkle, little star,\n" "How I wonder what you are.\n" "Up above the world so high,\n" "Like a diamond in the sky.\n" "Twinkle, twinkle, little star,\n" "How I wonder what you are!\n") tab2hbox = QHBoxLayout() tab2hbox.setContentsMargins(5, 5, 5, 5) tab2hbox.addWidget(textEdit) tab2.setLayout(tab2hbox) self.bottomLeftTabWidget.addTab(tab1, "&Table") self.bottomLeftTabWidget.addTab(tab2, "Text &Edit") def createBottomRightGroupBox(self): self.bottomRightGroupBox = QGroupBox("Group 3") self.bottomRightGroupBox.setCheckable(True) self.bottomRightGroupBox.setChecked(True) lineEdit = QLineEdit('s3cRe7') lineEdit.setEchoMode(QLineEdit.Password) spinBox = QSpinBox(self.bottomRightGroupBox) spinBox.setValue(50) dateTimeEdit = QDateTimeEdit(self.bottomRightGroupBox) dateTimeEdit.setDateTime(QDateTime.currentDateTime()) slider = QSlider(Qt.Horizontal, self.bottomRightGroupBox) slider.setValue(40) scrollBar = QScrollBar(Qt.Horizontal, self.bottomRightGroupBox) scrollBar.setValue(60) dial = QDial(self.bottomRightGroupBox) dial.setValue(30) dial.setNotchesVisible(True) layout = QGridLayout() layout.addWidget(lineEdit, 0, 0, 1, 2) layout.addWidget(spinBox, 1, 0, 1, 2) layout.addWidget(dateTimeEdit, 2, 0, 1, 2) layout.addWidget(slider, 3, 0) layout.addWidget(scrollBar, 4, 0) layout.addWidget(dial, 3, 1, 2, 1) layout.setRowStretch(5, 1) self.bottomRightGroupBox.setLayout(layout) def createProgressBar(self): self.progressBar = QProgressBar() self.progressBar.setRange(0, 10000) self.progressBar.setValue(0) timer = QTimer(self) timer.timeout.connect(self.advanceProgressBar) timer.start(1000)
class KernelConnectionDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(KernelConnectionDialog, self).__init__(parent) self.setWindowTitle(_('Connect to an existing kernel')) main_label = QLabel( _("<p>Please select a local JSON connection file (<i>e.g.</i> " "<tt>kernel-1234.json</tt>) of the existing kernel. " "<br><br>" "If connecting to a remote machine, enter the SSH information, " "adjust the command how to get jupyter runtime directory (if needed) " "push the button to fetch remote configuration files and select one " "of the loaded options." "<br><br>" "To learn more about starting external kernels and connecting " "to them, see <a href=\"https://docs.spyder-ide.org/" "ipythonconsole.html#connect-to-an-external-kernel\">" "our documentation</a>.</p>")) main_label.setWordWrap(True) main_label.setAlignment(Qt.AlignJustify) main_label.setOpenExternalLinks(True) self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files' self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir' # Connection file cf_label = QLabel(_('Connection file:')) self.cf = QLineEdit() self.cf.setPlaceholderText(_('Kernel connection file path')) self.cf.setMinimumWidth(350) cf_open_btn = QPushButton(_('Browse')) cf_open_btn.clicked.connect(self.select_connection_file) cf_layout = QHBoxLayout() cf_layout.addWidget(cf_label) cf_layout.addWidget(self.cf) cf_layout.addWidget(cf_open_btn) # Remote kernel groupbox self.rm_group = QGroupBox(_("This is a remote kernel (via SSH)")) # SSH connection hn_label = QLabel(_('Hostname:')) self.hn = QLineEdit() pn_label = QLabel(_('Port:')) self.pn = QLineEdit() self.pn.setMaximumWidth(75) un_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.kf_radio.toggled.connect(self.pw.setDisabled) self.kf = QLineEdit() kf_open_btn = QPushButton(_('Browse')) kf_open_btn.clicked.connect(self.select_ssh_key) kf_layout = QHBoxLayout() kf_layout.addWidget(self.kf) kf_layout.addWidget(kf_open_btn) kfp_label = QLabel(_('Passphase:')) self.kfp = QLineEdit() self.kfp.setPlaceholderText(_('Optional')) self.kfp.setEchoMode(QLineEdit.Password) self.kf_radio.toggled.connect(self.kf.setEnabled) self.kf_radio.toggled.connect(self.kfp.setEnabled) self.kf_radio.toggled.connect(kf_open_btn.setEnabled) self.kf_radio.toggled.connect(kfp_label.setEnabled) self.pw_radio.toggled.connect(self.kf.setDisabled) self.pw_radio.toggled.connect(self.kfp.setDisabled) self.pw_radio.toggled.connect(kf_open_btn.setDisabled) self.pw_radio.toggled.connect(kfp_label.setDisabled) # Button to fetch JSON files listing self.kf_fetch_conn_files_btn = QPushButton( _(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN)) self.kf_fetch_conn_files_btn.clicked.connect( self.fill_combobox_with_fetched_remote_connection_files) self.cb_remote_conn_files = QComboBox() self.cb_remote_conn_files.currentIndexChanged.connect( self._take_over_selected_remote_configuration_file) # Remote kernel groupbox self.start_remote_kernel_group = QGroupBox(_("Start remote kernel")) # Advanced settings to get remote connection files jupyter_runtime_location_cmd_label = QLabel( _('Command to get Jupyter runtime:')) self.jupyter_runtime_location_cmd_lineedit = QLineEdit() self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText( _(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME)) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(hn_label, 0, 0, 1, 2) ssh_layout.addWidget(self.hn, 0, 2) ssh_layout.addWidget(pn_label, 0, 3) ssh_layout.addWidget(self.pn, 0, 4) ssh_layout.addWidget(un_label, 1, 0, 1, 2) ssh_layout.addWidget(self.un, 1, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.kf_radio, 2, 0) auth_layout.addWidget(kf_label, 2, 1) auth_layout.addLayout(kf_layout, 2, 2) auth_layout.addWidget(kfp_label, 3, 1) auth_layout.addWidget(self.kfp, 3, 2) auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1) auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2) auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1) auth_layout.addWidget(self.cb_remote_conn_files, 5, 2) auth_group.setLayout(auth_layout) # Remote kernel layout rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(True) self.rm_group.toggled.connect(self.pw_radio.setChecked) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.save_connection_settings) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) # Save connection settings checkbox self.save_layout = QCheckBox(self) self.save_layout.setText(_("Save connection settings")) btns_layout = QHBoxLayout() btns_layout.addWidget(self.save_layout) btns_layout.addWidget(self.accept_btns) # Dialog layout layout = QVBoxLayout(self) layout.addWidget(main_label) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) layout.addLayout(cf_layout) layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addLayout(btns_layout) # List with connection file paths found on the remote host self.remote_conn_file_paths = [] self.load_connection_settings() def load_connection_settings(self): """Load the user's previously-saved kernel connection settings.""" existing_kernel = CONF.get("existing-kernel", "settings", {}) connection_file_path = existing_kernel.get("json_file_path", "") is_remote = existing_kernel.get("is_remote", False) username = existing_kernel.get("username", "") hostname = existing_kernel.get("hostname", "") port = str(existing_kernel.get("port", 22)) is_ssh_kf = existing_kernel.get("is_ssh_keyfile", False) ssh_kf = existing_kernel.get("ssh_key_file_path", "") cmd_jupyter_runtime = existing_kernel.get("cmd_jupyter_runtime") if connection_file_path != "": self.cf.setText(connection_file_path) if username != "": self.un.setText(username) if hostname != "": self.hn.setText(hostname) if ssh_kf != "": self.kf.setText(ssh_kf) if cmd_jupyter_runtime != "": self.jupyter_runtime_location_cmd_lineedit.setText( cmd_jupyter_runtime) self.rm_group.setChecked(is_remote) self.pn.setText(port) self.kf_radio.setChecked(is_ssh_kf) self.pw_radio.setChecked(not is_ssh_kf) try: import keyring ssh_passphrase = keyring.get_password("spyder_remote_kernel", "ssh_key_passphrase") ssh_password = keyring.get_password("spyder_remote_kernel", "ssh_password") if ssh_passphrase: self.kfp.setText(ssh_passphrase) if ssh_password: self.pw.setText(ssh_password) except Exception: pass def save_connection_settings(self): """Save user's kernel connection settings.""" if not self.save_layout.isChecked(): return is_ssh_key = bool(self.kf_radio.isChecked()) connection_settings = { "json_file_path": self.cf.text(), "is_remote": self.rm_group.isChecked(), "username": self.un.text(), "hostname": self.hn.text(), "port": self.pn.text(), "is_ssh_keyfile": is_ssh_key, "ssh_key_file_path": self.kf.text(), "cmd_jupyter_runtime": self.jupyter_runtime_location_cmd_lineedit.text() } CONF.set("existing-kernel", "settings", connection_settings) try: import keyring if is_ssh_key: keyring.set_password("spyder_remote_kernel", "ssh_key_passphrase", self.kfp.text()) else: keyring.set_password("spyder_remote_kernel", "ssh_password", self.pw.text()) except Exception: pass def select_connection_file(self): cf = getopenfilename(self, _('Select kernel connection file'), jupyter_runtime_dir(), '*.json;;*.*')[0] self.cf.setText(cf) def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.kf.setText(kf) def _take_over_selected_remote_configuration_file( self, chosen_idx_of_combobox_with_remote_conn_files): remote_path_filename = self.remote_conn_file_paths[ chosen_idx_of_combobox_with_remote_conn_files] self.cf.setText(remote_path_filename) def fill_combobox_with_fetched_remote_connection_files(self): """ Fill the combobox with found remote connection json files. :return: None """ _, username, _, only_host, port, keyfile, password = KernelConnectionDialog._get_remote_config( self) cmd_to_get_location_of_jupyter_runtime_files = self.jupyter_runtime_location_cmd_lineedit.text( ) self.remote_conn_file_paths = self._fetch_connection_files_list( host=only_host, keyfile=keyfile, password=password, username=username, port=port, cmd_to_get_location_of_jupyter_runtime_files= cmd_to_get_location_of_jupyter_runtime_files) conn_files_short = [ c.rsplit('/', 1)[1] if '/' in c else c for c in self.remote_conn_file_paths ] self.cb_remote_conn_files.addItems(conn_files_short) def _fetch_connection_files_list( self, host: str, keyfile: Optional[str], password: Optional[str], username: Optional[str], port: str, cmd_to_get_location_of_jupyter_runtime_files: Optional[str]): """ :param host: URL or IP of the host. :param keyfile: SSH key path or None if no key was provided. :param password: Password for SSH connection or None if no password is used. :rtype: List[str] :return: """ import paramiko client = paramiko.SSHClient() self.kf_fetch_conn_files_btn.setDisabled(True) list_of_copied_connection_files = [] try: client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=host, port=int(port), key_filename=keyfile, passphrase=password, username=username, timeout=10, auth_timeout=10) if cmd_to_get_location_of_jupyter_runtime_files is None: cmd_to_get_location_of_jupyter_runtime_files = self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME self.kf_fetch_conn_files_btn.setText( 'Getting location of jupyter runtime...') stdin, stdout, stderr = client.exec_command( cmd_to_get_location_of_jupyter_runtime_files) location_of_jupyter_runtime = stdout.readlines() if len(location_of_jupyter_runtime) > 0: location_of_jupyter_runtime = location_of_jupyter_runtime[ 0].strip() # get absolute paths stdin, stdout, stderr = client.exec_command( f'ls -d {location_of_jupyter_runtime}/*') list_of_connection_files = stdout.readlines() if len(list_of_connection_files) > 0: list_of_connection_files = [ l.strip() for l in list_of_connection_files ] import tempfile import os temp_dir = tempfile.gettempdir() only_filenames = [ f.rsplit('/', 1)[1] for f in list_of_connection_files ] list_of_copied_connection_files = [ os.path.join(temp_dir, f) for f in only_filenames ] self.kf_fetch_conn_files_btn.setText( f'Downloading {len(list_of_connection_files)} connection files...' ) for remote_path, filename_only in zip( list_of_connection_files, only_filenames): sftp = client.open_sftp() sftp.get(remote_path, os.path.join(temp_dir, filename_only)) sftp.close() else: show_info_dialog( "Warning", f"Could not find any jupyter configuration files in {location_of_jupyter_runtime}." ) else: show_info_dialog( "Warning", f"Could not extract jupyter runtime location. Error from command line: {stderr.readlines()}" ) finally: client.close() self.kf_fetch_conn_files_btn.setText( self.TEXT_FETCH_REMOTE_CONN_FILES_BTN) self.kf_fetch_conn_files_btn.setEnabled(True) return list_of_copied_connection_files @staticmethod def _get_remote_config(dialog): only_host = None username = None port = '22' if dialog.hn.text() and dialog.un.text(): port = dialog.pn.text() if dialog.pn.text() else '22' only_host = dialog.hn.text() username = dialog.un.text() hostname = "{0}@{1}:{2}".format(username, only_host, port) else: hostname = None if dialog.pw_radio.isChecked(): password = _falsy_to_none(dialog.pw.text()) keyfile = None elif dialog.kf_radio.isChecked(): keyfile = _falsy_to_none(dialog.kf.text()) password = _falsy_to_none(dialog.kfp.text()) else: # imposible? keyfile = None password = None return dialog.cf.text( ), username, hostname, only_host, port, keyfile, password @staticmethod def get_connection_parameters(parent=None, dialog=None): if not dialog: dialog = KernelConnectionDialog(parent) result = dialog.exec_() is_remote = bool(dialog.rm_group.isChecked()) accepted = result == QDialog.Accepted if is_remote: cf_text, _, hostname, _, _, keyfile, password = KernelConnectionDialog._get_remote_config( dialog) return cf_text, hostname, keyfile, password, accepted else: path = dialog.cf.text() _dir, filename = osp.dirname(path), osp.basename(path) if _dir == '' and not filename.endswith('.json'): path = osp.join(jupyter_runtime_dir(), 'kernel-' + path + '.json') return path, None, None, None, accepted
class EFMDialog(QDialog): """A dialog to set up EFM calculation""" def __init__(self, appdata: CnaData, centralwidget): QDialog.__init__(self) self.setWindowTitle("Elementary Flux Mode Computation") self.appdata = appdata self.centralwidget = centralwidget self.eng = appdata.engine self.out = io.StringIO() self.err = io.StringIO() self.layout = QVBoxLayout() l1 = QHBoxLayout() self.constraints = QCheckBox("consider 0 in current scenario as off") self.constraints.setCheckState(Qt.Checked) l1.addWidget(self.constraints) self.layout.addItem(l1) l2 = QHBoxLayout() self.flux_bounds = QGroupBox( "use flux bounds to calculate elementary flux vectors") self.flux_bounds.setCheckable(True) self.flux_bounds.setChecked(False) vbox = QVBoxLayout() label = QLabel("Threshold for bounds to be unconstrained") vbox.addWidget(label) self.threshold = QLineEdit("100") validator = QIntValidator() validator.setBottom(0) self.threshold.setValidator(validator) vbox.addWidget(self.threshold) self.flux_bounds.setLayout(vbox) l2.addWidget(self.flux_bounds) self.layout.addItem(l2) l3 = QHBoxLayout() self.check_reversibility = QCheckBox("check reversibility") self.check_reversibility.setCheckState(Qt.Checked) l3.addWidget(self.check_reversibility) self.layout.addItem(l3) l4 = QHBoxLayout() self.convex_basis = QCheckBox("only convex basis") l4.addWidget(self.convex_basis) self.layout.addItem(l4) l5 = QHBoxLayout() self.isozymes = QCheckBox("consider isozymes only once") l5.addWidget(self.isozymes) self.layout.addItem(l5) # TODO: choose solver l7 = QHBoxLayout() self.rational_numbers = QCheckBox("use rational numbers") l7.addWidget(self.rational_numbers) self.layout.addItem(l7) lx = QHBoxLayout() self.button = QPushButton("Compute") self.cancel = QPushButton("Close") lx.addWidget(self.button) lx.addWidget(self.cancel) self.layout.addItem(lx) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.compute) def compute(self): # create CobraModel for matlab self.appdata.create_cobra_model() legacy.read_cnapy_model(self.eng) # get some data reac_id = self.eng.get_reacID() # setting parameters a = self.eng.eval("constraints = {};", nargout=0, stdout=self.out, stderr=self.err) scenario = {} if self.constraints.checkState( ) == Qt.Checked or self.flux_bounds.isChecked(): onoff_str = "" for r in reac_id: if r in self.appdata.project.scen_values.keys(): (vl, vu) = self.appdata.project.scen_values[r] if vl == vu: if vl > 0: onoff_str = onoff_str + " NaN" # efmtool does not support 1 elif vl == 0: scenario[r] = (0, 0) onoff_str = onoff_str + " 0" else: onoff_str = onoff_str + " NaN" print("WARN: negative value in scenario") else: onoff_str = onoff_str + " NaN" print("WARN: not fixed value in scenario") else: onoff_str = onoff_str + " NaN" onoff_str = "reaconoff = [" + onoff_str + "];" a = self.eng.eval(onoff_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.reaconoff = reaconoff;", nargout=0, stdout=self.out, stderr=self.err) if self.flux_bounds.isChecked(): threshold = float(self.threshold.text()) lb_str = "" ub_str = "" for r in reac_id: c_reaction = self.appdata.project.cobra_py_model.reactions.get_by_id( r) if r in self.appdata.project.scen_values: (vl, vu) = self.appdata.project.scen_values[r] else: vl = c_reaction.lower_bound vu = c_reaction.upper_bound if vl <= -threshold: vl = "NaN" if vu >= threshold: vu = "NaN" if vl == 0 and vu == 0: # already in reaconoff, can be skipped here vl = "NaN" vu = "NaN" lb_str = lb_str + " " + str(vl) ub_str = ub_str + " " + str(vu) lb_str = "lb = [" + lb_str + "];" a = self.eng.eval(lb_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.lb = lb;", nargout=0, stdout=self.out, stderr=self.err) ub_str = "ub = [" + ub_str + "];" a = self.eng.eval(ub_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.ub = ub;", nargout=0, stdout=self.out, stderr=self.err) # TODO set solver 4 = EFMTool 3 = MetaTool, 1 = cna Mex file, 0 = cna functions a = self.eng.eval("solver = 4;", nargout=0, stdout=self.out, stderr=self.err) if self.check_reversibility.checkState() == Qt.Checked: a = self.eng.eval("irrev_flag = 1;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("irrev_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) # convex basis computation is only possible with METATOOL solver=3 if self.convex_basis.checkState() == Qt.Checked: a = self.eng.eval("conv_basis_flag = 1; solver = 3;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("conv_basis_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) if self.isozymes.checkState() == Qt.Checked: a = self.eng.eval("iso_flag = 1;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("iso_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) # default we have no macromolecules and display is et to ALL a = self.eng.eval("c_macro=[]; display= 'ALL';", nargout=0, stdout=self.out, stderr=self.err) if self.rational_numbers.checkState() == Qt.Checked: a = self.eng.eval( "efmtool_options = {'arithmetic', 'fractional'};", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("efmtool_options = {};", nargout=0, stdout=self.out, stderr=self.err) if self.appdata.is_matlab_set(): try: a = self.eng.eval( "[ems, irrev_ems, ems_idx, ray] = CNAcomputeEFM(cnap, constraints,solver,irrev_flag,conv_basis_flag,iso_flag,c_macro,display,efmtool_options);", nargout=0) except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning( self, 'Unknown exception occured!', exstr + '\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues' ) return else: ems = self.eng.workspace['ems'] idx = self.eng.workspace['ems_idx'] irreversible = numpy.squeeze(self.eng.workspace['irrev_ems']) unbounded = numpy.squeeze(self.eng.workspace['ray']) ems = numpy.array(ems) self.result2ui(ems, idx, reac_id, irreversible, unbounded, scenario) self.accept() elif self.appdata.is_octave_ready(): a = self.eng.eval( "[ems, irrev_ems, ems_idx, ray] = CNAcomputeEFM(cnap, constraints,solver,irrev_flag,conv_basis_flag,iso_flag,c_macro,display,efmtool_options);", nargout=0) ems = self.eng.pull('ems') idx = self.eng.pull('ems_idx') irreversible = numpy.squeeze(self.eng.pull('irrev_ems')) unbounded = numpy.squeeze(self.eng.pull('ray')) self.result2ui(ems, idx, reac_id, irreversible, unbounded, scenario) def result2ui(self, ems, idx, reac_id, irreversible, unbounded, scenario): if len(ems) == 0: QMessageBox.information( self, 'No modes', 'Modes have not been calculated or do not exist.') else: self.appdata.project.modes = FluxVectorContainer( ems, [reac_id[int(i) - 1] for i in idx[0]], irreversible, unbounded) self.centralwidget.mode_navigator.current = 0 self.centralwidget.mode_navigator.scenario = scenario self.centralwidget.mode_navigator.title.setText("Mode Navigation") self.centralwidget.update_mode()
class RemoteKernelSetupDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(RemoteKernelSetupDialog, self).__init__(parent) self.setWindowTitle(_('Setup remote kernel')) self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files' self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir' # Name of the connection cfg_name_label = QLabel(_('Configuration name:')) self.cfg_name_line_edit = QLineEdit() # SSH connection hostname_label = QLabel(_('Hostname:')) self.hostname_lineedit = QLineEdit() port_label = QLabel(_('Port:')) self.port_lineeidt = QLineEdit() self.port_lineeidt.setMaximumWidth(75) username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.keyfile_radio.toggled.connect(self.pw.setDisabled) self.keyfile_path_lineedit = QLineEdit() keyfile_browse_btn = QPushButton(_('Browse')) keyfile_browse_btn.clicked.connect(self.select_ssh_key) keyfile_layout = QHBoxLayout() keyfile_layout.addWidget(self.keyfile_path_lineedit) keyfile_layout.addWidget(keyfile_browse_btn) passphrase_label = QLabel(_('Passphrase:')) self.passphrase_lineedit = QLineEdit() self.passphrase_lineedit.setPlaceholderText(_('Optional')) self.passphrase_lineedit.setEchoMode(QLineEdit.Password) self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled) self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled) self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled) self.keyfile_radio.toggled.connect(passphrase_label.setEnabled) self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled) self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled) self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled) self.pw_radio.toggled.connect(passphrase_label.setDisabled) # Button to fetch JSON files listing # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN)) # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files) # self.cb_remote_conn_files = QComboBox() # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file) # Remote kernel groupbox self.start_remote_kernel_group = QGroupBox(_("Start remote kernel")) # Advanced settings to get remote connection files jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:')) self.jupyter_runtime_location_cmd_lineedit = QLineEdit() self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME)) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(cfg_name_label, 0, 0) ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2) ssh_layout.addWidget(hostname_label, 1, 0, 1, 2) ssh_layout.addWidget(self.hostname_lineedit, 1, 2) ssh_layout.addWidget(port_label, 1, 3) ssh_layout.addWidget(self.port_lineeidt, 1, 4) ssh_layout.addWidget(username_label, 2, 0, 1, 2) ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.keyfile_radio, 2, 0) auth_layout.addWidget(keyfile_label, 2, 1) auth_layout.addLayout(keyfile_layout, 2, 2) auth_layout.addWidget(passphrase_label, 3, 1) auth_layout.addWidget(self.passphrase_lineedit, 3, 2) auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1) auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2) # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1) # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2) auth_group.setLayout(auth_layout) # Remote kernel layout self.rm_group = QGroupBox(_("Setup up of a remote connection")) self.rm_group.setEnabled(False) rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(False) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) btns_layout = QHBoxLayout() btns_layout.addWidget(self.accept_btns) # Dialog layout layout = QVBoxLayout() layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addLayout(btns_layout) # Main layout hbox_layout = QHBoxLayout(self) # Left side with the list of all remote connection configurations items_label = QLabel(text="Configured remote locations") self.items_list = QListWidget() self.items_list.clicked.connect(self._on_items_list_click) items_layout = QVBoxLayout() items_layout.addWidget(items_label) items_layout.addWidget(self.items_list) edit_delete_new_buttons_layout = QHBoxLayout() edit_btn = QPushButton(text="Edit") add_btn = QPushButton(text="Add") delete_btn = QPushButton(text="Delete") add_btn.clicked.connect(self._on_add_btn_click) edit_btn.clicked.connect(self._on_edit_btn_click) delete_btn.clicked.connect(self._on_delete_btn_click) edit_delete_new_buttons_layout.addWidget(add_btn) edit_delete_new_buttons_layout.addWidget(edit_btn) edit_delete_new_buttons_layout.addWidget(delete_btn) items_layout.addLayout(edit_delete_new_buttons_layout) hbox_layout.addSpacerItem(QSpacerItem(10, 0)) hbox_layout.addLayout(items_layout) hbox_layout.addLayout(layout) self.lst_with_connecion_configs = [] def _on_items_list_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _clear_remote_connection_input_fields(self): self.keyfile_path_lineedit.setText("") self.passphrase_lineedit.setText("") self.hostname_lineedit.setText("") self.username_lineedit.setText("") self.port_lineeidt.setText("") self.cfg_name_line_edit.setText("") self.jupyter_runtime_location_cmd_lineedit.setText("") self.keyfile_radio.setChecked(False) self.pw_radio.setChecked(False) def _update_remote_connection_input_fields(self, remote_conn_settings): self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path) self.passphrase_lineedit.setText(remote_conn_settings.password) self.hostname_lineedit.setText(remote_conn_settings.hostname) self.username_lineedit.setText(remote_conn_settings.username) self.port_lineeidt.setText(str(remote_conn_settings.port)) self.cfg_name_line_edit.setText(remote_conn_settings.connection_name) self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location) self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None) self.pw_radio.setChecked(remote_conn_settings.password is not None) def _on_add_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings username = self.username_lineedit.text() passphrase = self.passphrase_lineedit.text() hostname = self.hostname_lineedit.text() keyfile_path = self.keyfile_path_lineedit.text() port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22 jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text() cfg_name = self.cfg_name_line_edit.text() cfg = RemoteConnectionSettings( username=username, hostname=hostname, keyfile_path=keyfile_path, port=port, connection_name=cfg_name, cmd_for_jupyter_runtime_location=jup_runtime_cmd, password=passphrase ) self.lst_with_connecion_configs.append(cfg) self._update_list_with_configs() self.rm_group.setEnabled(False) def _on_edit_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings self.rm_group.setEnabled(True) idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _on_delete_btn_click(self): idx_of_config = self.items_list.selectedIndexes()[0].row() self.lst_with_connecion_configs.pop(idx_of_config) self._update_list_with_configs() def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.keyfile_path_lineedit.setText(kf) def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files): remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files] self.cf.setText(remote_path_filename) def set_connection_configs(self, lst_with_connecion_configs): self.lst_with_connecion_configs = lst_with_connecion_configs self._update_list_with_configs() def _update_list_with_configs(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings # now, fill the list self.items_list.clear() for cfg in self.lst_with_connecion_configs: if isinstance(cfg, LocalConnectionSettings): self.items_list.addItem(f"Local: {cfg.connection_name}") elif isinstance(cfg, RemoteConnectionSettings): self.items_list.addItem(f"Remote: {cfg.connection_name}") def get_connection_settings(self): return self.lst_with_connecion_configs
class ImageRotationDialog(ExToolWindow): def __init__(self, signal, axes, parent, plugin): super(ImageRotationDialog, self).__init__(parent) self.ui = parent self.create_controls() self.accepted.connect(self.ok) self.rejected.connect(self.close_new) self.signal = signal self.plugin = plugin self.new_out = None self._connected_updates = False if isinstance(axes, str): axm = signal.signal.axes_manager if axes.startswith("nav"): axes = (axm._axes.index(axm.navigation_axes[0]), axm._axes.index(axm.navigation_axes[1])) elif axes.startswith("sig"): axes = (axm._axes.index(axm.signal_axes[0]), axm._axes.index(axm.signal_axes[1])) self.axes = axes self.setWindowTitle(tr("Rotate")) # TODO: TAG: Functionality check if not hasattr(signal.signal, 'events'): self.gbo_preview.setVisible(False) # TODO: Add dynamic rotation, e.g. one that rotates when source # signal's data_changed event triggers def connect(self): # TODO: Don't have to con/dis those in gbo self.opt_new.toggled.connect(self.close_new) self.num_angle.valueChanged.connect(self.update) self.chk_grid.toggled.connect(self.update) self.num_grid.valueChanged.connect(self.update) self.chk_reshape.toggled.connect(self.update) self.opt_new.toggled.connect(self.update) self.opt_replace.toggled.connect(self.update) def disconnect(self): self.num_angle.valueChanged.disconnect(self.update) self.chk_grid.toggled.disconnect(self.update) self.num_grid.valueChanged.disconnect(self.update) self.chk_reshape.toggled.disconnect(self.update) self.opt_new.toggled.disconnect(self.update) self.opt_replace.toggled.disconnect(self.update) def ok(self): # Draw figure if not already done # TODO: TAG: Functionality check if not hasattr(self.signal.signal, 'events') or \ not self.gbo_preview.isChecked(): self.update() angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() self.plugin.record_code( r"<p>.rotate_signal({0}, reshape={1}, axes={2})".format( angle, reshape, self.axes)) # Clean up event connections if self.new_out is not None: self.connect_update_plot(self.new_out.signal, disconnect=True) def close_new(self, value=False): if self.new_out is not None and not value: self.new_out.close() self.new_out = None self._connected_updates = False def set_preview(self, value): if not hasattr(self.signal.signal, 'events'): return if value: self.connect() self.update() else: self.disconnect() self.close_new() def _axes_in_nav(self): axm = self.signal.signal.axes_manager navidx = [axm._axes.index(ax) for ax in axm.navigation_axes] if self.axes[0] in navidx: return True return False def connect_update_plot(self, signal, disconnect=False): if self._connected_updates != disconnect: return # Nothing to do, prevent double connections if self._axes_in_nav(): f = signal._plot.navigator_plot.update else: f = signal._plot.signal_plot.update # TODO: TAG: Functionality check if hasattr(signal, 'events') and hasattr( signal.events, 'data_changed'): if disconnect: signal.events.data_changed.disconnect(f) else: signal.events.data_changed.connect(f, []) self._connected_updates = not disconnect def update(self): angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() if self.opt_new.isChecked(): if self.new_out is None: out = None else: out = self.new_out.signal elif self.opt_replace.isChecked(): out = self.signal.signal else: return # Indeterminate state, do nothing s = self.plugin.rotate_signal(angle, self.signal.signal, record=False, reshape=reshape, out=out, axes=self.axes) if out is None: s.metadata.General.title = self.signal.name + "[Rotated]" s.plot() self.connect_update_plot(s) if (self.gbo_preview.isChecked() and self.opt_new.isChecked() and self.new_out is None): self.new_out = self.ui.lut_signalwrapper[s] else: s = out if self.chk_grid.isChecked() is True: pass # TODO: Draw grid def create_controls(self): """ Create UI controls. """ vbox = QVBoxLayout() form = QFormLayout() self.num_angle = QDoubleSpinBox() self.num_angle.setValue(0.0) self.num_angle.setMinimum(-360) self.num_angle.setMaximum(360) form.addRow(tr("Angle:"), self.num_angle) vbox.addLayout(form) self.gbo_preview = QGroupBox(tr("Preview")) self.gbo_preview.setCheckable(True) self.gbo_preview.setChecked(False) gbo_vbox = QVBoxLayout() self.chk_grid = QCheckBox(tr("Grid")) self.chk_grid.setChecked(False) self.num_grid = QSpinBox() self.num_grid.setValue(4) self.num_grid.setMinimum(1) self.num_grid.setEnabled(False) self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled) gbo_vbox.addWidget(self.chk_grid) gbo_vbox.addWidget(self.num_grid) self.gbo_preview.setLayout(gbo_vbox) vbox.addWidget(self.gbo_preview) self.gbo_preview.toggled[bool].connect(self.set_preview) self.gbo_output = QGroupBox(tr("Output")) self.opt_new = QRadioButton(tr("New signal")) self.opt_replace = QRadioButton(tr("In place")) self.opt_new.setChecked(True) gbo_vbox2 = QVBoxLayout() gbo_vbox2.addWidget(self.opt_new) gbo_vbox2.addWidget(self.opt_replace) self.gbo_output.setLayout(gbo_vbox2) vbox.addWidget(self.gbo_output) self.chk_reshape = QCheckBox(tr("Resize to fit")) self.chk_reshape.setChecked(False) vbox.addWidget(self.chk_reshape) self.btn_ok = QPushButton(tr("&OK")) self.btn_ok.setDefault(True) self.btn_ok.clicked.connect(self.accept) self.btn_cancel = QPushButton(tr("&Cancel")) self.btn_cancel.clicked.connect(self.reject) hbox = QHBoxLayout() hbox.addWidget(self.btn_ok) hbox.addWidget(self.btn_cancel) vbox.addLayout(hbox) vbox.addStretch(1) self.setLayout(vbox)
class FitMapsWidget(FormBaseWidget): # Signal that is sent (to main window) to update global state of the program update_global_state = Signal() computations_complete = Signal(object) signal_map_fitting_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): v_spacing = global_gui_parameters["vertical_spacing_in_tabs"] self._setup_settings() self._setup_start_fitting() self._setup_compute_roi_maps() self._setup_save_results() self._setup_quantitative_analysis() vbox = QVBoxLayout() vbox.addWidget(self.group_settings) vbox.addSpacing(v_spacing) vbox.addWidget(self.pb_start_map_fitting) vbox.addWidget(self.pb_compute_roi_maps) vbox.addSpacing(v_spacing) vbox.addWidget(self.group_save_results) vbox.addWidget(self.group_quant_analysis) self.setLayout(vbox) self._set_tooltips() # Timer is currently used to simulate processing self._timer = None self._timer_counter = 0 def _setup_settings(self): self.group_settings = QGroupBox("Options") self._dset_n_rows = 1 self._dset_n_cols = 1 self._area_row_min = 1 self._area_row_max = 1 self._area_col_min = 1 self._area_col_max = 1 self._validator_selected_area = IntValidatorStrict() self.le_start_row = LineEditExtended() self.le_start_row.textChanged.connect(self.le_start_row_text_changed) self.le_start_row.editingFinished.connect(self.le_start_row_editing_finished) self.le_start_col = LineEditExtended() self.le_start_col.textChanged.connect(self.le_start_col_text_changed) self.le_start_col.editingFinished.connect(self.le_start_col_editing_finished) self.le_end_row = LineEditExtended() self.le_end_row.textChanged.connect(self.le_end_row_text_changed) self.le_end_row.editingFinished.connect(self.le_end_row_editing_finished) self.le_end_col = LineEditExtended() self.le_end_col.textChanged.connect(self.le_end_col_text_changed) self.le_end_col.editingFinished.connect(self.le_end_col_editing_finished) self.group_save_plots = QGroupBox("Save spectra for pixels in the selected region") self.group_save_plots.setCheckable(True) self.group_save_plots.setChecked(False) self.group_save_plots.toggled.connect(self.group_save_plots_toggled) vbox = QVBoxLayout() grid = QGridLayout() grid.addWidget(QLabel("Start row:"), 0, 0) grid.addWidget(self.le_start_row, 0, 1) grid.addWidget(QLabel("column:"), 0, 2) grid.addWidget(self.le_start_col, 0, 3) grid.addWidget(QLabel("End row:"), 1, 0) grid.addWidget(self.le_end_row, 1, 1) grid.addWidget(QLabel("column:"), 1, 2) grid.addWidget(self.le_end_col, 1, 3) vbox.addLayout(grid) self.group_save_plots.setLayout(vbox) vbox = QVBoxLayout() vbox.addWidget(self.group_save_plots) self.group_settings.setLayout(vbox) def _setup_start_fitting(self): self.pb_start_map_fitting = QPushButton("Start XRF Map Fitting") self.pb_start_map_fitting.clicked.connect(self.pb_start_map_fitting_clicked) def _setup_compute_roi_maps(self): self.pb_compute_roi_maps = QPushButton("Compute XRF Maps Based on ROI ...") self.pb_compute_roi_maps.clicked.connect(self.pb_compute_roi_maps_clicked) def _setup_save_results(self): self.group_save_results = QGroupBox("Save Results") self.pb_save_to_db = QPushButton("Save to Database (Databroker) ...") self.pb_save_to_db.setEnabled(False) self.pb_save_q_calibration = QPushButton("Save Quantitative Calibration ...") self.pb_save_q_calibration.clicked.connect(self.pb_save_q_calibration_clicked) self.pb_export_to_tiff_and_txt = QPushButton("Export to TIFF and TXT ...") self.pb_export_to_tiff_and_txt.clicked.connect(self.pb_export_to_tiff_and_txt_clicked) grid = QGridLayout() grid.addWidget(self.pb_save_to_db, 0, 0, 1, 2) grid.addWidget(self.pb_save_q_calibration, 1, 0, 1, 2) grid.addWidget(self.pb_export_to_tiff_and_txt, 2, 0, 1, 2) self.group_save_results.setLayout(grid) def _setup_quantitative_analysis(self): self.group_quant_analysis = QGroupBox("Quantitative Analysis") self.pb_load_quant_calib = QPushButton("Load Quantitative Calibration ...") self.pb_load_quant_calib.clicked.connect(self.pb_load_quant_calib_clicked) vbox = QVBoxLayout() vbox.addWidget(self.pb_load_quant_calib) self.group_quant_analysis.setLayout(vbox) def _set_tooltips(self): set_tooltip( self.group_settings, "Raw spectra of individual pixels are saved as <b>.png</b> files for " "the selected region of the map." "The region is selected by specifying the <b>Start</b> and <b>End</b> coordinates " "(ranges of rows and columns) in pixels. The first and last rows and columns are " "included in the selection.", ) set_tooltip( self.le_start_row, "Number of the <b>first row</b> of the map to be included in the selection. " "The number must be less than the number entered into 'End row' box.", ) set_tooltip( self.le_start_col, "Number of the <b>first column</b> of the map to be included in the selection. " "The number must be less than the number entered into 'End column' box.", ) set_tooltip( self.le_end_row, "Number of the <b>last row</b> included in the selection. " "The number must be greater than the number entered into 'Start row' box.", ) set_tooltip( self.le_end_col, "Number of the <b>last column</b> included in the selection. " "The number must be greater than the number entered into 'Start column' box.", ) set_tooltip( self.pb_start_map_fitting, "Click to start <b>fitting of the XRF Maps</b>. The generated XRF Maps can be viewed " "in <b>'XRF Maps' tab</b>", ) set_tooltip( self.pb_compute_roi_maps, "Opens the window for setting up <b>spectral ROIs</b> and computating XRF Maps based on the ROIs", ) set_tooltip(self.pb_save_to_db, "Save generated XRF Maps to a <b>database</b> via Databroker") set_tooltip( self.pb_save_q_calibration, "Opens a Dialog Box which allows to preview and save <b>Quantitative Calibration data</b>", ) set_tooltip( self.pb_export_to_tiff_and_txt, "Open a Dialog box which allows to export XRF Maps as <b>TIFF</b> and <b>TXT</b> files", ) set_tooltip( self.pb_load_quant_calib, "Open a window with GUI tools for loading and managing previously saved " "<b>Quantitative Calibration data</b> used for processing (normalization) " "of XRF Maps. The loaded calibration data is applied to XRF Maps if 'Quantitative' " "box is checked in 'XRF Maps' tab", ) def update_widget_state(self, condition=None): if condition == "tooltips": self._set_tooltips() state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"] state_model_exist = self.gui_vars["gui_state"]["state_model_exists"] state_xrf_map_exists = self.gui_vars["gui_state"]["state_xrf_map_exists"] self.group_settings.setEnabled(state_file_loaded & state_model_exist) self.pb_start_map_fitting.setEnabled(state_file_loaded & state_model_exist) self.pb_compute_roi_maps.setEnabled(state_file_loaded & state_model_exist) self.group_save_results.setEnabled(state_xrf_map_exists) self.group_quant_analysis.setEnabled(state_xrf_map_exists) def slot_update_for_new_loaded_run(self): self.gpc.set_enable_save_spectra(False) selected_area = {"row_min": 1, "row_max": 1, "col_min": 1, "col_max": 1} self.gpc.set_selection_area_save_spectra(selected_area) self._update_area_selection_controls() def pb_compute_roi_maps_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_compute_roi_maps.position_once(pos.x(), pos.y()) if not self.ref_main_window.wnd_compute_roi_maps.isVisible(): self.ref_main_window.wnd_compute_roi_maps.show() self.ref_main_window.wnd_compute_roi_maps.activateWindow() def pb_save_q_calibration_clicked(self): msg = "" if not self.gpc.is_quant_standard_selected(): # This is a safeguard. The button should be disabled if no standard is selected. msg += ( "No quantitative standard is selected. " "Use 'Load Quantitative Standard ...' button in 'Model' tab" ) if not self.gpc.is_quant_standard_fitting_available(): msg = msg + "\n" if msg else msg msg += ( "Select a dataset containing fitted XRF maps for the quantitative standard " "in XRF Maps tab (dataset name must end with 'fit')." ) if msg: msgbox = QMessageBox( QMessageBox.Information, "Additional Steps Needed", msg, QMessageBox.Ok, parent=self ) msgbox.exec() return try: params = self.gpc.get_displayed_quant_calib_parameters() distance_to_sample = params["distance_to_sample"] file_name = params["suggested_file_name"] preview = params["preview"] file_dir = self.gpc.get_current_working_directory() file_dir = os.path.expanduser(file_dir) file_path = os.path.join(file_dir, file_name) dlg = DialogSaveCalibration(file_path=file_path) dlg.distance_to_sample = distance_to_sample dlg.preview = preview res = dlg.exec() if res: self.gpc.save_quantitative_standard(dlg.file_path, dlg.overwrite_existing) logger.info(f"Quantitative calibration was saved to the file '{dlg.file_path}'") else: logger.info("Saving quantitative calibration was cancelled.") # We want to save distance to sample even if saving was cancelled self.gpc.save_distance_to_sample(dlg.distance_to_sample) except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def pb_export_to_tiff_and_txt_clicked(self): # TODO: Propagate full path to the saved file here dir_path = self.gpc.get_current_working_directory() dir_path = os.path.expanduser(dir_path) params = self.gpc.get_parameters_for_exporting_maps() dlg = DialogExportToTiffAndTxt(dir_path=dir_path) dlg.dset_list = params["dset_list"] dlg.dset_sel = params["dset_sel"] dlg.scaler_list = params["scaler_list"] dlg.scaler_sel = params["scaler_sel"] dlg.interpolate_on = params["interpolate_on"] dlg.quant_norm_on = params["quant_norm_on"] res = dlg.exec() if res: try: result_path = dlg.dir_path dataset_name = dlg.get_selected_dset_name() scaler_name = dlg.get_selected_scaler_name() interpolate_on = dlg.interpolate_on quant_norm_on = dlg.quant_norm_on file_formats = [] if dlg.save_tiff: file_formats.append("tiff") if dlg.save_txt: file_formats.append("txt") self.gpc.export_xrf_maps( results_path=result_path, dataset_name=dataset_name, scaler_name=scaler_name, interpolate_on=interpolate_on, quant_norm_on=quant_norm_on, file_formats=file_formats, ) if file_formats: formats_text = " and ".join([_.upper() for _ in file_formats]) else: formats_text = "No" msg = f"{formats_text} files were saved to the directory '{result_path}'" logger.info(msg) msgbox = QMessageBox(QMessageBox.Information, "Files Saved", msg, QMessageBox.Ok, parent=self) msgbox.exec() except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def pb_load_quant_calib_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_load_quantitative_calibration.position_once(pos.x(), pos.y()) if not self.ref_main_window.wnd_load_quantitative_calibration.isVisible(): self.ref_main_window.wnd_load_quantitative_calibration.show() self.ref_main_window.wnd_load_quantitative_calibration.activateWindow() def pb_start_map_fitting_clicked(self): def cb(): try: self.gpc.fit_individual_pixels() success, msg = True, "" except Exception as ex: success, msg = False, str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_start_map_fitting_clicked) @Slot(object) def slot_start_map_fitting_clicked(self, result): self._recover_after_compute(self.slot_start_map_fitting_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 Fit Individual Pixel Spectra", msg, QMessageBox.Ok, parent=self ) msgbox.exec() self.signal_map_fitting_complete.emit() self.update_global_state.emit() if success: self.signal_activate_tab_xrf_maps.emit() """ @Slot() def timerExpired(self): self._timer_counter += 1 progress_bar = self.ref_main_window.statusProgressBar progress_bar.setValue(self._timer_counter) if self._timer_counter >= 100: self._timer.stop() self._timer.timeout.disconnect(self.timerExpired) self._timer = None progress_bar.setValue(0) status_bar = self.ref_main_window.statusBar() status_bar.showMessage("XRF Maps are generated. " "Results are presented in 'XRF Maps' tab.", 5000) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit() """ def group_save_plots_toggled(self, state): self.gpc.set_enable_save_spectra(state) def le_start_row_text_changed(self, text): valid = self._validate_row_number(text) and int(text) <= self._area_row_max self.le_start_row.setValid(valid) def le_start_row_editing_finished(self): text = self.le_start_row.text() valid = self._validate_row_number(text) and int(text) <= self._area_row_max if valid: self._save_selected_area(row_min=int(text)) else: self._show_selected_area() def le_end_row_text_changed(self, text): valid = self._validate_row_number(text) and int(text) >= self._area_row_min self.le_end_row.setValid(valid) def le_end_row_editing_finished(self): text = self.le_end_row.text() valid = self._validate_row_number(text) and int(text) >= self._area_row_min if valid: self._save_selected_area(row_max=int(text)) else: self._show_selected_area() def le_start_col_text_changed(self, text): valid = self._validate_col_number(text) and int(text) <= self._area_col_max self.le_start_col.setValid(valid) def le_start_col_editing_finished(self): text = self.le_start_col.text() valid = self._validate_col_number(text) and int(text) <= self._area_col_max if valid: self._save_selected_area(col_min=int(text)) else: self._show_selected_area() def le_end_col_text_changed(self, text): valid = self._validate_col_number(text) and int(text) >= self._area_col_min self.le_end_col.setValid(valid) def le_end_col_editing_finished(self): text = self.le_end_col.text() valid = self._validate_col_number(text) and int(text) >= self._area_col_min if valid: self._save_selected_area(col_max=int(text)) else: self._show_selected_area() def _validate_row_number(self, value_str): if self._validator_selected_area.validate(value_str, 0)[0] != IntValidatorStrict.Acceptable: return False value = int(value_str) if 1 <= value <= self._dset_n_rows: return True else: return False def _validate_col_number(self, value_str): if self._validator_selected_area.validate(value_str, 0)[0] != IntValidatorStrict.Acceptable: return False value = int(value_str) if 1 <= value <= self._dset_n_cols: return True else: return False def _update_area_selection_controls(self): map_size = self.gpc.get_dataset_map_size() map_size = (1, 1) if map_size is None else map_size self._dset_n_rows, self._dset_n_cols = map_size self.group_save_plots.setChecked(self.gpc.get_enable_save_spectra()) area = self.gpc.get_selection_area_save_spectra() self._area_row_min = area["row_min"] self._area_row_max = area["row_max"] self._area_col_min = area["col_min"] self._area_col_max = area["col_max"] self._show_selected_area() def _show_selected_area(self): self.le_start_row.setText(f"{self._area_row_min}") self.le_end_row.setText(f"{self._area_row_max}") self.le_start_col.setText(f"{self._area_col_min}") self.le_end_col.setText(f"{self._area_col_max}") def _save_selected_area(self, row_min=None, row_max=None, col_min=None, col_max=None): if row_min is not None: self._area_row_min = row_min if row_max is not None: self._area_row_max = row_max if col_min is not None: self._area_col_min = col_min if col_max is not None: self._area_col_max = col_max area = { "row_min": self._area_row_min, "row_max": self._area_row_max, "col_min": self._area_col_min, "col_max": self._area_col_max, } self.gpc.set_selection_area_save_spectra(area) self._update_area_selection_controls() 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()