forked from eisfabian/SPACEtomo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSPACEtomo_TI.py
More file actions
1924 lines (1547 loc) · 90.9 KB
/
Copy pathSPACEtomo_TI.py
File metadata and controls
1924 lines (1547 loc) · 90.9 KB
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 python
# ===================================================================
# ScriptName: SPACEtomo_TI
# Purpose: User interface for training SPACEtomo segmentation models using nnU-Netv2
# More information at http://github.com/eisfabian/SPACEtomo
# Author: Fabian Eisenstein
# Created: 2024/02/15
# Revision: v1.1
# Last Change: 2024/03/22: fixed runNapari, disabled target selection by default (made independent GUI for it)
# 2024/03/14: added adding and deleting points with left and right click
# 2024/03/13: added drag and drop for targets, added import and export for targets, added plot color themes for clusters
# 2024/03/12: added target selection menu, added call to target selection, added show target with cam and beam overlays
# 2024/02/27: Cleaned up output and included file paths, added colors
# 2024/02/26: fixed export model, added logo
# 2024/02/23: added meta_data for exported png, fixed export pixel size to previous exports, added checkPixelSize function, added pixel size menu and scaling to training tab, added tooltips, added export model
# 2024/02/22: reverted to static textures for better performance and added slight delay after texture deletion, added open next map, added datasets to previous export detection
# 2024/02/21: added import of previous dataset segmentations, added open log button, added manual mont_shape, fixed mont_shape, temp fixes seg fault when deleting textures by using dynamic texture
# 2024/02/20: added training commands selecting device and fold
# ===================================================================
##### SETTINGS #####
dynamic_textures = False # slower performance, but fix some Segmentation fault crashes
show_tgt_selection = False # show options for target selection in inspection tab (it's recommended to do target selection in SPACEtomo_tgt.py)
### END SETTINGS ###
import os
os.environ["__GLVND_DISALLOW_PATCHING"] = "1" # helps to minimize Segmentation fault crashes on Linux when deleting textures
import sys
import copy
import shutil
import glob
import json
import dearpygui.dearpygui as dpg
from PIL import Image, ImageDraw
Image.MAX_IMAGE_PIXELS = None
import numpy as np
import time
import datetime
import mrcfile
import subprocess
from skimage import exposure, transform, draw
import torch
import SPACEtomo.modules.ext as space_ext
import SPACEtomo.modules.utils as utils
from SPACEtomo import __version__
CUR_DIR = os.getcwd()
SPACE_DIR = os.path.dirname(__file__)
FUNC_IMPORT = True
# Check if napari is installed
try:
import napari
napari_installed = True
except ModuleNotFoundError:
napari_installed = False
# Find available GPUs
if torch.cuda.is_available():
DEVICE = "cuda"
print("NOTE: Found CUDA device.")
for i in range(torch.cuda.device_count()):
print(i, torch.cuda.get_device_name(i))
elif torch.backends.mps.is_available():
DEVICE = "mps"
print("NOTE: Found MPS device.")
else:
DEVICE = "cpu"
print("NOTE: Found no GPU, using CPU.")
dpg.create_context()
##### Utilities
def openTxt(filename, app_data=None, user_data=None):
if os.path.exists(filename):
subprocess.Popen(["open", filename])
elif os.path.exists(user_data):
subprocess.Popen(["open", user_data])
def loadDatasetJson(filename):
if os.path.exists(filename):
with open(filename, "r") as f:
dataset_json = json.load(f)
classes = dataset_json["labels"]
img_num = dataset_json["numTraining"]
if "pixel_size" in dataset_json.keys():
pix_size = dataset_json["pixel_size"]
else:
pix_size = None
return classes, pix_size, img_num
else:
return None, None, None
def window_size_change():
# Update items anchored to side of window
dpg.set_item_pos("logo_img", pos=(10.0, dpg.get_viewport_height() - 40.0 - logo_dims[0]))
dpg.set_item_pos("logo_text", pos=(10.0 + logo_dims[1] / 2 - (40), dpg.get_viewport_height() - 40.0 - logo_dims[0] / 2))
dpg.set_item_pos("version_text", pos=(dpg.get_viewport_width() - 100, 10.0))
def cancel_callback():
pass
def mouse_click(sender, app_data):
mouse_coords = np.array(dpg.get_plot_mouse_pos())
mouse_coords_global = dpg.get_mouse_pos(local=False) # need global coords, because plot coords give last value at edge of plot when clicking outside of plot
# Get active tab
tab_label = dpg.get_item_label(dpg.get_value("tabbar"))
# Left mouse button functions
if dpg.is_mouse_button_down(dpg.mvMouseButton_Left):
# Send to tab specific function
if tab_label == "Inference":
inf_mouse_click_left(mouse_coords, mouse_coords_global)
elif tab_label == "Inspection":
ins_mouse_click_left(mouse_coords, mouse_coords_global)
# Right mouse button functions
elif dpg.is_mouse_button_down(dpg.mvMouseButton_Right):
if tab_label == "Inspection":
ins_mouse_click_right(mouse_coords, mouse_coords_global)
##### Inference functions
def inf_loadMap(sender, app_data):
global file_path, data, meta_data, mont_shape, pix_size, pix_size_model, inf_flip_map
file_path = sorted(glob.glob(app_data["file_path_name"]))[0]
# Get list of mrc and map files for next map selection
map_list = glob.glob(os.path.join(os.path.dirname(file_path), "*.mrc"))
map_list.extend(glob.glob(os.path.join(os.path.dirname(file_path), "*.map")))
map_list = sorted(map_list)
# Delete buttons if present and reorganize menu
if dpg.does_item_exist("inf_mont"): dpg.delete_item("inf_mont")
if dpg.does_item_exist("inf_mont_but"): dpg.delete_item("inf_mont_but")
dpg.set_item_label("inf_plot", os.path.basename(file_path) + " loading...")
dpg.set_value("inf_2", "Loading...\n\n\n\n\n")
dpg.set_value("inf_tileid", " \n")
dpg.set_value("inf_3", " \n ")
if dpg.does_item_exist("inf_pix"): dpg.delete_item("inf_pix")
dpg.add_text(default_value="", tag="inf_pix", parent="inf_left", before="inf_numimg")
if dpg.does_item_exist("inf_butexp"): dpg.delete_item("inf_butexp")
dpg.add_text(default_value="", tag="inf_butexp", parent="inf_left", before="inf_numimg")
# Delete any previous maps from texture registry
if "data" in globals():
if dpg.does_item_exist("inf_borderplot_yel"): dpg.delete_item("inf_borderplot_yel")
for i in range(data.shape[2]):
if dpg.does_item_exist("inf_imgplot" + str(i)): dpg.delete_item("inf_imgplot" + str(i))
#if dpg.does_item_exist("inf_img" + str(i)): dpg.delete_item("inf_img" + str(i))
if dpg.does_item_exist("inf_borderplot_whi" + str(i)): dpg.delete_item("inf_borderplot_whi" + str(i))
if dpg.does_item_exist("inf_tex") and not dynamic_textures:
print("NOTE: Deleting texture registry. If GUI crashes here with Segmentation Fault, try setting dynamic_textures = True!")
dpg.delete_item("inf_tex")
time.sleep(0.1) # helps to reduce Segmentation fault crashes
# Load mrc file
with mrcfile.open(file_path) as mrc:
vals = np.array((np.min(mrc.data), np.max(mrc.data), round(np.mean(mrc.data), 2)), dtype=float)
print("Map statistics:")
print("Min, max, mean:", vals)
print("Cutoff:", np.quantile(mrc.data, 0.99))
data = exposure.rescale_intensity(mrc.data, in_range=(0, np.quantile(mrc.data, 0.99)), out_range=(0, 255)).astype(np.uint8)
if data.ndim < 3:
data = np.expand_dims(data, 0)
header = mrc.header
pix_size = float(mrc.voxel_size.x) / 10 # nm/px
# Save metadata
meta_data = {"original_map": {"path": file_path, "name": os.path.splitext(os.path.basename(file_path))[0], "pixel_size": round(pix_size, 3), "min_val": vals[0], "max_val": vals[1], "mean_val": vals[2], "tiles": data.shape[0], "tile_dimensions": data[0].shape}}
print("Pixel size [nm/px]:", pix_size)
if data.shape[0] == 1: mont_shape = [1, 1]
elif data.shape[0] == 4: mont_shape = [2, 2]
elif data.shape[0] == 6: mont_shape = [2, 3]
elif data.shape[0] == 8: mont_shape = [2, 4]
elif data.shape[0] == 9: mont_shape = [3, 3]
elif data.shape[0] == 10: mont_shape = [2, 5]
elif data.shape[0] == 12: mont_shape = [3, 4]
elif data.shape[0] == 14: mont_shape = [2, 7]
elif data.shape[0] == 15: mont_shape = [3, 5]
elif data.shape[0] == 16: mont_shape = [4, 4]
elif data.shape[0] == 18: mont_shape = [3, 6]
elif data.shape[0] == 24: mont_shape = [4, 6]
elif data.shape[0] == 25: mont_shape = [5, 5]
elif data.shape[0] == 28: mont_shape = [4, 7]
elif data.shape[0] == 30: mont_shape = [5, 6]
elif data.shape[0] == 32: mont_shape = [4, 8]
elif data.shape[0] == 36: mont_shape = [4, 9]
elif data.shape[0] == 40: mont_shape = [4, 10]
else:
print("WARNING: " + str(data.shape[0]) + " is not covered! Please enter montage dimensions manually!")
mont_shape = [1, data.shape[0]]
if "inf_flip_map" not in globals():
inf_flip_map = False
if inf_flip_map:
mont_shape = mont_shape[::-1]
meta_data["original_map"]["montage_shape"] = mont_shape
# Check pixel size of previous exports and model
_, pix_size_input, pix_size_model = inf_checkPixelSize(model_list)
if pix_size_input is not None:
pix_size_model = meta_data["pixel_size"] = pix_size_input
if pix_size_model is None:
pix_size_model = pix_size
# Generate new map
if not dpg.does_item_exist("inf_tex"):
dpg.add_texture_registry(tag="inf_tex")
for i in range(mont_shape[1]):
for j in range(mont_shape[0]):
tile = i * mont_shape[0] + j
if tile >= len(data): break
bounds = np.array([i * data.shape[2], (mont_shape[0] - (j + 1)) * data.shape[1], (i + 1) * data.shape[2], (mont_shape[0] - j) * data.shape[1]]) * pix_size / 1000
image = np.ravel(np.dstack([data[tile], data[tile], data[tile], np.full(data[tile].shape, 255)])) / 255
if not dynamic_textures:
dpg.add_static_texture(width=data.shape[2], height=data.shape[1], default_value=image, tag="inf_img" + str(tile), parent="inf_tex")
else:
if dpg.does_item_exist("inf_img" + str(tile)):
dpg.set_value("inf_img" + str(tile), image)
else:
dpg.add_dynamic_texture(width=data.shape[2], height=data.shape[1], default_value=image, tag="inf_img" + str(tile), parent="inf_tex")
dpg.add_image_series("inf_img" + str(tile), bounds_min=bounds[:2], bounds_max=bounds[2:], parent="inf_x_axis", tag="inf_imgplot" + str(tile))
dpg.fit_axis_data("inf_x_axis")
dpg.fit_axis_data("inf_y_axis")
# Make highlight border
stroke = int(np.max(data.shape) * 0.01)
border = np.zeros([data.shape[1], data.shape[2]])
border[1:stroke, :] = 1
border[-stroke:-1, :] = 1
border[:, 1:stroke] = 1
border[:, -stroke:-1] = 1
border_yel = np.ravel(np.dstack([border, 0.84 * border, 0 * border, border]))
border_whi = np.ravel(np.dstack([0.75 * border, 0.75 * border, 0.75 * border, border]))
if not dpg.does_item_exist("inf_border_yel"):
with dpg.texture_registry():
dpg.add_static_texture(width=data.shape[2], height=data.shape[1], default_value=border_yel, tag="inf_border_yel")
dpg.add_static_texture(width=data.shape[2], height=data.shape[1], default_value=border_whi, tag="inf_border_whi")
inf_outlineExportedTiles()
dpg.set_item_label("inf_plot", os.path.basename(file_path) + " [" + str(round(pix_size, 2)) + " nm/px]")
if len(map_list) > 1:
next_map_id = map_list.index(file_path) + 1
if next_map_id >= len(map_list):
next_map_id = 0
print("WARNING: Reached end of folder. Next map will start from beginning.")
if dpg.does_item_exist("inf_butnext"): dpg.delete_item("inf_butnext")
dpg.add_button(label="Load next", callback=lambda: inf_loadMap("_",{"file_path_name": map_list[next_map_id]}), tag="inf_butnext", parent="inf_load")
with dpg.group(tag="inf_mont", horizontal=True, parent="inf_left", before="inf_2"):
dpg.add_text("x")
dpg.add_input_int(tag="inf_mont_x", default_value=mont_shape[1], step=0, width=50)
dpg.add_text("y")
dpg.add_input_int(tag="inf_mont_y", default_value=mont_shape[0], step=0, width=50)
with dpg.group(tag="inf_mont_but", horizontal=True, parent="inf_left", before="inf_2"):
dpg.add_button(label="Reorder", callback=inf_reorderMap)
dpg.add_button(label="Flip", callback=inf_flipMap)
dpg.set_value("inf_2", "\n2. Select a tile")
def inf_outlineExportedTiles():
# Find any files exported from the loaded map in datasets and input
file_name = os.path.splitext(os.path.basename(file_path))[0]
file_in_datasets = glob.glob(os.path.join(NN_RAW, "**", "imagesTr", file_name + "*"))
file_in_datasets.extend(glob.glob(os.path.join(CUR_DIR, "input*", file_name + "*")))
# Highlight the tiles that were exported previously
tile_list = []
for tile_file in file_in_datasets:
tile_id = int(tile_file.split("_")[-2])
if not tile_id in tile_list: # no double highlighting
tile_list.append(tile_id)
i = tile_id // mont_shape[0]
j = mont_shape[0] - tile_id % mont_shape[0] - 1
bounds = np.array([i * data.shape[2], (j + 1) * data.shape[1], (i + 1) * data.shape[2], j * data.shape[1]]) * pix_size / 1000
if not dpg.does_item_exist("inf_borderplot_whi" + str(tile_id)):
dpg.add_image_series("inf_border_whi", bounds_min=bounds[:2], bounds_max=bounds[2:], parent="inf_x_axis", tag="inf_borderplot_whi" + str(tile_id))
"""
for file in inf_input_files:
if os.path.splitext(os.path.basename(file_path))[0] in file:
tile_id = int(file.split("_")[-2])
i = tile_id // mont_shape[0]
j = mont_shape[0] - tile_id % mont_shape[0] - 1
bounds = np.array([i * data.shape[2], (j + 1) * data.shape[1], (i + 1) * data.shape[2], j * data.shape[1]]) * pix_size / 1000
if not dpg.does_item_exist("inf_borderplot_whi" + str(tile_id)):
dpg.add_image_series("inf_border_whi", bounds_min=bounds[:2], bounds_max=bounds[2:], parent="inf_x_axis", tag="inf_borderplot_whi" + str(tile_id))
"""
def inf_reorderMap():
global meta_data
# Delete any previous maps from plot
if dpg.does_item_exist("inf_borderplot_yel"): dpg.delete_item("inf_borderplot_yel")
for i in range(100):
if dpg.does_item_exist("inf_imgplot" + str(i)): dpg.delete_item("inf_imgplot" + str(i))
if dpg.does_item_exist("inf_borderplot_whi" + str(i)): dpg.delete_item("inf_borderplot_whi" + str(i))
# Get mont_shape from text input
mont_shape[1] = dpg.get_value("inf_mont_x")
mont_shape[0] = dpg.get_value("inf_mont_y")
# Set to maximum of tile size
if mont_shape[1] >= data.shape[0]:
mont_shape[1] = data.shape[0]
dpg.set_value("inf_mont_x", mont_shape[1])
if mont_shape[0] >= data.shape[0]:
mont_shape[0] = data.shape[0]
dpg.set_value("inf_mont_y", mont_shape[0])
# Update meta data
meta_data["original_map"]["montage_shape"] = mont_shape
# Generate new map
for i in range(mont_shape[1]):
for j in range(mont_shape[0]):
tile = i * mont_shape[0] + j
bounds = np.array([i * data.shape[2], (mont_shape[0] - (j + 1)) * data.shape[1], (i + 1) * data.shape[2], (mont_shape[0] - j) * data.shape[1]]) * pix_size / 1000
if not dpg.does_item_exist("inf_img" + str(tile)): break
dpg.add_image_series("inf_img" + str(tile), bounds_min=bounds[:2], bounds_max=bounds[2:], parent="inf_x_axis", tag="inf_imgplot" + str(tile))
dpg.fit_axis_data("inf_x_axis")
dpg.fit_axis_data("inf_y_axis")
inf_outlineExportedTiles()
def inf_flipMap():
global inf_flip_map
inf_flip_map = not inf_flip_map
dpg.set_value("inf_mont_x", mont_shape[0])
dpg.set_value("inf_mont_y", mont_shape[1])
inf_reorderMap()
def inf_mouse_click_left(mouse_coords, mouse_coords_global):
global tile_id
#mouse_coords = np.array(dpg.get_plot_mouse_pos())
#mouse_coords_global = dpg.get_mouse_pos(local=False) # need global coords, because plot coords give last value at edge of plot when clicking outside of plot
if "data" in globals() and np.all(mouse_coords > 0) and mouse_coords_global[0] > 200:
mouse_coords = mouse_coords * 1000 / pix_size
x = int(mouse_coords[0] / data.shape[2])
y = int(mouse_coords[1] / data.shape[1])
if x > mont_shape[1] - 1 or y > mont_shape[0] - 1:
return
tile_id = x * mont_shape[0] + (mont_shape[0] - y - 1)
dpg.set_value("inf_tileid", "Tile: (" + str(x) + "," + str(y) + ") [" + str(tile_id) + "]")
bounds = np.array([x * data.shape[2], (y + 1) * data.shape[1], (x + 1) * data.shape[2], y * data.shape[1]]) * pix_size / 1000
if dpg.does_item_exist("inf_borderplot_yel"): dpg.delete_item("inf_borderplot_yel")
dpg.add_image_series("inf_border_yel", bounds_min=bounds[:2], bounds_max=bounds[2:], parent="inf_x_axis", tag="inf_borderplot_yel")
# Pixel size input and export button
dpg.set_value("inf_3", "\n3. Export as png")
if dpg.does_item_exist("inf_pix"): dpg.delete_item("inf_pix")
with dpg.group(tag="inf_pix", horizontal=True, parent="inf_left", before="inf_numimg"):
dpg.add_text("Pixel size:")
# Make editable only if no images have been exported
if "pixel_size" in meta_data.keys():
dpg.add_text(tag="inf_pixsize", default_value=round(pix_size_model, 3))
dpg.add_text(" [nm/px]")
else:
dpg.add_input_float(tag="inf_pixsize", default_value=round(pix_size_model, 3), min_value=pix_size, format="%.3f", step=0, width=50, label="[nm/px]")
if dpg.does_item_exist("inf_butexp"): dpg.delete_item("inf_butexp")
dpg.add_button(label="Export tile", callback=inf_exportAsPng, tag="inf_butexp", parent="inf_left", before="inf_numimg")
def inf_exportAsPng():
global inf_input_files, pix_size_model, meta_data
if not os.path.exists(os.path.join(CUR_DIR, "input")):
os.makedirs(os.path.join(CUR_DIR, "input"))
inf_input_files = []
# Check if file was already exported
file_name = os.path.join(CUR_DIR, "input", os.path.splitext(os.path.basename(file_path))[0] + "_" + str(tile_id).zfill(2) + "_0000.png")
if os.path.exists(file_name):
print("ERROR: This image was already exported.")
return
# Check if file was already used in previous dataset and same pixel size
file_in_datasets = glob.glob(os.path.join(NN_RAW, "**", "imagesTr", os.path.basename(file_name)))
if len(file_in_datasets) > 0:
for file in file_in_datasets:
dataset_path = file.split("imagesTr")[0]
_, pix_size_previous, _ = loadDatasetJson(os.path.join(dataset_path, "dataset.json"))
if pix_size_previous is not None and pix_size_previous == pix_size_model:
print("ERROR: This image was already used for training.")
return
print("WARNING: This image was already used for training, but is exported now at different pixel size.")
dpg.set_value("inf_expstatus", "Saving...")
# Rescale image to model pixel size
pix_size_model = round(float(dpg.get_value("inf_pixsize")), 3)
if pix_size != pix_size_model:
if pix_size_model < pix_size:
print("WARNING: Upscaling images is not recommended!")
image = Image.fromarray(np.uint8(transform.rescale(data[tile_id], pix_size / pix_size_model) * 255))
else:
image = Image.fromarray(data[tile_id])
# Save image
image.save(file_name)
print("Image saved: " + file_name)
# Save meta data
meta_data.update({"pixel_size": round(pix_size_model, 3), "dimensions": image.size[::-1], "tile_id": tile_id})
save_path = os.path.splitext(file_name)[0] + ".json"
with open(save_path, "w+") as f:
json.dump(meta_data, f, indent=4)
print("Meta data saved: " + save_path)
inf_input_files.append(file_name)
inf_outlineExportedTiles()
dpg.set_value("inf_expstatus", "")
dpg.set_value("inf_numimg", "Total images: " + str(len(inf_input_files)))
dpg.set_value("inf_4", "\n4. Choose model")
if dpg.does_item_exist("inf_selmod"): dpg.delete_item("inf_selmod")
if dpg.does_item_exist("inf_butmod"): dpg.delete_item("inf_butmod")
if len(model_list) > 0:
dpg.add_combo(model_list, default_value=model_list[-1], callback=inf_checkModel, tag="inf_selmod", parent="inf_left", before="inf_5")
dpg.set_value("inf_5", "\n5. Segment images")
if dpg.does_item_exist("inf_butinf"): dpg.delete_item("inf_butinf")
dpg.add_button(label="Run inference", callback=inf_inference, tag="inf_butinf", parent="inf_left", before="inf_left_final")
else:
dpg.add_button(label="Find model", callback=lambda: dpg.show_item("inf_file2"), tag="inf_butmod", parent="inf_left", before="inf_5")
def inf_importModel(sender, app_data):
model_path = app_data["file_path_name"]
shutil.copytree(model_path, "model_0")
if dpg.does_item_exist("inf_butmod"): dpg.delete_item("inf_butmod")
dpg.set_value("inf_5", "Importing model...")
# Read dataset.json and look for pixel size
check_pix_size, pix_size_input, pix_size_model = inf_checkPixelSize("model_0")
if not check_pix_size:
print("WARNING: Model pixel size is not the same as export pixel size. Please export images at model pixel size (" + str(round(pix_size_model, 3)) + " nm/px).")
# Wait for checkpoint files to exist
timeout = 0
while len(glob.glob(os.path.join(CUR_DIR, "model_0", "**", "checkpoint_final.pth"))) < 5 and len(glob.glob(os.path.join(CUR_DIR, "model_0", "**", "checkpoint_best.pth"))) < 5 and timeout < 100:
time.sleep(1)
timeout += 1
if timeout >= 100:
print("ERROR: Model could not be imported.")
return
model_list.append("model_0")
dpg.add_combo(model_list, default_value=model_list[-1], callback=inf_checkModel, tag="inf_selmod", parent="inf_left", before="inf_5")
dpg.set_value("inf_5", "\n5. Segment images")
if dpg.does_item_exist("inf_butinf"): dpg.delete_item("inf_butinf")
if not check_pix_size:
dpg.add_text("Model pixel size differs \nfrom export pixel size. \nPlease reexport images at \nthe proper pixel size.", tag="inf_butinf", color=error_color, parent="inf_left", before="inf_left_final")
else:
dpg.add_button(label="Run inference", callback=inf_inference, tag="inf_butinf", parent="inf_left", before="inf_left_final")
def inf_inference():
model_name = dpg.get_value("inf_selmod")
model_no = model_name.split("_")[-1]
# Check if checkpoint files exist
if os.path.exists(os.path.join(model_name, "fold_0", "checkpoint_final.pth")):
chk_name = "checkpoint_final.pth"
elif os.path.exists(os.path.join(model_name, "fold_0", "checkpoint_best.pth")):
chk_name = "checkpoint_best.pth"
else:
print("ERROR: Checkpoint file not found. Try using a different model!")
return
# Check if pixel sizes are consistent:
check_pix_size, *_ = inf_checkPixelSize(model_name)
if not check_pix_size:
print("ERROR: Model pixel size differs from export pixel size. Please reexport images at the proper pixel size.")
return
# Delete textures to free up GPU memory for inference
if "data" in globals():
if dpg.does_item_exist("inf_borderplot_yel"): dpg.delete_item("inf_borderplot_yel")
for i in range(data.shape[2]):
if dpg.does_item_exist("inf_imgplot" + str(i)): dpg.delete_item("inf_imgplot" + str(i))
if dpg.does_item_exist("inf_borderplot_whi" + str(i)): dpg.delete_item("inf_borderplot_whi" + str(i))
if dpg.does_item_exist("inf_tex") and not dynamic_textures:
print("NOTE: Deleting texture registry. If GUI crashes here with Segmentation Fault, try setting dynamic_textures = True!")
dpg.delete_item("inf_tex")
time.sleep(0.1) # helps to reduce Segmentation fault crashes
# Define dirs
input_dir = os.path.join(CUR_DIR, "input_" + str(model_no))
output_dir = os.path.join(CUR_DIR, "output_" + str(model_no))
model_dir = os.path.join(CUR_DIR, model_name)
# Lock input folder by renaming it
os.rename(os.path.join(CUR_DIR, "input"), input_dir)
if model_name != "":
dpg.set_value("inf_left_final", "Running inference...\nThis might take \nseveral minutes.")
subprocess.run(["nnUNetv2_predict_from_modelfolder", "-i", input_dir, "-o", output_dir, "-m", model_dir + "/", "-chk", chk_name, "-device", DEVICE])
dpg.set_value("inf_left_final", "Segmentation finished.")
else:
print("WARNING: No model selected.")
def inf_checkPixelSize(model_name):
# Get pixel size for previous exports
input_json_list = glob.glob(os.path.join(CUR_DIR, "input", "*.json"))
if len(input_json_list) > 0:
with open(input_json_list[0], "r") as f:
pix_size_input = json.load(f)["pixel_size"]
else:
pix_size_input = None
# Check if argument is model list or name
if isinstance(model_name, list):
if len(model_name) > 0:
model_name = model_list[-1]
else:
return True, pix_size_input, None
# Get model pixel size
_, pix_size_model, _ = loadDatasetJson(os.path.join(CUR_DIR, model_name, "dataset.json"))
# Check compatibility
if pix_size_input is not None and pix_size_model is not None and pix_size_input != pix_size_model:
print("Model pixel size [nm/px]: " + str(pix_size_model))
print("Input pixel size [nm/px]: " + str(pix_size_input))
return False, pix_size_input, pix_size_model
else:
return True, pix_size_input, pix_size_model
def inf_checkModel():
model_name = dpg.get_value("inf_selmod")
check_pix_size, *_ = inf_checkPixelSize(model_name)
if dpg.does_item_exist("inf_butinf"): dpg.delete_item("inf_butinf")
if not check_pix_size:
dpg.add_text("Model pixel size differs \nfrom export pixel size. \nPlease reexport images at \nthe proper pixel size.", tag="inf_butinf", color=error_color, parent="inf_left", before="inf_left_final")
else:
dpg.add_button(label="Run inference", callback=inf_inference, tag="inf_butinf", parent="inf_left", before="inf_left_final")
##### Inspection functions
def ins_loadMap(sender, app_data):
global dims, binning, file_path, seg_path, image_orig, seg_folder, pix_size_png
file_path = sorted(glob.glob(os.path.splitext(app_data["file_path_name"])[0] + ".png"))[0]
dpg.set_item_label("ins_plot", os.path.basename(file_path) + " loading...")
# Check for metadata file to get pixel size
if os.path.exists(os.path.splitext(file_path)[0] + ".json"):
with open(os.path.splitext(file_path)[0] + ".json", "r") as f:
meta_data = json.load(f)
pix_size_png = float(meta_data["pixel_size"])
dpg.set_item_label("ins_x_axis", "x [µm]")
dpg.set_item_label("ins_y_axis", "y [µm]")
else:
pix_size_png = 1000
dpg.set_item_label("ins_x_axis", "x [px]")
dpg.set_item_label("ins_y_axis", "y [px]")
image = np.array(Image.open(file_path)).astype(float) / 255
image_orig = copy.deepcopy(image) # make copy for export
dims = [image.shape[1], image.shape[0]]
binning = 1
if np.max(dims) > 16384: # hard limit for texture sizes on apple GPU
print("WARNING: Map is too large and will be binned by 2 (for display only)! Export will be unbinned.")
image = image[::2, ::2]
dims = [image.shape[1], image.shape[0]]
binning = 2
image = np.ravel(np.dstack([image, image, image, np.ones(image.shape)]))
if dpg.does_item_exist("ins_img"): dpg.delete_item("ins_img")
if dpg.does_item_exist("ins_imgplot"): dpg.delete_item("ins_imgplot")
with dpg.texture_registry():
dpg.add_static_texture(width=dims[0], height=dims[1], default_value=image, tag="ins_img")
dpg.add_image_series("ins_img", bounds_min=(0, 0), bounds_max=np.array(dims) * pix_size_png / 1000 * binning, parent="ins_x_axis", tag="ins_imgplot")
dpg.fit_axis_data("ins_x_axis")
dpg.fit_axis_data("ins_y_axis")
seg_folder = False
if "_0000.png" in file_path:
seg_path = file_path.split("_0000.png")[0] + ".png"
if not os.path.exists(seg_path) and "input" in seg_path:
seg_path = seg_path.split("input")[0] + "output" + seg_path.split("input")[1]
seg_folder = True
if not os.path.exists(seg_path) and "imagesTr" in seg_path:
seg_path = seg_path.split("imagesTr")[0] + "labelsTr" + seg_path.split("imagesTr")[1]
seg_folder = True
else:
seg_path = os.path.splitext(file_path)[0] + "_seg.png"
if os.path.exists(seg_path):
ins_loadSeg(seg_path)
else:
seg_folder = False
print("WARNING: Segmentation was not found.")
dpg.set_item_label("ins_plot", os.path.basename(file_path))
def ins_loadSeg(filename):
global seg, seg_orig
seg = np.array(Image.open(filename))
dims = seg.shape
if binning == 2: # hard limit for texture sizes on apple GPU
seg_orig = copy.deepcopy(seg)
dims = (dims[0] // 2, dims[1] // 2)
seg = seg[::2, ::2]
ins_loadClasses()
ins_loadMask()
# Create mask from segmentation and selected classes
def ins_makeMask(seg, class_names):
mask = np.zeros(seg.shape)
for name in class_names:
mask[seg == CLASSES[name]] = 1
return mask
def ins_loadMask(sender=None, class_list=[]):
if not isinstance(class_list, list) or len(class_list) == 0:
class_list = [dpg.get_value("ins_class")]
mask = ins_makeMask(seg, class_list)
mask = np.ravel(np.dstack([mask, np.zeros(mask.shape), np.zeros(mask.shape), mask * np.full(mask.shape, 0.25)]))
dpg.delete_item("seg")
dpg.delete_item("segplot")
with dpg.texture_registry():
dpg.add_static_texture(width=dims[0], height=dims[1], default_value=mask, tag="seg")
dpg.add_image_series("seg", bounds_min=(0, 0), bounds_max=np.array(dims) * pix_size_png / 1000 * binning, parent="ins_x_axis", tag="segplot")
def ins_loadClasses():
global CLASSES
json_file = os.path.join(os.path.dirname(seg_path), "dataset.json")
if not os.path.exists(json_file):
json_file = os.path.join(os.path.dirname(file_path), "dataset.json")
if not os.path.exists(json_file):
json_file = os.path.join(os.path.dirname(seg_path), os.pardir, "dataset.json")
if not os.path.exists(json_file):
json_file = "dataset.json"
if not os.path.exists(json_file):
raise FileNotFoundError("A dataset.json file from the segmentation model is needed to load the classes!")
CLASSES, *_ = loadDatasetJson(json_file)
dpg.set_value("ins_2", "\n2. Inspect segmentation")
dpg.set_value("ins_cls", "Classes:")
if dpg.does_item_exist("ins_class"): dpg.delete_item("ins_class")
#dpg.add_radio_button([key for key in CLASSES.keys()], horizontal=False, default_value=list(CLASSES.keys())[0], callback=loadMask, tag="class", parent="ins_left", before="ins_3")
dpg.add_combo([key for key in CLASSES.keys()], default_value=list(CLASSES.keys())[0], callback=ins_loadMask, tag="ins_class", parent="ins_left", before="ins_3")
dpg.set_value("ins_3", "\n3. Export classes as layers")
if dpg.does_item_exist("ins_butexp"): dpg.delete_item("ins_butexp")
dpg.add_button(label="Export map", callback=ins_exportAsLayers, tag="ins_butexp", parent="ins_left", before="ins_4")
if dpg.does_item_exist("ins_butexpfol"): dpg.delete_item("ins_butexpfol")
if seg_folder:
dpg.add_button(label="Export folder", callback=ins_exportFolderAsLayers, tag="ins_butexpfol", parent="ins_left", before="ins_4")
if dpg.does_item_exist("exportbar"): dpg.delete_item("exportbar")
if not dpg.does_item_exist("ins_butnap") and napari_installed:
dpg.add_button(label="Open in Napari", callback=openNapari, tag="ins_butnap", parent="ins_left", before="ins_final")
if show_tgt_selection:
if FUNC_IMPORT and mic_params is not None and tgt_params is not None:
# Show targets if point file exists
ins_showTargets(load_from_file=True)
if not dpg.does_item_exist("ins_tsmenu"):
with dpg.collapsing_header(label="Target selection", tag="ins_tsmenu", parent="ins_left", before="ins_final"):
# Targeting settings
dpg.add_input_text(label="Target classes", tag="target_list", default_value=",".join(tgt_params.target_list), width=100)
dpg.add_input_text(label="Avoid classes", tag="avoid_list", default_value=",".join(tgt_params.penalty_list), width=100)
dpg.add_input_float(label="Score threshold", tag="target_score_threshold", default_value=tgt_params.threshold, format="%.2f", step=0, width=50)
dpg.add_input_float(label="Penalty weight", tag="penalty_weight", default_value=tgt_params.penalty, format="%.2f", step=0, width=50)
dpg.add_input_int(label="Max. tilt angle", tag="max_tilt", default_value=tgt_params.max_tilt, step=0, width=50)
dpg.add_input_float(label="Image shift limit", tag="IS_limit", default_value=mic_params.IS_limit, format="%.2f", step=0, width=50)
dpg.add_checkbox(label="Sparse targets", tag="sparse_targets", default_value=tgt_params.sparse)
dpg.add_checkbox(label="Target edge", tag="target_edge", default_value=tgt_params.edge)
dpg.add_checkbox(label="Extra tracking", tag="extra_tracking", default_value=tgt_params.extra_track)
with dpg.group(horizontal=True, tag="ins_butgrp"):
dpg.add_button(label="Select targets", callback=ins_runTargetSelection, tag="ins_butts")
else:
dpg.set_value("ins_final", "Target selection not possible.")
def ins_exportAsLayers():
ins_exportFolderAsLayers(folder=False)
def ins_exportFolderAsLayers(folder=True, color=True):
if folder:
seg_list = sorted(glob.glob(os.path.join(os.path.dirname(seg_path), "*.png")))
else:
seg_list = [seg_path]
if dpg.does_item_exist("exportbar"): dpg.delete_item("exportbar")
dpg.add_progress_bar(default_value=0, width=-1, overlay="0%", tag="exportbar", parent="ins_left", before="ins_4")
for s, seg_file in enumerate(seg_list):
if folder:
img_file = os.path.splitext(seg_file.split("output")[0] + "input" + seg_file.split("output")[1])[0] + "_0000.png"
else:
img_file = file_path
if os.path.exists(img_file):
img = np.array(Image.open(img_file))
else:
print("ERROR: MM map (" + os.path.basename(img_file) + ") not found.")
return
seg = np.array(Image.open(seg_file))
base_name = os.path.splitext(os.path.basename(seg_file))[0].split("_seg")[0]
base_path = os.path.dirname(seg_file)
layer_path = os.path.join(base_path, base_name)
if not os.path.exists(layer_path):
os.makedirs(layer_path)
for i, l in enumerate(CLASSES.keys()):
progress = (s * (len(CLASSES) + 1) + i) / (len(seg_list) * (len(CLASSES) + 1))
dpg.set_value("exportbar", progress)
dpg.configure_item("exportbar", overlay=f"{round(progress * 100)}%")
if CLASSES[l] == 0:
continue
label = np.zeros(seg.shape)
label[seg == CLASSES[l]] = 255
if not color:
label = np.dstack([label // 1.5, label // 8, label // 8, label])
else:
label = label / 255
if l in class_colors.keys():
layer_color = class_colors[l]
else:
layer_color = (255 // 1.5, 255 // 8, 255 // 8, 255) # dark red
label = np.dstack([label, label, label, label]) * layer_color
save_path = os.path.join(layer_path, str(len(CLASSES) - CLASSES[l]).zfill(2) + "_" + l + ".png")
Image.fromarray(np.uint8(label)).save(save_path)
progress = ((s * (len(CLASSES) + 1)) + len(CLASSES)) / (len(seg_list) * (len(CLASSES) + 1))
dpg.set_value("exportbar", progress)
dpg.configure_item("exportbar", overlay=f"{round(progress * 100)}%")
img = Image.fromarray(np.uint8(img))
save_path = os.path.join(layer_path, base_name + ".png")
img.convert("RGB").save(save_path)
print("Layers saved: " + layer_path)
dpg.set_value("exportbar", 1)
dpg.configure_item("exportbar", overlay=f"{100}%")
dpg.delete_item("exportbar")
dpg.set_value("ins_4", "\n4. Edit layers externally")
def ins_runTargetSelection():
global tgt_params, mic_params, MM_model
if FUNC_IMPORT:
map_dir = os.path.dirname(file_path)
map_name = os.path.splitext(os.path.basename(file_path))[0]
# Check for existing point files and delete them
point_files = sorted(glob.glob(os.path.join(map_dir, map_name + "_points*.json")))
for file in point_files:
os.remove(file)
# Update tgt params
tgt_params.target_list = [cat.strip() for cat in dpg.get_value("target_list").split(",")]
tgt_params.penalty_list = [cat.strip() for cat in dpg.get_value("avoid_list").split(",")]
tgt_params.parseLists(MM_model)
tgt_params.checkLists(MM_model)
tgt_params.sparse = dpg.get_value("sparse_targets")
tgt_params.edge = dpg.get_value("target_edge")
tgt_params.penalty = dpg.get_value("penalty_weight")
tgt_params.threshold = dpg.get_value("target_score_threshold")
tgt_params.max_tilt = dpg.get_value("max_tilt")
tgt_params.extra_track = dpg.get_value("extra_tracking")
mic_params.IS_limit = dpg.get_value("IS_limit")
# Load overlay
ins_loadMask(None, tgt_params.target_list)
# Run target selection
space_ext.runTargetSelection(map_dir, map_name, tgt_params, mic_params, MM_model, alt_seg_path=seg_path, save_final_plot=False)
ins_showTargets(load_from_file=True)
# Delete save button (only activated when point was dragged)
if dpg.does_item_exist("ins_buttsexp"): dpg.delete_item("ins_buttsexp")
def ins_showTargets(load_from_file=False):
global tgt_overlay_dims, target_areas
map_dir = os.path.dirname(file_path)
map_name = os.path.splitext(os.path.basename(file_path))[0]
if load_from_file:
# Load json data for all point files
point_files = sorted(glob.glob(os.path.join(map_dir, map_name + "_points*.json")))
if len(point_files) > 0:
target_areas = []
for file in point_files:
# Load json data
with open(file, "r") as f:
target_areas.append(json.load(f, object_hook=utils.revertTaggedString))
else:
return
# Delete previous target plots
for i in range(1000):
if dpg.does_item_exist("ins_tgtplot" + str(i)): dpg.delete_item("ins_tgtplot" + str(i))
if dpg.does_item_exist("ins_tgtdrag" + str(i)): dpg.delete_item("ins_tgtdrag" + str(i))
if dpg.does_item_exist("ins_tgtoverlayplot" + str(i)):
dpg.delete_item("ins_tgtoverlayplot" + str(i))
else:
break
if not dpg.does_item_exist("ins_tgtoverlay"):
# Generate target overlay texture
rec_dims = np.array(tgt_params.weight.shape)
tgt_overlay = np.zeros([int(MM_model.beam_diameter), int(MM_model.beam_diameter / np.cos(np.radians(tgt_params.max_tilt)))])
canvas = Image.fromarray(tgt_overlay).convert('RGB')
draw = ImageDraw.Draw(canvas)
draw.ellipse((0, 0, tgt_overlay.shape[1] - 1, tgt_overlay.shape[0] - 1), outline="#ffd700", width=10)
canvas = canvas.rotate(-mic_params.view_ta_rotation, expand=True)
rect = ((canvas.width - rec_dims[1]) // 2, (canvas.height - rec_dims[0]) // 2, (canvas.width + rec_dims[1]) // 2, (canvas.height + rec_dims[0]) // 2)
draw = ImageDraw.Draw(canvas)
draw.rectangle(rect, outline="#578abf", width=10)
tgt_overlay = np.array(canvas).astype(float) / 255
draw.rectangle(rect, outline="#c92b27", width=10)
trk_overlay = np.array(canvas).astype(float) / 255
tgt_overlay_dims = np.array(tgt_overlay.shape)[:2]
alpha = np.zeros(tgt_overlay.shape[:2])
alpha[np.sum(tgt_overlay, axis=-1) > 0] = 1
tgt_overlay_image = np.ravel(np.dstack([tgt_overlay, alpha]))
trk_overlay_image = np.ravel(np.dstack([trk_overlay, alpha]))
with dpg.texture_registry():
dpg.add_static_texture(width=int(tgt_overlay_dims[1]), height=int(tgt_overlay_dims[0]), default_value=tgt_overlay_image, tag="ins_tgtoverlay")
dpg.add_static_texture(width=int(tgt_overlay_dims[1]), height=int(tgt_overlay_dims[0]), default_value=trk_overlay_image, tag="ins_trkoverlay")
tgt_counter = 0
for t, target_area in enumerate(target_areas):
if len(target_area["points"]) == 0: continue
# Transform coords to plot
x_vals = target_area["points"][:, 1] * pix_size_png / 1000
y_vals = dims[1] * binning - target_area["points"][:, 0] * pix_size_png / 1000
dpg.add_scatter_series(x_vals, y_vals, tag="ins_tgtplot" + str(t), parent="ins_x_axis")
# Load color if not out of bounds of prepared themes
if dpg.does_item_exist("scatter_theme" + str(t)):
dpg.bind_item_theme("ins_tgtplot" + str(t), "scatter_theme" + str(t))
for p in range(len(x_vals)):
# add draggable point
dpg.add_drag_point(label="tgt_" + str(p + 1).zfill(3), user_data="pt_" + str(t) + "_" + str(p), tag="ins_tgtdrag" + str(tgt_counter), color=cluster_colors[t % len(cluster_colors)], default_value=(x_vals[p], y_vals[p]), callback=ins_dragPointUpdate, parent="ins_plot")
scaled_overlay_dims = tgt_overlay_dims * pix_size_png / 1000
bounds_min = (x_vals[p] - scaled_overlay_dims[1] // 2, y_vals[p] - scaled_overlay_dims[0] // 2)
bounds_max = (x_vals[p] + scaled_overlay_dims[1] // 2, y_vals[p] + scaled_overlay_dims[0] // 2)
if p == 0:
dpg.add_image_series("ins_trkoverlay", bounds_min=bounds_min, bounds_max=bounds_max, parent="ins_x_axis", tag="ins_tgtoverlayplot" + str(tgt_counter))
else:
dpg.add_image_series("ins_tgtoverlay", bounds_min=bounds_min, bounds_max=bounds_max, parent="ins_x_axis", tag="ins_tgtoverlayplot" + str(tgt_counter))
tgt_counter += 1
def ins_dragPointUpdate(sender, app_data, user_data):
coords = dpg.get_value(sender)[:2]
if dpg.does_item_exist("ins_tempplot"): dpg.delete_item("ins_tempplot")
dpg.add_scatter_series([coords[0]], [coords[1]], tag="ins_tempplot", parent="ins_x_axis")
dpg.bind_item_theme("ins_tempplot", "scatter_theme3") # red theme
def ins_tgtUpdate():
# Only execute when targets are loaded
if "target_areas" not in globals():
return
# Go through all points
update = False
for i in range(1000):
# Check if point exists
if dpg.does_item_exist("ins_tgtdrag" + str(i)):
# Get coords from drag point value
coords = np.array(dpg.get_value("ins_tgtdrag" + str(i))[:2])
# Get area and point IDs from user data embedded in drag point
point_id = np.array(dpg.get_item_user_data(("ins_tgtdrag" + str(i))).split("_")[1:], dtype=int)
# Transform points to plot points for comparison
old_coords = np.array([target_areas[point_id[0]]["points"][point_id[1]][1] * pix_size_png / 1000, dims[1] * binning - target_areas[point_id[0]]["points"][point_id[1]][0] * pix_size_png / 1000])
# Go to next points if coords have not changed
if np.all(coords == old_coords):
continue
else:
# Update coords if they have changed
target_areas[point_id[0]]["points"][point_id[1]][1] = coords[0] / pix_size_png * 1000
target_areas[point_id[0]]["points"][point_id[1]][0] = (dims[1] * binning - coords[1]) / pix_size_png * 1000
update = True
else:
break
# Re-plot targets if any coords have changed
if update:
ins_showTargets()
if not dpg.does_item_exist("ins_buttsexp"):
dpg.add_button(label="Save", callback=ins_exportPoints, tag="ins_buttsexp", parent="ins_butgrp")
# Export points
def ins_exportPoints():
map_dir = os.path.dirname(file_path)
map_name = os.path.splitext(os.path.basename(file_path))[0]
if len(target_areas) > 0:
for t, target_area in enumerate(target_areas):
with open(os.path.join(map_dir, map_name + "_points" + str(t) + ".json"), "w+") as f:
json.dump(target_area, f, indent=4, default=utils.convertToTaggedString)
else:
# Write empty points file to ensure empty targets file is written and map is considered processed
with open(os.path.join(map_dir, map_name + "_points.json"), "w+") as f:
json.dump({"points": []}, f)
# Delete save button (only activated when point was dragged)
if dpg.does_item_exist("ins_buttsexp"): dpg.delete_item("ins_buttsexp")
# Add points by shift + left clicking
def ins_mouse_click_left(mouse_coords, mouse_coords_global):
# Check if mouse click was in plot range and if Shift is pressed (to not double signal when dragging)
if (dpg.is_key_down(dpg.mvKey_LShift) or dpg.is_key_down(dpg.mvKey_RShift)) and "target_areas" in globals() and np.all(mouse_coords > 0) and mouse_coords_global[0] > 200:
# Transform mouse coords to px coords
img_coords = np.array([(dims[1] * binning - mouse_coords[1]) / pix_size_png * 1000, mouse_coords[0] / pix_size_png * 1000])
# Get camera dims
rec_dims = np.array(tgt_params.weight.shape)
# Check if coords are out of bounds
if not rec_dims[0] <= img_coords[0] < dims[1] * binning - rec_dims[0] or not rec_dims[1] <= img_coords[1] < dims[0] * binning - rec_dims[1]:
return
if len(target_areas[0]["points"]) > 0:
# Check if coords are too close to existing point (also allows for dragging to work without creating new point)
for target_area in target_areas:
for point in target_area["points"]:
if np.linalg.norm(point - img_coords) < np.min(rec_dims):
print("WARNING: Target is too close to existing target! It will not be added.")
return
# Figure out which target area tracking targets is closest
track_points = [target_area["points"][0] for target_area in target_areas]
closest_area = np.argmin(np.linalg.norm(track_points - img_coords, axis=1))
# Add point
target_areas[closest_area]["points"] = np.vstack([target_areas[closest_area]["points"], img_coords])
target_areas[closest_area]["scores"] = np.append(target_areas[closest_area]["scores"], [1])
else:
target_areas[0]["points"] = img_coords[np.newaxis, :]
target_areas[0]["scores"] = np.array([1])
print("NOTE: Added new target!")
ins_showTargets()
if not dpg.does_item_exist("ins_buttsexp"):
dpg.add_button(label="Save", callback=ins_exportPoints, tag="ins_buttsexp", parent="ins_butgrp")
#Call SPACEtomo_runNapari script
def openNapari():
# If layer folder already exists, open folder
map_name = os.path.splitext(os.path.basename(file_path))[0].split("_0000")[0]
folder_name = os.path.join(os.path.dirname(seg_path), map_name)
if os.path.exists(os.path.join(folder_name, map_name + ".png")):
print("NOTE: Opening exported layer folder in Napari. Closing Napari will overwrite layers.")
subprocess.Popen([sys.executable, os.path.join(SPACE_DIR, "SPACEtomo_runNapari.py"), "--folder", folder_name])