class AttributeCreatorWidget(CustomScrollableListItem): def __init__(self, document_base_creator_widget) -> None: super(AttributeCreatorWidget, self).__init__(document_base_creator_widget) self.document_base_creator_widget = document_base_creator_widget self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(10) self.name = QLineEdit() self.name.setFont(CODE_FONT_BOLD) self.name.setStyleSheet("border: none") self.layout.addWidget(self.name) self.delete_button = QPushButton() self.delete_button.setIcon(QIcon("aset_ui/resources/trash.svg")) self.delete_button.setFlat(True) self.delete_button.clicked.connect(self._delete_button_clicked) self.layout.addWidget(self.delete_button) def update_item(self, item, params=None): self.name.setText(item) def _delete_button_clicked(self): self.document_base_creator_widget.delete_attribute(self.name.text()) def enable_input(self): self.delete_button.setEnabled(True) def disable_input(self): self.delete_button.setEnabled(False)
class NuggetListWidget(QWidget): def __init__(self, interactive_matching_widget): super(NuggetListWidget, self).__init__(interactive_matching_widget) self.interactive_matching_widget = interactive_matching_widget self.layout = QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(10) # top widget self.top_widget = QWidget() self.top_layout = QHBoxLayout(self.top_widget) self.top_layout.setContentsMargins(0, 0, 0, 0) self.top_layout.setSpacing(10) self.layout.addWidget(self.top_widget) self.description = QLabel( "Below you see a list of guessed matches for you to confirm or correct." ) self.description.setFont(LABEL_FONT) self.top_layout.addWidget(self.description) self.stop_button = QPushButton("Continue With Next Attribute") self.stop_button.setFont(BUTTON_FONT) self.stop_button.clicked.connect(self._stop_button_clicked) self.stop_button.setMaximumWidth(240) self.top_layout.addWidget(self.stop_button) # nugget list self.nugget_list = CustomScrollableList(self, NuggetListItemWidget) self.layout.addWidget(self.nugget_list) def update_nuggets(self, nuggets): max_start_chars = max([ nugget[CachedContextSentenceSignal]["start_char"] for nugget in nuggets ]) self.nugget_list.update_item_list(nuggets, max_start_chars) def _stop_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task( {"message": "stop-interactive-matching"}) def enable_input(self): self.stop_button.setEnabled(True) self.nugget_list.enable_input() def disable_input(self): self.stop_button.setDisabled(True) self.nugget_list.disable_input()
class MainWidget(QWidget): def __init__(self, parent=None): super(MainWidget, self).__init__(parent) self.setWindowTitle("QThread Demo") self.thread = Worker() self.listFile = QListWidget() self.buttonStart = QPushButton("开始") layout = QGridLayout(self) layout.addWidget(self.listFile, 0, 0, 1, 2) layout.addWidget(self.buttonStart, 1, 1) self.buttonStart.clicked.connect(self.slotStart) self.thread.sinOut.connect(self.slodAdd) def slodAdd(self, file_inf): self.listFile.addItem(file_inf) def slotStart(self): self.buttonStart.setEnabled(False) self.thread.start()
def exerciseLayout(self, index): hLayout = QHBoxLayout() setSpinBox = QSpinBox() setSpinBox.setRange(1, 100) setSpinBox.setValue(5) setSpinBox.setMaximumWidth(80) startButton = QPushButton(self.classifyExercises.exercises[index].name) startButton.setMaximumWidth(120) assignedKey = QPushButton( self.classifyExercises.exercises[index].assigned_key[0]) assignedKey.setStyleSheet('background-color: grey; color: white;') assignedKey.setMaximumWidth(50) assignedKey.setEnabled(False) progress = QProgressBar() progress.setMaximumWidth(150) progress.setValue(randint(1, 100)) progress.setAlignment(QtCore.Qt.Alignment.AlignCenter) latency = QLabel(str(randint(50, 250)) + 'ms') avgLatency = QLabel(str(randint(50, 250)) + 'ms') hLayout.addWidget(setSpinBox) hLayout.setAlignment(setSpinBox, QtCore.Qt.Alignment.AlignHCenter) hLayout.addWidget(startButton) hLayout.setAlignment(startButton, QtCore.Qt.Alignment.AlignHCenter) hLayout.addWidget(assignedKey) hLayout.setAlignment(assignedKey, QtCore.Qt.Alignment.AlignHCenter) hLayout.addWidget(progress) hLayout.setAlignment(progress, QtCore.Qt.Alignment.AlignHCenter) hLayout.addWidget(latency) hLayout.setAlignment(latency, QtCore.Qt.Alignment.AlignHCenter) hLayout.addWidget(avgLatency) hLayout.setAlignment(avgLatency, QtCore.Qt.Alignment.AlignHCenter) hLayout.setContentsMargins(0, 10, 0, 10) self.exerciseLayouts.append(hLayout) return hLayout
class WinForm(QWidget): def __init__(self, parent=None): super(WinForm, self).__init__(parent) self.setWindowTitle("QTimer demo") self.listFile = QListWidget() self.label = QLabel("显示当前时间") self.startButton = QPushButton("开始") self.endButton = QPushButton("结束") layout = QGridLayout(self) # 初始化定时器 self.timer = QTimer(self) # 显示时间 self.timer.timeout.connect( self.showTime) # timeout 信号连接到特定的槽,当定时器超时,发出 timeout 信号 layout.addWidget(self.label, 0, 0, 1, 2) layout.addWidget(self.startButton, 1, 0) layout.addWidget(self.endButton, 1, 1) self.startButton.clicked.connect(self.start_timer) self.endButton.clicked.connect(self.end_timer) self.setLayout(layout) def showTime(self): # 获取当前系统时间 time = QDateTime.currentDateTime() # 设置时间格式 timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd") self.label.setText(timeDisplay) def start_timer(self): # 设置时间间隔并启动定时器 self.timer.start(1000) # start 内设置时间间隔,启动或重新启动计时器,如果计时器在运行,则重启 self.startButton.setEnabled(False) self.endButton.setEnabled(True) def end_timer(self): self.timer.stop() # 停止计时器 self.startButton.setEnabled(True) self.endButton.setEnabled(False)
class SaveAndCloseActions(QWidget): def __init__(self, parent): super(QWidget, self).__init__(parent) self.parent = parent self.saveButton = QPushButton("Save") self.saveButton.setEnabled(False) closeButton = QPushButton("Close") closeButton.clicked.connect(self.closeOnClick) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self.saveButton) hbox.addWidget(closeButton) self.setLayout(hbox) def closeOnClick(self): self.parent.close() def enableSave(self): self.saveButton.setEnabled(True) def disableSave(self): self.saveButton.setEnabled(False)
class UI(QWidget): update_signal = pyqtSignal(str) show_qr_signal = pyqtSignal(bytes) finish_signal = pyqtSignal() close_qr_signal = pyqtSignal() def __init__(self): super().__init__() self.logger = logging.getLogger(__name__) formatter = logging.Formatter( fmt="%(asctime)s-%(levelname)s-%(message)s", datefmt="%Y-%m-%d %H:%M:%S") filehandler = logging.FileHandler(filename="logs.log", mode="w", encoding="utf-8") handler = QLogger(update_signal=self.update_signal) handler.setLevel(logging.INFO) filehandler.setLevel(logging.INFO) self.logger.setLevel(logging.INFO) if os.path.exists("config.json") == False: self.gen_conf() with open(file="config.json", mode="r", encoding="utf-8") as conf_reader: conf = json.loads(conf_reader.read()) debug = bool(conf["debug"]) if debug == True: handler.setLevel(logging.DEBUG) filehandler.setLevel(logging.DEBUG) self.logger.setLevel(logging.DEBUG) handler.setFormatter(formatter) filehandler.setFormatter(formatter) self.logger.addHandler(handler) self.logger.addHandler(filehandler) self.logger.debug("当前调试状态:%s" % debug) self.resize(1024, 768) self.setWindowOpacity(0.9) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setWindowFlag(Qt.WindowFlags.FramelessWindowHint) self.setAutoFillBackground(True) self.work = Work(show_qr_signal=self.show_qr_signal, finish_signal=self.finish_signal, close_qr_signal=self.close_qr_signal) self.work_thread = QThread() self.work.moveToThread(self.work_thread) self.main_layout = QGridLayout() self.setLayout(self.main_layout) self.title = QLabel("ChinaUniOnlineGUI") self.title.setStyleSheet( "QLabel{border:none;border-radius:5px;background:transparent;color:#9AD3BC;font-size:60px;}" ) self.title.setAlignment(Qt.Alignment.AlignCenter) handler.widget.setStyleSheet( "QPlainTextEdit{font-family:Microsoft YaHei;background:#F3EAC2;border:none;border-radius:5px;}QScrollBar:vertical,QScrollBar::handle:vertical{background:#F3EAC2;border:none;border-radius:8px;width:16px;}QScrollBar::handle:vertical:hover{background:#F5B461;}QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background:#FFFDF9;border:none;border-radius:8px;width:16px;}QScrollBar::down-arrow:vertical,QScrollBar::up-arrow:vertical{background:#F5B461;border:none;border-radius:8px;width:16px;height:16px;}QScrollBar::sub-line:vertical,QScrollBar::add-line:vertical{background:transparent;border:none;}" ) self.control = QVBoxLayout() self.control_close = QPushButton() self.control_close.setToolTip("关闭") self.control_close.setStyleSheet( "QPushButton{background:#FFE3ED;border-radius:5px;border:none;}QPushButton:hover{background:#EC524B;}" ) self.contron_max = QPushButton() if self.isMaximized() == False: self.contron_max.setToolTip("最大化") else: self.contron_max.setToolTip("还原") self.contron_max.setStyleSheet( "QPushButton{background:#FFFDF9;border-radius:5px;border:none;}QPushButton:hover{background:#F5B461;}" ) self.control_min = QPushButton() self.control_min.setToolTip("最小化") self.control_min.setStyleSheet( "QPushButton{background:#BEEBE9;border-radius:5px;border:none;}QPushButton:hover{background:#F3EAC2;}" ) self.start_button = QPushButton("开始(&S)") self.start_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) self.start_button.setToolTip("开始") self.start_button.setFixedSize(120, 60) self.start_button.setDefault(True) setting_button = QPushButton("设置") setting_button.setToolTip("设置") setting_button.setFixedSize(60, 60) setting_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) setting_button.clicked.connect(self.setting_callback) start = QHBoxLayout() start.addWidget(self.start_button, 2) start.addWidget(setting_button, 1) self.control_close.clicked.connect(self.close) self.control_min.clicked.connect(self.min_callback) self.contron_max.clicked.connect(self.max_callback) self.start_button.clicked.connect(self.start_callback) self.work_thread.started.connect(self.work.start) self.finish_signal.connect(self.finish_callback) self.close_qr_signal.connect(self.close_qr) self.control.addWidget(self.control_min) self.control.addWidget(self.contron_max) self.control.addWidget(self.control_close) self.main_layout.addLayout(self.control, 0, 0) self.main_layout.addWidget(self.title, 0, 1) self.main_layout.addLayout(start, 0, 2) self.main_layout.addWidget(handler.widget, 1, 1) self.update_signal.connect(handler.widget.appendPlainText) handler.widget.textChanged.connect(handler.scroll_widget_to_bottom) self.show_qr_signal.connect(self.show_qr) self.logger.debug("已初始化UI") def min_callback(self): if self.isMinimized() == False: self.showMinimized() def max_callback(self): if self.isMaximized() == False: self.showMaximized() self.contron_max.setToolTip("还原") else: self.showNormal() self.contron_max.setToolTip("最大化") def start_callback(self): self.start_time = time.time() self.work_thread.start() self.start_button.setEnabled(False) self.start_button.setText("执行中...") def finish_callback(self): self.start_button.setEnabled(True) self.start_button.setText("开始") passed_time = time.time() - self.start_time mins, secs = divmod(passed_time, 60) hours, mins = divmod(mins, 60) self.logger.info("执行完成,共计用时 {:0>2d}:{:0>2d}:{:0>2d}".format( int(hours), int(mins), int(secs))) def show_qr(self, qr: bytes): title_label = QLabel("请使用微信扫描小程序码完成登陆") title_label.setStyleSheet( "QLabel{color:#ffe3ed;border:none;background-color:transparent;border-radius:5px;}" ) title_label.setAlignment(Qt.Alignment.AlignCenter) title_label.setFixedHeight(20) qr_label = QLabel() pixmap = QPixmap() pixmap.loadFromData(qr) qr_label.setPixmap(pixmap) qr_label.setStyleSheet( "QLabel{color:#ffe3ed;border:none;background-color:transparent;border-radius:5px;}" ) layout_ = QVBoxLayout() layout_.addWidget(title_label, 1) layout_.addWidget(qr_label, 9) self.qr_dialog = QWidget(self) self.qr_dialog.setLayout(layout_) self.main_layout.addWidget(self.qr_dialog, 1, 1, Qt.Alignment.AlignCenter) self.qr_dialog.show() def close_qr(self): self.qr_dialog.close() def setting_callback(self): setting = SettingWindow(parent=self) setting.setStyleSheet( "QDialog{border:none;border-radius:5px;background:#F3EAC2;}") setting.show() def gen_conf(self): default_conf = { "debug": False, "hero": { "title": "英雄篇", "enabled": True, "times": 1 }, "revival": { "title": "复兴篇", "enabled": True, "times": 1 }, "creation": { "title": "创新篇", "enabled": True, "times": 1 }, "belief": { "title": "信念篇", "enabled": True, "times": 1 }, "limit_time": { "title": "限时赛", "enabled": True, "times": 1 }, "rob": { "title": "抢十赛", "enabled": True, "times": 1 } } with open(file="config.json", mode="w", encoding="utf-8") as conf_writer: conf_writer.write( json.dumps(default_conf, indent=4, sort_keys=True, ensure_ascii=False)) self.logger.info("已生成默认配置文件") def mousePressEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标按压事件") super().mousePressEvent(event) self.setFocus() self.m_flag = True if event.button() == Qt.MouseButtons.LeftButton and self.isMaximized( ) == False and self.hasFocus() == True: self.old_pos = event.globalPosition() #获取鼠标相对窗口的位置 self.logger.debug("已获取鼠标位置") self.setCursor(QtGui.QCursor( Qt.CursorShape.SizeAllCursor)) #更改鼠标图标 def mouseMoveEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标移动事件") super().mouseMoveEvent(event) if self.m_flag == True: delta_x = int(event.globalPosition().x() - self.old_pos.x()) delta_y = int(event.globalPosition().y() - self.old_pos.y()) self.move(self.x() + delta_x, self.y() + delta_y) #更改窗口位置 self.logger.debug("已更改窗口位置") self.old_pos = event.globalPosition() def mouseReleaseEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标释放事件") super().mouseReleaseEvent(event) self.m_flag = False self.setCursor(QtGui.QCursor(Qt.CursorShape.ArrowCursor))
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 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 NuggetListItemWidget(CustomScrollableListItem): def __init__(self, nugget_list_widget): super(NuggetListItemWidget, self).__init__(nugget_list_widget) self.nugget_list_widget = nugget_list_widget self.nugget = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(10) self.info_label = QLabel() self.info_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.info_label) self.left_split_label = QLabel("|") self.left_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.left_split_label) self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) self.text_edit.setFrameStyle(0) self.text_edit.setFont(CODE_FONT) self.text_edit.setLineWrapMode(QTextEdit.LineWrapMode.FixedPixelWidth) self.text_edit.setLineWrapColumnOrWidth(10000) self.text_edit.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setFixedHeight(30) self.text_edit.setText("") self.layout.addWidget(self.text_edit) self.right_split_label = QLabel("|") self.right_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.right_split_label) self.match_button = QPushButton() self.match_button.setIcon(QIcon("aset_ui/resources/correct.svg")) self.match_button.setFlat(True) self.match_button.clicked.connect(self._match_button_clicked) self.layout.addWidget(self.match_button) self.fix_button = QPushButton() self.fix_button.setIcon(QIcon("aset_ui/resources/incorrect.svg")) self.fix_button.setFlat(True) self.fix_button.clicked.connect(self._fix_button_clicked) self.layout.addWidget(self.fix_button) def update_item(self, item, params=None): self.nugget = item sentence = self.nugget[CachedContextSentenceSignal]["text"] start_char = self.nugget[CachedContextSentenceSignal]["start_char"] end_char = self.nugget[CachedContextSentenceSignal]["end_char"] self.text_edit.setText("") formatted_text = ( f"{' ' * (params - start_char)}{sentence[:start_char]}" f"<span style='background-color: #FFFF00'><b>{sentence[start_char:end_char]}</b></span>" f"{sentence[end_char:]}{' ' * 50}") self.text_edit.textCursor().insertHtml(formatted_text) scroll_cursor = QTextCursor(self.text_edit.document()) scroll_cursor.setPosition(params + 50) self.text_edit.setTextCursor(scroll_cursor) self.text_edit.ensureCursorVisible() self.info_label.setText( f"{str(round(self.nugget[CachedDistanceSignal], 2)).ljust(4)}") def _match_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.main_window.give_feedback_task( { "message": "is-match", "nugget": self.nugget }) def _fix_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.get_document_feedback( self.nugget) def enable_input(self): self.match_button.setEnabled(True) self.fix_button.setEnabled(True) def disable_input(self): self.match_button.setDisabled(True) self.fix_button.setDisabled(True)
class DocumentBaseViewerWidget(MainWindowContent): def __init__(self, main_window): super(DocumentBaseViewerWidget, self).__init__(main_window, "Document Base") # controls self.controls = QWidget() self.controls_layout = QHBoxLayout(self.controls) self.controls_layout.setContentsMargins(0, 0, 0, 0) self.controls_layout.setSpacing(10) self.controls_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.layout.addWidget(self.controls) self.create_document_base_button = QPushButton( "Create a new Document Base") self.create_document_base_button.setFont(BUTTON_FONT) self.create_document_base_button.clicked.connect( self.main_window.show_document_base_creator_widget_task) self.controls_layout.addWidget(self.create_document_base_button) self.load_and_run_default_preprocessing_phase_button = QPushButton( "Preprocess the Document Base") self.load_and_run_default_preprocessing_phase_button.setFont( BUTTON_FONT) self.load_and_run_default_preprocessing_phase_button.clicked.connect( self.main_window.load_and_run_default_preprocessing_phase_task) self.controls_layout.addWidget( self.load_and_run_default_preprocessing_phase_button) self.load_and_run_default_matching_phase_button = QPushButton( "Match the Nuggets to the Attributes") self.load_and_run_default_matching_phase_button.setFont(BUTTON_FONT) self.load_and_run_default_matching_phase_button.clicked.connect( self.main_window.load_and_run_default_matching_phase_task) self.controls_layout.addWidget( self.load_and_run_default_matching_phase_button) self.save_table_button = QPushButton("Export the Table to CSV") self.save_table_button.setFont(BUTTON_FONT) self.save_table_button.clicked.connect( self.main_window.save_table_to_csv_task) self.controls_layout.addWidget(self.save_table_button) # documents self.documents = MainWindowContentSection(self, "Documents:") self.layout.addWidget(self.documents) self.num_documents = QLabel("number of documents: -") self.num_documents.setFont(LABEL_FONT) self.documents.layout.addWidget(self.num_documents) self.num_nuggets = QLabel("number of nuggets: -") self.num_nuggets.setFont(LABEL_FONT) self.documents.layout.addWidget(self.num_nuggets) # attributes self.attributes = MainWindowContentSection(self, "Attributes:") self.layout.addWidget(self.attributes) self.add_attribute_button = QPushButton("Add Attribute") self.add_attribute_button.setFont(BUTTON_FONT) self.add_attribute_button.clicked.connect( self.main_window.add_attribute_task) self.attributes_list = CustomScrollableList(self, AttributeWidget, self.add_attribute_button) self.attributes.layout.addWidget(self.attributes_list) def update_document_base(self, document_base): # update documents self.num_documents.setText( f"number of documents: {len(document_base.documents)}") self.num_nuggets.setText( f"number of nuggets: {len(document_base.nuggets)}") # update attributes self.attributes_list.update_item_list(document_base.attributes, document_base) def enable_input(self): self.create_document_base_button.setEnabled(True) if self.main_window.document_base is not None: self.load_and_run_default_preprocessing_phase_button.setEnabled( True) self.load_and_run_default_matching_phase_button.setEnabled(True) self.save_table_button.setEnabled(True) self.add_attribute_button.setEnabled(True) self.attributes_list.enable_input() def disable_input(self): self.create_document_base_button.setDisabled(True) self.load_and_run_default_preprocessing_phase_button.setDisabled(True) self.load_and_run_default_matching_phase_button.setDisabled(True) self.save_table_button.setDisabled(True) self.add_attribute_button.setDisabled(True) self.attributes_list.disable_input()
class AttributeWidget(CustomScrollableListItem): def __init__(self, document_base_viewer): super(AttributeWidget, self).__init__(document_base_viewer) self.document_base_viewer = document_base_viewer self.attribute = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(40) self.attribute_name = QLabel() self.attribute_name.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.attribute_name, alignment=Qt.AlignmentFlag.AlignLeft) self.num_matched = QLabel("matches: -") self.num_matched.setFont(CODE_FONT) self.layout.addWidget(self.num_matched, alignment=Qt.AlignmentFlag.AlignLeft) self.buttons_widget = QWidget() self.buttons_layout = QHBoxLayout(self.buttons_widget) self.buttons_layout.setContentsMargins(0, 0, 0, 0) self.buttons_layout.setSpacing(10) self.layout.addWidget(self.buttons_widget, alignment=Qt.AlignmentFlag.AlignRight) self.forget_matches_button = QPushButton() self.forget_matches_button.setIcon(QIcon("aset_ui/resources/redo.svg")) self.forget_matches_button.setToolTip( "Forget matches for this attribute.") self.forget_matches_button.setFlat(True) self.forget_matches_button.clicked.connect( self._forget_matches_button_clicked) self.buttons_layout.addWidget(self.forget_matches_button) self.remove_button = QPushButton() self.remove_button.setIcon(QIcon("aset_ui/resources/trash.svg")) self.remove_button.setToolTip("Remove this attribute.") self.remove_button.setFlat(True) self.remove_button.clicked.connect(self._remove_button_clicked) self.buttons_layout.addWidget(self.remove_button) def update_item(self, item, params=None): self.attribute = item if len(params.attributes) == 0: max_attribute_name_len = 10 else: max_attribute_name_len = max( len(attribute.name) for attribute in params.attributes) self.attribute_name.setText(self.attribute.name + ( " " * (max_attribute_name_len - len(self.attribute.name)))) mappings_in_some_documents = False no_mappings_in_some_documents = False num_matches = 0 for document in params.documents: if self.attribute.name in document.attribute_mappings.keys(): mappings_in_some_documents = True if document.attribute_mappings[self.attribute.name] != []: num_matches += 1 else: no_mappings_in_some_documents = True if not mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("not matched yet") elif mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("only partly matched") else: self.num_matched.setText(f"matches: {num_matches}") def enable_input(self): self.forget_matches_button.setEnabled(True) self.remove_button.setEnabled(True) def disable_input(self): self.forget_matches_button.setDisabled(True) self.remove_button.setDisabled(True) def _forget_matches_button_clicked(self): self.document_base_viewer.main_window.forget_matches_for_attribute_with_given_name_task( self.attribute.name) def _remove_button_clicked(self): self.document_base_viewer.main_window.remove_attribute_with_given_name_task( self.attribute.name)
class DocumentBaseCreatorWidget(MainWindowContent): def __init__(self, main_window) -> None: super(DocumentBaseCreatorWidget, self).__init__(main_window, "Create Document Base") self.documents = MainWindowContentSection(self, "Documents:") self.layout.addWidget(self.documents) self.documents_explanation = QLabel( "Enter the path of the directory that contains the documents as .txt files." ) self.documents_explanation.setFont(LABEL_FONT) self.documents.layout.addWidget(self.documents_explanation) self.path_widget = QFrame() self.path_layout = QHBoxLayout(self.path_widget) self.path_layout.setContentsMargins(20, 0, 20, 0) self.path_layout.setSpacing(10) self.path_widget.setStyleSheet("background-color: white") self.path_widget.setFixedHeight(40) self.documents.layout.addWidget(self.path_widget) self.path = QLineEdit() self.path.setFont(CODE_FONT_BOLD) self.path.setStyleSheet("border: none") self.path_layout.addWidget(self.path) self.edit_path_button = QPushButton() self.edit_path_button.setIcon(QIcon("aset_ui/resources/folder.svg")) self.edit_path_button.setFlat(True) self.edit_path_button.clicked.connect(self._edit_path_button_clicked) self.path_layout.addWidget(self.edit_path_button) self.attributes = MainWindowContentSection(self, "Attributes:") self.layout.addWidget(self.attributes) self.labels_explanation = QLabel("Enter the attribute names.") self.labels_explanation.setFont(LABEL_FONT) self.attributes.layout.addWidget(self.labels_explanation) self.create_attribute_button = QPushButton("New Attribute") self.create_attribute_button.setFont(BUTTON_FONT) self.create_attribute_button.clicked.connect( self._create_attribute_button_clicked) self.attribute_names = [] self.attributes_list = CustomScrollableList( self, AttributeCreatorWidget, self.create_attribute_button) self.attributes.layout.addWidget(self.attributes_list) self.buttons_widget = QWidget() self.buttons_layout = QHBoxLayout(self.buttons_widget) self.buttons_layout.setContentsMargins(0, 0, 0, 0) self.buttons_layout.setSpacing(10) self.buttons_layout.setAlignment(Qt.AlignmentFlag.AlignRight) self.attributes.layout.addWidget(self.buttons_widget) self.cancel_button = QPushButton("Cancel") self.cancel_button.setFont(BUTTON_FONT) self.cancel_button.clicked.connect(self._cancel_button_clicked) self.buttons_layout.addWidget(self.cancel_button) self.create_document_base_button = QPushButton("Create Document Base") self.create_document_base_button.setFont(BUTTON_FONT) self.create_document_base_button.clicked.connect( self._create_document_base_button_clicked) self.buttons_layout.addWidget(self.create_document_base_button) def enable_input(self): self.edit_path_button.setEnabled(True) self.create_attribute_button.setEnabled(True) self.cancel_button.setEnabled(True) self.create_document_base_button.setEnabled(True) self.attributes_list.enable_input() def disable_input(self): self.edit_path_button.setDisabled(True) self.create_attribute_button.setDisabled(True) self.cancel_button.setDisabled(True) self.create_document_base_button.setDisabled(True) self.attributes_list.disable_input() def initialize_for_new_document_base(self): self.path.setText("") self.attribute_names = [] self.attributes_list.update_item_list([]) def delete_attribute(self, attribute_name): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.attribute_names.remove(attribute_name) self.attributes_list.update_item_list(self.attribute_names) self.attributes_list.last_item_widget().name.setFocus() def _edit_path_button_clicked(self): path = str( QFileDialog.getExistingDirectory( self, "Choose a directory of text files.")) if path != "": path = f"{path}/*.txt" self.path.setText(path) def _create_attribute_button_clicked(self): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.attribute_names.append("") self.attributes_list.update_item_list(self.attribute_names) self.attributes_list.last_item_widget().name.setFocus() def _cancel_button_clicked(self): self.main_window.show_start_menu_widget() self.main_window.enable_global_input() def _create_document_base_button_clicked(self): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.main_window.create_document_base_task(self.path.text(), self.attribute_names)
class B23Download(QWidget): def __init__(self): super(B23Download, self).__init__() # setup some flags self.is_fetching = False self.is_downloading = False # default output path basepath = os.path.dirname(os.path.abspath(__file__)) path = os.path.join(basepath, "videos") self.output_path = path # setup some window specific things self.setWindowTitle("Bilibili Favorite Downloader") self.setWindowIcon(QIcon("images/icon_bilibili.ico")) self.setFixedSize(705, 343) # parent layout main_layout = QVBoxLayout() main_layout.setContentsMargins(15, 15, 15, 10) self.setLayout(main_layout) # top bar layout top_layout = QHBoxLayout() # detail section mid_main_layout = QHBoxLayout() mid_right_layout = QVBoxLayout() # download section bottom_main_layout = QHBoxLayout() bottom_right_layout = QVBoxLayout() # output path link button self.output_btn = QPushButton("📂 Output Path") self.output_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.output_btn.setToolTip(self.output_path) self.output_btn.clicked.connect(self.set_output_path) # status bar self.status_bar = QStatusBar() # message box self.message_box = QMessageBox() # setting up widgets self.url_edit = QLineEdit() self.url_edit.setPlaceholderText("🔍 Enter or paste favorite URL...") self.get_btn = QPushButton("Get") self.get_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.get_btn.clicked.connect(self.get_details) # thumbnail pixmap = QPixmap("images/placeholder.png") 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("Videos: ") self.publish_date = QLabel("Published: ") # progress bar self.progress_bar = QProgressBar() # download options self.download_btn = QPushButton(" Download Videos ") self.download_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download_btn.clicked.connect(self.get_content) self.download_btn.setEnabled(False) self.download_btn.setShortcut("Ctrl+Return") self.download_btn.setMinimumWidth(200) # add widgets and layouts top_layout.addWidget(self.url_edit) top_layout.addWidget(self.get_btn) # detail section mid_right_layout.addWidget(self.title) mid_right_layout.addWidget(self.author) mid_right_layout.addWidget(self.length) mid_right_layout.addWidget(self.publish_date) mid_main_layout.addWidget(self.thumb) mid_main_layout.addSpacing(20) mid_main_layout.addLayout(mid_right_layout) # download section bottom_right_layout.addWidget(self.download_btn) bottom_main_layout.addWidget(self.progress_bar) bottom_main_layout.addSpacing(10) bottom_main_layout.addLayout(bottom_right_layout) # status bar self.status_bar.setSizeGripEnabled(False) self.status_bar.addPermanentWidget(self.output_btn) # add content to parent layout main_layout.addLayout(top_layout) main_layout.addSpacing(20) main_layout.addLayout(mid_main_layout) main_layout.addSpacing(5) main_layout.addLayout(bottom_main_layout) main_layout.addWidget(self.status_bar) # set output path slot def set_output_path(self): # update the output path path = str( QFileDialog.getExistingDirectory(self, "Select Output Directory")) if path: self.output_path = path # update tooltip self.output_btn.setToolTip(path) # get button slot def get_details(self): text = self.url_edit.text().strip() if not text: return if text.find("fid") < 0: self.message_box.warning( self, "Error", ("Input a correct favorite URL!\n" "For example: https://space.bilibili.com/xxx/favlist?fid=xxx..." ), ) return if self.get_btn.text() == "Get": self.get_btn.setText("Stop") # indicate progress bar as busy self.progress_bar.setRange(0, 0) # set fetching flag self.is_fetching = True # setup a worker thread to keep UI responsive self.media_id = text.split("fid=")[-1].split("&")[0] self.worker = WorkerThread(self.media_id) 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.get_btn.text() == "Stop": if self.is_fetching: # stop worker thread self.worker.terminate() # set back the get_btn text self.get_btn.setText("Get") elif self.is_downloading: # stop download thread self.download_thread.terminate() # show the warning message_box self.message_box.information( self, "Interrupted", "Download interrupted!\nThe process was aborted while the file was being downloaded... ", ) # reset progress bar self.progress_bar.reset() # download options slot def get_content(self): if self.is_fetching: # show the warning message self.message_box.critical( self, "Error", "Please wait!\nWait while the details are being fetched... ", ) else: # disable the download options self.download_btn.setDisabled(True) # set downloading flag self.is_downloading = True # set button to stop self.get_btn.setText("Stop") self.download_thread = DownloadThread( self.media_id, self.media_counts, self.first_page_medias, self.output_path, ) # 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) # handling enter key for get/stop button def keyPressEvent(self, event): self.url_edit.setFocus() if (event.key() == Qt.Key.Key_Enter.value or event.key() == Qt.Key.Key_Return.value): self.get_details() # finished slot def finished_slot(self): # remove progress bar busy indication self.progress_bar.setRange(0, 100) # unset fetching flag self.is_fetching = False # response slot def response_slot(self, res): # set back the button text self.get_btn.setText("Get") # set the actual thumbnail of requested video self.thumb.setPixmap(res.thumb_img) # slice the title if it is more than the limit if len(res.title) > 50: self.title.setText(f"Title: {res.title[:50]}...") else: self.title.setText(f"Title: {res.title}") # cache first page medias self.first_page_medias = res.medias self.media_counts = res.media_counts # set leftover details self.author.setText(f"Author: {res.author}") self.length.setText(f"Videos: {res.media_counts}") self.publish_date.setText( f'Published: {time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(res.publish_date))}' ) self.download_btn.setDisabled(False) # error slot def err_slot(self): # show the warning message self.message_box.warning( self, "Warning", "Something went wrong!\nProbably a broken link or some restricted content... ", ) # set back the button text self.get_btn.setText("Get") # download finished slot def download_finished_slot(self): # set back the button text self.get_btn.setText("Get") # now enable the download options self.download_btn.setDisabled(False) # unset downloading flag self.is_downloading = 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_box.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_box.critical( self, "Error", "Error!\nSomething unusual happened and was unable to download...", )
class VideoWindow(QMainWindow): def __init__(self, parent=None): super(VideoWindow, self).__init__(parent) self.setWindowTitle("StudioProject") self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.gScene = QGraphicsScene(self) self.gView = GraphicView(self.gScene, self) self.gView.viewport().setAttribute( Qt.WidgetAttribute.WA_AcceptTouchEvents, False) # self.gView.setBackgroundBrush(QBrush(Qt.black)) self.videoStartDatetime = None self.videoCurrentDatetime = None self.projectFile = '' self.graphicsFile = '' self.videoFile = '' self.obsTb = ObsToolbox(self) # # ===================== Setting video item ============================== # self.videoItem = QGraphicsVideoItem() # self.videoItem.setAspectRatioMode(Qt.KeepAspectRatio) # self.gScene.addItem(self.videoItem) # self.videoItem.mouseMoveEvent = self.gView.mouseMoveEvent self.mediaPlayer = QMediaPlayer(self) # self.mediaPlayer.setVideoOutput(self.videoItem) self.mediaPlayer.playbackStateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.errorOccurred.connect(self.handleError) # self.mediaPlayer.setMuted(True) # self.mediaPlayer.setNotifyInterval(100) self.playButton = QPushButton() self.playButton.setEnabled(False) self.playButton.setIcon(self.style().standardIcon( QStyle.StandardPixmap.SP_MediaPlay)) self.playButton.clicked.connect(self.play) self.changePlayRateBtn = QPushButton('1x') self.changePlayRateBtn.setFixedWidth(40) # self.incrPlayRateBtn.setEnabled(False) self.changePlayRateBtn.clicked.connect(self.changePlayRate) self.positionSlider = QSlider(Qt.Orientation.Horizontal) self.positionSlider.setRange(0, 0) self.positionSlider.sliderMoved.connect(self.setPosition) self.timerLabel = QLabel() self.timerLabel.setText('--:--:--') self.timerLabel.setFixedWidth(58) self.dateLabel = QLabel() self.dateLabel.setText('Video date: --') self.statusBar.addPermanentWidget(self.dateLabel) # Create open action self.openVideoAction = QAction(QIcon('icons/video-file.png'), 'Open video', self) self.openVideoAction.setShortcut('Ctrl+O') self.openVideoAction.setStatusTip('Open video file') self.openVideoAction.triggered.connect(self.openVideoFile) # Create observation action obsTbAction = QAction(QIcon('icons/checklist.png'), 'Observation toolbox', self) obsTbAction.setStatusTip('Open observation toolbox') obsTbAction.triggered.connect(self.openObsToolbox) self.drawPointAction = QAction(QIcon('icons/drawPoint.png'), 'Draw point', self) self.drawPointAction.setStatusTip('Draw point over the video') self.drawPointAction.setCheckable(True) self.drawPointAction.setEnabled(False) self.drawPointAction.triggered.connect(self.drawingClick) self.drawLineAction = QAction(QIcon('icons/drawLine.png'), 'Draw line', self) self.drawLineAction.setStatusTip('Draw line over the video') self.drawLineAction.setCheckable(True) self.drawLineAction.setEnabled(False) self.drawLineAction.triggered.connect(self.drawingClick) self.drawZoneAction = QAction(QIcon('icons/drawZone.png'), 'Draw zone', self) self.drawZoneAction.setStatusTip('Draw zone over the video') self.drawZoneAction.setCheckable(True) self.drawZoneAction.setEnabled(False) self.drawZoneAction.triggered.connect(self.drawingClick) self.maskGenAction = QAction(QIcon('icons/mask.png'), 'Generate mask file', self) self.maskGenAction.setStatusTip( 'Generate mask file for TrafficIntelligence') self.maskGenAction.setCheckable(True) self.maskGenAction.setEnabled(False) self.maskGenAction.triggered.connect(self.generateMask) actionGroup = QActionGroup(self) actionGroup.addAction(self.drawPointAction) actionGroup.addAction(self.drawLineAction) actionGroup.addAction(self.drawZoneAction) openProjectAction = QAction(QIcon('icons/open-project.png'), 'Open project', self) openProjectAction.setStatusTip('Open project') openProjectAction.triggered.connect(self.openProject) self.saveProjectAction = QAction(QIcon('icons/save-project.png'), 'Save project', self) self.saveProjectAction.setStatusTip('Save project') self.saveProjectAction.setEnabled(False) self.saveProjectAction.triggered.connect(self.saveProject) self.saveGraphAction = QAction(QIcon('icons/save-graphics.png'), 'Save graphics', self) self.saveGraphAction.setStatusTip('Save graphics to database') self.saveGraphAction.setEnabled(False) self.saveGraphAction.triggered.connect(self.saveGraphics) self.loadGraphAction = QAction(QIcon('icons/folders.png'), 'Load graphics', self) self.loadGraphAction.setStatusTip('Load graphics from database') self.loadGraphAction.setEnabled(False) self.loadGraphAction.triggered.connect(self.loadGraphics) # Create exit action exitAction = QAction(QIcon('icons/close.png'), 'Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(self.exitCall) # self.exitCall # Create menu bar and add action # menuBar = self.menuBar() # menuBar.setNativeMenuBar(False) # fileMenu = menuBar.addMenu('&File') # fileMenu.addAction(openVideoAction) # fileMenu.addAction(obsTbAction) # fileMenu.addAction(exitAction) self.toolbar = self.addToolBar('Tools') self.toolbar.setIconSize(QSize(24, 24)) self.toolbar.addAction(openProjectAction) self.toolbar.addAction(self.saveProjectAction) self.toolbar.addAction(self.openVideoAction) # self.toolbar.insertSeparator(self.loadGraphAction) # self.toolbar.addAction(self.loadGraphAction) # self.toolbar.addAction(self.saveGraphAction) # self.toolbar.addAction(self.drawPointAction) # self.toolbar.addAction(self.drawLineAction) # self.toolbar.addAction(self.drawZoneAction) self.toolbar.addAction(self.maskGenAction) # self.toolbar.insertSeparator(self.drawPointAction) self.toolbar.insertSeparator(obsTbAction) self.toolbar.addAction(obsTbAction) self.toolbar.insertSeparator(exitAction) self.toolbar.addAction(exitAction) # Create a widget for window contents wid = QWidget(self) self.setCentralWidget(wid) # Create layouts to place inside widget controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) # controlLayout.addWidget(self.decrPlayRateBtn) controlLayout.addWidget(self.playButton) controlLayout.addWidget(self.changePlayRateBtn) controlLayout.addWidget(self.timerLabel) controlLayout.addWidget(self.positionSlider) # controlLayout.addWidget(self.durationLabel) layout = QVBoxLayout() layout.addWidget(self.gView) layout.addLayout(controlLayout) # Set widget to contain window contents wid.setLayout(layout) # def showEvent(self, event): # self.gView.fitInView(self.videoItem, Qt.KeepAspectRatio) def openVideoFile(self): # self.mediaPlayer.setMedia(QMediaContent()) if self.sender() == self.openVideoAction: self.videoFile, _ = QFileDialog.getOpenFileName( self, "Open video", QDir.homePath()) # if self.videoFile != '': # self.setWindowTitle('{} - {}'.format(os.path.basename(self.videoFile), # os.path.basename(self.projectFile))) if self.videoFile != '': self.setWindowTitle('{} - {}'.format( os.path.basename(self.videoFile), os.path.basename(self.projectFile))) self.saveProjectAction.setEnabled(True) self.maskGenAction.setEnabled(True) # self.loadGraphAction.setEnabled(True) # self.saveGraphAction.setEnabled(True) # self.drawPointAction.setEnabled(True) # self.drawLineAction.setEnabled(True) # self.drawZoneAction.setEnabled(True) creation_datetime, width, height = getVideoMetadata(self.videoFile) self.videoStartDatetime = self.videoCurrentDatetime = creation_datetime self.dateLabel.setText(creation_datetime.strftime('%a, %b %d, %Y')) self.gView.setSceneRect(0, 0, width, height) self.videoItem = QGraphicsVideoItem() self.videoItem.setAspectRatioMode( Qt.AspectRatioMode.KeepAspectRatio) self.gScene.addItem(self.videoItem) self.videoItem.mouseMoveEvent = self.gView.mouseMoveEvent self.videoItem.setSize(QSizeF(width, height)) self.mediaPlayer.setVideoOutput(self.videoItem) self.mediaPlayer.setSource(QUrl.fromLocalFile(self.videoFile)) self.gView.labelSize = width / 50 self.playButton.setEnabled(True) # self.gView.setViewport(QOpenGLWidget()) self.mediaPlayer.pause() def exitCall(self): # sys.exit(app.exec()) # self.mediaPlayer.pause() self.close() def play(self): # self.gView.fitInView(self.videoItem, Qt.KeepAspectRatio) if self.mediaPlayer.playbackState( ) == QMediaPlayer.PlaybackState.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def changePlayRate(self): if self.mediaPlayer.playbackRate() < 2: r = self.mediaPlayer.playbackRate() + 0.5 self.mediaPlayer.setPlaybackRate(r) self.changePlayRateBtn.setText('{:g}x'.format(r)) self.statusBar.showMessage('Play back rate = {:g}x'.format(r), 2000) elif self.mediaPlayer.playbackRate() == 2: self.mediaPlayer.setPlaybackRate(1) self.changePlayRateBtn.setText('{}x'.format(1)) self.statusBar.showMessage('Play back rate = {}x'.format(1), 2000) def mediaStateChanged(self, state): if self.mediaPlayer.playbackState( ) == QMediaPlayer.PlaybackState.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.StandardPixmap.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.StandardPixmap.SP_MediaPlay)) def positionChanged(self, position): self.positionSlider.setValue(position) s, m, h = self.convertMillis(position) self.videoCurrentDatetime = self.videoStartDatetime + \ timedelta(hours=h, minutes=m, seconds=s) self.timerLabel.setText('{:02d}:{:02d}:{:02d}'.format( self.videoCurrentDatetime.time().hour, self.videoCurrentDatetime.time().minute, self.videoCurrentDatetime.time().second)) def durationChanged(self, duration): self.positionSlider.setRange(0, duration) # s, m, h = self.convertMillis(duration) # self.durationLabel.setText('{:02d}:{:02d}'.format(m, s)) def setPosition(self, position): self.mediaPlayer.setPosition(position) def handleError(self): self.playButton.setEnabled(False) # self.errorLabel.setText("Error: " + self.mediaPlayer.errorString()) def openObsToolbox(self): if not self.obsTb.isVisible(): self.obsTb.show() def drawingClick(self): # if self.sender() == self.drawLineAction: # self.labelingAction.setChecked(False) # else: # self.drawLineAction.setChecked(False) cursor = QCursor(Qt.CursorShape.CrossCursor) self.gView.setCursor(cursor) def generateMask(self): if not self.sender().isChecked(): self.gView.unsetCursor() return cursor = QCursor(Qt.CursorShape.CrossCursor) self.gView.setCursor(cursor) # dbfilename = self.obsTb.dbFilename # if dbfilename != None: # self.session = connectDatabase(dbfilename) # else: # msg = QMessageBox() # msg.setIcon(QMessageBox.Information) # msg.setText('The database file is not defined.') # msg.setInformativeText('In order to set the database file, open the Observation Toolbox') # msg.setIcon(QMessageBox.Critical) # msg.exec_() # return # if self.gView.unsavedLines == [] and self.gView.unsavedZones == [] and \ # self.gView.unsavedPoints == []: # QMessageBox.information(self, 'Save', 'There is no polygon to generate mask!') # return def saveMaskFile(self): creation_datetime, width, height = getVideoMetadata(self.videoFile) item = self.gView.gPolyItem #self.gView.unsavedZones[0] mask_polygon = item.polygon() xy = [] for p in mask_polygon: xy.append((p.x(), p.y())) img = Image.new('RGB', (width, height), color='black') img1 = ImageDraw.Draw(img) img1.polygon(xy, fill="white", outline="white") fileName, _ = QFileDialog.getSaveFileName(self, "Open database file", QDir.homePath(), "PNG files (*.png)") if fileName != '': img.save(fileName) self.gView.scene().removeItem(item) self.gView.unsavedZones = [] def openProject(self): self.projectFile, _ = QFileDialog.getOpenFileName( self, "Open project file", QDir.homePath(), "Project (*.prj)") if self.projectFile == '': return self.saveProjectAction.setEnabled(True) self.maskGenAction.setEnabled(True) # self.loadGraphAction.setEnabled(True) # self.saveGraphAction.setEnabled(True) # self.drawPointAction.setEnabled(True) # self.drawLineAction.setEnabled(True) # self.drawZoneAction.setEnabled(True) tree = ET.parse(self.projectFile) root = tree.getroot() gItems = [] for elem in root: subEelTexts = {} for subelem in elem: subEelTexts[subelem.tag] = subelem.text gItems.append([elem.tag, subEelTexts]) for key in gItems: if key[0] == 'database': item = key[1] if item['fileName'] is not None: self.obsTb.dbFilename = item['fileName'] self.obsTb.opendbFile() elif key[0] == 'video': item = key[1] if item['fileName'] is not None: self.videoFile = item['fileName'] self.openVideoFile() self.mediaPlayer.setPosition(int(item['sliderValue'])) if item['fileName'] is not None: self.loadGraphics() elif key[0] == 'trajectory': item = key[1] if item['metadata'] != None: self.obsTb.mdbFileLedit.setText(item['metadata']) self.obsTb.openMdbFile() self.obsTb.siteNameCombobx.setCurrentIndex( int(item['site'])) self.obsTb.camViewCombobx.setCurrentIndex( int(item['cam_view'])) self.obsTb.trjDbCombobx.setCurrentIndex( int(item['traj_db'])) elif key[0] == 'window': item = key[1] x, y = item['mainWin_pos'].split(',') w, h = item['mainWin_size'].split(',') self.setGeometry(int(x), int(y), int(w), int(h)) if item['obsTbx_open'] == 'True': self.obsTb.show() x, y = item['obsTbx_pos'].split(',') w, h = item['obsTbx_size'].split(',') self.obsTb.setGeometry(int(x), int(y), int(w), int(h)) # self.setWindowTitle('{} - {}'.format(os.path.basename(self.videoFile), # os.path.basename(self.projectFile))) def saveProject(self): if self.projectFile == '': fileDir = QDir.homePath() else: fileDir = self.projectFile self.projectFile, _ = QFileDialog.getSaveFileName( self, "Save project file", fileDir, "Project (*.prj)") # fileName = "/Users/Abbas/project.xml" if self.projectFile == '': return file = QFile(self.projectFile) if (not file.open(QIODevice.OpenModeFlag.WriteOnly | QIODevice.OpenModeFlag.Text)): return xmlWriter = QXmlStreamWriter(file) xmlWriter.setAutoFormatting(True) xmlWriter.writeStartDocument() xmlWriter.writeStartElement('project') xmlWriter.writeStartElement('database') xmlWriter.writeTextElement("fileName", self.obsTb.dbFilename) xmlWriter.writeEndElement() xmlWriter.writeStartElement('video') xmlWriter.writeTextElement( "fileName", self.videoFile) #mediaPlayer.media().canonicalUrl().path()) xmlWriter.writeTextElement("sliderValue", str(self.mediaPlayer.position())) xmlWriter.writeEndElement() xmlWriter.writeStartElement('trajectory') xmlWriter.writeTextElement("metadata", self.obsTb.mdbFileLedit.text()) xmlWriter.writeTextElement( "site", str(self.obsTb.siteNameCombobx.currentIndex())) xmlWriter.writeTextElement( "cam_view", str(self.obsTb.camViewCombobx.currentIndex())) xmlWriter.writeTextElement("traj_db", str(self.obsTb.trjDbCombobx.currentIndex())) xmlWriter.writeEndElement() xmlWriter.writeStartElement('window') xmlWriter.writeTextElement( "mainWin_size", "{},{}".format(int(self.width()), int(self.height()))) xmlWriter.writeTextElement( "mainWin_pos", "{},{}".format(int(self.x()), int(self.y()))) xmlWriter.writeTextElement("obsTbx_open", str(self.obsTb.isVisible())) xmlWriter.writeTextElement( "obsTbx_size", "{},{}".format(int(self.obsTb.width()), int(self.obsTb.height()))) xmlWriter.writeTextElement( "obsTbx_pos", "{},{}".format(int(self.obsTb.x()), int(self.obsTb.y()))) xmlWriter.writeEndElement() xmlWriter.writeEndElement() self.setWindowTitle('{} - {}'.format( os.path.basename(self.videoFile), os.path.basename(self.projectFile))) if self.obsTb.dbFilename != None: self.obsTb.setWindowTitle('{} - {}'.format( os.path.basename(self.obsTb.dbFilename), os.path.basename(self.projectFile))) def saveGraphics(self): dbfilename = self.obsTb.dbFilename if dbfilename != None: self.session = connectDatabase(dbfilename) else: msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText('The database file is not defined.') msg.setInformativeText( 'In order to set the database file, open the Observation Toolbox' ) msg.setIcon(QMessageBox.Critical) msg.exec_() return if self.gView.unsavedLines == [] and self.gView.unsavedZones == [] and \ self.gView.unsavedPoints == []: QMessageBox.information(self, 'Save', 'No new graphical items to save!') return for item in self.gView.unsavedLines: x1 = round(item.line().x1(), 2) y1 = round(item.line().y1(), 2) x2 = round(item.line().x2(), 2) y2 = round(item.line().y2(), 2) line = Line(None, None, x1, y1, x2, y2) self.session.add(line) self.session.flush() label = self.generate_itemGroup([x1, x2], [y1, y2], line.idx) self.gView.scene().addItem(label) for item in self.gView.unsavedZones: xs = [] ys = [] for p in item.polygon(): xs.append(round(p.x(), 2)) ys.append(round(p.y(), 2)) zone = Zone(None, None, xs, ys) self.session.add(zone) self.session.flush() label = self.generate_itemGroup(xs, ys, zone.idx) self.gView.scene().addItem(label) for item in self.gView.unsavedPoints: x = round(item.rect().center().x(), 2) y = round(item.rect().center().y(), 2) point = Point(x, y) self.session.add(point) self.session.flush() label = self.generate_itemGroup([x], [y], point.idx) self.gView.scene().removeItem(item) self.gView.scene().addItem(label) QMessageBox.information( self, 'Save', '{} point(s), {} line(s) and {} zone(s) saved to database successfully!' .format(len(self.gView.unsavedPoints), len(self.gView.unsavedLines), len(self.gView.unsavedZones))) self.gView.unsavedLines = [] self.gView.unsavedZones = [] self.gView.unsavedPoints = [] self.session.commit() def generate_itemGroup(self, xs, ys, label, type): gItemGroup = QGraphicsItemGroup() pointBbx = QRectF() pointBbx.setSize(QSizeF(self.gView.labelSize, self.gView.labelSize)) textLabel = QGraphicsTextItem(label) if len(xs) == 1: pointBbx.moveCenter(QPointF(xs[0], ys[0])) textLabel.setPos(xs[0] - (textLabel.boundingRect().width() / 2), ys[0] - (textLabel.boundingRect().height() / 2)) pointShape = QGraphicsEllipseItem(pointBbx) shapeColor = Qt.GlobalColor.white textColor = Qt.GlobalColor.black tooltip = 'P{}:{}' elif len(xs) == 2: pointBbx.moveCenter(QPointF(xs[1], ys[1])) textLabel.setPos(xs[1] - (textLabel.boundingRect().width() / 2), ys[1] - (textLabel.boundingRect().height() / 2)) r, g, b = np.random.choice(range(256), size=3) line_item = QGraphicsLineItem(xs[0], ys[0], xs[1], ys[1]) line_item.setPen( QPen(QColor(r, g, b, 128), self.gView.labelSize / 6)) gItemGroup.addToGroup(line_item) # line_end = QGraphicsEllipseItem(xs[1], ys[1], # int(self.gView.labelSize/3), int(self.gView.labelSize/3)) # line_end.setPen(QPen(QColor(r, g, b), 0.5)) # line_end.setBrush(QBrush(QColor(r, g, b))) # gItemGroup.addToGroup(line_end) pointShape = QGraphicsEllipseItem(pointBbx) shapeColor = QColor(r, g, b, 128) textColor = Qt.GlobalColor.black tooltip = 'L{}:{}' # textLabel.setRotation(np.arctan((ys[1] - ys[0])/(xs[1] - xs[0]))*(180/3.14)) else: pointBbx.moveCenter(QPointF(np.mean(xs), np.mean(ys))) textLabel.setPos( np.mean(xs) - (textLabel.boundingRect().width() / 2), np.mean(ys) - (textLabel.boundingRect().height() / 2)) points = [QPointF(x, y) for x, y in zip(xs, ys)] polygon = QPolygonF(points) r, g, b = np.random.choice(range(256), size=3) zone_item = QGraphicsPolygonItem(polygon) zone_item.setPen(QPen(QColor(r, g, b), self.gView.labelSize / 10)) zone_item.setBrush(QBrush(QColor(r, g, b, 40))) gItemGroup.addToGroup(zone_item) pointShape = QGraphicsRectItem(pointBbx) shapeColor = Qt.GlobalColor.darkBlue textColor = Qt.GlobalColor.white tooltip = 'Z{}:{}' pointShape.setPen(QPen(Qt.GlobalColor.white, 0.5)) pointShape.setBrush(QBrush(shapeColor)) # self.gView.scene().addEllipse(pointBbx, QPen(Qt.white, 0.5), QBrush(Qt.black)) gItemGroup.setToolTip(tooltip.format(label, type)) gItemGroup.addToGroup(pointShape) labelFont = QFont() labelFont.setPointSize(round(self.gView.labelSize / 2)) labelFont.setBold(True) textLabel.setFont(labelFont) textLabel.setDefaultTextColor(textColor) gItemGroup.addToGroup(textLabel) return gItemGroup def loadGraphics(self): dbfilename = self.obsTb.dbFilename if dbfilename != None: self.session = connectDatabase(dbfilename) else: msg = QMessageBox() # msg.setIcon(QMessageBox.Icon.Information) msg.setText('The database file is not defined.') msg.setInformativeText( 'In order to set the database file, open the Observation Toolbox' ) msg.setIcon(QMessageBox.Icon.Critical) msg.exec() return for gitem in self.gView.scene().items(): if isinstance(gitem, QGraphicsItemGroup): self.gView.scene().removeItem(gitem) q_line = self.session.query(Line) q_zone = self.session.query(Zone) if q_line.all() == [] and q_zone.all() == []: QMessageBox.information(self, 'Warning!', 'There is no graphics to load!') return line_items = [] for line in q_line: p1 = line.points[0] p2 = line.points[1] if line.type != None: lineType = line.type.name else: lineType = None gItmGroup = self.generate_itemGroup([p1.x, p2.x], [p1.y, p2.y], str(line.idx), lineType) self.gScene.addItem(gItmGroup) line_items.append(str(line.idx)) self.obsTb.line_list_wdgt.clear() self.obsTb.line_list_wdgt.addItems(line_items) self.obsTb.line_list_wdgt.setCurrentRow(0) self.obsTb.line_newRecButton.setEnabled(False) self.obsTb.line_saveButton.setEnabled(True) self.obsTb.line_saveButton.setText('Edit line(s)') self.obsTb.line_saveButton.setIcon(QIcon('icons/edit.png')) zone_items = [] for zone in q_zone: if zone.type != None: zoneType = zone.type.name else: zoneType = None gItmGroup = self.generate_itemGroup( [point.x for point in zone.points], [point.y for point in zone.points], str(zone.idx), zoneType) self.gScene.addItem(gItmGroup) zone_items.append(str(zone.idx)) self.obsTb.zone_list_wdgt.clear() self.obsTb.zone_list_wdgt.addItems(zone_items) self.obsTb.zone_list_wdgt.setCurrentRow(0) self.obsTb.zone_newRecButton.setEnabled(False) self.obsTb.zone_saveButton.setEnabled(True) self.obsTb.zone_saveButton.setText('Edit zone(s)') self.obsTb.zone_saveButton.setIcon(QIcon('icons/edit.png')) @staticmethod def convertMillis(millis): seconds = int(millis / 1000) % 60 minutes = int(millis / (1000 * 60)) % 60 hours = int(millis / (1000 * 60 * 60)) % 24 return seconds, minutes, hours
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 MainWindow(QMainWindow): def __init__(self, parent = None): super(MainWindow, self).__init__() D = self.screen().availableGeometry() self.move(0,0)#center.x() + .25*D.width() , center.y() - .5*D.height() ) self.resize( int(.95*D.width()), int(6*D.height()) ) #qr = self.frameGeometry() #cp = self.screen().availableGeometry().center() #qr.moveCenter(cp) #self.move(qr.topLeft()) self.setWindowState(self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized | QtCore.Qt.WindowState.WindowActive) self.activateWindow() self.subWin = Window() self.iw = imwin() self.Manual = Manual() self.setCentralWidget(self.iw) #Stacked dock widgets docked1 = QDockWidget("", self) docked2 = QDockWidget("", self) self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, docked1) self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, docked2) docked1.setWidget(self.subWin) docked2.setWidget(self.Manual) docked1.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetFloatable) self.setCorner(QtCore.Qt.Corner.TopLeftCorner, QtCore.Qt.DockWidgetArea.LeftDockWidgetArea); self.setCorner(QtCore.Qt.Corner.TopRightCorner, QtCore.Qt.DockWidgetArea.RightDockWidgetArea) self.setCorner(QtCore.Qt.Corner.BottomLeftCorner, QtCore.Qt.DockWidgetArea.LeftDockWidgetArea); self.setCorner(QtCore.Qt.Corner.BottomRightCorner, QtCore.Qt.DockWidgetArea.RightDockWidgetArea) self.resizeDocks( (docked1,docked2), (400,400), QtCore.Qt.Orientation.Horizontal ) self.exportButton = QPushButton("Export Measurements", self) self.exportButton.clicked.connect(self.export_measurements) self.exportButton.setEnabled(False) self.importImage = QPushButton("New Image", self) self.importImage.clicked.connect(self.file_open) self.lengthButton = QPushButton("Measure Length", self) self.lengthButton.clicked.connect(self.measure_length) self.lengthButton.setEnabled(False) self.lengthButton.setCheckable(True) self.lengthNames = [] self.widthsButton = QPushButton("Measure Widths", self) self.widthsButton.clicked.connect(self.iw.measure_widths) self.widthsButton.setEnabled(False) self.widthsButton.setCheckable(True) self.areaButton = QPushButton("Measure Area", self) self.areaButton.clicked.connect(self.measure_area) self.areaButton.setEnabled(False) self.areaButton.setCheckable(True) self.areaNames = [] self.angleButton = QPushButton("Measure Angle", self) self.angleButton.clicked.connect(self.measure_angle) self.angleButton.setEnabled(False) self.angleButton.setCheckable(True) self.angleNames = [] shortcut_polyClose = QShortcut(QtGui.QKeySequence(QtCore.Qt.Key.Key_Tab), self) shortcut_polyClose.activated.connect(self.iw.polyClose) self.undoButton = QPushButton("Undo", self) self.undoButton.clicked.connect(self.undo) self.undoButton.setEnabled(False) shortcut_undo = QShortcut(QtGui.QKeySequence('Ctrl+Z'), self) shortcut_undo.activated.connect(self.undo) self.bezier = QRadioButton("Bezier fit", self) self.bezier.setEnabled(True) self.bezier.setChecked(True) #self.bezier.toggled.connect(self.onClicked) self.piecewise = QRadioButton("Piecewise", self) self.statusbar = self.statusBar() self.statusbar.showMessage('Select new image to begin') self.tb = QToolBar('Toolbar') #self.addToolBar(QtCore.Qt.RightToolBarArea,self.tb) spacer = QWidget(self) spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.tb.addWidget(spacer) self.addToolBar(self.tb) self.tb.addWidget(self.importImage) self.tb.addWidget(self.exportButton) self.tb.addWidget(self.lengthButton) self.tb.addWidget(self.widthsButton) self.tb.addWidget(self.areaButton) self.tb.addWidget(self.angleButton) self.tb.addWidget(self.undoButton) self.tb.addWidget(self.bezier) self.tb.addWidget(self.piecewise) #self.tb.setOrientation(QtCore.Qt.Vertical) def file_open(self): self.iw.scene.clear() self.image_name = QFileDialog.getOpenFileName(self, 'Open File') self.iw.pixmap = QtGui.QPixmap(self.image_name[0]) self.iw.pixmap_fit = self.iw.pixmap.scaled( self.iw.pixmap.width(), self.iw.pixmap.height(), QtCore.Qt.AspectRatioMode.KeepAspectRatio, transformMode=QtCore.Qt.TransformationMode.SmoothTransformation) self.iw.scene.addPixmap(self.iw.pixmap_fit) #add image self.iw.setScene(self.iw.scene) #Adjust window size automatically? self.iw.fitInView(self.iw.scene.sceneRect(), QtCore.Qt.AspectRatioMode.KeepAspectRatio) self.iw.scene.update() self.statusbar.showMessage('Select a measurement to make from the toolbar') self.lengthButton.setEnabled(True) self.areaButton.setEnabled(True) self.angleButton.setEnabled(True) self.exportButton.setEnabled(True) self.undoButton.setEnabled(True) self.bezier.setEnabled(True) self.bezier.setChecked(True) self.widthsButton.setEnabled(False) self.angleNames = [] self.areaNames = [] self.lengthNames = [] #self.iw.measurements = [[]] self.iw.widths = [] self.iw.lengths = [[]] self.iw.L = posData( np.empty(shape=(0, 0)), np.empty(shape=(0, 0))) #lengths self.iw.A = posData( np.empty(shape=(0, 0)), np.empty(shape=(0, 0))) #area self.iw.W = posData( np.empty(shape=(0, 0)), np.empty(shape=(0, 0))) #widths self.iw.T = angleData(np.empty(shape=(0, 0))) #angles self.iw.angleValues = np.empty((0,0)) self.iw.areaValues = np.empty((0,0)) self.iw._lastpos = None self.iw._thispos = None self.iw.measuring_length = False self.iw.measuring_area = False self.iw.measuring_widths = False self.iw.measuring_angle = False self.iw._zoom = 0 self.iw.factor = 1.0 self.iw.d = {} #dictionary for line items self.iw.k = 0 #initialize counter so lines turn yellow self.iw.m = None self.iw.scene.realline = None self.iw.scene.testline = None self.iw.scene.ellipseItem = None self.iw.scene.area_ellipseItem = None self.iw.scene.polyItem = None self.iw.image_name = None def measure_length(self): self.lel = QLineEdit(self) self.lel.move(130, 22) text, ok = QInputDialog.getText(self, 'Input Dialog', 'Length name') if ok: self.lel.setText(str(text)) self.lengthNames.append(self.lel.text()) QApplication.setOverrideCursor(QtCore.Qt.CursorShape.CrossCursor) #change cursor self.widthsButton.setChecked(False) self.widthsButton.setEnabled(False) self.iw.line_count = 0 self.iw.measuring_length = True self.iw.L = posData( np.empty(shape=(0, 0)), np.empty(shape=(0, 0))) #preallocate self.iw._lastpos = None self.iw._thispos = None self.statusbar.showMessage('Click initial point for length measurement') else: self.lengthButton.setChecked(False) def measure_angle(self): self.lea = QLineEdit(self) self.lea.move(130, 22) text, ok = QInputDialog.getText(self, 'Input Dialog', 'Angle name') if ok: self.lea.setText(str(text)) self.angleNames.append(self.lea.text()) QApplication.setOverrideCursor(QtCore.Qt.CrossCursor) #change cursor self.bezier.setEnabled(False) self.iw.measuring_angle = True self.iw._lastpos = None self.iw._thispos = None self.statusbar.showMessage('Click initial point for angle measurement') else: self.angleButton.setChecked(False) def measure_area(self): self.lea = QLineEdit(self) self.lea.move(130, 22) text, ok = QInputDialog.getText(self, 'Input Dialog', 'Area name') if ok: self.lea.setText(str(text)) self.areaNames.append(self.lea.text()) QApplication.setOverrideCursor(QtCore.Qt.CrossCursor) #change cursor self.bezier.setEnabled(False) self.iw.line_count = 0 self.iw.measuring_area = True self.iw._lastpos = None self.iw._thispos = None self.iw.A = posData( np.empty(shape=(0, 0)), np.empty(shape=(0, 0))) #preallocate self.statusbar.showMessage('Click initial point for area measurement') else: self.areaButton.setChecked(False) def undo(self): if self.iw.measuring_length: self.iw._thispos = self.iw._lastpos self.iw.L.downdate() #remove data self.iw.line_count += -1 self.iw.scene.removeItem(self.iw.scene.realline) #remove graphic self.iw.scene.realline = False if self.iw.measuring_area: self.iw._thispos = self.iw._lastpos self.iw.A.downdate() #remove data self.iw.line_count += -1 self.iw.scene.removeItem(self.iw.scene.realline) #remove graphic self.iw.scene.realline = False if self.iw.measuring_widths: self.iw.W.downdate() #remove data self.iw.scene.removeItem(self.iw.scene.ellipseItem) #remove graphic self.iw.scene.ellipseItem = False self.iw.d[str(self.iw.k)].setPen( QtGui.QPen(QtGui.QColor('black'))) #un-highlight next spine self.iw.k += -1 #reduce count if self.iw.measuring_angle: self.iw.T.downdate() #remove data self.iw._thispos = self.iw_lastpos self.iw.scene.removeItem(self.iw.scene.realline) #remove graphic self.iw.scene.realline = False def export_measurements(self): fac = max(self.iw.pixmap.width(), self.iw.pixmap.height()) / max( self.iw.pixmap_fit.width(), self.iw.pixmap_fit.height()) #scale pixel -> m by scaled image name = QFileDialog.getSaveFileName( self, 'Save File', self.image_name[0].split('.', 1)[0])[0] self.pixeldim = float(self.subWin.pixeldim.text()) self.altitude = float(self.subWin.altitude.text()) self.focal = float(self.subWin.focal.text()) #okay in mm https://www.imaging-resource.com/PRODS/sony-a5100/sony-a5100DAT.HTM if name: #Convert pixels to meters #measurements = [ f * fac * self.pixeldim * self.altitude / self.focal for f in self.iw.measurements] #lengths = [ f * fac * self.pixeldim * self.altitude / self.focal for f in self.iw.lengths] #print(self.iw.widths) areas = self.iw.areaValues * ( fac * self.pixeldim * self.altitude / self.focal)**2 values_optical = np.array([ self.subWin.id.text(), self.image_name[0], self.focal, self.altitude, self.pixeldim ]) names_optical = [ 'Image ID', 'Image Path', 'Focal Length', 'Altitude', 'Pixel Dimension' ] names_widths = ['Object'] + ['Length (m)'] + ['Widths (%)'] # + self.iw.widthNames[0] #names_widths.append([self.iw.widthNames[0]]) #Write .csv file print(f"Writing {name} to file") with open(name + '.csv', 'w') as csvfile: writer = csv.writer(csvfile) for (f, g) in zip(names_optical, values_optical): writer.writerow([f, g]) writer.writerow(['Notes', self.subWin.notes.toPlainText()]) writer.writerow(['']) writer.writerow(names_widths) for k,m in enumerate(self.lengthNames): #format and convert pixel length measurement l = "{0:.2f}".format( self.iw.lengths[k] * fac * self.pixeldim * self.altitude / self.focal ) if any(self.iw.widths[k]): #check if width measurement exists for length n = self.iw.widthNames[k] writer.writerow( [''] + [''] + n ) #format and convert pixel width measurement vals = [ "{0:.2f}".format(g * fac * self.pixeldim * self.altitude / self.focal) for g in self.iw.widths[k]] line = [m] + [l] + list(vals) else: #vals = l #f.copy() line = [m] + [l] writer.writerow(line) writer.writerow(['']) writer.writerow(['Object'] + ['Angle']) for k, f in enumerate(self.angleNames): #write angles line = [[f] + ["{0:.3f}".format(self.iw.angleValues[k])]] #need to convert NaNs to empty writer.writerows(line) writer.writerow(['']) writer.writerow(['Object'] + ['Area (m\u00B2)']) for k, f in enumerate(self.areaNames): #write areas line = [[f] + ["{0:.3f}".format(areas[k])]] #need to convert NaNs to empty writer.writerows(line) #Export image self.iw.fitInView(self.iw.scene.sceneRect(), QtCore.Qt.AspectRatioMode.KeepAspectRatio) pix = QtGui.QPixmap(self.iw.viewport().size()) self.iw.viewport().render(pix) pix.save(name + '-measurements.png')
class DocumentWidget(QWidget): def __init__(self, interactive_matching_widget): super(DocumentWidget, self).__init__(interactive_matching_widget) self.interactive_matching_widget = interactive_matching_widget self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(10) self.setLayout(self.layout) self.document = None self.current_nugget = None self.base_formatted_text = "" self.idx_mapper = {} self.nuggets_in_order = [] self.text_edit = QTextEdit() self.layout.addWidget(self.text_edit) self.text_edit.setReadOnly(True) self.text_edit.setFrameStyle(0) self.text_edit.setFont(CODE_FONT) self.text_edit.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setText("") self.buttons_widget = QWidget() self.buttons_widget_layout = QHBoxLayout(self.buttons_widget) self.layout.addWidget(self.buttons_widget) self.left_button = QPushButton("Skip Left") self.left_button.setFont(BUTTON_FONT) self.left_button.clicked.connect(self._left_button_clicked) self.buttons_widget_layout.addWidget(self.left_button) self.right_button = QPushButton("Skip Right") self.right_button.setFont(BUTTON_FONT) self.right_button.clicked.connect(self._right_button_clicked) self.buttons_widget_layout.addWidget(self.right_button) self.match_button = QPushButton("Confirm Match") self.match_button.setFont(BUTTON_FONT) self.match_button.clicked.connect(self._match_button_clicked) self.buttons_widget_layout.addWidget(self.match_button) self.no_match_button = QPushButton("There Is No Match") self.no_match_button.setFont(BUTTON_FONT) self.no_match_button.clicked.connect(self._no_match_button_clicked) self.buttons_widget_layout.addWidget(self.no_match_button) def _left_button_clicked(self): idx = self.nuggets_in_order.index(self.current_nugget) if idx > 0: self.current_nugget = self.nuggets_in_order[idx - 1] self._highlight_current_nugget() def _right_button_clicked(self): idx = self.nuggets_in_order.index(self.current_nugget) if idx < len(self.nuggets_in_order) - 1: self.current_nugget = self.nuggets_in_order[idx + 1] self._highlight_current_nugget() def _match_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task({ "message": "is-match", "nugget": self.current_nugget }) def _no_match_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task({ "message": "no-match-in-document", "nugget": self.current_nugget }) def _highlight_current_nugget(self): mapped_start_char = self.idx_mapper[self.current_nugget.start_char] mapped_end_char = self.idx_mapper[self.current_nugget.end_char] formatted_text = ( f"{self.base_formatted_text[:mapped_start_char]}" f"<span style='background-color: #FFFF00'><b>" f"{self.base_formatted_text[mapped_start_char:mapped_end_char]}</span></b>" f"{self.base_formatted_text[mapped_end_char:]}") self.text_edit.setText("") self.text_edit.textCursor().insertHtml(formatted_text) def update_document(self, nugget): self.document = nugget.document self.current_nugget = nugget self.nuggets_in_order = list( sorted(self.document.nuggets, key=lambda x: x.start_char)) if self.nuggets_in_order != []: self.idx_mapper = {} char_list = [] end_chars = [] next_start_char = 0 next_nugget_idx = 0 for idx, char in enumerate(list(self.document.text)): if idx == next_start_char: if end_chars == []: char_list += list("<b>") end_chars.append( self.nuggets_in_order[next_nugget_idx].end_char) next_nugget_idx += 1 if next_nugget_idx < len(self.nuggets_in_order): next_start_char = self.nuggets_in_order[ next_nugget_idx].start_char else: next_start_char = -1 while idx in end_chars: end_chars.remove(idx) if end_chars == []: char_list += list("</b>") self.idx_mapper[idx] = len(char_list) char_list.append(char) self.base_formatted_text = "".join(char_list) else: self.idx_mapper = {} for idx in range(len(self.document.text)): self.idx_mapper[idx] = idx self.base_formatted_text = "" self._highlight_current_nugget() scroll_cursor = QTextCursor(self.text_edit.document()) scroll_cursor.setPosition(nugget.start_char) self.text_edit.setTextCursor(scroll_cursor) self.text_edit.ensureCursorVisible() def enable_input(self): self.left_button.setEnabled(True) self.right_button.setEnabled(True) self.match_button.setEnabled(True) self.no_match_button.setEnabled(True) def disable_input(self): self.left_button.setDisabled(True) self.right_button.setDisabled(True) self.match_button.setDisabled(True) self.no_match_button.setDisabled(True)