spacr 0.1.1__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spacr/__init__.py +12 -12
- spacr/annotate_app.py +258 -99
- spacr/annotate_app_v2.py +163 -4
- spacr/app_annotate.py +539 -0
- spacr/app_classify.py +201 -0
- spacr/app_make_masks.py +929 -0
- spacr/app_make_masks_v2.py +688 -0
- spacr/app_mask.py +251 -0
- spacr/app_measure.py +248 -0
- spacr/classify_app.py +201 -0
- spacr/gui.py +23 -20
- spacr/gui_annotate.py +145 -0
- spacr/gui_classify_app.py +20 -6
- spacr/gui_make_masks_app.py +927 -0
- spacr/gui_make_masks_app_v2.py +688 -0
- spacr/gui_mask_app.py +8 -4
- spacr/gui_measure_app.py +15 -5
- spacr/gui_utils.py +110 -20
- spacr/make_masks_app.py +929 -0
- spacr/make_masks_app_v2.py +688 -0
- spacr/mask_app.py +239 -915
- spacr/measure_app.py +246 -0
- spacr/sim_app.py +0 -0
- {spacr-0.1.1.dist-info → spacr-0.1.12.dist-info}/METADATA +5 -1
- spacr-0.1.12.dist-info/RECORD +54 -0
- {spacr-0.1.1.dist-info → spacr-0.1.12.dist-info}/entry_points.txt +3 -3
- spacr-0.1.1.dist-info/RECORD +0 -40
- {spacr-0.1.1.dist-info → spacr-0.1.12.dist-info}/LICENSE +0 -0
- {spacr-0.1.1.dist-info → spacr-0.1.12.dist-info}/WHEEL +0 -0
- {spacr-0.1.1.dist-info → spacr-0.1.12.dist-info}/top_level.txt +0 -0
spacr/__init__.py
CHANGED
@@ -12,13 +12,13 @@ from . import sim
|
|
12
12
|
from . import sequencing
|
13
13
|
from . import timelapse
|
14
14
|
from . import deep_spacr
|
15
|
-
from . import
|
16
|
-
from . import annotate_app_v2
|
15
|
+
from . import app_annotate
|
17
16
|
from . import gui_utils
|
18
|
-
from . import
|
19
|
-
from . import
|
20
|
-
from . import
|
21
|
-
from . import
|
17
|
+
from . import app_make_masks
|
18
|
+
from . import app_make_masks_v2
|
19
|
+
from . import app_mask
|
20
|
+
from . import app_measure
|
21
|
+
from . import app_classify
|
22
22
|
from . import logger
|
23
23
|
|
24
24
|
|
@@ -33,13 +33,13 @@ __all__ = [
|
|
33
33
|
"sequencing"
|
34
34
|
"timelapse",
|
35
35
|
"deep_spacr",
|
36
|
-
"
|
37
|
-
"annotate_app_v2",
|
36
|
+
"app_annotate",
|
38
37
|
"gui_utils",
|
39
|
-
"
|
40
|
-
"
|
41
|
-
"
|
42
|
-
"
|
38
|
+
"app_make_masks",
|
39
|
+
"app_make_masks_v2",
|
40
|
+
"app_mask",
|
41
|
+
"app_measure",
|
42
|
+
"app_classify",
|
43
43
|
"logger"
|
44
44
|
]
|
45
45
|
|
spacr/annotate_app.py
CHANGED
@@ -1,25 +1,21 @@
|
|
1
|
+
import sqlite3
|
1
2
|
from queue import Queue
|
2
3
|
from tkinter import Label
|
3
4
|
import tkinter as tk
|
5
|
+
from tkinter import ttk
|
4
6
|
import os, threading, time, sqlite3
|
5
7
|
import numpy as np
|
6
8
|
from PIL import Image, ImageOps
|
7
9
|
from concurrent.futures import ThreadPoolExecutor
|
8
10
|
from PIL import ImageTk
|
9
|
-
from IPython.display import display, HTML
|
10
|
-
import tkinter as tk
|
11
|
-
from tkinter import ttk
|
12
|
-
from ttkthemes import ThemedTk
|
13
11
|
from skimage.exposure import rescale_intensity
|
14
|
-
import
|
15
|
-
import
|
16
|
-
|
17
|
-
from .logger import log_function_call
|
12
|
+
from IPython.display import display, HTML
|
13
|
+
from tkinter import font as tkFont
|
18
14
|
|
19
|
-
from .gui_utils import ScrollableFrame,
|
15
|
+
from .gui_utils import ScrollableFrame, CustomButton, set_dark_style, set_default_font, style_text_boxes, create_menu_bar
|
20
16
|
|
21
17
|
class ImageApp:
|
22
|
-
def __init__(self, root, db_path, src, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate', normalize=False, percentiles=(1,99)):
|
18
|
+
def __init__(self, root, db_path, src, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate', normalize=False, percentiles=(1,99), measurement=None, threshold=None):
|
23
19
|
"""
|
24
20
|
Initializes an instance of the ImageApp class.
|
25
21
|
|
@@ -34,7 +30,10 @@ class ImageApp:
|
|
34
30
|
- image_size (tuple): The size of the displayed images.
|
35
31
|
- annotation_column (str): The column name for image annotations in the database.
|
36
32
|
- normalize (bool): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
|
33
|
+
- measurement (str): The measurement column to filter by.
|
34
|
+
- threshold (float): The threshold value for filtering the measurement column.
|
37
35
|
"""
|
36
|
+
|
38
37
|
self.root = root
|
39
38
|
self.db_path = db_path
|
40
39
|
self.src = src
|
@@ -55,20 +54,115 @@ class ImageApp:
|
|
55
54
|
self.update_queue = Queue()
|
56
55
|
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
57
56
|
self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
|
57
|
+
self.measurement = measurement
|
58
|
+
self.threshold = threshold
|
59
|
+
|
60
|
+
self.filtered_paths_annotations = []
|
61
|
+
self.prefilter_paths_annotations()
|
58
62
|
|
59
63
|
self.db_update_thread = threading.Thread(target=self.update_database_worker)
|
60
64
|
self.db_update_thread.start()
|
61
|
-
|
65
|
+
|
62
66
|
for i in range(grid_rows * grid_cols):
|
63
67
|
label = Label(root)
|
64
68
|
label.grid(row=i // grid_cols, column=i % grid_cols)
|
65
69
|
self.labels.append(label)
|
66
70
|
|
71
|
+
def prefilter_paths_annotations(self):
|
72
|
+
"""
|
73
|
+
Pre-filters the paths and annotations based on the specified measurement and threshold.
|
74
|
+
"""
|
75
|
+
from .io import _read_and_join_tables
|
76
|
+
from .utils import is_list_of_lists
|
77
|
+
|
78
|
+
if self.measurement and self.threshold is not None:
|
79
|
+
df = _read_and_join_tables(self.db_path)
|
80
|
+
df[self.annotation_column] = None
|
81
|
+
before = len(df)
|
82
|
+
|
83
|
+
if is_list_of_lists(self.measurement):
|
84
|
+
if isinstance(self.threshold, list) or is_list_of_lists(self.threshold):
|
85
|
+
if len(self.measurement) == len(self.threshold):
|
86
|
+
for idx, var in enumerate(self.measurement):
|
87
|
+
df = df[df[var[idx]] > self.threshold[idx]]
|
88
|
+
after = len(df)
|
89
|
+
elif len(self.measurement) == len(self.threshold)*2:
|
90
|
+
th_idx = 0
|
91
|
+
for idx, var in enumerate(self.measurement):
|
92
|
+
if idx % 2 != 0:
|
93
|
+
th_idx += 1
|
94
|
+
thd = self.threshold
|
95
|
+
if isinstance(thd, list):
|
96
|
+
thd = thd[0]
|
97
|
+
df[f'threshold_measurement_{idx}'] = df[self.measurement[idx]]/df[self.measurement[idx+1]]
|
98
|
+
print(f"mean threshold_measurement_{idx}: {np.mean(df['threshold_measurement'])}")
|
99
|
+
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
100
|
+
df = df[df[f'threshold_measurement_{idx}'] > thd]
|
101
|
+
after = len(df)
|
102
|
+
elif isinstance(self.measurement, list):
|
103
|
+
df['threshold_measurement'] = df[self.measurement[0]]/df[self.measurement[1]]
|
104
|
+
print(f"mean threshold measurement: {np.mean(df['threshold_measurement'])}")
|
105
|
+
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
106
|
+
df = df[df['threshold_measurement'] > self.threshold]
|
107
|
+
after = len(df)
|
108
|
+
self.measurement = 'threshold_measurement'
|
109
|
+
print(f'Removed: {before-after} rows, retained {after}')
|
110
|
+
else:
|
111
|
+
print(f"mean threshold measurement: {np.mean(df[self.measurement])}")
|
112
|
+
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
113
|
+
before = len(df)
|
114
|
+
if isinstance(self.threshold, str):
|
115
|
+
if self.threshold == 'q1':
|
116
|
+
self.threshold = df[self.measurement].quantile(0.1)
|
117
|
+
if self.threshold == 'q2':
|
118
|
+
self.threshold = df[self.measurement].quantile(0.2)
|
119
|
+
if self.threshold == 'q3':
|
120
|
+
self.threshold = df[self.measurement].quantile(0.3)
|
121
|
+
if self.threshold == 'q4':
|
122
|
+
self.threshold = df[self.measurement].quantile(0.4)
|
123
|
+
if self.threshold == 'q5':
|
124
|
+
self.threshold = df[self.measurement].quantile(0.5)
|
125
|
+
if self.threshold == 'q6':
|
126
|
+
self.threshold = df[self.measurement].quantile(0.6)
|
127
|
+
if self.threshold == 'q7':
|
128
|
+
self.threshold = df[self.measurement].quantile(0.7)
|
129
|
+
if self.threshold == 'q8':
|
130
|
+
self.threshold = df[self.measurement].quantile(0.8)
|
131
|
+
if self.threshold == 'q9':
|
132
|
+
self.threshold = df[self.measurement].quantile(0.9)
|
133
|
+
print(f"threshold: {self.threshold}")
|
134
|
+
|
135
|
+
df = df[df[self.measurement] > self.threshold]
|
136
|
+
after = len(df)
|
137
|
+
print(f'Removed: {before-after} rows, retained {after}')
|
138
|
+
|
139
|
+
df = df.dropna(subset=['png_path'])
|
140
|
+
if self.image_type:
|
141
|
+
before = len(df)
|
142
|
+
if isinstance(self.image_type, list):
|
143
|
+
for tpe in self.image_type:
|
144
|
+
df = df[df['png_path'].str.contains(tpe)]
|
145
|
+
else:
|
146
|
+
df = df[df['png_path'].str.contains(self.image_type)]
|
147
|
+
after = len(df)
|
148
|
+
print(f'image_type: Removed: {before-after} rows, retained {after}')
|
149
|
+
|
150
|
+
self.filtered_paths_annotations = df[['png_path', self.annotation_column]].values.tolist()
|
151
|
+
else:
|
152
|
+
conn = sqlite3.connect(self.db_path)
|
153
|
+
c = conn.cursor()
|
154
|
+
if self.image_type:
|
155
|
+
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ?", (f"%{self.image_type}%",))
|
156
|
+
else:
|
157
|
+
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list")
|
158
|
+
self.filtered_paths_annotations = c.fetchall()
|
159
|
+
conn.close()
|
160
|
+
|
67
161
|
def load_images(self):
|
68
162
|
"""
|
69
163
|
Loads and displays images with annotations.
|
70
164
|
|
71
|
-
This method retrieves image paths and annotations from a
|
165
|
+
This method retrieves image paths and annotations from a pre-filtered list,
|
72
166
|
loads the images using a ThreadPoolExecutor for parallel processing,
|
73
167
|
adds colored borders to images based on their annotations,
|
74
168
|
and displays the images in the corresponding labels.
|
@@ -79,23 +173,15 @@ class ImageApp:
|
|
79
173
|
Returns:
|
80
174
|
None
|
81
175
|
"""
|
176
|
+
|
82
177
|
for label in self.labels:
|
83
178
|
label.config(image='')
|
84
179
|
|
85
180
|
self.images = {}
|
86
|
-
|
87
|
-
conn = sqlite3.connect(self.db_path)
|
88
|
-
c = conn.cursor()
|
89
|
-
if self.image_type:
|
90
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ? LIMIT ?, ?", (f"%{self.image_type}%", self.index, self.grid_rows * self.grid_cols))
|
91
|
-
else:
|
92
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list LIMIT ?, ?", (self.index, self.grid_rows * self.grid_cols))
|
93
|
-
|
94
|
-
paths = c.fetchall()
|
95
|
-
conn.close()
|
181
|
+
paths_annotations = self.filtered_paths_annotations[self.index:self.index + self.grid_rows * self.grid_cols]
|
96
182
|
|
97
183
|
adjusted_paths = []
|
98
|
-
for path, annotation in
|
184
|
+
for path, annotation in paths_annotations:
|
99
185
|
if not path.startswith(self.src):
|
100
186
|
parts = path.split('/data/')
|
101
187
|
if len(parts) > 1:
|
@@ -209,7 +295,6 @@ class ImageApp:
|
|
209
295
|
|
210
296
|
Returns:
|
211
297
|
PIL.Image.Image: The filtered image.
|
212
|
-
|
213
298
|
"""
|
214
299
|
r, g, b = img.split()
|
215
300
|
if self.channels:
|
@@ -354,7 +439,7 @@ class ImageApp:
|
|
354
439
|
"""
|
355
440
|
Shuts down the application.
|
356
441
|
|
357
|
-
This method sets the
|
442
|
+
This method sets the terminate flag to True, clears the pending updates,
|
358
443
|
updates the database, and quits the application.
|
359
444
|
|
360
445
|
"""
|
@@ -366,10 +451,23 @@ class ImageApp:
|
|
366
451
|
self.root.destroy()
|
367
452
|
print(f'Quit application')
|
368
453
|
|
369
|
-
def
|
370
|
-
|
371
|
-
|
372
|
-
|
454
|
+
def get_annotate_default_settings(settings):
|
455
|
+
|
456
|
+
settings.setdefault('image_type', 'cell_png')
|
457
|
+
settings.setdefault('channels', ['r', 'g', 'b'])
|
458
|
+
settings.setdefault('geom', "3200x2000")
|
459
|
+
settings.setdefault('img_size', (200, 200))
|
460
|
+
settings.setdefault('rows', 10)
|
461
|
+
settings.setdefault('columns', 18)
|
462
|
+
settings.setdefault('annotation_column', 'recruited_test')
|
463
|
+
settings.setdefault('normalize', False)
|
464
|
+
settings.setdefault('percentiles', (2,98))
|
465
|
+
settings.setdefault('measurement', ['cytoplasm_channel_3_mean_intensity', 'pathogen_channel_3_mean_intensity'])
|
466
|
+
settings.setdefault('threshold', 2)
|
467
|
+
|
468
|
+
return settings
|
469
|
+
|
470
|
+
def annotate(settings):
|
373
471
|
"""
|
374
472
|
Annotates images in a database using a graphical user interface.
|
375
473
|
|
@@ -384,21 +482,26 @@ def annotate(src, image_type=None, channels=None, geom="1000x1100", img_size=(20
|
|
384
482
|
columns (int, optional): The number of columns in the image grid. Defaults to 5.
|
385
483
|
annotation_column (str, optional): The name of the annotation column in the database table. Defaults to 'annotate'.
|
386
484
|
normalize (bool, optional): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
|
485
|
+
measurement (str, optional): The measurement column to filter by.
|
486
|
+
threshold (float, optional): The threshold value for filtering the measurement column.
|
387
487
|
"""
|
488
|
+
|
489
|
+
settings = get_annotate_default_settings(settings)
|
490
|
+
src = settings['src']
|
491
|
+
|
388
492
|
db = os.path.join(src, 'measurements/measurements.db')
|
389
493
|
conn = sqlite3.connect(db)
|
390
494
|
c = conn.cursor()
|
391
495
|
c.execute('PRAGMA table_info(png_list)')
|
392
496
|
cols = c.fetchall()
|
393
|
-
if annotation_column not in [col[1] for col in cols]:
|
394
|
-
c.execute(f
|
497
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
498
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
395
499
|
conn.commit()
|
396
500
|
conn.close()
|
397
501
|
|
398
|
-
|
399
502
|
root = tk.Tk()
|
400
|
-
root.geometry(geom)
|
401
|
-
app = ImageApp(root, db, src, image_type=image_type, channels=channels, image_size=img_size, grid_rows=rows, grid_cols=columns, annotation_column=annotation_column, normalize=normalize, percentiles=percentiles)
|
503
|
+
root.geometry(settings['geom'])
|
504
|
+
app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
|
402
505
|
next_button = tk.Button(root, text="Next", command=app.next_page)
|
403
506
|
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
404
507
|
back_button = tk.Button(root, text="Back", command=app.previous_page)
|
@@ -409,103 +512,159 @@ def annotate(src, image_type=None, channels=None, geom="1000x1100", img_size=(20
|
|
409
512
|
app.load_images()
|
410
513
|
root.mainloop()
|
411
514
|
|
412
|
-
|
413
|
-
|
414
|
-
Check for duplicates in the given SQLite database.
|
515
|
+
# Global list to keep references to PhotoImage objects
|
516
|
+
global_image_refs = []
|
415
517
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
Returns:
|
420
|
-
None
|
421
|
-
"""
|
422
|
-
db_path = db
|
423
|
-
conn = sqlite3.connect(db_path)
|
424
|
-
c = conn.cursor()
|
425
|
-
c.execute('SELECT file_name, COUNT(file_name) FROM png_list GROUP BY file_name HAVING COUNT(file_name) > 1')
|
426
|
-
duplicates = c.fetchall()
|
427
|
-
for duplicate in duplicates:
|
428
|
-
file_name = duplicate[0]
|
429
|
-
count = duplicate[1]
|
430
|
-
c.execute('SELECT rowid FROM png_list WHERE file_name = ?', (file_name,))
|
431
|
-
rowids = c.fetchall()
|
432
|
-
for rowid in rowids[:-1]:
|
433
|
-
c.execute('DELETE FROM png_list WHERE rowid = ?', (rowid[0],))
|
434
|
-
conn.commit()
|
435
|
-
conn.close()
|
436
|
-
|
437
|
-
#@log_function_call
|
438
|
-
def initiate_annotation_app_root(width, height):
|
439
|
-
theme = 'breeze'
|
440
|
-
root = ThemedTk(theme=theme)
|
441
|
-
style = ttk.Style(root)
|
518
|
+
def initiate_annotation_app_root(parent_frame):
|
519
|
+
style = ttk.Style(parent_frame)
|
442
520
|
set_dark_style(style)
|
443
521
|
style_text_boxes(style)
|
444
|
-
set_default_font(
|
445
|
-
root.geometry(f"{width}x{height}")
|
446
|
-
root.title("Annotation App")
|
522
|
+
set_default_font(parent_frame, font_name="Arial", size=8)
|
447
523
|
|
448
|
-
|
524
|
+
parent_frame.configure(bg='black')
|
525
|
+
|
526
|
+
container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg='black')
|
449
527
|
container.pack(fill=tk.BOTH, expand=True)
|
450
528
|
|
451
|
-
scrollable_frame = ScrollableFrame(container, bg='
|
529
|
+
scrollable_frame = ScrollableFrame(container, bg='black')
|
452
530
|
container.add(scrollable_frame, stretch="always")
|
453
531
|
|
454
532
|
# Setup input fields
|
455
533
|
vars_dict = {
|
456
|
-
'
|
534
|
+
'src': ttk.Entry(scrollable_frame.scrollable_frame),
|
457
535
|
'image_type': ttk.Entry(scrollable_frame.scrollable_frame),
|
458
536
|
'channels': ttk.Entry(scrollable_frame.scrollable_frame),
|
459
|
-
'annotation_column': ttk.Entry(scrollable_frame.scrollable_frame),
|
460
537
|
'geom': ttk.Entry(scrollable_frame.scrollable_frame),
|
461
538
|
'img_size': ttk.Entry(scrollable_frame.scrollable_frame),
|
462
539
|
'rows': ttk.Entry(scrollable_frame.scrollable_frame),
|
463
|
-
'columns': ttk.Entry(scrollable_frame.scrollable_frame)
|
540
|
+
'columns': ttk.Entry(scrollable_frame.scrollable_frame),
|
541
|
+
'annotation_column': ttk.Entry(scrollable_frame.scrollable_frame),
|
542
|
+
'normalize': ttk.Entry(scrollable_frame.scrollable_frame),
|
543
|
+
'percentiles': ttk.Entry(scrollable_frame.scrollable_frame),
|
544
|
+
'measurement': ttk.Entry(scrollable_frame.scrollable_frame),
|
545
|
+
'threshold': ttk.Entry(scrollable_frame.scrollable_frame),
|
546
|
+
}
|
547
|
+
|
548
|
+
default_settings = {
|
549
|
+
'src': 'path',
|
550
|
+
'image_type': 'cell_png',
|
551
|
+
'channels': 'r,g,b',
|
552
|
+
'geom': "3200x2000",
|
553
|
+
'img_size': '(200, 200)',
|
554
|
+
'rows': '10',
|
555
|
+
'columns': '18',
|
556
|
+
'annotation_column': 'recruited_test',
|
557
|
+
'normalize': 'False',
|
558
|
+
'percentiles': '(2,98)',
|
559
|
+
'measurement': 'None',
|
560
|
+
'threshold': 'None'
|
464
561
|
}
|
465
562
|
|
466
563
|
# Arrange input fields and labels
|
467
564
|
row = 0
|
468
565
|
for name, entry in vars_dict.items():
|
469
|
-
ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:"
|
566
|
+
ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:",
|
567
|
+
background="black", foreground="white").grid(row=row, column=0)
|
568
|
+
entry.insert(0, default_settings[name])
|
470
569
|
entry.grid(row=row, column=1)
|
471
570
|
row += 1
|
472
571
|
|
473
572
|
# Function to be called when "Run" button is clicked
|
474
573
|
def run_app():
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
574
|
+
settings = {key: entry.get() for key, entry in vars_dict.items()}
|
575
|
+
settings['channels'] = settings['channels'].split(',')
|
576
|
+
settings['img_size'] = tuple(map(int, settings['img_size'][1:-1].split(',')))
|
577
|
+
settings['percentiles'] = tuple(map(int, settings['percentiles'][1:-1].split(',')))
|
578
|
+
settings['normalize'] = settings['normalize'].lower() == 'true'
|
579
|
+
settings['rows'] = int(settings['rows'])
|
580
|
+
settings['columns'] = int(settings['columns'])
|
581
|
+
if settings['measurement'].lower() == 'none':
|
582
|
+
settings['measurement'] = None
|
583
|
+
if settings['threshold'].lower() == 'none':
|
584
|
+
settings['threshold'] = None
|
484
585
|
|
485
|
-
#
|
486
|
-
|
586
|
+
# Clear previous content instead of destroying the root
|
587
|
+
if hasattr(parent_frame, 'winfo_children'):
|
588
|
+
for widget in parent_frame.winfo_children():
|
589
|
+
widget.destroy()
|
487
590
|
|
488
|
-
#
|
489
|
-
|
490
|
-
new_root.geometry(f"{width}x{height}")
|
491
|
-
new_root.title("Mask Application")
|
591
|
+
# Start the annotate application in the same root window
|
592
|
+
annotate_app(parent_frame, settings)
|
492
593
|
|
594
|
+
run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=run_app,
|
595
|
+
font=tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL))
|
596
|
+
run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
|
493
597
|
|
494
|
-
|
495
|
-
app_instance = annotate(db, image_type, channels, annotation_column, geom, img_size, rows, columns)
|
496
|
-
|
497
|
-
new_root.mainloop()
|
498
|
-
|
499
|
-
create_dark_mode(root, style, console_output=None)
|
598
|
+
return parent_frame
|
500
599
|
|
501
|
-
|
502
|
-
|
600
|
+
def annotate_app(parent_frame, settings):
|
601
|
+
global global_image_refs
|
602
|
+
global_image_refs.clear()
|
603
|
+
root = parent_frame.winfo_toplevel()
|
604
|
+
annotate_with_image_refs(settings, root, lambda: load_next_app(root))
|
503
605
|
|
504
|
-
|
606
|
+
def annotate_with_image_refs(settings, root, shutdown_callback):
|
607
|
+
settings = get_annotate_default_settings(settings)
|
608
|
+
src = settings['src']
|
505
609
|
|
506
|
-
|
507
|
-
|
610
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
611
|
+
conn = sqlite3.connect(db)
|
612
|
+
c = conn.cursor()
|
613
|
+
c.execute('PRAGMA table_info(png_list)')
|
614
|
+
cols = c.fetchall()
|
615
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
616
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
617
|
+
conn.commit()
|
618
|
+
conn.close()
|
619
|
+
|
620
|
+
app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
|
621
|
+
|
622
|
+
# Set the canvas background to black
|
623
|
+
root.configure(bg='black')
|
624
|
+
|
625
|
+
next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
|
626
|
+
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
627
|
+
back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
|
628
|
+
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
629
|
+
exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
|
630
|
+
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
631
|
+
|
632
|
+
app.load_images()
|
633
|
+
|
634
|
+
# Store the shutdown function and next app details in the root
|
635
|
+
root.current_app_exit_func = app.shutdown
|
636
|
+
root.next_app_func = None
|
637
|
+
root.next_app_args = ()
|
638
|
+
|
639
|
+
def load_next_app(root):
|
640
|
+
# Get the next app function and arguments
|
641
|
+
next_app_func = root.next_app_func
|
642
|
+
next_app_args = root.next_app_args
|
643
|
+
|
644
|
+
if next_app_func:
|
645
|
+
next_app_func(*next_app_args)
|
646
|
+
|
647
|
+
def gui_annotate():
|
648
|
+
root = tk.Tk()
|
649
|
+
width = root.winfo_screenwidth()
|
650
|
+
height = root.winfo_screenheight()
|
651
|
+
root.geometry(f"{width}x{height}")
|
652
|
+
root.title("Annotate Application")
|
653
|
+
|
654
|
+
# Clear previous content if any
|
655
|
+
if hasattr(root, 'content_frame'):
|
656
|
+
for widget in root.content_frame.winfo_children():
|
657
|
+
widget.destroy()
|
658
|
+
root.content_frame.grid_forget()
|
659
|
+
else:
|
660
|
+
root.content_frame = tk.Frame(root)
|
661
|
+
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
662
|
+
root.grid_rowconfigure(1, weight=1)
|
663
|
+
root.grid_columnconfigure(0, weight=1)
|
664
|
+
|
665
|
+
initiate_annotation_app_root(root.content_frame)
|
666
|
+
create_menu_bar(root)
|
508
667
|
root.mainloop()
|
509
668
|
|
510
669
|
if __name__ == "__main__":
|
511
|
-
|
670
|
+
gui_annotate()
|