spacr 0.1.63__py3-none-any.whl → 0.1.75__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/app_annotate.py +39 -524
- spacr/app_make_masks.py +30 -904
- spacr/core.py +21 -21
- spacr/deep_spacr.py +6 -6
- spacr/gui.py +4 -20
- spacr/gui_core.py +108 -123
- spacr/gui_elements.py +1180 -12
- spacr/gui_utils.py +197 -10
- spacr/gui_wrappers.py +27 -15
- spacr/measure.py +4 -4
- spacr/settings.py +333 -260
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/METADATA +1 -1
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/RECORD +17 -17
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/LICENSE +0 -0
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/WHEEL +0 -0
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/entry_points.txt +0 -0
- {spacr-0.1.63.dist-info → spacr-0.1.75.dist-info}/top_level.txt +0 -0
spacr/app_annotate.py
CHANGED
@@ -1,538 +1,53 @@
|
|
1
|
-
import sqlite3
|
2
|
-
from queue import Queue
|
3
|
-
from tkinter import Label
|
4
1
|
import tkinter as tk
|
5
|
-
from
|
6
|
-
|
7
|
-
|
8
|
-
from
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
from tkinter import font as tkFont
|
14
|
-
from tkinter import TclError
|
15
|
-
|
16
|
-
from .gui_elements import spacrFrame, spacrButton, set_dark_style, create_menu_bar, set_default_font
|
17
|
-
|
18
|
-
class ImageApp:
|
19
|
-
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):
|
20
|
-
self.root = root
|
21
|
-
self.db_path = db_path
|
22
|
-
self.src = src
|
23
|
-
self.index = 0
|
24
|
-
self.grid_rows = grid_rows
|
25
|
-
self.grid_cols = grid_cols
|
26
|
-
self.image_size = image_size
|
27
|
-
self.annotation_column = annotation_column
|
28
|
-
self.image_type = image_type
|
29
|
-
self.channels = channels
|
30
|
-
self.normalize = normalize
|
31
|
-
self.percentiles = percentiles
|
32
|
-
self.images = {}
|
33
|
-
self.pending_updates = {}
|
34
|
-
self.labels = []
|
35
|
-
self.adjusted_to_original_paths = {}
|
36
|
-
self.terminate = False
|
37
|
-
self.update_queue = Queue()
|
38
|
-
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
39
|
-
self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
|
40
|
-
self.measurement = measurement
|
41
|
-
self.threshold = threshold
|
42
|
-
|
43
|
-
self.filtered_paths_annotations = []
|
44
|
-
self.prefilter_paths_annotations()
|
45
|
-
|
46
|
-
self.db_update_thread = threading.Thread(target=self.update_database_worker)
|
47
|
-
self.db_update_thread.start()
|
48
|
-
|
49
|
-
for i in range(grid_rows * grid_cols):
|
50
|
-
label = Label(root)
|
51
|
-
label.grid(row=i // grid_cols, column=i % grid_cols)
|
52
|
-
self.labels.append(label)
|
53
|
-
|
54
|
-
def prefilter_paths_annotations(self):
|
55
|
-
from .io import _read_and_join_tables
|
56
|
-
from .utils import is_list_of_lists
|
57
|
-
|
58
|
-
if self.measurement and self.threshold is not None:
|
59
|
-
df = _read_and_join_tables(self.db_path)
|
60
|
-
df[self.annotation_column] = None
|
61
|
-
before = len(df)
|
62
|
-
|
63
|
-
if is_list_of_lists(self.measurement):
|
64
|
-
if isinstance(self.threshold, list) or is_list_of_lists(self.threshold):
|
65
|
-
if len(self.measurement) == len(self.threshold):
|
66
|
-
for idx, var in enumerate(self.measurement):
|
67
|
-
df = df[df[var[idx]] > self.threshold[idx]]
|
68
|
-
after = len(df)
|
69
|
-
elif len(self.measurement) == len(self.threshold)*2:
|
70
|
-
th_idx = 0
|
71
|
-
for idx, var in enumerate(self.measurement):
|
72
|
-
if idx % 2 != 0:
|
73
|
-
th_idx += 1
|
74
|
-
thd = self.threshold
|
75
|
-
if isinstance(thd, list):
|
76
|
-
thd = thd[0]
|
77
|
-
df[f'threshold_measurement_{idx}'] = df[self.measurement[idx]]/df[self.measurement[idx+1]]
|
78
|
-
print(f"mean threshold_measurement_{idx}: {np.mean(df['threshold_measurement'])}")
|
79
|
-
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
80
|
-
df = df[df[f'threshold_measurement_{idx}'] > thd]
|
81
|
-
after = len(df)
|
82
|
-
elif isinstance(self.measurement, list):
|
83
|
-
df['threshold_measurement'] = df[self.measurement[0]]/df[self.measurement[1]]
|
84
|
-
print(f"mean threshold measurement: {np.mean(df['threshold_measurement'])}")
|
85
|
-
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
86
|
-
df = df[df['threshold_measurement'] > self.threshold]
|
87
|
-
after = len(df)
|
88
|
-
self.measurement = 'threshold_measurement'
|
89
|
-
print(f'Removed: {before-after} rows, retained {after}')
|
90
|
-
else:
|
91
|
-
print(f"mean threshold measurement: {np.mean(df[self.measurement])}")
|
92
|
-
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
93
|
-
before = len(df)
|
94
|
-
if isinstance(self.threshold, str):
|
95
|
-
if self.threshold == 'q1':
|
96
|
-
self.threshold = df[self.measurement].quantile(0.1)
|
97
|
-
if self.threshold == 'q2':
|
98
|
-
self.threshold = df[self.measurement].quantile(0.2)
|
99
|
-
if self.threshold == 'q3':
|
100
|
-
self.threshold = df[self.measurement].quantile(0.3)
|
101
|
-
if self.threshold == 'q4':
|
102
|
-
self.threshold = df[self.measurement].quantile(0.4)
|
103
|
-
if self.threshold == 'q5':
|
104
|
-
self.threshold = df[self.measurement].quantile(0.5)
|
105
|
-
if self.threshold == 'q6':
|
106
|
-
self.threshold = df[self.measurement].quantile(0.6)
|
107
|
-
if self.threshold == 'q7':
|
108
|
-
self.threshold = df[self.measurement].quantile(0.7)
|
109
|
-
if self.threshold == 'q8':
|
110
|
-
self.threshold = df[self.measurement].quantile(0.8)
|
111
|
-
if self.threshold == 'q9':
|
112
|
-
self.threshold = df[self.measurement].quantile(0.9)
|
113
|
-
print(f"threshold: {self.threshold}")
|
114
|
-
|
115
|
-
df = df[df[self.measurement] > self.threshold]
|
116
|
-
after = len(df)
|
117
|
-
print(f'Removed: {before-after} rows, retained {after}')
|
118
|
-
|
119
|
-
df = df.dropna(subset=['png_path'])
|
120
|
-
if self.image_type:
|
121
|
-
before = len(df)
|
122
|
-
if isinstance(self.image_type, list):
|
123
|
-
for tpe in self.image_type:
|
124
|
-
df = df[df['png_path'].str.contains(tpe)]
|
125
|
-
else:
|
126
|
-
df = df[df['png_path'].str.contains(self.image_type)]
|
127
|
-
after = len(df)
|
128
|
-
print(f'image_type: Removed: {before-after} rows, retained {after}')
|
129
|
-
|
130
|
-
self.filtered_paths_annotations = df[['png_path', self.annotation_column]].values.tolist()
|
131
|
-
else:
|
132
|
-
conn = sqlite3.connect(self.db_path)
|
133
|
-
c = conn.cursor()
|
134
|
-
if self.image_type:
|
135
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ?", (f"%{self.image_type}%",))
|
136
|
-
else:
|
137
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list")
|
138
|
-
self.filtered_paths_annotations = c.fetchall()
|
139
|
-
conn.close()
|
140
|
-
|
141
|
-
def load_images(self):
|
142
|
-
for label in self.labels:
|
143
|
-
label.config(image='')
|
144
|
-
|
145
|
-
self.images = {}
|
146
|
-
paths_annotations = self.filtered_paths_annotations[self.index:self.index + self.grid_rows * self.grid_cols]
|
147
|
-
|
148
|
-
adjusted_paths = []
|
149
|
-
for path, annotation in paths_annotations:
|
150
|
-
if not path.startswith(self.src):
|
151
|
-
parts = path.split('/data/')
|
152
|
-
if len(parts) > 1:
|
153
|
-
new_path = os.path.join(self.src, 'data', parts[1])
|
154
|
-
self.adjusted_to_original_paths[new_path] = path
|
155
|
-
adjusted_paths.append((new_path, annotation))
|
156
|
-
else:
|
157
|
-
adjusted_paths.append((path, annotation))
|
158
|
-
else:
|
159
|
-
adjusted_paths.append((path, annotation))
|
160
|
-
|
161
|
-
with ThreadPoolExecutor() as executor:
|
162
|
-
loaded_images = list(executor.map(self.load_single_image, adjusted_paths))
|
163
|
-
|
164
|
-
for i, (img, annotation) in enumerate(loaded_images):
|
165
|
-
if annotation:
|
166
|
-
border_color = 'teal' if annotation == 1 else 'red'
|
167
|
-
img = self.add_colored_border(img, border_width=5, border_color=border_color)
|
168
|
-
|
169
|
-
photo = ImageTk.PhotoImage(img)
|
170
|
-
label = self.labels[i]
|
171
|
-
self.images[label] = photo
|
172
|
-
label.config(image=photo)
|
173
|
-
|
174
|
-
path = adjusted_paths[i][0]
|
175
|
-
label.bind('<Button-1>', self.get_on_image_click(path, label, img))
|
176
|
-
label.bind('<Button-3>', self.get_on_image_click(path, label, img))
|
177
|
-
|
178
|
-
self.root.update()
|
179
|
-
|
180
|
-
def load_single_image(self, path_annotation_tuple):
|
181
|
-
path, annotation = path_annotation_tuple
|
182
|
-
img = Image.open(path)
|
183
|
-
img = self.normalize_image(img, self.normalize, self.percentiles)
|
184
|
-
img = img.convert('RGB')
|
185
|
-
img = self.filter_channels(img)
|
186
|
-
img = img.resize(self.image_size)
|
187
|
-
return img, annotation
|
188
|
-
|
189
|
-
@staticmethod
|
190
|
-
def normalize_image(img, normalize=False, percentiles=(1, 99)):
|
191
|
-
img_array = np.array(img)
|
192
|
-
|
193
|
-
if normalize:
|
194
|
-
if img_array.ndim == 2: # Grayscale image
|
195
|
-
p2, p98 = np.percentile(img_array, percentiles)
|
196
|
-
img_array = rescale_intensity(img_array, in_range=(p2, p98), out_range=(0, 255))
|
197
|
-
else: # Color image or multi-channel image
|
198
|
-
for channel in range(img_array.shape[2]):
|
199
|
-
p2, p98 = np.percentile(img_array[:, :, channel], percentiles)
|
200
|
-
img_array[:, :, channel] = rescale_intensity(img_array[:, :, channel], in_range=(p2, p98), out_range=(0, 255))
|
201
|
-
|
202
|
-
img_array = np.clip(img_array, 0, 255).astype('uint8')
|
203
|
-
|
204
|
-
return Image.fromarray(img_array)
|
2
|
+
from .gui import MainApp
|
3
|
+
|
4
|
+
def initiate_annotation_app(parent_frame):
|
5
|
+
from .gui_utils import generate_annotate_fields, annotate_app
|
6
|
+
# Set up the settings window
|
7
|
+
settings_window = tk.Toplevel(parent_frame)
|
8
|
+
settings_window.title("Annotation Settings")
|
9
|
+
settings_window.configure(bg='black') # Set the background color to black
|
205
10
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
right_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
211
|
-
|
212
|
-
bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color='white')
|
213
|
-
bordered_img.paste(top_border, (border_width, 0))
|
214
|
-
bordered_img.paste(bottom_border, (border_width, img.height + border_width))
|
215
|
-
bordered_img.paste(left_border, (0, border_width))
|
216
|
-
bordered_img.paste(right_border, (img.width + border_width, border_width))
|
217
|
-
bordered_img.paste(img, (border_width, border_width))
|
218
|
-
|
219
|
-
return bordered_img
|
11
|
+
# Use the existing function to create the settings UI
|
12
|
+
settings_frame = tk.Frame(settings_window, bg='black') # Set the background color to black
|
13
|
+
settings_frame.pack(fill=tk.BOTH, expand=True)
|
14
|
+
vars_dict = generate_annotate_fields(settings_frame)
|
220
15
|
|
221
|
-
def
|
222
|
-
|
223
|
-
if self.channels:
|
224
|
-
if 'r' not in self.channels:
|
225
|
-
r = r.point(lambda _: 0)
|
226
|
-
if 'g' not in self.channels:
|
227
|
-
g = g.point(lambda _: 0)
|
228
|
-
if 'b' not in self.channels:
|
229
|
-
b = b.point(lambda _: 0)
|
230
|
-
|
231
|
-
if len(self.channels) == 1:
|
232
|
-
channel_img = r if 'r' in self.channels else (g if 'g' in self.channels else b)
|
233
|
-
return ImageOps.grayscale(channel_img)
|
234
|
-
|
235
|
-
return Image.merge("RGB", (r, g, b))
|
236
|
-
|
237
|
-
def get_on_image_click(self, path, label, img):
|
238
|
-
def on_image_click(event):
|
239
|
-
new_annotation = 1 if event.num == 1 else (2 if event.num == 3 else None)
|
240
|
-
|
241
|
-
original_path = self.adjusted_to_original_paths.get(path, path)
|
242
|
-
|
243
|
-
if original_path in self.pending_updates and self.pending_updates[original_path] == new_annotation:
|
244
|
-
self.pending_updates[original_path] = None
|
245
|
-
new_annotation = None
|
246
|
-
else:
|
247
|
-
self.pending_updates[original_path] = new_annotation
|
248
|
-
|
249
|
-
print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
|
250
|
-
|
251
|
-
img_ = img.crop((5, 5, img.width-5, img.height-5))
|
252
|
-
border_fill = 'teal' if new_annotation == 1 else ('red' if new_annotation == 2 else None)
|
253
|
-
img_ = ImageOps.expand(img_, border=5, fill=border_fill) if border_fill else img_
|
254
|
-
|
255
|
-
photo = ImageTk.PhotoImage(img_)
|
256
|
-
self.images[label] = photo
|
257
|
-
label.config(image=photo)
|
258
|
-
self.root.update()
|
259
|
-
|
260
|
-
return on_image_click
|
261
|
-
|
262
|
-
@staticmethod
|
263
|
-
def update_html(text):
|
264
|
-
display(HTML(f"""
|
265
|
-
<script>
|
266
|
-
document.getElementById('unique_id').innerHTML = '{text}';
|
267
|
-
</script>
|
268
|
-
"""))
|
269
|
-
|
270
|
-
def update_database_worker(self):
|
271
|
-
conn = sqlite3.connect(self.db_path)
|
272
|
-
c = conn.cursor()
|
273
|
-
|
274
|
-
display(HTML("<div id='unique_id'>Initial Text</div>"))
|
275
|
-
|
276
|
-
while True:
|
277
|
-
if self.terminate:
|
278
|
-
conn.close()
|
279
|
-
break
|
280
|
-
|
281
|
-
if not self.update_queue.empty():
|
282
|
-
ImageApp.update_html("Do not exit, Updating database...")
|
283
|
-
self.status_label.config(text='Do not exit, Updating database...')
|
284
|
-
|
285
|
-
pending_updates = self.update_queue.get()
|
286
|
-
for path, new_annotation in pending_updates.items():
|
287
|
-
if new_annotation is None:
|
288
|
-
c.execute(f'UPDATE png_list SET {self.annotation_column} = NULL WHERE png_path = ?', (path,))
|
289
|
-
else:
|
290
|
-
c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
|
291
|
-
conn.commit()
|
292
|
-
|
293
|
-
ImageApp.update_html('')
|
294
|
-
self.status_label.config(text='')
|
295
|
-
self.root.update()
|
296
|
-
time.sleep(0.1)
|
297
|
-
|
298
|
-
def update_gui_text(self, text):
|
299
|
-
self.status_label.config(text=text)
|
300
|
-
self.root.update()
|
301
|
-
|
302
|
-
def next_page(self):
|
303
|
-
if self.pending_updates:
|
304
|
-
self.update_queue.put(self.pending_updates.copy())
|
305
|
-
self.pending_updates.clear()
|
306
|
-
self.index += self.grid_rows * self.grid_cols
|
307
|
-
self.load_images()
|
308
|
-
|
309
|
-
def previous_page(self):
|
310
|
-
if self.pending_updates:
|
311
|
-
self.update_queue.put(self.pending_updates.copy())
|
312
|
-
self.pending_updates.clear()
|
313
|
-
self.index -= self.grid_rows * self.grid_cols
|
314
|
-
if self.index < 0:
|
315
|
-
self.index = 0
|
316
|
-
self.load_images()
|
317
|
-
|
318
|
-
def shutdown(self):
|
319
|
-
self.terminate = True
|
320
|
-
self.update_queue.put(self.pending_updates.copy())
|
321
|
-
self.pending_updates.clear()
|
322
|
-
self.db_update_thread.join()
|
323
|
-
self.root.quit()
|
324
|
-
self.root.destroy()
|
325
|
-
print(f'Quit application')
|
326
|
-
|
327
|
-
def get_annotate_default_settings(settings):
|
328
|
-
settings.setdefault('image_type', 'cell_png')
|
329
|
-
settings.setdefault('channels', ['r', 'g', 'b'])
|
330
|
-
settings.setdefault('geom', "3200x2000")
|
331
|
-
settings.setdefault('img_size', (200, 200))
|
332
|
-
settings.setdefault('rows', 10)
|
333
|
-
settings.setdefault('columns', 18)
|
334
|
-
settings.setdefault('annotation_column', 'recruited_test')
|
335
|
-
settings.setdefault('normalize', False)
|
336
|
-
settings.setdefault('percentiles', (2,98))
|
337
|
-
settings.setdefault('measurement', ['cytoplasm_channel_3_mean_intensity', 'pathogen_channel_3_mean_intensity'])
|
338
|
-
settings.setdefault('threshold', 2)
|
339
|
-
|
340
|
-
return settings
|
341
|
-
|
342
|
-
def annotate(settings):
|
343
|
-
settings = get_annotate_default_settings(settings)
|
344
|
-
src = settings['src']
|
345
|
-
|
346
|
-
db = os.path.join(src, 'measurements/measurements.db')
|
347
|
-
conn = sqlite3.connect(db)
|
348
|
-
c = conn.cursor()
|
349
|
-
c.execute('PRAGMA table_info(png_list)')
|
350
|
-
cols = c.fetchall()
|
351
|
-
if settings['annotation_column'] not in [col[1] for col in cols]:
|
352
|
-
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
353
|
-
conn.commit()
|
354
|
-
conn.close()
|
355
|
-
|
356
|
-
root = tk.Tk()
|
357
|
-
root.geometry(settings['geom'])
|
358
|
-
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'])
|
359
|
-
next_button = tk.Button(root, text="Next", command=app.next_page)
|
360
|
-
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
361
|
-
back_button = tk.Button(root, text="Back", command=app.previous_page)
|
362
|
-
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
363
|
-
exit_button = tk.Button(root, text="Exit", command=app.shutdown)
|
364
|
-
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
365
|
-
|
366
|
-
app.load_images()
|
367
|
-
root.mainloop()
|
368
|
-
|
369
|
-
# Global list to keep references to PhotoImage objects
|
370
|
-
global_image_refs = []
|
371
|
-
|
372
|
-
def initiate_annotation_app_root(parent_frame):
|
373
|
-
style = ttk.Style(parent_frame)
|
374
|
-
set_dark_style(style)
|
375
|
-
set_default_font(parent_frame, font_name="Arial", size=8)
|
376
|
-
|
377
|
-
parent_frame.configure(bg='black')
|
378
|
-
|
379
|
-
container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg='black')
|
380
|
-
container.pack(fill=tk.BOTH, expand=True)
|
381
|
-
|
382
|
-
scrollable_frame = spacrFrame(container, bg='black')
|
383
|
-
container.add(scrollable_frame, stretch="always")
|
384
|
-
|
385
|
-
# Setup input fields
|
386
|
-
vars_dict = {
|
387
|
-
'src': ttk.Entry(scrollable_frame.scrollable_frame),
|
388
|
-
'image_type': ttk.Entry(scrollable_frame.scrollable_frame),
|
389
|
-
'channels': ttk.Entry(scrollable_frame.scrollable_frame),
|
390
|
-
'geom': ttk.Entry(scrollable_frame.scrollable_frame),
|
391
|
-
'img_size': ttk.Entry(scrollable_frame.scrollable_frame),
|
392
|
-
'rows': ttk.Entry(scrollable_frame.scrollable_frame),
|
393
|
-
'columns': ttk.Entry(scrollable_frame.scrollable_frame),
|
394
|
-
'annotation_column': ttk.Entry(scrollable_frame.scrollable_frame),
|
395
|
-
'normalize': ttk.Entry(scrollable_frame.scrollable_frame),
|
396
|
-
'percentiles': ttk.Entry(scrollable_frame.scrollable_frame),
|
397
|
-
'measurement': ttk.Entry(scrollable_frame.scrollable_frame),
|
398
|
-
'threshold': ttk.Entry(scrollable_frame.scrollable_frame),
|
399
|
-
}
|
400
|
-
|
401
|
-
default_settings = {
|
402
|
-
'src': 'path',
|
403
|
-
'image_type': 'cell_png',
|
404
|
-
'channels': 'r,g,b',
|
405
|
-
'geom': "3200x2000",
|
406
|
-
'img_size': '(200, 200)',
|
407
|
-
'rows': '10',
|
408
|
-
'columns': '18',
|
409
|
-
'annotation_column': 'recruited_test',
|
410
|
-
'normalize': 'False',
|
411
|
-
'percentiles': '(2,98)',
|
412
|
-
'measurement': 'None',
|
413
|
-
'threshold': 'None'
|
414
|
-
}
|
415
|
-
|
416
|
-
# Arrange input fields and labels
|
417
|
-
row = 0
|
418
|
-
for name, entry in vars_dict.items():
|
419
|
-
ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:",
|
420
|
-
background="black", foreground="white").grid(row=row, column=0)
|
421
|
-
entry.insert(0, default_settings[name])
|
422
|
-
entry.grid(row=row, column=1)
|
423
|
-
row += 1
|
424
|
-
|
425
|
-
# Function to be called when "Run" button is clicked
|
426
|
-
def run_app():
|
427
|
-
settings = {key: entry.get() for key, entry in vars_dict.items()}
|
16
|
+
def start_annotation_app():
|
17
|
+
settings = {key: data['entry'].get() for key, data in vars_dict.items()}
|
428
18
|
settings['channels'] = settings['channels'].split(',')
|
429
|
-
settings['img_size'] =
|
430
|
-
settings['percentiles'] =
|
19
|
+
settings['img_size'] = list(map(int, settings['img_size'].split(','))) # Convert string to list of integers
|
20
|
+
settings['percentiles'] = list(map(int, settings['percentiles'].split(','))) # Convert string to list of integers
|
431
21
|
settings['normalize'] = settings['normalize'].lower() == 'true'
|
432
22
|
settings['rows'] = int(settings['rows'])
|
433
23
|
settings['columns'] = int(settings['columns'])
|
434
|
-
if settings['measurement'].lower() == 'none':
|
435
|
-
settings['measurement'] = None
|
436
|
-
if settings['threshold'].lower() == 'none':
|
437
|
-
settings['threshold'] = None
|
438
|
-
|
439
|
-
# Clear previous content instead of destroying the root
|
440
|
-
if hasattr(parent_frame, 'winfo_children'):
|
441
|
-
for widget in parent_frame.winfo_children():
|
442
|
-
widget.destroy()
|
443
|
-
|
444
|
-
# Start the annotate application in the same root window
|
445
|
-
annotate_app(parent_frame, settings)
|
446
|
-
|
447
|
-
run_button = spacrButton(scrollable_frame.scrollable_frame, text="Run", command=run_app,
|
448
|
-
font=tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL))
|
449
|
-
run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
|
450
|
-
|
451
|
-
return parent_frame
|
452
|
-
|
453
|
-
def annotate_app(parent_frame, settings):
|
454
|
-
global global_image_refs
|
455
|
-
global_image_refs.clear()
|
456
|
-
root = parent_frame.winfo_toplevel()
|
457
|
-
annotate_with_image_refs(settings, root, lambda: load_next_app(root))
|
458
|
-
|
459
|
-
def annotate_with_image_refs(settings, root, shutdown_callback):
|
460
|
-
from .gui_utils import proceed_with_app
|
461
|
-
from .gui import gui_app
|
462
|
-
|
463
|
-
settings = get_annotate_default_settings(settings)
|
464
|
-
src = settings['src']
|
465
|
-
|
466
|
-
db = os.path.join(src, 'measurements/measurements.db')
|
467
|
-
conn = sqlite3.connect(db)
|
468
|
-
c = conn.cursor()
|
469
|
-
c.execute('PRAGMA table_info(png_list)')
|
470
|
-
cols = c.fetchall()
|
471
|
-
if settings['annotation_column'] not in [col[1] for col in cols]:
|
472
|
-
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
473
|
-
conn.commit()
|
474
|
-
conn.close()
|
475
|
-
|
476
|
-
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'])
|
477
|
-
|
478
|
-
# Set the canvas background to black
|
479
|
-
root.configure(bg='black')
|
480
24
|
|
481
|
-
next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
|
482
|
-
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
483
|
-
back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
|
484
|
-
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
485
|
-
exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
|
486
|
-
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
487
|
-
|
488
|
-
app.load_images()
|
489
|
-
|
490
|
-
# Store the shutdown function and next app details in the root
|
491
|
-
root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
|
492
|
-
root.next_app_func = proceed_with_app
|
493
|
-
root.next_app_args = ("Main App", gui_app) # Specify the main app function
|
494
|
-
|
495
|
-
def load_next_app(root):
|
496
|
-
# Get the next app function and arguments
|
497
|
-
next_app_func = root.next_app_func
|
498
|
-
next_app_args = root.next_app_args
|
499
|
-
|
500
|
-
if next_app_func:
|
501
25
|
try:
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
new_root = tk.Tk()
|
508
|
-
width = new_root.winfo_screenwidth()
|
509
|
-
height = new_root.winfo_screenheight()
|
510
|
-
new_root.geometry(f"{width}x{height}")
|
511
|
-
new_root.title("SpaCr Application")
|
512
|
-
next_app_func(new_root, *next_app_args)
|
26
|
+
settings['measurement'] = settings['measurement'].split(',') if settings['measurement'] else None
|
27
|
+
settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
|
28
|
+
except:
|
29
|
+
settings['measurement'] = None
|
30
|
+
settings['threshold'] = None
|
513
31
|
|
32
|
+
settings['db'] = settings.get('db', 'default.db')
|
514
33
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
for widget in root.content_frame.winfo_children():
|
525
|
-
widget.destroy()
|
526
|
-
root.content_frame.grid_forget()
|
527
|
-
else:
|
528
|
-
root.content_frame = tk.Frame(root)
|
529
|
-
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
530
|
-
root.grid_rowconfigure(1, weight=1)
|
531
|
-
root.grid_columnconfigure(0, weight=1)
|
34
|
+
# Convert empty strings to None
|
35
|
+
for key, value in settings.items():
|
36
|
+
if isinstance(value, list):
|
37
|
+
settings[key] = [v if v != '' else None for v in value]
|
38
|
+
elif value == '':
|
39
|
+
settings[key] = None
|
40
|
+
|
41
|
+
settings_window.destroy()
|
42
|
+
annotate_app(parent_frame, settings)
|
532
43
|
|
533
|
-
|
534
|
-
|
535
|
-
|
44
|
+
#start_button = spacrButton(settings_window, text="Start Annotation", command=lambda: start_annotation_app, font=('Helvetica', 12))
|
45
|
+
start_button = tk.Button(settings_window, text="Start Annotation", command=start_annotation_app)
|
46
|
+
start_button.pack(pady=10)
|
47
|
+
|
48
|
+
def start_annotate_app():
|
49
|
+
app = MainApp(default_app="Annotate")
|
50
|
+
app.mainloop()
|
536
51
|
|
537
52
|
if __name__ == "__main__":
|
538
|
-
|
53
|
+
start_annotate_app()
|