spacr 0.1.64__py3-none-any.whl → 0.1.76__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 CHANGED
@@ -1,538 +1,52 @@
1
- import sqlite3
2
- from queue import Queue
3
- from tkinter import Label
4
1
  import tkinter as tk
5
- from tkinter import ttk
6
- import os, threading, time, sqlite3
7
- import numpy as np
8
- from PIL import Image, ImageOps
9
- from concurrent.futures import ThreadPoolExecutor
10
- from PIL import ImageTk
11
- from skimage.exposure import rescale_intensity
12
- from IPython.display import display, HTML
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
- def add_colored_border(self, img, border_width, border_color):
207
- top_border = Image.new('RGB', (img.width, border_width), color=border_color)
208
- bottom_border = Image.new('RGB', (img.width, border_width), color=border_color)
209
- left_border = Image.new('RGB', (border_width, img.height), color=border_color)
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 filter_channels(self, img):
222
- r, g, b = img.split()
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'] = tuple(map(int, settings['img_size'][1:-1].split(',')))
430
- settings['percentiles'] = tuple(map(int, settings['percentiles'][1:-1].split(',')))
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
- if not root.winfo_exists():
503
- raise tk.TclError
504
- next_app_func(root, *next_app_args)
505
- except tk.TclError:
506
- # Reinitialize root if it has been destroyed
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
- def gui_annotate():
516
- root = tk.Tk()
517
- width = root.winfo_screenwidth()
518
- height = root.winfo_screenheight()
519
- root.geometry(f"{width}x{height}")
520
- root.title("Annotate Application")
521
-
522
- # Clear previous content if any
523
- if hasattr(root, 'content_frame'):
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
- initiate_annotation_app_root(root.content_frame)
534
- create_menu_bar(root)
535
- root.mainloop()
44
+ start_button = tk.Button(settings_window, text="Start Annotation", command=start_annotation_app, bg='black', fg='white')
45
+ start_button.pack(pady=10)
46
+
47
+ def start_annotate_app():
48
+ app = MainApp(default_app="Annotate")
49
+ app.mainloop()
536
50
 
537
51
  if __name__ == "__main__":
538
- gui_annotate()
52
+ start_annotate_app()