import os import pickle as pk import sys import time import pyqtgraph as pg from PyQt5 import QtCore, QtWidgets, uic from PyQt5.QtCore import QThread, QThreadPool, QTimer from sklearn.decomposition import IncrementalPCA from src.KNN.faissKNN import faissKNN from src.Projections.Python.PCA.PCAProjector import PCAProjector from src.Projections.Python.ProjectionTransformer import ProjectionTransformer from src.UI.Renderer2D import Renderer2D from src.UI.Renderer3D import Renderer3D from src.UI.SideGrip import SideGrip baseUIClass, baseUIWidget = uic.loadUiType("GUI_BASE.ui") # use loaded ui file in the logic class class App(baseUIWidget, baseUIClass): _gripSize = 8 def __init__(self, parent=None): super(App, self).__init__(parent) #UI related self.setupUi(self) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) #TODO, change these to lists, to allow multiple projectors, transformers self.PCAProjector = None self.transformer = None self.knn = None #All the models created / loaded in self.models = [] self.selectModel.addItem("",0) #For updating the percentages in the GUI #self.percentageTimer = QTimer() #self.percentageTimer.start(100) #Resizing related self.oldPos = QtCore.QPoint(0,0) self.sideGrips = [ SideGrip(self, QtCore.Qt.LeftEdge), SideGrip(self, QtCore.Qt.TopEdge), SideGrip(self, QtCore.Qt.RightEdge), SideGrip(self, QtCore.Qt.BottomEdge), ] # corner grips should be "on top" of everything, otherwise the side grips # will take precedence on mouse events, so we are adding them *after*; # alternatively, widget.raise_() can be used self.cornerGrips = [QtWidgets.QSizeGrip(self) for i in range(4)] self.startVisualisation() self.handleConnections() self.setupPercentageLoaders() def setupPercentageLoaders(self): self.setValue(self.labelPercentageFitting,self.circularProgressFitting," rgb(115, 185, 255)",0) self.setValue(self.labelPercentageTransforming,self.circularProgressTransforming," rgba(255, 0, 127, 255)",0) self.setValue(self.labelPercentageKNN,self.circularProgressKNN,"rgb(0, 159, 0)",0) #Resizing as shown in https://stackoverflow.com/questions/62807295/how-to-resize-a-window-from-the-edges-after-adding-the-property-qtcore-qt-framel #IGNORE THIS @property def gripSize(self): return self._gripSize def setGripSize(self, size): if size == self._gripSize: return self._gripSize = max(2, size) self.updateGrips() def updateGrips(self): self.setContentsMargins(*[self.gripSize] * 4) outRect = self.rect() # an "inner" rect used for reference to set the geometries of size grips inRect = outRect.adjusted(self.gripSize, self.gripSize, -self.gripSize, -self.gripSize) # top left self.cornerGrips[0].setGeometry( QtCore.QRect(outRect.topLeft(), inRect.topLeft())) # top right self.cornerGrips[1].setGeometry( QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized()) # bottom right self.cornerGrips[2].setGeometry( QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())) # bottom left self.cornerGrips[3].setGeometry( QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()) # left edge self.sideGrips[0].setGeometry( 0, inRect.top(), self.gripSize, inRect.height()) # top edge self.sideGrips[1].setGeometry( inRect.left(), 0, inRect.width(), self.gripSize) # right edge self.sideGrips[2].setGeometry( inRect.left() + inRect.width(), inRect.top(), self.gripSize, inRect.height()) # bottom edge self.sideGrips[3].setGeometry( self.gripSize, inRect.top() + inRect.height(), inRect.width(), self.gripSize) def resizeEvent(self, event): QtWidgets.QMainWindow.resizeEvent(self, event) self.updateGrips() #Connecting signals to functions, for example the buttons def handleConnections(self): self.uploadFileBTN.clicked.connect(self.uploadFile) self.getPrecisionPCA.clicked.connect(self.getPrecision) self.getProjectionBTN.clicked.connect(self.getProjection) self.stopProjectionBTN.clicked.connect(self.stopProjection) self.btn_close.clicked.connect(self.closeWindow) self.btn_minimize.clicked.connect(self.minimizeWindow) self.btn_maximize_restore.clicked.connect(self.maximizeWindow) self.removePointsBTN.clicked.connect(self.visualiser.removeAllPoints) self.btn_toggle_menu.clicked.connect(self.clickedMainButton) self.selectModel.currentIndexChanged.connect(self.modelChanged) self.loadModelBTN.clicked.connect(self.loadModel) self.saveModelBTN.clicked.connect(self.saveModel) self.knnRequestBTN.clicked.connect(self.getKNNRequest) def getKNNRequest(self): knnRequest = self.knnRequest.text() if not self.knn: return self.knn.getKNNSemantic(knnRequest) def saveModel(self): pk.dump(self.PCAProjector.getModel(), open("SavedModels//SavedModel.pkl","wb")) def loadModel(self): filePath = QtWidgets.QFileDialog.getOpenFileName(self, 'Select File',".//SavedModels")[0] with open(filePath,"rb") as f: model = pk.load(f) self.models.append(model) fileName = filePath.split('/')[-1].split('.')[0] self.selectModel.addItem(fileName) def addToVisualiser(self, results): self.visualiser.addPoints(results) def setupTransformer(self,model, path): if(not self.addProjection.isChecked()): self.visualiser.removeAllPoints() if self.transformer and not self.addProjection.isChecked(): self.transformer.stop() #Create new transformer self.transformerThread = QThread() self.transformer = ProjectionTransformer(model,path) self.transformer.newPointsSignal.connect(self.addToVisualiser) self.transformer.progressSignal.connect(self.updateTransformPercentage) self.transformer.moveToThread(self.transformerThread) self.transformerThread.started.connect(self.transformer.run) self.transformerThread.start() def modelChanged(self,value): if(value == 0): return selectedModel = self.models[value-1] #TODO own file for transformer self.setupTransformer(selectedModel,self.PCAProjector.filePath) def updateFittingPercentage(self,percentage): if self.PCAProjector: self.setValue(self.labelPercentageFitting,self.circularProgressFitting," rgba(115, 185, 255,255)",percentage) def updateTransformPercentage(self,percentage): if self.transformer: self.setValue(self.labelPercentageTransforming,self.circularProgressTransforming," rgb(161, 0, 241)",percentage) def updateKNNPercentage(self,percentage): if self.knn: self.setValue(self.labelPercentageKNN,self.circularProgressKNN,"rgb(0, 159, 0)",percentage) #For the custom top-row buttons def minimizeWindow(self): self.showMinimized() self.updateGrips() def clickedMainButton(self): print("clicked main") self.frame_left_menu.setVisible(not self.frame_left_menu.isVisible()) def maximizeWindow(self): self.showMaximized() self.updateGrips() def closeWindow(self): self.close() #Setting the percentage values def setValue(self, labelPercentage, progressBarName, color, value): # CONVERT VALUE TO INT value = round(value) htmlText = """<p align="center"><span style=" font-size:20pt;">{VALUE}</span><span style=" font-size:15pt; vertical-align:super;">%</span></p>""" # HTML TEXT PERCENTAGE labelPercentage.setText(htmlText.replace("{VALUE}", str(value))) # CALL DEF progressBarValue self.progressBarValue(value, progressBarName, color) def progressBarValue(self, value, widget, color): # PROGRESSBAR STYLESHEET BASE styleSheet = """ QFrame{ border-radius: 50px; background-color: qconicalgradient(cx:0.5, cy:0.5, angle:90, stop:{STOP_1} rgba(85, 85, 127, 100), stop:{STOP_2} {COLOR}); } """ # GET PROGRESS BAR VALUE, CONVERT TO FLOAT AND INVERT VALUES # stop works of 1.000 to 0.000 progress = (100 - value) / 100.0 # GET NEW VALUES stop_1 = str(progress - 0.001) stop_2 = str(progress) # FIX MAX VALUE if value == 100: stop_1 = "1.000" stop_2 = "1.000" # SET VALUES TO NEW STYLESHEET newStylesheet = styleSheet.replace("{STOP_1}", stop_1).replace("{STOP_2}", stop_2).replace("{COLOR}", color) # APPLY STYLESHEET WITH NEW VALUES widget.setStyleSheet(newStylesheet) #Default visualisation def startVisualisation(self): self.visualiser = Renderer2D(1) self.stackedWidget.insertWidget(0,self.visualiser.getWidget()) #Custom dragging window def mousePressEvent(self, event): print("test") self.oldPos = event.globalPos() print("Mousepress") def mouseMoveEvent(self, event): if self.frame_label_top_btns.underMouse(): delta = QtCore.QPoint (event.globalPos() - self.oldPos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos() #When stop Projection button clicked def stopProjection(self): if not self.transformer: print("No transformer yet") return self.transformer.stop() #Because they are running on separate threads, they can keep running even though main GUI is closed. #Overwrite closeEvent to stop them def closeEvent(self,event): print("Close") if self.transformer: self.transformer.stop() if self.PCAProjector: self.PCAProjector.stop() def getProjection(self): #Get current model from projector model = self.PCAProjector.getModel() path = self.PCAProjector.filePath self.models.append(model) modelName = "PCAModel_v" + str(len(self.models)) self.selectModel.addItem(modelName) #TODO own file for transformer self.setupTransformer(model,path) #TODO Unknown what to do with this, dont know if other projectors will suport this. Leave it for now since only using PCA def getPrecision(self): if not self.PCAProjector: print("No projector yet") return print(self.PCAProjector.getPrecision()) def setupKNN(self,filePath): self.knnThread = QThread() self.knn = faissKNN(filePath[0]) self.knn.moveToThread(self.knnThread) self.knnThread.started.connect(self.knn.run) self.knn.progressSignal.connect(self.updateKNNPercentage) self.knnThread.start() #Start the projection (PCA is only one for now) def uploadFile(self): filePath = QtWidgets.QFileDialog.getOpenFileName(self, 'Select File', os.path.expanduser('~\\Documents\\')) self.setupKNN(filePath) self.PCAProjectorThread = QThread() self.PCAProjector = PCAProjector(filePath[0]) self.PCAProjector.progressSignal.connect(self.updateFittingPercentage) self.PCAProjector.moveToThread(self.PCAProjectorThread) self.PCAProjectorThread.started.connect(self.PCAProjector.run) self.PCAProjectorThread.start() #QT related def main(): app = QtWidgets.QApplication(sys.argv) ui = App(None) ui.show() sys.exit(app.exec_()) main()