Diff of /QuArray.py [000000] .. [c52ce0]

Switch to unified view

a b/QuArray.py
1
# DEPLOY MAC
2
# 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
3
# Windows
4
# pyinstaller -F --windowed -p C:/Users/callum/QuArray/scripts --add-data "C:/Users/callum/QuArray/scripts;scripts" --icon=scripts/icons/icon.ico QuArray.py
5
# Linux
6
# pyinstaller -F -p ~/Documents/QuArray/scripts/ --add-data "scripts:scripts" QuArray.py
7
8
import qdarkgraystyle
9
from scripts import DABanalysis, Overlay, Cut_Application_thread
10
import sys
11
import os
12
from PyQt5.QtCore import pyqtSlot, Qt, pyqtSignal
13
from PyQt5.QtWidgets import QFileDialog
14
from PyQt5 import QtWidgets, QtGui
15
from PyQt5.QtWidgets import QApplication, QMainWindow
16
from PyQt5.uic import loadUi
17
import qimage2ndarray
18
import random
19
import numpy as np
20
from skimage.color import rgb2hsv
21
from PIL import Image
22
23
24
if not hasattr(sys, "_MEIPASS"):
25
    sys._MEIPASS = '.'  # for running locally
26
27
class MyWindow(QMainWindow):
28
    def __init__(self):
29
        super(MyWindow, self).__init__()
30
        loadUi(sys._MEIPASS + os.sep + "scripts" + os.sep + "MainWindow_layout.ui", self)  # for deployment
31
        self.statusBar()
32
        self.setWindowTitle('QuArray')
33
        self.setWindowOpacity(0.96)
34
        self.ndpi_exporter.clicked.connect(lambda: self.ndpi_export())
35
        self.trainingbutton.clicked.connect(lambda: self.dabanalysis())
36
        self.testingbutton.clicked.connect(lambda: self.thresh_action())
37
        self.overbtn.clicked.connect(lambda: self.overlay())
38
        self.actionNDPI_SVS_Export.triggered.connect(lambda: self.ndpi_export())
39
        self.actionRun_Dab_Analysis.triggered.connect(lambda: self.dabanalysis())
40
        self.actionSet_Thresholds.triggered.connect(lambda: self.thresh_action())
41
        self.actionOverlay_Figure.triggered.connect(lambda: self.overlay())
42
        self.saveimages = False
43
        self.checkBox.stateChanged.connect(self.clickbox)
44
        scene = QtWidgets.QGraphicsScene()
45
        self.pixmap = QtWidgets.QGraphicsPixmapItem()
46
        scene.addItem(self.pixmap)
47
        self.graphicsView.setScene(scene)
48
        self.show()
49
50
    def ndpi_export(self):  # select file(s) button 2
51
        self.NDPI = Cut_Application_thread.MyWindow()
52
        self.NDPI.show()
53
54
    def dabanalysis(self):  # select file(s) button 1
55
        self.statusupdate("Select path containing PNG files")
56
        self.path = QFileDialog.getExistingDirectory(parent=self, caption='Open file',
57
                                                     directory="/Users/callum/Desktop")
58
        if self.path:
59
            self.analysis = DABanalysis.DabAnalysis(path=self.path, save=self.saveimages)
60
            self.analysis.maxcuts.connect(self.progress.setMaximum)
61
            self.analysis.countChanged.connect(self.onCountChanged)
62
            self.analysis.info.connect(self.statusupdate)
63
            self.analysis.figures.connect(self.showimage)
64
            self.analysis.activate.connect(self.activate_input)
65
            if hasattr(self, "newthreshold"):
66
                print("Using new DAB threshold - " + str(self.newthreshold))
67
                self.analysis.threshold = self.newthreshold / 100
68
                self.analysis.start()
69
            else:
70
                self.analysis.start()
71
72
    def activate_input(self, onoff):
73
        self.trainingbutton.setEnabled(onoff)
74
        self.testingbutton.setEnabled(onoff)
75
        self.overbtn.setEnabled(onoff)
76
        self.checkBox.setEnabled(onoff)
77
78
    def statusupdate(self, message):
79
        self.statusbar.showMessage(message)
80
81
    def clickbox(self, state):
82
        if state == Qt.Checked:
83
            self.saveimages = True
84
        else:
85
            self.saveimages = False
86
87
    @pyqtSlot(int)
88
    def onCountChanged(self, value):
89
        self.progress.setValue(value)
90
91
    def showimage(self):
92
        img = qimage2ndarray.array2qimage(self.analysis.current_image, normalize=True)
93
        img = QtGui.QPixmap(img)
94
        self.pixmap.setPixmap(img)
95
        self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
96
97
    def thresh_action(self):  # select file(s) button 2
98
        self.SW = ThresholdSelectorWindow()
99
        self.SW.show()
100
        self.SW.signal.connect(self.updatethresholds)
101
102
    def updatethresholds(self, value):
103
        self.newthreshold = value
104
        self.statusupdate("threshold value updated - " + str(value))
105
        self.testingbutton.setStyleSheet("background-color: rgb(0,90,0)")
106
107
    def overlay(self):
108
        self.statusupdate("Select path - This is slow so only a few files if large")
109
        path = QFileDialog.getExistingDirectory(parent=self, caption='Open file',
110
                                                directory="/Users/callum/Desktop")
111
        if path:
112
            self.overlayfig = Overlay.Overlay(path=path)
113
            self.overlayfig.maxcuts.connect(self.progress.setMaximum)
114
            self.overlayfig.countChanged.connect(self.onCountChanged)
115
            self.overlayfig.info.connect(self.statusupdate)
116
            self.overlayfig.figures.connect(self.showimageoverlay)
117
            if hasattr(self, "newthreshold"):
118
                print("Using new DAB threshold - " + str(self.newthreshold))
119
                self.overlayfig.threshold = self.newthreshold / 100
120
                self.overlayfig.start()
121
            else:
122
                self.overlayfig.start()
123
124
    def showimageoverlay(self):
125
        img = qimage2ndarray.array2qimage(self.overlayfig.current_image, normalize=True)
126
        img = QtGui.QPixmap(img)
127
        self.pixmap.setPixmap(img)
128
        self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
129
130
131
class ThresholdSelectorWindow(QtWidgets.QWidget):
132
    signal = pyqtSignal(int)
133
134
    def __init__(self):
135
        super(ThresholdSelectorWindow, self).__init__()
136
        loadUi(sys._MEIPASS + os.sep + "scripts" + os.sep + "Threshold_selector_Layout.ui", self)
137
        self.slider.setSingleStep(1)
138
        self.slider.valueChanged[int].connect(self.sliderchange)
139
        self.setWindowTitle('Set DAB Thresholds')
140
        self.setWindowOpacity(0.96)
141
        # scene 1
142
        scene = QtWidgets.QGraphicsScene()
143
        self.pixmap = QtWidgets.QGraphicsPixmapItem()
144
        scene.addItem(self.pixmap)
145
        self.graphicsView.setScene(scene)
146
        # scene 2
147
        scene_2 = QtWidgets.QGraphicsScene()
148
        self.pixmap_2 = QtWidgets.QGraphicsPixmapItem()
149
        scene_2.addItem(self.pixmap_2)
150
        self.graphicsView_2.setScene(scene_2)
151
        # scene 3 - graphicsView_zoom1
152
        scene_3 = QtWidgets.QGraphicsScene()
153
        self.pixmap_3 = QtWidgets.QGraphicsPixmapItem()
154
        scene_3.addItem(self.pixmap_3)
155
        self.graphicsView_zoom1.setScene(scene_3)
156
        # scene 4 - graphicsView_zoom2
157
        scene_4 = QtWidgets.QGraphicsScene()
158
        self.pixmap_4 = QtWidgets.QGraphicsPixmapItem()
159
        scene_4.addItem(self.pixmap_4)
160
        self.graphicsView_zoom2.setScene(scene_4)
161
        self.trainingbutton.clicked.connect(lambda: self.getpath())
162
        self.imagebutton.clicked.connect(lambda: self.sampleimage())
163
        self.togglebtn.clicked.connect(lambda: self.toggle())
164
        self.applybtn.clicked.connect(lambda: self.apply())
165
166
        if not hasattr(self, "path"):
167
            self.imagebutton.setEnabled(False)
168
            self.togglebtn.setEnabled(False)
169
            self.applybtn.setEnabled(False)
170
            self.slider.setVisible(False)
171
            self.slbl.setEnabled(False)
172
            self.dablbl.setEnabled(False)
173
            self.lbl2.setEnabled(False)
174
        self.show()
175
176
    def getpath(self):  # select file(s) button 1
177
        self.path = QFileDialog.getExistingDirectory(parent=self, caption='Open file path',
178
                                                     directory="/Users/callum/Desktop")
179
        if self.path:
180
            self.imagebutton.setEnabled(True)
181
            self.togglebtn.setEnabled(True)
182
            self.applybtn.setEnabled(True)
183
            self.slider.setVisible(True)
184
            self.slbl.setEnabled(True)
185
            self.dablbl.setEnabled(True)
186
            self.lbl2.setEnabled(True)
187
            self.lbl.setText(self.path)
188
            self.sampleimage()  # get a sample image
189
190
    def settext(self, text):
191
        self.lbl2.setText(text)
192
        self.lbl2.repaint()
193
        return
194
195
    @pyqtSlot()
196
    def sampleimage(self):
197
        if hasattr(self, "path"):
198
            files = [f for f in os.listdir(self.path) if f.endswith(".png") and not f.endswith("Overlay.png")]
199
            self.randpath = files[random.randint(0, len(files) - 1)]
200
            self.settext(self.randpath)
201
            self.img = np.array(Image.open(self.path + os.sep + self.randpath))[:, :, :3]
202
            center = int(self.img.shape[0] / 2)
203
            self.zoomimg = self.img[center - 256:center + 256, center - 256:center + 256, :]
204
            zoomimg = qimage2ndarray.array2qimage(self.zoomimg, normalize=True)
205
            zoomimg = QtGui.QPixmap(zoomimg)
206
            self.pixmap_4.setPixmap(zoomimg)
207
            self.graphicsView_zoom2.fitInView(self.graphicsView_zoom2.sceneRect(), Qt.KeepAspectRatio)
208
            self.pixmap_3.setPixmap(zoomimg)
209
            self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio)
210
            self.img = self.img[::4, ::4]  # todo scale this better
211
            showim = qimage2ndarray.array2qimage(self.img, normalize=True)
212
            showim = QtGui.QPixmap(showim)
213
            self.pixmap_2.setPixmap(showim)
214
            self.graphicsView_2.fitInView(self.graphicsView_2.sceneRect(), Qt.KeepAspectRatio)
215
            # prep for analysis
216
            img_hsv = rgb2hsv(self.img)
217
            self.img_hue = img_hsv[:, :, 0]
218
            self.image_sat = img_hsv[:, :, 1]
219
            img_hsv_zoom = rgb2hsv(self.zoomimg)
220
            self.img_hue_sml = img_hsv_zoom[:, :, 0]
221
            self.image_sat_sml = img_hsv_zoom[:, :, 1]
222
            self.slider.setMaximum((self.image_sat.max()) * 100)
223
            self.slider.setMinimum((self.image_sat.min()) * 100)
224
            self.togimg = qimage2ndarray.array2qimage(self.img, normalize=True)
225
            self.togimg = QtGui.QPixmap(self.togimg)
226
            self.pixmap.setPixmap(self.togimg)
227
            self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
228
            return
229
230
    def resizeEvent(self, event):
231
        self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
232
        self.graphicsView_2.fitInView(self.graphicsView_2.sceneRect(), Qt.KeepAspectRatio)
233
        self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio)
234
        self.graphicsView_zoom2.fitInView(self.graphicsView_zoom2.sceneRect(), Qt.KeepAspectRatio)
235
        self.show()
236
237
    def sliderchange(self, value):
238
        if hasattr(self, "img"):
239
            self.slbl.setText(str(value))
240
            #  todo sort the poor code here
241
            # large image
242
            hue = np.logical_and(self.img_hue > 0.02, self.img_hue < 0.10)  # BROWN PIXELS BETWEEN 0.02 and 0.10
243
            img = np.logical_and(hue, self.image_sat > value / 100)
244
            img = qimage2ndarray.array2qimage(img, normalize=True)
245
            img = QtGui.QPixmap(img)
246
            self.pixmap.setPixmap(img)
247
            self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
248
            # small image
249
            hue = np.logical_and(self.img_hue_sml > 0.02, self.img_hue_sml < 0.10)  # BROWN PIXELS BETWEEN 0.02 and 0.10
250
            img = np.logical_and(hue, self.image_sat_sml > value / 100)
251
            img = qimage2ndarray.array2qimage(img, normalize=True)
252
            img = QtGui.QPixmap(img)
253
            self.pixmap_3.setPixmap(img)
254
            self.graphicsView_zoom1.fitInView(self.graphicsView_zoom1.sceneRect(), Qt.KeepAspectRatio)
255
256
    def toggle(self):
257
        if hasattr(self, "togimg"):
258
            self.pixmap.setPixmap(self.togimg)
259
            self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
260
261
    pyqtSlot()
262
263
    def apply(self):
264
        self.signal.emit(int(self.slider.value()))
265
266
267
if __name__ == '__main__':
268
    app = QApplication(sys.argv)
269
    window = MyWindow()
270
271
    # style sheet - https://github.com/mstuttgart/qdarkgraystyle
272
    app.setStyleSheet(qdarkgraystyle.load_stylesheet())
273
274
    sys.exit(app.exec_())