Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This script is the main script used to produce a GUI which should help for
cell segmentation. This script can only read .nd2 files containing the
images of cells, especially it displays for each recorded positions (field
of view) the pictures in the time axis.
The script opens first a window which allows you to load an nd2 file and
to load or create an hdf file. The hdf file contains all the masks, so if
it is the first the user segments an nd2 file, a new one should be created.
And it can be then loaded for later use. Along with new hdf file, created
by the name entered by the user (say filename), it creates three other hdf
files (filename_predicted.h5, filename_thresholded.h5 and
filename_segmented.h5) these contain all the steps of the NN to get to the
segmented picture.
After the first window is finished a second one opens, where at each time
index, three pictures
are displayed the t-1 picture, the t picture (current frame which can be
edited) and the t+1 picture. Using the arrows one can navigate through time.
On top of the picture, there is always a mask which is displayed, if no cells
are present in the mask then the mask is blank and the user does not see it.
If one wants to hand anmotate the pictures, one can just start to draw on the
picture using the different functions (New Cell, Add Region, Brush, Eraser,
Save Mask,...) and the informations will be saved in the mask overlayed on
top of the pictures.
If one wants to segment using a neural network, one can press the
corresponding button (Launch CNN) and select the time range and
the field of views on which the neural network is applied.
Once the neural network has finished predicting, there are still no visible
masks, but on the field of views and time indices where the NN has been
applied, the threshold and segment buttons are enabled. By checking these
two buttons one can either display the thresholded image of the prediction or
display the segmentation of the thresholded prediction.
At this stage, one can correct the segmentation of the prediction using
the functions (New Cell, Add Region, etc..) by selecting the Segment
checkbox and then save them using the Save Seg button.
If the user is happy with the segmentation, the Cell Correspondance button
can be clicked. Until then, the cells get random numbers attributed by
the segmentation algorithm. In order to keep track of the cell through time,
the same cells should have the same number between two different time pictures.
This can be (with some errors) achieved by the Cell Correspondance button,
which tries to attribute the same number to corresponding cells in time.
After that, the final mask is saved and it is always visible when you go on
the corresponding picture. This mask can also be corrected using the
usual buttons (because the Cell Correspondance makes also mistakes).
"""
import sys
#append all the paths where the modules are stored. Such that this script
#looks into all of these folders when importing modules.
sys.path.append("./unet")
sys.path.append("./disk")
sys.path.append("./icons")
sys.path.append("./init")
sys.path.append("./misc")
import time
import os
import numpy as np
# Import everything for the Graphical User Interface from the PyQt5 library.
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, QPushButton, QShortcut, QComboBox, QCheckBox, QLineEdit, QMenu, QAction, QStatusBar
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal, QObject, Qt
#Import from matplotlib to use it to display the pictures and masks.
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
#import the colormaps function to create a customed colormap scale with 10
#colors only
from matplotlib import cm
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
#import Path functions to handle the regions drawn by the user. ("add region
#and new cell")
from matplotlib.path import Path
#Import all the other python files
#this file handles the interaction with the disk, so loading/saving images
#and masks and it also runs the neural network.
import InteractionDisk_temp as nd
#this file contains a dialog window that takes two integers as entry to swap
#two cell values
import ExchangeCellValues as ecv
#this file contains a dialog window which is opened before the main program
#and allows to load the nd2 and hdf files by browsing through the computer.
import DialogFileBrowser as dfb
#this file contains a window that opens to change the value of one cell. It
#is opened as soon as the user presses with the left click on a specific cell.
import ChangeOneCellValue as cocv
#this file contains a dialog window to browse for the excel file where
#all the extracted information on the fluoerscence is written. Or to create a
#new excel file by typing a name in the text box. It is thought to have one
#excel file per field of view.
import DialogDataBrowser as ddb
#this file contains a dialog window where a time range and the field of views
#can be selected to then launch a prediction of the neural network on
#a specific range of pictures.
import LaunchBatchPrediction as lbp
#this file initializes all the buttons present in the gui, sets the shortcuts
#to these buttons and also connect the buttons to the function that are
#triggered when the buttons are pressed.
import InitButtons
#this file contains the layout of the main window so it justs puts the buttons
#and the pictures at the desired position in the main window.
import InitLayout
import random
#import everything needed to write and read excel files.
from openpyxl import load_workbook
from openpyxl import Workbook
class NavigationToolbar(NavigationToolbar):
"""This is the standard matplotlib toolbar but only the buttons
that are of interest for this gui are loaded. These buttons allow
to zoom into the pictures/masks and to navigate in the zoomed picture.
A Home button can be used to set the view back to the original view.
"""
toolitems = [t for t in NavigationToolbar.toolitems if
t[0] in ('Home', 'Pan', 'Zoom','Back', 'Forward')]
class App(QMainWindow):
"""This class creates the main window.
"""
def __init__(self, nd2pathstr, hdfpathstr, newhdfstr):
super().__init__()
# initializes the window
# id is an integer that gives the id of the connection between the mouseclick method
# and the activation of the button.
# all these ids are integers which are used to set a connection between
# the button and the function that this button calls.
# There are three of them because it happens that one can trigger three
# different functions with one button.
self.id = 0
self.id2 = 0
self.id3 = 0
# it calls an object of the class Load Image from the InteractionDisk
# file which is used to load images and masks from the nd2 file. To
# initialize this object it needs the path of the nd2 file, of an
# existing hdf file and the name of a new hdf file. If the user has no
# hdf file yet the hdfpathstr will be empty and vice versa if the user
# selects an already existing hdf file.
# It takes all the strings given by the first window
# (called before the main window opens) from the DialogFileBrowser.py
# file. (see at the end of this code)
self.reader = nd.Reader(hdfpathstr, newhdfstr, nd2pathstr)
# these variables are used to create/read/load the excel file used
# to write the fluorescence values extracted. For each field of view,
# the user will be asked each time to create a new xls file for the
# field of view or to load an existing field of view (this is the role
# of the boolean variable)
self.xlsfilename = ''
self.nd2path = nd2pathstr
self.FlagFluoExtraction = False
# Set the indices for the time axis and the field of view index. These
# indices represent everywhere the current picture (the one that can be
# edited, i.e. the time t frame)
self.Tindex = 0
self.FOVindex = 0
# loading the first images of the cells from the nd2 file
self.currentframe = self.reader.LoadOneImage(self.Tindex,self.FOVindex)
self.nextframe = self.reader.LoadOneImage(self.Tindex+1, self.FOVindex)
self.previousframe = np.zeros([self.reader.sizey, self.reader.sizex])
# loading the first masks from the hdf5 file
self.mask_curr = self.reader.LoadMask(self.Tindex, self.FOVindex)
self.mask_previous = np.zeros([self.reader.sizey, self.reader.sizex])
self.mask_next = self.reader.LoadMask(self.Tindex+1, self.FOVindex)
# creates a list of all the buttons, which will then be used in order
# to disable all the other buttons at once when one button/function
# is pressed/used in the gui.
self.buttonlist = []
# setting buttons as attributes
# the shortcuts for the buttons, the functions to which they are
# connected to,... are all set up in the ButtonInit file which is called
# in the self.initUI() method below.
self.button_newcell = QPushButton("New cell")
self.buttonlist.append(self.button_newcell)
self.button_add_region = QPushButton("Add region")
self.buttonlist.append(self.button_add_region)
self.button_savemask = QPushButton("Save Mask")
self.buttonlist.append(self.button_savemask)
self.button_drawmouse = QPushButton('Brush')
self.buttonlist.append(self.button_drawmouse)
self.button_eraser = QPushButton('Eraser')
self.buttonlist.append(self.button_eraser)
self.button_exval = QPushButton('Exchange Cell Values')
self.buttonlist.append(self.button_exval)
self.button_showval = QCheckBox('Show Cell Values')
self.buttonlist.append(self.button_showval)
self.button_hidemask = QCheckBox('Hide Mask')
self.buttonlist.append(self.button_hidemask)
self.button_nextframe = QPushButton("Next Time Frame")
self.buttonlist.append(self.button_nextframe)
self.button_previousframe = QPushButton("Previous Time Frame")
self.buttonlist.append(self.button_previousframe)
self.button_cnn = QPushButton('Launch CNN')
self.buttonlist.append(self.button_cnn)
self.button_threshold = QCheckBox('Threshold prediction')
self.buttonlist.append(self.button_threshold)
self.button_segment = QCheckBox('Segment')
self.buttonlist.append(self.button_segment)
self.button_cellcorespondance = QPushButton('Cell Correspondance')
self.buttonlist.append(self.button_cellcorespondance)
self.button_changecellvalue = QPushButton('Change cell value')
self.buttonlist.append(self.button_changecellvalue)
self.button_extractfluorescence = QPushButton('Extract Fluorescence')
self.buttonlist.append(self.button_extractfluorescence)
self.initUI()
def initUI(self):
"""Initializing the widgets contained in the window.
Especially, it creates the widget to plot the
pictures/masks by creating an object of the PlotCanvas class self.m.
Every interaction with the masks or the pictures (loading new
frames/editing the frames/masks) occurs through this class.
This method initializes all the buttons with the InitButtons file.
It connects the buttons to the functions that they should trigger,
it sets the shortcuts to the buttons, a tool tip,
eventually a message on the status bar when the user hovers
over the button, etc..
This function also sets all the layout in the InitLayout file. It
takes and places the widgets (buttons, canvas, toolbar).
The function initializes a Menu Bar to have a menu which can be
improved later on.
It sets a toolbar of the matplotlib library and hides it. But it allows
to connect to the functions of this toolbar through "homemade"
QPushButtons instead of the ones provided by matplotlib.
Finally, it sets a StatusBar which displays some text to describe
the use of some buttons, or to show that the program is working on
something (running the neural network, loading frames, etc...)
After all this has been initialized, the program is ready to be used.
"""
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
# Here our canvas is created where using matplotlib,
# one can plot data to display the pictures and masks.
self.m = PlotCanvas(self)
# Initialize all the buttons that are needed and the functions that are
# connected when the buttons are triggered.
InitButtons.Init(self)
InitLayout.Init(self)
# MENU, TOOLBAR AND STATUS BAR
# creates a menu just in case, some other functions can be added later
# in this menu.
menubar = self.menuBar()
self.fileMenu = menubar.addMenu('File')
self.saveactionmenu = QAction('Save')
self.fileMenu.addAction(self.saveactionmenu)
self.saveactionmenu.triggered.connect(self.ButtonSaveMask)
# hide the toolbar and instead of the original buttons of matplotlib,
# QPushbuttons are used and are connected to the functions of the toolbar
# it is than easier to interact with these buttons (for example to
# to disable them and so on..)
self.Nvgtlbar = NavigationToolbar(self.m, self)
self.addToolBar(self.Nvgtlbar)
self.Nvgtlbar.hide()
# creates a status bar, which displays (or should display) some text
# whenever a function is used.
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
self.show()
def mousePressEvent(self, QMouseEvent):
"""this function is implemented just to have the QLineButtons of the
change time index button, setthreshold button and the setsegmentation
button out of focus when the user clicks somewhere
on the gui. (to unfocus the buttons)
"""
self.button_timeindex.clearFocus()
if self.button_SetThreshold.isEnabled():
self.button_SetThreshold.clearFocus()
if self.button_SetSegmentation.isEnabled():
self.button_SetSegmentation.clearFocus()
# CONNECT the functions of the toolbar to our custom QPushbuttons.
def ZoomTlbar(self):
"""The button_zoom is connected to the zoom function of the toolbar
already present in the matplotlib library.
Depending on the buttons that are active or checked, when the zoom
function is used, it does not disable all the buttons.
If the segment and threshold button are not checked or used
when the zoom button is clicked, it disables all the button
using self.Disable which disables everything except the button passed
in argument (in this case button_zoom).
If the zoom button is used while the segment button is checked,
it disables all the buttons (1st elif) except the segment button
but once it is finished (so the zoom button becomes unchecked)
then it enables only the editing buttons (as long as the segment
button is still checked) such as New Cell, Add Region, Eraser,
Brush,etc.. and the other toolbar buttons (3rd elif)
If the zoom button is clicked while the threshold button is checked,
it disables all the button except the threshold button (2nd elif).
Once the zoom button is unchecked, it enables the toolbar buttons
(4th elif)
In any other case, it just enables all the buttons again.
"""
self.Nvgtlbar.zoom()
if self.button_zoom.isChecked() and not(self.button_segment.isChecked() or self.button_threshold.isChecked()):
self.Disable(self.button_zoom)
elif self.button_zoom.isChecked() and self.button_segment.isChecked():
self.Disable(self.button_zoom)
self.button_segment.setEnabled(True)
elif self.button_zoom.isChecked() and self.button_threshold.isChecked():
self.Disable(self.button_zoom)
self.button_threshold.setEnabled(True)
elif self.button_zoom.isChecked() == False and self.button_segment.isChecked():
self.button_pan.setEnabled(True)
self.button_home.setEnabled(True)
self.button_back.setEnabled(True)
self.button_forward.setEnabled(True)
self.EnableCorrectionsButtons()
elif self.button_zoom.isChecked() == False and self.button_threshold.isChecked():
self.button_pan.setEnabled(True)
self.button_home.setEnabled(True)
self.button_back.setEnabled(True)
self.button_forward.setEnabled(True)
else:
self.Enable(self.button_zoom)
def HomeTlbar(self):
# connects the home button to the home function of the matplotlib
# toolbar. It sets the view to the original view (no zoom)
self.Nvgtlbar.home()
def BackTlbar(self):
# It calls the back function of the matplotlib toolbar which sets the
# view to the previous one (if the user does several zooms/pans,
# this button allows to go back in the "history of views")
self.Nvgtlbar.back()
def ForwardTlbar(self):
# It calls the forward function of the matplotlib toolbar which sets the
# view to the next one (if the user does several zooms/pans,
# this button allows to go forward in the "history of views"
self.Nvgtlbar.forward()
def PanTlbar(self):
"""The button_pan is connected to the pan function of the toolbar
already present in the matplotlib library.
Depending on the buttons that are active or checked, when the pan
function is used, it does not disable all the buttons.
If the segment and threshold button are not checked or used
when the pan button is clicked, it disables all the button
using self.Disable which disables everything except the button passed
in argument (in this case button_pan).
If the pan button is used while the segment button is checked,
it disables all the buttons (1st elif) except the segment button
but once it is finished (so the zoom button becomes unchecked)
then it enables only the editing buttons (as long as the segment
button is still checked) such as New Cell, Add Region, Eraser,
Brush,etc.. and the other toolbar buttons (3rd elif)
If the pan button is clicked while the threshold button is checked,
it disables all the button except the threshold button (2nd elif).
Once the pan button is unchecked, it enables the toolbar buttons
(4th elif)
In any other case, it just enables all the buttons again.
"""
self.Nvgtlbar.pan()
if self.button_pan.isChecked() and not(self.button_segment.isChecked() or self.button_threshold.isChecked()):
self.Disable(self.button_pan)
elif self.button_pan.isChecked() and self.button_segment.isChecked():
self.Disable(self.button_pan)
self.button_segment.setEnabled(True)
elif self.button_pan.isChecked() and self.button_threshold.isChecked():
self.Disable(self.button_pan)
self.button_threshold.setEnabled(True)
elif not(self.button_pan.isChecked()) and self.button_segment.isChecked():
self.button_zoom.setEnabled(True)
self.button_home.setEnabled(True)
self.button_back.setEnabled(True)
self.button_forward.setEnabled(True)
self.EnableCorrectionsButtons()
elif not(self.button_pan.isChecked()) and self.button_threshold.isChecked():
self.button_zoom.setEnabled(True)
self.button_home.setEnabled(True)
self.button_back.setEnabled(True)
self.button_forward.setEnabled(True)
else:
self.Enable(self.button_pan)
def ButtonFluo(self):
"""This function is called everytime the Extract Fluorescence button is
clicked (self.button_extractfluorescence).
self.FlagFluoExtraction is boolean which is True when the path to the
excel file has already been loaded into self.xlsfilename.
This pathname changes for each field of view as it is thought to have
one xls file per field of view.
So at the beginning and each time the user changes field of view,
self.FlagFluoExtraction is set to False.
When it is set to False, this function calls a dialog window where
the user is asked to load an already existing xls file for the current
field of view or to give a name to create a new xls file for
the current field of view. (self.Dialogxls)
If it set to true, it means that self.xlsfilename contains the path
to the xls file for the current field of view and it is directly given
to the function that writes the fluorescence into the xls file.
(self.ExtractFluo)
"""
if self.FlagFluoExtraction:
self.ExtractFluo(self.xlsfilename)
else:
self.DialogXls()
def DialogXls(self):
"""This function creates a dialog window which gives two options to the
user either to load an existing xls file or to give a new name in order
to create a new xls file.
"""
# creates the window
dwind = ddb.FileBrowser()
# this test is True if the user presses ok in the dialog window, if the
# user presses cancels it returns and nothing happens.
if dwind.exec_():
# read the entry given by the file browser
xlsname = dwind.xlsname
# reads the entry given by the new filename text field.
newxlsname = dwind.newxlsentry.text()
# if the string containing the filepath to an existing xls file
# is not empty then it calls directly the function to write the
# data into this existing xls file and sets self.xlsfilename
if xlsname:
self.xlsfilename = xlsname
self.ExtractFluo(xlsname)
# if xlsname is empty then it creates a new pathfilename and puts
# the new created xls file into the folder where nd2 is located.
# the string containing the nd2 namepath is split
else:
xlsname = ''
templist = self.nd2path.split('/')
for k in range(0, len(templist)-1):
xlsname = xlsname + templist[k] + '/'
# this is the new path/filename
xlsname = xlsname + newxlsname + '.xlsx'
self.xlsfilename = xlsname
# here as a new name has been given, it means that a new xls file
# should be created, this is done with CreateXls
self.CreateXls(xlsname)
# once there is an existing xls file, it writes in this file
# using self.ExtractFluo.
self.ExtractFluo(xlsname)
# this flag is set to true, for the current field of view each
# time extract fluorescence is clicked it writes in the file located
# at self.xlsfilename.
self.FlagFluoExtraction = True
else:
return
def CreateXls(self, xlsfilename):
"""In case there is no xls file existing, here a new one is created
and prepared. For each channel a new sheet is created.
In the first row for each sheet, the time indices are written t = 0,
t = 1, etc... but only every third column. Because in the row below,
three values are extracted 'Total intensity', 'Area' and 'Variance'
at each time index. So three columns for each time index are needed,
for the three data points.
The first column is left empty (starting from third row) because
the cell numbers will be written in there.
"""
# creates a new xls file using xlwt library.
book = Workbook()
nbrchannels = self.reader.sizec
for i in range(0,nbrchannels):
sheetname = self.reader.channel_names[i]
# creates a sheet with the name of the corresponding channel.
if i == 0:
sheet = book.active
sheet.title = sheetname
else:
sheet = book.create_sheet(sheetname)
sheet.cell(1,1, 'Cell Number / Time axis')
sheet.cell(2,1, 'labels')
timeaxissize = self.reader.sizet
# start writing the time index at column 1, column 0 is reserved for
# cell numbers.
timecolindex = 2
for t in range(1,timeaxissize+1):
# in row 0 the time index is written
sheet.cell(1,timecolindex).value = 't = {}'.format(t-1)
# in row 1, the label of the three data points are written
sheet.cell(2,timecolindex).value = 'Total Intensity'
sheet.cell(2,timecolindex+1).value = 'Total Area'
sheet.cell(2,timecolindex+2).value = 'Mean Intensity'
sheet.cell(2,timecolindex+3).value = 'Variance'
# updates the index, where the next time index should be written
timecolindex = timecolindex + 4
# saves the xls file.
book.save(xlsfilename)
def ExtractFluo(self, xlsfilename):
"""This is the function that takes as argument the filepath to the xls
file and writes in the file.
It iterates over the different channels (or the sheets of the file,
each channel has one sheet.), and reads the image corresponding
to the time, field of view and channel index. It reads the already
existing file and makes a copy in which the data will be written in it.
The first step of calculating the data is to iterate through each
cell/segment of the mask (so each cell is a submatrix of one value
in the matrix of the mask).
For each of these value /cell, the area is extracted as being
the number of pixels corresponding to this cell/value.
(it is known from the microscope settings how to convert
the pixel in area).
The total intensity is just the value of the pixel and it is added over
all the pixels corresonding to the cell/value.
The mean is then calculated as being the total intensity divided by
the number of pixels (which here is equal to the area also).
With the mean it is then possible to calculate the variance of the
signal for one cell/value.
Then, it is checked if the value of the cell (cell number) already
exists in the first column, if it already exists it continues to
find the column corresponding to the time index where the values
should be written. It sets the flag to True such that it does not
write the cell as new one and adds it at the end of the column
If the value is not found in the cell number column (new cell or
first time writing in the file), the flag is False, thus it adds the
cell number at the end of the column.
It then saves the xls file.
"""
# disables all the buttons except the one passed in argument.
self.Disable(self.button_extractfluorescence)
# shows a message on the status bar to show that the program is working
self.statusBar.showMessage('Extracting the fluorescence...')
# opens the file to read it.
book = load_workbook(self.xlsfilename)
# makes a copy of the reading file to write in it the new values.
# wb = xlscopy(readbook) # a writable copy (can't read values out of this, only write to it)
# iterate over all the channels, so over all the sheets in the file
for channel in range(0, self.reader.sizec):
# loads the picture corresponding to the channel, time index and fov
image = self.reader.LoadImageChannel(self.Tindex, self.FOVindex, channel)
# loads the sheet to read out corresponding to the current channel
sheet = book.worksheets[channel]
# sheet = readbook.sheet_by_index(channel)
# this line is here to prevent some errors of streaming into
# the file due to read file which is open (I am not sure about this
# but it is a more or less working solution found on stackoverflow)
# os.remove(xlsfilename)
# load the sheet corresponding to the current channel to write in it
# writingsheet = wb.get_sheet(channel)
# this index contains the value of the maximum number of rows in the
# file, it is used to append at the end the cell number column a new
# cell/value, and it is updated each time a new cell is added.
tempidx = sheet.max_row
# np.unique(array) returns an array which contains all the value
# that appear in self.m.plotmask, so it returns every cell value
# including the background (value 0) present in self.m.plotmask
for val in np.unique(self.m.plotmask):
# exclude the background, as it is not a cell
if val != 0:
# this (self.m.plotmask==val).sum() adds one at every pixel
# where self.m.plotmask has the value val
area = (self.m.plotmask == val).sum()
# it sums the value of the pixel in image at the coordinates
# where self.m.plotmask equals val.
tot_intensity = image[self.m.plotmask == val].sum()
# calculate the mean to use it in the calc. of the variance.
mean = tot_intensity/area
# create a copy of plotmask because I had weird experiences
# with np.where where it modified sometimes the given array
# (not sure)
temparr = self.m.plotmask.copy()
# extract the coordinates of the mask matrix where it equals
# the current cell value val
coord = np.where(temparr == val)
# variable to save the variance.
var = 0
# we loop over the coord of the pixel where mask == val
for i in range(0,len(coord[0])):
# extract the intensity at the coordinate
val_intensity = image[coord[0][i], coord[1][i]]
# substract the value of the intensity with the mean,
# and square it. It is then add to the var.
var = var + (val_intensity-mean)*(val_intensity-mean)
# var is then divided by the number of the pixel
# corresponding to this value (also equal to the area)
# to get the variance.
var = var/area
# if flag is false it means that the cell number
# corresponding to val is not present in the xls file, first
# column.
flag = False
# iterate over all the rows
for row in range(sheet.max_row+1):
# test if in the first column 0, the number of the cell
# is already present
# if sheet.cell_value(row,0) == str(val):
if sheet.cell(row = row+1, column = 1).value == str(val):
# if is present, the column corresponding to the
# current time index is by iterating over the cols.
for col in range(sheet.max_column+1):
# test if it is the right column
# if sheet.cell_value(0, col) == 't = {}'.format(self.Tindex):
if sheet.cell(row = 1, column = col+1).value == 't = {}'.format(self.Tindex):
# write in the xls file at the row, col coord
sheet.cell(row+1, col+1, str(tot_intensity))
sheet.cell(row+1, col+2, str(area))
sheet.cell(row+1,col+3, str(mean))
sheet.cell(row+1, col+4, str(var))
book.save(xlsfilename)
# the flag is set to True so that it does
# not execute the code where the cell is
# added in the xls file in a new row.
flag = True
if not flag:
# this lines are executed if a new cell is detected or if
# if it is the first time to write in the file.
for col in range(sheet.max_column+1):
if sheet.cell(row = 1, column = col+1).value == 't = {}'.format(self.Tindex):
# it write the cell value/cell number in the
# column
sheet.cell(tempidx+1,1, str(val))
# writes the data extracted before
sheet.cell(tempidx+1,col+1,str(tot_intensity))
sheet.cell(tempidx+1, col+2, str(area))
sheet.cell(tempidx+1, col+3, str(mean))
sheet.cell(tempidx+1, col+4, str(var))
# it updates the number of rows as a new cell
# has been added, so there is one more row.
tempidx = tempidx + 1
# save in the file
book.save(xlsfilename)
# Enable again all the buttons
self.Enable(self.button_extractfluorescence)
# clear the message shown in the status bar
self.statusBar.clearMessage()
def LaunchBatchPrediction(self):
"""This function is called whenever the button Launch CNN is pressed.
It allows to run the neural network over a time range and selected
field of views.
It creates a dialog window with two entries, that define the time range
and a list where the user can select the desired fields of view.
Once it reads all the value, it calls the neural network function
inside of self.PredThreshSeg and it does the prediction of the neural
network, thresholds this prediction and then segments it.
"""
# creates a dialog window from the LaunchBatchPrediction.py file
dlg = lbp.CustomDialog(self)
# this if tests if the user pressed 'ok' in the dialog window
if dlg.exec_():
# it tests if the user has entered some values
# if not it ignores and returns.
if dlg.entry1.text()!= '' and dlg.entry2.text() != '':
# reads out the entry given by the user and converts the index
# to integers
time_value1 = int(dlg.entry1.text())
time_value2 = int(dlg.entry2.text())
# it tests if the first value is smaller or equal such that
# time_value1 is the lower range of the time range
# and time_value2 the upper boundary of the range.
if time_value1 <= time_value2 :
# displays that the neural network is running
self.statusBar.showMessage('Running the neural network...')
# it iterates in the list of the user-selected fields
# of view, to return the corresponding index, the function
# dlg.listfov.row(item) is used which gives an integer
for item in dlg.listfov.selectedItems():
# iterates over the time indices in the range
for t in range(time_value1, time_value2+1):
# calls the neural network for time t and selected
# fov
if dlg.entry_threshold.text() != '':
thr_val = float(dlg.entry_threshold.text())
else:
thr_val = None
if dlg.entry_segmentation.text() != '':
seg_val = int(dlg.entry_segmentation.text())
else:
seg_val = 10
self.PredThreshSeg(t, dlg.listfov.row(item), thr_val, seg_val)
# once it has iterated over all the fov, the message in
# the status bar is cleared and the buttons are enabled.
self.statusBar.clearMessage()
self.EnableCNNButtons()
else:
return
else:
return
else:
return
def PredThreshSeg(self, timeindex, fovindex, thr_val, seg_val):
"""
This function is called in the LaunchBatchPrediction function.
This function calls the neural network function in the
InteractionDisk.py file and then thresholds the result
of the prediction, saves this thresholded prediction.
Then it segments the thresholded prediction and saves the
segmentation.
"""
# launches the neural network
self.reader.LaunchPrediction(timeindex, fovindex)
# thresholds the prediction
self.m.ThresholdMask = self.reader.ThresholdPred(thr_val, timeindex,fovindex)
# saves the thresholded pred.
self.reader.SaveThresholdMask(timeindex, fovindex, self.m.ThresholdMask)
# segments the thresholded pred.
self.m.SegmentedMask = self.reader.Segment(seg_val, timeindex,fovindex)
# saves the segmentation
self.reader.SaveSegMask(timeindex, fovindex, self.m.SegmentedMask)
def LaunchPrediction(self):
"""This function is not used in the gui, but it can be used to launch
the prediction of one picture, with no thresholding and no segmentation
"""
if not(self.reader.TestPredExisting(self.Tindex, self.FOVindex)):
self.statusBar.showMessage('Running the neural network...')
self.Disable(self.button_cnn)
self.reader.LaunchPrediction(self.Tindex, self.FOVindex)
self.Enable(self.button_cnn)
self.button_cnn.setEnabled(False)
self.button_threshold.setEnabled(True)
self.button_segment.setEnabled(True)
self.button_cellcorespondance.setEnabled(True)
self.statusBar.clearMessage()
def ChangeOneValue(self):
"""This function is called when the button Change cell value is
clicked. It displays the instructions on the status bar.
And if the user clicks in the graph where the current mask is displayed
it connects the event of the click (meaning that user has clicked on
one cell) to the function self.DialogBoxChangeOneValue.
This function will then replaces the cell selected by the user with
the click with a new value entered by the user.
"""
# displaying the instructions on the statusbar
self.statusBar.showMessage('Select one cell using the left click and then enter the desired value in the dialog box')
# disables all the buttons
self.Disable(self.button_changecellvalue)
# connects the event "press mouse button" in the matplotlib plot
# (picture) to the function self.DialogBoxChangeOneValue
self.id = self.m.mpl_connect('button_press_event', self.DialogBoxChangeOneValue)
def DialogBoxChangeOneValue(self, event):
"""This function is called when the user after the user has selected
the button Change cell value and clicked in the picture to select
the desired cell to change.
It first deconnects the mouse click event in matplotlib with this
function to not generate any other dialog window.
It then tests if the click is inside the matplotlib plot (if it is
outside it equals to None) and if it is the current and editable plot
(the one in the middle of the gui, self.m.ax)
If is true, then it sets the coordinates to int. and creates a dialog
window where the user is asked to type a value to set it to the cell.
If the user presses ok, it tests if the entry is valid (>0 and not
empty) and looks for the old cell value and replaces it. And then
it updates the plot such that the result of the change can be seen.
"""
# the function is disconnected from the matplotlib event.
self.m.mpl_disconnect(self.id)
# test if the button is a left click and if the coordinates
# chosen by the user click is inside of the current matplotlib plot
# which is given by self.m.ax
if event.button == 1 and (event.xdata != None and event.ydata != None) and self.m.ax == event.inaxes:
newx = int(event.xdata)
newy = int(event.ydata)
# creates a dialog window
dlg = cocv.CustomDialog(self)
# if the user presses 'ok' in the dialog window it executes the code
# else it does nothing
if dlg.exec():
# it tests that the user has entered some value, that it is not
# empty and that it is equal or bigger to 0.
if dlg.entry1.text() != '' and int(dlg.entry1.text()) >= 0:
# reads the new value to set and converts it from str to int
value = int(dlg.entry1.text())
# self.m.plotmask[newy, newx] the value selected by the user
# self.m.plotmask == self.m.plotmask[newy, newx]
# gives the coordinates where it is equal to the value
# selected by the user. And it replaces it with the new
# value.
self.m.plotmask[self.m.plotmask == self.m.plotmask[newy,newx]] = value
# updates the plot to see the modification.
self.m.updatedata()
# if the button to show cell values is checked, then it
# replots the cell values
if self.button_showval.isChecked():
self.m.ShowCellNumbersCurr()
self.m.ShowCellNumbersNext()
self.m.ShowCellNumbersPrev()
# enables the button again
self.Enable(self.button_changecellvalue)
# clears the message in the status bar
self.statusBar.clearMessage()
# the button is a checkable and it has to be unchecked else it seems
# that the button is still in use, because it gets a blue color.
self.button_changecellvalue.setChecked(False)