spacr 0.1.1__tar.gz → 0.1.12__tar.gz

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.
Files changed (58) hide show
  1. {spacr-0.1.1/spacr.egg-info → spacr-0.1.12}/PKG-INFO +5 -1
  2. {spacr-0.1.1 → spacr-0.1.12}/setup.py +9 -5
  3. {spacr-0.1.1 → spacr-0.1.12}/spacr/__init__.py +12 -12
  4. spacr-0.1.1/spacr/annotate_app_v2.py → spacr-0.1.12/spacr/app_annotate.py +183 -155
  5. spacr-0.1.1/spacr/gui_classify_app.py → spacr-0.1.12/spacr/app_classify.py +29 -15
  6. spacr-0.1.1/spacr/mask_app.py → spacr-0.1.12/spacr/app_make_masks.py +75 -71
  7. spacr-0.1.12/spacr/app_make_masks_v2.py +688 -0
  8. spacr-0.1.1/spacr/gui_mask_app.py → spacr-0.1.12/spacr/app_mask.py +10 -4
  9. spacr-0.1.1/spacr/gui_measure_app.py → spacr-0.1.12/spacr/app_measure.py +17 -5
  10. {spacr-0.1.1 → spacr-0.1.12}/spacr/gui.py +23 -20
  11. {spacr-0.1.1 → spacr-0.1.12}/spacr/gui_utils.py +110 -20
  12. {spacr-0.1.1 → spacr-0.1.12/spacr.egg-info}/PKG-INFO +5 -1
  13. {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/SOURCES.txt +7 -8
  14. {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/entry_points.txt +3 -3
  15. {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/requires.txt +4 -0
  16. {spacr-0.1.1 → spacr-0.1.12}/tests/test_annotate_app.py +1 -1
  17. {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_classify_app.py +1 -1
  18. {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_mask_app.py +1 -1
  19. {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_measure_app.py +1 -1
  20. {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_sim_app.py +1 -1
  21. {spacr-0.1.1 → spacr-0.1.12}/tests/test_mask_app.py +1 -1
  22. spacr-0.1.1/spacr/annotate_app.py +0 -511
  23. spacr-0.1.1/spacr/gui_2.py +0 -160
  24. {spacr-0.1.1 → spacr-0.1.12}/LICENSE +0 -0
  25. {spacr-0.1.1 → spacr-0.1.12}/MANIFEST.in +0 -0
  26. {spacr-0.1.1 → spacr-0.1.12}/README.rst +0 -0
  27. {spacr-0.1.1 → spacr-0.1.12}/setup.cfg +0 -0
  28. {spacr-0.1.1 → spacr-0.1.12}/spacr/__main__.py +0 -0
  29. {spacr-0.1.1 → spacr-0.1.12}/spacr/chris.py +0 -0
  30. {spacr-0.1.1 → spacr-0.1.12}/spacr/core.py +0 -0
  31. {spacr-0.1.1 → spacr-0.1.12}/spacr/deep_spacr.py +0 -0
  32. {spacr-0.1.1 → spacr-0.1.12}/spacr/graph_learning.py +0 -0
  33. {spacr-0.1.1 → spacr-0.1.12}/spacr/io.py +0 -0
  34. {spacr-0.1.1 → spacr-0.1.12}/spacr/logger.py +0 -0
  35. {spacr-0.1.1 → spacr-0.1.12}/spacr/measure.py +0 -0
  36. {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model +0 -0
  37. {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -0
  38. {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
  39. {spacr-0.1.1 → spacr-0.1.12}/spacr/plot.py +0 -0
  40. {spacr-0.1.1 → spacr-0.1.12}/spacr/sequencing.py +0 -0
  41. {spacr-0.1.1 → spacr-0.1.12}/spacr/settings.py +0 -0
  42. {spacr-0.1.1 → spacr-0.1.12}/spacr/sim.py +0 -0
  43. /spacr-0.1.1/spacr/gui_sim_app.py → /spacr-0.1.12/spacr/sim_app.py +0 -0
  44. {spacr-0.1.1 → spacr-0.1.12}/spacr/timelapse.py +0 -0
  45. {spacr-0.1.1 → spacr-0.1.12}/spacr/utils.py +0 -0
  46. {spacr-0.1.1 → spacr-0.1.12}/spacr/version.py +0 -0
  47. {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/dependency_links.txt +0 -0
  48. {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/top_level.txt +0 -0
  49. {spacr-0.1.1 → spacr-0.1.12}/tests/test_core.py +0 -0
  50. {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_utils.py +0 -0
  51. {spacr-0.1.1 → spacr-0.1.12}/tests/test_io.py +0 -0
  52. {spacr-0.1.1 → spacr-0.1.12}/tests/test_measure.py +0 -0
  53. {spacr-0.1.1 → spacr-0.1.12}/tests/test_plot.py +0 -0
  54. {spacr-0.1.1 → spacr-0.1.12}/tests/test_sim.py +0 -0
  55. {spacr-0.1.1 → spacr-0.1.12}/tests/test_timelapse.py +0 -0
  56. {spacr-0.1.1 → spacr-0.1.12}/tests/test_train.py +0 -0
  57. {spacr-0.1.1 → spacr-0.1.12}/tests/test_umap.py +0 -0
  58. {spacr-0.1.1 → spacr-0.1.12}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spacr
3
- Version: 0.1.1
3
+ Version: 0.1.12
4
4
  Summary: Spatial phenotype analysis of crisp screens (SpaCr)
5
5
  Home-page: https://github.com/EinarOlafsson/spacr
6
6
  Author: Einar Birnir Olafsson
@@ -40,6 +40,10 @@ Requires-Dist: ttf_opensans>=2020.10.30
40
40
  Requires-Dist: customtkinter<6.0,>=5.2.2
41
41
  Requires-Dist: biopython<2.0,>=1.80
42
42
  Requires-Dist: lxml<6.0,>=5.1.0
43
+ Requires-Dist: qtpy<2.5,>=2.4.1
44
+ Requires-Dist: superqt<0.7,>=0.6.7
45
+ Requires-Dist: pyqt6<6.8,>=6.7.1
46
+ Requires-Dist: pyqtgraph<0.14,>=0.13.7
43
47
  Provides-Extra: dev
44
48
  Requires-Dist: pytest>=3.9; extra == "dev"
45
49
  Provides-Extra: headless
@@ -52,12 +52,16 @@ dependencies = [
52
52
  'ttf_opensans>=2020.10.30',
53
53
  'customtkinter>=5.2.2,<6.0',
54
54
  'biopython>=1.80,<2.0',
55
- 'lxml>=5.1.0,<6.0'
55
+ 'lxml>=5.1.0,<6.0',
56
+ 'qtpy>=2.4.1,<2.5',
57
+ 'superqt>=0.6.7,<0.7',
58
+ 'pyqt6>=6.7.1,<6.8',
59
+ 'pyqtgraph>=0.13.7,<0.14'
56
60
  ]
57
61
 
58
62
  setup(
59
63
  name="spacr",
60
- version="0.1.01",
64
+ version="0.1.12",
61
65
  author="Einar Birnir Olafsson",
62
66
  author_email="olafsson@med.umich.com",
63
67
  description="Spatial phenotype analysis of crisp screens (SpaCr)",
@@ -71,12 +75,12 @@ setup(
71
75
  'console_scripts': [
72
76
  'mask=spacr.gui_mask_app:gui_mask',
73
77
  'measure=spacr.gui_measure_app:gui_measure',
74
- 'make_masks=spacr.mask_app:gui_make_masks',
75
- 'annotate=spacr.annotate_app:gui_annotation',
78
+ 'make_masks=spacr.gui_make_mask_app:gui_make_masks',
79
+ 'make_masks2=spacr.gui_make_mask_app_v2:gui_make_masks',
80
+ 'annotate=spacr.annotate_app_v2:gui_annotate',
76
81
  'classify=spacr.gui_classify_app:gui_classify',
77
82
  'sim=spacr.gui_sim_app:gui_sim',
78
83
  'gui=spacr.gui:gui_app',
79
- 'gui2=spacr.gui_2:gui_app',
80
84
  ],
81
85
  },
82
86
  extras_require={
@@ -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 annotate_app
16
- from . import annotate_app_v2
15
+ from . import app_annotate
17
16
  from . import gui_utils
18
- from . import mask_app
19
- from . import gui_mask_app
20
- from . import gui_measure_app
21
- from . import gui_classify_app
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
- "annotate_app",
37
- "annotate_app_v2",
36
+ "app_annotate",
38
37
  "gui_utils",
39
- "mask_app",
40
- "gui_mask_app",
41
- "gui_measure_app",
42
- "gui_classify_app",
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
 
@@ -1,37 +1,22 @@
1
+ import sqlite3
1
2
  from queue import Queue
2
- from tkinter import Label, Entry, Button
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
- import pandas as pd
10
11
  from skimage.exposure import rescale_intensity
11
- import cv2
12
- import matplotlib.pyplot as plt
13
12
  from IPython.display import display, HTML
13
+ from tkinter import font as tkFont
14
+ from tkinter import TclError
15
+
16
+ from .gui_utils import ScrollableFrame, CustomButton, set_dark_style, set_default_font, style_text_boxes, create_menu_bar
14
17
 
15
18
  class ImageApp:
16
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):
17
- """
18
- Initializes an instance of the ImageApp class.
19
-
20
- Parameters:
21
- - root (tkinter.Tk): The root window of the application.
22
- - db_path (str): The path to the SQLite database.
23
- - src (str): The source directory that should be upstream of 'data' in the paths.
24
- - image_type (str): The type of images to display.
25
- - channels (list): The channels to filter in the images.
26
- - grid_rows (int): The number of rows in the image grid.
27
- - grid_cols (int): The number of columns in the image grid.
28
- - image_size (tuple): The size of the displayed images.
29
- - annotation_column (str): The column name for image annotations in the database.
30
- - normalize (bool): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
31
- - measurement (str): The measurement column to filter by.
32
- - threshold (float): The threshold value for filtering the measurement column.
33
- """
34
-
35
20
  self.root = root
36
21
  self.db_path = db_path
37
22
  self.src = src
@@ -67,9 +52,6 @@ class ImageApp:
67
52
  self.labels.append(label)
68
53
 
69
54
  def prefilter_paths_annotations(self):
70
- """
71
- Pre-filters the paths and annotations based on the specified measurement and threshold.
72
- """
73
55
  from .io import _read_and_join_tables
74
56
  from .utils import is_list_of_lists
75
57
 
@@ -157,21 +139,6 @@ class ImageApp:
157
139
  conn.close()
158
140
 
159
141
  def load_images(self):
160
- """
161
- Loads and displays images with annotations.
162
-
163
- This method retrieves image paths and annotations from a pre-filtered list,
164
- loads the images using a ThreadPoolExecutor for parallel processing,
165
- adds colored borders to images based on their annotations,
166
- and displays the images in the corresponding labels.
167
-
168
- Args:
169
- None
170
-
171
- Returns:
172
- None
173
- """
174
-
175
142
  for label in self.labels:
176
143
  label.config(image='')
177
144
 
@@ -211,16 +178,6 @@ class ImageApp:
211
178
  self.root.update()
212
179
 
213
180
  def load_single_image(self, path_annotation_tuple):
214
- """
215
- Loads a single image from the given path and annotation tuple.
216
-
217
- Args:
218
- path_annotation_tuple (tuple): A tuple containing the image path and its annotation.
219
-
220
- Returns:
221
- img (PIL.Image.Image): The loaded image.
222
- annotation: The annotation associated with the image.
223
- """
224
181
  path, annotation = path_annotation_tuple
225
182
  img = Image.open(path)
226
183
  img = self.normalize_image(img, self.normalize, self.percentiles)
@@ -231,18 +188,6 @@ class ImageApp:
231
188
 
232
189
  @staticmethod
233
190
  def normalize_image(img, normalize=False, percentiles=(1, 99)):
234
- """
235
- Normalize the pixel values of an image based on the 2nd and 98th percentiles or the image min and max values,
236
- and ensure the image is exported as 8-bit.
237
-
238
- Parameters:
239
- - img: PIL.Image.Image. The input image to be normalized.
240
- - normalize: bool. Whether to normalize based on the 2nd and 98th percentiles.
241
- - percentiles: tuple. The percentiles to use for normalization.
242
-
243
- Returns:
244
- - PIL.Image.Image. The normalized and 8-bit converted image.
245
- """
246
191
  img_array = np.array(img)
247
192
 
248
193
  if normalize:
@@ -259,17 +204,6 @@ class ImageApp:
259
204
  return Image.fromarray(img_array)
260
205
 
261
206
  def add_colored_border(self, img, border_width, border_color):
262
- """
263
- Adds a colored border to an image.
264
-
265
- Args:
266
- img (PIL.Image.Image): The input image.
267
- border_width (int): The width of the border in pixels.
268
- border_color (str): The color of the border in RGB format.
269
-
270
- Returns:
271
- PIL.Image.Image: The image with the colored border.
272
- """
273
207
  top_border = Image.new('RGB', (img.width, border_width), color=border_color)
274
208
  bottom_border = Image.new('RGB', (img.width, border_width), color=border_color)
275
209
  left_border = Image.new('RGB', (border_width, img.height), color=border_color)
@@ -285,15 +219,6 @@ class ImageApp:
285
219
  return bordered_img
286
220
 
287
221
  def filter_channels(self, img):
288
- """
289
- Filters the channels of an image based on the specified channels.
290
-
291
- Args:
292
- img (PIL.Image.Image): The input image.
293
-
294
- Returns:
295
- PIL.Image.Image: The filtered image.
296
- """
297
222
  r, g, b = img.split()
298
223
  if self.channels:
299
224
  if 'r' not in self.channels:
@@ -310,17 +235,6 @@ class ImageApp:
310
235
  return Image.merge("RGB", (r, g, b))
311
236
 
312
237
  def get_on_image_click(self, path, label, img):
313
- """
314
- Returns a callback function that handles the click event on an image.
315
-
316
- Parameters:
317
- path (str): The path of the image file.
318
- label (tkinter.Label): The label widget to update with the annotated image.
319
- img (PIL.Image.Image): The image object.
320
-
321
- Returns:
322
- function: The callback function for the image click event.
323
- """
324
238
  def on_image_click(event):
325
239
  new_annotation = 1 if event.num == 1 else (2 if event.num == 3 else None)
326
240
 
@@ -354,11 +268,6 @@ class ImageApp:
354
268
  """))
355
269
 
356
270
  def update_database_worker(self):
357
- """
358
- Worker function that continuously updates the database with pending updates from the update queue.
359
- It retrieves the pending updates from the queue, updates the corresponding records in the database,
360
- and resets the text in the HTML and status label.
361
- """
362
271
  conn = sqlite3.connect(self.db_path)
363
272
  c = conn.cursor()
364
273
 
@@ -381,51 +290,24 @@ class ImageApp:
381
290
  c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
382
291
  conn.commit()
383
292
 
384
- # Reset the text
385
293
  ImageApp.update_html('')
386
294
  self.status_label.config(text='')
387
295
  self.root.update()
388
296
  time.sleep(0.1)
389
297
 
390
298
  def update_gui_text(self, text):
391
- """
392
- Update the text of the status label in the GUI.
393
-
394
- Args:
395
- text (str): The new text to be displayed in the status label.
396
-
397
- Returns:
398
- None
399
- """
400
299
  self.status_label.config(text=text)
401
300
  self.root.update()
402
301
 
403
302
  def next_page(self):
404
- """
405
- Moves to the next page of images in the grid.
406
-
407
- If there are pending updates in the dictionary, they are added to the update queue.
408
- The pending updates dictionary is then cleared.
409
- The index is incremented by the number of rows multiplied by the number of columns in the grid.
410
- Finally, the images are loaded for the new page.
411
- """
412
- if self.pending_updates: # Check if the dictionary is not empty
303
+ if self.pending_updates:
413
304
  self.update_queue.put(self.pending_updates.copy())
414
305
  self.pending_updates.clear()
415
306
  self.index += self.grid_rows * self.grid_cols
416
307
  self.load_images()
417
308
 
418
309
  def previous_page(self):
419
- """
420
- Move to the previous page in the grid.
421
-
422
- If there are pending updates in the dictionary, they are added to the update queue.
423
- The dictionary of pending updates is then cleared.
424
- The index is decremented by the number of rows multiplied by the number of columns in the grid.
425
- If the index becomes negative, it is set to 0.
426
- Finally, the images are loaded for the new page.
427
- """
428
- if self.pending_updates: # Check if the dictionary is not empty
310
+ if self.pending_updates:
429
311
  self.update_queue.put(self.pending_updates.copy())
430
312
  self.pending_updates.clear()
431
313
  self.index -= self.grid_rows * self.grid_cols
@@ -434,23 +316,15 @@ class ImageApp:
434
316
  self.load_images()
435
317
 
436
318
  def shutdown(self):
437
- """
438
- Shuts down the application.
439
-
440
- This method sets the terminate flag to True, clears the pending updates,
441
- updates the database, and quits the application.
442
-
443
- """
444
- self.terminate = True # Set terminate first
319
+ self.terminate = True
445
320
  self.update_queue.put(self.pending_updates.copy())
446
321
  self.pending_updates.clear()
447
- self.db_update_thread.join() # Join the thread to make sure database is updated
322
+ self.db_update_thread.join()
448
323
  self.root.quit()
449
324
  self.root.destroy()
450
325
  print(f'Quit application')
451
326
 
452
327
  def get_annotate_default_settings(settings):
453
-
454
328
  settings.setdefault('image_type', 'cell_png')
455
329
  settings.setdefault('channels', ['r', 'g', 'b'])
456
330
  settings.setdefault('geom', "3200x2000")
@@ -466,24 +340,6 @@ def get_annotate_default_settings(settings):
466
340
  return settings
467
341
 
468
342
  def annotate(settings):
469
- """
470
- Annotates images in a database using a graphical user interface.
471
-
472
- Args:
473
- db (str): The path to the SQLite database.
474
- src (str): The source directory that should be upstream of 'data' in the paths.
475
- image_type (str, optional): The type of images to load from the database. Defaults to None.
476
- channels (str, optional): The channels of the images to load from the database. Defaults to None.
477
- geom (str, optional): The geometry of the GUI window. Defaults to "1000x1100".
478
- img_size (tuple, optional): The size of the images to display in the GUI. Defaults to (200, 200).
479
- rows (int, optional): The number of rows in the image grid. Defaults to 5.
480
- columns (int, optional): The number of columns in the image grid. Defaults to 5.
481
- annotation_column (str, optional): The name of the annotation column in the database table. Defaults to 'annotate'.
482
- normalize (bool, optional): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
483
- measurement (str, optional): The measurement column to filter by.
484
- threshold (float, optional): The threshold value for filtering the measurement column.
485
- """
486
-
487
343
  settings = get_annotate_default_settings(settings)
488
344
  src = settings['src']
489
345
 
@@ -509,3 +365,175 @@ def annotate(settings):
509
365
 
510
366
  app.load_images()
511
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
+ style_text_boxes(style)
376
+ set_default_font(parent_frame, font_name="Arial", size=8)
377
+
378
+ parent_frame.configure(bg='black')
379
+
380
+ container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg='black')
381
+ container.pack(fill=tk.BOTH, expand=True)
382
+
383
+ scrollable_frame = ScrollableFrame(container, bg='black')
384
+ container.add(scrollable_frame, stretch="always")
385
+
386
+ # Setup input fields
387
+ vars_dict = {
388
+ 'src': ttk.Entry(scrollable_frame.scrollable_frame),
389
+ 'image_type': ttk.Entry(scrollable_frame.scrollable_frame),
390
+ 'channels': ttk.Entry(scrollable_frame.scrollable_frame),
391
+ 'geom': ttk.Entry(scrollable_frame.scrollable_frame),
392
+ 'img_size': ttk.Entry(scrollable_frame.scrollable_frame),
393
+ 'rows': ttk.Entry(scrollable_frame.scrollable_frame),
394
+ 'columns': ttk.Entry(scrollable_frame.scrollable_frame),
395
+ 'annotation_column': ttk.Entry(scrollable_frame.scrollable_frame),
396
+ 'normalize': ttk.Entry(scrollable_frame.scrollable_frame),
397
+ 'percentiles': ttk.Entry(scrollable_frame.scrollable_frame),
398
+ 'measurement': ttk.Entry(scrollable_frame.scrollable_frame),
399
+ 'threshold': ttk.Entry(scrollable_frame.scrollable_frame),
400
+ }
401
+
402
+ default_settings = {
403
+ 'src': 'path',
404
+ 'image_type': 'cell_png',
405
+ 'channels': 'r,g,b',
406
+ 'geom': "3200x2000",
407
+ 'img_size': '(200, 200)',
408
+ 'rows': '10',
409
+ 'columns': '18',
410
+ 'annotation_column': 'recruited_test',
411
+ 'normalize': 'False',
412
+ 'percentiles': '(2,98)',
413
+ 'measurement': 'None',
414
+ 'threshold': 'None'
415
+ }
416
+
417
+ # Arrange input fields and labels
418
+ row = 0
419
+ for name, entry in vars_dict.items():
420
+ ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:",
421
+ background="black", foreground="white").grid(row=row, column=0)
422
+ entry.insert(0, default_settings[name])
423
+ entry.grid(row=row, column=1)
424
+ row += 1
425
+
426
+ # Function to be called when "Run" button is clicked
427
+ def run_app():
428
+ settings = {key: entry.get() for key, entry in vars_dict.items()}
429
+ settings['channels'] = settings['channels'].split(',')
430
+ settings['img_size'] = tuple(map(int, settings['img_size'][1:-1].split(',')))
431
+ settings['percentiles'] = tuple(map(int, settings['percentiles'][1:-1].split(',')))
432
+ settings['normalize'] = settings['normalize'].lower() == 'true'
433
+ settings['rows'] = int(settings['rows'])
434
+ settings['columns'] = int(settings['columns'])
435
+ if settings['measurement'].lower() == 'none':
436
+ settings['measurement'] = None
437
+ if settings['threshold'].lower() == 'none':
438
+ settings['threshold'] = None
439
+
440
+ # Clear previous content instead of destroying the root
441
+ if hasattr(parent_frame, 'winfo_children'):
442
+ for widget in parent_frame.winfo_children():
443
+ widget.destroy()
444
+
445
+ # Start the annotate application in the same root window
446
+ annotate_app(parent_frame, settings)
447
+
448
+ run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=run_app,
449
+ font=tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL))
450
+ run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
451
+
452
+ return parent_frame
453
+
454
+ def annotate_app(parent_frame, settings):
455
+ global global_image_refs
456
+ global_image_refs.clear()
457
+ root = parent_frame.winfo_toplevel()
458
+ annotate_with_image_refs(settings, root, lambda: load_next_app(root))
459
+
460
+ def annotate_with_image_refs(settings, root, shutdown_callback):
461
+ from .gui_utils import proceed_with_app
462
+ from .gui import gui_app
463
+
464
+ settings = get_annotate_default_settings(settings)
465
+ src = settings['src']
466
+
467
+ db = os.path.join(src, 'measurements/measurements.db')
468
+ conn = sqlite3.connect(db)
469
+ c = conn.cursor()
470
+ c.execute('PRAGMA table_info(png_list)')
471
+ cols = c.fetchall()
472
+ if settings['annotation_column'] not in [col[1] for col in cols]:
473
+ c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
474
+ conn.commit()
475
+ conn.close()
476
+
477
+ 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'])
478
+
479
+ # Set the canvas background to black
480
+ root.configure(bg='black')
481
+
482
+ next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
483
+ next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
484
+ back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
485
+ back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
486
+ exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
487
+ exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
488
+
489
+ app.load_images()
490
+
491
+ # Store the shutdown function and next app details in the root
492
+ root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
493
+ root.next_app_func = proceed_with_app
494
+ root.next_app_args = ("Main App", gui_app) # Specify the main app function
495
+
496
+ def load_next_app(root):
497
+ # Get the next app function and arguments
498
+ next_app_func = root.next_app_func
499
+ next_app_args = root.next_app_args
500
+
501
+ if next_app_func:
502
+ try:
503
+ if not root.winfo_exists():
504
+ raise tk.TclError
505
+ next_app_func(root, *next_app_args)
506
+ except tk.TclError:
507
+ # Reinitialize root if it has been destroyed
508
+ new_root = tk.Tk()
509
+ width = new_root.winfo_screenwidth()
510
+ height = new_root.winfo_screenheight()
511
+ new_root.geometry(f"{width}x{height}")
512
+ new_root.title("SpaCr Application")
513
+ next_app_func(new_root, *next_app_args)
514
+
515
+
516
+ def gui_annotate():
517
+ root = tk.Tk()
518
+ width = root.winfo_screenwidth()
519
+ height = root.winfo_screenheight()
520
+ root.geometry(f"{width}x{height}")
521
+ root.title("Annotate Application")
522
+
523
+ # Clear previous content if any
524
+ if hasattr(root, 'content_frame'):
525
+ for widget in root.content_frame.winfo_children():
526
+ widget.destroy()
527
+ root.content_frame.grid_forget()
528
+ else:
529
+ root.content_frame = tk.Frame(root)
530
+ root.content_frame.grid(row=1, column=0, sticky="nsew")
531
+ root.grid_rowconfigure(1, weight=1)
532
+ root.grid_columnconfigure(0, weight=1)
533
+
534
+ initiate_annotation_app_root(root.content_frame)
535
+ create_menu_bar(root)
536
+ root.mainloop()
537
+
538
+ if __name__ == "__main__":
539
+ gui_annotate()
@@ -77,7 +77,7 @@ def initiate_classify_root(parent_frame):
77
77
  style_text_boxes(style)
78
78
  set_default_font(parent_frame, font_name="Helvetica", size=8)
79
79
 
80
- parent_frame.configure(bg='#333333')
80
+ parent_frame.configure(bg='black')
81
81
  parent_frame.grid_rowconfigure(0, weight=1)
82
82
  parent_frame.grid_columnconfigure(0, weight=1)
83
83
  fig_queue = Queue()
@@ -94,7 +94,7 @@ def initiate_classify_root(parent_frame):
94
94
  ax.xaxis.set_visible(False) # Hide the x-axis
95
95
  ax.yaxis.set_visible(False) # Hide the y-axis
96
96
  fig.tight_layout()
97
- fig.set_facecolor('#333333')
97
+ fig.set_facecolor('black')
98
98
  canvas.figure = fig
99
99
  fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
100
100
  fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
@@ -117,9 +117,9 @@ def initiate_classify_root(parent_frame):
117
117
  parent_frame.grid_columnconfigure(0, weight=1)
118
118
 
119
119
  # Settings Section
120
- settings_frame = tk.Frame(vertical_container, bg='#333333')
120
+ settings_frame = tk.Frame(vertical_container, bg='black')
121
121
  vertical_container.add(settings_frame, stretch="always")
122
- settings_label = ttk.Label(settings_frame, text="Settings", background="#333333", foreground="white")
122
+ settings_label = ttk.Label(settings_frame, text="Settings", background="black", foreground="white")
123
123
  settings_label.grid(row=0, column=0, pady=10, padx=10)
124
124
  scrollable_frame = ScrollableFrame(settings_frame, width=500)
125
125
  scrollable_frame.grid(row=1, column=0, sticky="nsew")
@@ -131,11 +131,11 @@ def initiate_classify_root(parent_frame):
131
131
  vars_dict = generate_fields(variables, scrollable_frame)
132
132
 
133
133
  # Button section
134
- import_btn = CustomButton(scrollable_frame.scrollable_frame, text="Import Settings", command=lambda: import_settings(scrollable_frame))
134
+ import_btn = CustomButton(scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(scrollable_frame), font=('Helvetica', 10))
135
135
  import_btn.grid(row=47, column=0, pady=20, padx=20)
136
- run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue))
136
+ run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue), font=('Helvetica', 10))
137
137
  run_button.grid(row=45, column=0, pady=20, padx=20)
138
- abort_button = CustomButton(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
138
+ abort_button = CustomButton(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, font=('Helvetica', 10))
139
139
  abort_button.grid(row=45, column=1, pady=20, padx=20)
140
140
  progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Processing: 0%", background="black", foreground="white") # Create progress field
141
141
  progress_label.grid(row=50, column=0, columnspan=2, sticky="ew", pady=(5, 0), padx=10)
@@ -143,23 +143,23 @@ def initiate_classify_root(parent_frame):
143
143
  # Plot Canvas Section
144
144
  plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
145
145
  vertical_container.add(plot_frame, stretch="always")
146
- figure = Figure(figsize=(30, 4), dpi=100, facecolor='#333333')
146
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
147
147
  plot = figure.add_subplot(111)
148
148
  plot.plot([], [])
149
149
  plot.axis('off')
150
150
  canvas = FigureCanvasTkAgg(figure, master=plot_frame)
151
- canvas.get_tk_widget().configure(cursor='arrow', background='#333333', highlightthickness=0)
151
+ canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
152
152
  canvas_widget = canvas.get_tk_widget()
153
153
  plot_frame.add(canvas_widget, stretch="always")
154
154
  canvas.draw()
155
155
  canvas.figure = figure
156
156
 
157
157
  # Console Section
158
- console_frame = tk.Frame(vertical_container, bg='#333333')
158
+ console_frame = tk.Frame(vertical_container, bg='black')
159
159
  vertical_container.add(console_frame, stretch="always")
160
- console_label = ttk.Label(console_frame, text="Console", background="#333333", foreground="white")
160
+ console_label = ttk.Label(console_frame, text="Console", background="black", foreground="white")
161
161
  console_label.grid(row=0, column=0, pady=10, padx=10)
162
- console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='#333333', fg='white', insertbackground='white')
162
+ console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
163
163
  console_output.grid(row=1, column=0, sticky="nsew")
164
164
  console_frame.grid_rowconfigure(1, weight=1)
165
165
  console_frame.grid_columnconfigure(0, weight=1)
@@ -177,9 +177,23 @@ def initiate_classify_root(parent_frame):
177
177
 
178
178
  def gui_classify():
179
179
  root = tk.Tk()
180
- root.geometry("1000x800")
181
- root.title("SpaCer: generate masks")
182
- initiate_classify_root(root),
180
+ width = root.winfo_screenwidth()
181
+ height = root.winfo_screenheight()
182
+ root.geometry(f"{width}x{height}")
183
+ root.title("SpaCr: classify objects")
184
+
185
+ # Clear previous content if any
186
+ if hasattr(root, 'content_frame'):
187
+ for widget in root.content_frame.winfo_children():
188
+ widget.destroy()
189
+ root.content_frame.grid_forget()
190
+ else:
191
+ root.content_frame = tk.Frame(root)
192
+ root.content_frame.grid(row=1, column=0, sticky="nsew")
193
+ root.grid_rowconfigure(1, weight=1)
194
+ root.grid_columnconfigure(0, weight=1)
195
+
196
+ initiate_classify_root(root.content_frame)
183
197
  create_menu_bar(root)
184
198
  root.mainloop()
185
199