class PreferencesDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.contentsWidget = QListWidget() self.pagesWidget = QStackedWidget() self.plotPref = PlotPreferences(parent) self.dataPref = DataPreferences(parent) self.pagesWidget.addWidget(self.plotPref) self.pagesWidget.addWidget(self.dataPref) self.contentsWidget.addItem("Plot") self.contentsWidget.addItem("Data") self.contentsWidget.currentItemChanged.connect(self.changePage) self.contentsWidget.setCurrentRow(0) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Close) okButton = self.buttonBox.button(QDialogButtonBox.Ok) okButton.clicked.connect(self.ok) applyButton = self.buttonBox.button(QDialogButtonBox.Apply) applyButton.clicked.connect(self.apply) closeButton = self.buttonBox.button(QDialogButtonBox.Close) closeButton.clicked.connect(self.close) horizontalLayout = QHBoxLayout() horizontalLayout.addWidget(self.contentsWidget) horizontalLayout.addWidget(self.pagesWidget) self.contentsWidget.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) mainLayout = QVBoxLayout() mainLayout.addLayout(horizontalLayout) mainLayout.addWidget(self.buttonBox) self.setLayout(mainLayout) self.setWindowTitle("Preferences") def changePage(self, current, previous): if not current: current = previous self.pagesWidget.setCurrentIndex(self.contentsWidget.row(current)) def show(self): for n in range(self.pagesWidget.count()): self.pagesWidget.widget(n).setCurrentValues() super().show() def apply(self): self.pagesWidget.currentWidget().apply() def ok(self): for idx in range(self.pagesWidget.count()): self.pagesWidget.widget(idx).apply() self.accept()
class Editor(QDialog): """Basic scene editor.""" def __init__(self, parent: MainWindow, renderers: List[Renderer]) -> None: """Initialize the Editor.""" super().__init__(parent=parent) self.renderers = renderers self.tree_widget = QTreeWidget() self.tree_widget.setHeaderHidden(True) self.stacked_widget = QStackedWidget() self.layout = QHBoxLayout() self.layout.addWidget(self.tree_widget) self.layout.addWidget(self.stacked_widget) def _selection_callback() -> None: for item in self.tree_widget.selectedItems(): widget_idx = item.data(0, Qt.ItemDataRole.UserRole) self.stacked_widget.setCurrentIndex(widget_idx) self.tree_widget.itemSelectionChanged.connect(_selection_callback) self.setLayout(self.layout) self.setWindowTitle("Editor") self.setModal(True) self.update() def update(self) -> None: """Update the internal widget list.""" self.tree_widget.clear() for idx, renderer in enumerate(self.renderers): actors = renderer._actors # pylint: disable=protected-access widget_idx = self.stacked_widget.addWidget( _get_renderer_widget(renderer)) top_item = QTreeWidgetItem(self.tree_widget, ["Renderer {}".format(idx)]) top_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx) self.tree_widget.addTopLevelItem(top_item) for name, actor in actors.items(): if actor is not None: widget_idx = self.stacked_widget.addWidget( _get_actor_widget(actor)) child_item = QTreeWidgetItem(top_item, [name]) child_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx) top_item.addChild(child_item) self.tree_widget.expandAll() def toggle(self) -> None: """Toggle the editor visibility.""" self.update() if self.isVisible(): self.hide() else: self.show()
class MixerPanel(QFrame): '''A color mixer to hook up to an image. You pass the image you the panel to operate on and it operates on that image in place. You also pass a callback to be called to trigger a refresh. This callback is called every time the mixer modifies your image.''' def __init__(self, img): QFrame.__init__(self) # self.setFrameStyle(QFrame.Box | QFrame.Sunken) self.img = img self.mixer = ColorMixer(self.img) self.callback = None #--------------------------------------------------------------- # ComboBox #--------------------------------------------------------------- self.combo_box_entries = [ 'RGB Color', 'HSV Color', 'Brightness/Contrast', 'Gamma', 'Gamma (Sigmoidal)' ] self.combo_box = QComboBox() for entry in self.combo_box_entries: self.combo_box.addItem(entry) self.combo_box.currentIndexChanged.connect(self.combo_box_changed) #--------------------------------------------------------------- # RGB color sliders #--------------------------------------------------------------- # radio buttons self.rgb_add = QRadioButton('Additive') self.rgb_mul = QRadioButton('Multiplicative') self.rgb_mul.toggled.connect(self.rgb_radio_changed) self.rgb_add.toggled.connect(self.rgb_radio_changed) # sliders rs = IntelligentSlider('R', 0.51, -255, self.rgb_changed) gs = IntelligentSlider('G', 0.51, -255, self.rgb_changed) bs = IntelligentSlider('B', 0.51, -255, self.rgb_changed) self.rs = rs self.gs = gs self.bs = bs self.rgb_widget = QWidget() self.rgb_widget.layout = QGridLayout(self.rgb_widget) self.rgb_widget.layout.addWidget(self.rgb_add, 0, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rgb_mul, 1, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rs, 2, 0) self.rgb_widget.layout.addWidget(self.gs, 2, 1) self.rgb_widget.layout.addWidget(self.bs, 2, 2) #--------------------------------------------------------------- # HSV sliders #--------------------------------------------------------------- # radio buttons self.hsv_add = QRadioButton('Additive') self.hsv_mul = QRadioButton('Multiplicative') self.hsv_mul.toggled.connect(self.hsv_radio_changed) self.hsv_mul.toggled.connect(self.hsv_radio_changed) # sliders hs = IntelligentSlider('H', 0.36, -180, self.hsv_changed) ss = IntelligentSlider('S', 0.002, 0, self.hsv_changed) vs = IntelligentSlider('V', 0.002, 0, self.hsv_changed) self.hs = hs self.ss = ss self.vs = vs self.hsv_widget = QWidget() self.hsv_widget.layout = QGridLayout(self.hsv_widget) self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hs, 2, 0) self.hsv_widget.layout.addWidget(self.ss, 2, 1) self.hsv_widget.layout.addWidget(self.vs, 2, 2) #--------------------------------------------------------------- # Brightness/Contrast sliders #--------------------------------------------------------------- # sliders cont = IntelligentSlider('x', 0.002, 0, self.bright_changed) bright = IntelligentSlider('+', 0.51, -255, self.bright_changed) self.cont = cont self.bright = bright # layout self.bright_widget = QWidget() self.bright_widget.layout = QGridLayout(self.bright_widget) self.bright_widget.layout.addWidget(self.cont, 0, 0) self.bright_widget.layout.addWidget(self.bright, 0, 1) #---------------------------------------------------------------------- # Gamma Slider #---------------------------------------------------------------------- gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed) self.gamma = gamma # layout self.gamma_widget = QWidget() self.gamma_widget.layout = QGridLayout(self.gamma_widget) self.gamma_widget.layout.addWidget(self.gamma, 0, 0) #--------------------------------------------------------------- # Sigmoid Gamma sliders #--------------------------------------------------------------- # sliders alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed) beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed) self.a_gamma = alpha self.b_gamma = beta # layout self.sig_gamma_widget = QWidget() self.sig_gamma_widget.layout = QGridLayout(self.sig_gamma_widget) self.sig_gamma_widget.layout.addWidget(self.a_gamma, 0, 0) self.sig_gamma_widget.layout.addWidget(self.b_gamma, 0, 1) #--------------------------------------------------------------- # Buttons #--------------------------------------------------------------- self.commit_button = QPushButton('Commit') self.commit_button.clicked.connect(self.commit_changes) self.revert_button = QPushButton('Revert') self.revert_button.clicked.connect(self.revert_changes) #--------------------------------------------------------------- # Mixer Layout #--------------------------------------------------------------- self.sliders = QStackedWidget() self.sliders.addWidget(self.rgb_widget) self.sliders.addWidget(self.hsv_widget) self.sliders.addWidget(self.bright_widget) self.sliders.addWidget(self.gamma_widget) self.sliders.addWidget(self.sig_gamma_widget) self.layout = QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.sliders, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) #--------------------------------------------------------------- # State Initialization #--------------------------------------------------------------- self.combo_box.setCurrentIndex(0) self.rgb_mul.setChecked(True) self.hsv_mul.setChecked(True) def set_callback(self, callback): self.callback = callback def combo_box_changed(self, index): self.sliders.setCurrentIndex(index) self.reset() def rgb_radio_changed(self): self.reset() def hsv_radio_changed(self): self.reset() def reset(self): self.reset_sliders() self.mixer.set_to_stateimg() if self.callback: self.callback() def reset_sliders(self): # handle changing the conversion factors necessary if self.rgb_add.isChecked(): self.rs.set_conv_fac(0.51, -255) self.rs.set_value(0) self.gs.set_conv_fac(0.51, -255) self.gs.set_value(0) self.bs.set_conv_fac(0.51, -255) self.bs.set_value(0) else: self.rs.set_conv_fac(0.002, 0) self.rs.set_value(1.) self.gs.set_conv_fac(0.002, 0) self.gs.set_value(1.) self.bs.set_conv_fac(0.002, 0) self.bs.set_value(1.) self.hs.set_value(0) if self.hsv_add.isChecked(): self.ss.set_conv_fac(0.002, -1) self.ss.set_value(0) self.vs.set_conv_fac(0.002, -1) self.vs.set_value(0) else: self.ss.set_conv_fac(0.002, 0) self.ss.set_value(1.) self.vs.set_conv_fac(0.002, 0) self.vs.set_value(1.) self.bright.set_value(0) self.cont.set_value(1.) self.gamma.set_value(1) self.a_gamma.set_value(1) self.b_gamma.set_value(0.5) def rgb_changed(self, name, val): if name == 'R': channel = self.mixer.RED elif name == 'G': channel = self.mixer.GREEN else: channel = self.mixer.BLUE if self.rgb_mul.isChecked(): self.mixer.multiply(channel, val) elif self.rgb_add.isChecked(): self.mixer.add(channel, val) else: pass if self.callback: self.callback() def hsv_changed(self, name, val): h = self.hs.val() s = self.ss.val() v = self.vs.val() if self.hsv_mul.isChecked(): self.mixer.hsv_multiply(h, s, v) elif self.hsv_add.isChecked(): self.mixer.hsv_add(h, s, v) else: pass if self.callback: self.callback() def bright_changed(self, name, val): b = self.bright.val() c = self.cont.val() self.mixer.brightness(c, b) if self.callback: self.callback() def gamma_changed(self, name, val): self.mixer.gamma(val) if self.callback: self.callback() def sig_gamma_changed(self, name, val): ag = self.a_gamma.val() bg = self.b_gamma.val() self.mixer.sigmoid_gamma(ag, bg) if self.callback: self.callback() def commit_changes(self): self.mixer.commit_changes() self.reset_sliders() def revert_changes(self): self.mixer.revert() self.reset_sliders() if self.callback: self.callback()
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" resized = Signal(QSize) def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # Because there are multiple pages, need to keep a list of values sets. self._values_orig_set_list = [] self._values_set_list = [] for _key, setting in SETTINGS.schemas().items(): schema = json.loads(setting['json_schema']) # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties self._values_orig_set_list.append(set(values.items())) self._values_set_list.append(set(values.items())) # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" widget = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) widget.valueChanged.connect(self._reset_widgets) widget.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to SETTINGS.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. for n in range(self._stack.count()): # Must set the current row so that the proper set list is updated # in check differences. self._list.setCurrentRow(n) self.check_differences( self._values_orig_set_list[n], self._values_set_list[n], ) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ builder = WidgetBuilder() form = builder.create_form(schema, {}) # set state values for widget form.widget.state = values form.widget.on_changed.connect(lambda d: self.check_differences( set(d.items()), self._values_set_list[self._list.currentIndex().row()], )) return form def check_differences(self, new_set, values_set): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_set : set The set of new values, with tuples of key value pairs for each setting. values_set : set The old set of values. """ page = self._list.currentItem().text().split(" ")[0].lower() different_values = list(new_set - values_set) if len(different_values) > 0: # change the values in SETTINGS for val in different_values: try: setattr(SETTINGS._settings[page], val[0], val[1]) self._values_set_list[ self._list.currentIndex().row()] = new_set except: # noqa: E722 continue
class MultiTurnData(BaseWidget): def __init__(self, parent=None, acq_type='ACQ', prefix='', bpm=''): self.acq_type = acq_type data_prefix = 'GEN_' if acq_type == 'ACQ' else 'PM_' super().__init__( parent=parent, prefix=prefix, bpm=bpm, data_prefix=data_prefix) self.samplespre = SiriusConnectionSignal(self.get_pvname( acq_type+'SamplesPre-RB', is_data=False)) self.samplespost = SiriusConnectionSignal(self.get_pvname( acq_type+'SamplesPost-RB', is_data=False)) self.shots = SiriusConnectionSignal(self.get_pvname( acq_type+'Shots-RB', is_data=False)) self._chans.extend([self.samplespre, self.samplespost, self.shots]) self.radio_buttons = [] self.setupui() @property def nrsamplespershot(self): if self.samplespost.connected and self.samplespre.connected: return self.samplespost.getvalue() + self.samplespre.getvalue() @property def nrsamples(self): val = self.nrsamplespershot if val and self.shots.connected: return self.nrsamplespershot*self.shots.getvalue() def setupui(self): vbl = QVBoxLayout(self) self.stack = QStackedWidget(self) vbl.addWidget(self.stack) rbA = QRadioButton('Antennas', self) rbP = QRadioButton('Positions', self) rbA.toggled.connect(_part(self.toggle_button, 0)) rbP.toggled.connect(_part(self.toggle_button, 1)) self.radio_buttons.append(rbA) self.radio_buttons.append(rbP) rbA.setChecked(True) hbl = QHBoxLayout() hbl.addStretch() hbl.addWidget(rbA) hbl.addStretch() hbl.addWidget(rbP) hbl.addStretch() vbl.addItem(hbl) # ##### Antennas Widget ###### stack1 = QWidget(self.stack) self.stack.addWidget(stack1) vbl = QVBoxLayout(stack1) graph = self.create_graph(stack1, False) vbl.addWidget(graph) stats = self.create_statistics(stack1, False) vbl.addWidget(stats) if not self.data_prefix.startswith('PM'): hbl2 = QHBoxLayout() hbl2.addStretch() vbl.addItem(hbl2) pb = QPushButton('Open FFT Data', stack1) hbl2.addWidget(pb) hbl2.addStretch() Window = create_window_from_widget( FFTData, title=self.bpm+': FFT Data') util.connect_window( pb, Window, parent=stack1, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix, position=False) # ##### Position Widget ###### stack2 = QWidget(self.stack) self.stack.addWidget(stack2) vbl = QVBoxLayout(stack2) graph = self.create_graph(stack2, True) vbl.addWidget(graph) stats = self.create_statistics(stack2, True) vbl.addWidget(stats) if not self.data_prefix.startswith('PM'): hbl2 = QHBoxLayout() hbl2.addStretch() vbl.addItem(hbl2) pb = QPushButton('Open FFT Data', stack2) hbl2.addWidget(pb) hbl2.addStretch() Window = create_window_from_widget( FFTData, title=self.bpm+': FFT Data') util.connect_window( pb, Window, parent=stack1, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix, position=True) self.setStyleSheet(""" #MultiTurnDataGraph{ min-width:48em; min-height:24em; } QLabel{ min-width:6em; max-width:6em; min-height:1.5em; max-height:1.5em; }""") def create_statistics(self, wid, position=True): text, unit, names, _ = self._get_properties(position) wid = QWidget(wid) hbl = QHBoxLayout(wid) hbl.addStretch() grpbx = QGroupBox('Statistics', wid) hbl.addWidget(grpbx) hbl.addStretch() gdl = QGridLayout(grpbx) gdl.setHorizontalSpacing(20) gdl.setVerticalSpacing(20) stats = ('MeanValue', 'Sigma', 'MinValue', 'MaxValue') for j, stat in enumerate(('Average', 'Sigma', 'Minimum', 'Maximum')): lab = QLabel(stat, wid) lab.setAlignment(Qt.AlignCenter) gdl.addWidget(lab, 0, j+1) for i, name in enumerate(names): lab = QLabel(text[:3] + name, wid) lab.setAlignment(Qt.AlignCenter) gdl.addWidget(lab, i+1, 0) for j, stat in enumerate(stats): lab = SiriusLabel(wid, init_channel=self.get_pvname( name+'_STATS'+stat+'_RBV')) lab.setAlignment(Qt.AlignCenter) lab.unit_changed(unit) lab.showUnits = True lab.precisionFromPV = False lab.precision = 3 gdl.addWidget(lab, i+1, j+1) return wid def create_graph(self, wid, position=True): text, unit, names, colors = self._get_properties(position) if position: unit = unit[1:] graph = GraphWave( wid, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix) graph.setObjectName('MultiTurnDataGraph') graph.setLabel('left', text=text, units=unit) for name, cor in zip(names, colors): opts = dict( y_channel=name+'ArrayData', name=text[:3]+name, color=cor, lineStyle=1, lineWidth=1) # NOTE: If > 1: very low performance opts['y_channel'] = self.get_pvname(opts['y_channel']) if position: graph.addChannel(add_scale=1e-9, **opts) else: graph.addChannel(**opts) return graph def _get_properties(self, position=True): if position: text = 'Positions' unit = 'nm' names = ('X', 'Y', 'Q', 'SUM') colors = ('blue', 'red', 'green', 'black') else: text = 'Antennas' unit = 'count' names = ('A', 'B', 'C', 'D') colors = ('blue', 'red', 'green', 'magenta') return text, unit, names, colors def toggle_button(self, i, tog): if tog: self.stack.setCurrentIndex(i) def control_visibility_buttons(self, text): bo = not text.startswith('adc') self.radio_buttons[1].setVisible(bo) self.radio_buttons[0].setChecked(True)
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.size == 0: self.error(_("Array is empty")) return False if data.ndim > 3: self.error(_("Arrays with more than 3 dimensions are not supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error(_("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error(_("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - '+title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel(_("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':']*3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)]*3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index == None: stack_index = self.stack.count() self.stack.addWidget(ArrayEditorWidget(self, self.data[slice_index])) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i']*3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText(('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index]-1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class TomographyPlugin(GUIPlugin): name = "Tomography" def __init__(self, *args, **kwargs): self.active_filename = "" self._main_view = QMainWindow() self._main_view.setCentralWidget(QWidget()) self._main_view.centralWidget().hide() self._editors = QStackedWidget() self.workflow_process_selector = QComboBox() self.workflow_process_selector.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.workflow_process_selector.addItem("TomoPy Workflow") self._workflow = None self._tomo_workflow = TomographyWorkflow() # Create a workflow self._workflow = self._tomo_workflow self._workflow_editor = WorkflowEditor(workflow=self._workflow) self._workflow_editor.sigRunWorkflow.connect(self.run_workflow) self._active_workflow_executor = QTreeWidget() self._editors.addWidget(self._workflow_editor) if hasLTT: self._alt_workflow = LTTWorkflow() self.workflow_process_selector.addItem("LTT Workflow") self._alt_workflow_editor = WorkflowEditor(self._alt_workflow) self._alt_workflow_editor.sigRunWorkflow.connect(self.run_workflow) self._editors.addWidget(self._alt_workflow_editor) self.workflow_process_selector.setCurrentIndex(0) self.workflow_process_selector.currentIndexChanged.connect(self.active_workflow_changed) self._active_workflow_widget = QWidget() self._active_layout = QVBoxLayout() self._active_layout.addWidget(self.workflow_process_selector) self._active_layout.addWidget(self._editors) self._active_layout.addWidget(self._active_workflow_executor) self._active_workflow_widget.setLayout(self._active_layout) # self.hdf5_viewer = HDFTreeViewer() # self.hdf5_viewer.load("/Users/hari/20200521_160002_heartbeat_test.h5") self.top_controls = QWidget() self.top_controls_layout = QHBoxLayout() self.top_controls.setLayout(self.top_controls_layout) self.top_controls_add_new_selector = QComboBox() self.top_controls_frequency_selector = QSpinBox() self.top_controls_frequency_selector.setValue(0) self.top_controls_add_new_viewer = QPushButton() self.top_controls_add_new_viewer.setText("Add Viewer") self.top_controls_add_new_viewer.clicked.connect(self.add_new_viewer_selected) self.top_controls_preview = QPushButton() self.top_controls_preview.setText("Preview") self.top_controls_preview.clicked.connect(self.preview_workflows) self.top_controls_execute = QPushButton() self.top_controls_execute.setText("Execute All") self.top_controls_execute.clicked.connect(self.execute_workflows) self.top_controls_add_new_selector.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.top_controls_add_new_viewer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_execute.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_frequency_selector.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_layout.addWidget(self.top_controls_add_new_selector) self.top_controls_layout.addWidget(self.top_controls_add_new_viewer) self.top_controls_layout.addWidget(self.top_controls_frequency_selector) self.top_controls_layout.addWidget(self.top_controls_preview) self.top_controls_layout.addWidget(self.top_controls_execute) self.top_controls.setLayout(self.top_controls_layout) self.status_bar = QLabel("None Loaded...") # Create a layout to organize our widgets # The first argument (which corresponds to the center widget) is required. catalog_viewer_layout = GUILayout(self._main_view, top=self.top_controls, right=self._active_workflow_widget, bottom=self.status_bar) # Create a "View" stage that has the catalog viewer layout self.stages = {"View": catalog_viewer_layout} self.active_workflows = [] # For classes derived from GUIPlugin, this super __init__ must occur at end super(TomographyPlugin, self).__init__(*args, **kwargs) def active_workflow_changed(self, index): self._editors.setCurrentIndex(index) if index == 0: self._workflow = self._tomo_workflow else: self._workflow = self._alt_workflow def preview_workflows(self): if len(self.active_filename) == 0: return preview_sinogram = self.top_controls_frequency_selector.value() workflows = [] for aw in range(self._active_workflow_executor.topLevelItemCount()): widget_item = self._active_workflow_executor.topLevelItem(aw) workflow = widget_item.data(0, Qt.UserRole) workflows.append(workflow) active_workflows = self.active_workflows for workflow in workflows: def results_ready(workflow, *results): output_image = results[0]["recon"] # We want the output_image from the last operation # print("RECON SHAPE", output_image.shape) for active_workflow in active_workflows: if active_workflow[1] == workflow: active_workflow[0].widget().setImage(output_image) # Update the result view widget workflow.execute(callback_slot=partial(results_ready, workflow), path=self.active_filename, sinoindex=preview_sinogram) def execute_workflows(self): if len(self.active_filename) == 0: return workflows = [] for aw in range(self._active_workflow_executor.topLevelItemCount()): widget_item = self._active_workflow_executor.topLevelItem(aw) workflow = widget_item.data(0, Qt.UserRole) workflows.append(workflow) active_workflows = self.active_workflows for workflow in workflows: def results_ready(workflow, *results): output_image = results[0]["recon"] # We want the output_image from the last operation # print("RECON SHAPE", output_image.shape) for active_workflow in active_workflows: if active_workflow[1] == workflow: active_workflow[0].widget().setImage(output_image) # Update the result view widget workflow.execute(callback_slot=partial(results_ready, workflow), path=self.active_filename) def add_new_viewer_selected(self, checked): if self.top_controls_add_new_selector.count() == 0: return selected = self.top_controls_add_new_selector.currentIndex() if selected < 0: return workflow = self.top_controls_add_new_selector.currentData(Qt.UserRole) print("WORKFLOW", workflow, workflow.name) dock_widget = QDockWidget() viewer = CatalogView() dock_widget.setWidget(viewer) dock_widget.setWindowTitle(workflow.name) if len(self.active_workflows) == 0: self._main_view.addDockWidget(Qt.RightDockWidgetArea, dock_widget) else: self._main_view.tabifyDockWidget(self.active_workflows[-1][0], dock_widget) self.active_workflows.append((dock_widget, workflow)) def appendHeader(self, header, **kwargs): filename = header._documents["start"][0]["filename"] self.active_filename = filename self.status_bar.setText("Loading Filename:" + filename) dataset, bkgs, drks = read_als_hdf5(filename) slice_widget = MyCatalogView() dw = QDockWidget() dw.setWidget(slice_widget) dw.setWindowTitle(filename) slice_widget.setImage(dataset) dock_children = self._main_view.findChildren(QDockWidget) print(dock_children) #if len(dock_children) == 0: # self._main_view.addDockWidget(Qt.LeftDockWidgetArea, dw) #else: # self._main_view.tabifyDockWidget(dock_children[0], dw) self._main_view.addDockWidget(Qt.LeftDockWidgetArea, dw) self.status_bar.setText("Done Loading Filename:" + filename) def appendCatalog(self, catalog, **kwargs): """Re-implemented from GUIPlugin - gives us access to a catalog reference You MUST implement this method if you want to load catalog data into your GUIPlugin. """ # Set the catalog viewer's catalog, stream, and field (so it knows what to display) # This is a quick and simple demonstration; stream and field should NOT be hardcoded # stream = "primary" # field = "img" # self._catalog_viewer.setCatalog(catalog, stream, field) print("CATALOG CALLED", catalog) pass def run_workflow(self): """Run the internal workflowi. In this example, this will be called whenever the "Run Workflow" in the WorkflowEditor is clicked. """ item = QTreeWidgetItem() workflow_name = self._workflow.name + ":" + str(self._active_workflow_executor.model().rowCount()) workflow = self._workflow.clone() workflow.name = workflow_name workflow._pretty_print() item.setText(0, workflow_name) item.setData(0, Qt.UserRole, workflow) self._active_workflow_executor.addTopLevelItem(item) self.top_controls_add_new_selector.addItem(workflow_name, userData=workflow)
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" valueChanged = Signal() ui_schema = { "call_order": { "ui:widget": "plugins" }, "highlight_thickness": { "ui:widget": "highlight" }, } resized = Signal(QSize) closed = Signal() def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def _restart_dialog(self, event=None, extra_str=""): """Displays the dialog informing user a restart is required. Paramters --------- event : Event extra_str : str Extra information to add to the message about needing a restart. """ text_str = trans._( "napari requires a restart for image rendering changes to apply.") widget = ResetNapariInfoDialog( parent=self, text=text_str, ) widget.exec_() def closeEvent(self, event): """Override to emit signal.""" self.closed.emit() super().closeEvent(event) def reject(self): """Override to handle Escape.""" super().reject() self.close() def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" settings = get_settings() # Because there are multiple pages, need to keep a dictionary of values dicts. # One set of keywords are for each page, then in each entry for a page, there are dicts # of setting and its value. self._values_orig_dict = {} self._values_dict = {} self._setting_changed_dict = {} for page, setting in settings.schemas().items(): schema, values, properties = self.get_page_dict(setting) self._setting_changed_dict[page] = {} self._values_orig_dict[page] = values self._values_dict[page] = values # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def get_page_dict(self, setting): """Provides the schema, set of values for each setting, and the properties for each setting. Parameters ---------- setting : dict Dictionary of settings for a page within the settings manager. Returns ------- schema : dict Json schema of the setting page. values : dict Dictionary of values currently set for each parameter in the settings. properties : dict Dictionary of properties within the json schema. """ schema = json.loads(setting['json_schema']) # Resolve allOf references definitions = schema.get("definitions", {}) if definitions: for key, data in schema["properties"].items(): if "allOf" in data: allof = data["allOf"] allof = [d["$ref"].rsplit("/")[-1] for d in allof] for definition in allof: local_def = definitions[definition] schema["properties"][key]["enum"] = local_def["enum"] schema["properties"][key]["type"] = "string" # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties return schema, values, properties def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" self._reset_dialog = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) self._reset_dialog.valueChanged.connect(self._reset_widgets) self._reset_dialog.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self.valueChanged.emit() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to settings.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. settings = get_settings() for n in range(self._stack.count()): # Must set the current row so that the proper list is updated # in check differences. self._list.setCurrentRow(n) page = self._list.currentItem().text().split(" ")[0].lower() # get new values for settings. If they were changed from values at beginning # of preference dialog session, change them back. # Using the settings value seems to be the best way to get the checkboxes right # on the plugin call order widget. setting = settings.schemas()[page] schema, new_values, properties = self.get_page_dict(setting) self.check_differences(self._values_orig_dict[page], new_values) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ settings = get_settings() builder = WidgetBuilder() form = builder.create_form(schema, self.ui_schema) # Disable widgets that loaded settings from environment variables section = schema["section"] form_layout = form.widget.layout() for row in range(form.widget.layout().rowCount()): widget = form_layout.itemAt(row, form_layout.FieldRole).widget() name = widget._name disable = bool( settings._env_settings.get(section, {}).get(name, None)) widget.setDisabled(disable) try: widget.opacity.setOpacity(0.3 if disable else 1) except AttributeError: # some widgets may not have opacity (such as the QtPluginSorter) pass # set state values for widget form.widget.state = values if section == 'experimental': # need to disable async if octree is enabled. if values['octree'] is True: form = self._disable_async(form, values) form.widget.on_changed.connect(lambda d: self.check_differences( d, self._values_dict[schema["title"].lower()], )) return form def _disable_async(self, form, values, disable=True, state=True): """Disable async if octree is True.""" settings = get_settings() # need to make sure that if async_ is an environment setting, that we don't # enable it here. if (settings._env_settings['experimental'].get('async_', None) is not None): disable = True idx = list(values.keys()).index('async_') form_layout = form.widget.layout() widget = form_layout.itemAt(idx, form_layout.FieldRole).widget() widget.opacity.setOpacity(0.3 if disable else 1) widget.setDisabled(disable) return form def _values_changed(self, page, new_dict, old_dict): """Loops through each setting in a page to determine if it changed. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting value and each item is the value. old_dict : dict Dict wtih values set at the begining of preferences dialog session. """ for setting_name, value in new_dict.items(): if value != old_dict[setting_name]: self._setting_changed_dict[page][setting_name] = value elif (value == old_dict[setting_name] and setting_name in self._setting_changed_dict[page]): self._setting_changed_dict[page].pop(setting_name) def set_current_index(self, index: int): """ Set the current page on the preferences by index. Parameters ---------- index : int Index of page to set as current one. """ self._list.setCurrentRow(index) def check_differences(self, new_dict, old_dict): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting parameter and each item is the value. old_dict : dict Dict wtih values set at the beginning of the preferences dialog session. """ settings = get_settings() page = self._list.currentItem().text().split(" ")[0].lower() self._values_changed(page, new_dict, old_dict) different_values = self._setting_changed_dict[page] if len(different_values) > 0: # change the values in settings for setting_name, value in different_values.items(): try: setattr(settings._settings[page], setting_name, value) self._values_dict[page] = new_dict if page == 'experimental': if setting_name == 'octree': # disable/enable async checkbox widget = self._stack.currentWidget() cstate = True if value is True else False self._disable_async(widget, new_dict, disable=cstate) # need to inform user that napari restart needed. self._restart_dialog() elif setting_name == 'async_': # need to inform user that napari restart needed. self._restart_dialog() except: # noqa: E722 continue
class CalibrationWidget(ParameterWidget): def __init__(self, parent=None): ParameterWidget.__init__(self, _Calibration, parent) def _init_ui(self): # Widgets self._combobox = QComboBox() self._stack = QStackedWidget() # Layouts layout = ParameterWidget._init_ui(self) layout.addRow(self._combobox) layout.addRow(self._stack) # Register classes self._widget_indexes = {} for entry_point in iter_entry_points('pyhmsa_gui.spec.condition.calibration'): widget_class = entry_point.load(require=False) widget = widget_class() self._combobox.addItem(widget.accessibleName().title()) self._widget_indexes[widget.CLASS] = self._stack.addWidget(widget) widget.edited.connect(self.edited) # Signals self._combobox.currentIndexChanged.connect(self._on_combo_box) self._combobox.currentIndexChanged.connect(self.edited) return layout def _on_combo_box(self): # Old values oldwidget = self._stack.currentWidget() try: quantity = oldwidget._txt_quantity.text() except: quantity = None try: unit = oldwidget._txt_unit.text() except: unit = None # Change widget current_index = self._combobox.currentIndex() self._stack.setCurrentIndex(current_index) # Update values widget = self._stack.currentWidget() widget._txt_quantity.setText(quantity) widget._txt_unit.setText(unit) def parameter(self): return self._stack.currentWidget().calibration() def setParameter(self, calibration): index = self._widget_indexes[type(calibration)] self._combobox.setCurrentIndex(index) self._stack.setCurrentIndex(index) self._stack.currentWidget().setParameter(calibration) def calibration(self): return self.parameter() def setCalibration(self, calibration): self.setParameter(calibration) def setReadOnly(self, state): ParameterWidget.setReadOnly(self, state) self._combobox.setEnabled(not state) self._stack.currentWidget().setReadOnly(state) def isReadOnly(self): return ParameterWidget.isReadOnly(self) and \ not self._combobox.isEnabled() and \ self._stack.currentWidget().isReadOnly() def hasAcceptableInput(self): return ParameterWidget.hasAcceptableInput(self) and \ self._stack.currentWidget().hasAcceptableInput()
class FeaturesPlotWidget(CustomWidget): call_manage_settings = Signal() def __init__(self, *args, **kwargs): # define status images self.status_icons = { -2: QPixmap( os.path.join(os.path.dirname(__file__), 'icons', 'depth_status_delay.png')), -1: QPixmap( os.path.join(os.path.dirname(__file__), 'icons', 'depth_status_in_use.png')), 1: QPixmap( os.path.join(os.path.dirname(__file__), 'icons', 'depth_status_done.png')), 0: QPixmap( os.path.join(os.path.dirname(__file__), 'icons', 'depth_status_off.png')), } # Settings self.subject_settings = None self.procedure_settings = None self.depth_settings = None self.features_settings = None # Plot options self.plot_config = {} self.y_range = uVRANGE self.plot_stack = QStackedWidget() # generate a dict {chan_label: {Feature:[stack idx, latest_datum]}} self.stack_dict = {} # shared memory to display the currently monitored electrode self.monitored_channel_mem = QSharedMemory() self.monitored_channel_mem.setKey("MonitoredChannelMemory") self.monitored_channel_mem.attach(QSharedMemory.ReadOnly) # wrap up init super(FeaturesPlotWidget, self).__init__(*args, **kwargs) self.move(WINDOWDIMS_FEATURES[0], WINDOWDIMS_FEATURES[1]) self.resize(WINDOWDIMS_FEATURES[2], WINDOWDIMS_FEATURES[3]) self.setMaximumWidth(WINDOWDIMS_FEATURES[2]) # initialize plots self.layout().addWidget(self.plot_stack) self.refresh_axes() # Extra time on purpose. # Define and start processes # will only start processes when settings are received self.depth_wrapper = ProcessWrapper('Depth_Process') self.depth_process_running = False self.features_wrapper = ProcessWrapper('Features_Process') self.features_process_running = False def create_control_panel(self): # define Qt GUI elements layout = QHBoxLayout() layout_L = QVBoxLayout() layout_L1 = QHBoxLayout() # layout_L1.addSpacing(10) layout_L1.addWidget( QLabel("Electrode: ", alignment=Qt.AlignVCenter | Qt.AlignRight)) # Channel selection self.chan_select = QComboBox() self.chan_select.addItem("None") self.chan_select.setMinimumWidth(70) self.chan_select.setEnabled(False) layout_L1.addWidget(self.chan_select) layout_L1.addSpacing(20) # features selection layout_L1.addWidget( QLabel("Feature set: ", alignment=Qt.AlignVCenter | Qt.AlignRight)) self.feature_select = QComboBox() self.feature_select.setMinimumWidth(60) self.feature_select.addItems(['Raw', 'Mapping']) self.feature_select.setCurrentIndex(0) layout_L1.addWidget(self.feature_select) layout_L.addLayout(layout_L1) layout_L.addSpacing(5) layout_L2 = QHBoxLayout() layout_L2.addSpacing(10) layout_L2.addWidget( QLabel("+/- ", alignment=Qt.AlignVCenter | Qt.AlignRight)) self.range_edit = QLineEdit("{:.2f}".format(uVRANGE)) self.range_edit.setMaximumWidth(50) layout_L2.addWidget(self.range_edit) layout_L2.addSpacing(30) self.do_hp = QCheckBox('HP') self.do_hp.setChecked(True) layout_L2.addWidget(self.do_hp) layout_L2.addSpacing(30) self.sweep_control = QCheckBox("Match SweepGUI.") self.sweep_control.setChecked(True) self.sweep_control.setEnabled(True) layout_L2.addWidget(self.sweep_control) layout_L.addLayout(layout_L2) layout_R = QHBoxLayout() self.bt_refresh = QPushButton("Refresh") self.bt_refresh.setMaximumWidth(50) layout_R.addWidget(self.bt_refresh) layout_R.addSpacing(20) self.btn_settings = QPushButton("Settings") self.btn_settings.setMaximumWidth(50) layout_R.addWidget(self.btn_settings) layout_R.addSpacing(20) self.features_process_btn = QPushButton('Features') self.features_process_btn.setMaximumWidth(50) self.features_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") self.features_process_btn.clicked.connect( self.features_process_btn_callback) layout_R.addWidget(self.features_process_btn) layout_R.addSpacing(5) self.depth_process_btn = QPushButton('Record') self.depth_process_btn.setMaximumWidth(50) self.depth_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") self.depth_process_btn.clicked.connect(self.depth_process_btn_callback) layout_R.addWidget(self.depth_process_btn) layout_R.addSpacing(20) self.status_label = QLabel() self.status_label.setPixmap(self.status_icons[0]) layout_R.addWidget(self.status_label) layout_R.addSpacing(10) layout.addLayout(layout_L) layout.addStretch() layout.addLayout(layout_R) # layout.addSpacing(10) self.layout().addLayout(layout) # callbacks self.btn_settings.clicked.connect(self.call_manage_settings.emit) self.chan_select.currentIndexChanged.connect( self.manage_feat_chan_select) self.feature_select.currentIndexChanged.connect( self.manage_feat_chan_select) self.sweep_control.clicked.connect(self.manage_sweep_control) self.range_edit.editingFinished.connect(self.manage_range_edit) self.bt_refresh.clicked.connect(self.manage_refresh) def depth_process_btn_callback(self): # kill if self.depth_process_running: self.manage_depth_process(False) self.manage_nsp(False) else: # if we terminate and re-start the processes, we need to re-enable the shared memory self.depth_wrapper.manage_shared_memory() # re-send the settings self.depth_wrapper.send_settings(self.depth_settings) # start nsp recording if self.manage_nsp(True) == 0: # re-start the worker self.manage_depth_process(True) def features_process_btn_callback(self): # kill if self.features_process_running: self.manage_feature_process(False) else: # if we terminate and re-start the processes, we need to re-enable the shared memory self.features_wrapper.manage_shared_memory() # re-send the settings self.features_wrapper.send_settings(self.features_settings) # re-start the worker self.manage_feature_process(True) # GUI Callbacks def manage_feat_chan_select(self): self.plot_stack.setCurrentIndex( self.stack_dict[self.chan_select.currentText()][ self.feature_select.currentText()][0]) def manage_sweep_control(self): if self.sweep_control.isChecked( ) and self.monitored_channel_mem.isAttached(): self.chan_select.setEnabled(False) self.do_hp.setEnabled(False) self.range_edit.setEnabled(False) self.read_from_shared_memory() else: self.chan_select.setEnabled(True) self.do_hp.setEnabled(True) self.range_edit.setEnabled(True) def manage_range_edit(self): # need to do it like this because if we simply read the QLineEdit.text() on update calls, it breaks during # typing the new range values. self.y_range = float(self.range_edit.text()) @staticmethod def parse_patient_name(full_name): # parse the subject information names = full_name.split(' ') m_name = '' m_idx = -1 l_idx = -1 for idx, n in enumerate(names): if all([x.isupper() for x in n]): m_idx = idx l_idx = idx + 1 m_name = n break f_name = str.join(' ', names[:m_idx]) l_name = str.join(' ', names[l_idx:]) return f_name, m_name, l_name def manage_nsp(self, on_off): f_name, m_name, l_name = self.parse_patient_name( self.subject_settings['name']) file_info = { 'filename': os.path.normpath( os.path.join( BASEPATH, self.subject_settings['id'], self.procedure_settings['date'].strftime('%m%d%y') + '_' + self.subject_settings['id'] + '_' + self.procedure_settings['target_name'] + '_' + self.procedure_settings['recording_config'])), 'comment': self.subject_settings['NSP_comment'], 'patient_info': { 'ID': self.subject_settings['id'], # if only single name, returned in l_name 'firstname': f_name if f_name else l_name, 'middlename': m_name, # TODO: implement MiddleName 'lastname': l_name, 'DOBMonth': self.subject_settings['birthday'].month, 'DOBDay': self.subject_settings['birthday'].day, 'DOBYear': self.subject_settings['birthday'].year } } if not CbSdkConnection().is_connected: CbSdkConnection().connect() return CbSdkConnection().set_recording_state(on_off, file_info) def manage_depth_process(self, on_off): # start process if on_off and not self.depth_process_running: self.depth_wrapper.start_worker() self.depth_process_running = True else: self.depth_wrapper.kill_worker() self.depth_process_running = False def manage_feature_process(self, on_off): if on_off and not self.features_process_running: self.features_wrapper.start_worker() self.features_process_running = True else: self.features_wrapper.kill_worker() self.features_process_running = False def manage_refresh(self): self.plot_stack.widget(self.stack_dict[self.chan_select.currentText()][ self.feature_select.currentText()][0]).clear_plot() self.stack_dict[self.chan_select.currentText()][ self.feature_select.currentText()][1] = 0 def process_settings(self, sub_sett, proc_sett, depth_sett, feat_sett): self.subject_settings = dict(sub_sett) self.procedure_settings = dict(proc_sett) self.depth_settings = dict(depth_sett) self.features_settings = dict(feat_sett) # validate that we have some data in the electrode_settings. If the NSP is not connected we will have # to load the channel names from the DB. Also we want to keep the FeaturesGUI unaware of the DB channels. if len(self.depth_settings['electrode_settings']) == 0: self.depth_settings['electrode_settings'] = {} for lbl in DBWrapper().list_channel_labels(): self.depth_settings['electrode_settings'][lbl] = DEPTHSETTINGS CbSdkConnection().is_simulating = True # set new features self.feature_select.setCurrentIndex(0) # Raw while self.feature_select.count() > 2: # Raw and Mapping self.feature_select.removeItem(2) self.feature_select.addItems(self.features_settings['features'].keys()) # set new channels self.chan_select.setCurrentIndex(0) # None while self.chan_select.count() > 1: self.chan_select.removeItem(1) self.chan_select.addItems( self.depth_settings['electrode_settings'].keys()) # clear and update stacked widget to_delete = [ self.plot_stack.widget(x) for x in range(self.plot_stack.count()) ] for wid in to_delete: self.plot_stack.removeWidget(wid) wid.deleteLater() wid = None self.stack_dict = {} self.create_plots() self.depth_wrapper.send_settings(self.depth_settings) self.features_wrapper.send_settings(self.features_settings) if not self.features_process_running: self.manage_feature_process(True) # self.clear() self.read_from_shared_memory() def create_plots(self, theme='dark', **kwargs): # Collect PlotWidget configuration self.plot_config['theme'] = theme self.plot_config['color_iterator'] = -1 self.plot_config['x_range'] = XRANGE_FEATURES self.plot_config['y_range'] = uVRANGE self.plot_config['do_hp'] = True labels = [] for ii in range(0, self.chan_select.count()): labels.append(self.chan_select.itemText(ii)) # labels.extend(self.channel_labels) features = [] for ii in range(0, self.feature_select.count()): features.append(self.feature_select.itemText(ii)) stack_idx = 0 for lbl_idx, lbl in enumerate(labels): self.stack_dict[lbl] = {} self.plot_config['color_iterator'] = lbl_idx - 1 self.plot_config['title'] = lbl for feat in features: self.stack_dict[lbl][feat] = [stack_idx, 0] # TODO: not hard-coding?? if feat == 'Raw': self.plot_stack.addWidget(RawPlots(dict(self.plot_config))) elif feat == 'Mapping': self.plot_stack.addWidget( MappingPlots(dict(self.plot_config))) elif feat == 'STN': self.plot_stack.addWidget(STNPlots(dict(self.plot_config))) elif feat == 'LFP': self.plot_stack.addWidget(LFPPlots(dict(self.plot_config))) elif feat == 'Spikes': self.plot_stack.addWidget( SpikePlots(dict(self.plot_config))) else: self.plot_stack.addWidget( NullPlotWidget(dict(self.plot_config))) stack_idx += 1 self.plot_stack.setCurrentIndex(0) def refresh_axes(self): pass def clear(self): # set the current datum of all stacks to 0 for lbl in self.stack_dict: for feat in self.stack_dict[lbl]: self.stack_dict[lbl][feat][1] = 0 self.plot_stack.widget( self.stack_dict[lbl][feat][0]).clear_plot() def update(self): # Depth process output = self.depth_wrapper.worker_status() self.status_label.setPixmap(self.status_icons[output]) if self.depth_wrapper.is_running(): self.depth_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : green; " "border-color : green; " "border-width: 2px}") else: self.depth_process_running = False self.depth_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") if self.features_wrapper.is_running(): self.features_process_btn.setStyleSheet( "QPushButton { color: white; " "background-color : green; " "border-color : green; " "border-width: 2px}") else: self.features_process_running = False self.features_process_btn.setStyleSheet( "QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") if self.sweep_control.isChecked(): self.read_from_shared_memory() # features plot curr_chan_lbl = self.chan_select.currentText() if curr_chan_lbl != 'None': curr_feat = self.feature_select.currentText() do_hp = self.do_hp.isChecked() if do_hp != self.plot_stack.currentWidget().plot_config['do_hp'] or \ self.y_range != self.plot_stack.currentWidget().plot_config['y_range']: self.plot_stack.currentWidget().clear_plot() self.stack_dict[curr_chan_lbl][curr_feat][1] = 0 self.plot_stack.currentWidget().plot_config['do_hp'] = do_hp self.plot_stack.currentWidget( ).plot_config['y_range'] = self.y_range curr_datum = self.stack_dict[curr_chan_lbl][curr_feat][1] if curr_feat == 'Raw': all_data = DBWrapper().load_depth_data(chan_lbl=curr_chan_lbl, gt=curr_datum, do_hp=do_hp, return_uV=True) elif curr_feat == 'Mapping': all_data = DBWrapper().load_mapping_response( chan_lbl=curr_chan_lbl, gt=curr_datum) else: all_data = DBWrapper().load_features_data( category=curr_feat, chan_lbl=curr_chan_lbl, gt=curr_datum) if all_data: self.plot_stack.currentWidget().update_plot(dict(all_data)) self.stack_dict[curr_chan_lbl][curr_feat][1] = max( all_data.keys()) def kill_processes(self): self.manage_depth_process(False) self.manage_feature_process(False) def read_from_shared_memory(self): if self.monitored_channel_mem.isAttached(): self.monitored_channel_mem.lock() settings = np.frombuffer(self.monitored_channel_mem.data(), dtype=np.float)[-3:] self.chan_select.setCurrentIndex(int(settings[0])) self.range_edit.setText(str(settings[1])) self.manage_range_edit() self.do_hp.setChecked(bool(settings[2])) self.monitored_channel_mem.unlock() else: self.monitored_channel_mem.attach() # self.sweep_control.setChecked(False) self.manage_sweep_control()
class ExtendedTabWidget(QFrame): ''' Like QTabWidget, except the tab bar is located elsewhere. Intended for use with ExtendedTabBar. ''' def __init__(self): super(ExtendedTabWidget, self).__init__() self._tab_bar = None self._stack = QStackedWidget() self._main_layout = QBoxLayout(QBoxLayout.LeftToRight) self._main_layout.setContentsMargins(0, 0, 0, 0) self._main_layout.addWidget(self._stack) self.setLayout(self._main_layout) def _move_tab(self, from_, to): ''' Handles tab moves so that the tab bar indices stay aligned with the widget stack indices. ''' displaced_widget = self._stack.widget(from_) moved_widget = self._stack.widget(to) self._stack.removeWidget(moved_widget) self._stack.removeWidget(displaced_widget) self._stack.insertWidget(to, displaced_widget) self._stack.insertWidget(from_, moved_widget) self._stack.setCurrentIndex(self._tab_bar.currentIndex()) def setTabBar(self, tab_bar): ''' Sets the tab bar that will be used to switch between tabs. tab_bar The tab bar to set as the controller of this widget. ''' if self._tab_bar is not None: raise Exception('Tab bar already set.') self._tab_bar = tab_bar tab_bar.currentChanged.connect(self._stack.setCurrentIndex) tab_bar.tabCloseRequested.connect(self.closeTab) tab_bar.tabMoved.connect(self._move_tab) def closeTab(self, index): ''' Closes a tab, removing from this widget, the tab bar, and deleting its widget. index Index of the tab to be closed. ''' self._tab_bar.removeTab(index) widget = self._stack.widget(index) self._stack.removeWidget(widget) widget.deleteLater() self._stack.setCurrentIndex(self._tab_bar.currentIndex()) def addTab(self, widget, label): ''' Adds a tab. widget The widget for the tab contents. label The name of the tab to show in the tab bar. Returns the index of the added tab. ''' index = self._tab_bar.addTab(label) self._stack.insertWidget(index, widget) return index def count(self): ''' Returns the number of widgets. ''' return self._stack.count() def widget(self, index): ''' Returns the widget at the given index. ''' return self._stack.widget(index) def indexOf(self, widget): ''' Returns the index of the given widget. ''' return self._stack.indexOf(widget)
class MixerPanel(QFrame): '''A color mixer to hook up to an image. You pass the image you the panel to operate on and it operates on that image in place. You also pass a callback to be called to trigger a refresh. This callback is called every time the mixer modifies your image.''' def __init__(self, img): QFrame.__init__(self) # self.setFrameStyle(QFrame.Box | QFrame.Sunken) self.img = img self.mixer = ColorMixer(self.img) self.callback = None #--------------------------------------------------------------- # ComboBox #--------------------------------------------------------------- self.combo_box_entries = ['RGB Color', 'HSV Color', 'Brightness/Contrast', 'Gamma', 'Gamma (Sigmoidal)'] self.combo_box = QComboBox() for entry in self.combo_box_entries: self.combo_box.addItem(entry) self.combo_box.currentIndexChanged.connect(self.combo_box_changed) #--------------------------------------------------------------- # RGB color sliders #--------------------------------------------------------------- # radio buttons self.rgb_add = QRadioButton('Additive') self.rgb_mul = QRadioButton('Multiplicative') self.rgb_mul.toggled.connect(self.rgb_radio_changed) self.rgb_add.toggled.connect(self.rgb_radio_changed) # sliders rs = IntelligentSlider('R', 0.51, -255, self.rgb_changed) gs = IntelligentSlider('G', 0.51, -255, self.rgb_changed) bs = IntelligentSlider('B', 0.51, -255, self.rgb_changed) self.rs = rs self.gs = gs self.bs = bs self.rgb_widget = QWidget() self.rgb_widget.layout = QGridLayout(self.rgb_widget) self.rgb_widget.layout.addWidget(self.rgb_add, 0, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rgb_mul, 1, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rs, 2, 0) self.rgb_widget.layout.addWidget(self.gs, 2, 1) self.rgb_widget.layout.addWidget(self.bs, 2, 2) #--------------------------------------------------------------- # HSV sliders #--------------------------------------------------------------- # radio buttons self.hsv_add = QRadioButton('Additive') self.hsv_mul = QRadioButton('Multiplicative') self.hsv_mul.toggled.connect(self.hsv_radio_changed) self.hsv_mul.toggled.connect(self.hsv_radio_changed) # sliders hs = IntelligentSlider('H', 0.36, -180, self.hsv_changed) ss = IntelligentSlider('S', 0.002, 0, self.hsv_changed) vs = IntelligentSlider('V', 0.002, 0, self.hsv_changed) self.hs = hs self.ss = ss self.vs = vs self.hsv_widget = QWidget() self.hsv_widget.layout = QGridLayout(self.hsv_widget) self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hs, 2, 0) self.hsv_widget.layout.addWidget(self.ss, 2, 1) self.hsv_widget.layout.addWidget(self.vs, 2, 2) #--------------------------------------------------------------- # Brightness/Contrast sliders #--------------------------------------------------------------- # sliders cont = IntelligentSlider('x', 0.002, 0, self.bright_changed) bright = IntelligentSlider('+', 0.51, -255, self.bright_changed) self.cont = cont self.bright = bright # layout self.bright_widget = QWidget() self.bright_widget.layout = QGridLayout(self.bright_widget) self.bright_widget.layout.addWidget(self.cont, 0, 0) self.bright_widget.layout.addWidget(self.bright, 0, 1) #---------------------------------------------------------------------- # Gamma Slider #---------------------------------------------------------------------- gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed) self.gamma = gamma # layout self.gamma_widget = QWidget() self.gamma_widget.layout = QGridLayout(self.gamma_widget) self.gamma_widget.layout.addWidget(self.gamma, 0, 0) #--------------------------------------------------------------- # Sigmoid Gamma sliders #--------------------------------------------------------------- # sliders alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed) beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed) self.a_gamma = alpha self.b_gamma = beta # layout self.sig_gamma_widget = QWidget() self.sig_gamma_widget.layout = QGridLayout(self.sig_gamma_widget) self.sig_gamma_widget.layout.addWidget(self.a_gamma, 0, 0) self.sig_gamma_widget.layout.addWidget(self.b_gamma, 0, 1) #--------------------------------------------------------------- # Buttons #--------------------------------------------------------------- self.commit_button = QPushButton('Commit') self.commit_button.clicked.connect(self.commit_changes) self.revert_button = QPushButton('Revert') self.revert_button.clicked.connect(self.revert_changes) #--------------------------------------------------------------- # Mixer Layout #--------------------------------------------------------------- self.sliders = QStackedWidget() self.sliders.addWidget(self.rgb_widget) self.sliders.addWidget(self.hsv_widget) self.sliders.addWidget(self.bright_widget) self.sliders.addWidget(self.gamma_widget) self.sliders.addWidget(self.sig_gamma_widget) self.layout = QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.sliders, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) #--------------------------------------------------------------- # State Initialization #--------------------------------------------------------------- self.combo_box.setCurrentIndex(0) self.rgb_mul.setChecked(True) self.hsv_mul.setChecked(True) def set_callback(self, callback): self.callback = callback def combo_box_changed(self, index): self.sliders.setCurrentIndex(index) self.reset() def rgb_radio_changed(self): self.reset() def hsv_radio_changed(self): self.reset() def reset(self): self.reset_sliders() self.mixer.set_to_stateimg() if self.callback: self.callback() def reset_sliders(self): # handle changing the conversion factors necessary if self.rgb_add.isChecked(): self.rs.set_conv_fac(0.51, -255) self.rs.set_value(0) self.gs.set_conv_fac(0.51, -255) self.gs.set_value(0) self.bs.set_conv_fac(0.51, -255) self.bs.set_value(0) else: self.rs.set_conv_fac(0.002, 0) self.rs.set_value(1.) self.gs.set_conv_fac(0.002, 0) self.gs.set_value(1.) self.bs.set_conv_fac(0.002, 0) self.bs.set_value(1.) self.hs.set_value(0) if self.hsv_add.isChecked(): self.ss.set_conv_fac(0.002, -1) self.ss.set_value(0) self.vs.set_conv_fac(0.002, -1) self.vs.set_value(0) else: self.ss.set_conv_fac(0.002, 0) self.ss.set_value(1.) self.vs.set_conv_fac(0.002, 0) self.vs.set_value(1.) self.bright.set_value(0) self.cont.set_value(1.) self.gamma.set_value(1) self.a_gamma.set_value(1) self.b_gamma.set_value(0.5) def rgb_changed(self, name, val): if name == 'R': channel = self.mixer.RED elif name == 'G': channel = self.mixer.GREEN else: channel = self.mixer.BLUE if self.rgb_mul.isChecked(): self.mixer.multiply(channel, val) elif self.rgb_add.isChecked(): self.mixer.add(channel, val) else: pass if self.callback: self.callback() def hsv_changed(self, name, val): h = self.hs.val() s = self.ss.val() v = self.vs.val() if self.hsv_mul.isChecked(): self.mixer.hsv_multiply(h, s, v) elif self.hsv_add.isChecked(): self.mixer.hsv_add(h, s, v) else: pass if self.callback: self.callback() def bright_changed(self, name, val): b = self.bright.val() c = self.cont.val() self.mixer.brightness(c, b) if self.callback: self.callback() def gamma_changed(self, name, val): self.mixer.gamma(val) if self.callback: self.callback() def sig_gamma_changed(self, name, val): ag = self.a_gamma.val() bg = self.b_gamma.val() self.mixer.sigmoid_gamma(ag, bg) if self.callback: self.callback() def commit_changes(self): self.mixer.commit_changes() self.reset_sliders() def revert_changes(self): self.mixer.revert() self.reset_sliders() if self.callback: self.callback()
class VizTabs(QTabWidget): def __init__(self, parent=None): self._parent = parent super().__init__(parent) self.oneDViewer = None self.strainSliceViewer = None self.vtk3dviewer = None self.plot_1d = QStackedWidget() self.plot_2d = QStackedWidget() self.plot_3d = QStackedWidget() self.message = QLabel("Load project files") self.message.setAlignment(Qt.AlignCenter) font = self.message.font() font.setPointSize(20) self.message.setFont(font) self.message2 = QLabel("Load project files") self.message2.setAlignment(Qt.AlignCenter) self.message2.setFont(font) self.plot_2d.addWidget(self.message) self.plot_3d.addWidget(self.message2) self.addTab(self.plot_1d, "1D") self.addTab(self.plot_2d, "2D") self.addTab(self.plot_3d, "3D") self.set_1d_mode(False) self.setCornerWidget(QLabel("Visualization Pane "), corner=Qt.TopLeftCorner) def set_1d_mode(self, oned): self.setTabEnabled(0, oned) self.setTabEnabled(1, not oned) self.setTabEnabled(2, not USING_THINLINC and not DISABLE_3D and not oned) def set_ws(self, field): if field is not None: try: ws = field.to_md_histo_workspace() except Exception as e: self._parent.show_failure_msg("Failed to generate field", str(e), traceback.format_exc()) ws = None else: ws = None if ws: if len(ws.getNonIntegratedDimensions()) == 1: self.set_1d_mode(True) if self.oneDViewer: self.plot_1d.removeWidget(self.oneDViewer) fig = Figure() self.oneDViewer = FigureCanvas(fig) # get scan direction for d in ('x', 'y', 'z'): dim = getattr(field, d) if not np.allclose(dim, dim[0], atol=0.1): scan_dir = d # create simple 1D plot ax = fig.add_subplot(111) ax.errorbar(getattr(field, scan_dir), field.values, field.errors, marker='o') ax.set_xlabel(f'{scan_dir} (mm)') self.plot_1d.addWidget(self.oneDViewer) self.plot_1d.setCurrentIndex(1) else: self.set_1d_mode(False) if self.strainSliceViewer: self.strainSliceViewer.set_new_workspace(ws) else: self.strainSliceViewer = StrainSliceViewer(ws, parent=self) self.plot_2d.addWidget(self.strainSliceViewer.view) self.plot_2d.setCurrentIndex(1) self.strainSliceViewer.set_new_field( field, bin_widths=[ ws.getDimension(n).getBinWidth() for n in range(3) ]) if not USING_THINLINC and not DISABLE_3D: if self.vtk3dviewer: self.vtk3dviewer.set_ws(ws) else: self.vtk3dviewer = VTK3DView(ws) self.plot_3d.addWidget(self.vtk3dviewer) self.plot_3d.setCurrentIndex(1) else: self.set_1d_mode(False) if self.oneDViewer is not None: self.plot_1d.removeWidget(self.oneDViewer) self.oneDViewer = None if self.strainSliceViewer is not None: self.plot_2d.removeWidget(self.strainSliceViewer.view) self.strainSliceViewer = None if self.vtk3dviewer: self.plot_3d.removeWidget(self.vtk3dviewer) self.vtk3dviewer = None def set_message(self, text): self.message.setText(text) self.message2.setText(text)
class StackedCanvasView(CanvasView): """ View that can display intents in their corresponding canvases. Currently, uses a stacked widget with several pages: one for tabview and others for different split views. Can be adapted as long as its internal widgets are CanvasDisplayWidgets. """ def __init__(self, parent=None, model=None): super(StackedCanvasView, self).__init__(parent) if model is not None: self.setModel(model) self.canvas_display_widgets = [ CanvasDisplayTabWidget(), SplitHorizontal(), SplitVertical(), SplitThreeView(), SplitGridView() ] ### Create stacked widget and fill pages with different canvas display widgets self.stackedwidget = QStackedWidget(self) # Create a visual layout section for the buttons that are used to switch the widgets self.buttonpanel = QHBoxLayout() self.buttonpanel.addStretch(10) # Create a logical button grouping that will: # - show the currently selected view (button will be checked/pressed) # - allow for switching the buttons/views in a mutually exclusive manner (only one can be pressed at a time) self.buttongroup = QButtonGroup() def add_canvas_display_widgets(): for i in range(len(self.canvas_display_widgets)): # Add the view to the stacked widget self.stackedwidget.addWidget(self.canvas_display_widgets[i]) # Create a button, using the view's recommended display icon button = QPushButton(self) button.setCheckable(True) button.setIcon(self.canvas_display_widgets[i].icon) # Add the button to the logical button group self.buttongroup.addButton(button, i) # Add the button to the visual layout section self.buttonpanel.addWidget(button) add_canvas_display_widgets() def set_default_canvas_display_widget(): # The first button added to the buttongroup will be the currently selected button (and therefore view) self.buttongroup.button(0).setChecked(True) set_default_canvas_display_widget() # Whenever a button is switched, capture its id (corresponds to integer index in our case); # this will handle switching the view and displaying canvases. self.buttongroup.idToggled.connect(self.switch_view) # define outer layout & add stacked widget and button panel self.layout = QVBoxLayout() self.layout.addWidget(self.stackedwidget) self.layout.addLayout(self.buttonpanel) self.setLayout(self.layout) # INTERFACING WITH TOOLBAR (see XPCSToolbar) def view(self): # from xicam.gui.canvases import ImageIntentCanvas view = self.stackedwidget.currentWidget() return view.getView() # return None # DONE INTERFACING def switch_view(self, id, toggled): # when toggled==True, the the button is the new button that was switched to. # when False, the button is the previous button view = self.canvas_display_widgets[id] if not toggled: ... # TODO: is there anything we need to do here (re: cleanup)? else: self.stackedwidget.setCurrentIndex(id) self.show_canvases() def show_canvases(self): self.stackedwidget.currentWidget().clear_canvases() self.stackedwidget.currentWidget().show_canvases( self._canvas_manager.canvases(self.model()))
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" ui_schema = { "call_order": { "ui:widget": "plugins" }, } resized = Signal(QSize) closed = Signal() def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def closeEvent(self, event): """Override to emit signal.""" self.closed.emit() super().closeEvent(event) def reject(self): """Override to handle Escape.""" super().reject() self.close() def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # Because there are multiple pages, need to keep a dictionary of values dicts. # One set of keywords are for each page, then in each entry for a page, there are dicts # of setting and its value. self._values_orig_dict = {} self._values_dict = {} self._setting_changed_dict = {} for page, setting in SETTINGS.schemas().items(): schema, values, properties = self.get_page_dict(setting) self._setting_changed_dict[page] = {} self._values_orig_dict[page] = values self._values_dict[page] = values # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def get_page_dict(self, setting): """Provides the schema, set of values for each setting, and the properties for each setting. Parameters ---------- setting : dict Dictionary of settings for a page within the settings manager. Returns ------- schema : dict Json schema of the setting page. values : dict Dictionary of values currently set for each parameter in the settings. properties : dict Dictionary of properties within the json schema. """ schema = json.loads(setting['json_schema']) # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties return schema, values, properties def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" widget = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) widget.valueChanged.connect(self._reset_widgets) widget.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to SETTINGS.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. for n in range(self._stack.count()): # Must set the current row so that the proper list is updated # in check differences. self._list.setCurrentRow(n) page = self._list.currentItem().text().split(" ")[0].lower() # get new values for settings. If they were changed from values at beginning # of preference dialog session, change them back. # Using the settings value seems to be the best way to get the checkboxes right # on the plugin call order widget. setting = SETTINGS.schemas()[page] schema, new_values, properties = self.get_page_dict(setting) self.check_differences(self._values_orig_dict[page], new_values) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ builder = WidgetBuilder() form = builder.create_form(schema, self.ui_schema) # set state values for widget form.widget.state = values form.widget.on_changed.connect(lambda d: self.check_differences( d, self._values_dict[schema["title"].lower()], )) return form def _values_changed(self, page, new_dict, old_dict): """Loops through each setting in a page to determine if it changed. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting value and each item is the value. old_dict : dict Dict wtih values set at the begining of preferences dialog session. """ for setting_name, value in new_dict.items(): if value != old_dict[setting_name]: self._setting_changed_dict[page][setting_name] = value elif (value == old_dict[setting_name] and setting_name in self._setting_changed_dict[page]): self._setting_changed_dict[page].pop(setting_name) def check_differences(self, new_dict, old_dict): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting parameter and each item is the value. old_dict : dict Dict wtih values set at the beginning of the preferences dialog session. """ page = self._list.currentItem().text().split(" ")[0].lower() self._values_changed(page, new_dict, old_dict) different_values = self._setting_changed_dict[page] if len(different_values) > 0: # change the values in SETTINGS for setting_name, value in different_values.items(): try: setattr(SETTINGS._settings[page], setting_name, value) self._values_dict[page] = new_dict except: # noqa: E722 continue
class IMCWidget(QWidget): NAME = 'Imaging Mass Cytometry' FULL_NAME = f'napari-imc: {NAME}' AREA = 'right' ALLOWED_AREAS = ['left', 'right'] def __init__(self, napari_viewer: Viewer, parent: Optional[QWidget] = None): super(IMCWidget, self).__init__(parent) self._controller = IMCController(napari_viewer, self) self._imc_file_tree_model = IMCFileTreeModel(self._controller, parent=self) self._imc_file_tree_view = IMCFileTreeView(parent=self) self._imc_file_tree_view.setModel(self._imc_file_tree_model) self._channel_table_model = ChannelTableModel(self._controller, parent=self) self._channel_table_proxy_model = QSortFilterProxyModel(self) self._channel_table_proxy_model.setSourceModel( self._channel_table_model) self._channel_table_proxy_model.sort( self._channel_table_model.LABEL_COLUMN) self._channel_table_view = ChannelTableView(parent=self) self._channel_table_view.setModel(self._channel_table_proxy_model) self._channel_controls_widget = ChannelControlsWidget(self._controller, parent=self) self._channel_controls_container = QStackedWidget(self) self._channel_controls_container.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self._channel_controls_container.addWidget(QWidget(self)) self._channel_controls_container.addWidget( self._channel_controls_widget) layout = QVBoxLayout(self) splitter = QSplitter(Qt.Vertical, self) splitter.addWidget(self._imc_file_tree_view) channel_panel = QWidget(self) channel_panel_layout = QVBoxLayout() channel_panel_layout.addWidget(self._channel_table_view) channel_panel_layout.addWidget(self._channel_controls_container) channel_panel.setLayout(channel_panel_layout) splitter.addWidget(channel_panel) layout.addWidget(splitter) self.setLayout(layout) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._imc_file_tree_model.dataChanged.connect def on_imc_file_tree_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): item = top_left.internalPointer() if isinstance(item, IMCFilePanoramaModel): if item.is_shown: self._controller.hide_imc_file_panorama(item) else: self._controller.show_imc_file_panorama(item) elif isinstance(item, IMCFileAcquisitionModel): if item.is_loaded: self._controller.unload_imc_file_acquisition(item) else: self._controller.load_imc_file_acquisition(item) # noinspection PyUnresolvedReferences @self._imc_file_tree_view.events.imc_file_closed.connect def on_imc_file_tree_view_imc_file_closed(imc_file: IMCFileModel): self._controller.close_imc_file(imc_file) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._channel_table_model.dataChanged.connect def on_channel_table_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): channel = self._controller.channels[top_left.row()] if channel.is_shown: self._controller.hide_channel(channel) else: self._controller.show_channel(channel) channel_table_selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @channel_table_selection_model.selectionChanged.connect def on_channel_table_view_selection_changed( selected: QItemSelection, deselected: QItemSelection): selected_channels = [] for index in self._channel_table_view.selectedIndexes(): index = self._channel_table_proxy_model.mapToSource(index) channel = self._controller.channels[index.row()] selected_channels.append(channel) self._controller.selected_channels = selected_channels def select_channel(self, channel_index: int): top_left = self._channel_table_model.index(channel_index, 0) top_left = self._channel_table_proxy_model.mapFromSource(top_left) bottom_right = self._channel_table_model.index( channel_index, self._channel_table_model.columnCount() - 1) bottom_right = self._channel_table_proxy_model.mapFromSource( bottom_right) selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) selection_model.clearSelection( ) # first clear the selection, to make sure the channel controls refresh selection_model.select(QItemSelection(top_left, bottom_right), QItemSelectionModel.Select) def refresh_channel_controls_widget(self): self._channel_controls_widget.refresh() if len(self._controller.selected_channels) > 0: self._channel_controls_container.setCurrentIndex(1) else: self._channel_controls_container.setCurrentIndex(0) @property def controller(self): return self._controller @property def imc_file_tree_model(self) -> IMCFileTreeModel: return self._imc_file_tree_model @property def channel_table_model(self) -> ChannelTableModel: return self._channel_table_model
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super(PackagesDialog, self).__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:<br>' else: text = 'The following packages will be modified:<br>' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 300 if remove_only else 420 if remove_only: self.setWindowTitle("Remove Packages") elif update_only: self.setWindowTitle("Update Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = ("Some of the functionality of Anaconda Navigator " "will be limited in <b>offline mode</b>. <br><br>" "Installation and upgrade actions will be subject to " "the packages currently available on your package " "cache.") self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = ('Downgrading/removing these packages will modify ' 'Anaconda Navigator dependencies.') self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) deps = set() deps = deps.union({p['name'] for p in actions_link}) deps = deps.union({p['name'] for p in actions_unlink}) deps = deps - set(packages) deps = sorted(list(deps)) count_total_packages = len(packages) + len(deps) plural_total = 's' if count_total_packages != 1 else '' plural_selected = 's' if len(packages) != 1 else '' self.table.setRowCount(count_total_packages) self.table.setColumnCount(4) if actions_link: description = '{0} package{1} will be installed'.format( count_total_packages, plural_total) self.table.showColumn(2) self.table.showColumn(3) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.setHorizontalHeaderLabels( ['Name', 'Unlink', 'Link', 'Channel']) description = '{0} package{1} will be removed'.format( count_total_packages, plural_total) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) items = [item_name, item_unlink_v, item_link_v, item_link_c] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = ( '<b>*</b> indicates the package is a dependency of a ' 'selected package{0}<br>').format(plural_selected) self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels( ['Name ', 'Unlink ', 'Link ', 'Channel ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum( self.table.columnWidth(i) for i in range(self.table.columnCount())) delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value)
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None self.btn_save_and_close = None self.btn_close = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data self.data.flags.writeable = True is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.ndim > 3: self.error( _("Arrays with more than 3 dimensions are not " "supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error( _("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error( _("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget( ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect( self.save_and_close_enable) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect( self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel( _("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, left_top, bottom_right): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':'] * 3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText( (r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)] * 3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index == None: stack_index = self.stack.count() try: self.stack.addWidget( ArrayEditorWidget(self, self.data[slice_index])) except IndexError: # Handle arrays of size 0 in one axis self.stack.addWidget(ArrayEditorWidget(self, self.data)) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i'] * 3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText( ('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index] - 1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class SinglePassData(BaseWidget): def __init__(self, parent=None, prefix='', bpm=''): super().__init__(parent=parent, prefix=prefix, bpm=bpm, data_prefix='SP_') self.radio_buttons = [] self.setupui() def setupui(self): vbl = QVBoxLayout(self) self.stack = QStackedWidget(self) vbl.addWidget(self.stack) rbA = QRadioButton('Antennas', self) rbP = QRadioButton('Positions', self) rbA.toggled.connect(_part(self.toggle_button, 0)) rbP.toggled.connect(_part(self.toggle_button, 1)) self.radio_buttons.append(rbA) self.radio_buttons.append(rbP) rbA.setChecked(True) hbl = QHBoxLayout() hbl.addStretch() hbl.addWidget(rbA) hbl.addStretch() hbl.addWidget(rbP) hbl.addStretch() vbl.addItem(hbl) # ##### Antennas Widget ###### stack1 = QWidget(self.stack) self.stack.addWidget(stack1) vbl = QVBoxLayout(stack1) graph = self.create_graph(stack1, 'ant') vbl.addWidget(graph) stats = self.create_statistics(stack1, 'ant') vbl.addWidget(stats) # ##### Position and Amplitudes Widget ###### stack2 = QWidget(self.stack) self.stack.addWidget(stack2) vbl = QVBoxLayout(stack2) graph = self.create_graph(stack2, 'pos') vbl.addWidget(graph) graph = self.create_graph(stack2, 'amp') vbl.addWidget(graph) self.setStyleSheet(""" #SinglePassDataGraph{ min-width:48em; min-height:24em; } QLabel{ min-width:6em; max-width:6em; min-height:1.5em; max-height:1.5em; }""") def create_statistics(self, wid, typ='pos'): text, unit, names, _ = self._get_properties(typ) wid = QWidget(wid) hbl = QHBoxLayout(wid) hbl.addStretch() grpbx = QGroupBox('Statistics', wid) hbl.addWidget(grpbx) hbl.addStretch() gdl = QGridLayout(grpbx) gdl.setHorizontalSpacing(20) gdl.setVerticalSpacing(20) stats = ('MeanValue', 'Sigma', 'MinValue', 'MaxValue') for j, stat in enumerate(('Average', 'Sigma', 'Minimum', 'Maximum')): lab = QLabel(stat, wid) lab.setAlignment(Qt.AlignCenter) gdl.addWidget(lab, 0, j + 1) for i, name in enumerate(names): lab = QLabel(text[:3] + name, wid) lab.setAlignment(Qt.AlignCenter) gdl.addWidget(lab, i + 1, 0) for j, stat in enumerate(stats): lab = SiriusLabel( wid, init_channel=self.get_pvname(name + '_STATS' + stat + '_RBV')) lab.setAlignment(Qt.AlignCenter) lab.unit_changed(unit) lab.showUnits = True lab.precisionFromPV = False lab.precision = 3 gdl.addWidget(lab, i + 1, j + 1) return wid def create_graph(self, wid, typ='pos'): text, unit, names, colors = self._get_properties(typ) if typ == 'pos': unit = unit[1:] suff = '-Mon' CLASS = GraphTime elif typ == 'ant': suff = 'ArrayData' CLASS = GraphWave else: suff = '-Mon' CLASS = GraphTime graph = CLASS(wid, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix) graph.setLabel('left', text=text, units=unit) graph.setObjectName('SinglePassDataGraph') for name, cor in zip(names, colors): opts = dict(y_channel=name + suff, name=name[2:], color=cor, lineStyle=1, lineWidth=1) # NOTE: If > 1: very low performance if typ == 'pos': opts['y_channel'] = self.get_pvname(opts['y_channel'], is_data=False) graph.addYChannel(add_scale=1e-9, **opts) elif typ == 'amp': opts['y_channel'] = self.get_pvname(opts['y_channel'], is_data=False) graph.addYChannel(**opts) else: opts['name'] = text[:3] + name opts['y_channel'] = self.get_pvname(opts['y_channel']) graph.addChannel(**opts) return graph def _get_properties(self, typ='pos'): if typ == 'pos': text = 'Positions' unit = 'nm' names = ('SPPosX', 'SPPosY', 'SPPosQ', 'SPSum') colors = ('blue', 'red', 'green', 'black') elif typ == 'ant': text = 'Antennas' unit = 'count' names = ('A', 'B', 'C', 'D') colors = ('blue', 'red', 'green', 'magenta') if typ == 'amp': text = 'Amplitudes' unit = 'count' names = ('SPAmplA', 'SPAmplB', 'SPAmplC', 'SPAmplD') colors = ('blue', 'red', 'green', 'magenta') return text, unit, names, colors def toggle_button(self, i, tog): if tog: self.stack.setCurrentIndex(i)