spacr 0.2.21__py3-none-any.whl → 0.2.31__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 +3 -4
- spacr/core.py +58 -229
- spacr/gui_core.py +83 -36
- spacr/gui_elements.py +414 -63
- spacr/gui_utils.py +58 -32
- spacr/io.py +121 -37
- spacr/measure.py +6 -8
- spacr/resources/icons/measure.png +0 -0
- spacr/resources/icons/ml_analyze.png +0 -0
- spacr/resources/icons/recruitment.png +0 -0
- spacr/resources/icons/spacr_logo_rotation.gif +0 -0
- spacr/resources/icons/train_cellpose.png +0 -0
- spacr/settings.py +1 -4
- spacr/utils.py +55 -0
- {spacr-0.2.21.dist-info → spacr-0.2.31.dist-info}/METADATA +1 -1
- {spacr-0.2.21.dist-info → spacr-0.2.31.dist-info}/RECORD +20 -18
- {spacr-0.2.21.dist-info → spacr-0.2.31.dist-info}/LICENSE +0 -0
- {spacr-0.2.21.dist-info → spacr-0.2.31.dist-info}/WHEEL +0 -0
- {spacr-0.2.21.dist-info → spacr-0.2.31.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.21.dist-info → spacr-0.2.31.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,14 +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
|
|
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)
|
25
|
+
|
20
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'):
|
21
27
|
|
22
28
|
if active_color == 'teal':
|
23
29
|
active_color = '#008080'
|
24
30
|
if inactive_color == 'dark_gray':
|
25
|
-
inactive_color = '#050505'
|
31
|
+
inactive_color = '#2B2B2B' # '#333333' #'#050505'
|
26
32
|
if bg_color == 'black':
|
27
33
|
bg_color = '#000000'
|
28
34
|
if fg_color == 'white':
|
@@ -30,28 +36,39 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
30
36
|
if active_color == 'blue':
|
31
37
|
active_color = '#007BFF'
|
32
38
|
|
33
|
-
|
39
|
+
padding = '5 5 5 5'
|
34
40
|
font_style = tkFont.Font(family=font_family, size=font_size)
|
35
|
-
|
36
|
-
style.
|
37
|
-
|
38
|
-
style.configure('
|
39
|
-
style.
|
40
|
-
style.configure('
|
41
|
-
style.configure('
|
42
|
-
style.
|
43
|
-
style.configure('TLabel',
|
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
|
+
|
44
53
|
style.configure('TFrame', background=bg_color)
|
45
54
|
style.configure('TPanedwindow', background=bg_color)
|
46
|
-
style.configure('
|
47
|
-
|
48
|
-
|
49
|
-
style.configure('
|
50
|
-
style.
|
51
|
-
style.
|
52
|
-
|
53
|
-
|
54
|
-
style.configure('
|
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'))
|
55
72
|
|
56
73
|
if parent_frame:
|
57
74
|
parent_frame.configure(bg=bg_color)
|
@@ -82,20 +99,245 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
82
99
|
|
83
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}
|
84
101
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
91
294
|
|
92
295
|
class spacrDropdownMenu(tk.OptionMenu):
|
93
296
|
def __init__(self, parent, variable, options, command=None, **kwargs):
|
94
297
|
self.variable = variable
|
95
|
-
self.variable.set("
|
298
|
+
self.variable.set("Settings Category")
|
96
299
|
super().__init__(parent, self.variable, *options, command=command, **kwargs)
|
97
300
|
self.update_styles()
|
98
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
|
+
|
99
341
|
def update_styles(self, active_categories=None):
|
100
342
|
style = ttk.Style()
|
101
343
|
style_out = set_dark_style(style, widgets=[self])
|
@@ -120,6 +362,56 @@ class spacrCheckbutton(ttk.Checkbutton):
|
|
120
362
|
style = ttk.Style()
|
121
363
|
_ = set_dark_style(style, widgets=[self])
|
122
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
|
+
|
123
415
|
class spacrFrame(ttk.Frame):
|
124
416
|
def __init__(self, container, width=None, *args, bg='black', **kwargs):
|
125
417
|
super().__init__(container, *args, **kwargs)
|
@@ -192,7 +484,7 @@ class spacrLabel(tk.Frame):
|
|
192
484
|
self.canvas.itemconfig(self.label_text, text=text)
|
193
485
|
|
194
486
|
class spacrButton(tk.Frame):
|
195
|
-
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=
|
487
|
+
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, *args, **kwargs):
|
196
488
|
super().__init__(parent, *args, **kwargs)
|
197
489
|
|
198
490
|
self.text = text.capitalize() # Capitalize only the first letter of the text
|
@@ -216,10 +508,12 @@ class spacrButton(tk.Frame):
|
|
216
508
|
# Apply dark style and get color settings
|
217
509
|
color_settings = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
218
510
|
|
511
|
+
self.inactive_color = color_settings['inactive_color']
|
512
|
+
|
219
513
|
if self.outline:
|
220
|
-
self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=
|
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'])
|
221
515
|
else:
|
222
|
-
self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=
|
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)
|
223
517
|
|
224
518
|
self.load_icon()
|
225
519
|
self.font_style = font if font else ("Arial", 12) # Default font if not provided
|
@@ -234,7 +528,7 @@ class spacrButton(tk.Frame):
|
|
234
528
|
self.canvas.bind("<Leave>", self.on_leave)
|
235
529
|
self.canvas.bind("<Button-1>", self.on_click)
|
236
530
|
|
237
|
-
self.bg_color =
|
531
|
+
self.bg_color = self.inactive_color
|
238
532
|
self.active_color = color_settings['active_color']
|
239
533
|
self.fg_color = color_settings['fg_color']
|
240
534
|
self.is_zoomed_in = False # Track zoom state for smooth transitions
|
@@ -251,7 +545,7 @@ class spacrButton(tk.Frame):
|
|
251
545
|
icon_image = Image.open(self.get_icon_path("default"))
|
252
546
|
print(f'Icon not found: {icon_path}. Using default icon instead.')
|
253
547
|
|
254
|
-
initial_size = int(self.size * 0.
|
548
|
+
initial_size = int(self.size * 0.65) # 65% of button size initially
|
255
549
|
self.original_icon_image = icon_image.resize((initial_size, initial_size), Image.Resampling.LANCZOS)
|
256
550
|
self.icon_photo = ImageTk.PhotoImage(self.original_icon_image)
|
257
551
|
|
@@ -266,13 +560,13 @@ class spacrButton(tk.Frame):
|
|
266
560
|
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
267
561
|
self.update_description(event)
|
268
562
|
if not self.is_zoomed_in:
|
269
|
-
self.animate_zoom(
|
563
|
+
self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
|
270
564
|
|
271
565
|
def on_leave(self, event=None):
|
272
|
-
self.canvas.itemconfig(self.button_bg, fill=self.
|
566
|
+
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
273
567
|
self.clear_description(event)
|
274
568
|
if self.is_zoomed_in:
|
275
|
-
self.animate_zoom(
|
569
|
+
self.animate_zoom(0.65) # Reset the icon size to 65% of button size
|
276
570
|
|
277
571
|
def on_click(self, event=None):
|
278
572
|
if self.command:
|
@@ -317,7 +611,7 @@ class spacrButton(tk.Frame):
|
|
317
611
|
parent = parent.master
|
318
612
|
|
319
613
|
def animate_zoom(self, target_scale, steps=10, delay=10):
|
320
|
-
current_scale =
|
614
|
+
current_scale = 0.85 if self.is_zoomed_in else 0.65
|
321
615
|
step_scale = (target_scale - current_scale) / steps
|
322
616
|
self._animate_step(current_scale, step_scale, steps, delay)
|
323
617
|
|
@@ -331,13 +625,13 @@ class spacrButton(tk.Frame):
|
|
331
625
|
|
332
626
|
def zoom_icon(self, scale_factor):
|
333
627
|
# Resize the original icon image
|
334
|
-
new_size = int(self.size *
|
628
|
+
new_size = int(self.size * scale_factor)
|
335
629
|
resized_icon = self.original_icon_image.resize((new_size, new_size), Image.Resampling.LANCZOS)
|
336
630
|
self.icon_photo = ImageTk.PhotoImage(resized_icon)
|
337
631
|
|
338
632
|
# Update the icon on the canvas
|
339
633
|
self.canvas.itemconfig(self.button_icon, image=self.icon_photo)
|
340
|
-
self.canvas.image = self.icon_photo
|
634
|
+
self.canvas.image = self.icon_photo
|
341
635
|
|
342
636
|
class spacrSwitch(ttk.Frame):
|
343
637
|
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
@@ -1292,14 +1586,19 @@ class ModifyMaskApp:
|
|
1292
1586
|
self.update_display()
|
1293
1587
|
|
1294
1588
|
class AnnotateApp:
|
1295
|
-
def __init__(self, root, db_path, src, image_type=None, channels=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):
|
1296
1590
|
self.root = root
|
1297
1591
|
self.db_path = db_path
|
1298
1592
|
self.src = src
|
1299
1593
|
self.index = 0
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
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
|
+
|
1303
1602
|
self.annotation_column = annotation_column
|
1304
1603
|
self.image_type = image_type
|
1305
1604
|
self.channels = channels
|
@@ -1311,22 +1610,72 @@ class AnnotateApp:
|
|
1311
1610
|
self.adjusted_to_original_paths = {}
|
1312
1611
|
self.terminate = False
|
1313
1612
|
self.update_queue = Queue()
|
1314
|
-
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
1315
|
-
self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
|
1316
1613
|
self.measurement = measurement
|
1317
1614
|
self.threshold = threshold
|
1318
1615
|
|
1616
|
+
print('self.image_size', self.image_size)
|
1617
|
+
|
1618
|
+
style_out = set_dark_style(ttk.Style())
|
1619
|
+
self.root.configure(bg=style_out['inactive_color'])
|
1620
|
+
|
1319
1621
|
self.filtered_paths_annotations = []
|
1320
1622
|
self.prefilter_paths_annotations()
|
1321
1623
|
|
1322
1624
|
self.db_update_thread = threading.Thread(target=self.update_database_worker)
|
1323
1625
|
self.db_update_thread.start()
|
1324
1626
|
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1627
|
+
# Set the initial window size and make it fit the screen size
|
1628
|
+
self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}")
|
1629
|
+
self.root.update_idletasks()
|
1630
|
+
|
1631
|
+
# Create the status label
|
1632
|
+
self.status_label = Label(root, text="", font=("Arial", 12), bg=self.root.cget('bg'))
|
1633
|
+
self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
1634
|
+
|
1635
|
+
# Place the buttons at the bottom right
|
1636
|
+
self.button_frame = Frame(root, bg=self.root.cget('bg'))
|
1637
|
+
self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
|
1638
|
+
|
1639
|
+
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1640
|
+
self.next_button.pack(side="right", padx=5)
|
1641
|
+
|
1642
|
+
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1643
|
+
self.previous_button.pack(side="right", padx=5)
|
1644
|
+
|
1645
|
+
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1646
|
+
self.exit_button.pack(side="right", padx=5)
|
1647
|
+
|
1648
|
+
# Calculate grid rows and columns based on the root window size and image size
|
1649
|
+
self.calculate_grid_dimensions()
|
1650
|
+
|
1651
|
+
# Create a frame to hold the image grid
|
1652
|
+
self.grid_frame = Frame(root, bg=self.root.cget('bg'))
|
1653
|
+
self.grid_frame.grid(row=0, column=0, columnspan=2, padx=0, pady=0, sticky="nsew")
|
1654
|
+
|
1655
|
+
for i in range(self.grid_rows * self.grid_cols):
|
1656
|
+
label = Label(self.grid_frame, bg=self.root.cget('bg'))
|
1657
|
+
label.grid(row=i // self.grid_cols, column=i % self.grid_cols, padx=2, pady=2, sticky="nsew")
|
1328
1658
|
self.labels.append(label)
|
1329
1659
|
|
1660
|
+
# Make the grid frame resize with the window
|
1661
|
+
self.root.grid_rowconfigure(0, weight=1)
|
1662
|
+
self.root.grid_columnconfigure(0, weight=1)
|
1663
|
+
self.root.grid_columnconfigure(1, weight=1)
|
1664
|
+
|
1665
|
+
self.grid_frame.grid_rowconfigure(0, weight=1)
|
1666
|
+
self.grid_frame.grid_columnconfigure(0, 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
|
+
|
1330
1679
|
def prefilter_paths_annotations(self):
|
1331
1680
|
from .io import _read_and_join_tables
|
1332
1681
|
from .utils import is_list_of_lists
|
@@ -1580,6 +1929,7 @@ class AnnotateApp:
|
|
1580
1929
|
self.update_queue.put(self.pending_updates.copy())
|
1581
1930
|
self.pending_updates.clear()
|
1582
1931
|
self.index += self.grid_rows * self.grid_cols
|
1932
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1583
1933
|
self.load_images()
|
1584
1934
|
|
1585
1935
|
def previous_page(self):
|
@@ -1589,6 +1939,7 @@ class AnnotateApp:
|
|
1589
1939
|
self.index -= self.grid_rows * self.grid_cols
|
1590
1940
|
if self.index < 0:
|
1591
1941
|
self.index = 0
|
1942
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1592
1943
|
self.load_images()
|
1593
1944
|
|
1594
1945
|
def shutdown(self):
|
@@ -1603,20 +1954,20 @@ class AnnotateApp:
|
|
1603
1954
|
def create_menu_bar(root):
|
1604
1955
|
from .gui import initiate_root
|
1605
1956
|
gui_apps = {
|
1606
|
-
"Mask": (lambda frame: initiate_root(frame, 'mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
|
1607
|
-
"Measure": (lambda frame: initiate_root(frame, 'measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
|
1608
|
-
"Annotate": (lambda frame: initiate_root(frame, 'annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
|
1609
|
-
"Make Masks": (lambda frame: initiate_root(frame, 'make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
|
1610
|
-
"Classify": (lambda frame: initiate_root(frame, 'classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
|
1611
|
-
"Sequencing": (lambda frame: initiate_root(frame, 'sequencing'), "Analyze sequencing data."),
|
1612
|
-
"Umap": (lambda frame: initiate_root(frame, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
1613
|
-
"Train Cellpose": (lambda frame: initiate_root(frame, 'train_cellpose'), "Train custom Cellpose models."),
|
1614
|
-
"ML Analyze": (lambda frame: initiate_root(frame, 'ml_analyze'), "Machine learning analysis of data."),
|
1615
|
-
"Cellpose Masks": (lambda frame: initiate_root(frame, 'cellpose_masks'), "Generate Cellpose masks."),
|
1616
|
-
"Cellpose All": (lambda frame: initiate_root(frame, 'cellpose_all'), "Run Cellpose on all images."),
|
1617
|
-
"Map Barcodes": (lambda frame: initiate_root(frame, 'map_barcodes'), "Map barcodes to data."),
|
1618
|
-
"Regression": (lambda frame: initiate_root(frame, 'regression'), "Perform regression analysis."),
|
1619
|
-
"Recruitment": (lambda frame: initiate_root(frame, 'recruitment'), "Analyze recruitment data.")
|
1957
|
+
"Mask": (lambda frame: initiate_root(frame, settings_type='mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
|
1958
|
+
"Measure": (lambda frame: initiate_root(frame, settings_type='measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
|
1959
|
+
"Annotate": (lambda frame: initiate_root(frame, settings_type='annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
|
1960
|
+
"Make Masks": (lambda frame: initiate_root(frame, settings_type='make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
|
1961
|
+
"Classify": (lambda frame: initiate_root(frame, settings_type='classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
|
1962
|
+
"Sequencing": (lambda frame: initiate_root(frame, settings_type='sequencing'), "Analyze sequencing data."),
|
1963
|
+
"Umap": (lambda frame: initiate_root(frame, settings_type='umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
1964
|
+
"Train Cellpose": (lambda frame: initiate_root(frame, settings_type='train_cellpose'), "Train custom Cellpose models."),
|
1965
|
+
"ML Analyze": (lambda frame: initiate_root(frame, settings_type='ml_analyze'), "Machine learning analysis of data."),
|
1966
|
+
"Cellpose Masks": (lambda frame: initiate_root(frame, settings_type='cellpose_masks'), "Generate Cellpose masks."),
|
1967
|
+
"Cellpose All": (lambda frame: initiate_root(frame, settings_type='cellpose_all'), "Run Cellpose on all images."),
|
1968
|
+
"Map Barcodes": (lambda frame: initiate_root(frame, settings_type='map_barcodes'), "Map barcodes to data."),
|
1969
|
+
"Regression": (lambda frame: initiate_root(frame, settings_type='regression'), "Perform regression analysis."),
|
1970
|
+
"Recruitment": (lambda frame: initiate_root(frame, settings_type='recruitment'), "Analyze recruitment data.")
|
1620
1971
|
}
|
1621
1972
|
|
1622
1973
|
def load_app_wrapper(app_name, app_func):
|