class SLogWidget(QWidget): """Widget to log the STracking plugins messages in the graphical interface""" def __init__(self): super().__init__() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.progress_bar = QProgressBar() self.log_area = QTextEdit() layout.addWidget(self.progress_bar) layout.addWidget(self.log_area) self.setLayout(layout) def set_advanced(self, mode: bool): """Show hide the log area depending on the plugin mode""" if mode: self.log_area.setVisible(True) else: self.log_area.setVisible(False) def set_progress(self, value: int): """Callback to update the progress bar""" self.progress_bar.setValue(value) def add_log(self, value: str): """Callback to add a new message in the log area""" self.log_area.append(value) def clear_log(self): """Callback to clear all the log area""" self.log_area.clear()
class MainWindow(QMainWindow): def selectFileToOpen(self): def getPreProcessingChoice(self, filename, filestructure): items = ("Choose the longest", "Merge all") item, okPressed = QInputDialog.getItem( self, "Multiple tracks/segments", "File '" + filename + "' contains more than one track/segment\n\n" + infos + "\nWhat to do?", items, 0, False) if okPressed and item: return items.index(item) else: return 0 # Try to recover the last used directory old_directory = self.settings.value("lastdirectory", str) # Check if the setting exists if old_directory is not None: # Check if it's not empty if old_directory: old_directory = old_directory else: old_directory = bombo.TRACKS_FOLDER else: old_directory = bombo.TRACKS_FOLDER # Open the dialog box fullfilename_list = QFileDialog.getOpenFileNames( self, 'Open .gpx', old_directory, "GPX files (*.gpx)") if os.environ['QT_API'] == 'pyqt': pass elif os.environ['QT_API'] == 'pyqt5': fullfilename_list = fullfilename_list[0] # Process every selected file for i, fullfilename in enumerate(fullfilename_list): # Process filename directory, filename = os.path.split(str(fullfilename)) filename, fileextension = os.path.splitext(filename) # Save the new directory in the application settings (it only # needs to be done once) if i == 0: # print "New directory to be saved: {}\n".format(directory) if os.environ['QT_API'] == 'pyqt': self.settings.setValue("lastdirectory", str(directory)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("lastdirectory", QtCore.QVariant(str(directory))) # Open file and inspect what's inside gpxraw, longest_traseg, Ntracks, Nsegments, infos = bombo.LoadGPX( fullfilename) # If there's more than one track or segment, ask how to proceed if (Ntracks > 1) or (Nsegments > 1): preprocessingchoice = getPreProcessingChoice( self, filename, infos) if preprocessingchoice == 0: preprocessedgpx = bombo.SelectOneTrackAndSegmentFromGPX( gpxraw, longest_traseg[0], longest_traseg[1]) listname = filename + " (longest)" elif preprocessingchoice == 1: preprocessedgpx = bombo.MergeAllTracksAndSegmentsFromGPX( gpxraw) listname = filename + " (merged)" else: preprocessedgpx = gpxraw listname = filename # Append the list of open GPX files using the next available color (that's the size of the list -1) self.gpxlist.append(preprocessedgpx) self.gpxnamelist.append(listname) newitem = QListWidgetItem(listname) newitem.setBackground( QtGui.QColor(self.palette[len(self.gpxlist) - 1])) self.tracklist.addItem(newitem) return def Go(self): if len(self.gpxselectedlist) > 0: # Temporarily change cursor QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) # Clear up global variables self.proc_coords = [] self.proc_measurements = [] self.proc_state_means = [] self.proc_state_vars = [] self.proc_new_coords = [] self.proc_new_gpx = [] self.proc_coords_to_plot = [] self.proc_coords_to_plot2 = [] self.proc_balloondata = [] # For every GPX file that is selected self.textWarningConsole.clear() for i, currentgpx in enumerate(self.gpxselectedlist): # Parse the GPX file gpx, coords, dinfos_before, warnings = bombo.ParseGPX( currentgpx, track_nr=0, segment_nr=0, use_srtm_elevation=bool(self.checkUseSRTM.isChecked())) self.textWarningConsole.append(warnings) # Kalman processing coords, measurements, state_means, state_vars, dinfos_during = bombo.ApplyKalmanFilter( coords, gpx, method=self.comboBoxProcessingMethod.currentIndex(), use_acceleration=self.checkUseAcceleration.isChecked(), extra_smooth=self.checkExtraSmooth.isChecked(), debug_plot=False) # Save data in GPX structure to compute speed and elevations new_coords, new_gpx, dinfos_after = bombo.SaveDataToCoordsAndGPX( coords, state_means) # Update GUI with the computed stats parent = QtGui.QStandardItem(self.gpxselectednamelist[i]) parent_beforeprocessing = QtGui.QStandardItem("Raw GPX stats") parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_before['total_distance']) ]) parent_beforeprocessing_moving = QtGui.QStandardItem("Moving") parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['moving_time']) ]) parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['moving_distance']) ]) parent_beforeprocessing.appendRow( parent_beforeprocessing_moving) parent_beforeprocessing_idle = QtGui.QStandardItem("Idle") parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['idle_time']) ]) parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['idle_distance']) ]) parent_beforeprocessing.appendRow(parent_beforeprocessing_idle) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_before['elevation']) ]) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_before['climb']) ]) parent.appendRow(parent_beforeprocessing) parent.appendRow([ QtGui.QStandardItem("Samples"), QtGui.QStandardItem(dinfos_during['nsamples']) ]) parent.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_after['total_distance']) ]) parent_moving = QtGui.QStandardItem("Moving") parent_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['moving_time']) ]) parent_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['moving_distance']) ]) parent.appendRow(parent_moving) parent_idle = QtGui.QStandardItem("Idle") parent_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['idle_time']) ]) parent_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['idle_distance']) ]) parent.appendRow(parent_idle) parent.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_after['elevation']) ]) parent.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_after['climb']) ]) self.treemodel.appendRow(parent) # Create balloondata for the html plot balloondata = { 'distance': np.cumsum( bombo.HaversineDistance(np.asarray(new_coords['lat']), np.asarray( new_coords['lon']))), 'elevation': np.asarray(new_coords['ele']), 'speed': None } # Create extra data for the html plot (fully implemented in bombo, not here) """ data = np.ones((len(lat_cleaned),2)) data[:,0] = h_filtered / np.max(h_filtered) * 0.0004 data[:,1] = np.hstack((np.asarray([0]), speed_h)) / np.max(np.hstack((np.asarray([0]), speed_h))) * 0.0004 tangentdata = {'data': data, 'sides': (0, 1), 'palette': ('blue','red')} """ # Save relevant output in global variables self.proc_coords.append(coords) self.proc_measurements.append(measurements) self.proc_state_means.append(state_means) self.proc_state_vars.append(state_vars) self.proc_new_coords.append(new_coords) self.proc_new_gpx.append(new_gpx) self.proc_coords_to_plot.append( np.vstack((new_coords['lat'], new_coords['lon'])).T) self.proc_coords_to_plot2.append( np.vstack((coords['lat'], coords['lon'])).T) self.proc_balloondata.append(balloondata) # Restore original cursor QApplication.restoreOverrideCursor() # Generate embedded plots if len(self.gpxselectedlist) == 1: self.plotEmbeddedElevationAndSpeed.update_figure( measurements, state_means, new_gpx.tracks[0].segments[0]) self.plotEmbeddedDetails.update_figure( measurements, state_means, state_vars, new_gpx.tracks[0].segments[0]) else: # Commentato per adesso # self.plotEmbeddedElevationAndSpeed.update_figure_multiple_tracks(self.proc_measurements, self.proc_state_means, self.proc_new_gpx) self.plotEmbeddedElevationAndSpeed.clear_figure() self.plotEmbeddedDetails.clear_figure() # Generate html plot, if only one track is selected, proceed with the complete output, otherwise just plot the traces if len(self.gpxselectedlist) is 1: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=self.proc_coords_to_plot2, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) else: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=None, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) self.map2d.load(QtCore.QUrl(bombo.MAP_2D_FILENAME)) self.map2d.show() # Generate 3D plot, only with one track for the moment if len(self.gpxselectedlist) == 1: if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( new_coords['lat'], new_coords['lon'], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) else: self.textWarningConsole.setText( "You need to open a .gpx file before!") return def PlotSpecificAreaDialog(self): def PlotSpecificArea(): # Save coordinates for the next time if os.environ['QT_API'] == 'pyqt': self.settings.setValue("last_point_coord_lat", self.spinboxLatDec.value()) self.settings.setValue("last_point_coord_lon", self.spinboxLonDec.value()) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue( "last_point_coord_lat", QtCore.QVariant(self.spinboxLatDec.value())) self.settings.setValue( "last_point_coord_lon", QtCore.QVariant(self.spinboxLonDec.value())) # Select the 3D Map tab self.tab.setCurrentIndex(2) # Plot if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( [self.spinboxLatDec.value()], [self.spinboxLonDec.value()], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) d.done(0) def Convert(): try: dd = bombo.parse_dms(self.textLatLonGMS.text()) self.spinboxLatDec.setValue(dd[0]) self.spinboxLonDec.setValue(dd[1]) except: pass d = QDialog() grid = QGridLayout() hBox_coordsGMS = QHBoxLayout() hBox_coordsGMS.setSpacing(5) label = QLabel('Coordinates (gms)') grid.addWidget(label, 0, 0) self.textLatLonGMS = QLineEdit() self.textLatLonGMS.setText("") grid.addWidget(self.textLatLonGMS, 0, 1, 1, 2) button1 = QPushButton("Convert to decimal") button1.clicked.connect(Convert) grid.addWidget(button1, 0, 3) label = QLabel('Coordinates (decimal)') grid.addWidget(label, 1, 0) self.spinboxLatDec = QDoubleSpinBox() self.spinboxLatDec.setRange(-90, +90) self.spinboxLatDec.setSingleStep(0.0000001) self.spinboxLatDec.setDecimals(7) grid.addWidget(self.spinboxLatDec, 1, 1) self.spinboxLonDec = QDoubleSpinBox() self.spinboxLonDec.setRange(-180, +180) self.spinboxLonDec.setSingleStep(0.0000001) self.spinboxLonDec.setDecimals(7) grid.addWidget(self.spinboxLonDec, 1, 2) # Try to recover the last used points try: old_lat = self.settings.value("last_point_coord_lat", type=float) old_lon = self.settings.value("last_point_coord_lon", type=float) self.spinboxLatDec.setValue(old_lat) self.spinboxLonDec.setValue(old_lon) except: # Coordinates of Mt. Rinjani in Indonesia self.spinboxLatDec.setValue(-8.4166000) self.spinboxLonDec.setValue(116.4666000) button2 = QPushButton("Show 3D map") button2.clicked.connect(PlotSpecificArea) grid.addWidget(button2, 1, 3) d.setWindowTitle("Show point on 3D map") d.setLayout(grid) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def ProxyDialog(self): def SetProxy(): self.use_proxy = bool(self.checkUseProxy.isChecked()) self.proxy_config = self.textProxyConfig.text() if os.environ['QT_API'] == 'pyqt': self.settings.setValue("use_proxy", self.use_proxy) self.settings.setValue("proxy_config", str(self.proxy_config)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("use_proxy", QtCore.QVariant(self.use_proxy)) self.settings.setValue("proxy_config", QtCore.QVariant(str(self.proxy_config))) d.done(0) d = QDialog() box = QVBoxLayout() hBox_proxy = QHBoxLayout() hBox_proxy.setSpacing(5) label = QLabel('Proxy') hBox_proxy.addWidget(label) self.textProxyConfig = QLineEdit() try: self.textProxyConfig.setText( self.settings.value('proxy_config', str)) except: self.textProxyConfig.setText(bombo.PROXY_DATA) self.textProxyConfig.setMinimumWidth(200) hBox_proxy.addWidget(self.textProxyConfig) box.addLayout(hBox_proxy) self.checkUseProxy = QCheckBox("Use proxy") try: self.checkUseProxy.setChecked( self.settings.value('use_proxy', bool)) except: self.checkUseProxy.setChecked(bool(bombo.USE_PROXY)) box.addWidget(self.checkUseProxy) button = QPushButton("Save configuration") button.clicked.connect(SetProxy) box.addWidget(button) d.setWindowTitle("Proxy configuration") d.setLayout(box) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def __init__(self, parent=None): super(MainWindow, self).__init__() self.initVariables() self.initUI() def initVariables(self): self.gpxlist = list() self.gpxnamelist = list() self.gpxselectedlist = list() self.gpxselectednamelist = list() self.palette = bombo.GeneratePalette(N=10) * 5 # replicated 5 times #self.palette = ["#0000FF", "#00FF00", "#00FFFF", "#FF0000", "#FF00FF", "#FFFF00", "#FFFFFF"] # test palette self.selectedpalette = list() self.proc_coords = list() self.proc_measurements = list() self.proc_state_means = list() self.proc_state_vars = list() self.proc_new_coords = list() self.proc_new_gpx = list() self.proc_coords_to_plot = list() self.proc_coords_to_plot2 = list() self.proc_balloondata = list() def initUI(self): def selection_changed(): # Retrieve selected items # selecteditems = self.tracklist.selectedItems() selectedindexes = self.tracklist.selectedIndexes() # Adding the selected items to the processing list self.gpxselectedlist[:] = [] self.gpxselectednamelist[:] = [] self.selectedpalette[:] = [] for i in selectedindexes: # print str(i.text()) self.gpxselectedlist.append(self.gpxlist[i.row()]) self.gpxselectednamelist.append(self.gpxnamelist[i.row()]) self.selectedpalette.append(self.palette[i.row()]) def ClearStats(): """ # Some other code that could be used in the future index = self.treemodel.indexFromItem(parent1) self.tree.expand(index) selmod = self.tree.selectionModel() index2 = self.treemodel.indexFromItem(child2) selmod.select(index2, QtCore.QItemSelectionModel.Select|QtCore.QItemSelectionModel.Rows) root = self.treemodel.invisibleRootItem() (item.parent() or root).removeChild(item) """ # Returns a list of indexes. In our case, for each row there are 2 indexes, cos there are 2 columns. for index in self.tree.selectedIndexes(): # Consider only the first columns if index.column() == 0: # Need to check if it's a top item (i.e. track), otherwise if a subitem (i.e. distance or time) is selected, the result might be buggy parent = index.parent() parent_item = self.treemodel.itemFromIndex(parent) if parent_item is None: self.treemodel.removeRow(index.row()) # Application Settings QtCore.QCoreApplication.setOrganizationName("Ste") QtCore.QCoreApplication.setOrganizationDomain( "https://github.com/stesalati/sport/") QtCore.QCoreApplication.setApplicationName("TrackAnalyser") # Config settings self.settings = QtCore.QSettings(self) # Proxy settings try: self.use_proxy = self.settings.value('use_proxy', bool) self.proxy_config = self.settings.value('proxy_config', str) except: self.use_proxy = bombo.USE_PROXY self.proxy_config = bombo.PROXY_DATA # Actions openfile = QAction(QtGui.QIcon("icons/openfile.png"), "Open .gpx", self) openfile.setShortcut("Ctrl+O") openfile.setStatusTip("Open file") openfile.triggered.connect(self.selectFileToOpen) go = QAction(QtGui.QIcon("icons/go.png"), "Go!", self) go.setShortcut("Ctrl+R") go.setStatusTip("Run analysis") go.triggered.connect(self.Go) clearstats = QAction(QtGui.QIcon("icons/clear.png"), "Clear stats", self) clearstats.setShortcut("Ctrl+C") clearstats.setStatusTip("Clear stats") clearstats.triggered.connect(ClearStats) sep1 = QAction(self) sep1.setSeparator(True) showpoint = QAction(QtGui.QIcon("icons/point.png"), "Show point", self) showpoint.setShortcut("Ctrl+P") showpoint.setStatusTip("Show point") showpoint.triggered.connect(self.PlotSpecificAreaDialog) sep2 = QAction(self) sep2.setSeparator(True) quitapp = QAction(QtGui.QIcon("icons/quit.png"), "Quit", self) quitapp.setShortcut("Ctrl+Q") quitapp.setStatusTip("Quit application") quitapp.triggered.connect(qApp.quit) configs = QAction(QtGui.QIcon("icons/configs.png"), "Configs", self) configs.setStatusTip("Configs") configs.triggered.connect(self.ProxyDialog) # Menubar mainMenu = self.menuBar() configMenu = mainMenu.addMenu('&Config') configMenu.addAction(configs) # Toolbar toolbar = self.addToolBar('My tools') toolbar.addAction(openfile) toolbar.addAction(go) toolbar.addAction(clearstats) toolbar.addAction(sep1) toolbar.addAction(showpoint) toolbar.addAction(sep2) toolbar.addAction(quitapp) toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QtCore.QSize(30, 30)) # Status bar self.statusBar().show() # Main widget (everything that's not toolbar, statusbar or menubar must be in this widget) self.scatola = QWidget() # Main horizontal impagination hBox = QHBoxLayout() hBox.setSpacing(5) # Vertical left column vBox_left = QVBoxLayout() vBox_left.setSpacing(5) # 1st vertical box, a list self.tracklist = QListWidget() vBox_left.addWidget(self.tracklist) self.tracklist.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tracklist.itemSelectionChanged.connect(selection_changed) self.tracklist.setMaximumHeight(120) # 2nd vertical box, containing several horizontal boxes, one for each setting vBox2 = QVBoxLayout() vBox2.setSpacing(5) # Just the group label labelSettings = QLabel('Settings') vBox2.addWidget(labelSettings) # Use/don't use corrected altitude self.checkUseSRTM = QCheckBox( "Use SRTM corrected elevation (needs Internet)") self.checkUseSRTM.setChecked(False) vBox2.addWidget(self.checkUseSRTM) # Choose processing method + use/don't use acceleration hBoxProcessingMethod = QHBoxLayout() labelProcessingMethod = QLabel('Processing method') hBoxProcessingMethod.addWidget(labelProcessingMethod) self.comboBoxProcessingMethod = QComboBox() self.comboBoxProcessingMethod.addItem("Just use available data") self.comboBoxProcessingMethod.addItem( "Fill all gaps at T=1s (resample)") self.comboBoxProcessingMethod.addItem("Fill only smaller gaps at T=1s") hBoxProcessingMethod.addWidget(self.comboBoxProcessingMethod) self.checkUseAcceleration = QCheckBox("Use acceleration") self.checkUseAcceleration.setChecked(False) hBoxProcessingMethod.addWidget(self.checkUseAcceleration) vBox2.addLayout(hBoxProcessingMethod) # Use/don't use variance smooth self.checkExtraSmooth = QCheckBox("Extra smooth") self.checkExtraSmooth.setChecked(False) vBox2.addWidget(self.checkExtraSmooth) # 2D interactive map settings hBox2DMap = QHBoxLayout() self.checkUseRDP = QCheckBox("Use RDP to reduce points") self.checkUseRDP.setChecked(False) hBox2DMap.addWidget(self.checkUseRDP) self.check2DMapInExternalBrowser = QCheckBox( "Show in external browser") self.check2DMapInExternalBrowser.setChecked(False) hBox2DMap.addWidget(self.check2DMapInExternalBrowser) vBox2.addLayout(hBox2DMap) # Settings for the 3D map line3DViewSettings = QFrame() #line3DViewSettings.setGeometry(QtCore.QRect(320, 150, 118, 3)) line3DViewSettings.setFrameShape(QFrame.HLine) line3DViewSettings.setFrameShadow(QFrame.Sunken) vBox2.addWidget(line3DViewSettings) label3DViewSettings = QLabel('3D view settings') vBox2.addWidget(label3DViewSettings) hBox3DMapSelection = QHBoxLayout() self.check3DMapSelection = QCheckBox( "Select elevation tiles automatically, otherwise") self.check3DMapSelection.setChecked(True) hBox3DMapSelection.addWidget(self.check3DMapSelection) self.text3DMapName = QLineEdit() self.text3DMapName.setText("Iceland.tif") hBox3DMapSelection.addWidget(self.text3DMapName) vBox2.addLayout(hBox3DMapSelection) hBox3D = QHBoxLayout() label3DMargin = QLabel('Margin') hBox3D.addWidget(label3DMargin) self.spinbox3DMargin = QSpinBox() self.spinbox3DMargin.setRange(50, 1000) self.spinbox3DMargin.setValue(100) self.spinbox3DMargin.setSingleStep(10) hBox3D.addWidget(self.spinbox3DMargin) labelSpace = QLabel(' ') hBox3D.addWidget(labelSpace) label3DElevationScale = QLabel('Elev. scale') hBox3D.addWidget(label3DElevationScale) self.spinbox3DElevationScale = QDoubleSpinBox() self.spinbox3DElevationScale.setRange(1, 50) self.spinbox3DElevationScale.setSingleStep(0.1) hBox3D.addWidget(self.spinbox3DElevationScale) hBox3D.addWidget(labelSpace) label3DOSMZoom = QLabel('Zoom') hBox3D.addWidget(label3DOSMZoom) self.spinbox3DOSMZoom = QSpinBox() self.spinbox3DOSMZoom.setRange(8, 15) self.spinbox3DOSMZoom.setValue(13) self.spinbox3DOSMZoom.setSingleStep(1) hBox3D.addWidget(self.spinbox3DOSMZoom) hBox3D.addWidget(labelSpace) self.check3DOSMInvert = QCheckBox("Invert") self.check3DOSMInvert.setChecked(False) hBox3D.addWidget(self.check3DOSMInvert) vBox2.addLayout(hBox3D) vBox_left.addLayout(vBox2) # 3rd stats tree lineTree = QFrame() lineTree.setFrameShape(QFrame.HLine) lineTree.setFrameShadow(QFrame.Sunken) vBox2.addWidget(lineTree) labelTree = QLabel('Track stats') vBox2.addWidget(labelTree) self.tree = QTreeView() self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.treemodel = QtGui.QStandardItemModel() self.treemodel.setHorizontalHeaderLabels(['Name', 'Value']) self.tree.setModel(self.treemodel) self.tree.setUniformRowHeights(True) self.tree.setColumnWidth(0, 200) vBox_left.addWidget(self.tree) # 4th text, containing text messages/errors self.textWarningConsole = QTextEdit() self.textWarningConsole.setReadOnly(True) self.textWarningConsole.setFont(QtGui.QFont("Courier New", FONTSIZE)) self.textWarningConsole.clear() self.textWarningConsole.setMaximumHeight(50) vBox_left.addWidget(self.textWarningConsole) # I put "vBox_left" inside a widget and then the widget inside "hBox" # instead of just doing "hBox.addLayout(vBox_left) so I can set its # maximum width. vBox_left_widget = QWidget() vBox_left_widget.setLayout(vBox_left) vBox_left_widget.setMinimumWidth(400) vBox_left_widget.setMaximumWidth(500) hBox.addWidget(vBox_left_widget) # Vertical right column self.tab = QTabWidget() # Tab 1: Summary: elevation and speed tab1 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedElevationAndSpeed = EmbeddedPlot_ElevationSpeed( width=5, height=4, dpi=100) self.plotEmbeddedElevationAndSpeed.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar1 = NavigationToolbar( self.plotEmbeddedElevationAndSpeed, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedElevationAndSpeed) vBox_tab.addWidget(self.mpl_toolbar1) # Associate the layout to the tab tab1.setLayout(vBox_tab) # Tab 2: html 2D map tab2 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map2d = QtWebEngineWidgets.QWebEngineView() # Add widgets to the layout vBox_tab.addWidget(self.map2d) # Associate the layout to the tab tab2.setLayout(vBox_tab) # Tab 3: 3D plot tab3 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map3d = MayaviQWidget() # Add widgets to the layout vBox_tab.addWidget(self.map3d) # Associate the layout to the tab tab3.setLayout(vBox_tab) # Tab 4: Details tab4 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedDetails = EmbeddedPlot_Details(width=5, height=4, dpi=100) self.plotEmbeddedDetails.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar2 = NavigationToolbar(self.plotEmbeddedDetails, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedDetails) vBox_tab.addWidget(self.mpl_toolbar2) # Associate the layout to the tab tab4.setLayout(vBox_tab) # Associate tabs self.tab.addTab(tab1, "Summary") self.tab.addTab(tab2, "2D Map") self.tab.addTab(tab3, "3D Map") self.tab.addTab(tab4, "Details") hBox.addWidget(self.tab) # Setting hBox as main box self.scatola.setLayout(hBox) self.setCentralWidget(self.scatola) # Application settings self.setWindowTitle('TrackAnalyser') self.setWindowIcon((QtGui.QIcon('icons/app.png'))) self.setGeometry(100, 100, 1200, 700) self.show()
class QtPipDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() self.setAttribute(Qt.WA_DeleteOnClose) # create install process self.process = QProcess(self) self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join( [user_site_packages(), env.systemEnvironment().value("PYTHONPATH")] ) env.insert("PYTHONPATH", combined_paths) self.process.setProcessEnvironment(env) # connections self.install_button.clicked.connect(self._install) self.uninstall_button.clicked.connect(self._uninstall) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) from ..plugins import plugin_manager self.process.finished.connect(plugin_manager.discover) self.process.finished.connect(plugin_manager.prune) def setup_ui(self): layout = QVBoxLayout() self.setLayout(layout) title = QLabel("Install/Uninstall Packages:") title.setObjectName("h2") self.line_edit = QLineEdit() self.install_button = QPushButton("install", self) self.uninstall_button = QPushButton("uninstall", self) self.text_area = QTextEdit(self, readOnly=True) hlay = QHBoxLayout() hlay.addWidget(self.line_edit) hlay.addWidget(self.install_button) hlay.addWidget(self.uninstall_button) layout.addWidget(title) layout.addLayout(hlay) layout.addWidget(self.text_area) self.setFixedSize(700, 400) self.setMaximumHeight(800) self.setMaximumWidth(1280) def _install(self): cmd = ['-m', 'pip', 'install'] if running_as_bundled_app() and sys.platform.startswith('linux'): cmd += [ '--no-warn-script-location', '--prefix', user_plugin_dir(), ] self.process.setArguments(cmd + self.line_edit.text().split()) self.text_area.clear() self.process.start() def _uninstall(self): args = ['-m', 'pip', 'uninstall', '-y'] self.process.setArguments(args + self.line_edit.text().split()) self.text_area.clear() self.process.start() def _on_stdout_ready(self): text = self.process.readAllStandardOutput().data().decode() self.text_area.append(text)
class ZhuNoteForm(QWidget): def __init__(self, path=None): QWidget.__init__(self) self.initUI(path) def initUI(self, path): pathLabel = QLabel('Path') filenameLabel = QLabel('Filename') timeLabel = QLabel('Time') titleLabel = QLabel('Title') keywordLabel = QLabel('Keyword') figureLabel = QLabel('Figure') htmlLabel = QLabel('HTML') bodyLabel = QLabel('Body') self.pathEdit = QLineEdit(path) self.pathEdit.setReadOnly(True) self.filenameEdit = QLineEdit() self.timeEdit = QLineEdit() self.titleEdit = QLineEdit() self.keywordEdit = QLineEdit() self.figureEdit = QLineEdit() self.htmlEdit = QLineEdit() self.bodyEdit = QTextEdit() # If more than one keyword, delimit with comma. # Same for figure and html filenames. #btnSave = QPushButton('Save') #btnSave.setToolTip('Save script to file') #btnSave.clicked.connect(self.saveFile) # Replace save button with keyboard shortcut # Save move hand from keyboard to mouse. grid = QGridLayout() grid.setSpacing(5) row = 0 grid.addWidget(pathLabel, row, 0) grid.addWidget(self.pathEdit, row, 1) row += 1 grid.addWidget(filenameLabel, row, 0) grid.addWidget(self.filenameEdit, row, 1) row += 1 grid.addWidget(figureLabel, row, 0) grid.addWidget(self.figureEdit, row, 1) row += 1 grid.addWidget(htmlLabel, row, 0) grid.addWidget(self.htmlEdit, row, 1) row += 1 grid.addWidget(timeLabel, row, 0) grid.addWidget(self.timeEdit, row, 1) row += 1 grid.addWidget(titleLabel, row, 0) grid.addWidget(self.titleEdit, row, 1) row += 1 grid.addWidget(keywordLabel, row, 0) grid.addWidget(self.keywordEdit, row, 1) row += 1 grid.addWidget(bodyLabel, row, 0) grid.addWidget(self.bodyEdit, row, 1, 6, 1) #grid.addWidget(btnSave, 11, 1) self.actOpen = QAction('Open', self) self.actOpen.setShortcut('Ctrl+O') self.actOpen.triggered.connect(self.openFile) self.filenameEdit.addAction(self.actOpen) self.actSave = QAction('Save', self) self.actSave.setShortcut('Ctrl+S') self.actSave.triggered.connect(self.saveFile) self.bodyEdit.addAction(self.actSave) self.setLayout(grid) #self.setGeometry(300, 300, 600, 400) self.setWindowTitle('Form - ZhuNote') #self.show() def setFont(self, font): #font = self.bodyEdit.font() # current font #font = QFont() # default font self.pathEdit.setFont(font) self.filenameEdit.setFont(font) self.timeEdit.setFont(font) self.titleEdit.setFont(font) self.keywordEdit.setFont(font) self.figureEdit.setFont(font) self.htmlEdit.setFont(font) self.bodyEdit.setFont(font) def clear(self): self.filenameEdit.clear() self.timeEdit.clear() self.titleEdit.clear() self.keywordEdit.clear() self.figureEdit.clear() self.htmlEdit.clear() self.bodyEdit.clear() def viewDict(self, dictNote): self.filenameEdit.setText(dictNote['Filename']) self.timeEdit.setText(dictNote['Time']) self.titleEdit.setText(dictNote['Title']) self.keywordEdit.setText(dictNote['Keyword']) self.figureEdit.setText(dictNote['Figure']) self.htmlEdit.setText(dictNote['HTML']) self.bodyEdit.setText(dictNote['Body']) def openFile(self): path = self.pathEdit.text() fn = self.filenameEdit.text() ffn = os.path.join(path, fn) with open(ffn, 'rb') as f: dictNote = pickle.load(f) self.viewDict(dictNote) def saveFile(self): #fn = timeStr + '.txt' # Use title as filename to overwrite existing note file. base = self.titleEdit.text().replace(' ', '_') txtfn = base + '.txt' pklfn = base + '.pkl' path = self.pathEdit.text() timeStr = datetime.now().strftime('%Y-%m-%d-%H-%M-%S') self.filenameEdit.setText(pklfn) self.timeEdit.setText(timeStr) textSum = '' text = 'Filename: ' + txtfn + '\n' textSum += text text = 'Time: ' + timeStr + '\n' textSum += text text = 'Title: ' + self.titleEdit.text() + '\n' textSum += text text = 'Keyword: ' + self.keywordEdit.text() + '\n' textSum += text text = 'Figure: ' + self.figureEdit.text() + '\n' textSum += text text = 'HTML: ' + self.htmlEdit.text() + '\n' textSum += text text = 'Body: ' + self.bodyEdit.toPlainText() + '\n' textSum += text dictNote = {} dictNote['Filename'] = pklfn dictNote['Time'] = timeStr dictNote['Title'] = self.titleEdit.text() dictNote['Keyword'] = self.keywordEdit.text() dictNote['Figure'] = self.figureEdit.text() dictNote['HTML'] = self.htmlEdit.text() dictNote['Body'] = self.bodyEdit.toPlainText() txtffn = os.path.join(path, txtfn) pklffn = os.path.join(path, pklfn) # Check if file exist if os.path.isfile(txtffn): choice = QMessageBox.question( self, 'Warning', "File exists. Do you want overwrite?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if choice == QMessageBox.Yes: self.writeFile(textSum, txtffn, dictNote, pklffn) else: print("Change title and re-save.") return 1 else: self.writeFile(textSum, txtffn, dictNote, pklffn) return 0 @staticmethod def writeFile(textSum, txtfn, dictNote, pklfn): """ input are full filename (with absolute path) """ with open(txtfn, 'w', encoding='utf-8') as f: f.write(textSum) with open(pklfn, 'wb') as f: pickle.dump(dictNote, f, -1)
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 MainWindow(QMainWindow): def __init__(self): self.logger = logging.getLogger(APPLICATION_NAME + '.MainWindow') self.logger.info("MainWindow.__init__") super(MainWindow, self).__init__() self.init_ui() self.read_settings() def init_ui(self): # Define standard icon. standard_icon = self.style().standardIcon # Central widget. self.main_widget = SpectrumWidget() self.main_widget.setFocus() self.setCentralWidget(self.main_widget) # Project action new_project_action = QAction(QIcon(':/oi/svg/document.svg'), 'New project', self) new_project_action.setShortcut('Ctrl+N') new_project_action.setStatusTip('New project') new_project_action.triggered.connect(self.new_project) open_project_action = QAction(QIcon(':/oi/svg/envelope-open.svg'), 'Open project', self) open_project_action.setShortcut('Ctrl+O') open_project_action.setStatusTip('Open project') open_project_action.triggered.connect(self.open_project) close_project_action = QAction(QIcon(':/oi/svg/envelope-closed.svg'), 'Close project', self) close_project_action.setShortcut('Ctrl+C') close_project_action.setStatusTip('Close project') close_project_action.triggered.connect(self.close_project) save_project_action = QAction(QIcon(':/oi/svg/hard-drive.svg'), 'Save project', self) save_project_action.setShortcut('Ctrl+S') save_project_action.setStatusTip('Save project') save_project_action.triggered.connect(self.save_project) saveas_project_action = QAction(QIcon(':/oi/svg/hard-drive.svg'), 'Save project as ...', self) # saveas_project_action.setShortcut('Ctrl+S') saveas_project_action.setStatusTip('Save project as ...') saveas_project_action.triggered.connect(self.saveas_project) # Spectrum action import_spectrum_action = QAction(QIcon(':/oi/svg/account-login.svg'), 'Import spectrum', self) import_spectrum_action.setShortcut('Ctrl+I') import_spectrum_action.setStatusTip('Import spectrum') import_spectrum_action.triggered.connect(self.import_spectrum) export_spectrum_action = QAction(QIcon(':/oi/svg/account-logout.svg'), 'Export spectrum', self) # export_spectrum_action.setShortcut('Ctrl+I') export_spectrum_action.setStatusTip('Export spectrum') export_spectrum_action.triggered.connect(self.export_spectrum) # Exit action exit_action = QAction(QIcon(':/oi/svg/x.svg'), 'Exit', self) exit_action.setShortcut('Ctrl+Q') exit_action.setStatusTip('Exit application') exit_action.triggered.connect(self.close) # Status bar. self.statusBar() # Menu bar. menubar = self.menuBar() file_menu = menubar.addMenu('&File') file_menu.addAction(new_project_action) file_menu.addAction(open_project_action) file_menu.addAction(save_project_action) file_menu.addAction(saveas_project_action) file_menu.addAction(close_project_action) file_menu.addSeparator() file_menu.addAction(exit_action) view_menu = menubar.addMenu('&View') spectrum_menu = menubar.addMenu('&Spectrum') spectrum_menu.addAction(import_spectrum_action) analysis_menu = menubar.addMenu('&Analysis') # Toolbar file_toolbar = self.addToolBar('File') file_toolbar.addAction(new_project_action) file_toolbar.addAction(open_project_action) file_toolbar.addAction(save_project_action) file_toolbar.addAction(saveas_project_action) file_toolbar.addAction(close_project_action) file_toolbar.addAction(exit_action) view_menu.addAction(file_toolbar.toggleViewAction()) spectrum_toolbar = self.addToolBar('Spectrum') spectrum_toolbar.addAction(import_spectrum_action) view_menu.addAction(spectrum_toolbar.toggleViewAction()) analysis_toolbar = self.addToolBar('Analysis') view_menu.addAction(analysis_toolbar.toggleViewAction()) view_menu.addSeparator() # Dock widget. self.graphic_settings_dock = QDockWidget("Graphic settings", self) self.graphic_settings_dock.setObjectName("graphic_settings_dock") self.graphic_settings_dock.setAllowedAreas(Qt.AllDockWidgetAreas) label_test = QLabel("Test label") self.graphic_settings_dock.setWidget(label_test) view_menu.addAction(self.graphic_settings_dock.toggleViewAction()) self.addDockWidget(Qt.AllDockWidgetAreas, self.graphic_settings_dock) print(self.graphic_settings_dock.objectName()) # Final options. self.setWindowTitle('X-ray spectrum analyzer') self.show() def closeEvent(self, event): self.save_settings() super(MainWindow, self).closeEvent(event) def save_settings(self): settings = QSettings("openMicroanalysis", "xrayspectrumanalyzergui") # print(settings.fileName()) settings.beginGroup("MainWindow") settings.setValue("geometry", self.saveGeometry()) settings.setValue("window_state", self.saveState()) settings.endGroup() settings.beginGroup("graphic_settings_dock") settings.setValue("visible", self.graphic_settings_dock.isVisible()) settings.endGroup() # settings.beginGroup("zero_loss_peak_dock") # settings.setValue("visible", self.zero_loss_peak_dock.isVisible()) # settings.endGroup() def read_settings(self): settings = QSettings("openMicroanalysis", "xrayspectrumanalyzergui") # print(settings.fileName()) # settings.clear() settings.beginGroup("MainWindow") geometry_value = settings.value("geometry") if geometry_value is None: self.setGeometry(300, 300, 600, 400) else: self.restoreGeometry(geometry_value) window_state_value = settings.value("window_state") if window_state_value is not None: self.restoreState(window_state_value) settings.endGroup() settings.beginGroup("graphic_settings_dock") visible_value = settings.value("visible") if visible_value is not None: if visible_value == "true": self.graphic_settings_dock.setVisible(True) elif visible_value == "false": self.graphic_settings_dock.setVisible(False) settings.endGroup() # settings.beginGroup("zero_loss_peak_dock") # visible_value = settings.value("visible") # if visible_value is not None: # if visible_value == "true": # self.zero_loss_peak_dock.setVisible(True) # elif visible_value == "false": # self.zero_loss_peak_dock.setVisible(False) # settings.endGroup() def import_spectrum(self): self.statusBar().showMessage("Import spectrum", 2000) path = os.path.dirname(__file__) formats = ["*.msa", "*.txt"] file_filters = "Spectrum file ({:s})".format(" ".join(formats)) file_names = QFileDialog.getOpenFileName(self, "Import an x-ray spectrum", path, file_filters) def export_spectrum(self): self.statusBar().showMessage("Export spectrum", 2000) def new_project(self): self.statusBar().showMessage("New project", 2000) def open_project(self): self.statusBar().showMessage("Open project", 2000) def close_project(self): self.statusBar().showMessage("Close project", 2000) def save_project(self): self.statusBar().showMessage("Save project", 2000) def saveas_project(self): self.statusBar().showMessage("Save project as ...", 2000) def create_gui(self): self.logger.info("MainWindow.create_gui") self._create_main_window() self._create_actions() self._create_menus() self._create_toolbars() self._create_tooltip() self._create_spectra_display() self._create_data_display() self._create_operations_display() self._create_layout() self._create_statusbar() self._read_settings() self.show() def _create_main_window(self): self.setGeometry(300, 300, 500, 500) self.setWindowTitle('Spectrum Analyzer') # self.setWindowIcon(QIcon('../../../images/cog.svg')) self.setWindowIcon(self.style().standardIcon(QStyle.SP_DesktopIcon)) self._center_main_window() def _center_main_window(self): self.logger.info("MainWindow._center_main_window") qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def _create_menus(self): self.logger.info("MainWindow._create_menus") self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def _create_layout(self): mainLayout = QHBoxLayout() mainLayout.addWidget(self.dataGroupBox) mainLayout.addWidget(self.plotGroupBox) mainLayout.addWidget(self.operationsGroupBox) self.mainGroupBox = QGroupBox("Main layout") self.mainGroupBox.setLayout(mainLayout) self.setCentralWidget(self.mainGroupBox) def _create_spectra_display(self): self.plotGroupBox = QGroupBox("Plot layout") self.figure1 = Figure(facecolor=(1, 1, 1), edgecolor=(0, 0, 0)) self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setParent(self.plotGroupBox) self.canvas1.setFocusPolicy(Qt.StrongFocus) self.canvas1.setFocus() self.canvas1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.canvas1.updateGeometry() self.mpl_toolbar1 = NavigationToolbar(self.canvas1, self.plotGroupBox) self.canvas1.mpl_connect('key_press_event', self.on_key_press) self.figure2 = Figure(facecolor=(1, 1, 1), edgecolor=(0, 0, 0)) self.canvas2 = FigureCanvas(self.figure2) self.canvas2.setParent(self.plotGroupBox) self.mpl_toolbar2 = NavigationToolbar(self.canvas1, self.plotGroupBox) layout = QVBoxLayout() layout.addWidget(self.canvas1) layout.addWidget(self.mpl_toolbar1) layout.addWidget(self.canvas2) layout.addWidget(self.mpl_toolbar2) self.plotGroupBox.setLayout(layout) def _create_data_display(self): self.dataGroupBox = QGroupBox("Data layout") data_layout = QVBoxLayout() group_box = QGroupBox("Spectra") self.spectra_list_view = QListWidget(self) self.spectra_list_view.setMinimumWidth(200) layout = QVBoxLayout() layout.addWidget(self.spectra_list_view) group_box.setLayout(layout) data_layout.addWidget(group_box) group_box = QGroupBox("ROI") roi_list_view = QListWidget(self) layout = QVBoxLayout() layout.addWidget(roi_list_view) group_box.setLayout(layout) data_layout.addWidget(group_box) group_box = QGroupBox("Elements") element_list_view = QListWidget(self) layout = QVBoxLayout() layout.addWidget(element_list_view) group_box.setLayout(layout) data_layout.addWidget(group_box) self.dataGroupBox.setLayout(data_layout) def _create_operations_display(self): self.operationsGroupBox = QGroupBox("Operations layout") results_layout = QVBoxLayout() group_box = QGroupBox("Operation") results_layout.addWidget(group_box) group_box = QGroupBox("Results") results_layout.addWidget(group_box) self.operationsGroupBox.setLayout(results_layout) def _create_tooltip(self): QToolTip.setFont(QFont('SansSerif', 10)) self.setToolTip('This is a <b>QWidget</b> widget') def _create_actions(self): self.logger.info("MainWindow._create_actions") self.newAct = QAction(self.style().standardIcon(QStyle.SP_FileIcon), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(self.style().standardIcon( QStyle.SP_DirOpenIcon), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(self.style().standardIcon( QStyle.SP_DialogSaveButton), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction( self.style().standardIcon(QStyle.SP_DialogSaveButton), "Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.exitAct = QAction(self.style().standardIcon( QStyle.SP_DialogCloseButton), "E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close) self.textEdit = QTextEdit() self.aboutAct = QAction(self.style().standardIcon( QStyle.SP_MessageBoxInformation), "&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction(self.style().standardIcon( QStyle.SP_TitleBarMenuButton), "About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication().aboutQt) def _create_toolbars(self): self.logger.info("MainWindow._create_toolbars") self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) def _create_statusbar(self): self.logger.info("MainWindow._create_statusbar") self.statusBar().showMessage("Ready") def _read_settings(self): self.logger.info("MainWindow._read_settings") settings = QSettings(ORGANIZATION_NAME, APPLICATION_NAME) pos = settings.value("pos", QPoint(200, 200)) size = settings.value("size", QSize(400, 400)) self.resize(size) self.move(pos) def _write_settings(self): self.logger.info("MainWindow._write_settings") settings = QSettings(ORGANIZATION_NAME, APPLICATION_NAME) settings.setValue("pos", self.pos()) settings.setValue("size", self.size()) def maybeSave(self): self.logger.info("MainWindow.maybeSave") if self.textEdit.document().isModified(): ret = QMessageBox.warning( self, "Application", "The document has been modified.\nDo you want to save " "your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() elif ret == QMessageBox.Cancel: return False return True def closeEvent(self, event): self.logger.info("MainWindow.closeEvent") if self.maybeSave(): self._write_settings() event.accept() else: event.ignore() def newFile(self): self.logger.info("MainWindow.newFile") if self.maybeSave(): self.textEdit.clear() self.setCurrentFile('') def open(self): self.logger.info("MainWindow.open") if self.maybeSave(): filepath, _filtr = QFileDialog.getOpenFileName(self) if filepath: self.spectrumAnalyzer.readSpectrum(filepath) filename = os.path.basename(filepath) self.spectra_list_view.addItem(filename) self.spectrumAnalyzer.plotSpectrum(self.figure1) self.canvas1.draw() def save(self): self.logger.info("MainWindow.save") if self.curFile: return self.saveFile(self.curFile) return self.saveAs() def saveAs(self): self.logger.info("MainWindow.saveAs") fileName, _filtr = QFileDialog.getSaveFileName(self) if fileName: return self.saveFile(fileName) return False def about(self): self.logger.info("MainWindow.about") QMessageBox.about( self, "About xrayspectrumanalyzer", "The <b>xrayspectrumanalyzer</b> extract peak intensity from EDS spectrum." ) def documentWasModified(self): self.logger.info("MainWindow.documentWasModified") self.setWindowModified(self.textEdit.document().isModified()) def on_key_press(self, event): print('you pressed', event.key) # implement the default mpl key press events described at # http://matplotlib.org/users/navigation_toolbar.html#navigation-keyboard-shortcuts key_press_handler(event, self.canvas1, self.mpl_toolbar1)
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()