class Sidebar(QWidget): def __init__(self): super().__init__() self.setWindowTitle = "Longway Sidebar" self.setGeometry(1920 - 180, 0, 180, 1080 - 50) self.layout = QVBoxLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) self.setStyleSheet( 'background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #4d77b3, stop: 0.015 #67a6d0, stop: 0.03 #5578ac, stop: 1 #3364af)') self.gadgets = [] self.splitter = QSplitter(Qt.Vertical) self.splitter.setChildrenCollapsible(False) self.splitter.setMinimumHeight(20) self.clickable = QPushButton() self.clickable.setStyleSheet(''' QPushButton { background-color: #00ffffff; } QPushButton:pressed { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #111166, stop: 1 #222277); } QPushButton:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #4685d9, stop: 1 #89c8f1); }; height: 24px; border: none; color:white; font-weight: bold;''') self.clickable.setText('Add Gadget') self.clickable.clicked.connect(self.add_gadget) for i in range(0): r = randint(0, 1) if r == 0: self.gadgets.append(GadgetText()) elif r == 1: self.gadgets.append(GadgetWeb()) # self.gadgets[-1].setFrameShape(QFrame.StyledPanel) self.splitter.addWidget(self.gadgets[-1]) self.layout.addWidget(self.splitter) self.layout.addWidget(self.clickable) self.setLayout(self.layout) self.setWindowFlags(Qt.FramelessWindowHint) self.show() def add_gadget(self): self.gadgets.append(GadgetText()) self.splitter.addWidget(self.gadgets[-1])
class KUZA(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.video_url_input = QLineEdit() self.video_url_input.setPlaceholderText('유튜브 URL을 입력하세요.') self.target_lang = QComboBox() self.target_lang.addItem('영어') self.target_lang.addItem('중국어(간체-중국)') self.target_lang.addItem('일본어') self.target_lang.addItem('이탈리아어') self.target_lang.addItem('독일어') self.target_lang.addItem('러시아어') self.target_lang.addItem('스페인어') self.target_lang.addItem('프랑스어') self.target_lang.setToolTip('영상의 언어를 선택하세요.') self.manual_lang = QLineEdit() self.manual_lang.setPlaceholderText('영상 언어를 GCP의 STT에 맞게 입력(선택)') self.submit_btn = QPushButton() self.submit_btn.setText('시작') self.submit_btn.clicked.connect(self.submit) self.json_upload_btn = QPushButton("JSON 파일 열기") self.json_upload_btn.clicked.connect(self.upload_GCP_key) self.search_bar_groupbox = QGroupBox() search_grid = QGridLayout() search_grid.addWidget(self.video_url_input, 0, 0) search_grid.addWidget(self.target_lang, 0, 1) search_grid.addWidget(self.manual_lang, 0, 2) search_grid.addWidget(self.submit_btn, 0, 3) search_grid.addWidget(self.json_upload_btn, 0, 4) self.search_bar_groupbox.setLayout(search_grid) self.search_bar_groupbox.setMaximumHeight(50) self.search_bar_groupbox.setMinimumWidth(850) self.webview = QWebEngineView() self.webview.load(QUrl('about:blank')) self.webview_groupbox = QGroupBox() webview_grid = QGridLayout() webview_grid.addWidget(self.webview) self.webview_groupbox.setLayout(webview_grid) self.webview.show() self.caption_text_groupbox = QGroupBox() caption_grid = QGridLayout() self.caption_text_display = QTextEdit() caption_grid.addWidget(self.caption_text_display) self.caption_text_groupbox.setLayout(caption_grid) self.split_video_caption = QSplitter(Qt.Horizontal) self.split_video_caption.addWidget(self.webview_groupbox) self.split_video_caption.addWidget(self.caption_text_groupbox) self.split_video_caption.setMinimumHeight(700) grid = QGridLayout() grid.addWidget(self.search_bar_groupbox, 0, 0) grid.addWidget(self.split_video_caption, 1, 0) self.setLayout(grid) self.setWindowTitle('K-UZA') self.showMaximized() def submit(self): url = self.video_url_input.text().strip() if not 'watch?v=' in url: caption = '정상적인 URL을 입력해주세요.' else: youtube_id_idx = url.find('watch?v=') + len('watch?v=') youtube_id = url[youtube_id_idx:] self.webview.load( QUrl('https://www.youtube.com/embed/' + youtube_id)) man_lang = self.manual_lang.text() if man_lang: caption = run_backend(url, man_lang) else: lang = self.target_lang.currentText() if lang: lang_GCP = { '영어': 'en-US', '중국어(간체-중국)': 'zh', '프랑스어': 'fr-FR', '러시아어': 'ru-RU', '이탈리아어': 'it-IT', '스페인어': 'es-ES', '일본어': 'ja-JP', '독일어': 'de-DE' } caption = run_backend(url, lang_GCP[lang]) self.caption_text_display.setText(caption) def upload_GCP_key(self): json_path = QFileDialog.getOpenFileName(self, 'Open File', './') if not json_path[0]: return with open(json_path[0], 'r') as json: GCP_KEY = json.read() with open('./GCP-API-KEY.json', 'w') as key_file: key_file.write(GCP_KEY)
class Sidebar(QWidget): def __init__(self): super().__init__() self.setWindowTitle = "Longway Sidebar" self.setGeometry(1920 - 180, 0, 180, 1080 - 32) self.color_gradient_left = QColor(100, 100, 100, 50) self.color_gradient_right = QColor(0, 0, 0, 255) self.layout = QVBoxLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) # self.layout.addWidget(QWidget()) self.setLayout(self.layout) # Splitter for storing tiles self.tiles = [] self.splitter = QSplitter(Qt.Vertical) self.splitter.setChildrenCollapsible(False) self.splitter.setMinimumHeight(20) self.splitter.setStyleSheet(''' QSplitter::handle:vertical { height: 1px; background: black }''') self.layout.addWidget(self.splitter) self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground) # Starting the show! self.addTile(TileSlideshow()) self.addTile(TileTest()) self.addTile(Tile()) self.addTile(Tile()) self.addTile(QWidget()) self.show() def paintEvent(self, event): # Initializing QPainter qp = QPainter() qp.begin(self) qp.setRenderHint(QPainter.Antialiasing) sidebar_rect = self.geometry() # Gradient gradient = QLinearGradient(0, 0, sidebar_rect.width(), 0) gradient.setColorAt(0.0, self.color_gradient_left) gradient.setColorAt(1.0, self.color_gradient_right) qp.setBrush(QBrush(gradient)) # qp.setPen(Qt.white) qp.drawRect(0, 0, sidebar_rect.width(), sidebar_rect.height()) # Glass highlight qp.setBrush(QBrush(Qt.white)) qp.setPen(QPen(QBrush(Qt.white), 0.01)) qp.setOpacity(0.1) qppath = QPainterPath() qppath.moveTo(sidebar_rect.width() * 0.2, 0) qppath.quadTo(sidebar_rect.width() * 0.3, sidebar_rect.height() * 0.5, sidebar_rect.width() * 0.2, sidebar_rect.height() - 1) qppath.lineTo(0, sidebar_rect.height()) qppath.lineTo(0, 0) qp.setClipPath(qppath) qp.drawRect(1, 1, sidebar_rect.width() - 1, sidebar.height() - 1) # Left border highlight qp.setOpacity(1.0) gradient = QLinearGradient(0, 0, 2, 0) gradient.setColorAt(0.0, QColor(255, 255, 255, 80)) gradient.setColorAt(1.0, QColor(0, 0, 0, 0)) qp.setBrush(QBrush(gradient)) # qp.setPen(Qt.transparent) qp.drawRect(0, 0, 8, sidebar_rect.height()) qp.end() def addTile(self, tile): self.tiles.append(tile) self.splitter.addWidget(self.tiles[-1])
class App(QWidget): def __init__(self): super(App, self).__init__() self.title = 'IotHoneyPot' self.left = 0 self.top = 0 desktop = QDesktopWidget() self.width = desktop.geometry().width() self.height = desktop.geometry().height() print "width : %d height %d" % (self.width, self.height) self.initUI() self.showMaximized() def initUI(self): self.setWindowTitle(self.title) #self.setGeometry(self.left, self.top, self.width, self.height) # Add box layout, add table to box layout and add box layout to widget self.mainLayout = QVBoxLayout() #creating the topBar self.topBar = QGroupBox() self.horizontalLayout = QHBoxLayout() startButton = QPushButton("START") stopButton = QPushButton("STOP") startButton.setFixedSize(100, 28) stopButton.setFixedSize(100, 28) self.horizontalLayout.addWidget(startButton, 0, Qt.AlignRight) self.horizontalLayout.addWidget(stopButton) self.topBar.setLayout(self.horizontalLayout) startButton.clicked.connect(self.Start) stopButton.clicked.connect(self.Stop) #topBar ends here # create centralWidet self.createCentralWidget() self.mainLayout.addWidget(self.topBar) self.mainLayout.addWidget(self.centralWidget) self.setLayout(self.mainLayout) #self.updatePorts("23") #self.updateAttackerIPs("192.168.8.17") # Show widget self.show() def Start(self): #self.my=thread.start_new_thread(startmiddleware,("192.168.43.152","23",self)) self.my = MyThread("192.168.1.3", "23", self) self.my.start() def Stop(self): self.my.stop() def updatePorts(self, port): item = QStandardItem(port) item.setEditable(False) self.ports.appendRow(item) def updateAttackerIPs(self, ip): item = QStandardItem(ip) item.setEditable(False) self.attackerIP.appendRow(item) def updateUIlogs(self, date, time, attackerip, attackerport, direction, localip, localport, protocol, byte, country, payload): global i self.tableWidget.insertRow(i + 1) self.tableWidget.setItem(i, 0, QTableWidgetItem(date)) self.tableWidget.setItem(i, 1, QTableWidgetItem(time)) self.tableWidget.setItem(i, 2, QTableWidgetItem(attackerip)) self.tableWidget.setItem(i, 3, QTableWidgetItem(attackerport)) self.tableWidget.setItem(i, 4, QTableWidgetItem(direction)) self.tableWidget.setItem(i, 5, QTableWidgetItem(localip)) self.tableWidget.setItem(i, 6, QTableWidgetItem(localport)) self.tableWidget.setItem(i, 7, QTableWidgetItem(protocol)) self.tableWidget.setItem(i, 8, QTableWidgetItem(byte)) self.tableWidget.setItem(i, 9, QTableWidgetItem(country)) self.tableWidget.setItem(i, 10, QTableWidgetItem(payload)) i = (i + 1) % 1500 self.tableWidget.show() def createCentralWidget(self): # Create table self.centralWidget = QSplitter(Qt.Horizontal) self.treeView = QTreeView() self.treeView.setHeaderHidden(True) standardModel = QStandardItemModel() rootNode = standardModel.invisibleRootItem() self.ports = QStandardItem("Local Ports") self.attackerIP = QStandardItem("Attacker IPs") self.ports.setEditable(False) self.attackerIP.setEditable(False) rootNode.appendRow(self.ports) rootNode.appendRow(self.attackerIP) self.treeView.setModel(standardModel) self.tableWidget = QTableWidget(self) self.tableWidget.setRowCount(1500) self.tableWidget.setColumnCount(11) self.tableWidget.verticalHeader().setVisible(False) self.tableWidget.setHorizontalHeaderLabels([ 'Date', 'Time', 'RemoteIP', 'Remote Port', 'Direction', 'Local IP', 'Local Port', 'Protocol', 'Bytes', 'Country', 'Packet Detail' ]) self.tableWidget.setColumnWidth(0, 100) self.tableWidget.setColumnWidth(2, self.width * 0.1) self.tableWidget.setColumnWidth(3, self.width * 0.07) self.tableWidget.setColumnWidth(4, self.width * 0.05) self.tableWidget.setColumnWidth(5, self.width * 0.1) self.tableWidget.setColumnWidth(6, self.width * 0.06) self.tableWidget.setColumnWidth(7, self.width * 0.05) self.tableWidget.setColumnWidth(8, self.width * 0.04) self.tableWidget.setColumnWidth(10, self.width * 0.095) # self.tableWidget.move(30,10) # table selection change self.tableWidget.doubleClicked.connect(self.on_click) self.treeView.setMinimumWidth(self.width * 0.15) self.tableWidget.setMinimumWidth(self.width * 0.85) self.centralWidget.addWidget(self.treeView) self.centralWidget.addWidget(self.tableWidget) self.centralWidget.setMinimumHeight(self.height * 0.95) @pyqtSlot() def on_click(self): print("\n") for currentQTableWidgetItem in self.tableWidget.selectedItems(): print(currentQTableWidgetItem.row(), currentQTableWidgetItem.column(), currentQTableWidgetItem.text())
class App(QMainWindow): def __init__(self): super().__init__() self.title = 'kfit' self.left = 400 self.top = 150 self.width = 1200 self.height = 800 self.file_name = '' self.xcol_idx = 0 self.ycol_idx = 1 self.ngau = 0 self.nlor = 0 self.nvoi = 0 self.nlin = 1 self.model = None self.result = None self.curves_df = None self.params_df = None self.edit_mode = False # empty Parameters to hold parameter guesses/constraints self.params = Parameters() self.guesses = {'value': {}, 'min': {}, 'max': {}} self.usr_vals = {'value': {}, 'min': {}, 'max': {}} self.usr_entry_widgets = {} self.cid = None # file import settings self.sep = ',' self.header = 'infer' self.index_col = None self.skiprows = None self.dtype = None self.encoding = None # keyboard shortcuts self.fit_shortcut = QShortcut(QKeySequence('Ctrl+F'), self) self.fit_shortcut.activated.connect(self.fit) self.reset_shortcut = QShortcut(QKeySequence('Ctrl+R'), self) self.reset_shortcut.activated.connect(self.hard_reset) self.save_fit = QShortcut(QKeySequence('Ctrl+S'), self) self.save_fit.activated.connect(self.export_results) self.add_gau = QShortcut(QKeySequence('G'), self) self.add_gau.activated.connect(lambda: self.increment('gau', True)) self.add_gau.activated.connect(self.init_param_widgets) self.sub_gau = QShortcut(QKeySequence('Shift+G'), self) self.sub_gau.activated.connect(lambda: self.increment('gau', False)) self.sub_gau.activated.connect(self.init_param_widgets) self.add_lor = QShortcut(QKeySequence('L'), self) self.add_lor.activated.connect(lambda: self.increment('lor', True)) self.add_lor.activated.connect(self.init_param_widgets) self.sub_lor = QShortcut(QKeySequence('Shift+L'), self) self.sub_lor.activated.connect(lambda: self.increment('lor', False)) self.sub_lor.activated.connect(self.init_param_widgets) self.add_voi = QShortcut(QKeySequence('V'), self) self.add_voi.activated.connect(lambda: self.increment('voi', True)) self.add_voi.activated.connect(self.init_param_widgets) self.sub_voi = QShortcut(QKeySequence('Shift+V'), self) self.sub_voi.activated.connect(lambda: self.increment('voi', False)) self.sub_voi.activated.connect(self.init_param_widgets) self.add_lin = QShortcut(QKeySequence('N'), self) self.add_lin.activated.connect(lambda: self.increment('lin', True)) self.add_lin.activated.connect(self.init_param_widgets) self.sub_lin = QShortcut(QKeySequence('Shift+N'), self) self.sub_lin.activated.connect(lambda: self.increment('lin', False)) self.sub_lin.activated.connect(self.init_param_widgets) # temporary data x = np.linspace(0, 10, 500) y = models.gauss(x, 0.5, 4, 0.4) + \ models.gauss(x, 0.8, 5, 0.2) + \ models.gauss(x, 0.4, 6, 0.3) + 0.2 # set data self.data = pd.DataFrame([x, y]).T self.data.columns = ['x', 'y'] self.x = self.data['x'] self.y = self.data['y'] self.xmin = self.data['x'].min() self.xmax = self.data['x'].max() self.initUI() def initUI(self): self.setGeometry(self.left, self.top, self.width, self.height) self.setWindowTitle(self.title) self.setWindowIcon(QIcon('../images/K.png')) # set up the status bar self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage('Welcome to kfit!', msg_length) self.status_bar.setStyleSheet('background-color: white') # Create the Main Widget and Layout self.main_layout = QVBoxLayout() self.main_widget = QSplitter() self.main_widget.setOrientation(Qt.Vertical) self.setCentralWidget(self.main_widget) # create "top bar" widget self.topbar_layout = QHBoxLayout() self.topbar_widget = QWidget() # fit button self.fit_button = QPushButton('Fit', self) self.fit_button.setMaximumWidth(100) self.fit_button.clicked.connect(self.fit) self.fit_button.installEventFilter(self) # import button self.import_button = QPushButton('Import', self) self.import_button.setMaximumWidth(100) self.import_button.clicked.connect(self.get_data) self.import_button.installEventFilter(self) # import settings button self.import_settings_button = QPushButton('', self) self.import_settings_button.setIcon( QIcon.fromTheme('stock_properties')) self.import_settings_button.setMaximumWidth(40) self.import_settings_button.clicked.connect( self.import_settings_dialog) self.import_settings_button.installEventFilter(self) # reset fit button self.reset_button = QPushButton('', self) self.reset_button.setIcon(QIcon.fromTheme('view-refresh')) self.reset_button.setMaximumWidth(40) self.reset_button.clicked.connect(self.hard_reset) self.reset_button.installEventFilter(self) # save results button self.save_button = QPushButton('', self) self.save_button.setIcon(QIcon.fromTheme('filesave')) self.save_button.setMaximumWidth(40) self.save_button.clicked.connect(self.export_results) self.save_button.installEventFilter(self) # progress bar self.progress_bar = QProgressBar() # self.progressBar.setMaximumWidth(150) self.progress_bar.hide() # get column header for x self.xlabel = QLabel(self) self.xlabel.setText('ColumnIndex(X):') self.xlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) # self.xlabel.setMaximumWidth(250) self.xline_entry = QLineEdit(self) self.xline_entry.setText('0') self.xline_entry.setAlignment(Qt.AlignCenter) # self.xline_entry.setMaximumWidth(50) self.xline_entry.returnPressed.connect(self.column_index_set) # get column header for y self.ylabel = QLabel(self) self.ylabel.setText('ColumnIndex(Y):') self.ylabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) # self.ylabel.setMaximumWidth(100) self.yline_entry = QLineEdit(self) self.yline_entry.setText('1') self.yline_entry.setAlignment(Qt.AlignCenter) # self.yline_entry.setMaximumWidth(50) self.yline_entry.returnPressed.connect(self.column_index_set) # add topbar widgets to layout self.topbar_layout.addSpacing(600) self.topbar_layout.addWidget(self.xlabel) self.topbar_layout.addWidget(self.xline_entry) self.topbar_layout.addWidget(self.ylabel) self.topbar_layout.addWidget(self.yline_entry) self.topbar_layout.addWidget(self.fit_button) self.topbar_layout.addWidget(self.import_button) self.topbar_layout.addWidget(self.import_settings_button) self.topbar_layout.addWidget(self.reset_button) self.topbar_layout.addWidget(self.save_button) self.topbar_layout.addWidget(self.progress_bar) self.topbar_layout.setAlignment(Qt.AlignRight) self.topbar_widget.setLayout(self.topbar_layout) self.topbar_widget.setMaximumHeight(75) # create tabs widget self.tabs = QTabWidget(self) self.tab1 = QWidget(self) self.tab2 = QTableView(self) self.tab3 = QWidget(self) self.tabs.addTab(self.tab1, 'Graph') self.tabs.addTab(self.tab2, 'Data') self.tabs.addTab(self.tab3, 'Output') self.tabs.setMinimumHeight(300) # create params widget self.params_widget = QSplitter() self.gau_layout = QVBoxLayout() self.gau_layout.setAlignment(Qt.AlignTop) self.gau_widget = QWidget() self.gau_widget.setLayout(self.gau_layout) self.gau_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.gau_scroll = QScrollArea() self.gau_scroll.setWidget(self.gau_widget) self.gau_scroll.setWidgetResizable(True) self.lor_widget = QWidget() self.lor_layout = QVBoxLayout() self.lor_layout.setAlignment(Qt.AlignTop) self.lor_widget.setLayout(self.lor_layout) self.lor_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.lor_scroll = QScrollArea() self.lor_scroll.setWidget(self.lor_widget) self.lor_scroll.setWidgetResizable(True) self.voi_widget = QWidget() self.voi_layout = QVBoxLayout() self.voi_layout.setAlignment(Qt.AlignTop) self.voi_widget.setLayout(self.voi_layout) self.voi_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.voi_scroll = QScrollArea() self.voi_scroll.setWidget(self.voi_widget) self.voi_scroll.setWidgetResizable(True) self.lin_widget = QWidget() self.lin_layout = QVBoxLayout() self.lin_layout.setAlignment(Qt.AlignTop) self.lin_widget.setLayout(self.lin_layout) self.lin_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.lin_scroll = QScrollArea() self.lin_scroll.setWidget(self.lin_widget) self.lin_scroll.setWidgetResizable(True) self.params_widget.addWidget(self.gau_scroll) self.params_widget.addWidget(self.lor_scroll) self.params_widget.addWidget(self.voi_scroll) self.params_widget.addWidget(self.lin_scroll) self.params_widget.setMinimumHeight(180) # add everything to main widget self.main_widget.addWidget(self.topbar_widget) self.main_widget.addWidget(self.tabs) self.main_widget.addWidget(self.params_widget) # Tab 1 - Graph / Model # Graph plt.style.use('fivethirtyeight') self.tab1.figure = Figure(figsize=(8, 6), dpi=60) self.tab1.canvas = FigureCanvas(self.tab1.figure) self.tab1.toolbar = NavigationToolbar(self.tab1.canvas, self) # tristate checkbox for edit mode self.emode_box = QCheckBox() self.emode_box.setTristate(True) self.emode_box.setIcon(QIcon.fromTheme('stock_edit')) self.emode_box.stateChanged.connect(self.toggle_edit_mode) self.emode_box.installEventFilter(self) # tweaking the toolbar layout self.tab1.toolbar.setIconSize(QSize(18, 18)) spacer = QWidget() spacer.setFixedWidth(20) self.tab1.toolbar.addWidget(spacer) self.tab1.toolbar.addWidget(self.emode_box) self.tab1.toolbar.locLabel.setAlignment(Qt.AlignRight | Qt.AlignCenter) graph_layout = QVBoxLayout() graph_layout.addWidget(self.tab1.toolbar) graph_layout.addWidget(self.tab1.canvas) # The "Set Model" layout model_layout = QGridLayout() widget_setgau = QWidget() layout_setgau = QHBoxLayout() layout_setgau.setSizeConstraint(QLayout.SetFixedSize) widget_setgau.setLayout(layout_setgau) widget_setlor = QWidget() layout_setlor = QHBoxLayout() layout_setlor.setSizeConstraint(QLayout.SetFixedSize) widget_setlor.setLayout(layout_setlor) widget_setvoi = QWidget() layout_setvoi = QHBoxLayout() layout_setvoi.setSizeConstraint(QLayout.SetFixedSize) widget_setvoi.setLayout(layout_setvoi) widget_setlin = QWidget() layout_setlin = QHBoxLayout() layout_setlin.setSizeConstraint(QLayout.SetFixedSize) widget_setlin.setLayout(layout_setlin) model_layout.addWidget(widget_setgau, 0, 0) model_layout.addWidget(widget_setlor, 0, 1) model_layout.addWidget(widget_setvoi, 0, 2) model_layout.addWidget(widget_setlin, 0, 3) # specify number of gaussian curves gauss_label = QLabel(self) gauss_label.setText('Gaussians') gauss_label.setAlignment(Qt.AlignVCenter) gauss_button_add = QPushButton('', self) gauss_button_add.setIcon(QIcon.fromTheme('list-add')) gauss_button_add.clicked.connect(lambda: self.increment('gau', True)) gauss_button_add.clicked.connect(self.init_param_widgets) gauss_button_sub = QPushButton('', self) gauss_button_sub.setIcon(QIcon.fromTheme('list-remove')) gauss_button_sub.clicked.connect(lambda: self.increment('gau', False)) gauss_button_sub.clicked.connect(self.init_param_widgets) layout_setgau.addWidget(gauss_label) layout_setgau.addWidget(gauss_button_add) layout_setgau.addWidget(gauss_button_sub) # specify number of lorentzian curves lorentz_label = QLabel(self) lorentz_label.setText('Lorentzians') lorentz_label.setAlignment(Qt.AlignVCenter) lorentz_button_add = QPushButton('', self) lorentz_button_add.setIcon(QIcon.fromTheme('list-add')) lorentz_button_add.clicked.connect(lambda: self.increment('lor', True)) lorentz_button_add.clicked.connect(self.init_param_widgets) lorentz_button_sub = QPushButton('', self) lorentz_button_sub.setIcon(QIcon.fromTheme('list-remove')) lorentz_button_sub.clicked.connect( lambda: self.increment('lor', False)) lorentz_button_sub.clicked.connect(self.init_param_widgets) layout_setlor.addWidget(lorentz_label) layout_setlor.addWidget(lorentz_button_add) layout_setlor.addWidget(lorentz_button_sub) # specify number of voigt curves voigt_label = QLabel(self) voigt_label.setText('Pseudo-Voigts') voigt_label.setAlignment(Qt.AlignVCenter) voigt_button_add = QPushButton('', self) voigt_button_add.setIcon(QIcon.fromTheme('list-add')) voigt_button_add.clicked.connect(lambda: self.increment('voi', True)) voigt_button_add.clicked.connect(self.init_param_widgets) voigt_button_sub = QPushButton('', self) voigt_button_sub.setIcon(QIcon.fromTheme('list-remove')) voigt_button_sub.clicked.connect(lambda: self.increment('voi', False)) voigt_button_sub.clicked.connect(self.init_param_widgets) layout_setvoi.addWidget(voigt_label) layout_setvoi.addWidget(voigt_button_add) layout_setvoi.addWidget(voigt_button_sub) # specify number of lines line_label = QLabel(self) line_label.setText('Lines:') line_label.setAlignment(Qt.AlignVCenter) line_button_add = QPushButton('', self) line_button_add.setIcon(QIcon.fromTheme('list-add')) line_button_add.clicked.connect(lambda: self.increment('lin', True)) line_button_add.clicked.connect(self.init_param_widgets) line_button_sub = QPushButton('', self) line_button_sub.setIcon(QIcon.fromTheme('list-remove')) line_button_sub.clicked.connect(lambda: self.increment('lin', False)) line_button_sub.clicked.connect(self.init_param_widgets) layout_setlin.addWidget(line_label) layout_setlin.addWidget(line_button_add) layout_setlin.addWidget(line_button_sub) graph_layout.addLayout(model_layout) self.tab1.setLayout(graph_layout) self.plot() # Tab 2 - Data Table self.table_model = PandasModel(self.data) self.tab2.setModel(self.table_model) self.tab2.resizeColumnsToContents() # Tab 3 - Output self.tab3_widget = QPlainTextEdit() tab3_layout = QVBoxLayout() tab3_layout.addWidget(self.tab3_widget) self.tab3.setLayout(tab3_layout) self.init_param_widgets() self.show() def export_results(self): self.process_results() # open file dialog exp_file_name, _ = QFileDialog.getSaveFileName( self, 'QFileDialog.getSaveFileName()', 'fit_results.csv', 'CSV files (*.csv)', ) if exp_file_name: self.process_results() self.curves_df.to_csv(exp_file_name) # NOTE: if user chooses a file extension other than .csv, or does # not use a file extension, this should still work, but I haven't # tested too rigorously yet self.params_df.to_csv('{}.params.csv'.format( exp_file_name[:exp_file_name.find('.csv')])) self.status_bar.showMessage( 'Exported fit results to: ' + exp_file_name, 2 * msg_length) else: self.status_bar.showMessage('Export canceled.', 2 * msg_length) return def process_results(self): if self.result is not None: self.params_df = pd.DataFrame.from_dict(self.result.best_values, orient='index') self.params_df.index.name = 'parameter' self.params_df.columns = ['value'] curves_dict = { 'data': self.y, 'total_fit': self.result.best_fit, } components = self.result.eval_components() for i, comp in enumerate(components): curves_dict[comp[:comp.find('_')]] = components[comp] self.curves_df = pd.DataFrame.from_dict(curves_dict) self.curves_df.index = self.x self.curves_df.index.name = self.data.columns[self.xcol_idx] else: self.status_bar.showMessage('No fit results to export!', msg_length) def eventFilter(self, object, event): if event.type() == QEvent.Enter: if object is self.reset_button: self.status_bar.showMessage("Reset fit") if object is self.import_settings_button: self.status_bar.showMessage("File import settings") if object is self.import_button: self.status_bar.showMessage("Import .csv file") if object is self.fit_button: self.status_bar.showMessage("Fit data") if object is self.emode_box: self.status_bar.showMessage("Toggle edit mode") if object is self.save_button: self.status_bar.showMessage("Export fit results") return True elif event.type() == QEvent.Leave: self.status_bar.showMessage(None) return False def init_model(self): # increment() ensures nlin >= 1 self.model = models.line_mod(self.nlin) if self.ngau != 0: self.model += models.gauss_mod(self.ngau) if self.nlor != 0: self.model += models.lor_mod(self.nlor) if self.nvoi != 0: self.model += models.voigt_mod(self.nvoi) self.status_bar.showMessage( "Model updated: " + str([self.ngau, self.nlor, self.nvoi, self.nlin]), msg_length) def init_param_widgets(self): self.init_model() self.usr_entry_widgets = {'value': {}, 'min': {}, 'max': {}} labels = {} rnd = 3 # decimals to round to in placeholder text self.clear_fit_layouts() for param_name in self.model.param_names: # set param label text labels[param_name] = QLabel() labels[param_name].setText(param_name) # make qlineedit widgets for key in self.usr_entry_widgets: self.usr_entry_widgets[key][param_name] = QLineEdit() if param_name in self.usr_vals[key]: self.usr_entry_widgets[key][param_name]\ .setPlaceholderText( str(round(self.usr_vals[key][param_name], rnd)) ) else: self.usr_entry_widgets[key][param_name]\ .setPlaceholderText(key) # set up connections # connect() expects a callable func, hence the lambda self.usr_entry_widgets[key][param_name].returnPressed.connect( lambda: self.update_usr_vals(self.usr_entry_widgets)) # add widgets to respective layouts sublayout1 = QVBoxLayout() sublayout2 = QHBoxLayout() sublayout1.addWidget(labels[param_name]) for key in self.usr_entry_widgets: sublayout2.addWidget(self.usr_entry_widgets[key][param_name]) if param_name.find('gau') != -1: self.gau_layout.addLayout(sublayout1) self.gau_layout.addLayout(sublayout2) if param_name.find('lor') != -1: self.lor_layout.addLayout(sublayout1) self.lor_layout.addLayout(sublayout2) if param_name.find('voi') != -1: self.voi_layout.addLayout(sublayout1) self.voi_layout.addLayout(sublayout2) if param_name.find('lin') != -1: self.lin_layout.addLayout(sublayout1) self.lin_layout.addLayout(sublayout2) # Resize all of the LineEntry widgets for key in self.usr_entry_widgets: for param, widget in self.usr_entry_widgets[key].items(): widget.setMaximumWidth(150) if self.result is not None: self.set_params() self.update_param_widgets() def update_usr_vals(self, entry): # get text input from each usr_entry_widget for val_type, param_dict in self.usr_entry_widgets.items(): for param, param_widget in param_dict.items(): try: self.usr_vals[val_type][param] = \ float(param_widget.text()) except Exception: pass def update_param_widgets(self): rnd = 3 # the 'value' placeholder text is the result for that param # taken from self.result # the 'min' and 'max' text is from either the self.guesses # or from self.usr_vals for param in self.params: if param in self.result.best_values: self.usr_entry_widgets['value'][param].setPlaceholderText( str(round(self.result.best_values[param], rnd))) self.usr_entry_widgets['min'][param].setPlaceholderText( str(round(self.params[param].min, rnd))) self.usr_entry_widgets['max'][param].setPlaceholderText( str(round(self.params[param].max, rnd))) def guess_params(self): for comp in self.model.components: if comp.prefix.find('gau') != -1 or \ comp.prefix.find('lor') != -1 or \ comp.prefix.find('voi') != -1: # need to define explicitly to make proper guesses c = comp.prefix + 'center' a = comp.prefix + 'amplitude' s = comp.prefix + 'sigma' f = comp.prefix + 'fraction' self.guesses['value'][c] = \ self.data.iloc[:, self.xcol_idx].mean() self.guesses['value'][a] = \ self.data.iloc[:, self.ycol_idx].mean() self.guesses['value'][s] = \ self.data.iloc[:, self.xcol_idx].std() self.guesses['min'][c] = None self.guesses['min'][a] = 0 self.guesses['min'][s] = 0 self.guesses['max'][c] = None self.guesses['max'][a] = None self.guesses['max'][s] = None if comp.prefix.find('voi') != -1: self.guesses['value'][f] = 0.5 self.guesses['min'][f] = 0 self.guesses['max'][f] = 1 else: slope = comp.prefix + 'slope' intc = comp.prefix + 'intercept' for p in [slope, intc]: self.guesses['value'][p] = \ self.data.iloc[:, self.ycol_idx].mean() self.guesses['min'][p] = None self.guesses['max'][p] = None def set_params(self): self.params = Parameters() self.guess_params() self.update_usr_vals(self.usr_entry_widgets) vals = {} # fill params with any user-entered values # fill in blanks with guesses for param_name in self.model.param_names: for val_type in ['value', 'min', 'max']: if param_name in self.usr_vals[val_type]: vals[val_type] = self.usr_vals[val_type][param_name] # print('param: ' + param_name + ', type: ' +\ # val_type + ', set_by: user') else: vals[val_type] = self.guesses[val_type][param_name] # print('param: ' + param_name + ', type: ' +\ # val_type + ', set_by: guess') self.params.add(name=param_name, value=vals['value'], vary=True, min=vals['min'], max=vals['max']) def set_xy_range(self): self.x = self.data.iloc[:, self.xcol_idx] self.y = self.data.iloc[:, self.ycol_idx] self.xmin, self.xmax = self.ax.get_xlim() range_bool = (self.x >= self.xmin) & (self.x <= self.xmax) self.x = self.x[range_bool].values self.y = self.y[range_bool].values def reset_xy_range(self): self.xmin = np.min(self.x) self.xmax = np.max(self.x) def fit(self): self.emode_box.setCheckState(0) self.toggle_edit_mode() self.set_xy_range() self.set_params() self.result = self.model.fit(data=self.y, params=self.params, x=self.x, method='least_squares') self.tab3_widget.clear() self.tab3_widget.insertPlainText(self.result.fit_report()) self.plot() # overwrite widgets to clear input (not ideal method..) self.init_param_widgets() # update widgets with new placeholder text self.update_param_widgets() def column_index_set(self): # make sure user enters index that can be converted to int try: idx_x = int(self.xline_entry.text()) except ValueError: self.status_bar.showMessage(idx_type_error_msg, msg_length) self.xline_entry.setText(None) return try: idx_y = int(self.yline_entry.text()) except ValueError: self.status_bar.showMessage(idx_type_error_msg, msg_length) self.yline_entry.setText(None) return self.xcol_idx = idx_x self.ycol_idx = idx_y self.result = None # make sure user enters an index that's in the data range try: self.x = self.data.iloc[:, self.xcol_idx] except IndexError: self.status_bar.showMessage(idx_range_error_msg, msg_length) self.xline_entry.setText(None) return try: self.y = self.data.iloc[:, self.ycol_idx] except IndexError: self.status_bar.showMessage(idx_range_error_msg, msg_length) self.yline_entry.setText(None) return self.xmin = np.min(self.x) self.xmax = np.max(self.x) self.plot() self.status_bar.showMessage( 'ColumnIndex(X) = ' + str(idx_x) + ', ' + 'ColumnIndex(Y) = ' + str(idx_y), msg_length) def toggle_edit_mode(self): # first toggle off the zoom or pan button # so they don't interfere with edit_mode cursor style if self.tab1.toolbar._active == 'ZOOM': self.tab1.toolbar.zoom() if self.tab1.toolbar._active == 'PAN': self.tab1.toolbar.pan() states = { 0: 'Edit mode off', 1: 'Edit mode on | copy x-value', 2: 'Edit mode on | copy y-value', } self.status_bar.showMessage(states[self.emode_box.checkState()], msg_length) if self.emode_box.checkState() == 0: self.mpl_cursor = None self.tab1.canvas.mpl_disconnect(self.cid) if self.emode_box.checkState() == 1: self.mpl_cursor = Cursor(self.ax, lw=1, c='red', linestyle='--') self.cid = self.tab1.canvas.mpl_connect('button_press_event', self.get_coord_click) if self.emode_box.checkState() == 2: self.cid = self.tab1.canvas.mpl_connect('button_press_event', self.get_coord_click) def get_coord_click(self, event): self.x_edit, self.y_edit = round(event.xdata, 3), round(event.ydata, 3) if self.emode_box.checkState() == 1: pyperclip.copy(self.x_edit) self.status_bar.showMessage( 'Copied X=' + str(self.x_edit) + ' to clipboard!', msg_length) if self.emode_box.checkState() == 2: pyperclip.copy(self.y_edit) self.status_bar.showMessage( 'Copied Y=' + str(self.y_edit) + ' to clipboard!', msg_length) def close_app(self): sys.exit() def get_data(self): self.emode_box.setCheckState(0) self.toggle_edit_mode() # reset column indices self.xcol_idx = 0 self.ycol_idx = 1 # open file dialog self.file_name, _ = QFileDialog.getOpenFileName( self, 'Open File', '', 'CSV files (*.csv);; All Files (*)') if self.file_name: # this message isn't showing up... # TODO: needs to be threaded self.status_bar.showMessage('Importing: ' + self.file_name, msg_length) try: df = tools.to_df(self.file_name, sep=self.sep, header=self.header, index_col=self.index_col, skiprows=self.skiprows, dtype=self.dtype, encoding=self.encoding) df.iloc[:, self.xcol_idx] df.iloc[:, self.ycol_idx] except Exception: self.status_bar.showMessage(file_import_error_msg, 2 * msg_length) return else: self.status_bar.showMessage('Import canceled.', msg_length) return self.data = df self.table_model = PandasModel(self.data) self.tab2.setModel(self.table_model) self.tab2.resizeColumnsToContents() # clear any previous fit result self.result = None # reset x, y, and xlim self.x = self.data.iloc[:, self.xcol_idx].values self.y = self.data.iloc[:, self.ycol_idx].values self.xmin = self.data.iloc[:, self.xcol_idx].min() self.xmax = self.data.iloc[:, self.xcol_idx].max() self.plot() self.status_bar.showMessage('Import finished.', msg_length) def import_settings_dialog(self): self.dialog_window = QDialog() self.dialog_window.setWindowTitle('File Import Settings') toplevel = QVBoxLayout() dialog_layout = QHBoxLayout() label_layout = QVBoxLayout() entry_layout = QVBoxLayout() button_layout = QVBoxLayout() label1 = QLabel(self.dialog_window) label1.setText('sep') label2 = QLabel(self.dialog_window) label2.setText('header') label3 = QLabel(self.dialog_window) label3.setText('skiprows') label4 = QLabel(self.dialog_window) label4.setText('dtype') label5 = QLabel(self.dialog_window) label5.setText('encoding') for lbl in [label1, label2, label3, label4, label5]: label_layout.addWidget(lbl) self.sep_edit = QLineEdit(self.dialog_window) self.head_edit = QLineEdit(self.dialog_window) self.skipr_edit = QLineEdit(self.dialog_window) self.dtype_edit = QLineEdit(self.dialog_window) self.enc_edit = QLineEdit(self.dialog_window) self.sep_edit.setText(self.sep) self.head_edit.setText(self.header) # if value is None, show text as 'None' if self.skiprows is not None: self.skipr_edit.setText(self.skiprows) else: self.skipr_edit.setText('None') if self.dtype is not None: self.dtype_edit.setText(self.dtype) else: self.dtype_edit.setText('None') if self.encoding is not None: self.enc_edit.setText(self.encoding) else: self.enc_edit.setText('None') # add widgets to layout for ewidget in [ self.sep_edit, self.head_edit, self.skipr_edit, self.dtype_edit, self.enc_edit ]: ewidget.setAlignment(Qt.AlignCenter) entry_layout.addWidget(ewidget) button1 = QPushButton('Set', self.dialog_window) button2 = QPushButton('Set', self.dialog_window) button3 = QPushButton('Set', self.dialog_window) button4 = QPushButton('Set', self.dialog_window) button5 = QPushButton('Set', self.dialog_window) for btn in [button1, button2, button3, button4, button5]: btn.clicked.connect(self.set_import_settings) button_layout.addWidget(btn) reflabel = QLabel(self.dialog_window) reflabel.setText( "for help, refer to <a href='https://pandas.pydata.org/" + "pandas-docs/stable/reference/api/" + "pandas.read_csv.html'>pandas.read_csv()</a>") reflabel.setOpenExternalLinks(True) reflabel.setAlignment(Qt.AlignCenter) for lo in [label_layout, entry_layout, button_layout]: dialog_layout.addLayout(lo) toplevel.addLayout(dialog_layout) toplevel.addSpacing(25) toplevel.addWidget(reflabel) self.dialog_window.setLayout(toplevel) self.dialog_window.setWindowModality(Qt.ApplicationModal) self.dialog_window.exec_() def set_import_settings(self): self.sep = self.sep_edit.text() self.header = self.head_edit.text() # convert 'None' entries to None if self.skipr_edit.text() == 'None': self.skiprows = None else: self.skiprows = self.skipr_edit.text() if self.dtype_edit.text() == 'None': self.dtype = None else: self.dtype = self.dtype_edit.text() if self.enc_edit.text() == 'None': self.encoding = None else: self.encoding = self.enc_edit.text() def plot(self): self.tab1.figure.clear() self.ax = self.tab1.figure.add_subplot(111, label=self.file_name) self.ax.scatter(self.x, self.y, s=100, c='None', edgecolors='black', linewidth=1, label='data') if self.result is not None: yfit = self.result.best_fit self.ax.plot(self.x, yfit, c='r', linewidth=2.5) cmap = cm.get_cmap('gnuplot') components = self.result.eval_components() for i, comp in enumerate(components): self.ax.plot(self.x, components[comp], linewidth=2.5, linestyle='--', c=cmap(i / len(components)), label=comp[:comp.find('_')]) self.ax.set_xlabel(self.data.columns[self.xcol_idx], labelpad=15) self.ax.set_ylabel(self.data.columns[self.ycol_idx], labelpad=15) self.ax.set_xlim([self.xmin, self.xmax]) self.ax.legend(loc='upper right') self.tab1.figure.subplots_adjust(bottom=0.15, left=0.06, right=0.94) self.tab1.canvas.draw() def clear_layout(self, layout): if layout: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget: widget.deleteLater() else: self.clear_layout(item.layout()) layout.removeItem(item) def clear_fit_layouts(self): for layout in [ self.gau_layout, self.lor_layout, self.voi_layout, self.lin_layout ]: self.clear_layout(layout) def hard_reset(self): self.clear_fit_layouts() self.ngau = 0 self.nlor = 0 self.nvoi = 0 self.nlin = 1 self.init_model() self.params = Parameters() self.result = None self.params_df = None self.curves_df = None self.guesses = {'value': {}, 'min': {}, 'max': {}} self.usr_vals = {'value': {}, 'min': {}, 'max': {}} self.init_param_widgets() self.plot() def increment(self, val, add): if add: if val == 'gau': self.ngau += 1 if val == 'lor': self.nlor += 1 if val == 'voi': self.nvoi += 1 if val == 'lin': self.nlin += 1 if not add: if val == 'gau': self.ngau -= 1 if val == 'lor': self.nlor -= 1 if val == 'voi': self.nvoi -= 1 if val == 'lin': self.nlin -= 1 # make sure value doesn't go below zero if self.ngau < 0: self.ngau = 0 if self.nlor < 0: self.nlor = 0 if self.nvoi < 0: self.nvoi = 0 if self.nlin < 1: self.nlin = 1
class Visualization: def __init__(self, world): """ Main Interface between the OpenGL stuff and the simulator. Initializes the camera, and the opengl-widget. controlls the speed of the simulation. :param world: the world class """ self._world = world self._last_light_rotation = 0 self._rounds_per_second = 10 self._reset_flag = False self._running = False self._app = None self._viewer = None self._gui = None self._splitter = None self._recording = False self._animation = world.config_data.animation self._auto_animation = world.config_data.auto_animation self._manual_animation_speed = world.config_data.manual_animation_speed self.light_rotation = False self.grid_size = world.grid.size # create the QApplication self._app = QApplication([]) # create camera for the visualization # if grid is 2D, set to orthographic projection (it is better for 2D) if self._world.grid.get_dimension_count() == 2: self._camera = Camera( self._world.config_data.window_size_x, self._world.config_data.window_size_y, self._world.config_data.look_at, self._world.config_data.phi, self._world.config_data.theta, self._world.config_data.radius, self._world.config_data.fov, self._world.config_data.cursor_offset, self._world.config_data.render_distance, "ortho", self._world.grid.get_scaling()) else: self._camera = Camera( self._world.config_data.window_size_x, self._world.config_data.window_size_y, self._world.config_data.look_at, self._world.config_data.phi, self._world.config_data.theta, self._world.config_data.radius, self._world.config_data.fov, self._world.config_data.cursor_offset, self._world.config_data.render_distance, "perspective", self._world.grid.get_scaling()) # create the opengl widget self._viewer = OGLWidget(self._world, self._camera) self._viewer.glInit() self.recorder = Recorder(self._world, self._viewer) # create and show the main Window self._splitter = QSplitter() self._splitter.closeEvent = close self._splitter.setMinimumWidth(self._world.config_data.window_size_x) self._splitter.setMinimumHeight(self._world.config_data.window_size_y) self._splitter.setWindowTitle("Simulator") self._splitter.show() # create gui # creating the gui has to happen after showing the window, so the gui can access # opengl variables and programs during creation self._gui_module = importlib.import_module('components.gui.' + self._world.config_data.gui) # the key press handler def key_press_event(event): self._gui_module.key_handler(event.key(), self._world, self) # loading key handler from gui module if "key_handler" in dir(self._gui_module): self._viewer.keyPressEventHandler = key_press_event self._splitter.keyPressEvent = key_press_event else: show_msg("No key_handler(key, vis) function found in gui module!", Level.WARNING, self._splitter) # loading gui from gui module if "create_gui" in dir(self._gui_module): self._gui = self._gui_module.create_gui(self._world, self) if self._gui is not None and issubclass(self._gui.__class__, QWidget): self._splitter.addWidget(self._gui) self._splitter.keyPressEvent = self._viewer.keyPressEvent self._splitter.keyReleaseEvent = self._viewer.keyReleaseEvent self._splitter.addWidget(self._viewer) self._splitter.setSizes([ self._world.config_data.window_size_x * 0.25, self._world.config_data.window_size_x * 0.75 ]) else: # noinspection PyUnresolvedReferences show_msg( "The create_gui(world, vis) function in gui module didn't return a QWidget." + "Expected a QWidget or a subclass, but got %s." % self._gui.__class__.__name__, Level.WARNING, self._splitter) self._splitter.addWidget(self._viewer) else: show_msg( "No create_gui(world, vis) function found in gui module. GUI not created", Level.INFO, self._splitter) self._splitter.addWidget(self._viewer) # waiting for the simulation window to be fully active while not self._splitter.windowHandle().isExposed(): self._process_events() # first update and draw call. self._viewer.update_scene() def is_recording(self): return self._recording def _process_events(self): self._app.processEvents() if self._reset_flag: raise ResetException() def reset(self): """ stops the simulation. deletes all data in the visualization. resets the camera :return: """ self._reset_flag = False self._running = False self._viewer.agent_offset_data = {} self._viewer.agent_update_flag = True self._viewer.item_offset_data = {} self._viewer.item_update_flag = True self._viewer.location_offset_data = {} self._viewer.location_update_flag = True self._viewer.update_data() self._camera.reset() self._viewer.update_scene() def wait_for_thread(self, thread: Thread, window_message, window_title): """ executes a thread and shows a loading window till the thread stops. blocks the gui, while thread runs. :param thread: the thread :param window_message: the displayed message :param window_title: the title of the loading window :return: """ loading_window = LoadingWindow(window_message, window_title) if self._gui is not None and issubclass(self._gui.__class__, QWidget): self._gui.setDisabled(True) thread.start() while thread.is_alive(): try: self._process_events() except VisualizationError as ve: show_msg(ve, Level.CRITICAL, self._splitter) exit(1) thread.join() loading_window.close() self._gui.setDisabled(False) def rotate_light(self): """ rotates the light direction at a steady degrees/second velocity independent of the CPU-clock or framerate. :return: """ # rotation of light only in 3d if self._world.grid.get_dimension_count() > 2: # 20° per second rotation if self._last_light_rotation == 0: self._last_light_rotation = time.perf_counter() else: angle = (time.perf_counter() - self._last_light_rotation) * 20 self._last_light_rotation = time.perf_counter() self._viewer.rotate_light(angle) self._viewer.glDraw() def start_stop(self): """ starts and pauses the simulation :return: """ self._running = not self._running def _wait_while_not_running(self): """ helper function. waits until the running flag is set. :return: """ sleep_time = 1.0 / 120.0 self._process_events() if self.light_rotation: self.rotate_light() while not self._running: # sleeping for 1/120 secs, for responsive GUI time.sleep(sleep_time) if self.light_rotation: self.rotate_light() self._process_events() def animate(self, round_start_time, speed): """ loop for animating the movement of agents and carried items :param round_start_time: the start of the round :param speed: speed of the animation in 1/steps. less or equal zero = automatic mode """ if speed < 0: # draw at location according to the passed time and the rps half_round_time = (1.0 / self._rounds_per_second) / 2.0 now = time.perf_counter() while (now - round_start_time) < half_round_time: self._viewer.set_animation_percentage( min(1, (now - round_start_time) / half_round_time)) self._process_events() self._viewer.glDraw() now = time.perf_counter() else: # draw at location according to the selected animation speed for i in range(1, max(1, speed)): self._viewer.set_animation_percentage(float(i / max(1, speed))) self._process_events() self._viewer.glDraw() self._viewer.set_animation_percentage(1) self._viewer.glDraw() # reset the previous position after animation. # not reseting it causes a visual bug if the matter didn't move. for agent in self._viewer.agent_offset_data: current_data = self._viewer.agent_offset_data[agent] self._viewer.agent_offset_data[agent] = (current_data[0], current_data[1], agent.coordinates, current_data[3]) for item in self._viewer.item_offset_data: current_data = self._viewer.item_offset_data[item] self._viewer.item_offset_data[item] = (current_data[0], current_data[1], item.coordinates, current_data[3]) self._viewer.agent_update_flag = True self._viewer.item_update_flag = True def run(self, round_start_timestamp): """ main function for running the simulation with the visualization. At this time, its just error handling here.. the simulation and drawing stuff starts in the run_iteration method :param round_start_timestamp: timestamp of the start of the round. :return: """ # TODO: MARK - print stuff here to find exact seg fault location try: self._run_iteration(round_start_timestamp) except VisualizationError as ve: if ve.level == Level.INFO: show_msg(ve.msg, ve.level, self.get_main_window()) if ve.level == Level.CRITICAL: show_msg(ve.msg, ve.level, self.get_main_window()) exit(1) if ve.level == Level.WARNING: try: self._run_iteration(round_start_timestamp) show_msg(ve.msg, ve.level, self.get_main_window()) except VisualizationError as ve: show_msg(ve.msg, ve.level, self.get_main_window()) if ve.level != Level.INFO: exit(1) def _run_iteration(self, round_start_timestamp): """ Controls the "waiting time", so the rounds_per_second value is being kept at the specified value. :param round_start_timestamp: timestamp of the start of the round. :return: """ # update and draw/animate scene self._viewer.update_data() if self._animation: self.animate( round_start_timestamp, -1 if self._auto_animation else self._manual_animation_speed) else: self._viewer.glDraw() # waiting until simulation starts self._wait_while_not_running() # record round if self._recording: self.recorder.record_round() self._splitter.setWindowTitle("Simulator, recorded: %d rounds" % len(self.recorder.records)) # waiting until enough time passed to do the next simulation round. time_elapsed = time.perf_counter() - round_start_timestamp # sleeping time - max 1/120 s for a responsive GUI sleep_time = min(1.0 / 120, (1.0 / self._rounds_per_second) / 10.0) max_wait_time = 1 / self._rounds_per_second while time_elapsed < max_wait_time: time.sleep(sleep_time) # check if still running... if not wait (important for low rounds_per_second values) self._wait_while_not_running() time_elapsed = time.perf_counter() - round_start_timestamp def remove_agent(self, agent): """ removes an agent from the visualization. it wont be deleted immediately! not until the next round. if you want an immediate deletion of the agent, then call this function, then, update_data and after that glDraw of the OpenGLWidget. :param agent: the agent (not the id, the instance) to be deleted :return: """ self._viewer.agent_update_flag = True if agent in self._viewer.agent_offset_data: del self._viewer.agent_offset_data[agent] def agent_changed(self, agent): """ updates the offset, color and carry data of the agent in the visualization. it wont be an immediate update. it will update in the beginning of the next "run" call / after current round. :param agent: the agent that has changed (the instance) :return: """ self._viewer.agent_update_flag = True prev_pos = agent.coordinates if agent in self._viewer.agent_offset_data: prev_pos = self._viewer.agent_offset_data[agent][0] self._viewer.agent_offset_data[agent] = (agent.coordinates, agent.color, prev_pos, 1.0 if agent.is_carried() else 0.0) def remove_item(self, item): """ removes an item from the visualization. :param item: the item (not the id, the instance) to be deleted :return: """ self._viewer.item_update_flag = True if item in self._viewer.item_offset_data: del self._viewer.item_offset_data[item] def item_changed(self, item): """ updates the offset, color and carry data of the item in the visualization. :param item: the item ( not the id, the instance) to be deleted :return: """ self._viewer.item_update_flag = True prev_pos = item.coordinates if item in self._viewer.item_offset_data: prev_pos = self._viewer.item_offset_data[item][0] self._viewer.item_offset_data[item] = (item.coordinates, item.color, prev_pos, 1.0 if item.is_carried() else 0.0) def remove_location(self, location): """ removes a location from the visualization. :param location: the location (not the id, the instance) to be deleted :return: """ self._viewer.location_update_flag = True if location in self._viewer.location_offset_data: del self._viewer.location_offset_data[location] def location_changed(self, location): """ updates the offset and color data of the location in the visualization. :param location: the location ( not the id, the instance) to be deleted :return: """ self._viewer.location_update_flag = True self._viewer.location_offset_data[location] = (location.coordinates, location.color) def update_visualization_data(self): self._viewer.update_data() # setters and getters for various variables in the visualization def set_rounds_per_second(self, rounds_per_second): self._rounds_per_second = rounds_per_second def get_rounds_per_second(self): return self._rounds_per_second def reset_camera_position(self): self._camera.reset() self._viewer.update_scene() def set_field_of_view(self, fov: float): self._camera.set_fov(fov) self._viewer.update_programs_projection_matrix() self._viewer.update_cursor_data() self._viewer.glDraw() def get_field_of_view(self): return self._camera.get_fov() def set_drag_sensitivity(self, s: float): self._viewer.drag_sensitivity = s def get_drag_sensitivity(self): return self._viewer.drag_sensitivity def set_zoom_sensitivity(self, s: float): self._viewer.zoom_sensitivity = s def get_zoom_sensitivity(self): return self._viewer.zoom_sensitivity def set_rotation_sensitivity(self, s: float): self._viewer.rotation_sensitivity = s def get_rotation_sensitivity(self): return self._viewer.rotation_sensitivity def get_projection_type(self): return self._camera.get_projection_type() def set_projection_type(self, projection_type): self._camera.set_projection_type(projection_type) self._viewer.update_programs_projection_matrix() self._viewer.glDraw() def get_background_color(self): return self._viewer.background def set_background_color(self, color): self._viewer.set_background_color(color) def get_grid_line_color(self): return self._viewer.programs["grid"].get_line_color() def set_grid_line_color(self, color): self._viewer.programs["grid"].set_line_color(color) def get_grid_border_color(self): return self._viewer.programs["grid"].get_border_color() def set_grid_border_color(self, color): self._viewer.programs["grid"].set_border_color(color) def get_grid_line_width(self): return self._viewer.programs["grid"].width def set_grid_line_width(self, width): self._viewer.programs["grid"].set_width(width) self._viewer.glDraw() def get_grid_line_scaling(self): return self._viewer.programs["grid"].get_line_scaling() def set_grid_line_scaling(self, scaling): self._viewer.programs["grid"].set_line_scaling(scaling) self._viewer.glDraw() def get_grid_coordinates_color(self): return self._viewer.programs["grid"].get_model_color() def set_grid_coordinates_color(self, color): self._viewer.programs["grid"].set_model_color(color) self._viewer.glDraw() def get_grid_coordinates_scaling(self): return self._viewer.programs["grid"].get_model_scaling() def set_grid_coordinates_scaling(self, scaling): self._viewer.programs["grid"].set_model_scaling(scaling) self._viewer.glDraw() def get_render_distance(self): return self._camera.get_render_distance() def set_render_distance(self, render_distance): self._camera.set_render_distance(render_distance) self._viewer.update_programs_projection_matrix() self._viewer.glDraw() def get_show_lines(self): return self._viewer.programs["grid"].show_lines def set_show_lines(self, show_lines: bool): self._viewer.programs["grid"].show_lines = show_lines self._viewer.glDraw() def get_show_border(self): return self._viewer.programs["grid"].show_border def set_show_border(self, show_border: bool): self._viewer.programs["grid"].show_border = show_border self._viewer.glDraw() def get_show_coordinates(self): return self._viewer.programs["grid"].show_coordinates def set_show_coordinates(self, show_coordinates: bool): self._viewer.programs["grid"].show_coordinates = show_coordinates self._viewer.glDraw() def get_show_center(self): return self._viewer.show_center def set_show_center(self, show_center: bool): self._viewer.show_center = show_center self._viewer.glDraw() def get_show_focus(self): return self._viewer.show_focus def set_show_focus(self, show_focus: bool): self._viewer.show_focus = show_focus self._viewer.glDraw() def take_screenshot(self, quick): self._viewer.take_screenshot(quick) def recalculate_grid(self, size): self.grid_size = size self._viewer.programs["grid"].update_offsets( self._world.grid.get_box(size)) self._viewer.glDraw() def get_agent_scaling(self): return self._viewer.programs["agent"].get_model_scaling() def set_agent_scaling(self, scaling): self._viewer.programs["agent"].set_model_scaling(scaling) self._viewer.glDraw() def get_item_scaling(self): return self._viewer.programs["item"].get_model_scaling() def set_item_scaling(self, scaling): self._viewer.programs["item"].set_model_scaling(scaling) self._viewer.glDraw() def get_location_scaling(self): return self._viewer.programs["location"].get_model_scaling() def set_location_scaling(self, scaling): self._viewer.programs["location"].set_model_scaling(scaling) self._viewer.glDraw() def set_on_cursor_click_matter_type(self, matter_type): if matter_type == MatterType.ITEM or matter_type == MatterType.AGENT or matter_type == MatterType.LOCATION: self._viewer.cursor_type = matter_type self._viewer.update_cursor_data() def is_running(self): return self._running def get_added_matter_color(self): return self._viewer.added_matter_color def set_added_matter_color(self, color): self._viewer.added_matter_color = color def start_recording(self): self.recorder.record_round() self._splitter.setWindowTitle("Simulator, recorded: %d rounds" % len(self.recorder.records)) self._recording = True def get_viewer_res(self): return self._viewer.width(), self._viewer.height() def stop_recording(self): self._recording = False def export_recording(self): if len(self.recorder.records) == 0: show_msg("No rounds recorded. Nothing to export.", Level.INFO, self._splitter) return if self._running: self.start_stop() self._viewer.set_show_info_frame(False) self._viewer.set_enable_cursor(False) if "set_disable_sim" in dir(self._gui_module): self._gui_module.set_disable_sim(True) else: show_msg( "No 'set_disable_sim(disable_flag)' function in gui module found." "\nRunning simulation within recording mode may result in undefined behavior!", Level.WARNING, self._splitter) self.recorder.show(self.do_export) # loop while self.recorder.is_open(): self._process_events() # go back to the main window if "set_disable_sim" in dir(self._gui_module): self._gui_module.set_disable_sim(False) self._viewer.agent_update_flag = True self._viewer.item_update_flag = True self._viewer.location_update_flag = True self._viewer.update_data() self._viewer.set_show_info_frame(True) self._viewer.set_enable_cursor(True) def do_export(self, rps, width, height, codec, first_frame_idx, last_frame_idx, animation): if not os.path.exists("outputs/videos") or not os.path.isdir( "outputs/videos"): os.mkdir("outputs/videos") directory = "." if os.path.exists("outputs/videos") and os.path.isdir( "outputs/videos"): directory = "outputs/videos" path = TopQFileDialog(self._splitter).getSaveFileName( options=(TopQFileDialog.Options()), filter="*.mp4;;*.avi;;*.mkv", directory=directory) if path[0] == '': return if path[0].endswith("mp4") or path[0].endswith( ".avi") or path[0].endswith(".mkv"): fullpath = path[0] else: fullpath = path[0] + path[1].replace('*', '') if animation: animation_steps = int(30 / rps) if animation_steps < 1: animation_steps = 1 else: animation_steps = 1 writer = cv2.VideoWriter(fullpath, cv2.VideoWriter_fourcc(*codec), rps * animation_steps, (width, height)) self._viewer.setDisabled(True) # creating and opening loading window lw = LoadingWindow("", "Exporting Video...") lw.show() out_of = (last_frame_idx - first_frame_idx + 1) * animation_steps for i in range(first_frame_idx - 1, last_frame_idx): # render and write frame self._viewer.inject_record_data(self.recorder.records[i]) # animate for j in range(1, animation_steps + 1): # process events so the gui thread does respond to interactions.. self._process_events() # update loading windows text and progress bar processing = (i - first_frame_idx + 1) * animation_steps + j lw.set_message("Please wait!\nExporting frame %d/%d..." % (processing, out_of)) lw.set_progress(processing, out_of) self._viewer.set_animation_percentage(j / animation_steps) self._viewer.glDraw() img = self._viewer.get_frame_cv(width, height) writer.write(img) self._viewer.inject_record_data(self.recorder.records[last_frame_idx - 1]) writer.release() lw.close() self._viewer.setDisabled(False) self._viewer.resizeGL(self._viewer.width(), self._viewer.height()) self._viewer.update_scene() show_msg("Video exported successfully!", Level.INFO, self._splitter) def delete_recording(self): self.recorder = Recorder(self._world, self._viewer) self._splitter.setWindowTitle("Simulator") def set_antialiasing(self, value): if value <= 0: self._viewer.disable_aa() else: self._viewer.enable_aa(value) def take_vector_screenshot(self): if self._world.grid.__class__.__name__ == "TriangularGrid": if not os.path.exists("outputs/screenshots") or not os.path.isdir( "outputs/screenshots"): os.mkdir("outputs/screenshots") directory = "." if os.path.exists("outputs/screenshots") and os.path.isdir( "outputs/screenshots"): directory = "outputs/screenshots" path = TopQFileDialog(self._splitter).getSaveFileName( options=(TopQFileDialog.Options()), filter="*.svg", directory=directory) if path[0] == '': return if path[0].endswith(".svg"): create_svg(self._world, path[0]) else: create_svg(self._world, path[0] + ".svg") else: show_msg( "Not implemented yet.\nWorks only with Triangular Grid for now!\nSorry!", Level.WARNING, self._splitter) def set_animation(self, animation): if not animation: self._viewer.set_animation_percentage(1) self._animation = animation def get_animation(self): return self._animation def set_auto_animation(self, auto_animation): self._auto_animation = auto_animation def get_auto_animation(self): return self._auto_animation def set_manual_animation_speed(self, manual_animation_speed): self._manual_animation_speed = manual_animation_speed def get_manual_animation_speed(self): return self._manual_animation_speed def get_main_window(self): return self._splitter def set_reset_flag(self): self._reset_flag = True