spacr 0.2.2__py3-none-any.whl → 0.2.4__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.
Files changed (101) hide show
  1. spacr/app_annotate.py +3 -4
  2. spacr/core.py +58 -229
  3. spacr/gui.py +2 -1
  4. spacr/gui_core.py +83 -36
  5. spacr/gui_elements.py +422 -72
  6. spacr/gui_utils.py +58 -32
  7. spacr/io.py +121 -37
  8. spacr/measure.py +6 -8
  9. spacr/resources/icons/abort.png +0 -0
  10. spacr/resources/icons/classify.png +0 -0
  11. spacr/resources/icons/make_masks.png +0 -0
  12. spacr/resources/icons/mask.png +0 -0
  13. spacr/resources/icons/measure.png +0 -0
  14. spacr/resources/icons/ml_analyze.png +0 -0
  15. spacr/resources/icons/recruitment.png +0 -0
  16. spacr/resources/icons/regression.png +0 -0
  17. spacr/resources/icons/run.png +0 -0
  18. spacr/resources/icons/spacr_logo_rotation.gif +0 -0
  19. spacr/resources/icons/train_cellpose.png +0 -0
  20. spacr/resources/icons/umap.png +0 -0
  21. spacr/settings.py +1 -4
  22. spacr/utils.py +55 -0
  23. {spacr-0.2.2.dist-info → spacr-0.2.4.dist-info}/METADATA +1 -1
  24. spacr-0.2.4.dist-info/RECORD +58 -0
  25. spacr/alpha.py +0 -807
  26. spacr/annotate_app.py +0 -670
  27. spacr/annotate_app_v2.py +0 -670
  28. spacr/app_make_masks_v2.py +0 -686
  29. spacr/classify_app.py +0 -201
  30. spacr/cli.py +0 -41
  31. spacr/foldseek.py +0 -779
  32. spacr/get_alfafold_structures.py +0 -72
  33. spacr/gui_2.py +0 -157
  34. spacr/gui_annotate.py +0 -145
  35. spacr/gui_classify_app.py +0 -201
  36. spacr/gui_make_masks_app.py +0 -927
  37. spacr/gui_make_masks_app_v2.py +0 -688
  38. spacr/gui_mask_app.py +0 -249
  39. spacr/gui_measure_app.py +0 -246
  40. spacr/gui_run.py +0 -58
  41. spacr/gui_sim_app.py +0 -0
  42. spacr/gui_wrappers.py +0 -149
  43. spacr/icons/abort.png +0 -0
  44. spacr/icons/abort.svg +0 -1
  45. spacr/icons/download.png +0 -0
  46. spacr/icons/download.svg +0 -1
  47. spacr/icons/download_for_offline_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  48. spacr/icons/download_for_offline_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  49. spacr/icons/logo_spacr.png +0 -0
  50. spacr/icons/make_masks.png +0 -0
  51. spacr/icons/make_masks.svg +0 -1
  52. spacr/icons/map_barcodes.png +0 -0
  53. spacr/icons/map_barcodes.svg +0 -1
  54. spacr/icons/mask.png +0 -0
  55. spacr/icons/mask.svg +0 -1
  56. spacr/icons/measure.png +0 -0
  57. spacr/icons/measure.svg +0 -1
  58. spacr/icons/play_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  59. spacr/icons/play_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  60. spacr/icons/run.png +0 -0
  61. spacr/icons/run.svg +0 -1
  62. spacr/icons/sequencing.png +0 -0
  63. spacr/icons/sequencing.svg +0 -1
  64. spacr/icons/settings.png +0 -0
  65. spacr/icons/settings.svg +0 -1
  66. spacr/icons/settings_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  67. spacr/icons/settings_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  68. spacr/icons/stop_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  69. spacr/icons/stop_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  70. spacr/icons/theater_comedy_100dp_E8EAED_FILL0_wght100_GRAD200_opsz48.png +0 -0
  71. spacr/icons/theater_comedy_100dp_E8EAED_FILL0_wght100_GRAD200_opsz48.svg +0 -1
  72. spacr/make_masks_app.py +0 -929
  73. spacr/make_masks_app_v2.py +0 -688
  74. spacr/mask_app.py +0 -249
  75. spacr/measure_app.py +0 -246
  76. spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model +0 -0
  77. spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
  78. spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
  79. spacr/old_code.py +0 -358
  80. spacr/resources/icons/abort.svg +0 -1
  81. spacr/resources/icons/annotate.svg +0 -1
  82. spacr/resources/icons/classify.svg +0 -1
  83. spacr/resources/icons/download.svg +0 -1
  84. spacr/resources/icons/icon.psd +0 -0
  85. spacr/resources/icons/make_masks.svg +0 -1
  86. spacr/resources/icons/map_barcodes.svg +0 -1
  87. spacr/resources/icons/mask.svg +0 -1
  88. spacr/resources/icons/measure.svg +0 -1
  89. spacr/resources/icons/run.svg +0 -1
  90. spacr/resources/icons/run_2.png +0 -0
  91. spacr/resources/icons/run_2.svg +0 -1
  92. spacr/resources/icons/sequencing.svg +0 -1
  93. spacr/resources/icons/settings.svg +0 -1
  94. spacr/resources/icons/train_cellpose.svg +0 -1
  95. spacr/test_gui.py +0 -0
  96. spacr-0.2.2.dist-info/RECORD +0 -126
  97. /spacr/resources/icons/{cellpose.png → cellpose_all.png} +0 -0
  98. {spacr-0.2.2.dist-info → spacr-0.2.4.dist-info}/LICENSE +0 -0
  99. {spacr-0.2.2.dist-info → spacr-0.2.4.dist-info}/WHEEL +0 -0
  100. {spacr-0.2.2.dist-info → spacr-0.2.4.dist-info}/entry_points.txt +0 -0
  101. {spacr-0.2.2.dist-info → spacr-0.2.4.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py CHANGED
@@ -3,7 +3,7 @@ import tkinter as tk
3
3
  from tkinter import ttk
4
4
  import tkinter.font as tkFont
5
5
  from queue import Queue
6
- from tkinter import Label
6
+ from tkinter import Label, Frame, Button
7
7
  import numpy as np
8
8
  from PIL import Image, ImageOps, ImageTk
9
9
  from concurrent.futures import ThreadPoolExecutor
@@ -15,19 +15,20 @@ from skimage.draw import polygon, line
15
15
  from skimage.transform import resize
16
16
  from scipy.ndimage import binary_fill_holes, label
17
17
  from tkinter import ttk, scrolledtext
18
- import platform
19
18
 
20
- def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="Arial", font_size=12, bg_color='black', fg_color='white', active_color='blue', inactive_color='dark_gray'):
19
+ def set_default_font(root, font_name="Arial", size=12):
20
+ default_font = (font_name, size)
21
+ root.option_add("*Font", default_font)
22
+ root.option_add("*TButton.Font", default_font)
23
+ root.option_add("*TLabel.Font", default_font)
24
+ root.option_add("*TEntry.Font", default_font)
21
25
 
22
- if platform.system() == 'Darwin':
23
- bg_color = '#313131'
24
- else:
25
- bg_color = '#000000'
26
+ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="Arial", font_size=12, bg_color='black', fg_color='white', active_color='blue', inactive_color='dark_gray'):
26
27
 
27
28
  if active_color == 'teal':
28
29
  active_color = '#008080'
29
30
  if inactive_color == 'dark_gray':
30
- inactive_color = '#050505'
31
+ inactive_color = '#2B2B2B' # '#333333' #'#050505'
31
32
  if bg_color == 'black':
32
33
  bg_color = '#000000'
33
34
  if fg_color == 'white':
@@ -35,27 +36,39 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
35
36
  if active_color == 'blue':
36
37
  active_color = '#007BFF'
37
38
 
39
+ padding = '5 5 5 5'
38
40
  font_style = tkFont.Font(family=font_family, size=font_size)
39
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground=bg_color, foreground=fg_color, font=font_style)
40
- style.configure('TCombobox', fieldbackground=bg_color, background=bg_color, foreground=fg_color, selectbackground=bg_color, selectforeground=fg_color, font=font_style)
41
- style.map('TCombobox', fieldbackground=[('readonly', bg_color)], foreground=[('readonly', fg_color)], selectbackground=[('readonly', bg_color)], selectforeground=[('readonly', fg_color)])
42
- style.configure('Custom.TButton', background=bg_color, foreground=fg_color, bordercolor=fg_color, focusthickness=3, focuscolor=fg_color, font=(font_family, font_size))
43
- style.map('Custom.TButton', background=[('active', active_color), ('!active', bg_color)], foreground=[('active', fg_color), ('!active', fg_color)], bordercolor=[('active', fg_color), ('!active', fg_color)])
44
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background=bg_color, foreground=fg_color, font=font_style)
45
- style.configure('Spacr.TCheckbutton', background=bg_color, foreground=fg_color, indicatoron=False, relief='flat', font="15")
46
- style.map('Spacr.TCheckbutton', background=[('selected', bg_color), ('active', bg_color)], foreground=[('selected', fg_color), ('active', fg_color)])
47
- style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_style)
41
+
42
+ style.theme_use('clam')
43
+
44
+ style.configure('TEntry', padding=padding)
45
+ style.configure('TCombobox', padding=padding)
46
+ style.configure('Spacr.TEntry', padding=padding)
47
+ style.configure('TEntry', padding=padding)
48
+ style.configure('Spacr.TEntry', padding=padding)
49
+ style.configure('Custom.TLabel', padding=padding)
50
+ #style.configure('Spacr.TCheckbutton', padding=padding)
51
+ style.configure('TButton', padding=padding)
52
+
48
53
  style.configure('TFrame', background=bg_color)
49
54
  style.configure('TPanedwindow', background=bg_color)
50
- style.configure('TNotebook', background=bg_color, tabmargins=[2, 5, 2, 0])
51
- style.configure('TNotebook.Tab', background=bg_color, foreground=fg_color, padding=[5, 5], font=font_style)
52
- style.map('TNotebook.Tab', background=[('selected', active_color), ('active', active_color)], foreground=[('selected', fg_color), ('active', fg_color)])
53
- style.configure('TButton', background=bg_color, foreground=fg_color, padding='5 5 5 5', font=font_style)
54
- style.map('TButton', background=[('active', active_color), ('disabled', inactive_color)])
55
- style.configure('Vertical.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
56
- style.configure('Horizontal.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
57
- style.configure('Custom.TLabelFrame', font=(font_family, font_size, 'bold'), background=bg_color, foreground='white', relief='solid', borderwidth=1)
58
- style.configure('Custom.TLabelFrame.Label', background=bg_color, foreground='white', font=(font_family, font_size, 'bold'))
55
+ style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_style)
56
+
57
+
58
+ #style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background=bg_color, foreground=fg_color, font=font_style)
59
+ #style.configure('Spacr.TCheckbutton', background=bg_color, foreground=fg_color, indicatoron=False, relief='flat', font="15")
60
+ #style.map('Spacr.TCheckbutton', background=[('selected', bg_color), ('active', bg_color)], foreground=[('selected', fg_color), ('active', fg_color)])
61
+
62
+
63
+ #style.configure('TNotebook', background=bg_color, tabmargins=[2, 5, 2, 0])
64
+ #style.configure('TNotebook.Tab', background=bg_color, foreground=fg_color, padding=[5, 5], font=font_style)
65
+ #style.map('TNotebook.Tab', background=[('selected', active_color), ('active', active_color)], foreground=[('selected', fg_color), ('active', fg_color)])
66
+ #style.configure('TButton', background=bg_color, foreground=fg_color, padding='5 5 5 5', font=font_style)
67
+ #style.map('TButton', background=[('active', active_color), ('disabled', inactive_color)])
68
+ #style.configure('Vertical.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
69
+ #style.configure('Horizontal.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
70
+ #style.configure('Custom.TLabelFrame', font=(font_family, font_size, 'bold'), background=bg_color, foreground='white', relief='solid', borderwidth=1)
71
+ #style.configure('Custom.TLabelFrame.Label', background=bg_color, foreground='white', font=(font_family, font_size, 'bold'))
59
72
 
60
73
  if parent_frame:
61
74
  parent_frame.configure(bg=bg_color)
@@ -86,20 +99,245 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
86
99
 
87
100
  return {'font_family': font_family, 'font_size': font_size, 'bg_color': bg_color, 'fg_color': fg_color, 'active_color': active_color, 'inactive_color': inactive_color}
88
101
 
89
- def set_default_font(root, font_name="Arial", size=12):
90
- default_font = (font_name, size)
91
- root.option_add("*Font", default_font)
92
- root.option_add("*TButton.Font", default_font)
93
- root.option_add("*TLabel.Font", default_font)
94
- root.option_add("*TEntry.Font", default_font)
102
+ class spacrEntry(tk.Frame):
103
+ def __init__(self, parent, textvariable=None, outline=False, *args, **kwargs):
104
+ super().__init__(parent, *args, **kwargs)
105
+
106
+ # Set dark style
107
+ style_out = set_dark_style(ttk.Style())
108
+ self.bg_color = style_out['inactive_color']
109
+ self.active_color = style_out['active_color']
110
+ self.fg_color = style_out['fg_color']
111
+ self.outline = outline
112
+ self.font_family = style_out['font_family']
113
+ self.font_size = style_out['font_size']
114
+
115
+ # Set the background color of the frame
116
+ self.configure(bg=style_out['bg_color'])
117
+
118
+ # Create a canvas for the rounded rectangle background
119
+ self.canvas_width = 220 # Adjusted for padding
120
+ self.canvas_height = 40 # Adjusted for padding
121
+ self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=style_out['bg_color'])
122
+ self.canvas.pack()
123
+
124
+ self.entry = tk.Entry(self, textvariable=textvariable, bd=0, highlightthickness=0, fg=self.fg_color, font=(self.font_family, self.font_size), bg=self.bg_color)
125
+ self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=190, height=20) # Centered positioning
126
+
127
+ # Bind events to change the background color on focus
128
+ self.entry.bind("<FocusIn>", self.on_focus_in)
129
+ self.entry.bind("<FocusOut>", self.on_focus_out)
130
+
131
+ self.draw_rounded_rectangle(self.bg_color)
132
+
133
+ def draw_rounded_rectangle(self, color):
134
+ radius = 15 # Increased radius for more rounded corners
135
+ x0, y0 = 10, 5
136
+ x1, y1 = 210, 35
137
+ self.canvas.delete("all")
138
+ self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
139
+ self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
140
+ self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
141
+ self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
142
+ self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
143
+ self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
144
+
145
+ def on_focus_in(self, event):
146
+ self.draw_rounded_rectangle(self.active_color)
147
+ self.entry.config(bg=self.active_color)
148
+
149
+ def on_focus_out(self, event):
150
+ self.draw_rounded_rectangle(self.bg_color)
151
+ self.entry.config(bg=self.bg_color)
152
+
153
+ class spacrCheck(tk.Frame):
154
+ def __init__(self, parent, text="", variable=None, *args, **kwargs):
155
+ super().__init__(parent, *args, **kwargs)
156
+
157
+ style_out = set_dark_style(ttk.Style())
158
+ self.bg_color = style_out['bg_color']
159
+ self.active_color = style_out['active_color']
160
+ self.fg_color = style_out['fg_color']
161
+ self.inactive_color = style_out['inactive_color']
162
+ self.variable = variable
163
+
164
+ self.configure(bg=self.bg_color)
165
+
166
+ # Create a canvas for the rounded square background
167
+ self.canvas_width = 20
168
+ self.canvas_height = 20
169
+ self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=self.bg_color)
170
+ self.canvas.pack()
171
+
172
+ # Draw the initial rounded square based on the variable's value
173
+ self.draw_rounded_square(self.active_color if self.variable.get() else self.inactive_color)
174
+
175
+ # Bind variable changes to update the checkbox
176
+ self.variable.trace_add('write', self.update_check)
177
+
178
+ # Bind click event to toggle the variable
179
+ self.canvas.bind("<Button-1>", self.toggle_variable)
180
+
181
+ def draw_rounded_square(self, color):
182
+ radius = 5 # Adjust the radius for more rounded corners
183
+ x0, y0 = 2, 2
184
+ x1, y1 = 18, 18
185
+ self.canvas.delete("all")
186
+ self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=self.fg_color)
187
+ self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=self.fg_color)
188
+ self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=self.fg_color)
189
+ self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=self.fg_color)
190
+ self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
191
+ self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
192
+ self.canvas.create_line(x0 + radius / 2, y0, x1 - radius / 2, y0, fill=self.fg_color)
193
+ self.canvas.create_line(x0 + radius / 2, y1, x1 - radius / 2, y1, fill=self.fg_color)
194
+ self.canvas.create_line(x0, y0 + radius / 2, x0, y1 - radius / 2, fill=self.fg_color)
195
+ self.canvas.create_line(x1, y0 + radius / 2, x1, y1 - radius / 2, fill=self.fg_color)
196
+
197
+ def update_check(self, *args):
198
+ self.draw_rounded_square(self.active_color if self.variable.get() else self.inactive_color)
199
+
200
+ def toggle_variable(self, event):
201
+ self.variable.set(not self.variable.get())
202
+
203
+ class spacrCombo(tk.Frame):
204
+ def __init__(self, parent, textvariable=None, values=None, *args, **kwargs):
205
+ super().__init__(parent, *args, **kwargs)
206
+
207
+ # Set dark style
208
+ style_out = set_dark_style(ttk.Style())
209
+ self.bg_color = style_out['bg_color']
210
+ self.active_color = style_out['active_color']
211
+ self.fg_color = style_out['fg_color']
212
+ self.inactive_color = style_out['inactive_color']
213
+ self.font_family = style_out['font_family']
214
+ self.font_size = style_out['font_size']
215
+
216
+ self.values = values or []
217
+
218
+ # Create a canvas for the rounded rectangle background
219
+ self.canvas_width = 220 # Adjusted for padding
220
+ self.canvas_height = 40 # Adjusted for padding
221
+ self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=self.bg_color)
222
+ self.canvas.pack()
223
+
224
+ self.var = textvariable if textvariable else tk.StringVar()
225
+ self.selected_value = self.var.get()
226
+
227
+ # Create the label to display the selected value
228
+ self.label = tk.Label(self, text=self.selected_value, bg=self.inactive_color, fg=self.fg_color, font=(self.font_family, self.font_size))
229
+ self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
230
+
231
+ # Bind events to open the dropdown menu
232
+ self.canvas.bind("<Button-1>", self.on_click)
233
+ self.label.bind("<Button-1>", self.on_click)
234
+
235
+ self.draw_rounded_rectangle(self.inactive_color)
236
+
237
+ self.dropdown_menu = None
238
+
239
+ def draw_rounded_rectangle(self, color):
240
+ radius = 15 # Increased radius for more rounded corners
241
+ x0, y0 = 10, 5
242
+ x1, y1 = 210, 35
243
+ self.canvas.delete("all")
244
+ self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
245
+ self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
246
+ self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
247
+ self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
248
+ self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
249
+ self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
250
+ self.label.config(bg=color) # Update label background to match rectangle color
251
+
252
+ def on_click(self, event):
253
+ if self.dropdown_menu is None:
254
+ self.open_dropdown()
255
+ else:
256
+ self.close_dropdown()
257
+
258
+ def open_dropdown(self):
259
+ self.draw_rounded_rectangle(self.active_color)
260
+
261
+ self.dropdown_menu = tk.Toplevel(self)
262
+ self.dropdown_menu.wm_overrideredirect(True)
263
+
264
+ x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
265
+ self.dropdown_menu.geometry(f"{width}x{len(self.values) * 30}+{x}+{y + height}")
266
+
267
+ for index, value in enumerate(self.values):
268
+ display_text = value if value is not None else 'None'
269
+ item = tk.Label(self.dropdown_menu, text=display_text, bg=self.inactive_color, fg=self.fg_color, font=(self.font_family, self.font_size), anchor='w')
270
+ item.pack(fill='both')
271
+ item.bind("<Button-1>", lambda e, v=value: self.on_select(v))
272
+ item.bind("<Enter>", lambda e, w=item: w.config(bg=self.active_color))
273
+ item.bind("<Leave>", lambda e, w=item: w.config(bg=self.inactive_color))
274
+
275
+ def close_dropdown(self):
276
+ self.draw_rounded_rectangle(self.inactive_color)
277
+
278
+ if self.dropdown_menu:
279
+ self.dropdown_menu.destroy()
280
+ self.dropdown_menu = None
281
+
282
+ def on_select(self, value):
283
+ display_text = value if value is not None else 'None'
284
+ self.var.set(value)
285
+ self.label.config(text=display_text)
286
+ self.selected_value = value
287
+ self.close_dropdown()
288
+
289
+ def set(self, value):
290
+ display_text = value if value is not None else 'None'
291
+ self.var.set(value)
292
+ self.label.config(text=display_text)
293
+ self.selected_value = value
95
294
 
96
295
  class spacrDropdownMenu(tk.OptionMenu):
97
296
  def __init__(self, parent, variable, options, command=None, **kwargs):
98
297
  self.variable = variable
99
- self.variable.set("Select Category")
298
+ self.variable.set("Settings Category")
100
299
  super().__init__(parent, self.variable, *options, command=command, **kwargs)
101
300
  self.update_styles()
102
301
 
302
+ # Hide the original button
303
+ self.configure(highlightthickness=0, relief='flat', bg='#2B2B2B', fg='#2B2B2B')
304
+
305
+ # Create custom button
306
+ self.create_custom_button()
307
+
308
+ def create_custom_button(self):
309
+ self.canvas_width = self.winfo_reqwidth() # Use the required width of the widget
310
+ self.canvas_height = 40 # Adjust the height as needed
311
+ self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg='#2B2B2B')
312
+ self.canvas.pack()
313
+ self.label = tk.Label(self.canvas, text="Settings Category", bg='#2B2B2B', fg='#ffffff', font=('Arial', 12))
314
+ self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
315
+ self.draw_rounded_rectangle('#2B2B2B')
316
+
317
+ # Bind the click event to open the dropdown menu
318
+ self.canvas.bind("<Button-1>", self.on_click)
319
+ self.label.bind("<Button-1>", self.on_click)
320
+
321
+ def draw_rounded_rectangle(self, color):
322
+ radius = 15
323
+ x0, y0 = 10, 5
324
+ x1, y1 = self.canvas_width - 10, self.canvas_height - 5 # Adjust based on canvas size
325
+ self.canvas.delete("all")
326
+ self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
327
+ self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
328
+ self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
329
+ self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
330
+ self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
331
+ self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
332
+ self.label.config(bg=color) # Update label background to match rectangle color
333
+
334
+ def on_click(self, event):
335
+ self.post_menu()
336
+
337
+ def post_menu(self):
338
+ x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
339
+ self.menu.post(x, y + height)
340
+
103
341
  def update_styles(self, active_categories=None):
104
342
  style = ttk.Style()
105
343
  style_out = set_dark_style(style, widgets=[self])
@@ -124,6 +362,56 @@ class spacrCheckbutton(ttk.Checkbutton):
124
362
  style = ttk.Style()
125
363
  _ = set_dark_style(style, widgets=[self])
126
364
 
365
+ class spacrProgressBar(ttk.Progressbar):
366
+ def __init__(self, parent, *args, **kwargs):
367
+ super().__init__(parent, *args, **kwargs)
368
+
369
+ # Get the style colors
370
+ style_out = set_dark_style(ttk.Style())
371
+
372
+ self.fg_color = style_out['fg_color']
373
+ self.bg_color = style_out['bg_color']
374
+ self.active_color = style_out['active_color']
375
+ self.inactive_color = style_out['inactive_color']
376
+
377
+ # Configure the style for the progress bar
378
+ self.style = ttk.Style()
379
+ self.style.configure(
380
+ "spacr.Horizontal.TProgressbar",
381
+ troughcolor=self.bg_color,
382
+ background=self.active_color,
383
+ thickness=20,
384
+ troughrelief='flat',
385
+ borderwidth=0
386
+ )
387
+ self.configure(style="spacr.Horizontal.TProgressbar")
388
+
389
+ # Set initial value to 0
390
+ self['value'] = 0
391
+
392
+ # Create the progress label
393
+ self.progress_label = tk.Label(parent, text="Processing: 0/0", anchor='w', justify='left', bg=self.inactive_color, fg=self.fg_color)
394
+ self.progress_label.grid(row=1, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
395
+
396
+ # Initialize attributes for time and operation
397
+ self.operation_type = None
398
+ self.time_image = None
399
+ self.time_batch = None
400
+ self.time_left = None
401
+
402
+ def update_label(self):
403
+ # Update the progress label with current progress and additional info
404
+ label_text = f"Processing: {self['value']}/{self['maximum']}"
405
+ if self.operation_type:
406
+ label_text += f", {self.operation_type}"
407
+ if self.time_image:
408
+ label_text += f", Time/image: {self.time_image:.3f} sec"
409
+ if self.time_batch:
410
+ label_text += f", Time/batch: {self.time_batch:.3f} sec"
411
+ if self.time_left:
412
+ label_text += f", Time_left: {self.time_left:.3f} min"
413
+ self.progress_label.config(text=label_text)
414
+
127
415
  class spacrFrame(ttk.Frame):
128
416
  def __init__(self, container, width=None, *args, bg='black', **kwargs):
129
417
  super().__init__(container, *args, **kwargs)
@@ -196,7 +484,7 @@ class spacrLabel(tk.Frame):
196
484
  self.canvas.itemconfig(self.label_text, text=text)
197
485
 
198
486
  class spacrButton(tk.Frame):
199
- def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=True, *args, **kwargs):
487
+ def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, *args, **kwargs):
200
488
  super().__init__(parent, *args, **kwargs)
201
489
 
202
490
  self.text = text.capitalize() # Capitalize only the first letter of the text
@@ -220,10 +508,12 @@ class spacrButton(tk.Frame):
220
508
  # Apply dark style and get color settings
221
509
  color_settings = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
222
510
 
511
+ self.inactive_color = color_settings['inactive_color']
512
+
223
513
  if self.outline:
224
- self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=color_settings['bg_color'], outline=color_settings['fg_color'])
514
+ self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=self.inactive_color, outline=color_settings['fg_color'])
225
515
  else:
226
- self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=color_settings['bg_color'], outline=color_settings['bg_color'])
516
+ self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=self.inactive_color, outline=self.inactive_color)
227
517
 
228
518
  self.load_icon()
229
519
  self.font_style = font if font else ("Arial", 12) # Default font if not provided
@@ -238,7 +528,7 @@ class spacrButton(tk.Frame):
238
528
  self.canvas.bind("<Leave>", self.on_leave)
239
529
  self.canvas.bind("<Button-1>", self.on_click)
240
530
 
241
- self.bg_color = color_settings['bg_color']
531
+ self.bg_color = self.inactive_color
242
532
  self.active_color = color_settings['active_color']
243
533
  self.fg_color = color_settings['fg_color']
244
534
  self.is_zoomed_in = False # Track zoom state for smooth transitions
@@ -255,7 +545,7 @@ class spacrButton(tk.Frame):
255
545
  icon_image = Image.open(self.get_icon_path("default"))
256
546
  print(f'Icon not found: {icon_path}. Using default icon instead.')
257
547
 
258
- initial_size = int(self.size * 0.9) # Make the initial size slightly smaller
548
+ initial_size = int(self.size * 0.65) # 65% of button size initially
259
549
  self.original_icon_image = icon_image.resize((initial_size, initial_size), Image.Resampling.LANCZOS)
260
550
  self.icon_photo = ImageTk.PhotoImage(self.original_icon_image)
261
551
 
@@ -270,13 +560,13 @@ class spacrButton(tk.Frame):
270
560
  self.canvas.itemconfig(self.button_bg, fill=self.active_color)
271
561
  self.update_description(event)
272
562
  if not self.is_zoomed_in:
273
- self.animate_zoom(1.1) # Zoom in the icon by 10%
563
+ self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
274
564
 
275
565
  def on_leave(self, event=None):
276
- self.canvas.itemconfig(self.button_bg, fill=self.bg_color)
566
+ self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
277
567
  self.clear_description(event)
278
568
  if self.is_zoomed_in:
279
- self.animate_zoom(1.0) # Reset the icon size
569
+ self.animate_zoom(0.65) # Reset the icon size to 65% of button size
280
570
 
281
571
  def on_click(self, event=None):
282
572
  if self.command:
@@ -321,7 +611,7 @@ class spacrButton(tk.Frame):
321
611
  parent = parent.master
322
612
 
323
613
  def animate_zoom(self, target_scale, steps=10, delay=10):
324
- current_scale = 1.1 if self.is_zoomed_in else 1.0
614
+ current_scale = 0.85 if self.is_zoomed_in else 0.65
325
615
  step_scale = (target_scale - current_scale) / steps
326
616
  self._animate_step(current_scale, step_scale, steps, delay)
327
617
 
@@ -335,13 +625,13 @@ class spacrButton(tk.Frame):
335
625
 
336
626
  def zoom_icon(self, scale_factor):
337
627
  # Resize the original icon image
338
- new_size = int(self.size * 0.9 * scale_factor)
628
+ new_size = int(self.size * scale_factor)
339
629
  resized_icon = self.original_icon_image.resize((new_size, new_size), Image.Resampling.LANCZOS)
340
630
  self.icon_photo = ImageTk.PhotoImage(resized_icon)
341
631
 
342
632
  # Update the icon on the canvas
343
633
  self.canvas.itemconfig(self.button_icon, image=self.icon_photo)
344
- self.canvas.image = self.icon_photo # Keep a reference to avoid garbage collection
634
+ self.canvas.image = self.icon_photo
345
635
 
346
636
  class spacrSwitch(ttk.Frame):
347
637
  def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
@@ -1296,14 +1586,19 @@ class ModifyMaskApp:
1296
1586
  self.update_display()
1297
1587
 
1298
1588
  class AnnotateApp:
1299
- 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):
1589
+ def __init__(self, root, db_path, src, image_type=None, channels=None, image_size=200, annotation_column='annotate', normalize=False, percentiles=(1, 99), measurement=None, threshold=None):
1300
1590
  self.root = root
1301
1591
  self.db_path = db_path
1302
1592
  self.src = src
1303
1593
  self.index = 0
1304
- self.grid_rows = grid_rows
1305
- self.grid_cols = grid_cols
1306
- self.image_size = image_size
1594
+
1595
+ if isinstance(image_size, list):
1596
+ self.image_size = (int(image_size[0]), int(image_size[0]))
1597
+ elif isinstance(image_size, int):
1598
+ self.image_size = (image_size, image_size)
1599
+ else:
1600
+ raise ValueError("Invalid image size")
1601
+
1307
1602
  self.annotation_column = annotation_column
1308
1603
  self.image_type = image_type
1309
1604
  self.channels = channels
@@ -1315,22 +1610,72 @@ class AnnotateApp:
1315
1610
  self.adjusted_to_original_paths = {}
1316
1611
  self.terminate = False
1317
1612
  self.update_queue = Queue()
1318
- self.status_label = Label(self.root, text="", font=("Arial", 12))
1319
- self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
1320
1613
  self.measurement = measurement
1321
1614
  self.threshold = threshold
1322
1615
 
1616
+ style_out = set_dark_style(ttk.Style())
1617
+ self.root.configure(bg=style_out['inactive_color'])
1618
+
1323
1619
  self.filtered_paths_annotations = []
1324
1620
  self.prefilter_paths_annotations()
1325
1621
 
1326
1622
  self.db_update_thread = threading.Thread(target=self.update_database_worker)
1327
1623
  self.db_update_thread.start()
1328
1624
 
1329
- for i in range(grid_rows * grid_cols):
1330
- label = Label(root)
1331
- label.grid(row=i // grid_cols, column=i % grid_cols)
1625
+ # Set the initial window size and make it fit the screen size
1626
+ self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}")
1627
+ self.root.update_idletasks()
1628
+
1629
+ # Create the status label
1630
+ self.status_label = Label(root, text="", font=("Arial", 12), bg=self.root.cget('bg'))
1631
+ self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
1632
+
1633
+ # Place the buttons at the bottom right
1634
+ self.button_frame = Frame(root, bg=self.root.cget('bg'))
1635
+ self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
1636
+
1637
+ self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
1638
+ self.next_button.pack(side="right", padx=5)
1639
+
1640
+ self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
1641
+ self.previous_button.pack(side="right", padx=5)
1642
+
1643
+ self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
1644
+ self.exit_button.pack(side="right", padx=5)
1645
+
1646
+ # Calculate grid rows and columns based on the root window size and image size
1647
+ self.calculate_grid_dimensions()
1648
+
1649
+ # Create a frame to hold the image grid
1650
+ self.grid_frame = Frame(root, bg=self.root.cget('bg'))
1651
+ self.grid_frame.grid(row=0, column=0, columnspan=2, padx=0, pady=0, sticky="nsew")
1652
+
1653
+ for i in range(self.grid_rows * self.grid_cols):
1654
+ label = Label(self.grid_frame, bg=self.root.cget('bg'))
1655
+ label.grid(row=i // self.grid_cols, column=i % self.grid_cols, padx=2, pady=2, sticky="nsew")
1332
1656
  self.labels.append(label)
1333
1657
 
1658
+ # Make the grid frame resize with the window
1659
+ self.root.grid_rowconfigure(0, weight=1)
1660
+ self.root.grid_columnconfigure(0, weight=1)
1661
+ self.root.grid_columnconfigure(1, weight=1)
1662
+
1663
+ for row in range(self.grid_rows):
1664
+ self.grid_frame.grid_rowconfigure(row, weight=1)
1665
+ for col in range(self.grid_cols):
1666
+ self.grid_frame.grid_columnconfigure(col, weight=1)
1667
+
1668
+ def calculate_grid_dimensions(self):
1669
+ window_width = self.root.winfo_width()
1670
+ window_height = self.root.winfo_height()
1671
+
1672
+ self.grid_cols = window_width // (self.image_size[0] + 4)
1673
+ self.grid_rows = (window_height - self.button_frame.winfo_height() - 4) // (self.image_size[1] + 4)
1674
+
1675
+ # Update to make sure grid_rows and grid_cols are at least 1
1676
+ self.grid_cols = max(1, self.grid_cols)
1677
+ self.grid_rows = max(1, self.grid_rows)
1678
+
1334
1679
  def prefilter_paths_annotations(self):
1335
1680
  from .io import _read_and_join_tables
1336
1681
  from .utils import is_list_of_lists
@@ -1584,6 +1929,7 @@ class AnnotateApp:
1584
1929
  self.update_queue.put(self.pending_updates.copy())
1585
1930
  self.pending_updates.clear()
1586
1931
  self.index += self.grid_rows * self.grid_cols
1932
+ self.prefilter_paths_annotations() # Re-fetch annotations from the database
1587
1933
  self.load_images()
1588
1934
 
1589
1935
  def previous_page(self):
@@ -1593,34 +1939,38 @@ class AnnotateApp:
1593
1939
  self.index -= self.grid_rows * self.grid_cols
1594
1940
  if self.index < 0:
1595
1941
  self.index = 0
1942
+ self.prefilter_paths_annotations() # Re-fetch annotations from the database
1596
1943
  self.load_images()
1597
1944
 
1598
1945
  def shutdown(self):
1599
1946
  self.terminate = True
1600
1947
  self.update_queue.put(self.pending_updates.copy())
1601
- self.pending_updates.clear()
1602
- self.db_update_thread.join()
1603
- self.root.quit()
1604
- self.root.destroy()
1605
- print(f'Quit application')
1948
+ if not self.pending_updates:
1949
+ self.pending_updates.clear()
1950
+ self.db_update_thread.join()
1951
+ self.root.quit()
1952
+ self.root.destroy()
1953
+ print(f'Quit application')
1954
+ else:
1955
+ print('Waiting for pending updates to finish before quitting')
1606
1956
 
1607
1957
  def create_menu_bar(root):
1608
1958
  from .gui import initiate_root
1609
1959
  gui_apps = {
1610
- "Mask": (lambda frame: initiate_root(frame, 'mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
1611
- "Measure": (lambda frame: initiate_root(frame, 'measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
1612
- "Annotate": (lambda frame: initiate_root(frame, 'annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
1613
- "Make Masks": (lambda frame: initiate_root(frame, 'make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
1614
- "Classify": (lambda frame: initiate_root(frame, 'classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
1615
- "Sequencing": (lambda frame: initiate_root(frame, 'sequencing'), "Analyze sequencing data."),
1616
- "Umap": (lambda frame: initiate_root(frame, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
1617
- "Train Cellpose": (lambda frame: initiate_root(frame, 'train_cellpose'), "Train custom Cellpose models."),
1618
- "ML Analyze": (lambda frame: initiate_root(frame, 'ml_analyze'), "Machine learning analysis of data."),
1619
- "Cellpose Masks": (lambda frame: initiate_root(frame, 'cellpose_masks'), "Generate Cellpose masks."),
1620
- "Cellpose All": (lambda frame: initiate_root(frame, 'cellpose_all'), "Run Cellpose on all images."),
1621
- "Map Barcodes": (lambda frame: initiate_root(frame, 'map_barcodes'), "Map barcodes to data."),
1622
- "Regression": (lambda frame: initiate_root(frame, 'regression'), "Perform regression analysis."),
1623
- "Recruitment": (lambda frame: initiate_root(frame, 'recruitment'), "Analyze recruitment data.")
1960
+ "Mask": (lambda frame: initiate_root(frame, settings_type='mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
1961
+ "Measure": (lambda frame: initiate_root(frame, settings_type='measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
1962
+ "Annotate": (lambda frame: initiate_root(frame, settings_type='annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
1963
+ "Make Masks": (lambda frame: initiate_root(frame, settings_type='make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
1964
+ "Classify": (lambda frame: initiate_root(frame, settings_type='classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
1965
+ "Sequencing": (lambda frame: initiate_root(frame, settings_type='sequencing'), "Analyze sequencing data."),
1966
+ "Umap": (lambda frame: initiate_root(frame, settings_type='umap'), "Generate UMAP embeddings with datapoints represented as images."),
1967
+ "Train Cellpose": (lambda frame: initiate_root(frame, settings_type='train_cellpose'), "Train custom Cellpose models."),
1968
+ "ML Analyze": (lambda frame: initiate_root(frame, settings_type='ml_analyze'), "Machine learning analysis of data."),
1969
+ "Cellpose Masks": (lambda frame: initiate_root(frame, settings_type='cellpose_masks'), "Generate Cellpose masks."),
1970
+ "Cellpose All": (lambda frame: initiate_root(frame, settings_type='cellpose_all'), "Run Cellpose on all images."),
1971
+ "Map Barcodes": (lambda frame: initiate_root(frame, settings_type='map_barcodes'), "Map barcodes to data."),
1972
+ "Regression": (lambda frame: initiate_root(frame, settings_type='regression'), "Perform regression analysis."),
1973
+ "Recruitment": (lambda frame: initiate_root(frame, settings_type='recruitment'), "Analyze recruitment data.")
1624
1974
  }
1625
1975
 
1626
1976
  def load_app_wrapper(app_name, app_func):