a b/scripts/Cut_Application_thread.py
1
from PyQt5.uic import loadUi
2
from PyQt5.QtCore import Qt, QRectF, QThread, QObject, pyqtSignal, pyqtSlot
3
from PyQt5.QtGui import QColor, QFont, QImage, QPainter, QPixmap, QPen, QBrush
4
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QFileDialog, QWidget, QGraphicsPixmapItem
5
from openslide import OpenSlide
6
import qimage2ndarray
7
import numpy as np
8
import os
9
from openpyxl import load_workbook
10
from openpyxl.utils.cell import coordinate_from_string, column_index_from_string
11
import json
12
import sys
13
from skimage.filters import threshold_otsu, threshold_li, threshold_mean, threshold_triangle, gaussian
14
from skimage.color import rgb2gray
15
from skimage.morphology import closing, square, remove_small_objects, label
16
from skimage.measure import regionprops
17
from skimage.transform import resize
18
from PIL import Image
19
20
if not hasattr(sys, "_MEIPASS"):
21
    sys._MEIPASS = '.'  # for running locally
22
23
# setup the Graphics scene to detect clicks
24
class GraphicsScene(QGraphicsScene):
25
    def __init__(self, parent=None):
26
        QGraphicsScene.__init__(self, parent)
27
        self.coords = []
28
        self.circles = []
29
        self.rectsandtext = []
30
        self.parent = MyWindow
31
32
    def reset(self):
33
        [self.circles[i].setVisible(False) for i in range(len(self.circles))]
34
        self.coords = []
35
        self.circles = []
36
        self.rectsandtext = []
37
38
    def elipse_adder(self, x, y):
39
        pen = QPen(QColor(69, 130, 201, 200))  # QColor(128, 128, 255, 128)
40
        pen.setWidthF(10)  # border width
41
        brush = QBrush(Qt.transparent)
42
        elipse = self.addEllipse(x - 25, y - 25, 50, 50, pen, brush)
43
        elipse.setAcceptDrops(True)
44
        elipse.setCursor(Qt.OpenHandCursor)
45
        elipse.setFlag(QGraphicsItem.ItemIsSelectable, True)
46
        elipse.setFlag(QGraphicsItem.ItemIsMovable, True)
47
        elipse.setFlag(QGraphicsItem.ItemIsFocusable, True)
48
        elipse.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
49
        elipse.setAcceptHoverEvents(True)
50
        self.circles.append(elipse)
51
52
    def mouseDoubleClickEvent(self, event):
53
        x = event.scenePos().x()
54
        y = event.scenePos().y()
55
        btn = event.button()
56
        if btn == 1:  # left click
57
            self.coords.append((x, y))
58
            self.elipse_adder(x, y)
59
60
    def keyPressEvent(self, e): # hit space to remove point
61
        if e.key() == Qt.Key_Space:
62
            if len(self.circles) >= 1:
63
                self.circles[-1].setVisible(False)
64
                self.circles = self.circles[:-1]
65
                self.coords = self.coords[:-1]
66
67
    def sortCentroid(self, centroid):
68
        scent = sorted(centroid, key=lambda x: x[0])
69
        sortList = []
70
        a = 0
71
        comLength = 0
72
        for length in self.rowcount:
73
            comLength = comLength + length
74
            sortList.extend((sorted(scent[a:comLength], key=lambda k: [k[1]])))
75
            a = a + length
76
        return sortList
77
78
    def overlay_cores(self, core_diameter, scale_index, cores, autopilot=False):  # removed - centroid, image, cores
79
        if len(self.rectsandtext) >= 1:
80
            [i.setVisible(False) for i in self.rectsandtext]
81
            self.rectsandtext = []
82
        pen = QPen(QColor(69, 130, 201, 240))
83
        pen.setWidthF(6)  # border width
84
        brush = QBrush(QColor(215, 230, 248, 160))  # square fill
85
        if autopilot:
86
            self.centroid = self.coords
87
            self.centroid = self.sortCentroid(self.centroid)
88
            [self.elipse_adder(y, x) for (x, y) in self.centroid]
89
            self.coords = [(y, x) for (x, y) in self.centroid]
90
        else:
91
            self.centroid = [(y, x) for (x, y) in self.coords]
92
            self.centroid = [(self.centroid[i][0]+self.circles[i].scenePos().y(), self.centroid[i][1]+self.circles[i].scenePos().x()) for i in range(len(self.circles))]
93
        diameter = core_diameter / scale_index
94
        a = 0
95
        for y, x in self.centroid:
96
            try:
97
                rect = self.addRect((x - (diameter / 2)), (y - (diameter / 2)), diameter, diameter, pen, brush)
98
                text = self.addText(cores[a])  # label
99
                self.rectsandtext.append(rect)
100
                self.rectsandtext.append(text)
101
                text.setPos(x, y)
102
                text.setDefaultTextColor(QColor(35, 57, 82, 200))
103
                font = QFont()
104
                font.setPointSize(80)
105
                text.setFont(font)
106
                a = a + 1
107
            except IndexError as e:
108
                self.centroid.pop(a)
109
                print("index error", self.centroid[a])
110
                continue
111
112
    def save(self, output, name):
113
        # Get region of scene to capture from somewhere.
114
        area = self.sceneRect().size().toSize()
115
        image = QImage(area, QImage.Format_RGB888)
116
        painter = QPainter(image)
117
        self.render(painter, target=QRectF(image.rect()), source=self.sceneRect())
118
        painter.end()
119
        image.save(output + os.sep + name + "_overlay" + ".tiff")
120
121
122
class MyWindow(QWidget):
123
    def __init__(self):
124
        super(MyWindow, self).__init__()
125
        loadUi(sys._MEIPASS + os.sep + "scripts" + os.sep + "Cut_Application_thread_layout.ui", self)  # deployment
126
        self.setWindowTitle('QuArray')
127
        self.tabWidget.setStyleSheet("QTabWidget::pane {margin: 0px,0px,0px,0px; padding: 0px}")
128
        self.excel_layout = self.excel_btn.isChecked()
129
        self.excel_btn.toggled.connect(self.excel)
130
        self.load_ndpi.clicked.connect(lambda: self.loadndpi())
131
        self.load_excel.clicked.connect(lambda: self.read_excel())
132
        self.overlay.clicked.connect(lambda: self.overlaystart())
133
        self.export_2.clicked.connect(lambda: self.export_images())
134
        # self.export_again.clicked.connect(lambda: self.export_images(meta_only=True))
135
        self.current_image = None
136
        # threshold buttons
137
        self.gausianval = 0
138
        self.thresholdval = None
139
        self.otsu.clicked.connect(lambda: [self.threshold("otsu"), self.reset_sliders()])
140
        self.threshmean.clicked.connect(lambda: [self.threshold("mean"), self.reset_sliders()])
141
        self.threshtriangle.clicked.connect(lambda: [self.threshold("triangle"), self.reset_sliders()])
142
        self.threshli.clicked.connect(lambda: [self.threshold("li"), self.reset_sliders()])
143
        self.toggleorigional.clicked.connect(lambda: [self.threshold("origional"), self.reset_sliders()])
144
        self.gausslider.setMaximum(5)
145
        self.gausslider.setValue(0)
146
        self.gausslider.valueChanged.connect(lambda: self.gauslineEdit.setText(str(self.gausslider.value())))  # change
147
        self.gausslider.sliderReleased.connect(self.gaus)
148
        self.closingslider.setMaximum(50)
149
        self.closingslider.setValue(0)
150
        self.closingslider.valueChanged.connect(lambda: self.closelineEdit.setText(str(self.closingslider.value())))
151
        self.closingslider.sliderReleased.connect(self.closing)
152
        self.removesmallobjects.clicked.connect(self.removesmall)
153
        self.current_augments = {"threshold": False, "gausian": False, "closing": False, "overlay_applied": False,
154
                                 "manual_overlay": False}
155
        self.pathology = None
156
        self.excelpath = False
157
158
        self.init_scene()
159
        self.show()
160
161
    def overlaystart(self, autopilot=False, coords=None):
162
        """
163
        starts overlay
164
        :param autopilot: if the coords need to be asigned outside of the click function
165
        :param coords: the external coords to be applied in autopilot
166
        """
167
        try:
168
            self.core_diameter = int(self.diamiterLineEdit.text().strip())
169
        except:
170
            self.core_diameter = 6000
171
            self.diamiterLineEdit.setText('6000')
172
            self.info("core diamiter must be an integer - reset to 6000")
173
            pass
174
        if autopilot:
175
            self.scene.coords = coords
176
            self.scene.overlay_cores(self.core_diameter, self.scale_index, self.cores, autopilot=True)
177
        else:
178
            self.scene.overlay_cores(self.core_diameter, self.scale_index, self.cores)
179
        if self.overlaySave.isChecked():
180
            self.info(f"Overlay saved to - {self.output}")
181
            self.scene.save(self.output, self.name)
182
        self.current_augments["overlay_applied"] = True
183
        self.activate([self.export_2], action=True)
184
185
    def excel(self, x):
186
        self.excel_layout = x
187
        if self.excelpath:
188
            self.read_excel()
189
190
    def init_scene(self):
191
        self.scene = GraphicsScene(self)
192
        self.graphicsView.setScene(self.scene)
193
        self.pixmap = QGraphicsPixmapItem()
194
        self.scene.addItem(self.pixmap)
195
196
    def show_info(self, text):
197
        self.metadata.setText(text)
198
199
    def activate(self, names, action=True):
200
        for i in names:
201
            i.setEnabled(action)
202
203
    def loadndpi(self):
204
        self.init_scene()
205
        formats = '*.ndpi*;;*.svs*;;*.tif*;;*.scn*;;*.mrxs*;;*.tiff*;;*.svslide*;;*.bif*'
206
        self.path, _ = QFileDialog.getOpenFileName(parent=self, caption='Open file',
207
                                                   directory="/Users/callum/Desktop/", filter=formats)
208
        if self.path:
209
            self.output = os.path.splitext(self.path)[0] + '_split'
210
            if not os.path.exists(self.output):  # make output directory
211
                os.mkdir(self.output)
212
213
            self.name = os.path.split(self.output)[-1]
214
            self.nameLineEdit.setText(self.name)
215
            self.load_ndpi.setStyleSheet("background-color: rgb(0,90,0)")
216
            try:
217
                self.image = OpenSlide(self.path)
218
            except Exception as e:
219
                self.loadndpi()
220
            print(self.path + ' read to memory')
221
            print('    slide format = ' + str(OpenSlide.detect_format(self.path)))
222
            if str(OpenSlide.detect_format(self.path)) == "aperio":
223
                try:
224
                    print('    Magnification = ' + str(self.image.properties['openslide.objective-power']))  # TODO
225
                    print('    Date = ' + str(self.image.properties['aperio.Date']))
226
                    print('    dimensions = ' + str(self.image.dimensions))
227
                    print('    level_downsamples = ' + str(self.image.level_downsamples))
228
                except KeyError:
229
                    pass
230
            if str(OpenSlide.detect_format(self.path)) == "hamamatsu":
231
                try:
232
                    self.formatLineEdit.setText("Hamamatsu")
233
                    self.scanDateLineEdit.setText(str(self.image.properties['tiff.DateTime'][:10]))
234
                    self.dimensionsLineEdit.setText(str(self.image.dimensions))
235
                    self.magnificationLineEdit.setText(str(self.image.properties['hamamatsu.SourceLens']))
236
                    self.show_info(f"""Magnification = {str(self.image.properties['hamamatsu.SourceLens'])}
237
Date = {str(self.image.properties['tiff.DateTime'])}\ndimensions = {str(self.image.dimensions)}
238
level_downsamples = {str(self.image.level_downsamples)}""")
239
                    print('    Magnification = ' + str(self.image.properties['hamamatsu.SourceLens']))
240
                    print('    Date = ' + str(self.image.properties['tiff.DateTime']))
241
                    print('    dimensions = ' + str(self.image.dimensions))
242
                    print('    level_downsamples = ' + str(self.image.level_downsamples))
243
                    self.macro_image = self.image.associated_images['macro']
244
                except KeyError:
245
                    pass
246
            self.overview_level_width = 3000
247
            self.activate([self.nameLabel, self.nameLineEdit, self.formatLabel, self.formatLineEdit,
248
                           self.magnificationLabel, self.magnificationLineEdit, self.scanDateLabel,
249
                           self.scanDateLineEdit, self.dimensionsLabel, self.dimensionsLineEdit, self.overlayLevelLabel,
250
                           self.overlayLevelLineEdit, self.graphicsView, self.overlaySave, self.groupBox_2,
251
                           self.removesmallobjects])
252
            self.get_overview()
253
            if os.path.exists(os.path.splitext(self.path)[0] + '.xlsx'):
254
                self.excelpath = os.path.splitext(self.path)[0] + '.xlsx'
255
                self.read_excel()
256
            else:
257
                self.excelpath = False
258
259
    def get_overview(self):
260
        self.width_height = [
261
            (int(self.image.properties[f'openslide.level[{i}].width']),
262
             int(self.image.properties[f'openslide.level[{i}].height'])) for i
263
            in range(int(self.image.properties['openslide.level-count']))]
264
        width = [self.width_height[i][0] for i in range(len(self.width_height))]
265
        self.lvl = np.where(width == self.find_nearest(width, self.overview_level_width))[0][0]
266
        self.overlayLevelLineEdit.setText(str(self.lvl))
267
        self.scale_index = self.width_height[0][0] / self.width_height[self.lvl][0]
268
        self.overview = np.array(self.image.read_region(location=(0, 0), level=self.lvl,
269
                                                        size=self.width_height[self.lvl]))
270
        self.showimage(image=self.overview)
271
272
    def find_nearest(self, array, value):
273
        # https://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
274
        array = np.asarray(array)
275
        idx = (np.abs(array - value)).argmin()
276
        return array[idx]
277
278
    def showimage(self, image):
279
        self.current_image = image
280
        img = qimage2ndarray.array2qimage(image, normalize=True)
281
        img = QPixmap(img)
282
        self.pixmap.setPixmap(img)
283
        self.graphicsView.fitInView(self.graphicsView.sceneRect(), Qt.KeepAspectRatio)
284
285
    def reset_sliders(self):
286
        self.gausslider.setValue(0)
287
        self.closingslider.setValue(0)
288
289
    def threshold(self, threshold_name):
290
        if self.scene.circles:
291
            self.scene.reset()
292
        if self.current_augments['overlay_applied']:
293
            del self.scene.coords
294
            self.init_scene()
295
            self.read_excel()
296
            self.current_augments['overlay_applied'] = False
297
        if threshold_name == "origional":
298
            self.showimage(self.overview)
299
            self.thresholdval = None
300
            self.current_augments["threshold"] = False
301
            return
302
        self.thresholdval = threshold_name
303
304
        im = rgb2gray(self.overview)
305
        if threshold_name == "otsu":
306
            threshold = threshold_otsu(im)
307
        if threshold_name == "li":
308
            threshold = threshold_li(im)
309
        if threshold_name == "mean":
310
            threshold = threshold_mean(im)
311
        if threshold_name == "triangle":
312
            threshold = threshold_triangle(im)
313
        self.current_augments["threshold"] = threshold_name
314
        self.current_image = im < threshold
315
        self.showimage(self.current_image)
316
317
    def gaus(self):
318
        self.gauslineEdit.setText(str(self.gausslider.value()))
319
        if self.current_image.ndim > 2:
320
            filtered = gaussian(self.overview, sigma=self.gausslider.value())
321
        else:
322
            self.threshold(self.current_augments["threshold"])
323
            filtered = gaussian(self.current_image, sigma=self.gausslider.value())
324
        self.showimage(filtered)
325
326
    def closing(self):
327
        self.closelineEdit.setText(str(self.closingslider.value()))
328
        if self.current_augments["threshold"]:
329
            self.threshold(self.current_augments["threshold"])
330
            if self.closingslider.value() > 0:
331
                if self.current_augments["gausian"]:
332
                    self.current_image = gaussian(self.current_image, sigma=self.gausslider.value())
333
                closed = closing(self.current_image, square(self.closingslider.value()))
334
                closed = closed > 0
335
                self.showimage(closed)
336
337
    def removesmall(self):
338
        """
339
        the function name is misleading as this function now applies remove small objects - then
340
        overlays the cores and saves the image
341
        """
342
        if not self.current_augments['threshold']:
343
            return
344
        if self.current_augments['overlay_applied']:
345
            self.init_scene()
346
            self.read_excel()
347
        labeled_image = label(self.current_image)
348
        try:
349
            min = int(self.smallobs_text.text())
350
        except ValueError as e:
351
            self.smallobs_text.setText("6000")
352
            min = 6000
353
            self.info("remove small value must be an integer - reset to 6000")
354
        labeled_image = remove_small_objects(labeled_image, min_size=min)
355
        self.showimage(labeled_image)
356
        labels = regionprops(labeled_image)
357
        centroid = [r.centroid for r in labels]
358
        self.overlaystart(autopilot=True, coords=centroid)
359
        self.current_augments['overlay_applied'] = True
360
361
    def read_excel(self):
362
        if not self.excelpath:
363
            self.excelpath, _ = QFileDialog.getOpenFileName(parent=self, caption='Open file',
364
                                                            directory="/Users/callum/Desktop", filter="*.xlsx*")
365
        self.activate([self.numberOfCoresLabel, self.numberOfCoresLineEdit, self.diameterLabel, self.diamiterLineEdit,
366
                       self.overlay, self.progressBar, self.excel_btn, self.overlaySave, self.tabWidget])
367
        if self.excelpath:
368
            self.load_excel.setStyleSheet("background-color: rgb(0,90,0)")
369
            excelname = self.excelpath
370
            wb = load_workbook(excelname)
371
            ws = wb.worksheets[0]
372
            cores = []
373
            values = []
374
            rowcount = []
375
            self.rowcol = []
376
            if not self.excel_layout:
377
                for row in ws.iter_rows():
378
                    for cell in row:
379
                        values.append(cell.value)
380
                        if cell.value == 1:
381
                            cores.append((str(chr(int(cell.row) + 64))) + str(int(ord(cell.column_letter)) - 64))
382
            else:
383
                cores = []
384
                for col in ws.iter_cols():
385
                    for cell in col:
386
                        if cell.value == 1:
387
                            cores.append(cell.coordinate)  # get core names if contain a 1
388
                cores.sort(key=lambda x: x[1:])
389
                for row in ws.iter_rows():
390
                    for cell in row:
391
                        values.append(cell.value)
392
            values = np.array_split(values, ws.max_row)
393
            for row in values:
394
                rowcount.append(np.count_nonzero(row))  # EXCEL END
395
            self.numberOfCoresLineEdit.setText(str(len(cores)))
396
            self.cores = cores
397
            self.values = values
398
            self.rowcount = rowcount
399
            self.arrayshape = (ws.max_row, ws.max_column)
400
            self.scene.rowcount = rowcount
401
            # check for pathology file
402
            if len(wb.sheetnames) > 1:
403
                ws = wb.worksheets[1]  # pathology tab (hopefully)
404
                self.pathology = [ws[i].value for i in self.cores]
405
406
    def info(self, text):
407
        self.label.setText(text)
408
409
    def export_images(self, meta_only=False):
410
        if not self.scene.centroid:
411
            self.info("must overlay some selected core - double click on image")
412
            return
413
        self.progressBar.setMaximum(len(self.scene.centroid))
414
        self.activate([self.nameLabel, self.nameLineEdit, self.formatLabel, self.formatLineEdit,
415
                       self.magnificationLabel, self.magnificationLineEdit, self.scanDateLabel,
416
                       self.scanDateLineEdit, self.dimensionsLabel, self.dimensionsLineEdit, self.overlayLevelLabel,
417
                       self.overlayLevelLineEdit, self.graphicsView, self.numberOfCoresLabel,
418
                       self.numberOfCoresLineEdit, self.diameterLabel, self.diamiterLineEdit,
419
                       self.export_2, self.overlay, self.excel_btn, self.load_ndpi, self.load_excel, self.overlaySave,
420
                       self.tabWidget], action=False)
421
        try:
422
            resolution = int(self.resolution_edit.text())
423
            print("new resolution applied")
424
        except ValueError as E:
425
            resolution = 0
426
427
        self.export = Export(image=self.image, centroid=self.scene.centroid, cores=self.cores,
428
                             scale_index=self.scale_index,
429
                             core_diameter=self.core_diameter, output=self.output, name=self.name, lvl=self.lvl,
430
                            path=self.path, arrayshape=self.arrayshape, pathology=self.pathology, resolution=resolution, window=self,
431
                             meta_only=meta_only)
432
433
        self.thread = QThread()
434
        self.export.info.connect(self.info)
435
        self.export.done.connect(self.complete)
436
        self.export.countChanged.connect(self.progressBar.setValue)
437
        self.export.moveToThread(self.thread)
438
        self.thread.started.connect(self.export.run)
439
        self.thread.start()
440
441
    def complete(self):
442
        print('done')
443
        self.activate([self.nameLabel, self.nameLineEdit, self.formatLabel, self.formatLineEdit,
444
                       self.magnificationLabel, self.magnificationLineEdit, self.scanDateLabel,
445
                       self.scanDateLineEdit, self.dimensionsLabel, self.dimensionsLineEdit, self.overlayLevelLabel,
446
                       self.overlayLevelLineEdit, self.graphicsView, self.numberOfCoresLabel,
447
                       self.numberOfCoresLineEdit, self.diameterLabel, self.diamiterLineEdit,
448
                       self.export_2, self.overlay, self.excel_btn, self.load_ndpi, self.load_excel, self.overlaySave],
449
                      action=True)
450
451
452
class Export(QObject):
453
    info = pyqtSignal(str)
454
    countChanged = pyqtSignal(int)
455
    figures = pyqtSignal()
456
    done = pyqtSignal(bool)
457
    writemeta = pyqtSignal(bool)
458
459
    def __init__(self, image, centroid, cores, scale_index, core_diameter, output, name, lvl, path, arrayshape,
460
                 pathology, resolution, window, meta_only=False):
461
        super().__init__()
462
        self.image = image
463
        self.centroid = centroid
464
        self.cores = cores
465
        self.scale_index = scale_index
466
        self.core_diameter = core_diameter
467
        self.output = output
468
        self.name = name
469
        self.lvl = lvl
470
        self.path = path
471
        self.arrayshape = arrayshape
472
        self.pathology = pathology
473
        self.resolution = resolution
474
        self.meta_only = meta_only
475
        self.win = window
476
477
    @pyqtSlot()
478
    def run(self):
479
        if self.meta_only:
480
            self.json_write()
481
            self.wsifigure(higher_resolution=False, pathology=self.pathology)
482
        else:
483
            self.export_images(self.centroid, self.cores)
484
485
    def export_images(self, centroid, cores):
486
        infostr = []
487
        self.scaledcent = [(y * self.scale_index, x * self.scale_index) for (x, y) in centroid]  # rotate xy openslide
488
        self.scaledcent = [(int(x - (self.core_diameter / 2)), int(y - (self.core_diameter / 2))) for (x, y) in self.scaledcent]
489
        self.json_write()
490
        self.wsifigure(higher_resolution=False, pathology=self.pathology)
491
        w_h = (self.core_diameter, self.core_diameter)
492
        self.lvl = 0
493
        for i in range(len(self.scaledcent)):
494
            if not self.win.isVisible():
495
                print("need to kill here - main window closed")
496
                break
497
            infostr.append("Exporting " + self.name + "_" + cores[i] + ".png")
498
            print("Exporting " + self.name + "_" + cores[i] + ".png")
499
            self.info.emit('\n'.join(infostr))
500
            core = self.image.read_region(location=self.scaledcent[i], level=self.lvl, size=w_h)
501
            core.save(self.output + os.sep + self.name + "_" + cores[i] + ".png")
502
            infostr.append("Saved " + self.name + "_" + cores[i] + ".png")
503
            self.info.emit('\n'.join(infostr))
504
            self.countChanged.emit(i + 1)
505
506
        infostr.append("All files exported with JSON metadata")
507
        self.info.emit('\n'.join(infostr))
508
        print('\n'.join(infostr))
509
        self.done.emit(True)
510
511
    def json_write(self):
512
        jsondata = {"path": self.path, "coordinates": self.scaledcent, "cores": self.cores,
513
                    "diameter": self.core_diameter, "scale_index": self.scale_index, "lowlevel": int(self.lvl),
514
                    "arrayshape": self.arrayshape}
515
        self.info.emit('Saving ' + self.output + os.sep + self.name + '_metadata.json')
516
        with open(self.output + os.sep + self.name + '_metadata.json', "w") as write_file:
517
            json.dump(jsondata, write_file)
518
519
    def wsifigure(self, higher_resolution=False, pathology=None):
520
        """
521
        TODO: need to link this better to the actual script - rather than the meta file before its created
522
        todo: have removed button export_again for function later
523
        takes the metadata from the json
524
        makes a fig of the locations on the tissue array and saves it
525
        higher resolution = int start at 1 and move up to improve resolution - will slow code
526
        """
527
        higher_resolution = self.resolution
528
529
        def overly_path(im, overlay, pathology):
530
            if pathology == "N":
531
                colour = [0, 1, 0]  # green
532
            else:
533
                colour = [1, 0, 0]  # red
534
            im[overlay == 0] = colour
535
            return im
536
537
        jsonpath = self.output + os.sep + self.name + '_metadata.json'
538
        with open(jsonpath) as json_file:
539
            data = json.load(json_file)
540
            image = OpenSlide(data['path'])
541
            if higher_resolution:
542
                lvl = data['lowlevel'] - int(higher_resolution)
543
            else:
544
                lvl = data['lowlevel']
545
            scale_index = image.level_downsamples[lvl]
546
            diameter = int(data["diameter"] / scale_index)
547
            arrayshape = data['arrayshape']
548
            arrayshape = [(a * diameter) + 1 for a in arrayshape]
549
            arrayshape.append(3)
550
            figarray = np.ones(arrayshape)
551
            cindex = [coordinate_from_string(i) for i in data['cores']]  # returns ('A',4)
552
            cindex = [(b, column_index_from_string(a)) for (a, b) in cindex]  # returns index tuples for each core
553
            maskcoord = [[y * diameter - diameter if y > 1 else y for y in x] for x in cindex]
554
555
            if pathology:
556
                overlay = np.load(sys._MEIPASS + os.sep + "scripts" + os.sep + "outline.npy")
557
                overlay = resize(overlay, (diameter, diameter))
558
559
            for i in range(len(data["coordinates"])):  # iterate through coordinates pulling out images from wsi
560
                im = image.read_region(location=data["coordinates"][i], level=lvl, size=(diameter, diameter))
561
                im = np.array(im)[:, :, :3]
562
                if pathology:
563
                    try:
564
                        im = overly_path(im, overlay, pathology[i])
565
                    except:
566
                        continue
567
                figarray[maskcoord[i][0]:maskcoord[i][0] + diameter, maskcoord[i][1]:maskcoord[i][1] + diameter, :] = im
568
            figarray[figarray == [1, 1, 1]] = 255  # make background white
569
            savepath = self.output + os.sep + self.name + '_layoutfig.tiff'
570
            Image.fromarray(figarray.astype(np.uint8)).save(savepath)