class SerialPortSelector(QWidget): open_port = pyqtSignal(str, int) close_port = pyqtSignal() def __init__(self, *args): super(SerialPortSelector, self).__init__(*args) self.disabled = False self.init_ui() self.add_ports() def init_ui(self): layout = QHBoxLayout() self.setLayout(layout) self.ports_list_combobox = QComboBox() layout.addWidget(self.ports_list_combobox) self.baud_rate_combobox = QComboBox() self.baud_rate_combobox.addItems([ '300', '600', '1200', '2400', '4800', '9600', '19200', '38400', '43000', '56000', '57600', '115200' ]) self.baud_rate_combobox.setCurrentText('115200') self.baud_rate_combobox.setEditable(True) layout.addWidget(self.baud_rate_combobox) self.open_btn = QPushButton('打开') self.open_btn.clicked.connect(self.handle_open_port) layout.addWidget(self.open_btn) self.refresh_btn = QPushButton('刷新') self.refresh_btn.clicked.connect(self.add_ports) layout.addWidget(self.refresh_btn) def add_ports(self): self.ports_list_combobox.clear() for port in comports(False): self.ports_list_combobox.addItem(port.name, port) def handle_open_port(self): if self.disabled: self.close_port.emit() else: port = self.ports_list_combobox.currentText() if port == "": return baud_rate = int(self.baud_rate_combobox.currentText()) self.open_port.emit(port, baud_rate) def set_disable(self, b): self.disabled = b self.ports_list_combobox.setDisabled(b) self.baud_rate_combobox.setDisabled(b) if self.disabled: self.open_btn.setText('关闭') else: self.open_btn.setText('打开')
def initUI(self): self.lbl = QLabel('Ubuntu', self) combo = QComboBox(self) combo.addItem('Ubuntu') combo.addItem('Mandriva') combo.addItem('Fedora') combo.addItem('Arch') combo.addItem('Gentoo') combo.move(50, 50) self.lbl.move(50, 150) combo.textActivated[str].connect(self.onActivated) self.setGeometry(300, 300, 450, 400) self.setWindowTitle('QComboBox') self.show()
class SendText(QWidget): def __init__(self, parent=None): super(SendText, self).__init__(parent) self.init_ui() def init_ui(self): layout = QVBoxLayout() self.setLayout(layout) self.text = QTextEdit() layout.addWidget(self.text) layout2 = QHBoxLayout() layout.addLayout(layout2) self.combo = QComboBox() self.combo.addItem('不自动加换行符') self.combo.addItem('结尾自动加\\r\\n') self.combo.addItem('结尾自动加\\r') self.combo.addItem('结尾自动加\\n') self.send_btn = QPushButton('发送') layout2.addWidget(self.combo) layout2.addWidget(self.send_btn)
class App(QWidget): def __init__(self): super().__init__() self.setWindowTitle('GitHub Abuz!') self.setWindowIcon(QIcon('icon.png')) layout = QGridLayout() vl = QVBoxLayout() hl = QHBoxLayout() hl2 = QHBoxLayout() hl3 = QHBoxLayout() self.y = QComboBox() self.name = QLineEdit() self.email = QLineEdit() self.passw = QLineEdit() self.repo = QLineEdit() self.type = QLineEdit() self.fonts = QComboBox() self.err = QMessageBox() self.nc = QSpinBox() lbl = QLabel('Commits/day:') prev = QPushButton('Translate') invert = QPushButton('Invert') leggo = QPushButton('Do it') invert.clicked.connect(self.invert) leggo.clicked.connect(self.doit) prev.clicked.connect(self.textCheck) self.name.textChanged[str].connect(self.rmph) self.email.textChanged[str].connect(self.rmph) self.passw.textChanged[str].connect(self.rmph) self.type.textChanged[str].connect(self.rmph) self.repo.textChanged[str].connect(self.rmph) self.y.addItem('Year (default: last 52 weeks)') for yr in range(datetime.datetime.now().year + 5, datetime.datetime.now().year - 20, -1): self.y.addItem(str(yr)) self.fonts.addItems(os.listdir('Fonts')) self.name.setPlaceholderText('Committer name') self.email.setPlaceholderText('Committer email') self.passw.setPlaceholderText('Password') self.passw.setEchoMode(QLineEdit.EchoMode.Password) self.repo.setPlaceholderText('Link to repo') self.type.setPlaceholderText('Translate text to tile art!') self.nc.setMinimum(1) self.nc.setValue(1) self.err.setWindowIcon(QIcon('icon.png')) self.err.setWindowTitle('Error!') hl.addWidget(self.name) hl.addWidget(self.email) hl.addWidget(self.passw) hl3.addWidget(self.repo) hl3.addWidget(self.y) hl3.addWidget(lbl) hl3.addWidget(self.nc) hl2.addWidget(self.type) hl2.addWidget(self.fonts) hl2.addWidget(prev) hl2.addWidget(invert) vl.addLayout(hl) vl.addLayout(hl3) vl.addLayout(layout) vl.addLayout(hl2) vl.addWidget(leggo) self.setLayout(vl) self.checkM = [list() for i in range(7)] for i in range(7): for j in range(52): m = QCheckBox() layout.addWidget(m, i, j) self.checkM[i].append(m) def rmph(self): self.setStyleSheet(''' QLineEdit[text=""]{ border: 1px #30363d; border-radius: 3px; padding: 1px 18px 1px 3px; background-color: #1c2128; color: #8b949e; } QLineEdit{ border: 1px #30363d; border-radius: 3px; padding: 1px 18px 1px 3px; background-color: #1c2128; color: #f0f6fc; } QLineEdit:hover { border: 0.5px solid #c9d1d9; } QLineEdit:focus { border: 1px solid #c9d1d9; }''') def getActiveDates(self, dates): ad = [] for i in range(7): for j in range(52): if self.checkM[i][j].isChecked(): ad.append(dates[i][j]) return ad def doit(self): try: year = int(self.y.currentText()) dates = self.getActiveDates(getDates(year)) except: dates = self.getActiveDates(getDates()) author = git.Actor(self.name.text(), self.email.text()) if not self.name.text() or not self.email.text(): self.err.setText('Did you enter your name and email? 🙄') self.err.exec() return repurl = "https://" + self.name.text() + ":" + self.passw.text( ) + "@" + self.repo.text()[8:] repname = repurl.split('/')[-1].split('.')[0] if not os.path.isdir(repname): try: git.cmd.Git().clone(repurl) except: self.err.setText( 'Could not clone the repo. Ensure that the remote repo exists and that you have access to it.' ) self.err.exec() return rep = git.Repo.init(repname) for date in dates: for n in range(self.nc.value()): rep.index.commit("committed for the lullzz!!", author=author, committer=author, author_date=date.isoformat()) try: rep.remotes.origin.set_url(repurl) except: rep.create_remote('origin', repurl) try: rep.remotes.origin.push() except: self.err.setText( 'Error pushing. Verify you have permissions to push to the repo and that the given credentials are correct' ) self.err.exec() return result = QMessageBox() text = f"Created {len(dates)*2} commits as {self.name.text()} <{self.email.text()}> in {repname} : {self.repo.text()}" result.setWindowIcon(QIcon('icon.png')) result.setWindowTitle('All Done!') result.setText(text) result.exec() os.remove(repname) def textCheck(self): for r in self.checkM: for m in r: m.setChecked(False) text_to_render = self.type.text() font = Font(os.path.join('Fonts', self.fonts.currentText()), 8) try: text = repr(font.render_text(text_to_render, 52, 7)) text_by_weekday = text.split('\n') for i in range(7): for j in range(51): if text_by_weekday[i][j] == '#': self.checkM[i][j].setChecked(True) except: self.err.setText('You typed too long :(') self.err.exec() def invert(self): for r in self.checkM: for m in r: if m.isChecked(): m.setChecked(False) else: m.setChecked(True)
class NodeWidget(QWidget): def __init__(self, ID=0): super(NodeWidget, self).__init__() laytout = QHBoxLayout() self.input_id = QLineEdit(str(ID)) self.input_id.setFixedWidth(30) self.input_text = QLineEdit("") self.input_text.setFixedHeight(20) self.input_text.setFixedWidth(400) self.input_type = QComboBox() self.input_type.addItem('开始/结束') self.input_type.addItem('流程') self.input_type.addItem('判定') self.input_type.addItem('None') self.input_type.setFixedWidth(80) self.input_x = QLineEdit("0") self.input_x.setFixedWidth(30) self.input_y = QLineEdit("0") self.input_y.setFixedWidth(30) self.input_link1 = QLineEdit("") self.input_link1.setFixedWidth(30) self.input_link2 = QLineEdit("") self.input_link2.setFixedWidth(30) laytout.addWidget(self.input_id) laytout.addWidget(self.input_text) laytout.addWidget(self.input_type) laytout.addWidget(self.input_x) laytout.addWidget(self.input_y) laytout.addWidget(self.input_link1) laytout.addWidget(self.input_link2) laytout.addStretch() laytout.setContentsMargins(0, 0, 0, 0) self.setLayout(laytout) self.data = [] def get_node_info(self): print("读取数据") self.data.append(int(self.input_id.text())) self.data.append(self.input_text.text()) if '开始/结束' == self.input_type.currentText(): self.data.append(2) print("开始/结束") elif '流程' == self.input_type.currentText(): self.data.append(0) print("流程") elif '判定' == self.input_type.currentText(): self.data.append(1) print("判定") else: print("None") self.data.append(3) print("===1===") self.data.append(int(self.input_x.text())) self.data.append(int(self.input_y.text())) if self.input_link1.text(): self.data.append(int(self.input_link1.text())) if self.input_link2.text(): self.data.append(int(self.input_link2.text())) print(self.data) return self.data
class DataGUI(QWidget): def __init__(self): super().__init__() self.experiment_file_name = None self.experiment_file_directory = None self.data_directory = None self.max_rois = 50 self.roi_type = 'freehand' self.roi_radius = None self.existing_roi_set_paths = {} self.current_roi_index = 0 self.current_z_slice = 0 self.current_channel = 1 # index self.image_series_name = '' self.series_number = None self.roi_response = [] self.roi_mask = [] self.roi_path = [] self.roi_image = None self.roi_path_list = [] self.blank_image = np.zeros((1, 1)) self.colors = [ mcolors.to_rgb(x) for x in list(mcolors.TABLEAU_COLORS)[:20] ] self.initUI() def initUI(self): self.grid = QGridLayout(self) self.file_control_grid = QGridLayout() self.file_control_grid.setSpacing(3) self.grid.addLayout(self.file_control_grid, 0, 0) self.file_tree_grid = QGridLayout() self.file_tree_grid.setSpacing(3) self.grid.addLayout(self.file_tree_grid, 1, 0) self.group_control_grid = QGridLayout() self.group_control_grid.setSpacing(3) self.grid.addLayout(self.group_control_grid, 0, 1) self.attribute_grid = QGridLayout() self.attribute_grid.setSpacing(3) self.grid.addLayout(self.attribute_grid, 1, 1) self.roi_control_grid = QGridLayout() self.roi_control_grid.setSpacing(3) self.grid.addLayout(self.roi_control_grid, 0, 2) self.plot_grid = QGridLayout() self.plot_grid.setSpacing(3) self.grid.addLayout(self.plot_grid, 1, 2) # # # # File control browser: # # # # # # # # (0,0) loadButton = QPushButton("Load expt. file", self) loadButton.clicked.connect(self.selectDataFile) # Label with current expt file self.currentExperimentLabel = QLabel('') self.file_control_grid.addWidget(loadButton, 0, 0) self.file_control_grid.addWidget(self.currentExperimentLabel, 1, 0) directoryButton = QPushButton("Select data directory", self) directoryButton.clicked.connect(self.selectDataDirectory) self.file_control_grid.addWidget(directoryButton, 0, 1) self.data_directory_display = QLabel('') self.data_directory_display.setFont(QtGui.QFont('SansSerif', 8)) self.file_control_grid.addWidget(self.data_directory_display, 1, 1) # Attach metadata to file attachDatabutton = QPushButton("Attach metadata to file", self) attachDatabutton.clicked.connect(self.attachData) self.file_control_grid.addWidget(attachDatabutton, 2, 0, 1, 2) # Select image data file selectImageDataFileButton = QPushButton("Select image data file", self) selectImageDataFileButton.clicked.connect(self.selectImageDataFile) self.file_control_grid.addWidget(selectImageDataFileButton, 3, 0, 1, 2) # # # # File tree: # # # # # # # # (1,0) self.groupTree = QTreeWidget(self) self.groupTree.setHeaderHidden(True) self.groupTree.itemClicked.connect(self.onTreeItemClicked) self.file_tree_grid.addWidget(self.groupTree, 3, 0, 2, 7) # # # # Group control: # # # # # # # # (0, 1) deleteGroupButton = QPushButton("Delete selected group", self) deleteGroupButton.clicked.connect(self.deleteSelectedGroup) self.group_control_grid.addWidget(deleteGroupButton, 0, 0, 1, 2) # File name display self.currentImageFileNameLabel = QLabel('') self.group_control_grid.addWidget(self.currentImageFileNameLabel, 1, 0) # Channel drop down ch_label = QLabel('Channel:') self.ChannelComboBox = QComboBox(self) self.ChannelComboBox.addItem("1") self.ChannelComboBox.addItem("0") self.ChannelComboBox.activated.connect(self.selectChannel) self.group_control_grid.addWidget(ch_label, 2, 0) self.group_control_grid.addWidget(self.ChannelComboBox, 2, 1) # # # # Attribute table: # # # # # # # # (1, 1) self.tableAttributes = QTableWidget() self.tableAttributes.setStyleSheet("") self.tableAttributes.setColumnCount(2) self.tableAttributes.setObjectName("tableAttributes") self.tableAttributes.setRowCount(0) item = QTableWidgetItem() font = QtGui.QFont() font.setPointSize(10) item.setFont(font) item.setBackground(QtGui.QColor(121, 121, 121)) brush = QtGui.QBrush(QtGui.QColor(91, 91, 91)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) item.setForeground(brush) self.tableAttributes.setHorizontalHeaderItem(0, item) item = QTableWidgetItem() item.setBackground(QtGui.QColor(123, 123, 123)) brush = QtGui.QBrush(QtGui.QColor(91, 91, 91)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) item.setForeground(brush) self.tableAttributes.setHorizontalHeaderItem(1, item) self.tableAttributes.horizontalHeader().setCascadingSectionResizes( True) self.tableAttributes.horizontalHeader().setHighlightSections(False) self.tableAttributes.horizontalHeader().setSortIndicatorShown(True) self.tableAttributes.horizontalHeader().setStretchLastSection(True) self.tableAttributes.verticalHeader().setVisible(False) self.tableAttributes.verticalHeader().setHighlightSections(False) item = self.tableAttributes.horizontalHeaderItem(0) item.setText("Attribute") item = self.tableAttributes.horizontalHeaderItem(1) item.setText("Value") self.tableAttributes.itemChanged.connect(self.update_attrs_to_file) self.attribute_grid.addWidget(self.tableAttributes, 3, 0, 1, 8) # # # # Roi control # # # # # # # # (0, 2) # ROI type drop-down self.RoiTypeComboBox = QComboBox(self) self.RoiTypeComboBox.addItem("freehand") radii = [1, 2, 3, 4, 6, 8] for radius in radii: self.RoiTypeComboBox.addItem("circle:" + str(radius)) self.RoiTypeComboBox.activated.connect(self.selectRoiType) self.roi_control_grid.addWidget(self.RoiTypeComboBox, 0, 0) # Clear all ROIs button self.clearROIsButton = QPushButton("Clear ROIs", self) self.clearROIsButton.clicked.connect(self.clearRois) self.roi_control_grid.addWidget(self.clearROIsButton, 0, 2) # Response display type dropdown self.RoiResponseTypeComboBox = QComboBox(self) self.RoiResponseTypeComboBox.addItem("RawTrace") self.RoiResponseTypeComboBox.addItem("TrialAverage") self.RoiResponseTypeComboBox.addItem("TrialResponses") self.RoiResponseTypeComboBox.addItem("TrialAverageDFF") self.roi_control_grid.addWidget(self.RoiResponseTypeComboBox, 2, 2) # ROIset file name line edit box self.defaultRoiSetName = "roi_set_name" self.le_roiSetName = QLineEdit(self.defaultRoiSetName) self.roi_control_grid.addWidget(self.le_roiSetName, 1, 1) # Save ROIs button self.saveROIsButton = QPushButton("Save ROIs", self) self.saveROIsButton.clicked.connect(self.saveRois) self.roi_control_grid.addWidget(self.saveROIsButton, 1, 0) # Load ROI set combobox self.loadROIsComboBox = QComboBox(self) self.loadROIsComboBox.addItem("(load existing ROI set)") self.loadROIsComboBox.activated.connect(self.selectedExistingRoiSet) self.roi_control_grid.addWidget(self.loadROIsComboBox, 1, 2) self.updateExistingRoiSetList() # Delete current roi button self.deleteROIButton = QPushButton("Delete ROI", self) self.deleteROIButton.clicked.connect(self.deleteRoi) self.roi_control_grid.addWidget(self.deleteROIButton, 2, 0) # Current roi slider self.roiSlider = QSlider(QtCore.Qt.Orientation.Horizontal, self) self.roiSlider.setMinimum(0) self.roiSlider.setMaximum(self.max_rois) self.roiSlider.valueChanged.connect(self.sliderUpdated) self.roi_control_grid.addWidget(self.roiSlider, 2, 1, 1, 1) ctx = plt.rc_context({ 'xtick.major.size': 1, 'axes.spines.top': False, 'axes.spines.right': False, 'xtick.labelsize': 'xx-small', 'ytick.labelsize': 'xx-small', 'xtick.major.size': 1.0, 'ytick.major.size': 1.0, 'xtick.major.pad': 1.0, 'ytick.major.pad': 1.0 }) with ctx: self.responseFig = plt.figure(frameon=False, layout='constrained') self.responsePlot = self.responseFig.add_subplot(111) self.responseCanvas = FigureCanvas(self.responseFig) self.responseCanvas.draw_idle() self.plot_grid.addWidget(self.responseCanvas, 0, 0) # # # # Image canvas # # # # # # # # (1, 2) self.roi_fig = plt.figure() self.roi_ax = self.roi_fig.add_subplot(111) self.roi_canvas = FigureCanvas(self.roi_fig) self.toolbar = NavigationToolbar(self.roi_canvas, self) self.roi_ax.set_aspect('equal') self.roi_ax.set_axis_off() self.plot_grid.addWidget(self.toolbar, 1, 0) self.plot_grid.addWidget(self.roi_canvas, 2, 0) self.plot_grid.setRowStretch(0, 1) self.plot_grid.setRowStretch(1, 3) self.plot_grid.setRowStretch(2, 3) # Current z slice slider self.zSlider = QSlider(QtCore.Qt.Orientation.Horizontal, self) self.zSlider.setMinimum(0) self.zSlider.setMaximum(50) self.zSlider.setValue(0) self.zSlider.valueChanged.connect(self.zSliderUpdated) self.plot_grid.addWidget(self.zSlider, 3, 0) self.roi_fig.tight_layout() self.setWindowTitle('Visanalysis') self.setGeometry(200, 200, 1200, 600) self.show() def _populateTree(self, widget, dict): widget.clear() self.fill_item(widget.invisibleRootItem(), dict) def fill_item(self, item, value): item.setExpanded(True) if type(value) is dict: for key, val in sorted(value.items()): child = QTreeWidgetItem() child.setText(0, key) item.addChild(child) self.fill_item(child, val) elif type(value) is list: for val in value: child = QTreeWidgetItem() item.addChild(child) if type(val) is dict: child.setText(0, '[dict]') self.fill_item(child, val) elif type(val) is list: child.setText(0, '[list]') self.fill_item(child, val) else: child.setText(0, val) child.setExpanded(True) else: child = QTreeWidgetItem() child.setText(0, value) item.addChild(child) def onTreeItemClicked(self, item, column): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') group_path = h5io.getPathFromTreeItem( self.groupTree.selectedItems()[0]) self.clearRois() self.series_number = None if 'series_' in group_path: self.series_number = int( group_path.split('series_')[-1].split('/')[0]) if self.plugin.dataIsAttached(file_path, self.series_number): self.plugin.updateImagingDataObject( self.experiment_file_directory, self.experiment_file_name, self.series_number) # look for image_file_name or ask user to select it if self.data_directory is not None: image_file_name = h5io.readImageFileName( file_path, self.series_number) if image_file_name is None or image_file_name == '': image_file_path, _ = QFileDialog.getOpenFileName( self, "Select image file") print('User selected image file at {}'.format( image_file_path)) image_file_name = os.path.split(image_file_path)[-1] self.data_directory = os.path.split( image_file_path)[:-1][0] h5io.attachImageFileName(file_path, self.series_number, image_file_name) print('Attached image_file_name {} to series {}'.format( image_file_name, self.series_number)) print('Data directory is {}'.format(self.data_directory)) self.image_file_name = image_file_name self.currentImageFileNameLabel.setText(self.image_file_name) else: # clicked part of the tree upstream of any series self.series_number = None if item.parent() is not None: if item.parent().text( column) == 'rois': # selected existing roi group roi_set_name = item.text(column) # print('Selected roi set {} from series {}'.format(roi_set_name, self.series_number)) self.le_roiSetName.setText(roi_set_name) roi_set_path = h5io.getPathFromTreeItem( self.groupTree.selectedItems()[0]) self.loadRois(roi_set_path) self.redrawRoiTraces() if group_path != '': attr_dict = h5io.getAttributesFromGroup(file_path, group_path) editable_values = True # user can edit metadata self.populate_attrs(attr_dict=attr_dict, editable_values=editable_values) # show roi image if self.series_number is not None: # Clicked on node of the tree associated with a single series if self.data_directory is not None: # user has selected a raw data directory if self.plugin.dataIsAttached(file_path, self.series_number): self.plugin.updateImageSeries( data_directory=self.data_directory, image_file_name=self.image_file_name, series_number=self.series_number, channel=self.current_channel) self.roi_image = self.plugin.mean_brain self.zSlider.setValue(0) self.zSlider.setMaximum(self.roi_image.shape[2] - 1) self.redrawRoiTraces() else: print('Attach metadata to file before drawing rois') else: print('Select a data directory before drawing rois') # # # TEST # # # memory_usage = psutil.Process(os.getpid()).memory_info().rss * 10**-9 print('Current Memory Usage: {:.2f}GB'.format(memory_usage)) sys.stdout.flush() # # # TEST # # # def updateExistingRoiSetList(self): if self.experiment_file_name is not None: file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') self.existing_roi_set_paths = self.plugin.getRoiSetPaths( file_path) # dictionary of name: full path self.loadROIsComboBox.clear() for r_path in self.existing_roi_set_paths: self.loadROIsComboBox.addItem(r_path) self.show() def selectedExistingRoiSet(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') roi_set_key = self.loadROIsComboBox.currentText() roi_set_path = self.existing_roi_set_paths[roi_set_key] _, _, self.roi_path, self.roi_mask = self.plugin.loadRoiSet( file_path, roi_set_path) if self.series_number is not None: self.roi_response = [] for new_path in self.roi_path: new_roi_resp = self.plugin.getRoiDataFromPath( roi_path=new_path) self.roi_response.append(new_roi_resp) # update slider to show most recently drawn roi response self.current_roi_index = len(self.roi_response) - 1 self.roiSlider.setValue(self.current_roi_index) # Update figures self.redrawRoiTraces() def selectDataFile(self): filePath, _ = QFileDialog.getOpenFileName( self, "Open experiment (hdf5) file") self.experiment_file_name = os.path.split(filePath)[1].split('.')[0] self.experiment_file_directory = os.path.split(filePath)[0] if self.experiment_file_name != '': self.currentExperimentLabel.setText(self.experiment_file_name) self.initializeDataAnalysis() self.populateGroups() self.updateExistingRoiSetList() def selectDataDirectory(self): filePath = str( QFileDialog.getExistingDirectory(self, "Select data directory")) self.data_directory = filePath self.data_directory_display.setText('..' + self.data_directory[-24:]) def initializeDataAnalysis(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') data_type = h5io.getDataType(file_path) # Load plugin based on Rig name in hdf5 file if data_type == 'Bruker': from visanalysis.plugin import bruker self.plugin = bruker.BrukerPlugin() elif data_type == 'AODscope': from visanalysis.plugin import aodscope self.plugin = aodscope.AodScopePlugin() else: self.plugin = h5io.BasePlugin() self.plugin.parent_gui = self # # # TEST # # # memory_usage = psutil.Process(os.getpid()).memory_info().rss * 10**-9 print('Current memory usage: {:.2f}GB'.format(memory_usage)) sys.stdout.flush() # # # TEST # # # def attachData(self): if self.data_directory is not None: file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') self.plugin.attachData(self.experiment_file_name, file_path, self.data_directory) print('Data attached') else: print('Select a data directory before attaching new data') def selectImageDataFile(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') image_file_path, _ = QFileDialog.getOpenFileName( self, "Select image file") print('User selected image file at {}'.format(image_file_path)) self.image_file_name = os.path.split(image_file_path)[-1] self.data_directory = os.path.split(image_file_path)[:-1][0] h5io.attachImageFileName(file_path, self.series_number, self.image_file_name) print('Attached image_file_name {} to series {}'.format( self.image_file_name, self.series_number)) print('Data directory is {}'.format(self.data_directory)) self.currentImageFileNameLabel.setText(self.image_file_name) # show roi image if self.series_number is not None: if self.data_directory is not None: # user has selected a raw data directory self.plugin.updateImageSeries( data_directory=self.data_directory, image_file_name=self.image_file_name, series_number=self.series_number, channel=self.current_channel) self.roi_image = self.plugin.mean_brain self.zSlider.setValue(0) self.zSlider.setMaximum(self.roi_image.shape[2] - 1) self.redrawRoiTraces() else: print('Select a data directory before drawing rois') def deleteSelectedGroup(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') group_path = h5io.getPathFromTreeItem( self.groupTree.selectedItems()[0]) group_name = group_path.split('/')[-1] buttonReply = QMessageBox.question( self, 'Delete series', "Are you sure you want to delete group {}?".format(group_name), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) if buttonReply == QMessageBox.StandardButton.Yes: h5io.deleteGroup(file_path=file_path, group_path=group_path) print('Deleted group {}'.format(group_name)) self.updateExistingRoiSetList() self.populateGroups() else: print('Delete aborted') def populateGroups(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') self.group_dset_dict = h5io.getHierarchy(file_path) self._populateTree(self.groupTree, self.group_dset_dict) def populate_attrs(self, attr_dict=None, editable_values=False): """Populate attribute for currently selected group.""" self.tableAttributes.blockSignals( True) # block udpate signals for auto-filled forms self.tableAttributes.setRowCount(0) self.tableAttributes.setColumnCount(2) self.tableAttributes.setSortingEnabled(False) if attr_dict: for num, key in enumerate(attr_dict): self.tableAttributes.insertRow(self.tableAttributes.rowCount()) key_item = QTableWidgetItem(key) key_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) self.tableAttributes.setItem(num, 0, key_item) val_item = QTableWidgetItem(str(attr_dict[key])) if editable_values: val_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEditable | QtCore.Qt.ItemFlag.ItemIsEnabled) else: val_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) self.tableAttributes.setItem(num, 1, val_item) self.tableAttributes.blockSignals(False) def update_attrs_to_file(self, item): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') group_path = h5io.getPathFromTreeItem( self.groupTree.selectedItems()[0]) attr_key = self.tableAttributes.item(item.row(), 0).text() attr_val = item.text() # update attr in file h5io.changeAttribute(file_path=file_path, group_path=group_path, attr_key=attr_key, attr_val=attr_val) print('Changed attr {} to = {}'.format(attr_key, attr_val)) # %% # # # # # # # # ROI SELECTOR WIDGET # # # # # # # # # # # # # # # # # # # def refreshLassoWidget(self, keep_paths=False): self.roi_ax.clear() init_lasso = False if self.roi_image is not None: if len(self.roi_mask) > 0: newImage = plot_tools.overlayImage( self.roi_image[:, :, self.current_z_slice], self.roi_mask, 0.5, self.colors, z=self.current_z_slice) else: newImage = self.roi_image[:, :, self.current_z_slice] self.roi_ax.imshow(newImage, cmap=cm.gray) init_lasso = True else: self.roi_ax.imshow(self.blank_image) self.roi_ax.set_axis_off() self.roi_canvas.draw() if not keep_paths: self.roi_path_list = [] if init_lasso: if self.roi_type == 'circle': self.lasso_1 = EllipseSelector(self.roi_ax, onselect=self.newEllipse, button=1) elif self.roi_type == 'freehand': self.lasso_1 = LassoSelector(self.roi_ax, onselect=self.newFreehand, button=1) self.lasso_2 = LassoSelector(self.roi_ax, onselect=self.appendFreehand, button=3) else: print( 'Warning ROI type not recognized. Choose circle or freehand' ) def newFreehand(self, verts): new_roi_path = path.Path(verts) new_roi_path.z_level = self.zSlider.value() new_roi_path.channel = self.current_channel self.updateRoiSelection([new_roi_path]) def appendFreehand(self, verts): print('Appending rois, hit Enter/Return to finish') new_roi_path = path.Path(verts) new_roi_path.z_level = self.zSlider.value() new_roi_path.channel = self.current_channel self.roi_path_list.append(new_roi_path) def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: if np.any([ event.key() == QtCore.Qt.Key.Key_Return, event.key() == QtCore.Qt.Key.Key_Enter ]): if len(self.roi_path_list) > 0: event.accept() self.updateRoiSelection(self.roi_path_list) else: event.ignore() else: event.ignore() else: event.ignore() def newEllipse(self, pos1, pos2, definedRadius=None): x1 = np.round(pos1.xdata) x2 = np.round(pos2.xdata) y1 = np.round(pos1.ydata) y2 = np.round(pos2.ydata) radiusX = np.sqrt((x1 - x2)**2) / 2 radiusY = np.sqrt((y1 - y2)**2) / 2 if self.roi_radius is not None: radiusX = self.roi_radius center = (np.round((x1 + x2) / 2), np.round((y1 + y2) / 2)) new_roi_path = path.Path.circle(center=center, radius=radiusX) new_roi_path.z_level = self.zSlider.value() new_roi_path.channel = self.current_channel self.updateRoiSelection([new_roi_path]) def updateRoiSelection(self, new_roi_path): mask = self.plugin.getRoiMaskFromPath(new_roi_path) new_roi_resp = self.plugin.getRoiDataFromPath(roi_path=new_roi_path) if mask.sum() == 0: print('No pixels in the roi you just drew') return # update list of roi data self.roi_mask.append(mask) self.roi_path.append(new_roi_path) # list of lists of paths self.roi_response.append(new_roi_resp) # update slider to show most recently drawn roi response self.current_roi_index = len(self.roi_response) - 1 self.roiSlider.setValue(self.current_roi_index) # Update figures self.redrawRoiTraces() def sliderUpdated(self): self.current_roi_index = self.roiSlider.value() self.redrawRoiTraces() def zSliderUpdated(self): self.current_z_slice = self.zSlider.value() if self.roi_image is not None: self.refreshLassoWidget(keep_paths=True) def redrawRoiTraces(self): self.clearRoiArtists() if self.current_roi_index < len(self.roi_response): current_raw_trace = np.squeeze( self.roi_response[self.current_roi_index]) fxn_name = self.RoiResponseTypeComboBox.currentText() display_trace = getattr(self.plugin, 'getRoiResponse_{}'.format(fxn_name))( [current_raw_trace]) self.responsePlot.plot(display_trace, color=self.colors[self.current_roi_index], linewidth=1, alpha=0.5) self.responsePlot.set_xlim([0, len(display_trace)]) y_min = np.nanmin(display_trace) y_max = np.nanmax(display_trace) self.responsePlot.set_ylim([y_min, y_max]) self.responseCanvas.draw() self.refreshLassoWidget(keep_paths=False) # %% # # # # # # # # LOADING / SAVING / COMPUTING ROIS # # # # # # # # # # # # # # # # # # # def loadRois(self, roi_set_path): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') self.roi_response, self.roi_image, self.roi_path, self.roi_mask = self.plugin.loadRoiSet( file_path, roi_set_path) self.zSlider.setValue(0) self.zSlider.setMaximum(self.roi_image.shape[2] - 1) def saveRois(self): file_path = os.path.join(self.experiment_file_directory, self.experiment_file_name + '.hdf5') roi_set_name = self.le_roiSetName.text() if roi_set_name in h5io.getAvailableRoiSetNames( file_path, self.series_number): buttonReply = QMessageBox.question( self, 'Overwrite roi set', "Are you sure you want to overwrite roi set: {}?".format( roi_set_name), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) if buttonReply == QMessageBox.StandardButton.Yes: self.plugin.saveRoiSet(file_path, series_number=self.series_number, roi_set_name=roi_set_name, roi_mask=self.roi_mask, roi_response=self.roi_response, roi_image=self.roi_image, roi_path=self.roi_path) print('Saved roi set {} to series {}'.format( roi_set_name, self.series_number)) self.populateGroups() self.updateExistingRoiSetList() else: print('Overwrite aborted - pick a unique roi set name') else: self.plugin.saveRoiSet(file_path, series_number=self.series_number, roi_set_name=roi_set_name, roi_mask=self.roi_mask, roi_response=self.roi_response, roi_image=self.roi_image, roi_path=self.roi_path) print('Saved roi set {} to series {}'.format( roi_set_name, self.series_number)) self.populateGroups() self.updateExistingRoiSetList() def deleteRoi(self): if self.current_roi_index < len(self.roi_response): self.roi_mask.pop(self.current_roi_index) self.roi_response.pop(self.current_roi_index) self.roi_path.pop(self.current_roi_index) self.roiSlider.setValue(self.current_roi_index - 1) self.redrawRoiTraces() def clearRois(self): self.roi_mask = [] self.roi_response = [] self.roi_path = [] self.roi_image = None self.clearRoiArtists() self.redrawRoiTraces() self.roi_ax.clear() def clearRoiArtists(self): for artist in self.responsePlot.lines + self.responsePlot.collections: artist.remove() def selectRoiType(self): self.roi_type = self.RoiTypeComboBox.currentText().split(':')[0] if 'circle' in self.RoiTypeComboBox.currentText(): self.roi_radius = int( self.RoiTypeComboBox.currentText().split(':')[1]) else: self.roi_radius = None self.redrawRoiTraces() def selectChannel(self): self.current_channel = int(self.ChannelComboBox.currentText()) # show roi image if self.series_number is not None: if self.data_directory is not None: # user has selected a raw data directory self.plugin.updateImageSeries( data_directory=self.data_directory, image_file_name=self.image_file_name, series_number=self.series_number, channel=self.current_channel) self.roi_image = self.plugin.mean_brain self.zSlider.setValue(0) self.zSlider.setMaximum(self.roi_image.shape[2] - 1) self.redrawRoiTraces() else: print('Select a data directory before drawing rois')
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setFixedSize(800, 410) self.setWindowTitle("PyLX16A Servo Testing Software") self.port_selection_box = QComboBox(self) self.port_selection_box.setFixedSize(200, 27) self.port_selection_box.move(30, 65) port_selection_box_label = QLabel("Select Port:", self) port_selection_box_label.move(30, 35) self.port_selection_box_refresh_button = QPushButton("Refresh", self) self.port_selection_box_refresh_button.setFixedSize(60, 23) self.port_selection_box_refresh_button.move(170, 38) self.id_selection_box = QListWidget(self) self.id_selection_box.setFixedSize(200, 200) self.id_selection_box.move(30, 135) id_selection_box_label = QLabel("Connected Servos:", self) id_selection_box_label.setFixedWidth(200) id_selection_box_label.move(30, 105) self.id_selection_box_refresh_button = QPushButton("Refresh", self) self.id_selection_box_refresh_button.setFixedSize(60, 23) self.id_selection_box_refresh_button.move(170, 108) self.set_id_line_edit = QLineEdit(self) self.set_id_line_edit.setFixedSize(50, 27) self.set_id_line_edit.move(80, 355) set_id_line_edit_label = QLabel("Set ID:", self) set_id_line_edit_label.move(30, 355) set_id_line_edit_label.setFixedSize(50, 27) self.set_id_button = QPushButton("Change ID!", self) self.set_id_button.setFixedSize(85, 27) self.set_id_button.move(145, 355) self.position_slider = QSlider(Qt.Orientation.Horizontal, self) self.position_slider.setMinimum(0) self.position_slider.setMaximum(240) self.position_slider.setFixedWidth(200) self.position_slider.move(300, 55) self.position_slider_readout = QLabel("0.00°", self) self.position_slider_readout.setFixedWidth(50) self.position_slider_readout.move(450, 30) self.position_slider_readout.setAlignment( Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) position_slider_label = QLabel("Angle (degrees):", self) position_slider_label.move(300, 30) self.position_offset_slider = QSlider(Qt.Orientation.Horizontal, self) self.position_offset_slider.setMinimum(-30) self.position_offset_slider.setMaximum(30) self.position_offset_slider.setFixedWidth(200) self.position_offset_slider.move(300, 125) self.position_offset_slider_readout = QLabel("0.00°", self) self.position_offset_slider_readout.setFixedWidth(50) self.position_offset_slider_readout.move(450, 100) self.position_offset_slider_readout.setAlignment( Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) position_offset_slider_label = QLabel("Angle offset (degrees):", self) position_offset_slider_label.setFixedWidth(200) position_offset_slider_label.move(300, 100) self.angle_lower_limit_textentry = QLineEdit(self) self.angle_lower_limit_textentry.setFixedWidth(50) self.angle_lower_limit_textentry.move(450, 175) self.angle_lower_limit_textentry.setValidator( QIntValidator(0, 240, self)) self.angle_upper_limit_textentry = QLineEdit(self) self.angle_upper_limit_textentry.setFixedWidth(50) self.angle_upper_limit_textentry.move(450, 210) self.angle_upper_limit_textentry.setValidator( QIntValidator(0, 240, self)) self.angle_lower_limit_textentry_label = QLabel( "Lower Limit (degrees):", self) self.angle_lower_limit_textentry_label.move(300, 175) self.angle_lower_limit_textentry_label.setFixedWidth(150) self.angle_upper_limit_textentry_label = QLabel( "Upper Limit (degrees):", self) self.angle_upper_limit_textentry_label.move(300, 210) self.angle_upper_limit_textentry_label.setFixedWidth(150) self.vin_lower_limit_textentry = QLineEdit(self) self.vin_lower_limit_textentry.setFixedWidth(50) self.vin_lower_limit_textentry.move(450, 265) self.vin_lower_limit_textentry.setValidator( QIntValidator(4500, 12000, self)) self.vin_upper_limit_textentry = QLineEdit(self) self.vin_upper_limit_textentry.setFixedWidth(50) self.vin_upper_limit_textentry.move(450, 300) self.vin_upper_limit_textentry.setValidator( QIntValidator(4500, 12000, self)) self.vin_lower_limit_textentry_label = QLabel( "Voltage Lower Limit (mV):", self) self.vin_lower_limit_textentry_label.move(300, 265) self.vin_lower_limit_textentry_label.setFixedWidth(150) self.vin_upper_limit_textentry_label = QLabel( "Voltage Upper Limit (mV):", self) self.vin_upper_limit_textentry_label.move(300, 300) self.vin_upper_limit_textentry_label.setFixedWidth(150) self.temp_limit_textentry = QLineEdit(self) self.temp_limit_textentry.setFixedWidth(50) self.temp_limit_textentry.move(450, 355) self.temp_limit_textentry.setValidator(QIntValidator(50, 100, self)) self.temp_limit_textentry_label = QLabel("Temp Limit (°C):", self) self.temp_limit_textentry_label.move(300, 355) self.temp_limit_textentry_label.setFixedWidth(150) self.servo_mode_radio_button = QRadioButton("Servo Mode", self) self.servo_mode_radio_button.move(565, 50) self.motor_mode_radio_button = QRadioButton("Motor Mode", self) self.motor_mode_radio_button.move(565, 75) self.motor_speed_slider = QSlider(Qt.Orientation.Horizontal, self) self.motor_speed_slider.setMinimum(-1000) self.motor_speed_slider.setMaximum(1000) self.motor_speed_slider.setFixedWidth(200) self.motor_speed_slider.move(565, 125) motor_speed_slider_label = QLabel("Motor Speed:", self) motor_speed_slider_label.move(565, 100) self.torque_enabled_checkbox = QCheckBox("Torque Enabled", self) self.torque_enabled_checkbox.move(565, 175) self.torque_enabled_checkbox.setFixedWidth(200) self.led_enabled_checkbox = QCheckBox("LED Enabled", self) self.led_enabled_checkbox.move(565, 210) self.led_enabled_checkbox.setFixedWidth(200) self.led_over_temp_checkbox = QCheckBox("LED Over Temperature", self) self.led_over_temp_checkbox.move(565, 258) self.led_over_temp_checkbox.setFixedWidth(200) self.led_over_voltage_checkbox = QCheckBox("LED Over Voltage", self) self.led_over_voltage_checkbox.move(565, 283) self.led_over_voltage_checkbox.setFixedWidth(200) self.led_rotor_locked_checkbox = QCheckBox("LED Rotor Locked", self) self.led_rotor_locked_checkbox.move(565, 308) self.led_rotor_locked_checkbox.setFixedWidth(200) self.physical_position_readout = QLabel("--°", self) self.physical_position_readout.move(565, 367) self.physical_position_readout.setFixedWidth(200) self.physical_position_readout_label = QLabel("Position", self) self.physical_position_readout_label.move(565, 347) self.temperature_readout = QLabel("-- °C", self) self.temperature_readout.move(635, 367) self.temperature_readout.setFixedWidth(200) self.temperature_readout_label = QLabel("Temperature", self) self.temperature_readout_label.move(635, 347) self.voltage_readout = QLabel("-- V", self) self.voltage_readout.move(730, 367) self.voltage_readout.setFixedWidth(200) self.voltage_readout_label = QLabel("Voltage", self) self.voltage_readout_label.move(730, 347) self.readout_update_timer = QTimer(self) self.readout_update_timer.timeout.connect(self.update_readouts) self.readout_update_timer.start(250) self.active_servo: LX16A = None self.position_slider.setValue(0) self.position_offset_slider.setValue(0) self.motor_speed_slider.setValue(0) self.id_selection_box_refresh_button.setEnabled(False) self.disable_widgets() self.port_selection_box.currentTextChanged.connect( self.port_selection_box_changed) self.port_selection_box_refresh_button.clicked.connect( self.port_refresh_button_clicked) self.id_selection_box.currentTextChanged.connect( self.id_selection_box_changed) self.id_selection_box_refresh_button.clicked.connect( self.id_refresh_button_clicked) self.set_id_button.pressed.connect(self.id_updated) self.position_slider.sliderMoved.connect(self.position_slider_updated) self.position_offset_slider.sliderMoved.connect( self.position_offset_slider_updated) self.angle_lower_limit_textentry.textChanged.connect( self.angle_lower_limit_updated) self.angle_upper_limit_textentry.textChanged.connect( self.angle_upper_limit_updated) self.vin_lower_limit_textentry.textChanged.connect( self.vin_lower_limit_updated) self.vin_upper_limit_textentry.textChanged.connect( self.vin_upper_limit_updated) self.temp_limit_textentry.textChanged.connect(self.temp_limit_updated) self.servo_mode_radio_button.toggled.connect( self.servo_mode_radio_button_toggled) self.motor_mode_radio_button.toggled.connect( self.motor_mode_radio_button_toggled) self.motor_speed_slider.valueChanged.connect( self.motor_speed_slider_updated) self.torque_enabled_checkbox.stateChanged.connect( self.torque_enabled_checkbox_toggled) self.led_enabled_checkbox.stateChanged.connect( self.led_enabled_checkbox_toggled) self.led_over_temp_checkbox.stateChanged.connect( self.led_error_triggers_checkbox_toggled) self.led_over_voltage_checkbox.stateChanged.connect( self.led_error_triggers_checkbox_toggled) self.led_rotor_locked_checkbox.stateChanged.connect( self.led_error_triggers_checkbox_toggled) self.scan_for_ports() def disable_widgets(self): self.set_id_line_edit.setEnabled(False) self.position_slider.setEnabled(False) self.position_offset_slider.setEnabled(False) self.angle_lower_limit_textentry.setEnabled(False) self.angle_upper_limit_textentry.setEnabled(False) self.vin_lower_limit_textentry.setEnabled(False) self.vin_upper_limit_textentry.setEnabled(False) self.temp_limit_textentry.setEnabled(False) self.servo_mode_radio_button.setEnabled(False) self.motor_mode_radio_button.setEnabled(False) self.motor_speed_slider.setEnabled(False) self.torque_enabled_checkbox.setEnabled(False) self.led_enabled_checkbox.setEnabled(False) self.led_over_temp_checkbox.setEnabled(False) self.led_over_voltage_checkbox.setEnabled(False) self.led_rotor_locked_checkbox.setEnabled(False) def enable_widgets(self): self.set_id_line_edit.setEnabled(True) self.position_slider.setEnabled(True) self.position_offset_slider.setEnabled(True) self.angle_lower_limit_textentry.setEnabled(True) self.angle_upper_limit_textentry.setEnabled(True) self.vin_lower_limit_textentry.setEnabled(True) self.vin_upper_limit_textentry.setEnabled(True) self.temp_limit_textentry.setEnabled(True) self.servo_mode_radio_button.setEnabled(True) self.motor_mode_radio_button.setEnabled(True) self.motor_speed_slider.setEnabled(True) self.torque_enabled_checkbox.setEnabled(True) self.led_enabled_checkbox.setEnabled(True) self.led_over_temp_checkbox.setEnabled(True) self.led_over_voltage_checkbox.setEnabled(True) self.led_rotor_locked_checkbox.setEnabled(True) def clear_servo(self): self.active_servo = None @catch_disconnection def set_servo_id(self, id_): if not id_.isdigit(): return self.active_servo = LX16A(int(id_)) self.active_servo.enable_torque() self.position_slider.setValue( int(self.active_servo.get_physical_angle())) self.position_slider_readout.setText( f"{int(self.active_servo.get_physical_angle() * 25 / 6) * 6 / 25:0.2f}°" ) self.position_offset_slider.setValue( int(self.active_servo.get_angle_offset())) self.position_offset_slider_readout.setText( f"{int(self.active_servo.get_angle_offset() * 25 / 6) * 6 / 25:0.2f}°" ) self.angle_lower_limit_textentry.setText( str(int(self.active_servo.get_angle_limits()[0]))) self.angle_upper_limit_textentry.setText( str(int(self.active_servo.get_angle_limits()[1]))) self.vin_lower_limit_textentry.setText( str(self.active_servo.get_vin_limits()[0])) self.vin_upper_limit_textentry.setText( str(self.active_servo.get_vin_limits()[1])) self.temp_limit_textentry.setText( str(self.active_servo.get_temp_limit())) self.motor_speed_slider.setValue(self.active_servo.get_motor_speed( ) if self.active_servo.is_motor_mode() else 0) if self.active_servo.is_motor_mode(): self.motor_mode_radio_button.setChecked(True) else: self.servo_mode_radio_button.setChecked(True) self.motor_speed_slider.setEnabled(self.active_servo.is_motor_mode()) self.torque_enabled_checkbox.setChecked( self.active_servo.is_torque_enabled()) self.led_enabled_checkbox.setChecked( self.active_servo.is_led_power_on()) self.led_over_temp_checkbox.setChecked( self.active_servo.get_led_error_triggers()[0]) self.led_over_voltage_checkbox.setChecked( self.active_servo.get_led_error_triggers()[1]) self.led_rotor_locked_checkbox.setChecked( self.active_servo.get_led_error_triggers()[2]) @catch_disconnection def scan_for_servos(self, port): self.setCursor(Qt.CursorShape.WaitCursor) LX16A.initialize(port) self.id_selection_box.clear() for i in range(0, 254): try: servo = LX16A(i) self.id_selection_box.addItem(str(i)) except: pass self.setCursor(Qt.CursorShape.ArrowCursor) @catch_disconnection def scan_for_ports(self): ports = serial.tools.list_ports.comports() for port in ports: self.port_selection_box.addItem(port.device) @catch_disconnection def update_readouts(self): if self.active_servo is None: return try: self.physical_position_readout.setText( f"{self.active_servo.get_physical_angle():0.2f}°") self.temperature_readout.setText( f"{self.active_servo.get_temp()} °C") self.voltage_readout.setText( f"{self.active_servo.get_vin() / 1000} V") except (ServoTimeoutError, ServoChecksumError): pass @catch_disconnection def id_updated(self): new_id = self.set_id_line_edit.text() try: servo = LX16A(int(new_id)) except ServoTimeoutError: # Meaning this ID is not taken self.active_servo.set_id(int(new_id)) self.id_selection_box.item( self.id_selection_box.currentRow()).setText(new_id) return QMessageBox.warning(None, "Error", "ID already taken") @catch_disconnection def position_slider_updated(self, pos): if float(self.voltage_readout.text()[:-2]) < 5: QMessageBox.warning( None, "Error", "The voltage going through the servo is too low. Is your battery powered on?", ) return self.active_servo.move(pos) self.position_slider_readout.setText( f"{int(pos * 25 / 6) * 6 / 25:0.2f}°") @catch_disconnection def position_offset_slider_updated(self, pos): self.active_servo.set_angle_offset(pos) self.position_offset_slider_readout.setText( f"{int(pos * 25 / 6) * 6 / 25:0.2f}°") @catch_disconnection def angle_lower_limit_updated(self, text): if (QIntValidator(0, 240, self).validate(text, 0) != QIntValidator.State.Acceptable): return if int(text) > int(self.angle_upper_limit_textentry.text()): return self.active_servo.set_angle_limits( int(text), int(self.angle_upper_limit_textentry.text())) @catch_disconnection def angle_upper_limit_updated(self, text): if (QIntValidator(0, 240, self).validate(text, 0) != QIntValidator.State.Acceptable): return if int(text) < int(self.angle_lower_limit_textentry.text()): return self.active_servo.set_angle_limits( int(self.angle_lower_limit_textentry.text()), int(text)) @catch_disconnection def vin_lower_limit_updated(self, text): if (QIntValidator(4500, 12000, self).validate(text, 0) != QIntValidator.State.Acceptable): return if int(text) > int(self.vin_upper_limit_textentry.text()): return self.active_servo.set_vin_limits( int(text), int(self.vin_upper_limit_textentry.text())) @catch_disconnection def vin_upper_limit_updated(self, text): if (QIntValidator(4500, 12000, self).validate(text, 0) != QIntValidator.State.Acceptable): return if int(text) < int(self.vin_lower_limit_textentry.text()): return self.active_servo.set_vin_limits( int(self.vin_lower_limit_textentry.text()), int(text)) @catch_disconnection def temp_limit_updated(self, text): if (QIntValidator(50, 100, self).validate(text, 0) != QIntValidator.State.Acceptable): return self.active_servo.set_temp_limit(int(text)) @catch_disconnection def servo_mode_radio_button_toggled(self, checked): if checked: self.active_servo.servo_mode() self.motor_speed_slider.setEnabled(False) self.position_slider.setEnabled(True) self.position_offset_slider.setEnabled(True) else: self.active_servo.motor_mode(int(self.motor_speed_slider.value())) self.motor_speed_slider.setEnabled(True) self.position_slider.setEnabled(False) self.position_offset_slider.setEnabled(False) @catch_disconnection def motor_mode_radio_button_toggled(self, checked): if checked: self.active_servo.motor_mode(int(self.motor_speed_slider.value())) self.motor_speed_slider.setEnabled(True) self.position_slider.setEnabled(False) self.position_offset_slider.setEnabled(False) else: self.active_servo.servo_mode() self.motor_speed_slider.setEnabled(False) self.position_slider.setEnabled(True) self.position_offset_slider.setEnabled(True) @catch_disconnection def motor_speed_slider_updated(self, pos): self.active_servo.motor_mode(pos) @catch_disconnection def torque_enabled_checkbox_toggled(self, checked): if checked: self.active_servo.enable_torque() else: self.active_servo.disable_torque() self.position_slider.setEnabled(checked) self.position_offset_slider.setEnabled(checked) self.servo_mode_radio_button.setEnabled(checked) self.motor_mode_radio_button.setEnabled(checked) self.motor_speed_slider.setEnabled(checked) @catch_disconnection def led_enabled_checkbox_toggled(self, checked): if checked: self.active_servo.led_power_on() else: self.active_servo.led_power_off() @catch_disconnection def led_error_triggers_checkbox_toggled(self): self.active_servo.set_led_error_triggers( self.led_over_voltage_checkbox.isChecked(), self.led_over_temp_checkbox.isChecked(), self.led_rotor_locked_checkbox.isChecked(), ) @catch_disconnection def port_refresh_button_clicked(self, value): self.id_selection_box_refresh_button.setEnabled(False) self.disable_widgets() self.port_selection_box.clear() self.id_selection_box.clear() self.scan_for_ports() @catch_disconnection def id_refresh_button_clicked(self, value): self.disable_widgets() self.id_selection_box.clear() self.scan_for_servos(self.port_selection_box.currentText()) @catch_disconnection def port_selection_box_changed(self, text): if text == "": return self.id_selection_box_refresh_button.setEnabled(True) self.disable_widgets() self.id_selection_box.clear() self.clear_servo() self.scan_for_servos(text) @catch_disconnection def id_selection_box_changed(self, text): if text == "": return self.enable_widgets() self.set_servo_id(text)
class Madoka(QWidget): global serialPort, baud, fmt def __init__(self): global serialPort, baud, fmt self.serialLogState = False self.fmts = ['bin', 'oct', 'dec', 'hex', 'csv', 'csv+', 'ascii'] self.baudList = [ 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 76800, 115200, 230400, 460800, 921600, 1000000, 2000000, 4000000 ] fmt = self.fmts.index(fmt) super().__init__() layoutA = QHBoxLayout() self.baudBox = QComboBox() self.getBaud() layoutA.addWidget(self.baudBox) self.formatBox = QComboBox() self.getFormat() layoutA.addWidget(self.formatBox) layoutB = QHBoxLayout() self.portBox = QComboBox() self.getPorts() layoutB.addWidget(self.portBox) parentLayout = QVBoxLayout() parentLayout.addLayout(layoutB) parentLayout.addLayout(layoutA) self.setLayout(parentLayout) self.portBox.currentTextChanged.connect(self.portSelected) self.portBox.setCurrentIndex(len(self.ports) - 1) self.baudBox.currentTextChanged.connect(self.baudSelected) try: self.baudBox.setCurrentIndex(self.baudList.index(baud)) except: self.baudBox.setCurrentIndex(-1) self.formatBox.currentTextChanged.connect(self.formatSelected) self.formatBox.setCurrentIndex(fmt) self.button1 = QPushButton() self.button1.setText("start") self.button1.released.connect(self.btn1Clicked) layoutA.addWidget(self.button1) def portSelected(self, text): global serialPort if (text != ''): serialPort = self.ports[self.portsIndex.index(text)] # print(serialPort) # self.erandano.setText(self.port) def baudSelected(self, text): global baud if (text != ''): baud = text # print(baud) def formatSelected(self, text): global fmt if (text != ''): fmt = self.fmts.index(text) # print(text) def getPorts(self): global serialPort # sys.stderr.write("\n--- Available ports:\n") self.ports = [] self.portsIndex = [] for n, (port, desc, devid) in enumerate(sorted(comports()), 1): # sys.stderr.write("--- {:2}: {:20} {!r} \n".format(n, port, desc)) self.ports.append(port) print(port) self.portsIndex.append("{:2}: {:20} {!r}".format(n, port, desc)) try: serialPort = self.ports.index(serialPort) except: serialPort = self.ports[len(self.ports) - 1] for s in self.portsIndex: self.portBox.addItem(s) def getBaud(self): for b in self.baudList: self.baudBox.addItem(str(b)) def getFormat(self): global fmt for f in self.fmts: self.formatBox.addItem(f) def btn1Clicked(self): if (self.button1.text() == 'start'): self.baudBox.setDisabled(True) self.formatBox.setDisabled(True) self.button1.setText('stop') self.serialLogState = True th = threading.Thread(target=printData, daemon=True) th.start() elif (self.button1.text() == 'stop'): self.button1.setText('start') self.baudBox.setDisabled(False) self.formatBox.setDisabled(False) self.serialLogState = False
class SolveMazeGroupView(QWidget): setMazeSolverControlsEnabled = pyqtSignal(bool) __onSolveButtonPressed: Callable[[MazeSolverSpecification], None] __startPosition: XYPicker __endPosition: XYPicker __mazeSize: XY __maximumXY: XY def __init__( self, onPlayButtonPressed: Callable[[], None], onPauseButtonPressed: Callable[[], None], onStepButtonPressed: Callable[[], None], onRestartButtonPressed: Callable[[], None], onSpeedControlValueChanged: Callable[[int], None], onOpenLogButtonPressed: Callable[[], None], onAgentVarsButtonPressed: Callable[[], None], onSolveButtonPressed: Callable[[MazeSolverSpecification], None], mazeSize: XY, parent: Optional[QWidget] = None, *args: Tuple[Any, Any], **kwargs: Tuple[Any, Any], ) -> None: """ Grouped controls box for controls for solving mazes """ self.__onSolveButtonPressed = onSolveButtonPressed self.__mazeSize = mazeSize self.__maximumXY = XY( self.__mazeSize.x - 1, self.__mazeSize.y - 1, ) super().__init__(parent=parent, *args, **kwargs) self.setContentsMargins(0, 0, 0, 0) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) groupbox = QGroupBox("Solve Maze") layout.addWidget(groupbox) vbox = QFormLayout() groupbox.setLayout(vbox) self.__startPosition = XYPicker( minimum=XY(0, 0), maximum=self.__maximumXY, initialValue=XY(0, 0), parent=self, label="•", ) self.__endPosition = XYPicker( minimum=XY(0, 0), maximum=self.__maximumXY, initialValue=self.__maximumXY, parent=self, label="•", ) self.__solverTypePicker = QComboBox(self) self.__solverTypePicker.addItem("Wall Follower", WallFollower) self.__solverTypePicker.addItem("Pledge Solver", PledgeSolver) self.__solverTypePicker.addItem("Random Mouse", RandomMouse) solveButton = QPushButton("Solve") solveButton.clicked.connect( # type: ignore lambda: self.__onSolveButtonPressed( MazeSolverSpecification( startPosition=self.__startPosition.getValues(), endPosition=self.__endPosition.getValues(), solverType=self.__solverTypePicker.currentData(), ), ) ) self.__solverControlsDropdown = SolverControlsView( onPlayButtonPressed=onPlayButtonPressed, onPauseButtonPressed=onPauseButtonPressed, onStepButtonPressed=onStepButtonPressed, onRestartButtonPressed=onRestartButtonPressed, onSpeedControlValueChanged=onSpeedControlValueChanged, onOpenLogButtonPressed=onOpenLogButtonPressed, onAgentVarsButtonPressed=onAgentVarsButtonPressed, parent=self, ) # connect enable/disable signal to child view self.setMazeSolverControlsEnabled.connect( self.__solverControlsDropdown.setMazeSolverControlsEnabled ) vbox.addRow("Start Position", self.__startPosition) vbox.addRow("End Position", self.__endPosition) vbox.addRow("Solver Type", self.__solverTypePicker) vbox.addRow(solveButton) vbox.addRow(self.__solverControlsDropdown) self.setLayout(layout)