--- a +++ b/QuArray.py @@ -0,0 +1,274 @@ +# DEPLOY MAC +# sudo pyinstaller -F --windowed -p /Users/callum/callum/QuArray/scripts --add-data "/Users/callum/callum/QuArray/scripts:scripts" --add-binary "/Users/callum/Library/Application Support/pyinstaller/bincache00_py37_64bit/libopenslide.0.dylib:." --icon=scripts/icons/icon.icns QuArray.py +# Windows +# pyinstaller -F --windowed -p C:/Users/callum/QuArray/scripts --add-data "C:/Users/callum/QuArray/scripts;scripts" --icon=scripts/icons/icon.ico QuArray.py +# Linux +# pyinstaller -F -p ~/Documents/QuArray/scripts/ --add-data "scripts:scripts" QuArray.py + +import qdarkgraystyle +from scripts import DABanalysis, Overlay, Cut_Application_thread +import sys +import os +from PyQt5.QtCore import pyqtSlot, Qt, pyqtSignal +from PyQt5.QtWidgets import QFileDialog +from PyQt5 import QtWidgets, QtGui +from PyQt5.QtWidgets import QApplication, QMainWindow +from PyQt5.uic import loadUi +import qimage2ndarray +import random +import numpy as np +from skimage.color import rgb2hsv +from PIL import Image + + +if not hasattr(sys, "_MEIPASS"): + sys._MEIPASS = '.' # for running locally + +class MyWindow(QMainWindow): + def __init__(self): + super(MyWindow, self).__init__() + loadUi(sys._MEIPASS + os.sep + "scripts" + os.sep + "MainWindow_layout.ui", self) # for deployment + self.statusBar() + self.setWindowTitle('QuArray') + self.setWindowOpacity(0.96) + self.ndpi_exporter.clicked.connect(lambda: self.ndpi_export()) + self.trainingbutton.clicked.connect(lambda: self.dabanalysis()) + self.testingbutton.clicked.connect(lambda: self.thresh_action()) + self.overbtn.clicked.connect(lambda: self.overlay()) + self.actionNDPI_SVS_Export.triggered.connect(lambda: self.ndpi_export()) + self.actionRun_Dab_Analysis.triggered.connect(lambda: self.dabanalysis()) + self.actionSet_Thresholds.triggered.connect(lambda: self.thresh_action()) + self.actionOverlay_Figure.triggered.connect(lambda: self.overlay()) + self.saveimages = False + self.checkBox.stateChanged.connect(self.clickbox) + scene = QtWidgets.QGraphicsScene() + self.pixmap = QtWidgets.QGraphicsPixmapItem() + scene.addItem(self.pixmap) + self.graphicsView.setScene(scene) + self.show() + + def ndpi_export(self): # select file(s) button 2 + self.NDPI = Cut_Application_thread.MyWindow() + self.NDPI.show() + + def dabanalysis(self): # select file(s) button 1 + self.statusupdate("Select path containing PNG files") + self.path = QFileDialog.getExistingDirectory(parent=self, caption='Open file', + directory="/Users/callum/Desktop") + if self.path: + self.analysis = DABanalysis.DabAnalysis(path=self.path, save=self.saveimages) + self.analysis.maxcuts.connect(self.progress.setMaximum) + self.analysis.countChanged.connect(self.onCountChanged) + self.analysis.info.connect(self.statusupdate) + self.analysis.figures.connect(self.showimage) + self.analysis.activate.connect(self.activate_input) + if hasattr(self, "newthreshold"): + print("Using new DAB threshold - " + str(self.newthreshold)) + self.analysis.threshold = self.newthreshold / 100 + self.analysis.start() + else: + self.analysis.start() + + def activate_input(self, onoff): + self.trainingbutton.setEnabled(onoff) + self.testingbutton.setEnabled(onoff) + self.overbtn.setEnabled(onoff) + self.checkBox.setEnabled(onoff) + + def statusupdate(self, message): + self.statusbar.showMessage(message) + + def clickbox(self, state): + if state == Qt.Checked: + self.saveimages = True + else: + self.saveimages = False + + @pyqtSlot(int) + def onCountChanged(self, value): + self.progress.setValue(value) + + def showimage(self): + img = qimage2ndarray.array2qimage(self.analysis.current_image, normalize=True) + img = QtGui.QPixmap(img) + self.pixmap.setPixmap(img) + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + + def thresh_action(self): # select file(s) button 2 + self.SW = ThresholdSelectorWindow() + self.SW.show() + self.SW.signal.connect(self.updatethresholds) + + def updatethresholds(self, value): + self.newthreshold = value + self.statusupdate("threshold value updated - " + str(value)) + self.testingbutton.setStyleSheet("background-color: rgb(0,90,0)") + + def overlay(self): + self.statusupdate("Select path - This is slow so only a few files if large") + path = QFileDialog.getExistingDirectory(parent=self, caption='Open file', + directory="/Users/callum/Desktop") + if path: + self.overlayfig = Overlay.Overlay(path=path) + self.overlayfig.maxcuts.connect(self.progress.setMaximum) + self.overlayfig.countChanged.connect(self.onCountChanged) + self.overlayfig.info.connect(self.statusupdate) + self.overlayfig.figures.connect(self.showimageoverlay) + if hasattr(self, "newthreshold"): + print("Using new DAB threshold - " + str(self.newthreshold)) + self.overlayfig.threshold = self.newthreshold / 100 + self.overlayfig.start() + else: + self.overlayfig.start() + + def showimageoverlay(self): + img = qimage2ndarray.array2qimage(self.overlayfig.current_image, normalize=True) + img = QtGui.QPixmap(img) + self.pixmap.setPixmap(img) + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + + +class ThresholdSelectorWindow(QtWidgets.QWidget): + signal = pyqtSignal(int) + + def __init__(self): + super(ThresholdSelectorWindow, self).__init__() + loadUi(sys._MEIPASS + os.sep + "scripts" + os.sep + "Threshold_selector_Layout.ui", self) + self.slider.setSingleStep(1) + self.slider.valueChanged[int].connect(self.sliderchange) + self.setWindowTitle('Set DAB Thresholds') + self.setWindowOpacity(0.96) + # scene 1 + scene = QtWidgets.QGraphicsScene() + self.pixmap = QtWidgets.QGraphicsPixmapItem() + scene.addItem(self.pixmap) + self.graphicsView.setScene(scene) + # scene 2 + scene_2 = QtWidgets.QGraphicsScene() + self.pixmap_2 = QtWidgets.QGraphicsPixmapItem() + scene_2.addItem(self.pixmap_2) + self.graphicsView_2.setScene(scene_2) + # scene 3 - graphicsView_zoom1 + scene_3 = QtWidgets.QGraphicsScene() + self.pixmap_3 = QtWidgets.QGraphicsPixmapItem() + scene_3.addItem(self.pixmap_3) + self.graphicsView_zoom1.setScene(scene_3) + # scene 4 - graphicsView_zoom2 + scene_4 = QtWidgets.QGraphicsScene() + self.pixmap_4 = QtWidgets.QGraphicsPixmapItem() + scene_4.addItem(self.pixmap_4) + self.graphicsView_zoom2.setScene(scene_4) + self.trainingbutton.clicked.connect(lambda: self.getpath()) + self.imagebutton.clicked.connect(lambda: self.sampleimage()) + self.togglebtn.clicked.connect(lambda: self.toggle()) + self.applybtn.clicked.connect(lambda: self.apply()) + + if not hasattr(self, "path"): + self.imagebutton.setEnabled(False) + self.togglebtn.setEnabled(False) + self.applybtn.setEnabled(False) + self.slider.setVisible(False) + self.slbl.setEnabled(False) + self.dablbl.setEnabled(False) + self.lbl2.setEnabled(False) + self.show() + + def getpath(self): # select file(s) button 1 + self.path = QFileDialog.getExistingDirectory(parent=self, caption='Open file path', + directory="/Users/callum/Desktop") + if self.path: + self.imagebutton.setEnabled(True) + self.togglebtn.setEnabled(True) + self.applybtn.setEnabled(True) + self.slider.setVisible(True) + self.slbl.setEnabled(True) + self.dablbl.setEnabled(True) + self.lbl2.setEnabled(True) + self.lbl.setText(self.path) + self.sampleimage() # get a sample image + + def settext(self, text): + self.lbl2.setText(text) + self.lbl2.repaint() + return + + @pyqtSlot() + def sampleimage(self): + if hasattr(self, "path"): + files = [f for f in os.listdir(self.path) if f.endswith(".png") and not f.endswith("Overlay.png")] + self.randpath = files[random.randint(0, len(files) - 1)] + self.settext(self.randpath) + self.img = np.array(Image.open(self.path + os.sep + self.randpath))[:, :, :3] + center = int(self.img.shape[0] / 2) + self.zoomimg = self.img[center - 256:center + 256, center - 256:center + 256, :] + zoomimg = qimage2ndarray.array2qimage(self.zoomimg, normalize=True) + zoomimg = QtGui.QPixmap(zoomimg) + self.pixmap_4.setPixmap(zoomimg) + self.graphicsView_zoom2.fitInView(self.graphicsView_zoom2.sceneRect(), Qt.KeepAspectRatio) + self.pixmap_3.setPixmap(zoomimg) + self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio) + self.img = self.img[::4, ::4] # todo scale this better + showim = qimage2ndarray.array2qimage(self.img, normalize=True) + showim = QtGui.QPixmap(showim) + self.pixmap_2.setPixmap(showim) + self.graphicsView_2.fitInView(self.graphicsView_2.sceneRect(), Qt.KeepAspectRatio) + # prep for analysis + img_hsv = rgb2hsv(self.img) + self.img_hue = img_hsv[:, :, 0] + self.image_sat = img_hsv[:, :, 1] + img_hsv_zoom = rgb2hsv(self.zoomimg) + self.img_hue_sml = img_hsv_zoom[:, :, 0] + self.image_sat_sml = img_hsv_zoom[:, :, 1] + self.slider.setMaximum((self.image_sat.max()) * 100) + self.slider.setMinimum((self.image_sat.min()) * 100) + self.togimg = qimage2ndarray.array2qimage(self.img, normalize=True) + self.togimg = QtGui.QPixmap(self.togimg) + self.pixmap.setPixmap(self.togimg) + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + return + + def resizeEvent(self, event): + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + self.graphicsView_2.fitInView(self.graphicsView_2.sceneRect(), Qt.KeepAspectRatio) + self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio) + self.graphicsView_zoom2.fitInView(self.graphicsView_zoom2.sceneRect(), Qt.KeepAspectRatio) + self.show() + + def sliderchange(self, value): + if hasattr(self, "img"): + self.slbl.setText(str(value)) + # todo sort the poor code here + # large image + hue = np.logical_and(self.img_hue > 0.02, self.img_hue < 0.10) # BROWN PIXELS BETWEEN 0.02 and 0.10 + img = np.logical_and(hue, self.image_sat > value / 100) + img = qimage2ndarray.array2qimage(img, normalize=True) + img = QtGui.QPixmap(img) + self.pixmap.setPixmap(img) + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + # small image + hue = np.logical_and(self.img_hue_sml > 0.02, self.img_hue_sml < 0.10) # BROWN PIXELS BETWEEN 0.02 and 0.10 + img = np.logical_and(hue, self.image_sat_sml > value / 100) + img = qimage2ndarray.array2qimage(img, normalize=True) + img = QtGui.QPixmap(img) + self.pixmap_3.setPixmap(img) + self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio) + + def toggle(self): + if hasattr(self, "togimg"): + self.pixmap.setPixmap(self.togimg) + self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio) + + pyqtSlot() + + def apply(self): + self.signal.emit(int(self.slider.value())) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = MyWindow() + + # style sheet - https://github.com/mstuttgart/qdarkgraystyle + app.setStyleSheet(qdarkgraystyle.load_stylesheet()) + + sys.exit(app.exec_())