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('打开')
class MainWindow(QMainWindow): def __init__(self, admin_or_not=None): """ MainWindow Constructor """ super().__init__() self.admin_or_not = admin_or_not # Used to grant the user admin privileges self.curr_proxy_model = None # Variable that refers to the current page's proxy mmodel self.initializeUI() def initializeUI(self): """Set up the GUI's main window.""" self.setWindowTitle("Database Manager") self.setMinimumSize(800, 400) self.setUpMainWindow() def setUpMainWindow(self): """Create and arrange widgets in the main window.""" # Create the container widget for each of the pages # in the tab widget self.customer_tab = QWidget() self.orders_tab = QWidget() self.category_tab = QWidget() self.products_tab = QWidget() # Add or insert the tabs into the tab widget self.tabs = QTabWidget() self.tabs.setDocumentMode(True) self.tabs.addTab(self.customer_tab, "Customers") self.tabs.addTab(self.orders_tab, "Orders") self.tabs.addTab(self.category_tab, "Categories") self.tabs.addTab(self.products_tab, "Products") if self.admin_or_not == 1: self.staff_tab = QWidget() self.tabs.insertTab(0, self.staff_tab, "Staff") self.createStaffTab() self.tabs.setCurrentIndex(1) # Set tab to Customers tab self.tabs.currentChanged.connect(self.updateWidgetsAndStates) # Call the methods to construct each page self.createCustomersTab() self.createOrdersTab() self.createCategoriesTab() self.createProductsTab() # Create the widgets in the sidebar for filtering table content self.table_name_label = QLabel("<b>Customers</b>") self.table_name_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.filter_pattern_line = QLineEdit() self.filter_pattern_line.setClearButtonEnabled(True) self.filter_pattern_line.textChanged.connect(self.filterRegExpChanged) self.filter_regex_combo = QComboBox() filter_options = ["Default", "Wildcard", "Fixed String"] self.filter_regex_combo.addItems(filter_options) self.filter_regex_combo.currentIndexChanged.connect( self.filterRegExpChanged) self.filter_field_combo = QComboBox() self.updateWidgetsAndStates( 1) # Initialize the values in filter_field_combo self.filter_field_combo.currentIndexChanged.connect( self.selectTableColumn) filter_case_sensitivity_cb = QCheckBox("Filter with Case Sensitivity") filter_case_sensitivity_cb.toggled.connect(self.toggleCaseSensitivity) filter_case_sensitivity_cb.toggle() # Layout for the sidebar filter_v_box = QVBoxLayout() filter_v_box.addWidget(self.table_name_label) filter_v_box.addWidget(QLabel("Filter Pattern")) filter_v_box.addWidget(self.filter_pattern_line) filter_v_box.addWidget(QLabel("Filter filter")) filter_v_box.addWidget(self.filter_regex_combo) filter_v_box.addWidget(QLabel("Select Table Column")) filter_v_box.addWidget(self.filter_field_combo) filter_v_box.addWidget(filter_case_sensitivity_cb) filter_v_box.addStretch(2) self.filter_group = QGroupBox("Filtering") self.filter_group.setMaximumWidth(260) self.filter_group.setLayout(filter_v_box) # Arrange the containers in the main window main_h_box = QHBoxLayout() main_h_box.addWidget(self.tabs) main_h_box.addWidget(self.filter_group) main_container = QWidget() main_container.setLayout(main_h_box) self.setCentralWidget(main_container) # Create status bar self.setStatusBar(QStatusBar()) def createStaffTab(self): """Create the page to view the Staff table from the database.""" staff_sql_model = QSqlRelationalTableModel() staff_sql_model.setTable("Staff") staff_sql_model.select() # Populate the model with data staff_proxy_model = QSortFilterProxyModel() staff_proxy_model.setSourceModel(staff_sql_model) staff_table = QTableView() staff_table.setSortingEnabled(True) staff_table.setModel(staff_proxy_model) staff_table.setItemDelegateForColumn( staff_sql_model.fieldIndex("staff_id"), ReadOnlyDelegate()) staff_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch) staff_h_box = QHBoxLayout() staff_h_box.addWidget(staff_table) self.staff_tab.setLayout(staff_h_box) def createCustomersTab(self): """Create the page to view the Customers table from the database.""" cust_sql_model = QSqlRelationalTableModel() cust_sql_model.setTable("Customers") cust_sql_model.setRelation( cust_sql_model.fieldIndex("staff_id"), QSqlRelation("Staff", "staff_id", "username")) cust_sql_model.setHeaderData(cust_sql_model.fieldIndex("staff_id"), Qt.Orientation.Horizontal, "staff_username") cust_sql_model.select() # Populate the model with data cust_proxy_model = QSortFilterProxyModel() cust_proxy_model.setSourceModel(cust_sql_model) cust_table = QTableView() cust_table.setSortingEnabled(True) cust_table.setModel(cust_proxy_model) cust_table.setItemDelegate(SqlProxyDelegate(cust_table)) cust_table.setItemDelegateForColumn(cust_sql_model.fieldIndex("phone"), PhoneDelegate()) cust_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch) cust_h_box = QHBoxLayout() cust_h_box.addWidget(cust_table) self.customer_tab.setLayout(cust_h_box) def createOrdersTab(self): """Create the page to view the Orders table from the database.""" ord_sql_model = QSqlRelationalTableModel() ord_sql_model.setTable("Orders") ord_sql_model.setRelation( ord_sql_model.fieldIndex("product_id"), QSqlRelation("Products", "product_id", "product_name")) ord_sql_model.setRelation( ord_sql_model.fieldIndex("customer_id"), QSqlRelation("Customers", "customer_id", "first_name")) ord_sql_model.setHeaderData(ord_sql_model.fieldIndex("customer_id"), Qt.Orientation.Horizontal, "customer_name") ord_sql_model.select() # Populate the model with data ord_proxy_model = QSortFilterProxyModel() ord_proxy_model.setSourceModel(ord_sql_model) ord_table = QTableView() ord_table.setSortingEnabled(True) ord_table.setModel(ord_proxy_model) ord_table.setItemDelegate(SqlProxyDelegate(ord_table)) ord_table.setItemDelegateForColumn( ord_sql_model.fieldIndex("date_of_order"), DateDelegate()) ord_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch) ord_h_box = QHBoxLayout() ord_h_box.addWidget(ord_table) self.orders_tab.setLayout(ord_h_box) def createCategoriesTab(self): """Create the page to view the Categories table from the database.""" cat_sql_model = QSqlRelationalTableModel() cat_sql_model.setTable("Categories") cat_sql_model.select() # Populate the model with data cat_proxy_model = QSortFilterProxyModel() cat_proxy_model.setSourceModel(cat_sql_model) cat_table = QTableView() cat_table.setSortingEnabled(True) cat_table.setModel(cat_proxy_model) cat_table.setItemDelegateForColumn( cat_sql_model.fieldIndex("category_id"), ReadOnlyDelegate()) cat_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch) cat_h_box = QHBoxLayout() cat_h_box.addWidget(cat_table) self.category_tab.setLayout(cat_h_box) def createProductsTab(self): """Create the page to view the Products table from the database.""" prod_sql_model = QSqlRelationalTableModel() prod_sql_model.setTable("Products") prod_sql_model.setRelation( prod_sql_model.fieldIndex("category_id"), QSqlRelation("Categories", "category_id", "category_name")) prod_sql_model.select() # Populate the model with data prod_proxy_model = QSortFilterProxyModel() prod_proxy_model.setSourceModel(prod_sql_model) prod_table = QTableView() prod_table.setSortingEnabled(True) prod_table.setModel(prod_proxy_model) prod_table.setItemDelegate(SqlProxyDelegate(prod_table)) prod_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch) prod_h_box = QHBoxLayout() prod_h_box.addWidget(prod_table) self.products_tab.setLayout(prod_h_box) def filterRegExpChanged(self, value): """Slot for collecting the expression (pattern) for filtering items in the tables. Expressions are then passed to various QSortFilterProxyModel methods depending upon the value in filter_regex_combo.""" pattern = self.filter_pattern_line.text() filter = self.filter_regex_combo.currentText() model = self.curr_proxy_model if filter == "Wildcard": regex = QRegularExpression() pattern = regex.wildcardToRegularExpression( pattern, regex.WildcardConversionOption.UnanchoredWildcardConversion) elif filter == "Fixed String": pattern = QRegularExpression.escape(pattern) option = QRegularExpression.PatternOption.NoPatternOption regex = QRegularExpression(pattern, option) # Check whether or not the regular expression is valid or not if regex.isValid(): model.setFilterRegularExpression(regex) else: # Display error message in the statusbar self.statusBar().showMessage(regex.errorString(), 4000) model.setFilterRegularExpression(QRegularExpression()) def selectTableColumn(self, index): """Select the field (column) in the SQL table to be filtered.""" self.curr_proxy_model.setFilterKeyColumn(index) def toggleCaseSensitivity(self, toggled): """Toggle whether items are filtered with or without case sensitivity.""" if toggled: self.curr_proxy_model.setFilterCaseSensitivity( Qt.CaseSensitivity.CaseSensitive) else: self.curr_proxy_model.setFilterCaseSensitivity( Qt.CaseSensitivity.CaseInsensitive) def updateWidgetsAndStates(self, index): """Whenever the user switches a tab, update information regarding the tab selected, the current table's QSortFilterProxyModel, and information displayed in the sidebar for filtering.""" self.filter_field_combo.clear() curr_table = self.tabs.currentWidget().findChild(QTableView) curr_model = curr_table.model().sourceModel() # Set text to display current table's name in the sidebar self.table_name_label.setText(f"<b>{curr_model.tableName()}</b>") self.curr_proxy_model = curr_table.model() # Update QComboBox values based on currently selected tab field_names = [] for col in range(0, curr_model.columnCount()): field_names.append(curr_model.record().fieldName(col)) if curr_model.tableName() == "Orders" and \ "first_name" in field_names: field_names = [ "customer_name" if n == "first_name" else n for n in field_names ] self.filter_field_combo.addItems(field_names) # NOTE: To the reader, the following code differs slightly from the book. # This portion is left here as reference should you need to use both # QSqlTableModel and QSqlRelationalTableModel classes. Simply replace the code # above with the code below. """ if isinstance(curr_table.model(), QSqlRelationalTableModel): self.table_name_label.setText(f"<b>{curr_table.model().tableName()}</b>") # Update QComboBox values based on currently selected tab for col in range(0, curr_table.model().columnCount()): field_names.append(curr_table.model().record().fieldName(col)) self.filter_field_combo.addItems(field_names) elif isinstance(curr_table.model(), QSortFilterProxyModel): self.table_name_label.setText(f"<b>{curr_model.tableName()}</b>") self.curr_proxy_model = curr_table.model() # Update QComboBox values based on currently selected tab for col in range(0, curr_model.columnCount()): field_names.append(curr_model.record().fieldName(col)) if "first_name" in field_names: field_names = ["customer_name" if i=="first_name" else i for i in field_names] self.filter_field_combo.addItems(field_names) """ def closeEvent(self, event): """Close database connection when window is closed.""" model = self.curr_proxy_model.sourceModel() model.database().close()
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) # region Create CartPole instance and load initial settings # Create CartPole instance self.initial_state = create_cartpole_state() self.CartPoleInstance = CartPole(initial_state=self.initial_state) # Set timescales self.CartPoleInstance.dt_simulation = dt_simulation self.CartPoleInstance.dt_controller = controller_update_interval self.CartPoleInstance.dt_save = save_interval # set other settings self.CartPoleInstance.set_controller(controller_init) self.CartPoleInstance.stop_at_90 = stop_at_90_init self.set_random_experiment_generator_init_params() # endregion # region Decide whether to save the data in "CartPole memory" or not self.save_history = save_history_init self.show_experiment_summary = show_experiment_summary_init if self.save_history or self.show_experiment_summary: self.CartPoleInstance.save_data_in_cart = True else: self.CartPoleInstance.save_data_in_cart = False # endregion # region Other variables initial values as provided in gui_default_parameters.py # Start user controlled experiment/ start random experiment/ load and replay - on start button self.simulator_mode = simulator_mode_init self.slider_on_click = slider_on_click_init # Update slider on click/update slider while hoovering over it self.speedup = speedup_init # Default simulation speed-up # endregion # region Initialize loop-timer # This timer allows to relate the simulation time to user time # And (if your computer is fast enough) run simulation # slower or faster than real-time by predefined factor (speedup) self.looper = loop_timer( dt_target=(self.CartPoleInstance.dt_simulation / self.speedup)) # endregion # region Variables controlling the state of various processes (DO NOT MODIFY) self.terminate_experiment_or_replay_thread = False # True: gives signal causing thread to terminate self.pause_experiment_or_replay_thread = False # True: gives signal causing the thread to pause self.run_set_labels_thread = True # True if gauges (labels) keep being repeatedly updated # Stop threads by setting False # Flag indicating if the "START! / STOP!" button should act as start or as stop when pressed. # Can take values "START!" or "STOP!" self.start_or_stop_action = "START!" # Flag indicating whether the pause button should pause or unpause. self.pause_or_unpause_action = "PAUSE" # Flag indicating that saving of experiment recording to csv file has finished self.experiment_or_replay_thread_terminated = False self.user_time_counter = 0 # Measures the user time # Slider instant value (which is draw in GUI) differs from value saved in CartPole instance # if the option updating slider "on-click" is enabled. self.slider_instant_value = self.CartPoleInstance.slider_value self.noise = 'OFF' self.CartPoleInstance.NoiseAdderInstance.noise_mode = self.noise # endregion # region Create GUI Layout # region - Create container for top level layout layout = QVBoxLayout() # endregion # region - Change geometry of the main window self.setGeometry(300, 300, 2500, 1000) # endregion # region - Matplotlib figures (CartPole drawing and Slider) # Draw Figure self.fig = Figure( figsize=(25, 10) ) # Regulates the size of Figure in inches, before scaling to window size. self.canvas = FigureCanvas(self.fig) self.fig.AxCart = self.canvas.figure.add_subplot(211) self.fig.AxSlider = self.canvas.figure.add_subplot(212) self.fig.AxSlider.set_ylim(0, 1) self.CartPoleInstance.draw_constant_elements(self.fig, self.fig.AxCart, self.fig.AxSlider) # Attach figure to the layout lf = QVBoxLayout() lf.addWidget(self.canvas) # endregion # region - Radio buttons selecting current controller self.rbs_controllers = [] for controller_name in self.CartPoleInstance.controller_names: self.rbs_controllers.append(QRadioButton(controller_name)) # Ensures that radio buttons are exclusive self.controllers_buttons_group = QButtonGroup() for button in self.rbs_controllers: self.controllers_buttons_group.addButton(button) lr_c = QVBoxLayout() lr_c.addStretch(1) for rb in self.rbs_controllers: rb.clicked.connect(self.RadioButtons_controller_selection) lr_c.addWidget(rb) lr_c.addStretch(1) self.rbs_controllers[self.CartPoleInstance.controller_idx].setChecked( True) # endregion # region - Create central part of the layout for figures and radio buttons and add it to the whole layout lc = QHBoxLayout() lc.addLayout(lf) lc.addLayout(lr_c) layout.addLayout(lc) # endregion # region - Gauges displaying current values of various states and parameters (time, velocity, angle,...) # First row ld = QHBoxLayout() # User time self.labTime = QLabel("User's time (s): ") self.timer = QTimer() self.timer.setInterval(100) # Tick every 1/10 of the second self.timer.timeout.connect(self.set_user_time_label) self.timer.start() ld.addWidget(self.labTime) # Speed, angle, motor power (Q) self.labSpeed = QLabel('Speed (m/s):') self.labAngle = QLabel('Angle (deg):') self.labMotor = QLabel('') self.labTargetPosition = QLabel('') ld.addWidget(self.labSpeed) ld.addWidget(self.labAngle) ld.addWidget(self.labMotor) ld.addWidget(self.labTargetPosition) layout.addLayout(ld) # Second row of labels # Simulation time, Measured (real) speed-up, slider-value ld2 = QHBoxLayout() self.labTimeSim = QLabel('Simulation Time (s):') ld2.addWidget(self.labTimeSim) self.labSpeedUp = QLabel('Speed-up (measured):') ld2.addWidget(self.labSpeedUp) self.labSliderInstant = QLabel('') ld2.addWidget(self.labSliderInstant) layout.addLayout(ld2) # endregion # region - Buttons "START!" / "STOP!", "PAUSE", "QUIT" self.bss = QPushButton("START!") self.bss.pressed.connect(self.start_stop_button) self.bp = QPushButton("PAUSE") self.bp.pressed.connect(self.pause_unpause_button) bq = QPushButton("QUIT") bq.pressed.connect(self.quit_application) lspb = QHBoxLayout() # Sub-Layout for Start/Stop and Pause Buttons lspb.addWidget(self.bss) lspb.addWidget(self.bp) # endregion # region - Sliders setting initial state and buttons for kicking the pole # Sliders setting initial position and angle lb = QVBoxLayout() # Layout for buttons lb.addLayout(lspb) lb.addWidget(bq) ip = QHBoxLayout() # Layout for initial position sliders self.initial_position_slider = QSlider( orientation=Qt.Orientation.Horizontal) self.initial_position_slider.setRange( -int(float(1000 * TrackHalfLength)), int(float(1000 * TrackHalfLength))) self.initial_position_slider.setValue(0) self.initial_position_slider.setSingleStep(1) self.initial_position_slider.valueChanged.connect( self.update_initial_position) self.initial_angle_slider = QSlider( orientation=Qt.Orientation.Horizontal) self.initial_angle_slider.setRange(-int(float(100 * np.pi)), int(float(100 * np.pi))) self.initial_angle_slider.setValue(0) self.initial_angle_slider.setSingleStep(1) self.initial_angle_slider.valueChanged.connect( self.update_initial_angle) ip.addWidget(QLabel("Initial position:")) ip.addWidget(self.initial_position_slider) ip.addWidget(QLabel("Initial angle:")) ip.addWidget(self.initial_angle_slider) ip.addStretch(0.01) # Slider setting latency self.LATENCY_SLIDER_RANGE_INT = 1000 self.latency_slider = QSlider(orientation=Qt.Orientation.Horizontal) self.latency_slider.setRange(0, self.LATENCY_SLIDER_RANGE_INT) self.latency_slider.setValue( int(self.CartPoleInstance.LatencyAdderInstance.latency * self.LATENCY_SLIDER_RANGE_INT / self.CartPoleInstance.LatencyAdderInstance.max_latency)) self.latency_slider.setSingleStep(1) self.latency_slider.valueChanged.connect(self.update_latency) ip.addWidget(QLabel("Latency:")) ip.addWidget(self.latency_slider) self.labLatency = QLabel('Latency (ms): {:.1f}'.format( self.CartPoleInstance.LatencyAdderInstance.latency * 1000)) ip.addWidget(self.labLatency) # Buttons activating noise self.rbs_noise = [] for mode_name in ['ON', 'OFF']: self.rbs_noise.append(QRadioButton(mode_name)) # Ensures that radio buttons are exclusive self.noise_buttons_group = QButtonGroup() for button in self.rbs_noise: self.noise_buttons_group.addButton(button) lr_n = QHBoxLayout() lr_n.addWidget(QLabel('Noise:')) for rb in self.rbs_noise: rb.clicked.connect(self.RadioButtons_noise_on_off) lr_n.addWidget(rb) self.rbs_noise[1].setChecked(True) ip.addStretch(0.01) ip.addLayout(lr_n) ip.addStretch(0.01) # Buttons giving kick to the pole kick_label = QLabel("Kick pole:") kick_left_button = QPushButton() kick_left_button.setText("Left") kick_left_button.adjustSize() kick_left_button.clicked.connect(self.kick_pole) kick_right_button = QPushButton() kick_right_button.setText("Right") kick_right_button.adjustSize() kick_right_button.clicked.connect(self.kick_pole) ip.addWidget(kick_label) ip.addWidget(kick_left_button) ip.addWidget(kick_right_button) lb.addLayout(ip) layout.addLayout(lb) # endregion # region - Text boxes and Combobox to provide settings concerning generation of random experiment l_generate_trace = QHBoxLayout() l_generate_trace.addWidget(QLabel('Random experiment settings:')) l_generate_trace.addWidget(QLabel('Length (s):')) self.textbox_length = QLineEdit() l_generate_trace.addWidget(self.textbox_length) l_generate_trace.addWidget(QLabel('Turning Points (m):')) self.textbox_turning_points = QLineEdit() l_generate_trace.addWidget(self.textbox_turning_points) l_generate_trace.addWidget(QLabel('Interpolation:')) self.cb_interpolation = QComboBox() self.cb_interpolation.addItems( ['0-derivative-smooth', 'linear', 'previous']) self.cb_interpolation.currentIndexChanged.connect( self.cb_interpolation_selectionchange) self.cb_interpolation.setCurrentText( self.CartPoleInstance.interpolation_type) l_generate_trace.addWidget(self.cb_interpolation) layout.addLayout(l_generate_trace) # endregion # region - Textbox to provide csv file name for saving or loading data l_text = QHBoxLayout() textbox_title = QLabel('CSV file name:') self.textbox = QLineEdit() l_text.addWidget(textbox_title) l_text.addWidget(self.textbox) layout.addLayout(l_text) # endregion # region - Make strip of layout for checkboxes l_cb = QHBoxLayout() # endregion # region - Textbox to provide the target speed-up value l_text_speedup = QHBoxLayout() tx_speedup_title = QLabel('Speed-up (target):') self.tx_speedup = QLineEdit() l_text_speedup.addWidget(tx_speedup_title) l_text_speedup.addWidget(self.tx_speedup) self.tx_speedup.setText(str(self.speedup)) l_cb.addLayout(l_text_speedup) self.wrong_speedup_msg = QMessageBox() self.wrong_speedup_msg.setWindowTitle("Speed-up value problem") self.wrong_speedup_msg.setIcon(QMessageBox.Icon.Critical) # endregion # region - Checkboxes # region -- Checkbox: Save/don't save experiment recording self.cb_save_history = QCheckBox('Save results', self) if self.save_history: self.cb_save_history.toggle() self.cb_save_history.toggled.connect(self.cb_save_history_f) l_cb.addWidget(self.cb_save_history) # endregion # region -- Checkbox: Display plots showing dynamic evolution of the system as soon as experiment terminates self.cb_show_experiment_summary = QCheckBox('Show experiment summary', self) if self.show_experiment_summary: self.cb_show_experiment_summary.toggle() self.cb_show_experiment_summary.toggled.connect( self.cb_show_experiment_summary_f) l_cb.addWidget(self.cb_show_experiment_summary) # endregion # region -- Checkbox: Block pole if it reaches +/-90 deg self.cb_stop_at_90_deg = QCheckBox('Stop-at-90-deg', self) if self.CartPoleInstance.stop_at_90: self.cb_stop_at_90_deg.toggle() self.cb_stop_at_90_deg.toggled.connect(self.cb_stop_at_90_deg_f) l_cb.addWidget(self.cb_stop_at_90_deg) # endregion # region -- Checkbox: Update slider on click/update slider while hoovering over it self.cb_slider_on_click = QCheckBox('Update slider on click', self) if self.slider_on_click: self.cb_slider_on_click.toggle() self.cb_slider_on_click.toggled.connect(self.cb_slider_on_click_f) l_cb.addWidget(self.cb_slider_on_click) # endregion # endregion # region - Radio buttons selecting simulator mode: user defined experiment, random experiment, replay # List available simulator modes - constant self.available_simulator_modes = [ 'Slider-Controlled Experiment', 'Random Experiment', 'Replay' ] self.rbs_simulator_mode = [] for mode_name in self.available_simulator_modes: self.rbs_simulator_mode.append(QRadioButton(mode_name)) # Ensures that radio buttons are exclusive self.simulator_mode_buttons_group = QButtonGroup() for button in self.rbs_simulator_mode: self.simulator_mode_buttons_group.addButton(button) lr_sm = QHBoxLayout() lr_sm.addStretch(1) lr_sm.addWidget(QLabel('Simulator mode:')) for rb in self.rbs_simulator_mode: rb.clicked.connect(self.RadioButtons_simulator_mode) lr_sm.addWidget(rb) lr_sm.addStretch(1) self.rbs_simulator_mode[self.available_simulator_modes.index( self.simulator_mode)].setChecked(True) l_cb.addStretch(1) l_cb.addLayout(lr_sm) l_cb.addStretch(1) # endregion # region - Add checkboxes to layout layout.addLayout(l_cb) # endregion # region - Create an instance of a GUI window w = QWidget() w.setLayout(layout) self.setCentralWidget(w) self.show() self.setWindowTitle('CartPole Simulator') # endregion # endregion # region Open controller-specific popup windows self.open_additional_controller_widget() # endregion # region Activate functions capturing mouse movements and clicks over the slider # This line links function capturing the mouse position on the canvas of the Figure self.canvas.mpl_connect("motion_notify_event", self.on_mouse_movement) # This line links function capturing the mouse position on the canvas of the Figure click self.canvas.mpl_connect("button_press_event", self.on_mouse_click) # endregion # region Introducing multithreading # To ensure smooth functioning of the app, # the calculations and redrawing of the figures have to be done in a different thread # than the one capturing the mouse position and running the animation self.threadpool = QThreadPool() # endregion # region Starts a thread repeatedly redrawing gauges (labels) of the GUI # It runs till the QUIT button is pressed worker_labels = Worker(self.set_labels_thread) self.threadpool.start(worker_labels) # endregion # region Start animation repeatedly redrawing changing elements of matplotlib figures (CartPole drawing and slider) # This animation runs ALWAYS when the GUI is open # The buttons of GUI only decide if new parameters are calculated or not self.anim = self.CartPoleInstance.run_animation(self.fig) # endregion # region Thread performing CartPole experiment, slider-controlled or random # It iteratively updates CartPole state and save data to a .csv file # It also put simulation time in relation to user time def experiment_thread(self): # Necessary only for debugging in Visual Studio Code IDE try: ptvsd.debug_this_thread() except: pass self.looper.start_loop() while not self.terminate_experiment_or_replay_thread: if self.pause_experiment_or_replay_thread: time.sleep(0.1) else: # Calculations of the Cart state in the next timestep self.CartPoleInstance.update_state() # Terminate thread if random experiment reached its maximal length if ((self.CartPoleInstance.use_pregenerated_target_position is True) and (self.CartPoleInstance.time >= self.CartPoleInstance.t_max_pre)): self.terminate_experiment_or_replay_thread = True # FIXME: when Speedup empty in GUI I expected inf speedup but got error Loop timer was not initialized properly self.looper.sleep_leftover_time() # Save simulation history if user chose to do so at the end of the simulation if self.save_history: csv_name = self.textbox.text() self.CartPoleInstance.save_history_csv( csv_name=csv_name, mode='init', length_of_experiment=np.around( self.CartPoleInstance.dict_history['time'][-1], decimals=2)) self.CartPoleInstance.save_history_csv(csv_name=csv_name, mode='save offline') self.experiment_or_replay_thread_terminated = True # endregion # region Thread replaying a saved experiment recording def replay_thread(self): # Necessary only for debugging in Visual Studio Code IDE try: ptvsd.debug_this_thread() except: pass # Check what is in the csv textbox csv_name = self.textbox.text() # Load experiment history history_pd, filepath = self.CartPoleInstance.load_history_csv( csv_name=csv_name) # Set cartpole in the right mode (just to ensure slider behaves properly) with open(filepath, newline='') as f: reader = csv.reader(f) for line in reader: line = line[0] if line[:len('# Controller: ')] == '# Controller: ': controller_set = self.CartPoleInstance.set_controller( line[len('# Controller: '):].rstrip("\n")) if controller_set: self.rbs_controllers[self.CartPoleInstance. controller_idx].setChecked(True) else: self.rbs_controllers[1].setChecked( True) # Set first, but not manual stabilization break # Augment the experiment history with simulation time step size dt = [] row_iterator = history_pd.iterrows() _, last = next(row_iterator) # take first item from row_iterator for i, row in row_iterator: dt.append(row['time'] - last['time']) last = row dt.append(dt[-1]) history_pd['dt'] = np.array(dt) # Initialize loop timer (with arbitrary dt) replay_looper = loop_timer(dt_target=0.0) # Start looping over history replay_looper.start_loop() global L for index, row in history_pd.iterrows(): self.CartPoleInstance.s[POSITION_IDX] = row['position'] self.CartPoleInstance.s[POSITIOND_IDX] = row['positionD'] self.CartPoleInstance.s[ANGLE_IDX] = row['angle'] self.CartPoleInstance.time = row['time'] self.CartPoleInstance.dt = row['dt'] try: self.CartPoleInstance.u = row['u'] except KeyError: pass self.CartPoleInstance.Q = row['Q'] self.CartPoleInstance.target_position = row['target_position'] if self.CartPoleInstance.controller_name == 'manual-stabilization': self.CartPoleInstance.slider_value = self.CartPoleInstance.Q else: self.CartPoleInstance.slider_value = self.CartPoleInstance.target_position / TrackHalfLength # TODO: Make it more general for all possible parameters try: L[...] = row['L'] except KeyError: pass except: print('Error while assigning L') print("Unexpected error:", sys.exc_info()[0]) print("Unexpected error:", sys.exc_info()[1]) dt_target = (self.CartPoleInstance.dt / self.speedup) replay_looper.dt_target = dt_target replay_looper.sleep_leftover_time() if self.terminate_experiment_or_replay_thread: # Means that stop button was pressed break while self.pause_experiment_or_replay_thread: # Means that pause button was pressed time.sleep(0.1) if self.show_experiment_summary: self.CartPoleInstance.dict_history = history_pd.loc[:index].to_dict( orient='list') self.experiment_or_replay_thread_terminated = True # endregion # region "START! / STOP!" button -> run/stop slider-controlled experiment, random experiment or replay experiment recording # Actions to be taken when "START! / STOP!" button is clicked def start_stop_button(self): # If "START! / STOP!" button in "START!" mode... if self.start_or_stop_action == 'START!': self.bss.setText("STOP!") self.start_thread() # If "START! / STOP!" button in "STOP!" mode... elif self.start_or_stop_action == 'STOP!': self.bss.setText("START!") self.bp.setText("PAUSE") # This flag is periodically checked by thread. It terminates if set True. self.terminate_experiment_or_replay_thread = True # The stop_thread function is called automatically by the thread when it terminates # It is implemented this way, because thread my terminate not only due "STOP!" button # (e.g. replay thread when whole experiment is replayed) def pause_unpause_button(self): # Only Pause if experiment is running if self.pause_or_unpause_action == 'PAUSE' and self.start_or_stop_action == 'STOP!': self.pause_or_unpause_action = 'UNPAUSE' self.pause_experiment_or_replay_thread = True self.bp.setText("UNPAUSE") elif self.pause_or_unpause_action == 'UNPAUSE' and self.start_or_stop_action == 'STOP!': self.pause_or_unpause_action = 'PAUSE' self.pause_experiment_or_replay_thread = False self.bp.setText("PAUSE") # Run thread. works for all simulator modes. def start_thread(self): # Check if value provided in speed-up textbox makes sense # If not abort start speedup_updated = self.get_speedup() if not speedup_updated: return # Disable GUI elements for features which must not be changed in runtime # For other features changing in runtime may not cause errors, but will stay without effect for current run self.cb_save_history.setEnabled(False) for rb in self.rbs_simulator_mode: rb.setEnabled(False) for rb in self.rbs_controllers: rb.setEnabled(False) if self.simulator_mode != 'Replay': self.cb_show_experiment_summary.setEnabled(False) # Set user-provided initial values for state (or its part) of the CartPole # Search implementation for more detail # The following line is important as it let the user to set with the slider the starting target position # After the slider was reset at the end of last experiment # With the small sliders he can also adjust starting initial_state self.reset_variables( 2, s=np.copy(self.initial_state), target_position=self.CartPoleInstance.target_position) if self.simulator_mode == 'Random Experiment': self.CartPoleInstance.use_pregenerated_target_position = True if self.textbox_length.text() == '': self.CartPoleInstance.length_of_experiment = length_of_experiment_init else: self.CartPoleInstance.length_of_experiment = float( self.textbox_length.text()) turning_points_list = [] if self.textbox_turning_points.text() != '': for turning_point in self.textbox_turning_points.text().split( ', '): turning_points_list.append(float(turning_point)) self.CartPoleInstance.turning_points = turning_points_list self.CartPoleInstance.setup_cartpole_random_experiment() self.looper.dt_target = self.CartPoleInstance.dt_simulation / self.speedup # Pass the function to execute if self.simulator_mode == "Replay": worker = Worker(self.replay_thread) elif self.simulator_mode == 'Slider-Controlled Experiment' or self.simulator_mode == 'Random Experiment': worker = Worker(self.experiment_thread) worker.signals.finished.connect(self.finish_thread) # Execute self.threadpool.start(worker) # Determine what should happen when "START! / STOP!" is pushed NEXT time self.start_or_stop_action = "STOP!" # finish_threads works for all simulation modes # Some lines mya be redundant for replay, # however as they do not take much computation time we leave them here # As it my code shorter, while hopefully still clear. # It is called automatically at the end of experiment_thread def finish_thread(self): self.CartPoleInstance.use_pregenerated_target_position = False self.initial_state = create_cartpole_state() self.initial_position_slider.setValue(0) self.initial_angle_slider.setValue(0) self.CartPoleInstance.s = self.initial_state # Some controllers may collect they own statistics about their usage and print it after experiment terminated if self.simulator_mode != 'Replay': try: self.CartPoleInstance.controller.controller_report() except: pass if self.show_experiment_summary: self.w_summary = SummaryWindow( summary_plots=self.CartPoleInstance.summary_plots) # Reset variables and redraw the figures self.reset_variables(0) # Draw figures self.CartPoleInstance.draw_constant_elements(self.fig, self.fig.AxCart, self.fig.AxSlider) self.canvas.draw() # Enable back all elements of GUI: self.cb_save_history.setEnabled(True) self.cb_show_experiment_summary.setEnabled(True) for rb in self.rbs_simulator_mode: rb.setEnabled(True) for rb in self.rbs_controllers: rb.setEnabled(True) self.start_or_stop_action = "START!" # What should happen when "START! / STOP!" is pushed NEXT time # endregion # region Methods: "Get, set, reset, quit" # Set parameters from gui_default_parameters related to generating a random experiment target position def set_random_experiment_generator_init_params(self): self.CartPoleInstance.track_relative_complexity = track_relative_complexity_init self.CartPoleInstance.length_of_experiment = length_of_experiment_init self.CartPoleInstance.interpolation_type = interpolation_type_init self.CartPoleInstance.turning_points_period = turning_points_period_init self.CartPoleInstance.start_random_target_position_at = start_random_target_position_at_init self.CartPoleInstance.end_random_target_position_at = end_random_target_position_at_init self.CartPoleInstance.turning_points = turning_points_init # Method resetting variables which change during experimental run def reset_variables(self, reset_mode=1, s=None, target_position=None): self.CartPoleInstance.set_cartpole_state_at_t0( reset_mode, s=s, target_position=target_position) self.user_time_counter = 0 # "Try" because this function is called for the first time during initialisation of the Window # when the timer label instance is not yer there. try: self.labt.setText("Time (s): " + str(float(self.user_time_counter) / 10.0)) except: pass self.experiment_or_replay_thread_terminated = False # This is a flag informing thread terminated self.terminate_experiment_or_replay_thread = False # This is a command to terminate a thread self.pause_experiment_or_replay_thread = False # This is a command to pause a thread self.start_or_stop_action = "START!" self.pause_or_unpause_action = "PAUSE" self.looper.first_call_done = False ###################################################################################################### # (Marcin) Below are methods with less critical functions. # A thread redrawing labels (except for timer, which has its own function) of GUI every 0.1 s def set_labels_thread(self): while (self.run_set_labels_thread): self.labSpeed.setText( "Speed (m/s): " + str(np.around(self.CartPoleInstance.s[POSITIOND_IDX], 2))) self.labAngle.setText("Angle (deg): " + str( np.around( self.CartPoleInstance.s[ANGLE_IDX] * 360 / (2 * np.pi), 2))) self.labMotor.setText("Motor power (Q): {:.3f}".format( np.around(self.CartPoleInstance.Q, 2))) if self.CartPoleInstance.controller_name == 'manual-stabilization': self.labTargetPosition.setText("") else: self.labTargetPosition.setText( "Target position (m): " + str(np.around(self.CartPoleInstance.target_position, 2))) if self.CartPoleInstance.controller_name == 'manual_stabilization': self.labSliderInstant.setText( "Slider instant value (-): " + str(np.around(self.slider_instant_value, 2))) else: self.labSliderInstant.setText( "Slider instant value (m): " + str(np.around(self.slider_instant_value, 2))) self.labTimeSim.setText('Simulation time (s): {:.2f}'.format( self.CartPoleInstance.time)) mean_dt_real = np.mean(self.looper.circ_buffer_dt_real) if mean_dt_real > 0: self.labSpeedUp.setText('Speed-up (measured): x{:.2f}'.format( self.CartPoleInstance.dt_simulation / mean_dt_real)) sleep(0.1) # Function to measure the time of simulation as experienced by user # It corresponds to the time of simulation according to equations only if real time mode is on # TODO (Marcin) I just retained this function from some example being my starting point # It seems it sometimes counting time to slow. Consider replacing in future def set_user_time_label(self): # "If": Increment time counter only if simulation is running if self.start_or_stop_action == "STOP!": # indicates what start button was pressed and some process is running self.user_time_counter += 1 # The updates are done smoother if the label is updated here # and not in the separate thread self.labTime.setText("Time (s): " + str(float(self.user_time_counter) / 10.0)) # The actions which has to be taken to properly terminate the application # The method is evoked after QUIT button is pressed # TODO: Can we connect it somehow also the the default cross closing the application? def quit_application(self): # Stops animation (updating changing elements of the Figure) self.anim._stop() # Stops the two threads updating the GUI labels and updating the state of Cart instance self.run_set_labels_thread = False self.terminate_experiment_or_replay_thread = True self.pause_experiment_or_replay_thread = False # Closes the GUI window self.close() # The standard command # It seems however not to be working by its own # I don't know how it works QApplication.quit() # endregion # region Mouse interaction """ These are some methods GUI uses to capture mouse effect while hoovering or clicking over/on the charts """ # Function evoked at a mouse movement # If the mouse cursor is over the lower chart it reads the corresponding value # and updates the slider def on_mouse_movement(self, event): if self.simulator_mode == 'Slider-Controlled Experiment': if event.xdata == None or event.ydata == None: pass else: if event.inaxes == self.fig.AxSlider: self.slider_instant_value = event.xdata if not self.slider_on_click: self.CartPoleInstance.update_slider( mouse_position=event.xdata) # Function evoked at a mouse click # If the mouse cursor is over the lower chart it reads the corresponding value # and updates the slider def on_mouse_click(self, event): if self.simulator_mode == 'Slider-Controlled Experiment': if event.xdata == None or event.ydata == None: pass else: if event.inaxes == self.fig.AxSlider: self.CartPoleInstance.update_slider( mouse_position=event.xdata) # endregion # region Changing "static" options: radio buttons, text boxes, combo boxes, check boxes """ This section collects methods used to change some ''static option'': e.g. change current controller, switch between saving and not saving etc. These are functions associated with radio buttons, check boxes, textfilds etc. The functions of "START! / STOP!" button is much more complex and we put them hence in a separate section. """ # region - Radio buttons # Chose the controller method which should be used with the CartPole def RadioButtons_controller_selection(self): # Change the mode variable depending on the Radiobutton state for i in range(len(self.rbs_controllers)): if self.rbs_controllers[i].isChecked(): self.CartPoleInstance.set_controller(controller_idx=i) # Reset the state of GUI and of the Cart instance after the mode has changed # TODO: Do I need the follwowing lines? self.reset_variables(0) self.CartPoleInstance.draw_constant_elements(self.fig, self.fig.AxCart, self.fig.AxSlider) self.canvas.draw() self.open_additional_controller_widget() # Chose the simulator mode - effect of start/stop button def RadioButtons_simulator_mode(self): # Change the mode variable depending on the Radiobutton state for i in range(len(self.rbs_simulator_mode)): sleep(0.001) if self.rbs_simulator_mode[i].isChecked(): self.simulator_mode = self.available_simulator_modes[i] # Reset the state of GUI and of the Cart instance after the mode has changed # TODO: Do I need the follwowing lines? self.reset_variables(0) self.CartPoleInstance.draw_constant_elements(self.fig, self.fig.AxCart, self.fig.AxSlider) self.canvas.draw() # Chose the noise mode - effect of start/stop button def RadioButtons_noise_on_off(self): # Change the mode variable depending on the Radiobutton state if self.rbs_noise[0].isChecked(): self.noise = 'ON' self.CartPoleInstance.NoiseAdderInstance.noise_mode = self.noise elif self.rbs_noise[1].isChecked(): self.noise = 'OFF' self.CartPoleInstance.NoiseAdderInstance.noise_mode = self.noise else: raise Exception('Something wrong with ON/OFF button for noise') self.open_additional_noise_widget() # endregion # region - Text Boxes # Read speedup provided by user from appropriate GUI textbox def get_speedup(self): """ Get speedup provided by user from appropriate textbox. Speed-up gives how many times faster or slower than real time the simulation or replay should run. The provided values may not always be reached due to computer speed limitation """ speedup = self.tx_speedup.text() if speedup == '': self.speedup = np.inf return True else: try: speedup = float(speedup) except ValueError: self.wrong_speedup_msg.setText( 'You have provided the input for speed-up which is not convertible to a number' ) x = self.wrong_speedup_msg.exec_() return False if speedup == 0.0: self.wrong_speedup_msg.setText( 'You cannot run an experiment with 0 speed-up (stopped time flow)' ) x = self.wrong_speedup_msg.exec_() return False else: self.speedup = speedup return True # endregion # region - Combo Boxes # Select how to interpolate between turning points of randomly chosen target positions def cb_interpolation_selectionchange(self, i): """ Select interpolation type for random target positions of randomly generated experiment """ self.CartPoleInstance.interpolation_type = self.cb_interpolation.currentText( ) # endregion # region - Check boxes # Action toggling between saving and not saving simulation results def cb_save_history_f(self, state): if state: self.save_history = 1 else: self.save_history = 0 if self.save_history or self.show_experiment_summary: self.CartPoleInstance.save_data_in_cart = True else: self.CartPoleInstance.save_data_in_cart = False # Action toggling between saving and not saving simulation results def cb_show_experiment_summary_f(self, state): if state: self.show_experiment_summary = 1 else: self.show_experiment_summary = 0 if self.save_history or self.show_experiment_summary: self.CartPoleInstance.save_data_in_cart = True else: self.CartPoleInstance.save_data_in_cart = False # Action toggling between stopping (or not) the pole if it reaches 90 deg def cb_stop_at_90_deg_f(self, state): if state: self.CartPoleInstance.stop_at_90 = True else: self.CartPoleInstance.stop_at_90 = False # Action toggling between updating CarPole slider value on click or by hoovering over it def cb_slider_on_click_f(self, state): if state: self.slider_on_click = True else: self.slider_on_click = False # endregion # region - Additional GUI Popups def open_additional_controller_widget(self): # Open up additional options widgets depending on the controller type if self.CartPoleInstance.controller_name == 'mppi': self.optionsControllerWidget = MPPIOptionsWindow() else: try: self.optionsControllerWidget.close() except: pass self.optionsControllerWidget = None def open_additional_noise_widget(self): # Open up additional options widgets depending on the controller type if self.noise == 'ON': self.optionsNoiseWidget = NoiseOptionsWindow() else: try: self.optionsNoiseWidget.close() except: pass self.optionsNoiseWidget = None # endregion # region - Sliders setting initial position and angle of the CartPole def update_initial_position(self, value: str): self.initial_state[POSITION_IDX] = float(value) / 1000.0 def update_initial_angle(self, value: str): self.initial_state[ANGLE_IDX] = float(value) / 100.0 # endregion # region - Slider setting latency of the controller def update_latency(self, value: str): latency_slider = float(value) latency = latency_slider * self.CartPoleInstance.LatencyAdderInstance.max_latency / self.LATENCY_SLIDER_RANGE_INT # latency in seconds self.CartPoleInstance.LatencyAdderInstance.set_latency(latency) self.labLatency.setText('{:.1f} ms'.format(latency * 1000.0)) # latency in ms # endregion # region Buttons for providing a kick to the pole def kick_pole(self): if self.sender().text() == "Left": self.CartPoleInstance.s[ANGLED_IDX] += .6 elif self.sender().text() == "Right": self.CartPoleInstance.s[ANGLED_IDX] -= .6
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 ConfigDialog(QDialog): def __init__(self, parent=None): super(ConfigDialog, self).__init__(parent) self.classifyExercises = parent.classifyExercises self.setFixedSize(500, 400) self.setWindowTitle("Model Configurations") self.epochValue = QLabel() self.vbox = QVBoxLayout() self.label_maximum = QLabel() self.label_minimum = QLabel() self.slider_hbox = QHBoxLayout() self.slider_vbox = QVBoxLayout() self.batchSizeMenu = QComboBox() self.properties = QFormLayout() self.epochSlider = Slider(orientation=Qt.Orientations.Horizontal) self.trainButton = QPushButton('Train Model') self.resultButton = QPushButton('Show result image') self.progress = QProgressBar() self.batchSizeMenu.addItems(['2', '4', '8', '16', '32', '64', '128']) self.batchSizeMenu.setCurrentIndex(3) self.batchSizeMenu.setMaximumWidth(100) self.initSlider() self.properties.addRow('Batch Size', self.batchSizeMenu) self.resultButton.setEnabled(False) self.actionsLayout = QHBoxLayout() self.actionsLayout.addWidget(self.trainButton) self.actionsLayout.addWidget(self.resultButton) self.optionsLayout = QVBoxLayout() self.optionsLayout.addWidget(QLabel('Model properties')) self.optionsLayout.addLayout(self.vbox) self.optionsLayout.addLayout(self.properties) self.optionsLayout.addLayout(self.actionsLayout) self.progress.setAlignment(QtCore.Qt.Alignment.AlignCenter) self.optionsLayout.addWidget(self.progress) # self.options_layout.addWidget(self.label) # self.options_layout.addWidget(self.list_widget) self.setLayout(self.optionsLayout) self.trainThread = TrainThread(self.classifyExercises) self.connections() print("init config") def initSlider(self): self.epochValue.setAlignment(QtCore.Qt.Alignment.AlignHCenter) self.epochSlider.setMinimum(2) self.epochSlider.setMaximum(10) self.epochSlider.setTickInterval(1) # self.epochSlider.setInterval(1) # self.epochSlider.setValue(8) # no idea why, but 8 is the middle somehow self.epochSlider.setSliderPosition(6) self.epochSlider.setTickPosition(QSlider.TickPosition.TicksBelow) self.label_minimum.setNum(self.epochSlider.minimum().real * 50) self.label_minimum.setAlignment(QtCore.Qt.Alignment.AlignLeft) self.label_maximum.setNum(self.epochSlider.maximum().real * 50) self.label_maximum.setAlignment(QtCore.Qt.Alignment.AlignRight) self.epochSlider.minimumChanged.connect(self.label_minimum.setNum) self.epochSlider.maximumChanged.connect(self.label_maximum.setNum) self.slider_hbox.addWidget(self.label_minimum, QtCore.Qt.Alignment.AlignLeft) self.slider_hbox.addWidget(self.epochValue) self.slider_hbox.addWidget(self.label_maximum, QtCore.Qt.Alignment.AlignRight) self.slider_vbox.addWidget(self.epochSlider) self.slider_vbox.addLayout(self.slider_hbox) # self.slider_vbox.addStretch() self.vbox.addLayout(self.slider_vbox) def connections(self): self.batchSizeMenu.currentIndexChanged.connect( self.onBatchSizeSelected) self.resultButton.clicked.connect(self.onResultClicked) self.trainButton.clicked.connect(self.onTrainClicked) self.epochSlider.valueChanged.connect(self.updateEpochValue) self.trainThread.taskFinished.connect(self.onFinished) def updateEpochValue(self, num): print(num) epochs = num * 50 self.epochValue.setNum(epochs) self.classifyExercises.epochs = epochs def onBatchSizeSelected(self, ind): self.classifyExercises.training_batch_size = int( self.batchSizeMenu.currentText()) def onResultClicked(self): print("open image") self.classifyExercises.DisplayResults() def onTrainClicked(self): if self.classifyExercises.subject is not None: if self.classifyExercises.DataAvailable(): if self.resultButton.isEnabled: self.resultButton.setEnabled(False) self.trainThread.start() self.progress.setRange(0, 0) else: CustomMessage.showDialog( "Message", "Calibrate for patient to obtain data.", QMessageBox.StandardButtons.Ok) else: CustomMessage.showDialog( "Message", "You must either select or enter a subject name.", QMessageBox.StandardButtons.Ok) print("Subject is none!") def onFinished(self): # Stop the progress self.progress.setRange(0, 1) self.progress.setValue(1) CustomMessage.showDialog("Message", "Training model finished!", QMessageBox.StandardButtons.Ok) self.resultButton.setEnabled(True)
class YTdownloader(QWidget): def __init__(self): super().__init__() # setup some flags self.isFetching = False self.isDownloading = False # default output path self.outputPath = f'{QDir.homePath()}/videos' # setup some window specific things self.setWindowTitle('YouTube Downloader') self.setWindowIcon(QIcon('assets/yt-icon.ico')) self.setFixedSize(705, 343) # parent layout layout = QVBoxLayout() layout.setContentsMargins(15, 15, 15, 10) self.setLayout(layout) # top bar layout topBar = QHBoxLayout() # detail section detailSec = QHBoxLayout() metaSec = QVBoxLayout() # download section downloadSec = QHBoxLayout() downloadBtn = QVBoxLayout() # output path link button self.outputBtn = QPushButton('📂 Output Path') self.outputBtn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.outputBtn.setToolTip(self.outputPath) self.outputBtn.clicked.connect(self.setOutputPath) # status bar self.statusBar = QStatusBar() # message box self.message = QMessageBox() # setting up widgets self.urlBox = QLineEdit() self.urlBox.setFocusPolicy(Qt.FocusPolicy.ClickFocus or Qt.FocusPolicy.NoFocus) self.urlBox.setPlaceholderText('🔍 Enter or paste video URL...') self.button = QPushButton('Get') self.button.setDefault(True) self.button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.button.clicked.connect(self.getDetails) # thumbnail pixmap = QPixmap('assets\placeholder.jpg') self.thumb = QLabel() self.thumb.setFixedSize(250, 141) self.thumb.setScaledContents(True) self.thumb.setPixmap(pixmap) # detail widgets self.title = QLabel('Title: ') self.author = QLabel('Author: ') self.length = QLabel('Duration: ') self.publish_date = QLabel('Published: ') # progress bar self.progress_bar = QProgressBar() # download options self.download = QComboBox() self.download.setPlaceholderText('Download Video') self.download.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download.activated.connect(lambda: self.getContent(0)) self.download.setEnabled(False) # download audio button self.download_audio = QPushButton('Download Audio') self.download_audio.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download_audio.clicked.connect(lambda: self.getContent(1)) self.download_audio.setEnabled(False) # add widgets and layouts topBar.addWidget(self.urlBox) topBar.addWidget(self.button) # detail section metaSec.addWidget(self.title) metaSec.addWidget(self.author) metaSec.addWidget(self.length) metaSec.addWidget(self.publish_date) detailSec.addWidget(self.thumb) detailSec.addSpacing(20) detailSec.addLayout(metaSec) # download section downloadBtn.addWidget(self.download) downloadBtn.addWidget(self.download_audio) downloadSec.addWidget(self.progress_bar) downloadSec.addSpacing(10) downloadSec.addLayout(downloadBtn) # status bar self.statusBar.setSizeGripEnabled(False) self.statusBar.addPermanentWidget(self.outputBtn) # add content to parent layout layout.addLayout(topBar) layout.addSpacing(20) layout.addLayout(detailSec) layout.addSpacing(5) layout.addLayout(downloadSec) layout.addWidget(self.statusBar) # setup a connection thread to keep checking internet connectivity self.connection = ConnectionThread() self.connection.start() # catch the connection response signal self.connection.con_response.connect(self.connection_slot) # connection slot def connection_slot(self, status): curMsg = self.statusBar.currentMessage() # connection succeeded if status: if curMsg == '🔴 Disconnected': self.statusBar.showMessage('🟢 Connection restored!', 3000) elif curMsg != '🟢 Connected': self.statusBar.showMessage('🟢 Connected') # connection failed elif curMsg == '🟢 Connected': self.statusBar.showMessage('🔴 Connection interrupted!', 3000) elif curMsg != '🔴 Disconnected': self.statusBar.showMessage('🔴 Disconnected') # set output path slot def setOutputPath(self): # update the output path path = str(QFileDialog.getExistingDirectory(self, "Select Output Directory")) if path: self.outputPath = path # update tooltip self.outputBtn.setToolTip(path) # get button slot def getDetails(self): curMsg = self.statusBar.currentMessage() if curMsg == '🔴 Disconnected' or curMsg == '🔴 Connection interrupted!': self.message.critical( self, 'Error', 'Connection failed!\nAre you sure you\'re connected to the internet ? ' ) elif self.button.text() == 'Get': self.button.setText('Stop') # indicate progress bar as busy self.progress_bar.setRange(0, 0) # set fetching flag self.isFetching = True # setup a worker thread to keep UI responsive self.worker = WorkerThread(self.urlBox.text()) self.worker.start() # catch the finished signal self.worker.finished.connect(self.finished_slot) # catch the response signal self.worker.worker_response.connect(self.response_slot) # catch the error signal self.worker.worker_err_response.connect(self.err_slot) elif self.button.text() == 'Stop': if self.isFetching: # stop worker thread self.worker.terminate() # set back the button text self.button.setText('Get') elif self.isDownloading: # stop download thread self.download_thread.terminate() # show the warning message self.message.information( self, 'Interrupted', 'Download interrupted!\nThe process was aborted while the file was being downloaded... ' ) # reset pogress bar self.progress_bar.reset() # download options slot def getContent(self, id): if self.isFetching: # show the warning message self.message.warning( self, 'Warning', 'Please wait!\nWait while the details are being fetched... ' ) else: # disable the download options self.download.setDisabled(True) self.download_audio.setDisabled(True) # set downloading flag self.isDownloading = True # set button to stop self.button.setText('Stop') # setup download thread if id == 0: self.download_thread = DownloadThread(self.yt, self.download.currentText()[:4], self.outputPath) else: self.download_thread = DownloadThread(self.yt, 'audio', self.outputPath) # start the thread self.download_thread.start() # catch the finished signal self.download_thread.finished.connect(self.download_finished_slot) # catch the response signal self.download_thread.download_response.connect(self.download_response_slot) # catch the complete signal self.download_thread.download_complete.connect(self.download_complete_slot) # catch the error signal self.download_thread.download_err.connect(self.download_err_slot) # finished slot def finished_slot(self): # remove progress bar busy indication self.progress_bar.setRange(0, 100) # unset fetching flag self.isFetching = False # response slot def response_slot(self, res): # set back the button text self.button.setText('Get') # save the yt object for speeding up download self.yt = res[0] # set the actual thumbnail of requested video self.thumb.setPixmap(res[1]) # slice the title if it is more than the limit if len(res[2]) > 50: self.title.setText(f'Title: {res[2][:50]}...') else: self.title.setText(f'Title: {res[2]}') # set leftover details self.author.setText(f'Author: {res[3]}') self.length.setText(f'Duration: {timedelta(seconds=res[4])}') self.publish_date.setText(f'Published: {res[5].strftime("%d/%m/%Y")}') # clear any previous items if any self.download.clear() # add resolutions as items to the download button and enable them self.download.addItems([item for item in res[6]]) self.download.setDisabled(False) self.download_audio.setDisabled(False) # error slot def err_slot(self): # show the warning message self.message.warning( self, 'Warning', 'Something went wrong!\nProbably a broken link or some restricted content... ' ) # set back the button text self.button.setText('Get') # download finished slot def download_finished_slot(self): # set back the button text self.button.setText('Get') # now enable the download options self.download.setDisabled(False) self.download_audio.setDisabled(False) # unset downloading flag self.isDownloading = False # reset pogress bar self.progress_bar.reset() # download response slot def download_response_slot(self, per): # update progress bar self.progress_bar.setValue(per) # adjust the font color to maintain the contrast if per > 52: self.progress_bar.setStyleSheet('QProgressBar { color: #fff }') else: self.progress_bar.setStyleSheet('QProgressBar { color: #000 }') # download complete slot def download_complete_slot(self, location): # use native separators location = QDir.toNativeSeparators(location) # show the success message if self.message.information( self, 'Downloaded', f'Download complete!\nFile was successfully downloaded to :\n{location}\n\nOpen the downloaded file now ?', QMessageBox.StandardButtons.Open, QMessageBox.StandardButtons.Cancel ) is QMessageBox.StandardButtons.Open: subprocess.Popen(f'explorer /select,{location}') # download error slot def download_err_slot(self): # show the error message self.message.critical( self, 'Error', 'Error!\nSomething unusual happened and was unable to download...' )
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 MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupTrayicon() self.setupVariables() self.setupUi() self.setupConnections() self.show() def setupVariables(self): settings = QSettings() self.workEndTime = QTime( int(settings.value(workHoursKey, 0)), int(settings.value(workMinutesKey, 25)), int(settings.value(workSecondsKey, 0)), ) self.restEndTime = QTime( int(settings.value(restHoursKey, 0)), int(settings.value(restMinutesKey, 5)), int(settings.value(restSecondsKey, 0)), ) self.timeFormat = "hh:mm:ss" self.time = QTime(0, 0, 0, 0) self.workTime = QTime(0, 0, 0, 0) self.restTime = QTime(0, 0, 0, 0) self.totalTime = QTime(0, 0, 0, 0) self.currentMode = Mode.work self.maxRepetitions = -1 self.currentRepetitions = 0 def setupConnections(self): """ Create button connections """ self.startButton.clicked.connect(self.startTimer) self.startButton.clicked.connect( lambda: self.startButton.setDisabled(True)) self.startButton.clicked.connect( lambda: self.pauseButton.setDisabled(False)) self.startButton.clicked.connect( lambda: self.resetButton.setDisabled(False)) self.pauseButton.clicked.connect(self.pauseTimer) self.pauseButton.clicked.connect( lambda: self.startButton.setDisabled(False)) self.pauseButton.clicked.connect( lambda: self.pauseButton.setDisabled(True)) self.pauseButton.clicked.connect( lambda: self.resetButton.setDisabled(False)) self.pauseButton.clicked.connect( lambda: self.startButton.setText("continue")) self.resetButton.clicked.connect(self.resetTimer) self.resetButton.clicked.connect( lambda: self.startButton.setDisabled(False)) self.resetButton.clicked.connect( lambda: self.pauseButton.setDisabled(True)) self.resetButton.clicked.connect( lambda: self.resetButton.setDisabled(True)) self.resetButton.clicked.connect( lambda: self.startButton.setText("start")) self.acceptTaskButton.pressed.connect(self.insertTask) self.deleteTaskButton.pressed.connect(self.deleteTask) """ Create spinbox connections """ self.workHoursSpinBox.valueChanged.connect(self.updateWorkEndTime) self.workMinutesSpinBox.valueChanged.connect(self.updateWorkEndTime) self.workSecondsSpinBox.valueChanged.connect(self.updateWorkEndTime) self.restHoursSpinBox.valueChanged.connect(self.updateRestEndTime) self.restMinutesSpinBox.valueChanged.connect(self.updateRestEndTime) self.restSecondsSpinBox.valueChanged.connect(self.updateRestEndTime) self.repetitionsSpinBox.valueChanged.connect(self.updateMaxRepetitions) """ Create combobox connections """ self.modeComboBox.currentTextChanged.connect(self.updateCurrentMode) """ Create tablewidget connections """ self.tasksTableWidget.cellDoubleClicked.connect( self.markTaskAsFinished) def setupUi(self): self.size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) """ Create tabwidget """ self.tabWidget = QTabWidget() """ Create tab widgets """ timerWidget = self.setupTimerTab() tasksWidget = self.setupTasksTab() statisticsWidget = self.setupStatisticsTab() """ add tab widgets to tabwidget""" self.timerTab = self.tabWidget.addTab(timerWidget, makeIcon("timer"), "Timer") self.tasksTab = self.tabWidget.addTab(tasksWidget, makeIcon("tasks"), "Tasks") self.statisticsTab = self.tabWidget.addTab(statisticsWidget, makeIcon("statistics"), "Statistics") """ Set mainwindows central widget """ self.setCentralWidget(self.tabWidget) def setupTimerTab(self): settings = QSettings() self.timerContainer = QWidget(self) self.timerContainerLayout = QVBoxLayout(self.timerContainer) self.timerContainer.setLayout(self.timerContainerLayout) """ Create work groupbox""" self.workGroupBox = QGroupBox("Work") self.workGroupBoxLayout = QHBoxLayout(self.workGroupBox) self.workGroupBox.setLayout(self.workGroupBoxLayout) self.workHoursSpinBox = QSpinBox( minimum=0, maximum=24, value=int(settings.value(workHoursKey, 0)), suffix="h", sizePolicy=self.size_policy, ) self.workMinutesSpinBox = QSpinBox( minimum=0, maximum=60, value=int(settings.value(workMinutesKey, 25)), suffix="m", sizePolicy=self.size_policy, ) self.workSecondsSpinBox = QSpinBox( minimum=0, maximum=60, value=int(settings.value(workSecondsKey, 0)), suffix="s", sizePolicy=self.size_policy, ) """ Create rest groupbox""" self.restGroupBox = QGroupBox("Rest") self.restGroupBoxLayout = QHBoxLayout(self.restGroupBox) self.restGroupBox.setLayout(self.restGroupBoxLayout) self.restHoursSpinBox = QSpinBox( minimum=0, maximum=24, value=int(settings.value(restHoursKey, 0)), suffix="h", sizePolicy=self.size_policy, ) self.restMinutesSpinBox = QSpinBox( minimum=0, maximum=60, value=int(settings.value(restMinutesKey, 5)), suffix="m", sizePolicy=self.size_policy, ) self.restSecondsSpinBox = QSpinBox( minimum=0, maximum=60, value=int(settings.value(restSecondsKey, 0)), suffix="s", sizePolicy=self.size_policy, ) self.restGroupBoxLayout.addWidget(self.restHoursSpinBox) self.restGroupBoxLayout.addWidget(self.restMinutesSpinBox) self.restGroupBoxLayout.addWidget(self.restSecondsSpinBox) """ Create other groupbox""" self.otherGroupBox = QGroupBox("Other") self.otherGroupBoxLayout = QHBoxLayout(self.otherGroupBox) self.otherGroupBox.setLayout(self.otherGroupBoxLayout) self.repetitionsLabel = QLabel("Repetitions") self.repetitionsSpinBox = QSpinBox( minimum=0, maximum=10000, value=0, sizePolicy=self.size_policy, specialValueText="∞", ) self.modeLabel = QLabel("Mode") self.modeComboBox = QComboBox(sizePolicy=self.size_policy) self.modeComboBox.addItems(["work", "rest"]) self.otherGroupBoxLayout.addWidget(self.repetitionsLabel) self.otherGroupBoxLayout.addWidget(self.repetitionsSpinBox) self.otherGroupBoxLayout.addWidget(self.modeLabel) self.otherGroupBoxLayout.addWidget(self.modeComboBox) """ Create timer groupbox""" self.lcdDisplayGroupBox = QGroupBox("Time") self.lcdDisplayGroupBoxLayout = QHBoxLayout(self.lcdDisplayGroupBox) self.lcdDisplayGroupBox.setLayout(self.lcdDisplayGroupBoxLayout) self.timeDisplay = QLCDNumber(8, sizePolicy=self.size_policy) self.timeDisplay.setFixedHeight(100) self.timeDisplay.display("00:00:00") self.lcdDisplayGroupBoxLayout.addWidget(self.timeDisplay) """ Create pause, start and reset buttons""" self.buttonContainer = QWidget() self.buttonContainerLayout = QHBoxLayout(self.buttonContainer) self.buttonContainer.setLayout(self.buttonContainerLayout) self.startButton = self.makeButton("start", disabled=False) self.resetButton = self.makeButton("reset") self.pauseButton = self.makeButton("pause") """ Add widgets to container """ self.workGroupBoxLayout.addWidget(self.workHoursSpinBox) self.workGroupBoxLayout.addWidget(self.workMinutesSpinBox) self.workGroupBoxLayout.addWidget(self.workSecondsSpinBox) self.timerContainerLayout.addWidget(self.workGroupBox) self.timerContainerLayout.addWidget(self.restGroupBox) self.timerContainerLayout.addWidget(self.otherGroupBox) self.timerContainerLayout.addWidget(self.lcdDisplayGroupBox) self.buttonContainerLayout.addWidget(self.pauseButton) self.buttonContainerLayout.addWidget(self.startButton) self.buttonContainerLayout.addWidget(self.resetButton) self.timerContainerLayout.addWidget(self.buttonContainer) return self.timerContainer def setupTasksTab(self): settings = QSettings() """ Create vertical tasks container """ self.tasksWidget = QWidget(self.tabWidget) self.tasksWidgetLayout = QVBoxLayout(self.tasksWidget) self.tasksWidget.setLayout(self.tasksWidgetLayout) """ Create horizontal input container """ self.inputContainer = QWidget() self.inputContainer.setFixedHeight(50) self.inputContainerLayout = QHBoxLayout(self.inputContainer) self.inputContainerLayout.setContentsMargins(0, 0, 0, 0) self.inputContainer.setLayout(self.inputContainerLayout) """ Create text edit """ self.taskTextEdit = QTextEdit( placeholderText="Describe your task briefly.", undoRedoEnabled=True) """ Create vertical buttons container """ self.inputButtonContainer = QWidget() self.inputButtonContainerLayout = QVBoxLayout( self.inputButtonContainer) self.inputButtonContainerLayout.setContentsMargins(0, 0, 0, 0) self.inputButtonContainer.setLayout(self.inputButtonContainerLayout) """ Create buttons """ self.acceptTaskButton = QToolButton(icon=makeIcon("check")) self.deleteTaskButton = QToolButton(icon=makeIcon("trash")) """ Create tasks tablewidget """ self.tasksTableWidget = QTableWidget(0, 1) self.tasksTableWidget.setHorizontalHeaderLabels(["Tasks"]) self.tasksTableWidget.horizontalHeader().setStretchLastSection(True) self.tasksTableWidget.verticalHeader().setVisible(False) self.tasksTableWidget.setWordWrap(True) self.tasksTableWidget.setTextElideMode(Qt.TextElideMode.ElideNone) self.tasksTableWidget.setEditTriggers( QAbstractItemView.EditTriggers.NoEditTriggers) self.tasksTableWidget.setSelectionMode( QAbstractItemView.SelectionMode.SingleSelection) self.insertTasks(*settings.value(tasksKey, [])) """ Add widgets to container widgets """ self.inputButtonContainerLayout.addWidget(self.acceptTaskButton) self.inputButtonContainerLayout.addWidget(self.deleteTaskButton) self.inputContainerLayout.addWidget(self.taskTextEdit) self.inputContainerLayout.addWidget(self.inputButtonContainer) self.tasksWidgetLayout.addWidget(self.inputContainer) self.tasksWidgetLayout.addWidget(self.tasksTableWidget) return self.tasksWidget def setupStatisticsTab(self): """ Create statistics container """ self.statisticsContainer = QWidget() self.statisticsContainerLayout = QVBoxLayout(self.statisticsContainer) self.statisticsContainer.setLayout(self.statisticsContainerLayout) """ Create work time groupbox """ self.statisticsWorkTimeGroupBox = QGroupBox("Work Time") self.statisticsWorkTimeGroupBoxLayout = QHBoxLayout() self.statisticsWorkTimeGroupBox.setLayout( self.statisticsWorkTimeGroupBoxLayout) self.statisticsWorkTimeDisplay = QLCDNumber(8) self.statisticsWorkTimeDisplay.display("00:00:00") self.statisticsWorkTimeGroupBoxLayout.addWidget( self.statisticsWorkTimeDisplay) """ Create rest time groupbox """ self.statisticsRestTimeGroupBox = QGroupBox("Rest Time") self.statisticsRestTimeGroupBoxLayout = QHBoxLayout() self.statisticsRestTimeGroupBox.setLayout( self.statisticsRestTimeGroupBoxLayout) self.statisticsRestTimeDisplay = QLCDNumber(8) self.statisticsRestTimeDisplay.display("00:00:00") self.statisticsRestTimeGroupBoxLayout.addWidget( self.statisticsRestTimeDisplay) """ Create total time groupbox """ self.statisticsTotalTimeGroupBox = QGroupBox("Total Time") self.statisticsTotalTimeGroupBoxLayout = QHBoxLayout() self.statisticsTotalTimeGroupBox.setLayout( self.statisticsTotalTimeGroupBoxLayout) self.statisticsTotalTimeDisplay = QLCDNumber(8) self.statisticsTotalTimeDisplay.display("00:00:00") self.statisticsTotalTimeGroupBoxLayout.addWidget( self.statisticsTotalTimeDisplay) """ Add widgets to container """ self.statisticsContainerLayout.addWidget( self.statisticsTotalTimeGroupBox) self.statisticsContainerLayout.addWidget( self.statisticsWorkTimeGroupBox) self.statisticsContainerLayout.addWidget( self.statisticsRestTimeGroupBox) return self.statisticsContainer def setupTrayicon(self): self.trayIcon = QSystemTrayIcon(makeIcon("tomato")) self.trayIcon.setContextMenu(QMenu()) self.quitAction = self.trayIcon.contextMenu().addAction( makeIcon("exit"), "Quit", self.exit) self.quitAction.triggered.connect(self.exit) self.trayIcon.activated.connect(self.onActivate) self.trayIcon.show() self.trayIcon.setToolTip("Pomodoro") self.toast = ToastNotifier() def leaveEvent(self, event): super(MainWindow, self).leaveEvent(event) self.tasksTableWidget.clearSelection() def closeEvent(self, event): super(MainWindow, self).closeEvent(event) settings = QSettings() settings.setValue(workHoursKey, self.workHoursSpinBox.value()) settings.setValue( workMinutesKey, self.workMinutesSpinBox.value(), ) settings.setValue( workSecondsKey, self.workSecondsSpinBox.value(), ) settings.setValue(restHoursKey, self.restHoursSpinBox.value()) settings.setValue( restMinutesKey, self.restMinutesSpinBox.value(), ) settings.setValue( restSecondsKey, self.restSecondsSpinBox.value(), ) tasks = [] for i in range(self.tasksTableWidget.rowCount()): item = self.tasksTableWidget.item(i, 0) if not item.font().strikeOut(): tasks.append(item.text()) settings.setValue(tasksKey, tasks) def startTimer(self): try: if not self.timer.isActive(): self.createTimer() except: self.createTimer() def createTimer(self): self.timer = QTimer() self.timer.timeout.connect(self.updateTime) self.timer.timeout.connect(self.maybeChangeMode) self.timer.setInterval(1000) self.timer.setSingleShot(False) self.timer.start() def pauseTimer(self): try: self.timer.stop() self.timer.disconnect() except: pass def resetTimer(self): try: self.pauseTimer() self.time = QTime(0, 0, 0, 0) self.displayTime() except: pass def maybeStartTimer(self): if self.currentRepetitions != self.maxRepetitions: self.startTimer() started = True else: self.currentRepetitions = 0 started = False return started def updateWorkEndTime(self): self.workEndTime = QTime( self.workHoursSpinBox.value(), self.workMinutesSpinBox.value(), self.workSecondsSpinBox.value(), ) def updateRestEndTime(self): self.restEndTime = QTime( self.restHoursSpinBox.value(), self.restMinutesSpinBox.value(), self.restSecondsSpinBox.value(), ) def updateCurrentMode(self, mode: str): self.currentMode = Mode.work if mode == "work" else Mode.rest def updateTime(self): self.time = self.time.addSecs(1) self.totalTime = self.totalTime.addSecs(1) if self.modeComboBox.currentText() == "work": self.workTime = self.workTime.addSecs(1) else: self.restTime = self.restTime.addSecs(1) self.displayTime() def updateMaxRepetitions(self, value): if value == 0: self.currentRepetitions = 0 self.maxRepetitions = -1 else: self.maxRepetitions = 2 * value def maybeChangeMode(self): if self.currentMode is Mode.work and self.time >= self.workEndTime: self.resetTimer() self.modeComboBox.setCurrentIndex(1) self.incrementCurrentRepetitions() started = self.maybeStartTimer() self.showWindowMessage( Status.workFinished if started else Status.repetitionsReached) if not started: self.resetButton.click() elif self.currentMode is Mode.rest and self.time >= self.restEndTime: self.resetTimer() self.modeComboBox.setCurrentIndex(0) self.incrementCurrentRepetitions() started = self.maybeStartTimer() self.showWindowMessage( Status.restFinished if started else Status.repetitionsReached) if not started: self.resetButton.click() def incrementCurrentRepetitions(self): if self.maxRepetitions > 0: self.currentRepetitions += 1 def insertTask(self): task = self.taskTextEdit.toPlainText() self.insertTasks(task) def insertTasks(self, *tasks): for task in tasks: if task: rowCount = self.tasksTableWidget.rowCount() self.tasksTableWidget.setRowCount(rowCount + 1) self.tasksTableWidget.setItem(rowCount, 0, QTableWidgetItem(task)) self.tasksTableWidget.resizeRowsToContents() self.taskTextEdit.clear() def deleteTask(self): selectedIndexes = self.tasksTableWidget.selectedIndexes() if selectedIndexes: self.tasksTableWidget.removeRow(selectedIndexes[0].row()) def markTaskAsFinished(self, row, col): item = self.tasksTableWidget.item(row, col) font = self.tasksTableWidget.item(row, col).font() font.setStrikeOut(False if item.font().strikeOut() else True) item.setFont(font) def displayTime(self): self.timeDisplay.display(self.time.toString(self.timeFormat)) self.statisticsRestTimeDisplay.display( self.restTime.toString(self.timeFormat)) self.statisticsWorkTimeDisplay.display( self.workTime.toString(self.timeFormat)) self.statisticsTotalTimeDisplay.display( self.totalTime.toString(self.timeFormat)) def showWindowMessage(self, status): if status is Status.workFinished: title, text = "Break", choice(work_finished_phrases) elif status is Status.restFinished: title, text = "Work", choice(rest_finished_phrases) else: title, text = "Finished", choice(work_finished_phrases) self.trayIcon.showMessage(title, text, makeIcon("tomato")) self.toast.show_toast(title, text, icon_path="pomodoro/data/icons/tomato.ico", duration=10, threaded=True) def makeButton(self, text, iconName=None, disabled=True): button = QPushButton(text, sizePolicy=self.size_policy) if iconName: button.setIcon(makeIcon(iconName)) button.setDisabled(disabled) return button def exit(self): self.close() app = QApplication.instance() if app: app.quit() def onActivate(self, reason): if reason == QSystemTrayIcon.ActivationReason.Trigger: self.show()
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 MoveFileDialog(QDialog): '''移动文件对话框''' new_infos = pyqtSignal(object) def __init__(self, parent=None): super(MoveFileDialog, self).__init__(parent) self.infos = None self.dirs = {} self.initUI() self.setStyleSheet(dialog_qss_style) def update_ui(self): names = "\n".join([i.name for i in self.infos]) names_tip = "\n".join([i.name for i in self.infos]) self.tx_name.setText(names) self.tx_name.setToolTip(names_tip) self.tx_new_path.clear() f_icon = QIcon(SRC_DIR + "folder.gif") for f_name, fid in self.dirs.items(): if len(f_name) > 50: # 防止文件夹名字过长? f_name = f_name[:47] + "..." self.tx_new_path.addItem(f_icon, "id:{:>8},name:{}".format(fid, f_name)) def set_values(self, infos, all_dirs_dict): self.infos = infos self.dirs = all_dirs_dict self.update_ui() self.exec() def initUI(self): self.setWindowTitle("移动文件(夹)") self.setWindowIcon(QIcon(SRC_DIR + "move.ico")) self.lb_name = QLabel() self.lb_name.setText("文件(夹)名:") self.lb_name.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.tx_name = AutoResizingTextEdit() self.tx_name.setFocusPolicy(Qt.FocusPolicy.NoFocus) # 只读 self.tx_name.setReadOnly(True) self.lb_new_path = QLabel() self.lb_new_path.setText("目标文件夹:") self.lb_new_path.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.tx_new_path = QComboBox() self.buttonBox = QDialogButtonBox() self.buttonBox.setOrientation(Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定") self.buttonBox.button( QDialogButtonBox.StandardButton.Cancel).setText("取消") self.grid = QGridLayout() self.grid.setSpacing(10) self.grid.addWidget(self.lb_name, 1, 0) self.grid.addWidget(self.tx_name, 1, 1) self.grid.addWidget(self.lb_new_path, 2, 0) self.grid.addWidget(self.tx_new_path, 2, 1) self.grid.addWidget(self.buttonBox, 3, 0, 1, 2) self.setLayout(self.grid) self.buttonBox.accepted.connect(self.btn_ok) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.setMinimumWidth(280) def btn_ok(self): new_id = self.tx_new_path.currentText().split(",")[0].split(":")[1] for index in range(len(self.infos)): self.infos[index].new_id = int(new_id) self.new_infos.emit(self.infos)