def __init__(self, theme='dark', emphasized=False): super().__init__(None) self.setProperty('emphasized', emphasized) self.setStyleSheet(template(raw_stylesheet, **palettes[theme])) lay = QVBoxLayout() self.setLayout(lay) lay.addWidget(QPushButton('push button')) box = QComboBox() box.addItems(['a', 'b', 'c', 'cd']) lay.addWidget(box) lay.addWidget(QFontComboBox()) hbox = QHBoxLayout() chk = QCheckBox('tristate') chk.setToolTip('I am a tooltip') chk.setTristate(True) chk.setCheckState(Qt.PartiallyChecked) chk3 = QCheckBox('checked') chk3.setChecked(True) hbox.addWidget(QCheckBox('unchecked')) hbox.addWidget(chk) hbox.addWidget(chk3) lay.addLayout(hbox) lay.addWidget(TabDemo(emphasized=emphasized)) sld = QSlider(Qt.Horizontal) sld.setValue(50) lay.addWidget(sld) scroll = QScrollBar(Qt.Horizontal) scroll.setValue(50) lay.addWidget(scroll) lay.addWidget(QHRangeSlider(parent=self)) text = QTextEdit() text.setMaximumHeight(100) text.setHtml(blurb) lay.addWidget(text) lay.addWidget(QTimeEdit()) edit = QLineEdit() edit.setPlaceholderText('LineEdit placeholder...') lay.addWidget(edit) lay.addWidget(QLabel('label')) prog = QProgressBar() prog.setValue(50) lay.addWidget(prog) groupBox = QGroupBox("Exclusive Radio Buttons") radio1 = QRadioButton("&Radio button 1") radio2 = QRadioButton("R&adio button 2") radio3 = QRadioButton("Ra&dio button 3") radio1.setChecked(True) hbox = QHBoxLayout() hbox.addWidget(radio1) hbox.addWidget(radio2) hbox.addWidget(radio3) hbox.addStretch(1) groupBox.setLayout(hbox) lay.addWidget(groupBox)
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 MappingWidget(CustomWidget): def __init__(self, *args, **kwargs): super(MappingWidget, self).__init__(*args, **kwargs) self.move(WINDOWDIMS_MAPPING[0], WINDOWDIMS_MAPPING[1]) self.resize(WINDOWDIMS_MAPPING[2], WINDOWDIMS_MAPPING[3]) # configure lsl stream outlet_info = pylsl.StreamInfo(name='sensorimotor_mapping', type='map', channel_count=1, nominal_srate=pylsl.IRREGULAR_RATE, channel_format=pylsl.cf_string, source_id='mapping1214') self.map_stream = pylsl.StreamOutlet(outlet_info) def create_control_panel(self): pass def create_plots(self, theme='dark', **kwargs): bold_font = QFont() bold_font.setPixelSize(12) bold_font.setBold(True) self.layout().addStretch() self.layout().addWidget(QLabel("Channels: ", font=bold_font)) self.layout().addSpacing(10) # Add check boxes for channels self.channels = {} for chan_ix in range(len(self.group_info)): chan_lbl = self.group_info[chan_ix]['label'] chk_box = QCheckBox(chan_lbl) chk_box.stateChanged.connect(self.check_channel_and_stim) self.channels[chan_lbl] = chk_box self.layout().addWidget(chk_box) self.layout().addSpacing(10) bt_clear = QPushButton("Uncheck all") bt_clear.clicked.connect(lambda: self.uncheck_all(self.channels)) self.layout().addWidget(bt_clear) self.layout().addSpacing(20) # Stimuli types self.layout().addWidget(QLabel("Stimuli: ", font=bold_font)) self.layout().addSpacing(10) self.mapping_stimuli = {} for stim in MAPPINGSTIMULI: self.mapping_stimuli[stim] = QCheckBox(stim) self.mapping_stimuli[stim].stateChanged.connect( self.check_channel_and_stim) self.layout().addWidget(self.mapping_stimuli[stim]) self.mapping_stimuli['Custom'] = QCheckBox() l = QHBoxLayout() self.custom_stimulus = QLineEdit("Custom") l.addWidget(self.mapping_stimuli['Custom']) l.addSpacing(3) l.addWidget(self.custom_stimulus) self.layout().addLayout(l) self.layout().addSpacing(10) bt_clear = QPushButton("Uncheck all") bt_clear.clicked.connect( lambda: self.uncheck_all(self.mapping_stimuli)) self.layout().addWidget(bt_clear) self.layout().addSpacing(20) # side self.layout().addWidget(QLabel("Body Side: ", font=bold_font)) self.layout().addSpacing(10) self.sides = {'Left': QCheckBox("Left"), 'Right': QCheckBox("Right")} l = QHBoxLayout() l.addWidget(self.sides['Left']) l.addWidget(self.sides['Right']) self.layout().addLayout(l) self.layout().addSpacing(20) # Body part self.layout().addWidget(QLabel("Limb: ", font=bold_font)) body_widget = QWidget(self) body_widget.setLayout(QGridLayout()) body_widget.layout().setContentsMargins(0, 0, 0, 0) lbl = QLabel() lbl.setPixmap( QPixmap( os.path.join(os.path.dirname(__file__), 'icons', 'HalfBody.png'))) body_widget.layout().addWidget(lbl, 0, 0, 20, 10) self.body_parts = {} cb = QCheckBox('') self.body_parts['Head'] = cb body_widget.layout().addWidget(cb, 1, 0, 1, 1) cb = QCheckBox('') self.body_parts['Arm'] = cb body_widget.layout().addWidget(cb, 8, 4, 1, 1) cb = QCheckBox('') self.body_parts['Hand'] = cb body_widget.layout().addWidget(cb, 10, 5, 1, 1) cb = QCheckBox('') self.body_parts['Leg'] = cb body_widget.layout().addWidget(cb, 14, 1, 1, 1) cb = QCheckBox('') self.body_parts['Foot'] = cb body_widget.layout().addWidget(cb, 18, 1, 1, 1) bt_clear = QPushButton("Uncheck all") bt_clear.clicked.connect(lambda: self.uncheck_all(self.body_parts)) body_widget.layout().addWidget(bt_clear, 20, 0, 1, 10) self.layout().addWidget(body_widget) self.layout().addSpacing(20) self.bt_map = QPushButton("Submit Response") self.bt_map.setEnabled(False) self.bt_map.setMinimumHeight(40) self.bt_map.clicked.connect(self.submit_map) self.layout().addWidget(self.bt_map) self.layout().addSpacing(10) bt_clear = QPushButton("Clear Channel") bt_clear.setMinimumHeight(20) bt_clear.clicked.connect(self.clear_data) self.layout().addWidget(bt_clear) self.layout().addStretch() # manual notes self.layout().addWidget(QLabel("Note: ", font=bold_font)) self.note_field = QTextEdit() self.note_field.setMaximumHeight(80) self.note_field.textChanged.connect(self.check_note) self.layout().addWidget(self.note_field) self.layout().addSpacing(10) self.bt_note = QPushButton("Submit Note") self.bt_note.setEnabled(False) self.bt_note.setMinimumHeight(20) self.bt_note.clicked.connect(self.submit_note) self.layout().addWidget(self.bt_note) self.layout().addStretch() def uncheck_all(self, d): for k, v in d.items(): v.setChecked(False) def check_channel_and_stim(self): if any([x.isChecked() for x in self.channels.values()]) and \ any([x.isChecked() for x in self.mapping_stimuli.values()]): self.bt_map.setEnabled(True) else: self.bt_map.setEnabled(False) def check_note(self): tmp = self.note_field.toPlainText() if len(tmp) > 0: self.bt_note.setEnabled(True) else: self.bt_note.setEnabled(False) def submit_map(self): out_dict = {} for chan in [s for s, c in self.channels.items() if c.isChecked()]: side = [s for s, c in self.sides.items() if c.isChecked()] if not side: side = ['Unspecified'] limb = [l for l, c in self.body_parts.items() if c.isChecked()] if not limb: limb = ['Unspecified'] out_dict[chan] = { 'Stimuli': [s for s, c in self.mapping_stimuli.items() if c.isChecked()], 'Sides': side, 'Limbs': limb, } out_string = json.dumps(out_dict) self.map_stream.push_sample([out_string]) # stims = '' # for stim, cb in self.mapping_stimuli.items(): # if cb.isChecked(): # if stim == "Custom": # stims += self.custom_stimulus.text() + ',' # else: # stims += stim + ',' # stims = stims.rstrip(',') # # limbs = '' # for limb, cb in self.body_parts.items(): # if cb.isChecked(): # limbs += limb + ',' # limbs = limbs.rstrip(',') # # for lbl, chan in self.channels.items(): # if chan.isChecked(): # if stims != '' and limbs != '': # self.map_stream.push_sample([lbl, stims + '__' + limbs + ';']) # else: # self.map_stream.push_sample([lbl, 'Clear']) def submit_note(self): out_string = self.note_field.toPlainText() self.map_stream.push_sample([out_string]) self.note_field.setPlainText("") def clear_data(self): out_dict = {} for chan in [s for s, c in self.channels.items() if c.isChecked()]: out_dict[chan] = 'Clear' out_string = json.dumps(out_dict) self.map_stream.push_sample([out_string]) def refresh_axes(self): pass def clear(self): pass
class SubjectWidget(QWidget): subject_change = Signal(int) def __init__(self, subject_settings): super(SubjectWidget, self).__init__() self.subject_enums = DBWrapper().return_enums('subject') subject_layout = QGridLayout(self) subject_layout.setColumnMinimumWidth(2, 60) subject_layout.addWidget(QLabel("Id: "), 0, 0, 1, 1) self.id_combo = QComboBox() self.id_combo.setEditable(True) self.id_combo.addItem('') self.id_combo.addItems(DBWrapper().list_all_subjects()) self.id_combo.currentIndexChanged.connect(self.load_subject) self.id_combo.lineEdit().editingFinished.connect(self.check_subject) subject_layout.addWidget(self.id_combo, 0, 1, 1, 4) subject_layout.addWidget(QLabel("Name: "), 1, 0, 1, 1) self.name_edit = QLineEdit() self.name_edit.setMaxLength(135) subject_layout.addWidget(self.name_edit, 1, 1, 1, 4) subject_layout.addWidget(QLabel("Sex: "), 2, 0, 1, 1) self.sex_combo = QComboBox() self.sex_combo.addItems(self.subject_enums['sex'] if 'sex' in self.subject_enums.keys() else []) self.sex_combo.setCurrentIndex(0) subject_layout.addWidget(self.sex_combo, 2, 1, 1, 1) subject_layout.addWidget((QLabel("Date of birth: ")), 3, 0, 1, 1) self.dob_calendar = QCalendarWidget() subject_layout.addWidget(self.dob_calendar, 3, 1, 1, 3) subject_layout.addWidget(QLabel("NSP file comment: "), 4, 0, 1, 1) self.file_comment = QTextEdit("") self.file_comment.setMaximumHeight(150) subject_layout.addWidget(self.file_comment, 4, 1, 1, 4) # Subject Settings self.subject_settings = subject_settings if not self.subject_settings: self.update_settings_from_db(-1) self.update_subject() def update_subject(self): self.name_edit.setText( self.read_dict_value(self.subject_settings, 'name')) self.id_combo.setCurrentText( self.read_dict_value(self.subject_settings, 'id')) self.sex_combo.setCurrentText( self.read_dict_value(self.subject_settings, 'sex')) dob = self.read_dict_value(self.subject_settings, 'birthday') if dob not in [None, '']: q_dob = QDate.fromString(dob, 'yyyy-MM-d') self.dob_calendar.setSelectedDate(q_dob) else: self.dob_calendar.setSelectedDate(QDate.currentDate()) def update_settings_from_db(self, idx): for key, value in DBWrapper().load_subject_details(idx).items(): self.subject_settings[key] = value def load_subject(self): # id is a unique and mandatory field self.check_subject() self.update_subject() def check_subject(self): # when changing the id in the combobox, can be modifying or entering an existing subject id. Check to load data # if so. curr_id = self.id_combo.currentText() if curr_id != '': self.update_settings_from_db(curr_id) self.subject_change.emit(self.subject_settings['subject_id']) else: self.update_settings_from_db(-1) self.subject_change.emit(-1) @staticmethod def read_dict_value(dictionary, value): return str(dictionary[value]) if value in dictionary.keys() else '' def to_dict(self): self.subject_settings['id'] = self.id_combo.currentText() self.subject_settings['name'] = self.name_edit.text() self.subject_settings['sex'] = self.sex_combo.currentText() self.subject_settings['birthday'] = self.dob_calendar.selectedDate( ).toPyDate() self.subject_settings['NSP_comment'] = self.file_comment.toPlainText()