def get_orbitdetails_widget(self, parent): """.""" grp_bx = QWidget(parent) grp_bx.setLayout(QVBoxLayout()) lbl = CALabel('OfflineOrb:', grp_bx) combo = OfflineOrbControl( grp_bx, self.device, self.ctrls, prefix=self.prefix, acc=self.acc) rules = ( '[{"name": "EnblRule", "property": "Visible", ' + '"expression": "not ch[0]", "channels": [{"channel": "' + self.devpref.substitute(propty='SOFBMode-Sts') + '", "trigger": true}]}]') combo.rules = rules lbl.rules = rules fbl = QFormLayout() grp_bx.layout().addLayout(fbl) fbl.addRow(lbl, combo) grp_bx.layout().addStretch() hbl = QHBoxLayout() grp_bx.layout().addLayout(hbl) fbl = QFormLayout() hbl.addItem(fbl) lbl = QLabel('Orbit [Hz]', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'OrbAcqRate') fbl.addRow(lbl, wid) lbl = QLabel('Kicks [Hz]', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'KickAcqRate') fbl.addRow(lbl, wid) wid = QWidget(grp_bx) wid.setStyleSheet('max-width:6em;') hbl.addWidget(wid) vbl = QVBoxLayout(wid) vbl.setContentsMargins(0, 0, 0, 0) lab = QLabel('Sync. Injection', wid, alignment=Qt.AlignCenter) vbl.addWidget(lab) hbl = QHBoxLayout() hbl.setContentsMargins(0, 0, 0, 0) vbl.addItem(hbl) spt = PyDMStateButton( wid, self.devpref.substitute(propty='SyncWithInjection-Sel')) rdb = SiriusLedState( wid, self.devpref.substitute(propty='SyncWithInjection-Sts')) hbl.addWidget(spt) hbl.addWidget(rdb) grp_bx.layout().addStretch() fbl = QFormLayout() grp_bx.layout().addLayout(fbl) lbl = QLabel('Smooth Method', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair_sel(grp_bx, 'SmoothMethod') fbl.addRow(lbl, wid) if self.isring: lbl = QLabel('Extend Ring', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'RingSize') fbl.addRow(lbl, wid) return grp_bx
def setupui(self): vbl = QVBoxLayout(self) tip = 'Configure ' + ('Acquisition' if self.is_orb else 'Correctors') pv = 'TrigAcqConfig-Cmd' if self.is_orb else 'CorrConfig-Cmd' conf = PyDMPushButton(self, init_channel=self.devpref.substitute(propty=pv), pressValue=1) conf.setToolTip(tip) conf.setIcon(qta.icon('fa5s.sync')) conf.setObjectName('conf') conf.setStyleSheet( '#conf{min-width:25px; max-width:25px; icon-size:20px;}') vbl.addWidget(conf) pv = 'Orb' if self.is_orb else 'Corr' pdm_led = SiriusLedAlert( self, self.devpref.substitute(propty=pv + 'Status-Mon')) hbl = QHBoxLayout() hbl.setSpacing(9) hbl.addWidget(pdm_led) hbl.addWidget(QLabel('Global Status', self)) hbl.addStretch() hbl.addWidget(conf) vbl.addItem(hbl) grpbx = self.creategroupbox() vbl.addWidget(grpbx)
class PMFlowAreaWidget(QWidget): def __init__(self): super().__init__() from pmgwidgets import PMFlowLayout self.outer_layout = QVBoxLayout() self.flow_layout = PMFlowLayout() self.setMinimumWidth(100) self.outer_layout.addLayout(self.flow_layout) spacer_v = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) self.outer_layout.addItem(spacer_v) self.setLayout(self.outer_layout) def add_widget(self, w: 'QWidget'): self.flow_layout.add_widget(w) def setup_ui(self): if hasattr(self.widget(), 'setup_ui'): self.widget().setup_ui() def resizeEvent(self, a0: 'QResizeEvent') -> None: super().resizeEvent(a0) layout: 'PMFlowLayout' = self.flow_layout layout.on_resize()
def setupui(self): if self.position: text = 'Positions' names = ('X', 'Y', 'Q', 'SUM') colors = ('blue', 'red', 'green', 'black') else: text = 'Antennas' names = ('A', 'B', 'C', 'D') colors = ('blue', 'red', 'green', 'magenta') vbl = QVBoxLayout(self) graph = GraphWave(self, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix) graph.setLabel('left', text='Amplitude', units='a.u.') graph.setLabel('bottom', text='Frequency', units='Hz') for name, cor in zip(names, colors): opts = dict(y_channel=self.get_pvname(name + 'FFTData-RB.AMP'), x_channel=self.get_pvname(name + 'FFTFreq-RB.AVAL'), name=text[:3] + name, color=cor, lineStyle=1, lineWidth=1) # NOTE: If > 1: very low performance graph.addChannel(**opts) vbl.addWidget(graph) graph = GraphWave(self, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix) graph.setLabel('left', text='Phase', units='rad') graph.setLabel('bottom', text='Frequency', units='Hz') for name, cor in zip(names, colors): opts = dict(y_channel=self.get_pvname(name + 'FFTData-RB.PHA'), x_channel=self.get_pvname(name + 'FFTFreq-RB.AVAL'), name=text[:3] + name, color=cor, lineStyle=1, lineWidth=1) # NOTE: If > 1: very low performance graph.addChannel(**opts) vbl.addWidget(graph) hbl = QHBoxLayout() hbl.addStretch() vbl.addItem(hbl) pb = QPushButton('Open FFT Config', self) hbl.addWidget(pb) hbl.addStretch() Window = create_window_from_widget(FFTConfig, title=self.bpm + ': FFT Config') util.connect_window(pb, Window, parent=self, prefix=self.prefix, bpm=self.bpm, data_prefix=self.data_prefix, position=self.position)
class EFMtoolDialog(QDialog): """A dialog to set up EFM calculation""" def __init__(self, appdata: CnaData, centralwidget): QDialog.__init__(self) self.setWindowTitle("Elementary Flux Mode Computation") self.appdata = appdata self.centralwidget = centralwidget self.layout = QVBoxLayout() l1 = QHBoxLayout() self.constraints = QCheckBox("consider 0 in current scenario as off") self.constraints.setCheckState(Qt.Checked) l1.addWidget(self.constraints) self.layout.addItem(l1) lx = QHBoxLayout() self.button = QPushButton("Compute") self.cancel = QPushButton("Close") lx.addWidget(self.button) lx.addWidget(self.cancel) self.layout.addItem(lx) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.compute) def compute(self): self.setCursor(Qt.BusyCursor) (ems, scenario) = cnapy.core.efm_computation( self.appdata.project.cobra_py_model, self.appdata.project.scen_values, self.constraints.checkState() == Qt.Checked) self.setCursor(Qt.ArrowCursor) if ems is None: QMessageBox.information(self, 'No modes', 'An error occured and modes have not been calculated.') else: if len(ems) == 0: QMessageBox.information(self, 'No modes', 'No elementary modes exist.') else: print(scenario) self.appdata.project.modes = ems self.centralwidget.mode_navigator.current = 0 self.centralwidget.mode_navigator.scenario = scenario self.centralwidget.mode_navigator.title.setText( "Mode Navigation") self.centralwidget.update_mode()
def _create_prop_widget(self, name, parent, wids, align_ver=True): pwid = QWidget(parent) vbl = QVBoxLayout(pwid) lab = QLabel(name) lab.setAlignment(Qt.AlignCenter) vbl.addWidget(lab) hbl = QHBoxLayout() hbl.setAlignment(Qt.AlignCenter) vbl.addItem(hbl) for wid in wids: wid.setParent(pwid) hbl.addWidget(wid) return pwid
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 __init__(self, *args, **kwargs): super(MessageCheckBox, self).__init__(*args, **kwargs) self._checkbox = QCheckBox() # Set layout to include checkbox size = 9 check_layout = QVBoxLayout() check_layout.addItem(QSpacerItem(size, size)) check_layout.addWidget(self._checkbox, 0, Qt.AlignRight) check_layout.addItem(QSpacerItem(size, size)) # Access the Layout of the MessageBox to add the Checkbox layout = self.layout() layout.addLayout(check_layout, 1, 1)
def setupui(self): vbl = QVBoxLayout(self) lab = QLabel('Response Matrix', self, alignment=Qt.AlignCenter) lab.setStyleSheet("font-weight: bold;") vbl.addWidget(lab) graph = Graph(self) vbl.addWidget(graph) self.spbox = QSpinBoxPlus(self) self.spbox.setMaximum(1000) self.spbox.setValue(80) self.spbox.setKeyboardTracking(False) self.spbox.editingFinished.connect(self._update_graph) hbl = QHBoxLayout() vbl.addItem(hbl) hbl.addWidget(QLabel('Lines spacing:', self)) hbl.addWidget(self.spbox) hbl.addStretch() graph.setShowLegend(False) graph.setLabel('bottom', text='BPM Position', units='m') graph.setLabel('left', text='Matrix', units='m/rad') for i in range(self._csorb.nr_corrs): color = 'blue' if i >= self._csorb.nr_ch: color = 'red' if i >= self._csorb.nr_ch + self._csorb.nr_cv: color = 'black' opts = dict( y_channel='', x_channel='', # self.devpref.substitute(propty='BPMPosS-Mon'), name='', color=color, redraw_mode=2, lineStyle=1, lineWidth=3, symbol='o', symbolSize=10) graph.addChannel(**opts) graph.plotItem.scene().sigMouseMoved.connect(self._show_tooltip) self.graph = graph
class RenameMapDialog(QDialog): """A dialog to rename maps""" def __init__(self, appdata: CnaData, central_widget): QDialog.__init__(self) self.setWindowTitle("Rename map") self.appdata = appdata self.central_widget = central_widget self.layout = QVBoxLayout() h1 = QHBoxLayout() label = QLabel("Enter new map name") self.layout.addWidget(label) self.idx = self.central_widget.map_tabs.currentIndex() self.old_name = self.central_widget.map_tabs.tabText(self.idx) self.name_field = QLineEdit(self.old_name) h1.addWidget(self.name_field) self.layout.addItem(h1) l2 = QHBoxLayout() self.button = QPushButton("Rename") self.cancel = QPushButton("Cancel") l2.addWidget(self.button) l2.addWidget(self.cancel) self.layout.addItem(l2) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.apply) def apply(self): new_name = self.name_field.text() if not new_name in self.appdata.project.maps.keys(): self.appdata.project.maps[ new_name] = self.appdata.project.maps.pop(self.old_name) self.central_widget.map_tabs.setTabText(self.idx, new_name) m = self.central_widget.map_tabs.widget(self.idx) m.name = new_name self.accept()
def setupui(self): vbl = QVBoxLayout(self) lab = QLabel('<h2>BPMs List</h2>', alignment=Qt.AlignCenter) vbl.addWidget(lab) vbl.addSpacing(20) hbl = QHBoxLayout() search = QLineEdit(parent=self) search.setPlaceholderText("Search for BPMs...") search.textEdited.connect(self._filter_bpms) hbl.addWidget(search) hbl.addStretch() self.btnautorange = QPushButton('Auto Range graphics', self) hbl.addWidget(self.btnautorange) vbl.addItem(hbl) sa_class = get_custom_widget_class(QScrollArea) scarea = sa_class(self) scarea.setSizeAdjustPolicy(scarea.AdjustToContents) scarea.setWidgetResizable(True) wid = QWidget() wid.setObjectName('scrollarea') wid.setStyleSheet('#scrollarea {background-color: transparent;}') gdl = QGridLayout(wid) gdl.setSpacing(15) for i, bpm in enumerate(sorted(self.bpm_dict.keys())): widb = QWidget(wid) vbl2 = QVBoxLayout(widb) vbl2.addWidget( QLabel('<h3>' + bpm + '</h3>', alignment=Qt.AlignCenter)) wbpm = self.create_graph(widb, bpm=bpm, typ='ant') vbl2.addWidget(wbpm) gdl.addWidget(widb, i // 3, i % 3) self.bpm_dict[bpm] = widb self.gdl = gdl vbl.addWidget(scarea) scarea.setWidget(wid) self.scarea = scarea
def __init__(self, *args, **kwargs): super(MessageCheckBox, self).__init__(*args, **kwargs) self._checkbox = QCheckBox() # Set layout to include checkbox size = 9 check_layout = QVBoxLayout() check_layout_h = QHBoxLayout() check_layout.addItem(QSpacerItem(size, size)) check_layout_h.addStretch() check_layout_h.addWidget(self._checkbox, 1, Qt.AlignRight) check_layout.addLayout(check_layout_h) check_layout.addItem(QSpacerItem(size, size)) # Access the Layout of the MessageBox to add the Checkbox layout = self.layout() if PYQT4: grid_position = 1 else: grid_position = 2 layout.addLayout(check_layout, 1, grid_position)
def _setupUi(self): label = QLabel('<h3>' + self.scrn_prefix + ' Calibration</h3>', self, alignment=Qt.AlignCenter) positioning = QGroupBox('Positioning', self) positioning.setStyleSheet(""" .QLabel{ min-width:16em;\nmax-width:16em; qproperty-alignment: AlignRight;\n}""") positioning.setLayout(self._setupPositionLayout()) LED = QGroupBox('LED Brightness', self) LED.setStyleSheet(""" .QLabel{ min-width:11em;\nmax-width:11em; qproperty-alignment: AlignRight;\n}""") LED.setLayout(self._setupLEDLayout()) Img = QGroupBox('Statistics Unit Conversion (Pixels→mm)', self) Img.setStyleSheet(""" .QLabel{ min-width:12em;\nmax-width:12em; qproperty-alignment: AlignRight;\n}""") Img.setLayout(self._setupImageCalibLayout()) vlay = QVBoxLayout() vlay.addWidget(label) vlay.addItem( QSpacerItem(1, 10, QSzPlcy.Fixed, QSzPlcy.MinimumExpanding)) vlay.addWidget(positioning) vlay.addItem( QSpacerItem(1, 10, QSzPlcy.Fixed, QSzPlcy.MinimumExpanding)) vlay.addWidget(LED) vlay.addItem( QSpacerItem(1, 10, QSzPlcy.Fixed, QSzPlcy.MinimumExpanding)) vlay.addWidget(Img) self.setLayout(vlay)
def setupui(self): """.""" vbl = QVBoxLayout(self) vbl.setAlignment(Qt.AlignCenter) graphx = self.uigetgraph('x', (45, 15)) graphy = self.uigetgraph('y', (45, 15)) suf = 'Orbit' if self.is_orb else 'Correctors' lab = QLabel('<h2>Horizontal ' + suf + '</h2>', self, alignment=Qt.AlignLeft) lab.setStyleSheet("""min-height:1.5em; max-height:1.5em;""") self.hbl_nameh = QHBoxLayout() vbl.addItem(self.hbl_nameh) self.hbl_nameh.addWidget(lab) self.hbl_nameh.addStretch(1) vbl.addWidget(graphx) vbl.addSpacing(30) lab = QLabel('<h2>Vertical ' + suf + '</h2>', self, alignment=Qt.AlignLeft) lab.setStyleSheet("""min-height:1.5em; max-height:1.5em;""") self.hbl_namev = QHBoxLayout() vbl.addItem(self.hbl_namev) self.hbl_namev.addWidget(lab) self.hbl_namev.addStretch(1) vbl.addWidget(graphy) self.graph = {'x': graphx, 'y': graphy} self.hbl = QHBoxLayout() vbl.addItem(self.hbl) self.hbl.addStretch(1) for i, _ in enumerate(self.line_names): grpbx = self.uicreate_groupbox(i) grpbx.setObjectName('GroupBox' + str(i)) self.hbl.addWidget(grpbx) self.hbl.addStretch(1)
class FunctionDisplay(QGroupBox): """ Display controls for an annotated function in a QGroupBox. In order to display function arguments in the user interface, the class must be aware of what the type is of each of the parameters. Instead of requiring a user to enter this information manually, the class takes advantage of the function annotation language described in PEP 3107. This allows us to quickly create the appropriate widget for the given parameter based on the type. If a function parameter is not given an annotation, we will attempt to infer it from the default value if available. If this is not possible, and the type is not specified in the ``annotations`` dictionary an exception will be raised. The created user interface consists of a button to execute the function, the required parameters are always displayed beneath the button, and a :class:`.TogglePanel` object that toggles the view of the optional parameters below. Attributes ---------- accepted_types : list List of types FunctionDisplay can create widgets for. Parameters ---------- func : callable name : str, optional Name to label the box with, by default this will be the function meeting. annotations : dict, optional If the function your are creating a display for is not annotated, you may manually supply types for parameters by passing in a dictionary of name to type mapping. hide_params : list, optional List of parameters to exclude from the display. These should have appropriate defaults. By default, ``self``, ``args`` and ``kwargs`` are all excluded. parent : QWidget, optional """ accepted_types = [bool, str, int, float] def __init__(self, func, name=None, annotations=None, hide_params=None, parent=None): # Function information self.func = func self.signature = inspect.signature(func) self.name = name or self.func.__name__ # Initialize parent super().__init__('{} Parameters'.format(clean_attr(self.name)), parent=parent) # Ignore certain parameters, args and kwargs by default self.hide_params = ['self', 'args', 'kwargs'] if hide_params: self.hide_params.extend(hide_params) # Create basic layout self._layout = QVBoxLayout() self._layout.setSpacing(2) self.setLayout(self._layout) # Create an empty list to fill later with parameter widgets self.param_controls = list() # Add our button to execute the function self.execute_button = QPushButton() self.docs = {'summary': func.__doc__ or '', 'params': {} } if func.__doc__ is not None: try: self.docs.update(**parse_numpy_docstring(func.__doc__)) except Exception as ex: logger.warning('Unable to parse docstring for function %s: %s', name, ex, exc_info=ex) self.execute_button.setToolTip(self.docs['summary']) self.execute_button.setText(clean_attr(self.name)) self.execute_button.clicked.connect(self.execute) self._layout.addWidget(self.execute_button) # Add a panel for the optional parameters self.optional = TogglePanel("Optional Parameters") self.optional.contents = QWidget() self.optional.contents.setLayout(QVBoxLayout()) self.optional.contents.layout().setSpacing(2) self.optional.layout().addWidget(self.optional.contents) self.optional.show_contents(False) self._layout.addWidget(self.optional) self._layout.addItem(QSpacerItem(10, 5, vPolicy=QSizePolicy.Expanding)) # Create parameters from function signature annotations = annotations or dict() for param in [param for param in self.signature.parameters.values() if param.name not in self.hide_params]: logger.debug("Adding parameter %s ", param.name) # See if we received a manual annotation for this parameter if param.name in annotations: _type = annotations[param.name] logger.debug("Found manually specified type %r", _type.__name__) # Try and get the type from the function annotation elif param.annotation != inspect._empty: _type = param.annotation logger.debug("Found annotated type %r ", _type.__name__) # Try and get the type from the default value elif param.default != inspect._empty: _type = type(param.default) logger.debug("Gathered type %r from parameter default ", _type.__name__) # If we don't have a default value or an annotation, # we can not make a widget for this parameter. Since # this is a required variable (no default), the function # will not work without it. Raise an Exception else: raise TypeError("Parameter {} has an unspecified " "type".format(param.name)) # Add our parameter self.add_parameter(param.name, _type, default=param.default) # Hide optional parameter widget if there are no such parameters if not self.optional_params: self.optional.hide() @property def required_params(self): """ Required parameters. """ parameters = self.signature.parameters return [param.parameter for param in self.param_controls if parameters[param.parameter].default == inspect._empty] @property def optional_params(self): """ Optional parameters. """ parameters = self.signature.parameters return [param.parameter for param in self.param_controls if parameters[param.parameter].default != inspect._empty] @Slot() def execute(self): """ Execute :attr:`.func`. This takes the parameters configured by the :attr:`.param_controls` widgets and passes them into the given callable. All generated exceptions are captured and logged. """ logger.info("Executing %s ...", self.name) # If our function does not take any argument # just pass it on. Otherwise, collect information # from the appropriate widgets if not self.signature.parameters: func = self.func else: kwargs = dict() # Gather information from parameter widgets for button in self.param_controls: logger.debug("Gathering parameters for %s ...", button.parameter) val = button.get_param_value() logger.debug("Received %s", val) # Watch for NaN values returned from widgets # This indicates that there was improper information given if np.isnan(val): logger.error("Invalid information supplied for %s " "parameter", button.parameter) return kwargs[button.parameter] = val # Button up function call with partial to try below func = partial(self.func, **kwargs) try: # Execute our function func() except Exception: logger.exception("Exception while executing function") else: logger.info("Operation Complete") def add_parameter(self, name, _type, default=inspect._empty, tooltip=None): """ Add a parameter to the function display. Parameters ---------- name : str Parameter name. _type : type Type of variable that we are expecting the user to input. default : any, optional Default value for the parameter. tooltip : str, optional Tooltip to use for the control widget. If not specified, docstring parameter information will be used if available to generate a default. Returns ------- widget : QWidget The generated widget. """ if tooltip is None: tooltip_header = f'{name} - {_type.__name__}' tooltip = [ tooltip_header, '-' * len(tooltip_header) ] if default != inspect._empty: tooltip.append(f'Default: {default}') try: doc_param = self.docs['params'][name] except KeyError: logger.debug('Parameter information is not available ' 'for %s(%s)', self.name, name) else: if doc_param: tooltip.extend(doc_param) # If the tooltip is just the header, remove the dashes underneath: if len(tooltip) == 2: tooltip = tooltip[:1] tooltip = '\n'.join(tooltip) # Create our parameter control widget # QCheckBox field if _type == bool: cntrl = ParamCheckBox(name, default=default) else: # Check if this is a valid type if _type not in self.accepted_types: raise TypeError("Parameter {} has type {} which can not " "be represented in a widget" "".format(name, _type.__name__)) # Create our QLineEdit cntrl = ParamLineEdit(name, default=default, _type=_type) # Add our button to the widget # If it is required add it above the panel so that it is always # visisble. Otherwise, add it to the hideable panel self.param_controls.append(cntrl) if default == inspect._empty: self.layout().insertWidget(len(self.required_params), cntrl) else: # If this is the first optional parameter, # show the whole optional panel if self.optional.isHidden(): self.optional.show() # Add the control widget to our contents self.optional.contents.layout().addWidget(cntrl) cntrl.param_label.setToolTip(tooltip) return cntrl def sizeHint(self): """Suggested size.""" return QSize(175, 100)
def setupui(self): vbl = QVBoxLayout(self) lab = QLabel('<h2>' + self.bpm + ' Settings</h2>') lab.setAlignment(Qt.AlignCenter) vbl.addWidget(lab) vbl.addSpacing(10) hbl = QHBoxLayout() hbl.setSpacing(15) hbl.addStretch() grpbx = CustomGroupBox('Status', self) gdl = QGridLayout(grpbx) props = (('asyn.CNCT', 'Connected'), ('asyn.ENBL', 'Enabled'), ('RFFEasyn.CNCT', 'RFFE Connected'), ('RFFEasyn.ENBL', 'RFFE Enabled'), ('ADCAD9510PllStatus-Mon', 'Clock Synched')) for i, prop in enumerate(props): led = SiriusLedState(grpbx, init_channel=self.get_pvname(prop[0])) led.setOffColor(led.Red) lab = QLabel(prop[1], grpbx) gdl.addWidget(led, i, 0) gdl.addWidget(lab, i, 1) hbl.addWidget(grpbx) hbl.addStretch() grpbx = CustomGroupBox('Advanced Settings', self) vbl2 = QVBoxLayout(grpbx) vbl2.setSpacing(10) pbt = QPushButton('Software') Window = create_window_from_widget(AdvancedSettings, title=self.bpm + ': Advanced Settings') util.connect_window(pbt, Window, parent=grpbx, prefix=self.prefix, bpm=self.bpm) vbl2.addWidget(pbt) pbt = QPushButton('Hardware') Window = create_window_from_widget(HardwareSettings, title=self.bpm + ': Hardware Settings') util.connect_window(pbt, Window, parent=grpbx, prefix=self.prefix, bpm=self.bpm) vbl2.addWidget(pbt) hbl.addWidget(grpbx) hbl.addStretch() vbl.addItem(hbl) vbl.addSpacing(20) vbl.addStretch() grpbx = self._create_formlayout_groupbox( 'Offset Parameters', (('PosQOffset-SP', 'Offset PosQ'), ('PosXOffset-SP', 'Offset PosX'), ('PosYOffset-SP', 'Offset PosY'))) vbl.addWidget(grpbx) vbl.addSpacing(20) vbl.addStretch() grpbx = self._create_formlayout_groupbox('Gain Parameters', (('PosKq-SP', 'Gain PosQ'), ('PosKsum-SP', 'Gain Sum'), ('PosKx-SP', 'Gain PosX'), ('PosKy-SP', 'Gain PosY'))) vbl.addWidget(grpbx) vbl.addSpacing(20) vbl.addStretch() grpbx = self._create_formlayout_groupbox( 'Informations', (('INFOHarmonicNumber-SP', 'Harmonic Number'), ('INFOFOFBRate-SP', 'FOFB Rate'), ('INFOMONITRate-SP', 'Monitor Rate'), ('INFOTBTRate-SP', 'TbT Rate'), ('RFFEAtt-SP', 'RFFE Attenuation'))) vbl.addWidget(grpbx) vbl.addSpacing(20) vbl.addStretch()
class ModelInfo(QWidget): """A widget that shows infos about the model""" def __init__(self, appdata: CnaData): QWidget.__init__(self) self.appdata = appdata self.layout = QVBoxLayout() label = QLabel("Description") self.layout.addWidget(label) self.description = QTextEdit() self.description.setPlaceholderText("Enter a project description") self.layout.addWidget(self.description) h1 = QHBoxLayout() label = QLabel("Optimization direction") h1.addWidget(label) self.opt_direction = QComboBox() self.opt_direction.insertItem(1, "minimize") self.opt_direction.insertItem(2, "maximize") h1.addWidget(self.opt_direction) self.layout.addItem(h1) self.setLayout(self.layout) self.description.textChanged.connect(self.description_changed) self.opt_direction.currentTextChanged.connect( self.opt_direction_changed) self.update() def update(self): if "description" in self.appdata.project.meta_data: description = self.appdata.project.meta_data["description"] else: description = "" self.description.textChanged.disconnect(self.description_changed) self.description.setText(description) self.description.textChanged.connect(self.description_changed) x = self.appdata.project.cobra_py_model.objective_direction self.opt_direction.currentTextChanged.disconnect( self.opt_direction_changed) if x == "max": self.opt_direction.setCurrentIndex(1) elif x == "min": self.opt_direction.setCurrentIndex(0) self.opt_direction.currentTextChanged.connect( self.opt_direction_changed) def description_changed(self): self.appdata.project.meta_data[ "description"] = self.description.toPlainText() self.appdata.window.unsaved_changes() def opt_direction_changed(self): if self.opt_direction.currentIndex() == 0: self.appdata.project.cobra_py_model.objective_direction = "min" self.optimizationDirectionChanged.emit("min") if self.opt_direction.currentIndex() == 1: self.appdata.project.cobra_py_model.objective_direction = "max" self.optimizationDirectionChanged.emit("max") optimizationDirectionChanged = Signal(str)
def __init__(self, appdata: CnaData, centralwidget): QDialog.__init__(self) self.setWindowTitle("Minimal Cut Sets Computation") self.appdata = appdata self.centralwidget = centralwidget self.eng = appdata.engine self.out = io.StringIO() self.err = io.StringIO() self.layout = QVBoxLayout() l1 = QLabel("Target Region(s)") self.layout.addWidget(l1) s1 = QHBoxLayout() completer = QCompleter( self.appdata.project.cobra_py_model.reactions.list_attr("id"), self) completer.setCaseSensitivity(Qt.CaseInsensitive) self.target_list = QTableWidget(1, 4) self.target_list.setHorizontalHeaderLabels( ["region no", "T", "≥/≤", "t"]) self.target_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.target_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) self.target_list.horizontalHeader().resizeSection(0, 100) self.target_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) self.target_list.horizontalHeader().resizeSection(2, 50) item = QLineEdit("1") self.target_list.setCellWidget(0, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.target_list.setCellWidget(0, 1, item2) combo = QComboBox(self.target_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.target_list.setCellWidget(0, 2, combo) item = QLineEdit("0") self.target_list.setCellWidget(0, 3, item) s1.addWidget(self.target_list) s11 = QVBoxLayout() self.add_target = QPushButton("+") self.add_target.clicked.connect(self.add_target_region) self.rem_target = QPushButton("-") self.rem_target.clicked.connect(self.rem_target_region) s11.addWidget(self.add_target) s11.addWidget(self.rem_target) s1.addItem(s11) self.layout.addItem(s1) l2 = QLabel("Desired Region(s)") self.layout.addWidget(l2) s2 = QHBoxLayout() self.desired_list = QTableWidget(1, 4) self.desired_list.setHorizontalHeaderLabels( ["region no", "D", "≥/≤", "d"]) self.desired_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.desired_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) self.desired_list.horizontalHeader().resizeSection(0, 100) self.desired_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) self.desired_list.horizontalHeader().resizeSection(2, 50) item = QLineEdit("1") self.desired_list.setCellWidget(0, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.desired_list.setCellWidget(0, 1, item2) combo = QComboBox(self.desired_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.desired_list.setCellWidget(0, 2, combo) item = QLineEdit("0") self.desired_list.setCellWidget(0, 3, item) s2.addWidget(self.desired_list) s21 = QVBoxLayout() self.add_desire = QPushButton("+") self.add_desire.clicked.connect(self.add_desired_region) self.rem_desire = QPushButton("-") self.rem_desire.clicked.connect(self.rem_desired_region) s21.addWidget(self.add_desire) s21.addWidget(self.rem_desire) s2.addItem(s21) self.layout.addItem(s2) s3 = QHBoxLayout() sgx = QVBoxLayout() self.gen_kos = QCheckBox("Gene KOs") self.exclude_boundary = QCheckBox( "Exclude boundary\nreactions as cuts") sg1 = QHBoxLayout() s31 = QVBoxLayout() l = QLabel("Max. Solutions") s31.addWidget(l) l = QLabel("Max. Size") s31.addWidget(l) l = QLabel("Time Limit [sec]") s31.addWidget(l) sg1.addItem(s31) s32 = QVBoxLayout() self.max_solu = QLineEdit("inf") self.max_solu.setMaximumWidth(50) s32.addWidget(self.max_solu) self.max_size = QLineEdit("7") self.max_size.setMaximumWidth(50) s32.addWidget(self.max_size) self.time_limit = QLineEdit("inf") self.time_limit.setMaximumWidth(50) s32.addWidget(self.time_limit) sg1.addItem(s32) sgx.addWidget(self.gen_kos) sgx.addWidget(self.exclude_boundary) sgx.addItem(sg1) s3.addItem(sgx) g3 = QGroupBox("Solver") s33 = QVBoxLayout() self.bg1 = QButtonGroup() optlang_solver_name = interface_to_str( appdata.project.cobra_py_model.problem) self.solver_optlang = QRadioButton(f"{optlang_solver_name} (optlang)") self.solver_optlang.setToolTip( "Uses the solver specified by the current model.") s33.addWidget(self.solver_optlang) self.bg1.addButton(self.solver_optlang) self.solver_cplex_matlab = QRadioButton("CPLEX (MATLAB)") self.solver_cplex_matlab.setToolTip( "Only enabled with MATLAB and CPLEX") s33.addWidget(self.solver_cplex_matlab) self.bg1.addButton(self.solver_cplex_matlab) self.solver_cplex_java = QRadioButton("CPLEX (Octave)") self.solver_cplex_java.setToolTip("Only enabled with Octave and CPLEX") s33.addWidget(self.solver_cplex_java) self.bg1.addButton(self.solver_cplex_java) self.solver_intlinprog = QRadioButton("intlinprog (MATLAB)") self.solver_intlinprog.setToolTip("Only enabled with MATLAB") s33.addWidget(self.solver_intlinprog) self.bg1.addButton(self.solver_intlinprog) self.solver_glpk = QRadioButton("GLPK (Octave/MATLAB)") s33.addWidget(self.solver_glpk) self.bg1.addButton(self.solver_glpk) self.bg1.buttonClicked.connect(self.configure_solver_options) g3.setLayout(s33) s3.addWidget(g3) g4 = QGroupBox("MCS search") s34 = QVBoxLayout() self.bg2 = QButtonGroup() self.any_mcs = QRadioButton("any MCS (fast)") self.any_mcs.setChecked(True) s34.addWidget(self.any_mcs) self.bg2.addButton(self.any_mcs) # Search type: by cardinality only with CPLEX possible self.mcs_by_cardinality = QRadioButton("by cardinality") s34.addWidget(self.mcs_by_cardinality) self.bg2.addButton(self.mcs_by_cardinality) self.smalles_mcs_first = QRadioButton("smallest MCS first") s34.addWidget(self.smalles_mcs_first) self.bg2.addButton(self.smalles_mcs_first) g4.setLayout(s34) s3.addWidget(g4) self.layout.addItem(s3) # Disable incompatible combinations if appdata.selected_engine == 'None': self.solver_optlang.setChecked(True) self.solver_cplex_matlab.setEnabled(False) self.solver_cplex_java.setEnabled(False) self.solver_glpk.setEnabled(False) self.solver_intlinprog.setEnabled(False) if optlang_solver_name != 'cplex': self.mcs_by_cardinality.setEnabled(False) else: self.solver_glpk.setChecked(True) if not self.eng.is_cplex_matlab_ready(): self.solver_cplex_matlab.setEnabled(False) if not self.eng.is_cplex_java_ready(): self.solver_cplex_java.setEnabled(False) if self.appdata.is_matlab_set(): self.solver_cplex_java.setEnabled(False) if not self.appdata.is_matlab_set(): self.solver_cplex_matlab.setEnabled(False) self.solver_intlinprog.setEnabled(False) self.configure_solver_options() s4 = QVBoxLayout() self.consider_scenario = QCheckBox( "Consider constraint given by scenario") s4.addWidget(self.consider_scenario) self.advanced = QCheckBox( "Advanced: Define knockout/addition costs for genes/reactions") self.advanced.setEnabled(False) s4.addWidget(self.advanced) self.layout.addItem(s4) buttons = QHBoxLayout() # self.save = QPushButton("save") # buttons.addWidget(self.save) # self.load = QPushButton("load") # buttons.addWidget(self.load) self.compute_mcs = QPushButton("Compute MCS") buttons.addWidget(self.compute_mcs) # self.compute_mcs2 = QPushButton("Compute MCS2") # buttons.addWidget(self.compute_mcs2) self.cancel = QPushButton("Close") buttons.addWidget(self.cancel) self.layout.addItem(buttons) # max width for buttons self.add_target.setMaximumWidth(20) self.rem_target.setMaximumWidth(20) self.add_desire.setMaximumWidth(20) self.rem_desire.setMaximumWidth(20) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.compute_mcs.clicked.connect(self.compute)
class MCSDialog(QDialog): """A dialog to perform minimal cut set computation""" def __init__(self, appdata: CnaData, centralwidget): QDialog.__init__(self) self.setWindowTitle("Minimal Cut Sets Computation") self.appdata = appdata self.centralwidget = centralwidget self.eng = appdata.engine self.out = io.StringIO() self.err = io.StringIO() self.layout = QVBoxLayout() l1 = QLabel("Target Region(s)") self.layout.addWidget(l1) s1 = QHBoxLayout() completer = QCompleter( self.appdata.project.cobra_py_model.reactions.list_attr("id"), self) completer.setCaseSensitivity(Qt.CaseInsensitive) self.target_list = QTableWidget(1, 4) self.target_list.setHorizontalHeaderLabels( ["region no", "T", "≥/≤", "t"]) self.target_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.target_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) self.target_list.horizontalHeader().resizeSection(0, 100) self.target_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) self.target_list.horizontalHeader().resizeSection(2, 50) item = QLineEdit("1") self.target_list.setCellWidget(0, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.target_list.setCellWidget(0, 1, item2) combo = QComboBox(self.target_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.target_list.setCellWidget(0, 2, combo) item = QLineEdit("0") self.target_list.setCellWidget(0, 3, item) s1.addWidget(self.target_list) s11 = QVBoxLayout() self.add_target = QPushButton("+") self.add_target.clicked.connect(self.add_target_region) self.rem_target = QPushButton("-") self.rem_target.clicked.connect(self.rem_target_region) s11.addWidget(self.add_target) s11.addWidget(self.rem_target) s1.addItem(s11) self.layout.addItem(s1) l2 = QLabel("Desired Region(s)") self.layout.addWidget(l2) s2 = QHBoxLayout() self.desired_list = QTableWidget(1, 4) self.desired_list.setHorizontalHeaderLabels( ["region no", "D", "≥/≤", "d"]) self.desired_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.desired_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) self.desired_list.horizontalHeader().resizeSection(0, 100) self.desired_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) self.desired_list.horizontalHeader().resizeSection(2, 50) item = QLineEdit("1") self.desired_list.setCellWidget(0, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.desired_list.setCellWidget(0, 1, item2) combo = QComboBox(self.desired_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.desired_list.setCellWidget(0, 2, combo) item = QLineEdit("0") self.desired_list.setCellWidget(0, 3, item) s2.addWidget(self.desired_list) s21 = QVBoxLayout() self.add_desire = QPushButton("+") self.add_desire.clicked.connect(self.add_desired_region) self.rem_desire = QPushButton("-") self.rem_desire.clicked.connect(self.rem_desired_region) s21.addWidget(self.add_desire) s21.addWidget(self.rem_desire) s2.addItem(s21) self.layout.addItem(s2) s3 = QHBoxLayout() sgx = QVBoxLayout() self.gen_kos = QCheckBox("Gene KOs") self.exclude_boundary = QCheckBox( "Exclude boundary\nreactions as cuts") sg1 = QHBoxLayout() s31 = QVBoxLayout() l = QLabel("Max. Solutions") s31.addWidget(l) l = QLabel("Max. Size") s31.addWidget(l) l = QLabel("Time Limit [sec]") s31.addWidget(l) sg1.addItem(s31) s32 = QVBoxLayout() self.max_solu = QLineEdit("inf") self.max_solu.setMaximumWidth(50) s32.addWidget(self.max_solu) self.max_size = QLineEdit("7") self.max_size.setMaximumWidth(50) s32.addWidget(self.max_size) self.time_limit = QLineEdit("inf") self.time_limit.setMaximumWidth(50) s32.addWidget(self.time_limit) sg1.addItem(s32) sgx.addWidget(self.gen_kos) sgx.addWidget(self.exclude_boundary) sgx.addItem(sg1) s3.addItem(sgx) g3 = QGroupBox("Solver") s33 = QVBoxLayout() self.bg1 = QButtonGroup() optlang_solver_name = interface_to_str( appdata.project.cobra_py_model.problem) self.solver_optlang = QRadioButton(f"{optlang_solver_name} (optlang)") self.solver_optlang.setToolTip( "Uses the solver specified by the current model.") s33.addWidget(self.solver_optlang) self.bg1.addButton(self.solver_optlang) self.solver_cplex_matlab = QRadioButton("CPLEX (MATLAB)") self.solver_cplex_matlab.setToolTip( "Only enabled with MATLAB and CPLEX") s33.addWidget(self.solver_cplex_matlab) self.bg1.addButton(self.solver_cplex_matlab) self.solver_cplex_java = QRadioButton("CPLEX (Octave)") self.solver_cplex_java.setToolTip("Only enabled with Octave and CPLEX") s33.addWidget(self.solver_cplex_java) self.bg1.addButton(self.solver_cplex_java) self.solver_intlinprog = QRadioButton("intlinprog (MATLAB)") self.solver_intlinprog.setToolTip("Only enabled with MATLAB") s33.addWidget(self.solver_intlinprog) self.bg1.addButton(self.solver_intlinprog) self.solver_glpk = QRadioButton("GLPK (Octave/MATLAB)") s33.addWidget(self.solver_glpk) self.bg1.addButton(self.solver_glpk) self.bg1.buttonClicked.connect(self.configure_solver_options) g3.setLayout(s33) s3.addWidget(g3) g4 = QGroupBox("MCS search") s34 = QVBoxLayout() self.bg2 = QButtonGroup() self.any_mcs = QRadioButton("any MCS (fast)") self.any_mcs.setChecked(True) s34.addWidget(self.any_mcs) self.bg2.addButton(self.any_mcs) # Search type: by cardinality only with CPLEX possible self.mcs_by_cardinality = QRadioButton("by cardinality") s34.addWidget(self.mcs_by_cardinality) self.bg2.addButton(self.mcs_by_cardinality) self.smalles_mcs_first = QRadioButton("smallest MCS first") s34.addWidget(self.smalles_mcs_first) self.bg2.addButton(self.smalles_mcs_first) g4.setLayout(s34) s3.addWidget(g4) self.layout.addItem(s3) # Disable incompatible combinations if appdata.selected_engine == 'None': self.solver_optlang.setChecked(True) self.solver_cplex_matlab.setEnabled(False) self.solver_cplex_java.setEnabled(False) self.solver_glpk.setEnabled(False) self.solver_intlinprog.setEnabled(False) if optlang_solver_name != 'cplex': self.mcs_by_cardinality.setEnabled(False) else: self.solver_glpk.setChecked(True) if not self.eng.is_cplex_matlab_ready(): self.solver_cplex_matlab.setEnabled(False) if not self.eng.is_cplex_java_ready(): self.solver_cplex_java.setEnabled(False) if self.appdata.is_matlab_set(): self.solver_cplex_java.setEnabled(False) if not self.appdata.is_matlab_set(): self.solver_cplex_matlab.setEnabled(False) self.solver_intlinprog.setEnabled(False) self.configure_solver_options() s4 = QVBoxLayout() self.consider_scenario = QCheckBox( "Consider constraint given by scenario") s4.addWidget(self.consider_scenario) self.advanced = QCheckBox( "Advanced: Define knockout/addition costs for genes/reactions") self.advanced.setEnabled(False) s4.addWidget(self.advanced) self.layout.addItem(s4) buttons = QHBoxLayout() # self.save = QPushButton("save") # buttons.addWidget(self.save) # self.load = QPushButton("load") # buttons.addWidget(self.load) self.compute_mcs = QPushButton("Compute MCS") buttons.addWidget(self.compute_mcs) # self.compute_mcs2 = QPushButton("Compute MCS2") # buttons.addWidget(self.compute_mcs2) self.cancel = QPushButton("Close") buttons.addWidget(self.cancel) self.layout.addItem(buttons) # max width for buttons self.add_target.setMaximumWidth(20) self.rem_target.setMaximumWidth(20) self.add_desire.setMaximumWidth(20) self.rem_desire.setMaximumWidth(20) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.compute_mcs.clicked.connect(self.compute) @Slot() def configure_solver_options(self): optlang_solver_name = interface_to_str( self.appdata.project.cobra_py_model.problem) if self.solver_optlang.isChecked(): self.gen_kos.setChecked(False) self.gen_kos.setEnabled(False) self.exclude_boundary.setEnabled(True) if optlang_solver_name != 'cplex': if self.mcs_by_cardinality.isChecked(): self.mcs_by_cardinality.setChecked(False) self.any_mcs.setChecked(True) self.mcs_by_cardinality.setEnabled(False) self.mcs_by_cardinality.setChecked(False) else: self.gen_kos.setEnabled(True) self.exclude_boundary.setChecked(False) self.exclude_boundary.setEnabled(False) self.mcs_by_cardinality.setEnabled(True) def add_target_region(self): i = self.target_list.rowCount() self.target_list.insertRow(i) completer = QCompleter( self.appdata.project.cobra_py_model.reactions.list_attr("id"), self) completer.setCaseSensitivity(Qt.CaseInsensitive) item = QLineEdit("1") self.target_list.setCellWidget(i, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.target_list.setCellWidget(i, 1, item2) combo = QComboBox(self.target_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.target_list.setCellWidget(i, 2, combo) item = QLineEdit("0") self.target_list.setCellWidget(i, 3, item) def add_desired_region(self): i = self.desired_list.rowCount() self.desired_list.insertRow(i) completer = QCompleter( self.appdata.project.cobra_py_model.reactions.list_attr("id"), self) completer.setCaseSensitivity(Qt.CaseInsensitive) item = QLineEdit("1") self.desired_list.setCellWidget(i, 0, item) item2 = QLineEdit("") item2.setCompleter(completer) self.desired_list.setCellWidget(i, 1, item2) combo = QComboBox(self.desired_list) combo.insertItem(1, "≤") combo.insertItem(2, "≥") self.desired_list.setCellWidget(i, 2, combo) item = QLineEdit("0") self.desired_list.setCellWidget(i, 3, item) def rem_target_region(self): i = self.target_list.rowCount() self.target_list.removeRow(i-1) def rem_desired_region(self): i = self.desired_list.rowCount() self.desired_list.removeRow(i-1) def compute(self): if self.solver_optlang.isChecked(): self.compute_optlang() else: self.compute_legacy() def compute_legacy(self): self.setCursor(Qt.BusyCursor) # create CobraModel for matlab with self.appdata.project.cobra_py_model as model: if self.consider_scenario.isChecked(): # integrate scenario into model bounds for r in self.appdata.project.scen_values.keys(): model.reactions.get_by_id( r).bounds = self.appdata.project.scen_values[r] cobra.io.save_matlab_model(model, os.path.join( self.appdata.cna_path, "cobra_model.mat"), varname="cbmodel") self.eng.eval("load('cobra_model.mat')", nargout=0) try: self.eng.eval("cnap = CNAcobra2cna(cbmodel);", nargout=0, stdout=self.out, stderr=self.err) except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning(self, 'Unknown exception occured!', exstr+'\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues') return self.eng.eval("genes = [];", nargout=0, stdout=self.out, stderr=self.err) cmd = "maxSolutions = " + str(float(self.max_solu.text())) + ";" self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err) cmd = "maxSize = " + str(int(self.max_size.text())) + ";" self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err) cmd = "milp_time_limit = " + str(float(self.time_limit.text())) + ";" self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err) if self.gen_kos.isChecked(): self.eng.eval("gKOs = 1;", nargout=0) else: self.eng.eval("gKOs = 0;", nargout=0) if self.advanced.isChecked(): self.eng.eval("advanced_on = 1;", nargout=0) else: self.eng.eval("advanced_on = 0;", nargout=0) if self.solver_intlinprog.isChecked(): self.eng.eval("solver = 'intlinprog';", nargout=0) if self.solver_cplex_java.isChecked(): self.eng.eval("solver = 'java_cplex_new';", nargout=0) if self.solver_cplex_matlab.isChecked(): self.eng.eval("solver = 'matlab_cplex';", nargout=0) if self.solver_glpk.isChecked(): self.eng.eval("solver = 'glpk';", nargout=0) if self.any_mcs.isChecked(): self.eng.eval("mcs_search_mode = 'search_1';", nargout=0) elif self.mcs_by_cardinality.isChecked(): self.eng.eval("mcs_search_mode = 'search_2';", nargout=0) elif self.smalles_mcs_first.isChecked(): self.eng.eval("mcs_search_mode = 'search_3';", nargout=0) rows = self.target_list.rowCount() for i in range(0, rows): p1 = self.target_list.cellWidget(i, 0).text() p2 = self.target_list.cellWidget(i, 1).text() if self.target_list.cellWidget(i, 2).currentText() == '≤': p3 = "<=" else: p3 = ">=" p4 = self.target_list.cellWidget(i, 3).text() cmd = "dg_T = {[" + p1+"], '" + p2 + \ "', '" + p3 + "', [" + p4 + "']};" self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err) rows = self.desired_list.rowCount() for i in range(0, rows): p1 = self.desired_list.cellWidget(i, 0).text() p2 = self.desired_list.cellWidget(i, 1).text() if self.desired_list.cellWidget(i, 2).currentText() == '≤': p3 = "<=" else: p3 = ">=" p4 = self.desired_list.cellWidget(i, 3).text() cmd = "dg_D = {[" + p1+"], '" + p2 + \ "', '" + p3 + "', [" + p4 + "']};" self.eng.eval(cmd, nargout=0) # get some data self.eng.eval("reac_id = cellstr(cnap.reacID).';", nargout=0, stdout=self.out, stderr=self.err) mcs = [] values = [] reactions = [] reac_id = [] if self.appdata.is_matlab_set(): reac_id = self.eng.workspace['reac_id'] try: self.eng.eval("[mcs] = cnapy_compute_mcs(cnap, genes, maxSolutions, maxSize, milp_time_limit, gKOs, advanced_on, solver, mcs_search_mode, dg_T,dg_D);", nargout=0) except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning(self, 'Unknown exception occured!', exstr+'\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues') return else: self.eng.eval("[reaction, mcs, value] = find(mcs);", nargout=0, stdout=self.out, stderr=self.err) reactions = self.eng.workspace['reaction'] mcs = self.eng.workspace['mcs'] values = self.eng.workspace['value'] elif self.appdata.is_octave_ready(): reac_id = self.eng.pull('reac_id') reac_id = reac_id[0] try: self.eng.eval("[mcs] = cnapy_compute_mcs(cnap, genes, maxSolutions, maxSize, milp_time_limit, gKOs, advanced_on, solver, mcs_search_mode, dg_T,dg_D);", nargout=0) except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning(self, 'Unknown exception occured!', exstr+'\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues') return else: self.eng.eval("[reaction, mcs, value] = find(mcs);", nargout=0, stdout=self.out, stderr=self.err) reactions = self.eng.pull('reaction') mcs = self.eng.pull('mcs') values = self.eng.pull('value') if len(mcs) == 0: QMessageBox.information(self, 'No cut sets', 'Cut sets have not been calculated or do not exist.') else: last_mcs = 1 omcs = [] current_mcs = {} for i in range(0, len(reactions)): reacid = int(reactions[i][0]) reaction = reac_id[reacid-1] c_mcs = int(mcs[i][0]) c_value = int(values[i][0]) if c_value == -1: # -1 stands for removed which is 0 in the ui c_value = 0 if c_mcs > last_mcs: omcs.append(current_mcs) last_mcs = c_mcs current_mcs = {} current_mcs[reaction] = c_value omcs.append(current_mcs) self.appdata.project.modes = omcs self.centralwidget.mode_navigator.current = 0 QMessageBox.information(self, 'Cut sets found', str(len(omcs))+' Cut sets have been calculated.') self.centralwidget.update_mode() self.centralwidget.mode_navigator.title.setText("MCS Navigation") self.setCursor(Qt.ArrowCursor) def compute_optlang(self): self.setCursor(Qt.BusyCursor) max_mcs_num = float(self.max_solu.text()) max_mcs_size = int(self.max_size.text()) timeout = float(self.time_limit.text()) if timeout is float('inf'): timeout = None # if self.gen_kos.isChecked(): # self.eng.eval("gKOs = 1;", nargout=0) # else: # self.eng.eval("gKOs = 0;", nargout=0) # if self.advanced.isChecked(): # self.eng.eval("advanced_on = 1;", nargout=0) # else: # self.eng.eval("advanced_on = 0;", nargout=0) if self.smalles_mcs_first.isChecked(): enum_method = 1 elif self.mcs_by_cardinality.isChecked(): enum_method = 2 elif self.any_mcs.isChecked(): enum_method = 3 with self.appdata.project.cobra_py_model as model: if self.consider_scenario.isChecked(): # integrate scenario into model bounds for r in self.appdata.project.scen_values.keys(): model.reactions.get_by_id( r).bounds = self.appdata.project.scen_values[r] reac_id = model.reactions.list_attr("id") reac_id_symbols = cMCS_enumerator.get_reac_id_symbols(reac_id) rows = self.target_list.rowCount() targets = dict() for i in range(0, rows): p1 = self.target_list.cellWidget(i, 0).text() p2 = self.target_list.cellWidget(i, 1).text() if len(p1) > 0 and len(p2) > 0: if self.target_list.cellWidget(i, 2).currentText() == '≤': p3 = "<=" else: p3 = ">=" p4 = float(self.target_list.cellWidget(i, 3).text()) targets.setdefault(p1, []).append((p2, p3, p4)) targets = list(targets.values()) targets = [cMCS_enumerator.relations2leq_matrix(cMCS_enumerator.parse_relations( t, reac_id_symbols=reac_id_symbols), reac_id) for t in targets] rows = self.desired_list.rowCount() desired = dict() for i in range(0, rows): p1 = self.desired_list.cellWidget(i, 0).text() p2 = self.desired_list.cellWidget(i, 1).text() if len(p1) > 0 and len(p2) > 0: if self.desired_list.cellWidget(i, 2).currentText() == '≤': p3 = "<=" else: p3 = ">=" p4 = float(self.desired_list.cellWidget(i, 3).text()) desired.setdefault(p1, []).append((p2, p3, p4)) desired = list(desired.values()) desired = [cMCS_enumerator.relations2leq_matrix(cMCS_enumerator.parse_relations( d, reac_id_symbols=reac_id_symbols), reac_id) for d in desired] try: mcs = cMCS_enumerator.compute_mcs(model, targets=targets, desired=desired, enum_method=enum_method, max_mcs_size=max_mcs_size, max_mcs_num=max_mcs_num, timeout=timeout, exclude_boundary_reactions_as_cuts=self.exclude_boundary.isChecked()) except cMCS_enumerator.InfeasibleRegion as e: QMessageBox.warning(self, 'Cannot calculate MCS', str(e)) return targets, desired except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning(self, 'An exception has occured!', exstr+'\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues') return targets, desired finally: self.setCursor(Qt.ArrowCursor) if len(mcs) == 0: QMessageBox.information(self, 'No cut sets', 'Cut sets have not been calculated or do not exist.') return targets, desired omcs = [{reac_id[i]: 0 for i in m} for m in mcs] self.appdata.project.modes = omcs self.centralwidget.mode_navigator.current = 0 QMessageBox.information(self, 'Cut sets found', str(len(omcs))+' Cut sets have been calculated.') self.centralwidget.update_mode() self.centralwidget.mode_navigator.title.setText("MCS Navigation")
class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. """ instruments = {} choppers = {} minE = {} maxE = {} def __init__(self): super(PyChopGui, self).__init__() self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 def setInstrument(self, instname): """ Defines the instrument parameters by the name of the instrument. """ self.engine = self.instruments[str(instname)] self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['ChopperCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Label'].setText('Frequency') self.widgets['PulseRemoverCombo']['Combo'].clear() for item in self.choppers[str(instname)]: self.widgets['ChopperCombo']['Combo'].addItem(item) rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies # At the moment, the GUI only supports up to two independent frequencies if not hasattr(maxfreq, '__len__') or len(maxfreq) == 1: self.widgets['PulseRemoverCombo']['Combo'].hide() self.widgets['PulseRemoverCombo']['Label'].hide() for fq in range( rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) if hasattr(self.engine.chopper_system, 'frequency_names'): self.widgets['FrequencyCombo']['Label'].setText( self.engine.chopper_system.frequency_names[0]) else: self.widgets['PulseRemoverCombo']['Combo'].show() self.widgets['PulseRemoverCombo']['Label'].show() if hasattr(self.engine.chopper_system, 'frequency_names'): for idx, chp in enumerate([ self.widgets['FrequencyCombo']['Label'], self.widgets['PulseRemoverCombo']['Label'] ]): chp.setText( self.engine.chopper_system.frequency_names[idx]) for fq in range(rep, maxfreq[0] + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) for fq in range(rep, maxfreq[1] + 1, rep): self.widgets['PulseRemoverCombo']['Combo'].addItem(str(fq)) if len(self.engine.chopper_system.choppers) > 1: self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.engine.chopper_system.isPhaseIndependent: self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText( str(self.engine.chopper_system.defaultPhase[0])) self.widgets['Chopper2Phase']['Label'].setText( self.engine.chopper_system.phaseNames[0]) # Special case for MERLIN - hide phase control from normal users if 'MERLIN' in str(instname) and not self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() self.engine.setChopper( str(self.widgets['ChopperCombo']['Combo'].currentText())) self.engine.setFrequency( float(self.widgets['FrequencyCombo']['Combo'].currentText())) val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) nframe = self.engine.moderator.n_frame if hasattr( self.engine.moderator, 'n_frame') else 1 self.repfig_nframe_edit.setText(str(nframe)) if hasattr(self.engine.chopper_system, 'default_frequencies'): cb = [ self.widgets['FrequencyCombo']['Combo'], self.widgets['PulseRemoverCombo']['Combo'] ] for idx, freq in enumerate( self.engine.chopper_system.default_frequencies): cb[idx].setCurrentIndex([ i for i in range(cb[idx].count()) if str(freq) in cb[idx].itemText(i) ][0]) if idx > 1: break self.tabs.setTabEnabled(self.qetabID, False) if self.engine.has_detector and hasattr(self.engine.detector, 'tthlims'): self.tabs.setTabEnabled(self.qetabID, True) def setChopper(self, choppername): """ Defines the Fermi chopper slit package type by name, or the disk chopper arrangement variant. """ self.engine.setChopper(str(choppername)) self.engine.setFrequency( float(self.widgets['FrequencyCombo']['Combo'].currentText())) # Special case for MERLIN - only enable multirep for 'G' chopper if 'MERLIN' in self.engine.instname: if 'G' in str(choppername): self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText( 'Disk chopper phase delay time') if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() def setFreq(self, freqtext=None, **kwargs): """ Sets the chopper frequency(ies), in Hz. """ freq_gui = float(self.widgets['FrequencyCombo']['Combo'].currentText()) freq_in = kwargs['manual_freq'] if ('manual_freq' in kwargs.keys()) else freq_gui if len(self.engine.getFrequency()) > 1 and ( not hasattr(freq_in, '__len__') or len(freq_in) == 1): freqpr = float( self.widgets['PulseRemoverCombo']['Combo'].currentText()) freq_in = [freq_in, freqpr] if not self.widgets['Chopper2Phase']['Label'].isHidden(): chop2phase = self.widgets['Chopper2Phase']['Edit'].text() if isinstance(self.engine.chopper_system.defaultPhase[0], string_types): chop2phase = str(chop2phase) else: chop2phase = float(chop2phase) % ( 1e6 / self.engine.moderator.source_rep) self.engine.setFrequency(freq_in, phase=chop2phase) else: self.engine.setFrequency(freq_in) def setEi(self): """ Sets the incident energy (or focused incident energy for multi-rep case). """ try: eitxt = float(self.widgets['EiEdit']['Edit'].text()) self.engine.setEi(eitxt) if self.eiPlots.isChecked(): self.calc_callback() except ValueError: raise ValueError('No Ei specified, or Ei string not understood') def calc_callback(self): """ Calls routines to calculate the resolution / flux and to update the Matplotlib graphs. """ try: if self.engine.getChopper() is None: self.setChopper( self.widgets['ChopperCombo']['Combo'].currentText()) self.setEi() self.setFreq() self.calculate() if self.errormess: idx = [ i for i, ei in enumerate(self.eis) if np.abs(ei - self.engine.getEi()) < 1.e-4 ] if idx and self.flux[idx[0]] == 0: raise ValueError(self.errormess) self.errormessage(self.errormess) self.plot_res() self.plot_frame() if self.instSciAct.isChecked(): self.update_script() except ValueError as err: self.errormessage(err) self.plot_flux_ei() self.plot_flux_hz() def calculate(self): """ Performs the resolution and flux calculations. """ self.errormess = None if self.engine.getEi() is None: self.setEi() if self.widgets['MultiRepCheck'].isChecked(): en = np.linspace(0, 0.95, 200) self.eis = self.engine.getAllowedEi() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getMultiRepResolution(en) self.flux = self.engine.getMultiRepFlux() if len(w) > 0: mess = [str(w[i].message) for i in range(len(w))] self.errormess = '\n'.join( [m for m in mess if 'tchop' in m]) else: en = np.linspace(0, 0.95 * self.engine.getEi(), 200) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getResolution(en) self.flux = self.engine.getFlux() if len(w) > 0: raise ValueError(w[0].message) def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): axis.hold(True) else: setattr(self, axisname + '_xlim', 0) axis.clear() axis.axhline(color='k') def plot_res(self): """ Plots the resolution in the resolution tab """ overplot = self.widgets['HoldCheck'].isChecked() multiplot = self.widgets['MultiRepCheck'].isChecked() self._set_overplot(overplot, 'resaxes') self._set_overplot(overplot, 'qeaxes') inst = self.engine.instname freq = self.engine.getFrequency() if hasattr(freq, '__len__'): freq = freq[0] if multiplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95 * Ei, 200) if any(self.res[ie]): if not self.flux[ie]: continue line, = self.resaxes.plot(en, self.res[ie]) label_text = '%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % ( inst, Ei, freq, self.flux[ie]) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95 * ei, 200) line, = self.resaxes.plot(en, self.res) chopper = self.engine.getChopper() label_text = '%s_%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % ( inst, chopper, ei, freq, self.flux) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(ei, label_text, overplot) self.resaxes_xlim = max(ei, self.resaxes_xlim) self.resaxes.set_xlim([0, self.resaxes_xlim]) self.resaxes.legend().draggable() self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.rescanvas.draw() def plot_qe(self, Ei, label_text, hold=False): """ Plots the Q-E diagram """ from scipy import constants E2q, meV2J = (2. * constants.m_n / (constants.hbar**2), constants.e / 1000.) en = np.linspace(-Ei / 5., Ei, 100) q2 = [] for tth in self.engine.detector.tthlims: q = np.sqrt(E2q * (2 * Ei - en - 2 * np.sqrt(Ei * (Ei - en)) * np.cos(np.deg2rad(tth))) * meV2J) / 1e10 q2.append(np.concatenate((np.flipud(q), q))) self._set_overplot(hold, 'qeaxes') self.qeaxes_xlim = max(np.max(q2), self.qeaxes_xlim) line, = self.qeaxes.plot( np.hstack(q2), np.concatenate((np.flipud(en), en)).tolist() * len(self.engine.detector.tthlims)) line.set_label(label_text) self.qeaxes.set_xlim([0, self.qeaxes_xlim]) self.qeaxes.legend().draggable() self.qecanvas.draw() def plot_flux_ei(self, **kwargs): """ Plots the flux vs Ei in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() freq = self.engine.getFrequency() overplot = self.widgets['HoldCheck'].isChecked() if hasattr(freq, '__len__'): freq = freq[0] update = kwargs['update'] if 'update' in kwargs.keys() else False # Do not recalculate if all relevant parameters still the same. _, labels = self.flxaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" ([0-9]+) Hz' tmpinst = [] if (labels and (overplot or len(labels) == 1)) or update: for prevtitle in labels: prevInst, prevChop, prevFreq = re.search(searchStr, prevtitle).groups() if update: tmpinst.append( copy.deepcopy( Instrument(self.instruments[prevInst], prevChop, float(prevFreq)))) else: if inst == prevInst and chop == prevChop and freq == float( prevFreq): return ne = 25 mn = self.minE[inst] mx = (self.flxslder.val / 100) * self.maxE[inst] eis = np.linspace(mn, mx, ne) flux = eis * 0 elres = eis * 0 if update: self.flxaxes1.clear() self.flxaxes2.clear() if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = instrument.getFlux(ei) elres[ie] = instrument.getResolution(0., ei)[0] self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label(labels[ii]) else: for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label('%s "%s" %d Hz' % (inst, chop, freq)) self.flxaxes1.set_xlim([mn, mx]) self.flxaxes2.set_xlim([mn, mx]) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') lg = self.flxaxes2.legend() lg.draggable() self.flxcanvas.draw() def update_slider(self, val=None): """ Callback function for the x-axis slider of the flux tab """ if val is None: val = float( self.flxedt.text()) / self.maxE[self.engine.instname] * 100 if val < self.minE[self.engine.instname]: self.errormessage("Max Ei must be greater than %2.1f" % (self.minE[self.engine.instname])) val = (self.minE[self.engine.instname] + 0.1) / self.maxE[self.engine.instname] * 100 self.flxslder.set_val(val) else: val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) self.plot_flux_ei(update=True) self.flxcanvas.draw() def plot_flux_hz(self): """ Plots the flux vs freq in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() ei = float(self.widgets['EiEdit']['Edit'].text()) overplot = self.widgets['HoldCheck'].isChecked() # Do not recalculate if one of the plots has the same parametersc _, labels = self.frqaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" Ei = ([0-9.-]+) meV' if labels and (overplot or len(labels) == 1): for prevtitle in labels: prevInst, prevChop, prevEi = re.search(searchStr, prevtitle).groups() if inst == prevInst and chop == prevChop and abs( ei - float(prevEi)) < 0.01: return freq0 = self.engine.getFrequency() rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies freqs = range( rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep) flux = np.zeros(len(freqs)) elres = np.zeros(len(freqs)) for ie, freq in enumerate(freqs): if hasattr(freq0, '__len__'): self.setFreq(manual_freq=[freq] + freq0[1:]) else: self.setFreq(manual_freq=freq) with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.frqaxes1.hold(True) self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() self.setFreq(manual_freq=freq0) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') line, = self.frqaxes1.plot(freqs, flux, 'o-') self.frqaxes1.set_xlim([0, np.max(freqs)]) self.frqaxes2.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') line, = self.frqaxes2.plot(freqs, elres, 'o-') line.set_label('%s "%s" Ei = %5.3f meV' % (inst, chop, ei)) lg = self.frqaxes2.legend() lg.draggable() self.frqaxes2.set_xlim([0, np.max(freqs)]) self.frqcanvas.draw() def instSciCB(self): """ Callback function for the "Instrument Scientist Mode" menu option """ # MERLIN is a special case - want to hide ability to change phase from users if 'MERLIN' in self.engine.instname and 'G' in self.engine.getChopper( ): if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText( 'Disk chopper phase delay time') else: self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.instSciAct.isChecked(): self.tabs.insertTab(self.scrtabID, self.scrtab, 'ScriptOutput') self.scrtab.show() else: self.tabs.removeTab(self.scrtabID) self.scrtab.hide() def errormessage(self, message): msg = QMessageBox() msg.setText(str(message)) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): yaml_file = QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)') if isinstance(yaml_file, tuple): yaml_file = yaml_file[0] yaml_file = str(yaml_file) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder try: new_inst = Instrument(yaml_file) except (RuntimeError, AttributeError, ValueError) as err: self.errormessage(err) newname = new_inst.name if newname in self.instruments.keys( ) and not self.overwriteload.isChecked(): overwrite, newname = self._ask_overwrite() if overwrite == 1: return elif overwrite == 0: newname = new_inst.name self.instruments[newname] = new_inst self.choppers[newname] = new_inst.getChopperNames() self.minE[newname] = max([new_inst.emin, 0.01]) self.maxE[newname] = new_inst.emax self.updateInstrumentList() combo = self.widgets['InstrumentCombo']['Combo'] idx = [ i for i in range(combo.count()) if str(combo.itemText(i)) == newname ] combo.setCurrentIndex(idx[0]) self.setInstrument(newname) def _ask_overwrite(self): msg = QDialog() msg.setWindowTitle('Load overwrite') layout = QGridLayout() layout.addWidget( QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) buttons = [ QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to'] ] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 def overwriteCB(idx): self.overwrite_flag = idx msg.accept() for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) msg.exec_() newname = str(newname.text()) if not newname or newname in self.instruments: self.errormessage('Invalid instrument name. Cancelling load.') self.overwrite_flag = 1 return self.overwrite_flag, newname def updateInstrumentList(self): combo = self.widgets['InstrumentCombo']['Combo'] old_instruments = [ str(combo.itemText(i)) for i in range(combo.count()) ] new_instruments = [ inst for inst in self.instruments if inst not in old_instruments ] for inst in new_instruments: combo.addItem(inst) def plot_frame(self): """ Plots the distance-time diagram in the right tab """ if len(self.engine.chopper_system.choppers) > 1: self.engine.n_frame = int(self.repfig_nframe_edit.text()) self.repaxes.clear() self.engine.plotMultiRepFrame(self.repaxes) self.repcanvas.draw() def genText(self): """ Generates text output of the resolution function versus energy transfer and other information. """ en = np.linspace(0, 0.95 * self.engine.getEi(), 10) try: flux = self.engine.getFlux() res = self.engine.getResolution(en) except ValueError as err: self.errormessage(err) raise ValueError(err) obj = self.engine instname, chtyp, freqs, ei_in = tuple( [obj.instname, obj.getChopper(), obj.getFrequency(), obj.getEi()]) ei = ei_in tsqvan, tsqdic, tsqmodchop = obj.getVanVar() v_mod, v_chop = tuple(np.sqrt(tsqmodchop[:2]) * 1e6) x0, _, x1, x2, _ = obj.chopper_system.getDistances() first_component = 'moderator' if x0 != tsqmodchop[2]: x0 = tsqmodchop[2] first_component = 'chopper 1' txt = '# ------------------------------------------------------------- #\n' txt += '# Chop calculation for instrument %s\n' % (instname) if obj.isFermi: txt += '# with chopper %s at %3i Hz\n' % (chtyp, freqs[0]) else: txt += '# in %s mode with:\n' % (chtyp) freq_names = obj.chopper_system.frequency_names for idx in range(len(freq_names)): txt += '# %s at %3i Hz\n' % (freq_names[idx], freqs[idx]) txt += '# ------------------------------------------------------------- #\n' txt += '# Flux = %8.2f n/cm2/s\n' % (flux) txt += '# Elastic resolution = %6.2f meV\n' % (res[0]) txt += '# Time width at sample = %6.2f us, of which:\n' % ( 1e6 * np.sqrt(tsqvan)) for ky, val in list(tsqdic.items()): txt += '# %20s : %6.2f us\n' % (ky, 1e6 * np.sqrt(val)) txt += '# %s distances:\n' % (instname) txt += '# x0 = %6.2f m (%s to Fermi)\n' % (x0, first_component) txt += '# x1 = %6.2f m (Fermi to sample)\n' % (x1) txt += '# x2 = %6.2f m (sample to detector)\n' % (x2) txt += '# Approximate inelastic resolution is given by:\n' txt += '# dE = 2 * E2V * sqrt(ef**3 * t_van**2) / x2\n' txt += '# where: E2V = 4.373e-4 meV/(m/us) conversion from energy to speed\n' txt += '# t_van**2 = (geom*t_mod)**2 + ((1+geom)*t_chop)**2\n' txt += '# geom = (x1 + x2*(ei/ef)**1.5) / x0\n' txt += '# and t_mod and t_chop are the moderator and chopper time widths at the\n' txt += '# moderator and chopper positions (not at the sample as listed above).\n' txt += '# Which in this case is:\n' txt += '# %.4e*sqrt(ef**3 * ( (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2 \n' % ( 874.78672e-6 / x2, v_mod, x1 / x0, x2 / x0) txt += '# + (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2) )\n' % ( v_chop, 1 + x1 / x0, x2 / x0) txt += '# EN (meV) Full dE (meV) Approx dE (meV)\n' for ii in range(len(res)): ef = ei - en[ii] approx = (874.78672e-6 / x2) * np.sqrt(ef**3 * ( (v_mod * ((x1 / x0) + (x2 / x0) * (ei / ef)**1.5))**2 + (v_chop * (1 + (x1 / x0) + (x2 / x0) * (ei / ef)**1.5))**2)) txt += '%12.5f %12.5f %12.5f\n' % (en[ii], res[ii], approx) return txt def showText(self): """ Creates a dialog to show the generated text output. """ try: generatedText = self.genText() except ValueError: return self.txtwin = QDialog() self.txtedt = QTextEdit() self.txtbtn = QPushButton('OK') self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') self.txtwin.setWindowModality(Qt.ApplicationModal) self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ fname = QFileDialog.getSaveFileName(self, 'Open file', '') if isinstance(fname, tuple): fname = fname[0] fid = open(fname, 'w') fid.write(self.genText()) fid.close() def update_script(self): """ Updates the text window with information about the previous calculation. """ if self.widgets['MultiRepCheck'].isChecked(): out = self.engine.getMultiWidths() new_str = '\n' for ie, ee in enumerate(out['Eis']): res = out['Energy'][ie] percent = res / ee * 100 chop_width = out['chopper'][ie] mod_width = out['moderator'][ie] new_str += 'Ei is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % ( ee, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % ( chop_width, mod_width) else: ei = self.engine.getEi() out = self.engine.getWidths() res = out['Energy'] percent = res / ei * 100 chop_width = out['chopper'] mod_width = out['moderator'] new_str = '\nEi is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % ( ei, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % ( chop_width, mod_width) self.scredt.append(new_str) def onHelp(self): """ Shows the help page """ try: from pymantidplot.proxies import showCustomInterfaceHelp showCustomInterfaceHelp("PyChop") except ImportError: helpTxt = "PyChop is a tool to allow direct inelastic neutron\nscattering users to estimate the inelastic resolution\n" helpTxt += "and incident flux for a given spectrometer setting.\n\nFirst select the instrument, chopper settings and\n" helpTxt += "Ei, and then click 'Calculate and Plot'. Data for all\nthe graphs will be generated (may take 1-2s) and\n" helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." self.hlpwin = QDialog() self.hlpedt = QLabel(helpTxt) self.hlpbtn = QPushButton('OK') self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') self.hlpwin.setWindowModality(Qt.ApplicationModal) self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): """ Draws the GUI layout. """ self.widgetslist = [[ 'pair', 'show', 'Instrument', 'combo', self.instruments, self.setInstrument, 'InstrumentCombo' ], [ 'pair', 'show', 'Chopper', 'combo', '', self.setChopper, 'ChopperCombo' ], [ 'pair', 'show', 'Frequency', 'combo', '', self.setFreq, 'FrequencyCombo' ], [ 'pair', 'hide', 'Pulse remover chopper freq', 'combo', '', self.setFreq, 'PulseRemoverCombo' ], [ 'pair', 'show', 'Ei', 'edit', '', self.setEi, 'EiEdit' ], [ 'pair', 'hide', 'Chopper 2 phase delay time', 'edit', '5', self.setFreq, 'Chopper2Phase' ], ['spacer'], [ 'single', 'show', 'Calculate and Plot', 'button', self.calc_callback, 'CalculateButton' ], [ 'single', 'show', 'Hold current plot', 'check', lambda: None, 'HoldCheck' ], [ 'single', 'show', 'Show multi-reps', 'check', lambda: None, 'MultiRepCheck' ], ['spacer'], [ 'single', 'show', 'Show data ascii window', 'button', self.showText, 'ShowAsciiButton' ], [ 'single', 'show', 'Save data as ascii', 'button', self.saveText, 'SaveAsciiButton' ]] self.droplabels = [] self.dropboxes = [] self.singles = [] self.widgets = {} self.leftPanel = QVBoxLayout() self.rightPanel = QVBoxLayout() self.tabs = QTabWidget(self) self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = { 'Combo': self.dropboxes[-1], 'Label': self.droplabels[-1] } elif 'edit' in widget[3]: self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = { 'Edit': self.dropboxes[-1], 'Label': self.droplabels[-1] } else: raise RuntimeError( 'Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.droplabels[-1]) self.leftPanel.addWidget(self.dropboxes[-1]) if 'hide' in widget[1]: self.droplabels[-1].hide() self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError( 'Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.singles[-1]) if 'hide' in widget[1]: self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError( 'Bug in code - widget class %s is not recognised.' % (widget[0])) # Right panel, matplotlib figures self.resfig = Figure() self.resfig.patch.set_facecolor('white') self.rescanvas = FigureCanvas(self.resfig) self.resaxes = self.resfig.add_subplot(111) self.resaxes.axhline(color='k') self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) self.restab = QWidget(self.tabs) self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) self.flxfig = Figure() self.flxfig.patch.set_facecolor('white') self.flxcanvas = FigureCanvas(self.flxfig) self.flxaxes1 = self.flxfig.add_subplot(121) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes2 = self.flxfig.add_subplot(122) self.flxaxes2.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.flxfig_controls = NavigationToolbar(self.flxcanvas, self) self.flxsldfg = Figure() self.flxsldfg.patch.set_facecolor('white') self.flxsldcv = FigureCanvas(self.flxsldfg) self.flxsldax = self.flxsldfg.add_subplot(111) self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) self.flxtab = QWidget(self.tabs) self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) self.flxtab.setLayout(self.flxtabbox) self.frqfig = Figure() self.frqfig.patch.set_facecolor('white') self.frqcanvas = FigureCanvas(self.frqfig) self.frqaxes1 = self.frqfig.add_subplot(121) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.frqaxes2 = self.frqfig.add_subplot(122) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) self.frqtab = QWidget(self.tabs) self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) self.repfig = Figure() self.repfig.patch.set_facecolor('white') self.repcanvas = FigureCanvas(self.repfig) self.repaxes = self.repfig.add_subplot(111) self.repaxes.axhline(color='k') self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) self.repfig_nframe_label = QLabel('Number of frames to plot') self.repfig_nframe_edit = QLineEdit('1') self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) self.reptab = QWidget(self.tabs) self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) self.repfig_nframe.setSizePolicy( QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) self.reptab.setLayout(self.reptabbox) self.qefig = Figure() self.qefig.patch.set_facecolor('white') self.qecanvas = FigureCanvas(self.qefig) self.qeaxes = self.qefig.add_subplot(111) self.qeaxes.axhline(color='k') self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) self.scrtab = QWidget(self.tabs) self.scredt = QTextEdit() self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) self.scrtab.hide() self.tabs.addTab(self.restab, 'Resolution') self.tabs.addTab(self.flxtab, 'Flux-Ei') self.tabs.addTab(self.frqtab, 'Flux-Freq') self.tabs.addTab(self.reptab, 'Time-Distance') self.tdtabID = 3 self.tabs.setTabEnabled(self.tdtabID, False) self.tabs.addTab(self.qetab, 'Q-E') self.qetabID = 4 self.tabs.setTabEnabled(self.qetabID, False) self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) self.menuLoad = QMenu('Load') self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) self.menuOptions = QMenu('Options') self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) self.leftPanelWidget.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show()
class PhasePlaneDialog(QDialog): """A dialog to create phase plane plots""" def __init__(self, appdata): QDialog.__init__(self) self.setWindowTitle("Phase plane plotting") self.appdata = appdata completer = QCompleter( self.appdata.project.cobra_py_model.reactions.list_attr("id"), self) completer.setCaseSensitivity(Qt.CaseInsensitive) self.layout = QVBoxLayout() l1 = QHBoxLayout() t1 = QLabel("Reaction (x-axis):") l1.addWidget(t1) self.x_axis = CompleterLineEdit( self.appdata.project.cobra_py_model.reactions.list_attr("id"), "") self.x_axis.setPlaceholderText("Enter reaction Id") l1.addWidget(self.x_axis) l2 = QHBoxLayout() t2 = QLabel("Reaction (y-axis):") l2.addWidget(t2) self.y_axis = QLineEdit("") self.y_axis.setPlaceholderText("Enter reaction Id") self.y_axis.setCompleter(completer) l2.addWidget(self.y_axis) self.layout.addItem(l1) self.layout.addItem(l2) l3 = QHBoxLayout() self.button = QPushButton("Plot") self.cancel = QPushButton("Close") l3.addWidget(self.button) l3.addWidget(self.cancel) self.layout.addItem(l3) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.compute) def compute(self): self.setCursor(Qt.BusyCursor) with self.appdata.project.cobra_py_model as model: self.appdata.project.load_scenario_into_model(model) x_axis = self.x_axis.text() y_axis = self.y_axis.text() try: x_reac_idx = model.reactions.index(x_axis) y_reac_idx = model.reactions.index(y_axis) except KeyError: return points = 100 with model as ppmodel: ppmodel.objective = ppmodel.reactions[x_reac_idx] ppmodel.objective.direction = 'min' x_lb = ppmodel.slim_optimize() ppmodel.objective.direction = 'max' x_ub = ppmodel.slim_optimize() result2 = numpy.zeros((points, 3)) result2[:, 0] = numpy.linspace(x_lb, x_ub, num=points) var = numpy.linspace(x_lb, x_ub, num=points) lb = numpy.full(points, numpy.nan) ub = numpy.full(points, numpy.nan) with model as ppmodel: ppmodel.objective = ppmodel.reactions[y_reac_idx] for i in range(points): # without second context the original reaction bounds are not restored (?) with ppmodel as ppmodel2: ppmodel2.reactions[x_reac_idx].lower_bound = result2[i, 0] ppmodel2.reactions[x_reac_idx].upper_bound = result2[i, 0] ppmodel2.objective.direction = 'min' lb[i] = result2[i, 1] = ppmodel2.slim_optimize() ppmodel2.objective.direction = 'max' ub[i] = result2[i, 2] = ppmodel2.slim_optimize() _fig, axes = plt.subplots() axes.set_xlabel(model.reactions[x_reac_idx].id) axes.set_ylabel(model.reactions[y_reac_idx].id) x = [v for v in var] + [v for v in reversed(var)] y = [v for v in lb] + [v for v in reversed(ub)] if lb[0] != ub[0]: x.extend([var[0], var[0]]) y.extend([lb[0], ub[0]]) plt.plot(x, y) plt.show() self.appdata.window.centralWidget().show_bottom_of_console() self.setCursor(Qt.ArrowCursor)
def get_measurement_widget(self, parent): """.""" meas_wid = QWidget(parent) meas_lay = QVBoxLayout(meas_wid) strt = PyDMPushButton( meas_wid, init_channel=self.devpref.substitute(propty="MeasRespMat-Cmd"), pressValue=ConstTLines.MeasRespMatCmd.Start) strt.setEnabled(True) strt.setToolTip('Start Measurement') strt.setIcon(qta.icon('fa5s.play')) strt.setObjectName('strt') strt.setStyleSheet( '#strt{min-width:25px; max-width:25px; icon-size:20px;}') stop = PyDMPushButton( meas_wid, init_channel=self.devpref.substitute(propty="MeasRespMat-Cmd"), pressValue=ConstTLines.MeasRespMatCmd.Stop) stop.setEnabled(True) stop.setToolTip('Stop Measurement') stop.setIcon(qta.icon('fa5s.stop')) stop.setObjectName('stop') stop.setStyleSheet( '#stop{min-width:25px; max-width:25px; icon-size:20px;}') rst = PyDMPushButton( meas_wid, init_channel=self.devpref.substitute(propty="MeasRespMat-Cmd"), pressValue=ConstTLines.MeasRespMatCmd.Reset) rst.setEnabled(True) rst.setToolTip('Reset Measurement Status') rst.setIcon(qta.icon('fa5s.sync')) rst.setObjectName('conf') rst.setStyleSheet( '#conf{min-width:25px; max-width:25px; icon-size:20px;}') lbl = PyDMLabel( meas_wid, self.devpref.substitute(propty='MeasRespMat-Mon')) lbl.setAlignment(Qt.AlignCenter) hbl = QHBoxLayout() hbl.setSpacing(8) meas_lay.addItem(hbl) hbl.addWidget(strt) hbl.addWidget(stop) hbl.addWidget(rst) hbl.addStretch() hbl.addWidget(lbl) fml = QFormLayout() meas_lay.addSpacing(20) meas_lay.addItem(fml) lbl = QLabel('CH [urad]', meas_wid) wid = self.create_pair(meas_wid, 'MeasRespMatKickCH') fml.addRow(lbl, wid) lbl = QLabel('CV [urad]', meas_wid) wid = self.create_pair(meas_wid, 'MeasRespMatKickCV') fml.addRow(lbl, wid) if self.acc in {'SI', 'BO'}: lbl = QLabel('RF [Hz]', meas_wid) wid = self.create_pair(meas_wid, 'MeasRespMatKickRF') fml.addRow(lbl, wid) lbl = QLabel('Wait [s]', meas_wid) lbl.setToolTip('Time to wait between kicks') wid = self.create_pair(meas_wid, 'MeasRespMatWait') fml.addRow(lbl, wid) return meas_wid
class ConfigDialog(QDialog): """A dialog to set values in cnapy-config.txt""" def __init__(self, appdata: CnaData): QDialog.__init__(self) self.setWindowTitle("Configure CNApy") cross_icon = QIcon(":/icons/cross.png") cross = cross_icon.pixmap(QSize(32, 32)) self.appdata = appdata self.oeng = appdata.octave_engine self.meng = appdata.matlab_engine self.layout = QVBoxLayout() self.cna_ok = False descr = QLabel("\ Some functionalities in CNApy need a working CNA installation.\n \ To use CNA you need either Matlab >= R2019 or Octave >= 5 .\n \ Below you can choose a Matlab directory or the Octave executable.\n \ Only if one of the engines is green your CNA directory can be validated." ) self.layout.addWidget(descr) ml = QHBoxLayout() label = QLabel("Matlab") label.setFixedWidth(100) ml.addWidget(label) self.ml_label = QLabel() self.ml_label.setPixmap(cross) self.ml_label.setFixedWidth(100) ml.addWidget(self.ml_label) self.choose_ml_path_btn = QPushButton("Choose Matlab folder") self.choose_ml_path_btn.setFixedWidth(300) ml.addWidget(self.choose_ml_path_btn) label2 = QLabel("") label2.setFixedWidth(30) ml.addWidget(label2) self.matlab_path = QLabel() self.matlab_path.setMinimumWidth(200) self.matlab_path.setText(self.appdata.matlab_path) ml.addWidget(self.matlab_path) self.layout.addItem(ml) oc = QHBoxLayout() label = QLabel("Octave") label.setFixedWidth(100) oc.addWidget(label) self.oc_label = QLabel() self.oc_label.setPixmap(cross) self.oc_label.setFixedWidth(100) oc.addWidget(self.oc_label) self.choose_oc_exe_btn = QPushButton("Choose Octave octave-cli(.exe)") self.choose_oc_exe_btn.setFixedWidth(300) oc.addWidget(self.choose_oc_exe_btn) label2 = QLabel("") label2.setFixedWidth(30) oc.addWidget(label2) self.oc_exe = QLabel() self.oc_exe.setText(self.appdata.octave_executable) self.oc_exe.setMinimumWidth(200) oc.addWidget(self.oc_exe) self.layout.addItem(oc) h9 = QHBoxLayout() label = QLabel("Selected engine:") h9.addWidget(label) label2 = QLabel("") label2.setFixedWidth(30) h9.addWidget(label2) self.selected_engine = QComboBox() self.selected_engine.addItem("None") h9.addWidget(self.selected_engine) label2 = QLabel("") label2.setMinimumWidth(200) h9.addWidget(label2) self.layout.addItem(h9) cna_l = QHBoxLayout() label = QLabel("CNA") label.setFixedWidth(100) cna_l.addWidget(label) self.cna_label = QLabel() self.cna_label.setPixmap(cross) self.cna_label.setFixedWidth(100) cna_l.addWidget(self.cna_label) self.choose_cna_path_btn = QPushButton("Choose CNA directory") self.choose_cna_path_btn.setFixedWidth(300) cna_l.addWidget(self.choose_cna_path_btn) label2 = QLabel("") label2.setFixedWidth(30) cna_l.addWidget(label2) self.cna_path = QLabel() self.cna_path.setMinimumWidth(200) self.cna_path.setText(self.appdata.cna_path) cna_l.addWidget(self.cna_path) self.layout.addItem(cna_l) h2 = QHBoxLayout() label = QLabel("Default color for values in a scenario:") h2.addWidget(label) self.scen_color_btn = QPushButton() self.scen_color_btn.setFixedWidth(100) palette = self.scen_color_btn.palette() palette.setColor(QPalette.Button, self.appdata.scen_color) self.scen_color_btn.setPalette(palette) h2.addWidget(self.scen_color_btn) self.layout.addItem(h2) h3 = QHBoxLayout() label = QLabel( "Default color for computed values not part of the scenario:") h3.addWidget(label) self.comp_color_btn = QPushButton() self.comp_color_btn.setFixedWidth(100) palette = self.comp_color_btn.palette() palette.setColor(QPalette.Button, self.appdata.comp_color) self.comp_color_btn.setPalette(palette) h3.addWidget(self.comp_color_btn) self.layout.addItem(h3) h4 = QHBoxLayout() label = QLabel("Special Color used for non equal flux bounds:") h4.addWidget(label) self.spec1_color_btn = QPushButton() self.spec1_color_btn.setFixedWidth(100) palette = self.spec1_color_btn.palette() palette.setColor(QPalette.Button, self.appdata.special_color_1) self.spec1_color_btn.setPalette(palette) h4.addWidget(self.spec1_color_btn) self.layout.addItem(h4) h5 = QHBoxLayout() label = QLabel( "Special Color 2 used for non equal flux bounds that exclude 0:") h5.addWidget(label) self.spec2_color_btn = QPushButton() self.spec2_color_btn.setFixedWidth(100) palette = self.spec2_color_btn.palette() palette.setColor(QPalette.Button, self.appdata.special_color_2) self.spec2_color_btn.setPalette(palette) h5.addWidget(self.spec2_color_btn) self.layout.addItem(h5) h6 = QHBoxLayout() label = QLabel("Color used for empty reaction boxes:") h6.addWidget(label) self.default_color_btn = QPushButton() self.default_color_btn.setFixedWidth(100) palette = self.default_color_btn.palette() palette.setColor(QPalette.Button, self.appdata.default_color) self.default_color_btn.setPalette(palette) h6.addWidget(self.default_color_btn) self.layout.addItem(h6) h = QHBoxLayout() label = QLabel("Work directory:") h.addWidget(label) self.work_directory = QPushButton() self.work_directory.setText(self.appdata.work_directory) h.addWidget(self.work_directory) self.layout.addItem(h) h7 = QHBoxLayout() label = QLabel("Shown number of digits after the decimal point:") h7.addWidget(label) self.rounding = QLineEdit() self.rounding.setFixedWidth(100) self.rounding.setText(str(self.appdata.rounding)) validator = QIntValidator(0, 20, self) self.rounding.setValidator(validator) h7.addWidget(self.rounding) self.layout.addItem(h7) h8 = QHBoxLayout() label = QLabel( "Absolute tolerance used to compare float values in the UI:") h8.addWidget(label) self.abs_tol = QLineEdit() self.abs_tol.setFixedWidth(100) self.abs_tol.setText(str(self.appdata.abs_tol)) validator = QDoubleValidator(self) validator.setTop(1) self.abs_tol.setValidator(validator) h8.addWidget(self.abs_tol) self.layout.addItem(h8) l2 = QHBoxLayout() self.button = QPushButton("Apply Changes") self.cancel = QPushButton("Close") l2.addWidget(self.button) l2.addWidget(self.cancel) self.layout.addItem(l2) self.setLayout(self.layout) # Connecting the signal self.choose_ml_path_btn.clicked.connect(self.choose_ml_path) self.choose_oc_exe_btn.clicked.connect(self.choose_oc_exe) self.choose_cna_path_btn.clicked.connect(self.choose_cna_path) self.work_directory.clicked.connect(self.choose_work_directory) self.scen_color_btn.clicked.connect(self.choose_scen_color) self.comp_color_btn.clicked.connect(self.choose_comp_color) self.spec1_color_btn.clicked.connect(self.choose_spec1_color) self.spec2_color_btn.clicked.connect(self.choose_spec2_color) self.default_color_btn.clicked.connect(self.choose_default_color) self.selected_engine.currentTextChanged.connect(self.update) self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.apply) self.check_all() if self.meng is not None: self.selected_engine.insertItem(1, "Matlab") if self.appdata.selected_engine == "matlab": self.selected_engine.setCurrentIndex(1) if self.oeng is not None: self.selected_engine.insertItem(1, "Octave") if self.appdata.selected_engine == "octave": self.selected_engine.setCurrentIndex(1) self.update() def update(self): cross_icon = QIcon(":/icons/cross.png") cross = cross_icon.pixmap(QSize(32, 32)) check_icon = QIcon(":/icons/check.png") check = check_icon.pixmap(QSize(32, 32)) selected_engine = self.selected_engine.currentText() if selected_engine == "None": qmark_icon = QIcon(":/icons/qmark.png") qmark = qmark_icon.pixmap(QSize(32, 32)) self.cna_label.setPixmap(qmark) else: if self.cna_ok: self.cna_label.setPixmap(check) else: self.cna_label.setPixmap(cross) if self.oeng is not None: # disable button if octave is already working self.oc_label.setPixmap(check) else: self.oc_label.setPixmap(cross) if self.meng is not None: self.ml_label.setPixmap(check) else: self.ml_label.setPixmap(cross) self.selected_engine.currentTextChanged.disconnect(self.update) self.selected_engine.clear() self.selected_engine.addItem("None") if self.meng is not None: self.selected_engine.addItem("Matlab") if self.oeng is not None: self.selected_engine.addItem("Octave") # if selected_engine is "None": self.selected_engine.setCurrentIndex(0) if selected_engine == "Matlab": self.selected_engine.setCurrentIndex(1) if selected_engine == "Octave": if self.selected_engine.count() == 2: self.selected_engine.setCurrentIndex(1) elif self.selected_engine.count() == 3: self.selected_engine.setCurrentIndex(2) self.selected_engine.currentTextChanged.connect(self.update) def choose_ml_path(self): dialog = QFileDialog(self, directory=self.matlab_path.text()) dialog.setFileMode(QFileDialog.DirectoryOnly) directory: str = dialog.getExistingDirectory() if not directory or len( directory) == 0 or not os.path.exists(directory): return self.choose_ml_path_btn.setEnabled(False) self.matlab_path.setText(directory) self.try_install_matlab_engine(directory) self.check_matlab() self.choose_ml_path_btn.setEnabled(True) self.update() def try_install_matlab_engine(self, directory: str): try: path = os.path.join(directory, 'extern/engines/python') cwd = os.getcwd() os.chdir(path) temp_dir = TemporaryDirectory() os.system("python setup.py build --build-base=" + temp_dir.name + ' install') os.chdir(cwd) except FileNotFoundError: # no Matlab engine found pass def choose_oc_exe(self): dialog = QFileDialog(self, directory=self.oc_exe.text()) filename: str = dialog.getOpenFileName(directory=os.getcwd())[0] if not filename or len(filename) == 0 or not os.path.exists(filename): return self.oc_exe.setText(filename) if os.path.isfile(filename): os.environ['OCTAVE_EXECUTABLE'] = filename self.choose_oc_exe_btn.setEnabled(False) self.check_octave() self.choose_oc_exe_btn.setEnabled(True) self.update() def check_all(self): self.check_octave() self.check_matlab() self.check_cna() def check_octave(self): if self.oeng is None: self.oeng = try_octave_engine(self.oc_exe.text()) def check_matlab(self): # only recheck matlab if necessary if self.meng is None: self.meng = try_matlab_engine() def choose_cna_path(self): dialog = QFileDialog(self, directory=self.cna_path.text()) dialog.setFileMode(QFileDialog.DirectoryOnly) directory: str = dialog.getExistingDirectory() if not directory or len( directory) == 0 or not os.path.exists(directory): return self.cna_path.setText(directory) self.update() self.reset_engine() self.check_cna() self.update() def choose_work_directory(self): dialog = QFileDialog(self, directory=self.work_directory.text()) directory: str = dialog.getExistingDirectory() if not directory or len( directory) == 0 or not os.path.exists(directory): return self.work_directory.setText(directory) def reset_engine(self): # This resets the engines if self.oeng is None: self.meng = try_matlab_engine() else: self.oeng = try_octave_engine(self.oc_exe.text()) def check_cna(self): if self.oeng is not None: self.cna_ok = try_cna(self.oeng, self.cna_path.text()) elif self.meng is not None: self.cna_ok = try_cna(self.meng, self.cna_path.text()) def choose_scen_color(self): palette = self.scen_color_btn.palette() initial = palette.color(QPalette.Button) dialog = QColorDialog(self) color: str = dialog.getColor(initial) if color.isValid(): palette.setColor(QPalette.Button, color) self.scen_color_btn.setPalette(palette) def choose_comp_color(self): palette = self.comp_color_btn.palette() initial = palette.color(QPalette.Button) dialog = QColorDialog(self) color: str = dialog.getColor(initial) if color.isValid(): palette.setColor(QPalette.Button, color) self.comp_color_btn.setPalette(palette) def choose_spec1_color(self): palette = self.spec1_color_btn.palette() initial = palette.color(QPalette.Button) dialog = QColorDialog(self) color: str = dialog.getColor(initial) if color.isValid(): palette.setColor(QPalette.Button, color) self.spec1_color_btn.setPalette(palette) def choose_spec2_color(self): palette = self.spec2_color_btn.palette() initial = palette.color(QPalette.Button) dialog = QColorDialog(self) color: str = dialog.getColor(initial) if color.isValid(): palette.setColor(QPalette.Button, color) self.spec2_color_btn.setPalette(palette) def choose_default_color(self): palette = self.default_color_btn.palette() initial = palette.color(QPalette.Button) dialog = QColorDialog(self) color: str = dialog.getColor(initial) if color.isValid(): palette.setColor(QPalette.Button, color) self.default_color_btn.setPalette(palette) def apply(self): self.appdata.matlab_path = self.matlab_path.text() self.appdata.octave_executable = self.oc_exe.text() self.appdata.cna_path = self.cna_path.text() self.appdata.matlab_engine = self.meng self.appdata.octave_engine = self.oeng if self.selected_engine.currentText() == "None": self.appdata.selected_engine = None elif self.selected_engine.currentText() == "Matlab": self.appdata.selected_engine = "matlab" elif self.selected_engine.currentText() == "Octave": self.appdata.selected_engine = "octave" self.appdata.select_engine() self.appdata.window.disable_enable_dependent_actions() self.appdata.work_directory = self.work_directory.text() palette = self.scen_color_btn.palette() self.appdata.scen_color = palette.color(QPalette.Button) palette = self.comp_color_btn.palette() self.appdata.comp_color = palette.color(QPalette.Button) palette = self.spec1_color_btn.palette() self.appdata.special_color_1 = palette.color(QPalette.Button) palette = self.spec2_color_btn.palette() self.appdata.special_color_2 = palette.color(QPalette.Button) palette = self.default_color_btn.palette() self.appdata.default_color = palette.color(QPalette.Button) self.appdata.rounding = int(self.rounding.text()) self.appdata.abs_tol = float(self.abs_tol.text()) parser = configparser.ConfigParser() parser.add_section('cnapy-config') parser.set('cnapy-config', 'version', self.appdata.version) parser.set('cnapy-config', 'matlab_path', self.appdata.matlab_path) parser.set('cnapy-config', 'OCTAVE_EXECUTABLE', self.appdata.octave_executable) parser.set('cnapy-config', 'work_directory', self.appdata.work_directory) parser.set('cnapy-config', 'cna_path', self.appdata.cna_path) parser.set('cnapy-config', 'scen_color', str(self.appdata.scen_color.rgb())) parser.set('cnapy-config', 'comp_color', str(self.appdata.comp_color.rgb())) parser.set('cnapy-config', 'spec1_color', str(self.appdata.special_color_1.rgb())) parser.set('cnapy-config', 'spec2_color', str(self.appdata.special_color_2.rgb())) parser.set('cnapy-config', 'default_color', str(self.appdata.default_color.rgb())) parser.set('cnapy-config', 'rounding', str(self.appdata.rounding)) parser.set('cnapy-config', 'abs_tol', str(self.appdata.abs_tol)) parser.set('cnapy-config', 'selected_engine', str(self.appdata.selected_engine)) try: fp = open(self.appdata.conf_path, "w") except FileNotFoundError: os.makedirs( appdirs.user_config_dir("cnapy", roaming=True, appauthor=False)) fp = open(self.appdata.conf_path, "w") parser.write(fp) fp.close() self.accept()
class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. """ instruments = {} choppers = {} minE = {} maxE = {} def __init__(self): super(PyChopGui, self).__init__() self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 def setInstrument(self, instname): """ Defines the instrument parameters by the name of the instrument. """ self.engine = self.instruments[str(instname)] self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['ChopperCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Label'].setText('Frequency') self.widgets['PulseRemoverCombo']['Combo'].clear() for item in self.choppers[str(instname)]: self.widgets['ChopperCombo']['Combo'].addItem(item) rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies # At the moment, the GUI only supports up to two independent frequencies if not hasattr(maxfreq, '__len__') or len(maxfreq) == 1: self.widgets['PulseRemoverCombo']['Combo'].hide() self.widgets['PulseRemoverCombo']['Label'].hide() for fq in range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) if hasattr(self.engine.chopper_system, 'frequency_names'): self.widgets['FrequencyCombo']['Label'].setText(self.engine.chopper_system.frequency_names[0]) else: self.widgets['PulseRemoverCombo']['Combo'].show() self.widgets['PulseRemoverCombo']['Label'].show() if hasattr(self.engine.chopper_system, 'frequency_names'): for idx, chp in enumerate([self.widgets['FrequencyCombo']['Label'], self.widgets['PulseRemoverCombo']['Label']]): chp.setText(self.engine.chopper_system.frequency_names[idx]) for fq in range(rep, maxfreq[0] + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) for fq in range(rep, maxfreq[1] + 1, rep): self.widgets['PulseRemoverCombo']['Combo'].addItem(str(fq)) if len(self.engine.chopper_system.choppers) > 1: self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.engine.chopper_system.isPhaseIndependent: self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText(str(self.engine.chopper_system.defaultPhase[0])) self.widgets['Chopper2Phase']['Label'].setText(self.engine.chopper_system.phaseNames[0]) # Special case for MERLIN - hide phase control from normal users if 'MERLIN' in str(instname) and not self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() self.engine.setChopper(str(self.widgets['ChopperCombo']['Combo'].currentText())) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) nframe = self.engine.moderator.n_frame if hasattr(self.engine.moderator, 'n_frame') else 1 self.repfig_nframe_edit.setText(str(nframe)) self.repfig_nframe_rep1only.setChecked(False) if hasattr(self.engine.chopper_system, 'default_frequencies'): cb = [self.widgets['FrequencyCombo']['Combo'], self.widgets['PulseRemoverCombo']['Combo']] for idx, freq in enumerate(self.engine.chopper_system.default_frequencies): cb[idx].setCurrentIndex([i for i in range(cb[idx].count()) if str(freq) in cb[idx].itemText(i)][0]) if idx > 1: break self.tabs.setTabEnabled(self.qetabID, False) if self.engine.has_detector and hasattr(self.engine.detector, 'tthlims'): self.tabs.setTabEnabled(self.qetabID, True) def setChopper(self, choppername): """ Defines the Fermi chopper slit package type by name, or the disk chopper arrangement variant. """ self.engine.setChopper(str(choppername)) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) # Special case for MERLIN - only enable multirep for 'G' chopper if 'MERLIN' in self.engine.instname: if 'G' in str(choppername): self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() def setFreq(self, freqtext=None, **kwargs): """ Sets the chopper frequency(ies), in Hz. """ freq_gui = float(self.widgets['FrequencyCombo']['Combo'].currentText()) freq_in = kwargs['manual_freq'] if ('manual_freq' in kwargs.keys()) else freq_gui if len(self.engine.getFrequency()) > 1 and (not hasattr(freq_in, '__len__') or len(freq_in)==1): freqpr = float(self.widgets['PulseRemoverCombo']['Combo'].currentText()) freq_in = [freq_in, freqpr] if not self.widgets['Chopper2Phase']['Label'].isHidden(): chop2phase = self.widgets['Chopper2Phase']['Edit'].text() if isinstance(self.engine.chopper_system.defaultPhase[0], string_types): chop2phase = str(chop2phase) else: chop2phase = float(chop2phase) % (1e6 / self.engine.moderator.source_rep) self.engine.setFrequency(freq_in, phase=chop2phase) else: self.engine.setFrequency(freq_in) def setEi(self): """ Sets the incident energy (or focused incident energy for multi-rep case). """ try: eitxt = float(self.widgets['EiEdit']['Edit'].text()) self.engine.setEi(eitxt) if self.eiPlots.isChecked(): self.calc_callback() except ValueError: raise ValueError('No Ei specified, or Ei string not understood') def calc_callback(self): """ Calls routines to calculate the resolution / flux and to update the Matplotlib graphs. """ try: if self.engine.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) self.setEi() self.setFreq() self.calculate() if self.errormess: idx = [i for i, ei in enumerate(self.eis) if np.abs(ei - self.engine.getEi()) < 1.e-4] if idx and self.flux[idx[0]] == 0: raise ValueError(self.errormess) self.errormessage(self.errormess) self.plot_res() self.plot_frame() if self.instSciAct.isChecked(): self.update_script() except ValueError as err: self.errormessage(err) self.plot_flux_ei() self.plot_flux_hz() def calculate(self): """ Performs the resolution and flux calculations. """ self.errormess = None if self.engine.getEi() is None: self.setEi() if self.widgets['MultiRepCheck'].isChecked(): en = np.linspace(0, 0.95, 200) self.eis = self.engine.getAllowedEi() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getMultiRepResolution(en) self.flux = self.engine.getMultiRepFlux() if len(w) > 0: mess = [str(w[i].message) for i in range(len(w))] self.errormess = '\n'.join([m for m in mess if 'tchop' in m]) else: en = np.linspace(0, 0.95*self.engine.getEi(), 200) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getResolution(en) self.flux = self.engine.getFlux() if len(w) > 0: raise ValueError(w[0].message) def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): axis.hold(True) else: setattr(self, axisname+'_xlim', 0) axis.clear() axis.axhline(color='k') def plot_res(self): """ Plots the resolution in the resolution tab """ overplot = self.widgets['HoldCheck'].isChecked() multiplot = self.widgets['MultiRepCheck'].isChecked() self._set_overplot(overplot, 'resaxes') self._set_overplot(overplot, 'qeaxes') inst = self.engine.instname freq = self.engine.getFrequency() if hasattr(freq, '__len__'): freq = freq[0] if multiplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95*Ei, 200) if any(self.res[ie]): if not self.flux[ie]: continue line, = self.resaxes.plot(en, self.res[ie]) label_text = '%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, Ei, freq, self.flux[ie]) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95*ei, 200) line, = self.resaxes.plot(en, self.res) chopper = self.engine.getChopper() label_text = '%s_%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, chopper, ei, freq, self.flux) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(ei, label_text, overplot) self.resaxes_xlim = max(ei, self.resaxes_xlim) self.resaxes.set_xlim([0, self.resaxes_xlim]) self.resaxes.legend().draggable() self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.rescanvas.draw() def plot_qe(self, Ei, label_text, hold=False): """ Plots the Q-E diagram """ from scipy import constants E2q, meV2J = (2. * constants.m_n / (constants.hbar ** 2), constants.e / 1000.) en = np.linspace(-Ei / 5., Ei, 100) q2 = [] for tth in self.engine.detector.tthlims: q = np.sqrt(E2q * (2 * Ei - en - 2 * np.sqrt(Ei * (Ei - en)) * np.cos(np.deg2rad(tth))) * meV2J) / 1e10 q2.append(np.concatenate((np.flipud(q), q))) self._set_overplot(hold, 'qeaxes') self.qeaxes_xlim = max(np.max(q2), self.qeaxes_xlim) line, = self.qeaxes.plot(np.hstack(q2), np.concatenate((np.flipud(en), en)).tolist() * len(self.engine.detector.tthlims)) line.set_label(label_text) self.qeaxes.set_xlim([0, self.qeaxes_xlim]) self.qeaxes.legend().draggable() self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qecanvas.draw() def plot_flux_ei(self, **kwargs): """ Plots the flux vs Ei in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() freq = self.engine.getFrequency() overplot = self.widgets['HoldCheck'].isChecked() if hasattr(freq, '__len__'): freq = freq[0] update = kwargs['update'] if 'update' in kwargs.keys() else False # Do not recalculate if all relevant parameters still the same. _, labels = self.flxaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" ([0-9]+) Hz' tmpinst = [] if (labels and (overplot or len(labels) == 1)) or update: for prevtitle in labels: prevInst, prevChop, prevFreq = re.search(searchStr, prevtitle).groups() if update: tmpinst.append(copy.deepcopy(Instrument(self.instruments[prevInst], prevChop, float(prevFreq)))) else: if inst == prevInst and chop == prevChop and freq == float(prevFreq): return ne = 25 mn = self.minE[inst] mx = (self.flxslder.val/100)*self.maxE[inst] eis = np.linspace(mn, mx, ne) flux = eis*0 elres = eis*0 if update: self.flxaxes1.clear() self.flxaxes2.clear() if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = instrument.getFlux(ei) elres[ie] = instrument.getResolution(0., ei)[0] self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label(labels[ii]) else: for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label('%s "%s" %d Hz' % (inst, chop, freq)) self.flxaxes1.set_xlim([mn, mx]) self.flxaxes2.set_xlim([mn, mx]) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') lg = self.flxaxes2.legend() lg.draggable() self.flxcanvas.draw() def update_slider(self, val=None): """ Callback function for the x-axis slider of the flux tab """ if val is None: val = float(self.flxedt.text()) / self.maxE[self.engine.instname] * 100 if val < self.minE[self.engine.instname]: self.errormessage("Max Ei must be greater than %2.1f" % (self.minE[self.engine.instname])) val = (self.minE[self.engine.instname]+0.1) / self.maxE[self.engine.instname] * 100 self.flxslder.set_val(val) else: val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) self.plot_flux_ei(update=True) self.flxcanvas.draw() def plot_flux_hz(self): """ Plots the flux vs freq in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() ei = float(self.widgets['EiEdit']['Edit'].text()) overplot = self.widgets['HoldCheck'].isChecked() # Do not recalculate if one of the plots has the same parametersc _, labels = self.frqaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" Ei = ([0-9.-]+) meV' if labels and (overplot or len(labels) == 1): for prevtitle in labels: prevInst, prevChop, prevEi = re.search(searchStr, prevtitle).groups() if inst == prevInst and chop == prevChop and abs(ei-float(prevEi)) < 0.01: return freq0 = self.engine.getFrequency() rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies freqs = range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep) flux = np.zeros(len(freqs)) elres = np.zeros(len(freqs)) for ie, freq in enumerate(freqs): if hasattr(freq0, '__len__'): self.setFreq(manual_freq=[freq] + freq0[1:]) else: self.setFreq(manual_freq=freq) with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.frqaxes1.hold(True) self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() self.setFreq(manual_freq=freq0) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') line, = self.frqaxes1.plot(freqs, flux, 'o-') self.frqaxes1.set_xlim([0, np.max(freqs)]) self.frqaxes2.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') line, = self.frqaxes2.plot(freqs, elres, 'o-') line.set_label('%s "%s" Ei = %5.3f meV' % (inst, chop, ei)) lg = self.frqaxes2.legend() lg.draggable() self.frqaxes2.set_xlim([0, np.max(freqs)]) self.frqcanvas.draw() def instSciCB(self): """ Callback function for the "Instrument Scientist Mode" menu option """ # MERLIN is a special case - want to hide ability to change phase from users if 'MERLIN' in self.engine.instname and 'G' in self.engine.getChopper(): if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') else: self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.instSciAct.isChecked(): self.tabs.insertTab(self.scrtabID, self.scrtab, 'ScriptOutput') self.scrtab.show() else: self.tabs.removeTab(self.scrtabID) self.scrtab.hide() def errormessage(self, message): msg = QMessageBox() msg.setText(str(message)) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): yaml_file = QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)') if isinstance(yaml_file, tuple): yaml_file = yaml_file[0] yaml_file = str(yaml_file) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder try: new_inst = Instrument(yaml_file) except (RuntimeError, AttributeError, ValueError) as err: self.errormessage(err) newname = new_inst.name if newname in self.instruments.keys() and not self.overwriteload.isChecked(): overwrite, newname = self._ask_overwrite() if overwrite == 1: return elif overwrite == 0: newname = new_inst.name self.instruments[newname] = new_inst self.choppers[newname] = new_inst.getChopperNames() self.minE[newname] = max([new_inst.emin, 0.01]) self.maxE[newname] = new_inst.emax self.updateInstrumentList() combo = self.widgets['InstrumentCombo']['Combo'] idx = [i for i in range(combo.count()) if str(combo.itemText(i)) == newname] combo.setCurrentIndex(idx[0]) self.setInstrument(newname) def _ask_overwrite(self): msg = QDialog() msg.setWindowTitle('Load overwrite') layout = QGridLayout() layout.addWidget(QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) buttons = [QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to']] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 def overwriteCB(idx): self.overwrite_flag = idx msg.accept() for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) msg.exec_() newname = str(newname.text()) if not newname or newname in self.instruments: self.errormessage('Invalid instrument name. Cancelling load.') self.overwrite_flag = 1 return self.overwrite_flag, newname def updateInstrumentList(self): combo = self.widgets['InstrumentCombo']['Combo'] old_instruments = [str(combo.itemText(i)) for i in range(combo.count())] new_instruments = [inst for inst in self.instruments if inst not in old_instruments] for inst in new_instruments: combo.addItem(inst) def plot_frame(self): """ Plots the distance-time diagram in the right tab """ if len(self.engine.chopper_system.choppers) > 1: self.engine.n_frame = int(self.repfig_nframe_edit.text()) self.repaxes.clear() self.engine.plotMultiRepFrame(self.repaxes, first_rep=self.repfig_nframe_rep1only.isChecked()) self.repcanvas.draw() def _gen_text_ei(self, ei, obj_in): obj = Instrument(obj_in) obj.setEi(ei) en = np.linspace(0, 0.95*ei, 10) try: flux = self.engine.getFlux() res = self.engine.getResolution(en) except ValueError as err: self.errormessage(err) raise ValueError(err) tsqvan, tsqdic, tsqmodchop = obj.getVanVar() v_mod, v_chop = tuple(np.sqrt(tsqmodchop[:2]) * 1e6) x0, _, x1, x2, _ = obj.chopper_system.getDistances() first_component = 'moderator' if x0 != tsqmodchop[2]: x0 = tsqmodchop[2] first_component = 'chopper 1' txt = '# ------------------------------------------------------------- #\n' txt += '# Ei = %8.2f meV\n' % (ei) txt += '# Flux = %8.2f n/cm2/s\n' % (flux) txt += '# Elastic resolution = %6.2f meV\n' % (res[0]) txt += '# Time width at sample = %6.2f us, of which:\n' % (1e6*np.sqrt(tsqvan)) for ky, val in list(tsqdic.items()): txt += '# %20s : %6.2f us\n' % (ky, 1e6*np.sqrt(val)) txt += '# %s distances:\n' % (obj.instname) txt += '# x0 = %6.2f m (%s to Fermi)\n' % (x0, first_component) txt += '# x1 = %6.2f m (Fermi to sample)\n' % (x1) txt += '# x2 = %6.2f m (sample to detector)\n' % (x2) txt += '# Approximate inelastic resolution is given by:\n' txt += '# dE = 2 * E2V * sqrt(ef**3 * t_van**2) / x2\n' txt += '# where: E2V = 4.373e-4 meV/(m/us) conversion from energy to speed\n' txt += '# t_van**2 = (geom*t_mod)**2 + ((1+geom)*t_chop)**2\n' txt += '# geom = (x1 + x2*(ei/ef)**1.5) / x0\n' txt += '# and t_mod and t_chop are the moderator and chopper time widths at the\n' txt += '# moderator and chopper positions (not at the sample as listed above).\n' txt += '# Which in this case is:\n' txt += '# %.4e*sqrt(ef**3 * ( (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2 \n' % (874.78672e-6/x2, v_mod, x1/x0, x2/x0) txt += '# + (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2) )\n' % (v_chop, 1+x1/x0, x2/x0) txt += '# EN (meV) Full dE (meV) Approx dE (meV)\n' for ii in range(len(res)): ef = ei-en[ii] approx = (874.78672e-6/x2)*np.sqrt(ef**3 * ((v_mod*((x1/x0)+(x2/x0)*(ei/ef)**1.5))**2 + (v_chop*(1+(x1/x0)+(x2/x0)*(ei/ef)**1.5))**2)) txt += '%12.5f %12.5f %12.5f\n' % (en[ii], res[ii], approx) return txt def genText(self): """ Generates text output of the resolution function versus energy transfer and other information. """ multiplot = self.widgets['MultiRepCheck'].isChecked() obj = self.engine if obj.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) if obj.getEi() is None: self.setEi() instname, chtyp, freqs, ei_in = tuple([obj.instname, obj.getChopper(), obj.getFrequency(), obj.getEi()]) txt = '# ------------------------------------------------------------- #\n' txt += '# Chop calculation for instrument %s\n' % (instname) if obj.isFermi: txt += '# with chopper %s at %3i Hz\n' % (chtyp, freqs[0]) else: txt += '# in %s mode with:\n' % (chtyp) freq_names = obj.chopper_system.frequency_names for idx in range(len(freq_names)): txt += '# %s at %3i Hz\n' % (freq_names[idx], freqs[idx]) txt += self._gen_text_ei(ei_in, obj) if multiplot: for ei in sorted(self.engine.getAllowedEi()): if np.abs(ei - ei_in) > 0.001: txt += self._gen_text_ei(ei, obj) return txt def showText(self): """ Creates a dialog to show the generated text output. """ try: generatedText = self.genText() except ValueError: return self.txtwin = QDialog() self.txtedt = QTextEdit() self.txtbtn = QPushButton('OK') self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') self.txtwin.setWindowModality(Qt.ApplicationModal) self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ fname = QFileDialog.getSaveFileName(self, 'Open file', '') if isinstance(fname, tuple): fname = fname[0] fid = open(fname, 'w') fid.write(self.genText()) fid.close() def update_script(self): """ Updates the text window with information about the previous calculation. """ if self.widgets['MultiRepCheck'].isChecked(): out = self.engine.getMultiWidths() new_str = '\n' for ie, ee in enumerate(out['Eis']): res = out['Energy'][ie] percent = res / ee * 100 chop_width = out['chopper'][ie] mod_width = out['moderator'][ie] new_str += 'Ei is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ee, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) else: ei = self.engine.getEi() out = self.engine.getWidths() res = out['Energy'] percent = res / ei * 100 chop_width = out['chopper'] mod_width = out['moderator'] new_str = '\nEi is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ei, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) self.scredt.append(new_str) def onHelp(self): """ Shows the help page """ try: from pymantidplot.proxies import showCustomInterfaceHelp showCustomInterfaceHelp("PyChop") except ImportError: helpTxt = "PyChop is a tool to allow direct inelastic neutron\nscattering users to estimate the inelastic resolution\n" helpTxt += "and incident flux for a given spectrometer setting.\n\nFirst select the instrument, chopper settings and\n" helpTxt += "Ei, and then click 'Calculate and Plot'. Data for all\nthe graphs will be generated (may take 1-2s) and\n" helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." self.hlpwin = QDialog() self.hlpedt = QLabel(helpTxt) self.hlpbtn = QPushButton('OK') self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') self.hlpwin.setWindowModality(Qt.ApplicationModal) self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): """ Draws the GUI layout. """ self.widgetslist = [ ['pair', 'show', 'Instrument', 'combo', self.instruments, self.setInstrument, 'InstrumentCombo'], ['pair', 'show', 'Chopper', 'combo', '', self.setChopper, 'ChopperCombo'], ['pair', 'show', 'Frequency', 'combo', '', self.setFreq, 'FrequencyCombo'], ['pair', 'hide', 'Pulse remover chopper freq', 'combo', '', self.setFreq, 'PulseRemoverCombo'], ['pair', 'show', 'Ei', 'edit', '', self.setEi, 'EiEdit'], ['pair', 'hide', 'Chopper 2 phase delay time', 'edit', '5', self.setFreq, 'Chopper2Phase'], ['spacer'], ['single', 'show', 'Calculate and Plot', 'button', self.calc_callback, 'CalculateButton'], ['single', 'show', 'Hold current plot', 'check', lambda: None, 'HoldCheck'], ['single', 'show', 'Show multi-reps', 'check', lambda: None, 'MultiRepCheck'], ['spacer'], ['single', 'show', 'Show data ascii window', 'button', self.showText, 'ShowAsciiButton'], ['single', 'show', 'Save data as ascii', 'button', self.saveText, 'SaveAsciiButton'] ] self.droplabels = [] self.dropboxes = [] self.singles = [] self.widgets = {} self.leftPanel = QVBoxLayout() self.rightPanel = QVBoxLayout() self.tabs = QTabWidget(self) self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = {'Combo':self.dropboxes[-1], 'Label':self.droplabels[-1]} elif 'edit' in widget[3]: self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = {'Edit':self.dropboxes[-1], 'Label':self.droplabels[-1]} else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.droplabels[-1]) self.leftPanel.addWidget(self.dropboxes[-1]) if 'hide' in widget[1]: self.droplabels[-1].hide() self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.singles[-1]) if 'hide' in widget[1]: self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError('Bug in code - widget class %s is not recognised.' % (widget[0])) # Right panel, matplotlib figures self.resfig = Figure() self.resfig.patch.set_facecolor('white') self.rescanvas = FigureCanvas(self.resfig) self.resaxes = self.resfig.add_subplot(111) self.resaxes.axhline(color='k') self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) self.restab = QWidget(self.tabs) self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) self.flxfig = Figure() self.flxfig.patch.set_facecolor('white') self.flxcanvas = FigureCanvas(self.flxfig) self.flxaxes1 = self.flxfig.add_subplot(121) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes2 = self.flxfig.add_subplot(122) self.flxaxes2.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.flxfig_controls = NavigationToolbar(self.flxcanvas, self) self.flxsldfg = Figure() self.flxsldfg.patch.set_facecolor('white') self.flxsldcv = FigureCanvas(self.flxsldfg) self.flxsldax = self.flxsldfg.add_subplot(111) self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) self.flxtab = QWidget(self.tabs) self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) self.flxtab.setLayout(self.flxtabbox) self.frqfig = Figure() self.frqfig.patch.set_facecolor('white') self.frqcanvas = FigureCanvas(self.frqfig) self.frqaxes1 = self.frqfig.add_subplot(121) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.frqaxes2 = self.frqfig.add_subplot(122) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) self.frqtab = QWidget(self.tabs) self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) self.repfig = Figure() self.repfig.patch.set_facecolor('white') self.repcanvas = FigureCanvas(self.repfig) self.repaxes = self.repfig.add_subplot(111) self.repaxes.axhline(color='k') self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) self.repfig_nframe_label = QLabel('Number of frames to plot') self.repfig_nframe_edit = QLineEdit('1') self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) self.repfig_nframe_rep1only = QCheckBox('First Rep Only') self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) self.repfig_nframe_box.addWidget(self.repfig_nframe_rep1only) self.reptab = QWidget(self.tabs) self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) self.repfig_nframe.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) self.reptab.setLayout(self.reptabbox) self.qefig = Figure() self.qefig.patch.set_facecolor('white') self.qecanvas = FigureCanvas(self.qefig) self.qeaxes = self.qefig.add_subplot(111) self.qeaxes.axhline(color='k') self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) self.scrtab = QWidget(self.tabs) self.scredt = QTextEdit() self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) self.scrtab.hide() self.tabs.addTab(self.restab, 'Resolution') self.tabs.addTab(self.flxtab, 'Flux-Ei') self.tabs.addTab(self.frqtab, 'Flux-Freq') self.tabs.addTab(self.reptab, 'Time-Distance') self.tdtabID = 3 self.tabs.setTabEnabled(self.tdtabID, False) self.tabs.addTab(self.qetab, 'Q-E') self.qetabID = 4 self.tabs.setTabEnabled(self.qetabID, False) self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) self.menuLoad = QMenu('Load') self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) self.menuOptions = QMenu('Options') self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) self.leftPanelWidget.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show()
def __init__(self, parent: ReactionList): QWidget.__init__(self) self.parent = parent self.reaction = None self.is_valid = True self.changed = False self.setAcceptDrops(False) layout = QVBoxLayout() l = QHBoxLayout() self.delete_button = QPushButton("Delete reaction") self.delete_button.setIcon(QIcon.fromTheme("edit-delete")) policy = QSizePolicy() policy.ShrinkFlag = True self.delete_button.setSizePolicy(policy) l.addWidget(self.delete_button) layout.addItem(l) l = QHBoxLayout() label = QLabel("Id:") self.id = QLineEdit() l.addWidget(label) l.addWidget(self.id) layout.addItem(l) l = QHBoxLayout() label = QLabel("Name:") self.name = QLineEdit() l.addWidget(label) l.addWidget(self.name) layout.addItem(l) l = QHBoxLayout() label = QLabel("Equation:") self.equation = QLineEdit() l.addWidget(label) l.addWidget(self.equation) layout.addItem(l) l = QHBoxLayout() label = QLabel("Rate min:") self.lower_bound = QLineEdit() l.addWidget(label) l.addWidget(self.lower_bound) layout.addItem(l) l = QHBoxLayout() label = QLabel("Rate max:") self.upper_bound = QLineEdit() l.addWidget(label) l.addWidget(self.upper_bound) layout.addItem(l) l = QHBoxLayout() label = QLabel("Coefficient in obj. function:") self.coefficent = QLineEdit() l.addWidget(label) l.addWidget(self.coefficent) layout.addItem(l) l = QHBoxLayout() label = QLabel("Gene reaction rule:") self.gene_reaction_rule = QLineEdit() l.addWidget(label) l.addWidget(self.gene_reaction_rule) layout.addItem(l) l = QVBoxLayout() label = QLabel("Annotations:") l.addWidget(label) l2 = QHBoxLayout() self.annotation = QTableWidget(0, 2) self.annotation.setHorizontalHeaderLabels( ["key", "value"]) self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) l2.addWidget(self.annotation) self.add_anno = QPushButton("+") self.add_anno.clicked.connect(self.add_anno_row) l2.addWidget(self.add_anno) l.addItem(l2) layout.addItem(l) l = QVBoxLayout() label = QLabel("Metabolites involved in this reaction:") l.addWidget(label) l2 = QHBoxLayout() self.metabolites = QTreeWidget() self.metabolites.setHeaderLabels(["Id"]) self.metabolites.setSortingEnabled(True) l2.addWidget(self.metabolites) l.addItem(l2) self.metabolites.itemDoubleClicked.connect( self.emit_jump_to_metabolite) layout.addItem(l) self.jump_list = JumpList(self) layout.addWidget(self.jump_list) self.setLayout(layout) self.delete_button.clicked.connect(self.delete_reaction) self.throttler = SignalThrottler(500) self.throttler.triggered.connect(self.reaction_data_changed) self.id.textEdited.connect(self.throttler.throttle) self.name.textEdited.connect(self.throttler.throttle) self.equation.textEdited.connect(self.throttler.throttle) self.lower_bound.textEdited.connect(self.throttler.throttle) self.upper_bound.textEdited.connect(self.throttler.throttle) self.coefficent.textEdited.connect(self.throttler.throttle) self.gene_reaction_rule.textEdited.connect(self.throttler.throttle) self.annotation.itemChanged.connect(self.throttler.throttle) self.validate_mask()
class ReactionList(QWidget): """A list of reaction""" def __init__(self, appdata: CnaData): QWidget.__init__(self) self.appdata = appdata self.last_selected = None self.reaction_counter = 1 self.add_button = QPushButton("Add new reaction") self.add_button.setIcon(QIcon.fromTheme("list-add")) policy = QSizePolicy() policy.ShrinkFlag = True self.add_button.setSizePolicy(policy) self.reaction_list = DragableTreeWidget() self.reaction_list.setDragEnabled(True) self.reaction_list.setHeaderLabels(["Id", "Name", "Flux"]) self.reaction_list.setSortingEnabled(True) for r in self.appdata.project.cobra_py_model.reactions: self.add_reaction(r) self.reaction_mask = ReactionMask(self) self.reaction_mask.hide() self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) l = QHBoxLayout() l.setAlignment(Qt.AlignRight) l.addWidget(self.add_button) self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.splitter.addWidget(self.reaction_list) self.splitter.addWidget(self.reaction_mask) self.layout.addItem(l) self.layout.addWidget(self.splitter) self.setLayout(self.layout) self.reaction_list.currentItemChanged.connect(self.reaction_selected) self.reaction_mask.reactionChanged.connect( self.handle_changed_reaction) self.reaction_mask.reactionDeleted.connect( self.handle_deleted_reaction) self.reaction_mask.jumpToMap.connect(self.emit_jump_to_map) self.reaction_mask.jumpToMetabolite.connect( self.emit_jump_to_metabolite) self.add_button.clicked.connect(self.add_new_reaction) def clear(self): self.reaction_list.clear() self.reaction_mask.hide() def add_reaction(self, reaction: cobra.Reaction) -> QTreeWidgetItem: ''' create a new item in the reaction list''' self.reaction_list.clearSelection() item = QTreeWidgetItem(self.reaction_list) item.setText(0, reaction.id) item.setText(1, reaction.name) self.set_flux_value(item, reaction.id) text = "Id: " + reaction.id + "\nName: " + reaction.name \ + "\nEquation: " + reaction.build_reaction_string()\ + "\nLowerbound: " + str(reaction.lower_bound) \ + "\nUpper bound: " + str(reaction.upper_bound) \ + "\nObjective coefficient: " + str(reaction.objective_coefficient) item.setToolTip(1, text) item.setData(3, 0, reaction) return item def set_flux_value(self, item, key): if key in self.appdata.project.scen_values.keys(): (vl, vu) = self.appdata.project.scen_values[key] if isclose(vl, vu, abs_tol=self.appdata.abs_tol): item.setData(2, 0, round(vl, self.appdata.rounding)) else: item.setData( 2, 0, str((round(vl, self.appdata.rounding), round(vu, self.appdata.rounding)))) item.setBackground(2, self.appdata.scen_color) item.setForeground(2, Qt.black) elif key in self.appdata.project.comp_values.keys(): (vl, vu) = self.appdata.project.comp_values[key] # We differentiate special cases like (vl==vu) if isclose(vl, vu, abs_tol=self.appdata.abs_tol): if self.appdata.modes_coloring: if vl == 0: item.setBackground(2, Qt.red) else: item.setBackground(2, Qt.green) else: item.setBackground(2, self.appdata.comp_color) item.setData(2, 0, round(vl, self.appdata.rounding)) else: if isclose(vl, 0.0, abs_tol=self.appdata.abs_tol): item.setBackground(2, self.appdata.special_color_1) elif isclose(vu, 0.0, abs_tol=self.appdata.abs_tol): item.setBackground(2, self.appdata.special_color_1) elif vl <= 0 and vu >= 0: item.setBackground(2, self.appdata.special_color_1) else: item.setBackground(2, self.appdata.special_color_2) item.setData( 2, 0, str((round(vl, self.appdata.rounding), round(vu, self.appdata.rounding)))) item.setForeground(2, Qt.black) def add_new_reaction(self): self.reaction_mask.show() while True: name = "rxn_"+str(self.reaction_counter) self.reaction_counter += 1 if name not in self.appdata.project.cobra_py_model.reactions: break reaction = cobra.Reaction(name) self.appdata.project.cobra_py_model.add_reactions([reaction]) item = self.add_reaction(reaction) self.reaction_selected(item, 1) self.appdata.window.unsaved_changes() def update_annotations(self, annotation): self.reaction_mask.annotation.itemChanged.disconnect( self.reaction_mask.throttler.throttle) c = self.reaction_mask.annotation.rowCount() for i in range(0, c): self.reaction_mask.annotation.removeRow(0) i = 0 for key in annotation: self.reaction_mask.annotation.insertRow(i) keyl = QTableWidgetItem(key) iteml = QTableWidgetItem(str(annotation[key])) self.reaction_mask.annotation.setItem(i, 0, keyl) self.reaction_mask.annotation.setItem(i, 1, iteml) i += 1 self.reaction_mask.annotation.itemChanged.connect( self.reaction_mask.throttler.throttle) def reaction_selected(self, item: QTreeWidgetItem, _column): if item is None: self.reaction_mask.hide() else: item.setSelected(True) self.reaction_mask.show() reaction: cobra.Reaction = item.data(3, 0) self.last_selected = reaction.id self.reaction_mask.reaction = reaction self.reaction_mask.id.setText(reaction.id) self.reaction_mask.name.setText(reaction.name) self.reaction_mask.equation.setText( reaction.build_reaction_string()) self.reaction_mask.lower_bound.setText(str(reaction.lower_bound)) self.reaction_mask.upper_bound.setText(str(reaction.upper_bound)) self.reaction_mask.coefficent.setText( str(reaction.objective_coefficient)) self.reaction_mask.gene_reaction_rule.setText( str(reaction.gene_reaction_rule)) self.update_annotations(reaction.annotation) self.reaction_mask.changed = False turn_white(self.reaction_mask.id) turn_white(self.reaction_mask.name) turn_white(self.reaction_mask.name) turn_white(self.reaction_mask.equation) turn_white(self.reaction_mask.lower_bound) turn_white(self.reaction_mask.upper_bound) turn_white(self.reaction_mask.coefficent) turn_white(self.reaction_mask.gene_reaction_rule) self.reaction_mask.is_valid = True (_, r) = self.splitter.getRange(1) self.splitter.moveSplitter(r/2, 1) self.reaction_mask.update_state() def handle_changed_reaction(self, reaction: cobra.Reaction): # Update reaction item in list root = self.reaction_list.invisibleRootItem() child_count = root.childCount() for i in range(child_count): item = root.child(i) if item.data(3, 0) == reaction: old_id = item.text(0) item.setText(0, reaction.id) item.setText(1, reaction.name) break self.last_selected = self.reaction_mask.id.text() self.reactionChanged.emit(old_id, reaction) def handle_deleted_reaction(self, reaction: cobra.Reaction): '''Remove reaction item from reaction list''' root = self.reaction_list.invisibleRootItem() child_count = root.childCount() for i in range(child_count): item = root.child(i) if item.data(3, 0) == reaction: # remove item self.reaction_list.takeTopLevelItem( self.reaction_list.indexOfTopLevelItem(item)) break self.last_selected = self.reaction_mask.id.text() self.reactionDeleted.emit(reaction) def update_selected(self, string): root = self.reaction_list.invisibleRootItem() child_count = root.childCount() for i in range(child_count): item = root.child(i) item.setHidden(True) for item in self.reaction_list.findItems(string, Qt.MatchContains, 0): item.setHidden(False) for item in self.reaction_list.findItems(string, Qt.MatchContains, 1): item.setHidden(False) def update(self): self.reaction_list.clear() for r in self.appdata.project.cobra_py_model.reactions: self.add_reaction(r) if self.last_selected is None: pass else: items = self.reaction_list.findItems( self.last_selected, Qt.MatchExactly) for i in items: self.reaction_list.setCurrentItem(i) break self.reaction_mask.update_state() def set_current_item(self, key): self.last_selected = key self.update() def emit_jump_to_map(self, idx: str, reaction: str): self.jumpToMap.emit(idx, reaction) def emit_jump_to_metabolite(self, metabolite): self.jumpToMetabolite.emit(metabolite) itemActivated = Signal(str) reactionChanged = Signal(str, cobra.Reaction) reactionDeleted = Signal(cobra.Reaction) jumpToMap = Signal(str, str) jumpToMetabolite = Signal(str)
def _setupChromSettings(self): l_chromconfig = QLabel('<h3>Chromaticity Variation Config</h3>', self) l_chromconfig.setAlignment(Qt.AlignCenter) self.le_chromconfig = _ConfigLineEdit( parent=self, config_type='bo_chromcorr_params') self.le_chromconfig.textChanged.connect(self._showChromConfigData) l_chrommat = QLabel('<h4>Matrix</h4>', self) l_chrommat.setAlignment(Qt.AlignCenter) self.table_chrommat = QTableWidget(self) self.table_chrommat.setObjectName('chrommat') self.table_chrommat.setStyleSheet(""" #chrommat{ background-color: #efebe7; min-width: 22.14em; min-height: 6em; max-height: 6em;}""") self.table_chrommat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_chrommat.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_chrommat.setRowCount(2) self.table_chrommat.setColumnCount(2) self.table_chrommat.setVerticalHeaderLabels([' X', ' Y']) self.table_chrommat.setHorizontalHeaderLabels(['SF', 'SD']) self.table_chrommat.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_chrommat.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_chrommat.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomSL = QLabel('<h4>Nominal SL</h4>') l_nomSL.setAlignment(Qt.AlignCenter) self.table_nomSL = QTableWidget(self) self.table_nomSL.setObjectName('nomSL') self.table_nomSL.setStyleSheet(""" #nomSL{ background-color: #efebe7; min-width: 22.14em; min-height: 4em; max-height: 4em;}""") self.table_nomSL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_nomSL.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_nomSL.setRowCount(1) self.table_nomSL.setColumnCount(2) self.table_nomSL.setVerticalHeaderLabels(['SL']) self.table_nomSL.setHorizontalHeaderLabels(['SF', 'SD']) self.table_nomSL.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_nomSL.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_nomSL.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomchrom = QLabel('<h4>Nominal Chrom</h4>') l_nomchrom.setAlignment(Qt.AlignCenter) self.label_nomchrom = QLabel() self.label_nomchrom.setAlignment(Qt.AlignCenter) lay = QVBoxLayout() lay.addWidget(l_chromconfig) lay.addWidget(self.le_chromconfig) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_chrommat) lay.addWidget(self.table_chrommat) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomSL) lay.addWidget(self.table_nomSL) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomchrom) lay.addWidget(self.label_nomchrom) return lay
class C2ConfigFrame(ConfigBaseFrame): def __init__(self, parent): super(C2ConfigFrame, self).__init__(parent) self.widget_layout = QVBoxLayout(self) self.setLayout(self.widget_layout) self.c2_conf_label = QLabel(self) self.c2_conf_label.setText("C2 server configuration") self.widget_layout.addWidget(self.c2_conf_label) self.desc_label = QLabel(self) self.desc_label.setWordWrap(True) self.desc_label.setText( "Make sure that port you use is opened and IP address is " "valid. To balance server load, select amount of threads to use. Set 0 to use all " "available system cores.") self.widget_layout.addWidget(self.desc_label) self.ip_config = QWidget(self) self.ip_config_layout = QFormLayout(self.ip_config) self.widget_layout.addWidget(self.ip_config) self.c2_ip_label = QLabel(self.ip_config) self.c2_ip_label.setText("C2 IP Address") self.c2_ip_combo = QComboBox(self.ip_config) self.ip_config_layout.addRow(self.c2_ip_label, self.c2_ip_combo) self.port_label = QLabel(self.ip_config) self.port_label.setText("C2 Port") self.port_edit = QSpinBox(self.ip_config) self.port_edit.setRange(1024, 65535) self.port_edit.setValue(8192) self.ip_config_layout.addRow(self.port_label, self.port_edit) self.threads_label = QLabel(self.ip_config) self.threads_label.setText("C2 handler threads") self.threads_edit = QSpinBox(self.ip_config) self.threads_edit.setRange(0, 256) self.ip_config_layout.addRow(self.threads_label, self.threads_edit) self.c2_key_label = QLabel(self.ip_config) self.c2_key_label.setText("C2 encryption key") self.c2_key_edit = QLineEdit(self.ip_config) self.c2_key_edit.setReadOnly(True) self.c2_key_edit.setText(generate_key().decode()) self.ip_config_layout.addRow(self.c2_key_label, self.c2_key_edit) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.widget_layout.addItem(spacerItem) @Slot(dict) def set_options(self, options): self.c2_ip_combo.addItems( [iface.get("ip") for iface in options.get("interfaces")]) self.c2_key_edit.setText(options.get("c2_key")) @Slot() def collect_info(self): return { "c2_ip": self.c2_ip_combo.currentText(), "c2_port": int(self.port_edit.text()), "c2_threads": int(self.threads_edit.value()), "c2_key": str(self.c2_key_edit.text()) }
def __init__(self, appdata): QWidget.__init__(self) self.appdata = appdata self.metabolite = None self.is_valid = True self.changed = False self.setAcceptDrops(False) layout = QVBoxLayout() l = QHBoxLayout() label = QLabel("Id:") self.id = QLineEdit() l.addWidget(label) l.addWidget(self.id) layout.addItem(l) l = QHBoxLayout() label = QLabel("Name:") self.name = QLineEdit() l.addWidget(label) l.addWidget(self.name) layout.addItem(l) l = QHBoxLayout() label = QLabel("Formula:") self.formula = QLineEdit() l.addWidget(label) l.addWidget(self.formula) layout.addItem(l) l = QHBoxLayout() label = QLabel("Charge:") self.charge = QLineEdit() l.addWidget(label) l.addWidget(self.charge) layout.addItem(l) l = QHBoxLayout() label = QLabel("Compartment:") self.compartment = QLineEdit() l.addWidget(label) l.addWidget(self.compartment) layout.addItem(l) l = QVBoxLayout() label = QLabel("Annotations:") l.addWidget(label) l2 = QHBoxLayout() self.annotation = QTableWidget(0, 2) self.annotation.setHorizontalHeaderLabels( ["key", "value"]) self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) l2.addWidget(self.annotation) self.add_anno = QPushButton("+") self.add_anno.clicked.connect(self.add_anno_row) l2.addWidget(self.add_anno) l.addItem(l2) layout.addItem(l) l = QVBoxLayout() label = QLabel("Reactions using this metabolite:") l.addWidget(label) l2 = QHBoxLayout() self.reactions = QTreeWidget() self.reactions.setHeaderLabels(["Id"]) self.reactions.setSortingEnabled(True) l2.addWidget(self.reactions) l.addItem(l2) self.reactions.itemDoubleClicked.connect(self.emit_jump_to_reaction) layout.addItem(l) self.setLayout(layout) self.throttler = SignalThrottler(500) self.throttler.triggered.connect(self.metabolites_data_changed) self.id.textEdited.connect(self.throttler.throttle) self.name.textEdited.connect(self.throttler.throttle) self.formula.textEdited.connect(self.throttler.throttle) self.charge.textEdited.connect(self.throttler.throttle) self.compartment.textEdited.connect(self.throttler.throttle) self.annotation.itemChanged.connect(self.throttler.throttle) self.validate_mask()
class ConfigCobrapyDialog(QDialog): """A dialog to set values in cobrapy-config.txt""" def __init__(self, appdata: CnaData): QDialog.__init__(self) self.setWindowTitle("Configure COBRApy") self.appdata = appdata self.layout = QVBoxLayout() # allow MILP solvers only? avail_solvers = list(set(solvers.keys()) - {'scipy'}) # SCIPY currently not even usable for FBA h2 = QHBoxLayout() label = QLabel("Default solver:\n(set when loading a model)") h2.addWidget(label) self.default_solver = QComboBox() self.default_solver.addItems(avail_solvers) self.default_solver.setCurrentIndex(avail_solvers.index(interface_to_str(cobra.Configuration().solver))) h2.addWidget(self.default_solver) self.layout.addItem(h2) h9 = QHBoxLayout() label = QLabel("Solver for current model:") h9.addWidget(label) self.current_solver = QComboBox() self.current_solver.addItems(avail_solvers) self.current_solver.setCurrentIndex(avail_solvers.index(interface_to_str(appdata.project.cobra_py_model.problem))) h9.addWidget(self.current_solver) self.layout.addItem(h9) h7 = QHBoxLayout() label = QLabel( "Number of processes for multiprocessing (e.g. FVA):") h7.addWidget(label) self.num_processes = QLineEdit() self.num_processes.setFixedWidth(100) self.num_processes.setText(str(cobra.Configuration().processes)) validator = QIntValidator(1, cpu_count(), self) self.num_processes.setValidator(validator) h7.addWidget(self.num_processes) self.layout.addItem(h7) h8 = QHBoxLayout() label = QLabel( "Default tolerance:\n(set when loading a model)") h8.addWidget(label) self.default_tolerance = QLineEdit() self.default_tolerance.setFixedWidth(100) self.default_tolerance.setText(str(cobra.Configuration().tolerance)) validator = QDoubleValidator(self) validator.setBottom(1e-9) # probably a reasonable consensus value self.default_tolerance.setValidator(validator) h8.addWidget(self.default_tolerance) self.layout.addItem(h8) h10 = QHBoxLayout() label = QLabel( "Tolerance for current model:") h10.addWidget(label) self.current_tolerance = QLineEdit() self.current_tolerance.setFixedWidth(100) self.current_tolerance.setText(str(self.appdata.project.cobra_py_model.tolerance)) validator = QDoubleValidator(self) validator.setBottom(0) self.current_tolerance.setValidator(validator) h10.addWidget(self.current_tolerance) self.layout.addItem(h10) l2 = QHBoxLayout() self.button = QPushButton("Apply Changes") self.cancel = QPushButton("Close") l2.addWidget(self.button) l2.addWidget(self.cancel) self.layout.addItem(l2) self.setLayout(self.layout) self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.apply) def apply(self): cobra.Configuration().solver = self.default_solver.currentText() cobra.Configuration().processes = int(self.num_processes.text()) try: val = float(self.default_tolerance.text()) if 1e-9 <= val <= 0.1: cobra.Configuration().tolerance = val else: raise ValueError except: QMessageBox.critical(self, "Cannot set default tolerance", "Choose a value between 0.1 and 1e-9 as default tolerance.") return try: self.appdata.project.cobra_py_model.solver = self.current_solver.currentText() self.appdata.project.cobra_py_model.tolerance = float(self.current_tolerance.text()) except Exception as e: QMessageBox.critical(self, "Cannot set current solver/tolerance", str(e)) return parser = configparser.ConfigParser() parser.add_section('cobrapy-config') parser.set('cobrapy-config', 'solver', interface_to_str(cobra.Configuration().solver)) parser.set('cobrapy-config', 'processes', str(cobra.Configuration().processes)) parser.set('cobrapy-config', 'tolerance', str(cobra.Configuration().tolerance)) try: fp = open(self.appdata.cobrapy_conf_path, "w") except FileNotFoundError: os.makedirs(appdirs.user_config_dir( "cnapy", roaming=True, appauthor=False)) fp = open(self.appdata.cobrapy_conf_path, "w") parser.write(fp) fp.close() self.accept()
class EFMDialog(QDialog): """A dialog to set up EFM calculation""" def __init__(self, appdata: CnaData, centralwidget): QDialog.__init__(self) self.setWindowTitle("Elementary Flux Mode Computation") self.appdata = appdata self.centralwidget = centralwidget self.eng = appdata.engine self.out = io.StringIO() self.err = io.StringIO() self.layout = QVBoxLayout() l1 = QHBoxLayout() self.constraints = QCheckBox("consider 0 in current scenario as off") self.constraints.setCheckState(Qt.Checked) l1.addWidget(self.constraints) self.layout.addItem(l1) l2 = QHBoxLayout() self.flux_bounds = QGroupBox( "use flux bounds to calculate elementary flux vectors") self.flux_bounds.setCheckable(True) self.flux_bounds.setChecked(False) vbox = QVBoxLayout() label = QLabel("Threshold for bounds to be unconstrained") vbox.addWidget(label) self.threshold = QLineEdit("100") validator = QIntValidator() validator.setBottom(0) self.threshold.setValidator(validator) vbox.addWidget(self.threshold) self.flux_bounds.setLayout(vbox) l2.addWidget(self.flux_bounds) self.layout.addItem(l2) l3 = QHBoxLayout() self.check_reversibility = QCheckBox("check reversibility") self.check_reversibility.setCheckState(Qt.Checked) l3.addWidget(self.check_reversibility) self.layout.addItem(l3) l4 = QHBoxLayout() self.convex_basis = QCheckBox("only convex basis") l4.addWidget(self.convex_basis) self.layout.addItem(l4) l5 = QHBoxLayout() self.isozymes = QCheckBox("consider isozymes only once") l5.addWidget(self.isozymes) self.layout.addItem(l5) # TODO: choose solver l7 = QHBoxLayout() self.rational_numbers = QCheckBox("use rational numbers") l7.addWidget(self.rational_numbers) self.layout.addItem(l7) lx = QHBoxLayout() self.button = QPushButton("Compute") self.cancel = QPushButton("Close") lx.addWidget(self.button) lx.addWidget(self.cancel) self.layout.addItem(lx) self.setLayout(self.layout) # Connecting the signal self.cancel.clicked.connect(self.reject) self.button.clicked.connect(self.compute) def compute(self): # create CobraModel for matlab self.appdata.create_cobra_model() legacy.read_cnapy_model(self.eng) # get some data reac_id = self.eng.get_reacID() # setting parameters a = self.eng.eval("constraints = {};", nargout=0, stdout=self.out, stderr=self.err) scenario = {} if self.constraints.checkState( ) == Qt.Checked or self.flux_bounds.isChecked(): onoff_str = "" for r in reac_id: if r in self.appdata.project.scen_values.keys(): (vl, vu) = self.appdata.project.scen_values[r] if vl == vu: if vl > 0: onoff_str = onoff_str + " NaN" # efmtool does not support 1 elif vl == 0: scenario[r] = (0, 0) onoff_str = onoff_str + " 0" else: onoff_str = onoff_str + " NaN" print("WARN: negative value in scenario") else: onoff_str = onoff_str + " NaN" print("WARN: not fixed value in scenario") else: onoff_str = onoff_str + " NaN" onoff_str = "reaconoff = [" + onoff_str + "];" a = self.eng.eval(onoff_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.reaconoff = reaconoff;", nargout=0, stdout=self.out, stderr=self.err) if self.flux_bounds.isChecked(): threshold = float(self.threshold.text()) lb_str = "" ub_str = "" for r in reac_id: c_reaction = self.appdata.project.cobra_py_model.reactions.get_by_id( r) if r in self.appdata.project.scen_values: (vl, vu) = self.appdata.project.scen_values[r] else: vl = c_reaction.lower_bound vu = c_reaction.upper_bound if vl <= -threshold: vl = "NaN" if vu >= threshold: vu = "NaN" if vl == 0 and vu == 0: # already in reaconoff, can be skipped here vl = "NaN" vu = "NaN" lb_str = lb_str + " " + str(vl) ub_str = ub_str + " " + str(vu) lb_str = "lb = [" + lb_str + "];" a = self.eng.eval(lb_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.lb = lb;", nargout=0, stdout=self.out, stderr=self.err) ub_str = "ub = [" + ub_str + "];" a = self.eng.eval(ub_str, nargout=0, stdout=self.out, stderr=self.err) a = self.eng.eval("constraints.ub = ub;", nargout=0, stdout=self.out, stderr=self.err) # TODO set solver 4 = EFMTool 3 = MetaTool, 1 = cna Mex file, 0 = cna functions a = self.eng.eval("solver = 4;", nargout=0, stdout=self.out, stderr=self.err) if self.check_reversibility.checkState() == Qt.Checked: a = self.eng.eval("irrev_flag = 1;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("irrev_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) # convex basis computation is only possible with METATOOL solver=3 if self.convex_basis.checkState() == Qt.Checked: a = self.eng.eval("conv_basis_flag = 1; solver = 3;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("conv_basis_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) if self.isozymes.checkState() == Qt.Checked: a = self.eng.eval("iso_flag = 1;", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("iso_flag = 0;", nargout=0, stdout=self.out, stderr=self.err) # default we have no macromolecules and display is et to ALL a = self.eng.eval("c_macro=[]; display= 'ALL';", nargout=0, stdout=self.out, stderr=self.err) if self.rational_numbers.checkState() == Qt.Checked: a = self.eng.eval( "efmtool_options = {'arithmetic', 'fractional'};", nargout=0, stdout=self.out, stderr=self.err) else: a = self.eng.eval("efmtool_options = {};", nargout=0, stdout=self.out, stderr=self.err) if self.appdata.is_matlab_set(): try: a = self.eng.eval( "[ems, irrev_ems, ems_idx, ray] = CNAcomputeEFM(cnap, constraints,solver,irrev_flag,conv_basis_flag,iso_flag,c_macro,display,efmtool_options);", nargout=0) except Exception: output = io.StringIO() traceback.print_exc(file=output) exstr = output.getvalue() print(exstr) QMessageBox.warning( self, 'Unknown exception occured!', exstr + '\nPlease report the problem to:\n\ \nhttps://github.com/cnapy-org/CNApy/issues' ) return else: ems = self.eng.workspace['ems'] idx = self.eng.workspace['ems_idx'] irreversible = numpy.squeeze(self.eng.workspace['irrev_ems']) unbounded = numpy.squeeze(self.eng.workspace['ray']) ems = numpy.array(ems) self.result2ui(ems, idx, reac_id, irreversible, unbounded, scenario) self.accept() elif self.appdata.is_octave_ready(): a = self.eng.eval( "[ems, irrev_ems, ems_idx, ray] = CNAcomputeEFM(cnap, constraints,solver,irrev_flag,conv_basis_flag,iso_flag,c_macro,display,efmtool_options);", nargout=0) ems = self.eng.pull('ems') idx = self.eng.pull('ems_idx') irreversible = numpy.squeeze(self.eng.pull('irrev_ems')) unbounded = numpy.squeeze(self.eng.pull('ray')) self.result2ui(ems, idx, reac_id, irreversible, unbounded, scenario) def result2ui(self, ems, idx, reac_id, irreversible, unbounded, scenario): if len(ems) == 0: QMessageBox.information( self, 'No modes', 'Modes have not been calculated or do not exist.') else: self.appdata.project.modes = FluxVectorContainer( ems, [reac_id[int(i) - 1] for i in idx[0]], irreversible, unbounded) self.centralwidget.mode_navigator.current = 0 self.centralwidget.mode_navigator.scenario = scenario self.centralwidget.mode_navigator.title.setText("Mode Navigation") self.centralwidget.update_mode()