spacr 0.2.5__py3-none-any.whl → 0.2.8__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/__init__.py +1 -11
- spacr/core.py +226 -287
- spacr/deep_spacr.py +248 -269
- spacr/gui.py +41 -19
- spacr/gui_core.py +404 -151
- spacr/gui_elements.py +778 -179
- spacr/gui_utils.py +163 -106
- spacr/io.py +116 -45
- spacr/measure.py +1 -0
- spacr/plot.py +51 -5
- spacr/sequencing.py +477 -587
- spacr/settings.py +211 -66
- spacr/utils.py +34 -14
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -39
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/RECORD +19 -19
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
import os, threading, time, sqlite3, webbrowser
|
1
|
+
import os, threading, time, sqlite3, webbrowser, pyautogui
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
import tkinter.font as tkFont
|
5
|
+
from tkinter import filedialog
|
6
|
+
from tkinter import font
|
5
7
|
from queue import Queue
|
6
8
|
from tkinter import Label, Frame, Button
|
7
9
|
import numpy as np
|
@@ -15,29 +17,30 @@ from skimage.draw import polygon, line
|
|
15
17
|
from skimage.transform import resize
|
16
18
|
from scipy.ndimage import binary_fill_holes, label
|
17
19
|
from tkinter import ttk, scrolledtext
|
20
|
+
fig = None
|
18
21
|
|
19
|
-
def
|
22
|
+
def set_element_size():
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
screen_width, screen_height = pyautogui.size()
|
25
|
+
screen_area = screen_width * screen_height
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# Fallback to a default font in case of an error
|
31
|
-
default_font = ("Arial", size)
|
32
|
-
else:
|
33
|
-
default_font = (font_name, size)
|
27
|
+
# Calculate sizes based on screen dimensions
|
28
|
+
btn_size = int((screen_area * 0.002) ** 0.5) # Button size as a fraction of screen area
|
29
|
+
bar_size = screen_height // 20 # Bar size based on screen height
|
30
|
+
settings_width = screen_width // 4 # Settings panel width as a fraction of screen width
|
31
|
+
panel_width = screen_width - settings_width # Panel width as a fraction of screen width
|
32
|
+
panel_height = screen_height // 6 # Panel height as a fraction of screen height
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
size_dict = {
|
35
|
+
'btn_size': btn_size,
|
36
|
+
'bar_size': bar_size,
|
37
|
+
'settings_width': settings_width,
|
38
|
+
'panel_width': panel_width,
|
39
|
+
'panel_height': panel_height
|
40
|
+
}
|
41
|
+
return size_dict
|
39
42
|
|
40
|
-
def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="
|
43
|
+
def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="OpenSans", font_size=12, bg_color='black', fg_color='white', active_color='blue', inactive_color='dark_gray'):
|
41
44
|
|
42
45
|
if active_color == 'teal':
|
43
46
|
active_color = '#008080'
|
@@ -53,6 +56,11 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
53
56
|
padding = '5 5 5 5'
|
54
57
|
font_style = tkFont.Font(family=font_family, size=font_size)
|
55
58
|
|
59
|
+
if font_family == 'OpenSans':
|
60
|
+
font_loader = spacrFont(font_name='OpenSans', font_style='Regular', font_size=12)
|
61
|
+
else:
|
62
|
+
font_loader = None
|
63
|
+
|
56
64
|
style.theme_use('clam')
|
57
65
|
|
58
66
|
style.configure('TEntry', padding=padding)
|
@@ -64,7 +72,10 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
64
72
|
style.configure('TButton', padding=padding)
|
65
73
|
style.configure('TFrame', background=bg_color)
|
66
74
|
style.configure('TPanedwindow', background=bg_color)
|
67
|
-
|
75
|
+
if font_loader:
|
76
|
+
style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_loader.get_font(size=font_size))
|
77
|
+
else:
|
78
|
+
style.configure('TLabel', background=bg_color, foreground=fg_color, font=(font_family, font_size))
|
68
79
|
|
69
80
|
if parent_frame:
|
70
81
|
parent_frame.configure(bg=bg_color)
|
@@ -85,15 +96,97 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
85
96
|
if isinstance(widget, (tk.Label, tk.Button, tk.Frame, ttk.LabelFrame, tk.Canvas)):
|
86
97
|
widget.configure(bg=bg_color)
|
87
98
|
if isinstance(widget, (tk.Label, tk.Button)):
|
88
|
-
|
99
|
+
if font_loader:
|
100
|
+
widget.configure(fg=fg_color, font=font_loader.get_font(size=font_size))
|
101
|
+
else:
|
102
|
+
widget.configure(fg=fg_color, font=(font_family, font_size))
|
89
103
|
if isinstance(widget, scrolledtext.ScrolledText):
|
90
104
|
widget.configure(bg=bg_color, fg=fg_color, insertbackground=fg_color)
|
91
105
|
if isinstance(widget, tk.OptionMenu):
|
92
|
-
|
106
|
+
if font_loader:
|
107
|
+
widget.configure(bg=bg_color, fg=fg_color, font=font_loader.get_font(size=font_size))
|
108
|
+
else:
|
109
|
+
widget.configure(bg=bg_color, fg=fg_color, font=(font_family, font_size))
|
93
110
|
menu = widget['menu']
|
94
|
-
|
111
|
+
if font_loader:
|
112
|
+
menu.configure(bg=bg_color, fg=fg_color, font=font_loader.get_font(size=font_size))
|
113
|
+
else:
|
114
|
+
menu.configure(bg=bg_color, fg=fg_color, font=(font_family, font_size))
|
115
|
+
|
116
|
+
return {'font_loader':font_loader, 'font_family': font_family, 'font_size': font_size, 'bg_color': bg_color, 'fg_color': fg_color, 'active_color': active_color, 'inactive_color': inactive_color}
|
117
|
+
|
118
|
+
class spacrFont:
|
119
|
+
def __init__(self, font_name, font_style, font_size=12):
|
120
|
+
"""
|
121
|
+
Initializes the FontLoader class.
|
122
|
+
|
123
|
+
Parameters:
|
124
|
+
- font_name: str, the name of the font (e.g., 'OpenSans').
|
125
|
+
- font_style: str, the style of the font (e.g., 'Regular', 'Bold').
|
126
|
+
- font_size: int, the size of the font (default: 12).
|
127
|
+
"""
|
128
|
+
self.font_name = font_name
|
129
|
+
self.font_style = font_style
|
130
|
+
self.font_size = font_size
|
131
|
+
|
132
|
+
# Determine the path based on the font name and style
|
133
|
+
self.font_path = self.get_font_path(font_name, font_style)
|
134
|
+
|
135
|
+
# Register the font with Tkinter
|
136
|
+
self.load_font()
|
137
|
+
|
138
|
+
def get_font_path(self, font_name, font_style):
|
139
|
+
"""
|
140
|
+
Returns the font path based on the font name and style.
|
141
|
+
|
142
|
+
Parameters:
|
143
|
+
- font_name: str, the name of the font.
|
144
|
+
- font_style: str, the style of the font.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
- str, the path to the font file.
|
148
|
+
"""
|
149
|
+
base_dir = os.path.dirname(__file__)
|
150
|
+
|
151
|
+
if font_name == 'OpenSans':
|
152
|
+
if font_style == 'Regular':
|
153
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Regular.ttf')
|
154
|
+
elif font_style == 'Bold':
|
155
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Bold.ttf')
|
156
|
+
elif font_style == 'Italic':
|
157
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Italic.ttf')
|
158
|
+
# Add more styles as needed
|
159
|
+
# Add more fonts as needed
|
160
|
+
|
161
|
+
raise ValueError(f"Font '{font_name}' with style '{font_style}' not found.")
|
95
162
|
|
96
|
-
|
163
|
+
def load_font(self):
|
164
|
+
"""
|
165
|
+
Loads the font into Tkinter.
|
166
|
+
"""
|
167
|
+
try:
|
168
|
+
font.Font(family=self.font_name, size=self.font_size)
|
169
|
+
except tk.TclError:
|
170
|
+
# Load the font manually if it's not already loaded
|
171
|
+
self.tk_font = font.Font(
|
172
|
+
name=self.font_name,
|
173
|
+
file=self.font_path,
|
174
|
+
size=self.font_size
|
175
|
+
)
|
176
|
+
|
177
|
+
def get_font(self, size=None):
|
178
|
+
"""
|
179
|
+
Returns the font in the specified size.
|
180
|
+
|
181
|
+
Parameters:
|
182
|
+
- size: int, the size of the font (optional).
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
- tkFont.Font object.
|
186
|
+
"""
|
187
|
+
if size is None:
|
188
|
+
size = self.font_size
|
189
|
+
return font.Font(family=self.font_name, size=size)
|
97
190
|
|
98
191
|
class spacrContainer(tk.Frame):
|
99
192
|
def __init__(self, parent, orient=tk.VERTICAL, bg=None, *args, **kwargs):
|
@@ -200,6 +293,7 @@ class spacrEntry(tk.Frame):
|
|
200
293
|
self.outline = outline
|
201
294
|
self.font_family = style_out['font_family']
|
202
295
|
self.font_size = style_out['font_size']
|
296
|
+
self.font_loader = style_out['font_loader']
|
203
297
|
|
204
298
|
# Set the background color of the frame
|
205
299
|
self.configure(bg=style_out['bg_color'])
|
@@ -214,7 +308,10 @@ class spacrEntry(tk.Frame):
|
|
214
308
|
self.canvas.pack()
|
215
309
|
|
216
310
|
# Create the entry widget
|
217
|
-
|
311
|
+
if self.font_loader:
|
312
|
+
self.entry = tk.Entry(self, textvariable=textvariable, bd=0, highlightthickness=0, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size), bg=self.bg_color)
|
313
|
+
else:
|
314
|
+
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)
|
218
315
|
self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=self.canvas_width - 30, height=20) # Centered positioning
|
219
316
|
|
220
317
|
# Bind events to change the background color on focus
|
@@ -305,6 +402,7 @@ class spacrCombo(tk.Frame):
|
|
305
402
|
self.inactive_color = style_out['inactive_color']
|
306
403
|
self.font_family = style_out['font_family']
|
307
404
|
self.font_size = style_out['font_size']
|
405
|
+
self.font_loader = style_out['font_loader']
|
308
406
|
|
309
407
|
self.values = values or []
|
310
408
|
|
@@ -318,7 +416,10 @@ class spacrCombo(tk.Frame):
|
|
318
416
|
self.selected_value = self.var.get()
|
319
417
|
|
320
418
|
# Create the label to display the selected value
|
321
|
-
|
419
|
+
if self.font_loader:
|
420
|
+
self.label = tk.Label(self, text=self.selected_value, bg=self.inactive_color, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size))
|
421
|
+
else:
|
422
|
+
self.label = tk.Label(self, text=self.selected_value, bg=self.inactive_color, fg=self.fg_color, font=(self.font_family, self.font_size))
|
322
423
|
self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
|
323
424
|
|
324
425
|
# Bind events to open the dropdown menu
|
@@ -359,7 +460,10 @@ class spacrCombo(tk.Frame):
|
|
359
460
|
|
360
461
|
for index, value in enumerate(self.values):
|
361
462
|
display_text = value if value is not None else 'None'
|
362
|
-
|
463
|
+
if self.font_loader:
|
464
|
+
item = tk.Label(self.dropdown_menu, text=display_text, bg=self.inactive_color, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size), anchor='w')
|
465
|
+
else:
|
466
|
+
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')
|
363
467
|
item.pack(fill='both')
|
364
468
|
item.bind("<Button-1>", lambda e, v=value: self.on_select(v))
|
365
469
|
item.bind("<Enter>", lambda e, w=item: w.config(bg=self.active_color))
|
@@ -385,124 +489,102 @@ class spacrCombo(tk.Frame):
|
|
385
489
|
self.label.config(text=display_text)
|
386
490
|
self.selected_value = value
|
387
491
|
|
388
|
-
class spacrDropdownMenu(tk.
|
389
|
-
def __init__(self, parent, variable, options, command=None, **kwargs):
|
390
|
-
self.variable = variable
|
391
|
-
self.variable.set("Settings Category")
|
392
|
-
super().__init__(parent, self.variable, *options, command=command, **kwargs)
|
393
|
-
self.update_styles()
|
394
|
-
|
395
|
-
# Hide the original button
|
396
|
-
self.configure(highlightthickness=0, relief='flat', bg='#2B2B2B', fg='#2B2B2B')
|
397
|
-
|
398
|
-
# Create custom button
|
399
|
-
self.create_custom_button()
|
400
|
-
|
401
|
-
def create_custom_button(self):
|
402
|
-
self.canvas_width = self.winfo_reqwidth() + 20 # Adjust width based on required size
|
403
|
-
self.canvas_height = 40 # Adjust the height as needed
|
404
|
-
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg='#2B2B2B')
|
405
|
-
self.canvas.pack()
|
406
|
-
self.label = tk.Label(self.canvas, text="Settings Category", bg='#2B2B2B', fg='#ffffff', font=('Arial', 12))
|
407
|
-
self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
|
408
|
-
self.draw_rounded_rectangle('#2B2B2B')
|
409
|
-
|
410
|
-
# Bind the click event to open the dropdown menu
|
411
|
-
self.canvas.bind("<Button-1>", self.on_click)
|
412
|
-
self.label.bind("<Button-1>", self.on_click)
|
413
|
-
|
414
|
-
def draw_rounded_rectangle(self, color):
|
415
|
-
radius = 15
|
416
|
-
x0, y0 = 10, 5
|
417
|
-
x1, y1 = self.canvas_width - 10, self.canvas_height - 5 # Adjust based on canvas size
|
418
|
-
|
419
|
-
self.canvas.delete("all")
|
420
|
-
|
421
|
-
# Create the rounded rectangle
|
422
|
-
self.canvas.create_arc(x0, y0, x0 + 2 * radius, y0 + 2 * radius, start=90, extent=90, fill=color, outline=color)
|
423
|
-
self.canvas.create_arc(x1 - 2 * radius, y0, x1, y0 + 2 * radius, start=0, extent=90, fill=color, outline=color)
|
424
|
-
self.canvas.create_arc(x0, y1 - 2 * radius, x0 + 2 * radius, y1, start=180, extent=90, fill=color, outline=color)
|
425
|
-
self.canvas.create_arc(x1 - 2 * radius, y1 - 2 * radius, x1, y1, start=270, extent=90, fill=color, outline=color)
|
426
|
-
|
427
|
-
self.canvas.create_rectangle(x0 + radius, y0, x1 - radius, y1, fill=color, outline=color)
|
428
|
-
self.canvas.create_rectangle(x0, y0 + radius, x1, y1 - radius, fill=color, outline=color)
|
492
|
+
class spacrDropdownMenu(tk.Frame):
|
429
493
|
|
430
|
-
|
494
|
+
def __init__(self, parent, variable, options, command=None, font=None, size=50, **kwargs):
|
495
|
+
super().__init__(parent, **kwargs)
|
496
|
+
self.variable = variable
|
497
|
+
self.options = options
|
498
|
+
self.command = command
|
499
|
+
self.text = "Settings"
|
500
|
+
self.size = size
|
431
501
|
|
432
|
-
|
433
|
-
|
502
|
+
# Apply dark style and get color settings
|
503
|
+
style_out = set_dark_style(ttk.Style())
|
504
|
+
self.font_size = style_out['font_size']
|
505
|
+
self.font_loader = style_out['font_loader']
|
434
506
|
|
435
|
-
|
436
|
-
|
437
|
-
self.
|
507
|
+
# Button size configuration
|
508
|
+
self.button_width = int(size * 3)
|
509
|
+
self.canvas_width = self.button_width + 4
|
510
|
+
self.canvas_height = self.size + 4
|
438
511
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
self.menu = self['menu']
|
443
|
-
style_out = set_dark_style(style, widgets=[self.menu])
|
512
|
+
# Create the canvas and rounded button
|
513
|
+
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, highlightthickness=0, bg=style_out['bg_color'])
|
514
|
+
self.canvas.grid(row=0, column=0)
|
444
515
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
self.menu.entryconfig(idx, background=style_out['bg_color'], foreground=style_out['fg_color'])
|
516
|
+
# Apply dark style and get color settings
|
517
|
+
color_settings = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
518
|
+
self.inactive_color = color_settings['inactive_color']
|
519
|
+
self.active_color = color_settings['active_color']
|
520
|
+
self.fg_color = color_settings['fg_color']
|
521
|
+
self.bg_color = style_out['bg_color']
|
452
522
|
|
523
|
+
# Create the button with rounded edges
|
524
|
+
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)
|
453
525
|
|
526
|
+
# Create and place the label on the button
|
527
|
+
if self.font_loader:
|
528
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
529
|
+
else:
|
530
|
+
self.font_style = font if font else ("Arial", 12)
|
454
531
|
|
455
|
-
|
456
|
-
def __init__(self, parent, variable, options, command=None, **kwargs):
|
457
|
-
self.variable = variable
|
458
|
-
self.variable.set("Settings Category")
|
459
|
-
super().__init__(parent, self.variable, *options, command=command, **kwargs)
|
460
|
-
self.update_styles()
|
532
|
+
self.button_text = self.canvas.create_text(self.button_width // 2, self.size // 2 + 2, text=self.text, fill=self.fg_color, font=self.font_style, anchor="center")
|
461
533
|
|
462
|
-
#
|
463
|
-
self.
|
534
|
+
# Bind events for button behavior
|
535
|
+
self.bind("<Enter>", self.on_enter)
|
536
|
+
self.bind("<Leave>", self.on_leave)
|
537
|
+
self.bind("<Button-1>", self.on_click)
|
538
|
+
self.canvas.bind("<Enter>", self.on_enter)
|
539
|
+
self.canvas.bind("<Leave>", self.on_leave)
|
540
|
+
self.canvas.bind("<Button-1>", self.on_click)
|
464
541
|
|
465
|
-
# Create
|
466
|
-
self.
|
542
|
+
# Create a popup menu with the desired background color
|
543
|
+
self.menu = tk.Menu(self, tearoff=0, bg=self.bg_color, fg=self.fg_color)
|
544
|
+
for option in self.options:
|
545
|
+
self.menu.add_command(label=option, command=lambda opt=option: self.on_select(opt))
|
467
546
|
|
468
|
-
def
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
547
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
548
|
+
points = [
|
549
|
+
x1 + radius, y1,
|
550
|
+
x2 - radius, y1,
|
551
|
+
x2 - radius, y1,
|
552
|
+
x2, y1,
|
553
|
+
x2, y1 + radius,
|
554
|
+
x2, y2 - radius,
|
555
|
+
x2, y2 - radius,
|
556
|
+
x2, y2,
|
557
|
+
x2 - radius, y2,
|
558
|
+
x1 + radius, y2,
|
559
|
+
x1 + radius, y2,
|
560
|
+
x1, y2,
|
561
|
+
x1, y2 - radius,
|
562
|
+
x1, y2 - radius,
|
563
|
+
x1, y1 + radius,
|
564
|
+
x1, y1 + radius,
|
565
|
+
x1, y1
|
566
|
+
]
|
567
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
476
568
|
|
477
|
-
|
478
|
-
self.canvas.
|
479
|
-
self.label.bind("<Button-1>", self.on_click)
|
569
|
+
def on_enter(self, event=None):
|
570
|
+
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
480
571
|
|
481
|
-
def
|
482
|
-
|
483
|
-
x0, y0 = 10, 5
|
484
|
-
x1, y1 = self.canvas_width - 10, self.canvas_height - 5 # Adjust based on canvas size
|
485
|
-
self.canvas.delete("all")
|
486
|
-
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
487
|
-
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
488
|
-
self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
|
489
|
-
self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
|
490
|
-
self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
|
491
|
-
self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
|
492
|
-
self.label.config(bg=color) # Update label background to match rectangle color
|
572
|
+
def on_leave(self, event=None):
|
573
|
+
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
493
574
|
|
494
|
-
def on_click(self, event):
|
575
|
+
def on_click(self, event=None):
|
495
576
|
self.post_menu()
|
496
577
|
|
497
578
|
def post_menu(self):
|
498
579
|
x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
|
499
580
|
self.menu.post(x, y + height)
|
500
581
|
|
582
|
+
def on_select(self, option):
|
583
|
+
if self.command:
|
584
|
+
self.command(option)
|
585
|
+
|
501
586
|
def update_styles(self, active_categories=None):
|
502
|
-
|
503
|
-
style_out = set_dark_style(style, widgets=[self])
|
504
|
-
self.menu = self['menu']
|
505
|
-
style_out = set_dark_style(style, widgets=[self.menu])
|
587
|
+
style_out = set_dark_style(ttk.Style(), widgets=[self.menu])
|
506
588
|
|
507
589
|
if active_categories is not None:
|
508
590
|
for idx in range(self.menu.index("end") + 1):
|
@@ -533,17 +615,26 @@ class spacrProgressBar(ttk.Progressbar):
|
|
533
615
|
self.bg_color = style_out['bg_color']
|
534
616
|
self.active_color = style_out['active_color']
|
535
617
|
self.inactive_color = style_out['inactive_color']
|
618
|
+
self.font_size = style_out['font_size']
|
619
|
+
self.font_loader = style_out['font_loader']
|
536
620
|
|
537
621
|
# Configure the style for the progress bar
|
538
622
|
self.style = ttk.Style()
|
623
|
+
|
624
|
+
# Remove any borders and ensure the active color fills the entire space
|
539
625
|
self.style.configure(
|
540
626
|
"spacr.Horizontal.TProgressbar",
|
541
|
-
troughcolor=self.
|
542
|
-
background=self.active_color,
|
543
|
-
|
544
|
-
|
545
|
-
|
627
|
+
troughcolor=self.inactive_color, # Set the trough to bg color
|
628
|
+
background=self.active_color, # Active part is the active color
|
629
|
+
borderwidth=0, # Remove border width
|
630
|
+
pbarrelief="flat", # Flat relief for the progress bar
|
631
|
+
troughrelief="flat", # Flat relief for the trough
|
632
|
+
thickness=20, # Set the thickness of the progress bar
|
633
|
+
darkcolor=self.active_color, # Ensure darkcolor matches the active color
|
634
|
+
lightcolor=self.active_color, # Ensure lightcolor matches the active color
|
635
|
+
bordercolor=self.bg_color # Set the border color to the background color to hide it
|
546
636
|
)
|
637
|
+
|
547
638
|
self.configure(style="spacr.Horizontal.TProgressbar")
|
548
639
|
|
549
640
|
# Set initial value to 0
|
@@ -552,16 +643,23 @@ class spacrProgressBar(ttk.Progressbar):
|
|
552
643
|
# Track whether to show the progress label
|
553
644
|
self.label = label
|
554
645
|
|
555
|
-
# Create the progress label
|
646
|
+
# Create the progress label with text wrapping
|
556
647
|
if self.label:
|
557
|
-
self.progress_label = tk.Label(
|
558
|
-
|
648
|
+
self.progress_label = tk.Label(
|
649
|
+
parent,
|
650
|
+
text="Processing: 0/0",
|
651
|
+
anchor='w',
|
652
|
+
justify='left',
|
653
|
+
bg=self.inactive_color,
|
654
|
+
fg=self.fg_color,
|
655
|
+
wraplength=300,
|
656
|
+
font=self.font_loader.get_font(size=self.font_size)
|
657
|
+
)
|
658
|
+
self.progress_label.grid_forget()
|
559
659
|
|
560
660
|
# Initialize attributes for time and operation
|
561
661
|
self.operation_type = None
|
562
|
-
self.
|
563
|
-
self.time_batch = None
|
564
|
-
self.time_left = None
|
662
|
+
self.additional_info = None
|
565
663
|
|
566
664
|
def set_label_position(self):
|
567
665
|
if self.label and self.progress_label:
|
@@ -572,18 +670,189 @@ class spacrProgressBar(ttk.Progressbar):
|
|
572
670
|
|
573
671
|
def update_label(self):
|
574
672
|
if self.label and self.progress_label:
|
575
|
-
#
|
673
|
+
# Start with the base progress information
|
576
674
|
label_text = f"Processing: {self['value']}/{self['maximum']}"
|
675
|
+
|
676
|
+
# Include the operation type if it exists
|
577
677
|
if self.operation_type:
|
578
678
|
label_text += f", {self.operation_type}"
|
579
|
-
|
580
|
-
|
581
|
-
if self.
|
582
|
-
|
583
|
-
|
584
|
-
|
679
|
+
|
680
|
+
# Handle additional info without adding newlines
|
681
|
+
if hasattr(self, 'additional_info') and self.additional_info:
|
682
|
+
# Join all additional info items with a space and ensure they're on the same line
|
683
|
+
items = self.additional_info.split(", ")
|
684
|
+
formatted_additional_info = " ".join(items)
|
685
|
+
|
686
|
+
# Append the additional info to the label_text, ensuring it's all in one line
|
687
|
+
label_text += f" {formatted_additional_info.strip()}"
|
688
|
+
|
689
|
+
# Update the progress label
|
585
690
|
self.progress_label.config(text=label_text)
|
586
691
|
|
692
|
+
class spacrSlider(tk.Frame):
|
693
|
+
def __init__(self, master=None, length=None, thickness=2, knob_radius=10, position="center", from_=0, to=100, value=None, show_index=False, command=None, **kwargs):
|
694
|
+
super().__init__(master, **kwargs)
|
695
|
+
|
696
|
+
self.specified_length = length # Store the specified length, if any
|
697
|
+
self.knob_radius = knob_radius
|
698
|
+
self.thickness = thickness
|
699
|
+
self.knob_position = knob_radius # Start at the beginning of the slider
|
700
|
+
self.slider_line = None
|
701
|
+
self.knob = None
|
702
|
+
self.position = position.lower() # Store the position option
|
703
|
+
self.offset = 0 # Initialize offset
|
704
|
+
self.from_ = from_ # Minimum value of the slider
|
705
|
+
self.to = to # Maximum value of the slider
|
706
|
+
self.value = value if value is not None else from_ # Initial value of the slider
|
707
|
+
self.show_index = show_index # Whether to show the index Entry widget
|
708
|
+
self.command = command # Callback function to handle value changes
|
709
|
+
|
710
|
+
# Initialize the style and colors
|
711
|
+
style_out = set_dark_style(ttk.Style())
|
712
|
+
self.fg_color = style_out['fg_color']
|
713
|
+
self.bg_color = style_out['bg_color']
|
714
|
+
self.active_color = style_out['active_color']
|
715
|
+
self.inactive_color = style_out['inactive_color']
|
716
|
+
|
717
|
+
# Configure the frame's background color
|
718
|
+
self.configure(bg=self.bg_color)
|
719
|
+
|
720
|
+
# Create a frame for the slider and entry if needed
|
721
|
+
self.grid_columnconfigure(1, weight=1)
|
722
|
+
|
723
|
+
# Entry widget for showing and editing index, if enabled
|
724
|
+
if self.show_index:
|
725
|
+
self.index_var = tk.StringVar(value=str(int(self.value)))
|
726
|
+
self.index_entry = tk.Entry(self, textvariable=self.index_var, width=5, bg=self.bg_color, fg=self.fg_color, insertbackground=self.fg_color)
|
727
|
+
self.index_entry.grid(row=0, column=0, padx=5)
|
728
|
+
# Bind the entry to update the slider on change
|
729
|
+
self.index_entry.bind("<Return>", self.update_slider_from_entry)
|
730
|
+
|
731
|
+
# Create the slider canvas
|
732
|
+
self.canvas = tk.Canvas(self, height=knob_radius * 2, bg=self.bg_color, highlightthickness=0)
|
733
|
+
self.canvas.grid(row=0, column=1, sticky="ew")
|
734
|
+
|
735
|
+
# Set initial length to specified length or default value
|
736
|
+
self.length = self.specified_length if self.specified_length is not None else self.canvas.winfo_reqwidth()
|
737
|
+
|
738
|
+
# Calculate initial knob position based on the initial value
|
739
|
+
self.knob_position = self.value_to_position(self.value)
|
740
|
+
|
741
|
+
# Bind resize event to dynamically adjust the slider length if no length is specified
|
742
|
+
self.canvas.bind("<Configure>", self.resize_slider)
|
743
|
+
|
744
|
+
# Draw the slider components
|
745
|
+
self.draw_slider(inactive=True)
|
746
|
+
|
747
|
+
# Bind mouse events to the knob and slider
|
748
|
+
self.canvas.bind("<B1-Motion>", self.move_knob)
|
749
|
+
self.canvas.bind("<Button-1>", self.activate_knob) # Activate knob on click
|
750
|
+
self.canvas.bind("<ButtonRelease-1>", self.release_knob) # Trigger command on release
|
751
|
+
|
752
|
+
def resize_slider(self, event):
|
753
|
+
if self.specified_length is not None:
|
754
|
+
self.length = self.specified_length
|
755
|
+
else:
|
756
|
+
self.length = int(event.width * 0.9) # 90% of the container width
|
757
|
+
|
758
|
+
# Calculate the horizontal offset based on the position
|
759
|
+
if self.position == "center":
|
760
|
+
self.offset = (event.width - self.length) // 2
|
761
|
+
elif self.position == "right":
|
762
|
+
self.offset = event.width - self.length
|
763
|
+
else: # position is "left"
|
764
|
+
self.offset = 0
|
765
|
+
|
766
|
+
# Update the knob position after resizing
|
767
|
+
self.knob_position = self.value_to_position(self.value)
|
768
|
+
self.draw_slider(inactive=True)
|
769
|
+
|
770
|
+
def value_to_position(self, value):
|
771
|
+
if self.to == self.from_:
|
772
|
+
return self.knob_radius
|
773
|
+
relative_value = (value - self.from_) / (self.to - self.from_)
|
774
|
+
return self.knob_radius + relative_value * (self.length - 2 * self.knob_radius)
|
775
|
+
|
776
|
+
def position_to_value(self, position):
|
777
|
+
if self.to == self.from_:
|
778
|
+
return self.from_
|
779
|
+
relative_position = (position - self.knob_radius) / (self.length - 2 * self.knob_radius)
|
780
|
+
return self.from_ + relative_position * (self.to - self.from_)
|
781
|
+
|
782
|
+
def draw_slider(self, inactive=False):
|
783
|
+
self.canvas.delete("all")
|
784
|
+
|
785
|
+
self.slider_line = self.canvas.create_line(
|
786
|
+
self.offset + self.knob_radius,
|
787
|
+
self.knob_radius,
|
788
|
+
self.offset + self.length - self.knob_radius,
|
789
|
+
self.knob_radius,
|
790
|
+
fill=self.fg_color,
|
791
|
+
width=self.thickness
|
792
|
+
)
|
793
|
+
|
794
|
+
knob_color = self.inactive_color if inactive else self.active_color
|
795
|
+
self.knob = self.canvas.create_oval(
|
796
|
+
self.offset + self.knob_position - self.knob_radius,
|
797
|
+
self.knob_radius - self.knob_radius,
|
798
|
+
self.offset + self.knob_position + self.knob_radius,
|
799
|
+
self.knob_radius + self.knob_radius,
|
800
|
+
fill=knob_color,
|
801
|
+
outline=""
|
802
|
+
)
|
803
|
+
|
804
|
+
def move_knob(self, event):
|
805
|
+
new_position = min(max(event.x - self.offset, self.knob_radius), self.length - self.knob_radius)
|
806
|
+
self.knob_position = new_position
|
807
|
+
self.value = self.position_to_value(self.knob_position)
|
808
|
+
self.canvas.coords(
|
809
|
+
self.knob,
|
810
|
+
self.offset + self.knob_position - self.knob_radius,
|
811
|
+
self.knob_radius - self.knob_radius,
|
812
|
+
self.offset + self.knob_position + self.knob_radius,
|
813
|
+
self.knob_radius + self.knob_radius
|
814
|
+
)
|
815
|
+
if self.show_index:
|
816
|
+
self.index_var.set(str(int(self.value)))
|
817
|
+
|
818
|
+
def activate_knob(self, event):
|
819
|
+
self.draw_slider(inactive=False)
|
820
|
+
self.move_knob(event)
|
821
|
+
|
822
|
+
def release_knob(self, event):
|
823
|
+
self.draw_slider(inactive=True)
|
824
|
+
if self.command:
|
825
|
+
self.command(self.value) # Call the command with the final value when the knob is released
|
826
|
+
|
827
|
+
def set_to(self, new_to):
|
828
|
+
self.to = new_to
|
829
|
+
self.knob_position = self.value_to_position(self.value)
|
830
|
+
self.draw_slider(inactive=False)
|
831
|
+
|
832
|
+
def get(self):
|
833
|
+
return self.value
|
834
|
+
|
835
|
+
def set(self, value):
|
836
|
+
"""Set the slider's value and update the knob position."""
|
837
|
+
self.value = max(self.from_, min(value, self.to)) # Ensure the value is within bounds
|
838
|
+
self.knob_position = self.value_to_position(self.value)
|
839
|
+
self.draw_slider(inactive=False)
|
840
|
+
if self.show_index:
|
841
|
+
self.index_var.set(str(int(self.value)))
|
842
|
+
|
843
|
+
def jump_to_click(self, event):
|
844
|
+
self.activate_knob(event)
|
845
|
+
|
846
|
+
def update_slider_from_entry(self, event):
|
847
|
+
"""Update the slider's value from the entry."""
|
848
|
+
try:
|
849
|
+
index = int(self.index_var.get())
|
850
|
+
self.set(index)
|
851
|
+
if self.command:
|
852
|
+
self.command(self.value)
|
853
|
+
except ValueError:
|
854
|
+
pass
|
855
|
+
|
587
856
|
def spacrScrollbarStyle(style, inactive_color, active_color):
|
588
857
|
# Check if custom elements already exist to avoid duplication
|
589
858
|
if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
|
@@ -698,12 +967,16 @@ class spacrLabel(tk.Frame):
|
|
698
967
|
label_height = height
|
699
968
|
label_width = label_height * 10
|
700
969
|
|
701
|
-
style_out = set_dark_style(ttk.Style())
|
970
|
+
self.style_out = set_dark_style(ttk.Style())
|
971
|
+
self.font_style = self.style_out['font_family']
|
972
|
+
self.font_size = self.style_out['font_size']
|
973
|
+
self.font_family = self.style_out['font_family']
|
974
|
+
self.font_loader = self.style_out['font_loader']
|
702
975
|
|
703
|
-
self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=style_out['bg_color'])
|
976
|
+
self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=self.style_out['bg_color'])
|
704
977
|
self.canvas.grid(row=0, column=0, sticky="ew")
|
705
|
-
|
706
|
-
|
978
|
+
if self.style_out['font_family'] != 'OpenSans':
|
979
|
+
self.font_style = font if font else tkFont.Font(family=self.style_out['font_family'], size=self.style_out['font_size'], weight=tkFont.NORMAL)
|
707
980
|
self.style = style
|
708
981
|
|
709
982
|
if self.align == "center":
|
@@ -715,13 +988,21 @@ class spacrLabel(tk.Frame):
|
|
715
988
|
|
716
989
|
if self.style:
|
717
990
|
ttk_style = ttk.Style()
|
718
|
-
|
991
|
+
if self.font_loader:
|
992
|
+
ttk_style.configure(self.style, font=self.font_loader.get_font(size=self.font_size), background=self.style_out['bg_color'], foreground=self.style_out['fg_color'])
|
993
|
+
else:
|
994
|
+
ttk_style.configure(self.style, font=self.font_style, background=self.style_out['bg_color'], foreground=self.style_out['fg_color'])
|
719
995
|
self.label_text = ttk.Label(self.canvas, text=self.text, style=self.style, anchor=text_anchor)
|
720
996
|
self.label_text.pack(fill=tk.BOTH, expand=True)
|
721
997
|
else:
|
722
|
-
|
723
|
-
|
724
|
-
|
998
|
+
if self.font_loader:
|
999
|
+
self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
|
1000
|
+
label_height // 2, text=self.text, fill=self.style_out['fg_color'],
|
1001
|
+
font=self.font_loader.get_font(size=self.font_size), anchor=anchor_value, justify=tk.RIGHT)
|
1002
|
+
else:
|
1003
|
+
self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
|
1004
|
+
label_height // 2, text=self.text, fill=self.style_out['fg_color'],
|
1005
|
+
font=self.font_style, anchor=anchor_value, justify=tk.RIGHT)
|
725
1006
|
|
726
1007
|
_ = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
727
1008
|
|
@@ -744,6 +1025,8 @@ class spacrButton(tk.Frame):
|
|
744
1025
|
self.animation = animation # Add animation attribute
|
745
1026
|
|
746
1027
|
style_out = set_dark_style(ttk.Style())
|
1028
|
+
self.font_size = style_out['font_size']
|
1029
|
+
self.font_loader = style_out['font_loader']
|
747
1030
|
|
748
1031
|
if self.show_text:
|
749
1032
|
self.button_width = int(size * 3)
|
@@ -765,7 +1048,10 @@ class spacrButton(tk.Frame):
|
|
765
1048
|
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)
|
766
1049
|
|
767
1050
|
self.load_icon()
|
768
|
-
self.
|
1051
|
+
if self.font_loader:
|
1052
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
1053
|
+
else:
|
1054
|
+
self.font_style = font if font else ("Arial", 12)
|
769
1055
|
|
770
1056
|
if self.show_text:
|
771
1057
|
self.button_text = self.canvas.create_text(self.size + 10, self.size // 2 + 2, text=self.text, fill=color_settings['fg_color'], font=self.font_style, anchor="w") # Align text to the left of the specified point
|
@@ -1863,6 +2149,19 @@ class AnnotateApp:
|
|
1863
2149
|
self.threshold = threshold
|
1864
2150
|
|
1865
2151
|
style_out = set_dark_style(ttk.Style())
|
2152
|
+
self.font_loader = style_out['font_loader']
|
2153
|
+
self.font_size = style_out['font_size']
|
2154
|
+
self.bg_color = style_out['bg_color']
|
2155
|
+
self.fg_color = style_out['fg_color']
|
2156
|
+
self.active_color = style_out['active_color']
|
2157
|
+
self.inactive_color = style_out['inactive_color']
|
2158
|
+
|
2159
|
+
|
2160
|
+
if self.font_loader:
|
2161
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
2162
|
+
else:
|
2163
|
+
self.font_style = ("Arial", 12)
|
2164
|
+
|
1866
2165
|
self.root.configure(bg=style_out['inactive_color'])
|
1867
2166
|
|
1868
2167
|
self.filtered_paths_annotations = []
|
@@ -1876,20 +2175,20 @@ class AnnotateApp:
|
|
1876
2175
|
self.root.update_idletasks()
|
1877
2176
|
|
1878
2177
|
# Create the status label
|
1879
|
-
self.status_label = Label(root, text="", font=
|
2178
|
+
self.status_label = Label(root, text="", font=self.font_style, bg=self.root.cget('bg'))
|
1880
2179
|
self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
1881
2180
|
|
1882
2181
|
# Place the buttons at the bottom right
|
1883
2182
|
self.button_frame = Frame(root, bg=self.root.cget('bg'))
|
1884
2183
|
self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
|
1885
2184
|
|
1886
|
-
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg=
|
2185
|
+
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1887
2186
|
self.next_button.pack(side="right", padx=5)
|
1888
2187
|
|
1889
|
-
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg=
|
2188
|
+
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1890
2189
|
self.previous_button.pack(side="right", padx=5)
|
1891
2190
|
|
1892
|
-
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg=
|
2191
|
+
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1893
2192
|
self.exit_button.pack(side="right", padx=5)
|
1894
2193
|
|
1895
2194
|
# Calculate grid rows and columns based on the root window size and image size
|
@@ -1953,6 +2252,7 @@ class AnnotateApp:
|
|
1953
2252
|
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
1954
2253
|
df = df[df[f'threshold_measurement_{idx}'] > thd]
|
1955
2254
|
after = len(df)
|
2255
|
+
|
1956
2256
|
elif isinstance(self.measurement, list):
|
1957
2257
|
df['threshold_measurement'] = df[self.measurement[0]]/df[self.measurement[1]]
|
1958
2258
|
print(f"mean threshold measurement: {np.mean(df['threshold_measurement'])}")
|
@@ -1961,6 +2261,7 @@ class AnnotateApp:
|
|
1961
2261
|
after = len(df)
|
1962
2262
|
self.measurement = 'threshold_measurement'
|
1963
2263
|
print(f'Removed: {before-after} rows, retained {after}')
|
2264
|
+
|
1964
2265
|
else:
|
1965
2266
|
print(f"mean threshold measurement: {np.mean(df[self.measurement])}")
|
1966
2267
|
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
@@ -2037,7 +2338,7 @@ class AnnotateApp:
|
|
2037
2338
|
|
2038
2339
|
for i, (img, annotation) in enumerate(loaded_images):
|
2039
2340
|
if annotation:
|
2040
|
-
border_color =
|
2341
|
+
border_color = self.active_color if annotation == 1 else 'red'
|
2041
2342
|
img = self.add_colored_border(img, border_width=5, border_color=border_color)
|
2042
2343
|
|
2043
2344
|
photo = ImageTk.PhotoImage(img)
|
@@ -2083,7 +2384,7 @@ class AnnotateApp:
|
|
2083
2384
|
left_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
2084
2385
|
right_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
2085
2386
|
|
2086
|
-
bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color=
|
2387
|
+
bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color=self.fg_color)
|
2087
2388
|
bordered_img.paste(top_border, (border_width, 0))
|
2088
2389
|
bordered_img.paste(bottom_border, (border_width, img.height + border_width))
|
2089
2390
|
bordered_img.paste(left_border, (0, border_width))
|
@@ -2123,7 +2424,7 @@ class AnnotateApp:
|
|
2123
2424
|
print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
|
2124
2425
|
|
2125
2426
|
img_ = img.crop((5, 5, img.width-5, img.height-5))
|
2126
|
-
border_fill =
|
2427
|
+
border_fill = self.active_color if new_annotation == 1 else ('red' if new_annotation == 2 else None)
|
2127
2428
|
img_ = ImageOps.expand(img_, border=5, fill=border_fill) if border_fill else img_
|
2128
2429
|
|
2129
2430
|
photo = ImageTk.PhotoImage(img_)
|
@@ -2206,25 +2507,22 @@ class AnnotateApp:
|
|
2206
2507
|
def create_menu_bar(root):
|
2207
2508
|
from .gui import initiate_root
|
2208
2509
|
gui_apps = {
|
2209
|
-
"Mask":
|
2210
|
-
"Measure":
|
2211
|
-
"Annotate":
|
2212
|
-
"Make Masks":
|
2213
|
-
"Classify":
|
2214
|
-
"Sequencing":
|
2215
|
-
"Umap":
|
2216
|
-
"Train Cellpose":
|
2217
|
-
"ML Analyze":
|
2218
|
-
"Cellpose Masks":
|
2219
|
-
"Cellpose All":
|
2220
|
-
"Map Barcodes":
|
2221
|
-
"Regression":
|
2222
|
-
"Recruitment":
|
2510
|
+
"Mask": lambda: initiate_root(root, settings_type='mask'),
|
2511
|
+
"Measure": lambda: initiate_root(root, settings_type='measure'),
|
2512
|
+
"Annotate": lambda: initiate_root(root, settings_type='annotate'),
|
2513
|
+
"Make Masks": lambda: initiate_root(root, settings_type='make_masks'),
|
2514
|
+
"Classify": lambda: initiate_root(root, settings_type='classify'),
|
2515
|
+
"Sequencing": lambda: initiate_root(root, settings_type='sequencing'),
|
2516
|
+
"Umap": lambda: initiate_root(root, settings_type='umap'),
|
2517
|
+
"Train Cellpose": lambda: initiate_root(root, settings_type='train_cellpose'),
|
2518
|
+
"ML Analyze": lambda: initiate_root(root, settings_type='ml_analyze'),
|
2519
|
+
"Cellpose Masks": lambda: initiate_root(root, settings_type='cellpose_masks'),
|
2520
|
+
"Cellpose All": lambda: initiate_root(root, settings_type='cellpose_all'),
|
2521
|
+
"Map Barcodes": lambda: initiate_root(root, settings_type='map_barcodes'),
|
2522
|
+
"Regression": lambda: initiate_root(root, settings_type='regression'),
|
2523
|
+
"Recruitment": lambda: initiate_root(root, settings_type='recruitment')
|
2223
2524
|
}
|
2224
2525
|
|
2225
|
-
def load_app_wrapper(app_name, app_func):
|
2226
|
-
root.load_app(app_name, app_func)
|
2227
|
-
|
2228
2526
|
# Create the menu bar
|
2229
2527
|
menu_bar = tk.Menu(root, bg="#008080", fg="white")
|
2230
2528
|
|
@@ -2233,14 +2531,315 @@ def create_menu_bar(root):
|
|
2233
2531
|
menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
|
2234
2532
|
|
2235
2533
|
# Add options to the "SpaCr Applications" menu
|
2236
|
-
for app_name,
|
2237
|
-
|
2238
|
-
|
2534
|
+
for app_name, app_func in gui_apps.items():
|
2535
|
+
app_menu.add_command(
|
2536
|
+
label=app_name,
|
2537
|
+
command=app_func
|
2538
|
+
)
|
2239
2539
|
|
2240
2540
|
# Add a separator and an exit option
|
2241
2541
|
app_menu.add_separator()
|
2242
|
-
app_menu.add_command(label="Help", command=lambda: webbrowser.open("https://readthedocs.
|
2542
|
+
app_menu.add_command(label="Help", command=lambda: webbrowser.open("https://spacr.readthedocs.io/en/latest/?badge=latest"))
|
2243
2543
|
app_menu.add_command(label="Exit", command=root.quit)
|
2244
2544
|
|
2245
2545
|
# Configure the menu for the root window
|
2246
2546
|
root.config(menu=menu_bar)
|
2547
|
+
|
2548
|
+
def standardize_figure(fig):
|
2549
|
+
from .gui_elements import set_dark_style
|
2550
|
+
from matplotlib.font_manager import FontProperties
|
2551
|
+
|
2552
|
+
style_out = set_dark_style(ttk.Style())
|
2553
|
+
bg_color = style_out['bg_color']
|
2554
|
+
fg_color = style_out['fg_color']
|
2555
|
+
font_size = style_out['font_size']
|
2556
|
+
font_loader = style_out['font_loader']
|
2557
|
+
|
2558
|
+
# Get the custom font path from the font loader
|
2559
|
+
font_path = font_loader.font_path
|
2560
|
+
font_prop = FontProperties(fname=font_path, size=font_size)
|
2561
|
+
|
2562
|
+
"""
|
2563
|
+
Standardizes the appearance of the figure:
|
2564
|
+
- Font size: from style
|
2565
|
+
- Font color: from style
|
2566
|
+
- Font family: custom OpenSans from font_loader
|
2567
|
+
- Removes top and right spines
|
2568
|
+
- Figure and subplot background: from style
|
2569
|
+
- Line width: 1
|
2570
|
+
- Line color: from style
|
2571
|
+
"""
|
2572
|
+
|
2573
|
+
|
2574
|
+
for ax in fig.get_axes():
|
2575
|
+
# Set font properties for title and labels
|
2576
|
+
ax.title.set_fontsize(font_size)
|
2577
|
+
ax.title.set_color(fg_color)
|
2578
|
+
ax.title.set_fontproperties(font_prop)
|
2579
|
+
|
2580
|
+
ax.xaxis.label.set_fontsize(font_size)
|
2581
|
+
ax.xaxis.label.set_color(fg_color)
|
2582
|
+
ax.xaxis.label.set_fontproperties(font_prop)
|
2583
|
+
|
2584
|
+
ax.yaxis.label.set_fontsize(font_size)
|
2585
|
+
ax.yaxis.label.set_color(fg_color)
|
2586
|
+
ax.yaxis.label.set_fontproperties(font_prop)
|
2587
|
+
|
2588
|
+
# Set font properties for tick labels
|
2589
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2590
|
+
label.set_fontsize(font_size)
|
2591
|
+
label.set_color(fg_color)
|
2592
|
+
label.set_fontproperties(font_prop)
|
2593
|
+
|
2594
|
+
# Remove top and right spines
|
2595
|
+
ax.spines['top'].set_visible(False)
|
2596
|
+
ax.spines['right'].set_visible(False)
|
2597
|
+
ax.spines['left'].set_visible(True)
|
2598
|
+
ax.spines['bottom'].set_visible(True)
|
2599
|
+
|
2600
|
+
# Set spine line width and color
|
2601
|
+
for spine in ax.spines.values():
|
2602
|
+
spine.set_linewidth(1)
|
2603
|
+
spine.set_edgecolor(fg_color)
|
2604
|
+
|
2605
|
+
# Set line width and color
|
2606
|
+
for line in ax.get_lines():
|
2607
|
+
line.set_linewidth(1)
|
2608
|
+
line.set_color(fg_color)
|
2609
|
+
|
2610
|
+
# Set subplot background color
|
2611
|
+
ax.set_facecolor(bg_color)
|
2612
|
+
|
2613
|
+
# Adjust the grid if needed
|
2614
|
+
ax.grid(True, color='gray', linestyle='--', linewidth=0.5)
|
2615
|
+
|
2616
|
+
# Set figure background color
|
2617
|
+
fig.patch.set_facecolor(bg_color)
|
2618
|
+
|
2619
|
+
fig.canvas.draw_idle()
|
2620
|
+
|
2621
|
+
return fig
|
2622
|
+
|
2623
|
+
def modify_figure_properties(fig, scale_x=None, scale_y=None, line_width=None, font_size=None, x_lim=None, y_lim=None, grid=False, legend=None, title=None, x_label_rotation=None, remove_axes=False, bg_color=None, text_color=None, line_color=None):
|
2624
|
+
"""
|
2625
|
+
Modifies the properties of the figure, including scaling, line widths, font sizes, axis limits, x-axis label rotation, background color, text color, line color, and other common options.
|
2626
|
+
|
2627
|
+
Parameters:
|
2628
|
+
- fig: The Matplotlib figure object to modify.
|
2629
|
+
- scale_x: Scaling factor for the width of subplots (optional).
|
2630
|
+
- scale_y: Scaling factor for the height of subplots (optional).
|
2631
|
+
- line_width: Desired line width for all lines (optional).
|
2632
|
+
- font_size: Desired font size for all text (optional).
|
2633
|
+
- x_lim: Tuple specifying the x-axis limits (min, max) (optional).
|
2634
|
+
- y_lim: Tuple specifying the y-axis limits (min, max) (optional).
|
2635
|
+
- grid: Boolean to add grid lines to the plot (optional).
|
2636
|
+
- legend: Boolean to show/hide the legend (optional).
|
2637
|
+
- title: String to set as the title of the plot (optional).
|
2638
|
+
- x_label_rotation: Angle to rotate the x-axis labels (optional).
|
2639
|
+
- remove_axes: Boolean to remove or show the axes labels (optional).
|
2640
|
+
- bg_color: Color for the figure and subplot background (optional).
|
2641
|
+
- text_color: Color for all text in the figure (optional).
|
2642
|
+
- line_color: Color for all lines in the figure (optional).
|
2643
|
+
"""
|
2644
|
+
if fig is None:
|
2645
|
+
print("Error: The figure provided is None.")
|
2646
|
+
return
|
2647
|
+
|
2648
|
+
for ax in fig.get_axes():
|
2649
|
+
# Rescale subplots if scaling factors are provided
|
2650
|
+
if scale_x is not None or scale_y is not None:
|
2651
|
+
bbox = ax.get_position()
|
2652
|
+
width = bbox.width * (scale_x if scale_x else 1)
|
2653
|
+
height = bbox.height * (scale_y if scale_y else 1)
|
2654
|
+
new_bbox = [bbox.x0, bbox.y0, width, height]
|
2655
|
+
ax.set_position(new_bbox)
|
2656
|
+
|
2657
|
+
# Set axis limits if provided
|
2658
|
+
if x_lim is not None:
|
2659
|
+
ax.set_xlim(x_lim)
|
2660
|
+
if y_lim is not None:
|
2661
|
+
ax.set_ylim(y_lim)
|
2662
|
+
|
2663
|
+
# Set grid visibility only
|
2664
|
+
ax.grid(grid)
|
2665
|
+
|
2666
|
+
# Adjust line width and color if specified
|
2667
|
+
if line_width is not None or line_color is not None:
|
2668
|
+
for line in ax.get_lines():
|
2669
|
+
if line_width is not None:
|
2670
|
+
line.set_linewidth(line_width)
|
2671
|
+
if line_color is not None:
|
2672
|
+
line.set_color(line_color)
|
2673
|
+
for spine in ax.spines.values(): # Modify width and color of spines (e.g., scale bars)
|
2674
|
+
if line_width is not None:
|
2675
|
+
spine.set_linewidth(line_width)
|
2676
|
+
if line_color is not None:
|
2677
|
+
spine.set_edgecolor(line_color)
|
2678
|
+
ax.tick_params(width=line_width, colors=text_color if text_color else 'black')
|
2679
|
+
|
2680
|
+
# Adjust font size if specified
|
2681
|
+
if font_size is not None:
|
2682
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2683
|
+
label.set_fontsize(font_size)
|
2684
|
+
ax.title.set_fontsize(font_size)
|
2685
|
+
ax.xaxis.label.set_fontsize(font_size)
|
2686
|
+
ax.yaxis.label.set_fontsize(font_size)
|
2687
|
+
if ax.legend_:
|
2688
|
+
for text in ax.legend_.get_texts():
|
2689
|
+
text.set_fontsize(font_size)
|
2690
|
+
|
2691
|
+
# Rotate x-axis labels if rotation is specified
|
2692
|
+
if x_label_rotation is not None:
|
2693
|
+
for label in ax.get_xticklabels():
|
2694
|
+
label.set_rotation(x_label_rotation)
|
2695
|
+
if 0 <= x_label_rotation <= 90:
|
2696
|
+
label.set_ha('center')
|
2697
|
+
|
2698
|
+
# Toggle axes labels visibility without affecting the grid or spines
|
2699
|
+
if remove_axes:
|
2700
|
+
ax.xaxis.set_visible(False)
|
2701
|
+
ax.yaxis.set_visible(False)
|
2702
|
+
else:
|
2703
|
+
ax.xaxis.set_visible(True)
|
2704
|
+
ax.yaxis.set_visible(True)
|
2705
|
+
|
2706
|
+
# Set text color if specified
|
2707
|
+
if text_color:
|
2708
|
+
ax.title.set_color(text_color)
|
2709
|
+
ax.xaxis.label.set_color(text_color)
|
2710
|
+
ax.yaxis.label.set_color(text_color)
|
2711
|
+
ax.tick_params(colors=text_color)
|
2712
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2713
|
+
label.set_color(text_color)
|
2714
|
+
|
2715
|
+
# Set background color for subplots if specified
|
2716
|
+
if bg_color:
|
2717
|
+
ax.set_facecolor(bg_color)
|
2718
|
+
|
2719
|
+
# Set figure background color if specified
|
2720
|
+
if bg_color:
|
2721
|
+
fig.patch.set_facecolor(bg_color)
|
2722
|
+
|
2723
|
+
fig.canvas.draw_idle()
|
2724
|
+
|
2725
|
+
def save_figure_as_format(fig, file_format):
|
2726
|
+
file_path = filedialog.asksaveasfilename(defaultextension=f".{file_format}", filetypes=[(f"{file_format.upper()} files", f"*.{file_format}"), ("All files", "*.*")])
|
2727
|
+
if file_path:
|
2728
|
+
try:
|
2729
|
+
fig.savefig(file_path, format=file_format)
|
2730
|
+
print(f"Figure saved as {file_format.upper()} at {file_path}")
|
2731
|
+
except Exception as e:
|
2732
|
+
print(f"Error saving figure: {e}")
|
2733
|
+
|
2734
|
+
def modify_figure(fig):
|
2735
|
+
from .gui_core import display_figure
|
2736
|
+
def apply_modifications():
|
2737
|
+
try:
|
2738
|
+
# Only apply changes if the fields are filled
|
2739
|
+
scale_x = float(scale_x_var.get()) if scale_x_var.get() else None
|
2740
|
+
scale_y = float(scale_y_var.get()) if scale_y_var.get() else None
|
2741
|
+
line_width = float(line_width_var.get()) if line_width_var.get() else None
|
2742
|
+
font_size = int(font_size_var.get()) if font_size_var.get() else None
|
2743
|
+
x_lim = eval(x_lim_var.get()) if x_lim_var.get() else None
|
2744
|
+
y_lim = eval(y_lim_var.get()) if y_lim_var.get() else None
|
2745
|
+
title = title_var.get() if title_var.get() else None
|
2746
|
+
bg_color = bg_color_var.get() if bg_color_var.get() else None
|
2747
|
+
text_color = text_color_var.get() if text_color_var.get() else None
|
2748
|
+
line_color = line_color_var.get() if line_color_var.get() else None
|
2749
|
+
x_label_rotation = int(x_label_rotation_var.get()) if x_label_rotation_var.get() else None
|
2750
|
+
|
2751
|
+
modify_figure_properties(
|
2752
|
+
fig,
|
2753
|
+
scale_x=scale_x,
|
2754
|
+
scale_y=scale_y,
|
2755
|
+
line_width=line_width,
|
2756
|
+
font_size=font_size,
|
2757
|
+
x_lim=x_lim,
|
2758
|
+
y_lim=y_lim,
|
2759
|
+
grid=grid_var.get(),
|
2760
|
+
legend=legend_var.get(),
|
2761
|
+
title=title,
|
2762
|
+
x_label_rotation=x_label_rotation,
|
2763
|
+
remove_axes=remove_axes_var.get(),
|
2764
|
+
bg_color=bg_color,
|
2765
|
+
text_color=text_color,
|
2766
|
+
line_color=line_color
|
2767
|
+
)
|
2768
|
+
display_figure(fig)
|
2769
|
+
except ValueError:
|
2770
|
+
print("Invalid input; please enter numeric values.")
|
2771
|
+
|
2772
|
+
def toggle_spleens():
|
2773
|
+
for ax in fig.get_axes():
|
2774
|
+
if spleens_var.get():
|
2775
|
+
ax.spines['top'].set_visible(False)
|
2776
|
+
ax.spines['right'].set_visible(False)
|
2777
|
+
ax.spines['left'].set_visible(True)
|
2778
|
+
ax.spines['bottom'].set_visible(True)
|
2779
|
+
ax.spines['top'].set_linewidth(2)
|
2780
|
+
ax.spines['right'].set_linewidth(2)
|
2781
|
+
else:
|
2782
|
+
ax.spines['top'].set_visible(True)
|
2783
|
+
ax.spines['right'].set_visible(True)
|
2784
|
+
display_figure(fig)
|
2785
|
+
|
2786
|
+
# Create a new window for user input
|
2787
|
+
modify_window = tk.Toplevel()
|
2788
|
+
modify_window.title("Modify Figure Properties")
|
2789
|
+
|
2790
|
+
# Apply dark style to the popup window
|
2791
|
+
style = ttk.Style()
|
2792
|
+
style.configure("TCheckbutton", background="#2E2E2E", foreground="white", selectcolor="blue")
|
2793
|
+
|
2794
|
+
modify_window.configure(bg="#2E2E2E")
|
2795
|
+
|
2796
|
+
# Create and style the input fields
|
2797
|
+
scale_x_var = tk.StringVar()
|
2798
|
+
scale_y_var = tk.StringVar()
|
2799
|
+
line_width_var = tk.StringVar()
|
2800
|
+
font_size_var = tk.StringVar()
|
2801
|
+
x_lim_var = tk.StringVar()
|
2802
|
+
y_lim_var = tk.StringVar()
|
2803
|
+
title_var = tk.StringVar()
|
2804
|
+
bg_color_var = tk.StringVar()
|
2805
|
+
text_color_var = tk.StringVar()
|
2806
|
+
line_color_var = tk.StringVar()
|
2807
|
+
x_label_rotation_var = tk.StringVar()
|
2808
|
+
remove_axes_var = tk.BooleanVar()
|
2809
|
+
grid_var = tk.BooleanVar()
|
2810
|
+
legend_var = tk.BooleanVar()
|
2811
|
+
spleens_var = tk.BooleanVar()
|
2812
|
+
|
2813
|
+
options = [
|
2814
|
+
("Rescale X:", scale_x_var),
|
2815
|
+
("Rescale Y:", scale_y_var),
|
2816
|
+
("Line Width:", line_width_var),
|
2817
|
+
("Font Size:", font_size_var),
|
2818
|
+
("X Axis Limits (tuple):", x_lim_var),
|
2819
|
+
("Y Axis Limits (tuple):", y_lim_var),
|
2820
|
+
("Title:", title_var),
|
2821
|
+
("X Label Rotation (degrees):", x_label_rotation_var),
|
2822
|
+
("Background Color:", bg_color_var),
|
2823
|
+
("Text Color:", text_color_var),
|
2824
|
+
("Line Color:", line_color_var)
|
2825
|
+
]
|
2826
|
+
|
2827
|
+
for i, (label_text, var) in enumerate(options):
|
2828
|
+
tk.Label(modify_window, text=label_text, bg="#2E2E2E", fg="white").grid(row=i, column=0, padx=10, pady=5)
|
2829
|
+
tk.Entry(modify_window, textvariable=var, bg="#2E2E2E", fg="white").grid(row=i, column=1, padx=10, pady=5)
|
2830
|
+
|
2831
|
+
checkboxes = [
|
2832
|
+
("Grid", grid_var),
|
2833
|
+
("Legend", legend_var),
|
2834
|
+
("Spleens", spleens_var),
|
2835
|
+
("Remove Axes", remove_axes_var)
|
2836
|
+
]
|
2837
|
+
|
2838
|
+
for i, (label_text, var) in enumerate(checkboxes, start=len(options)):
|
2839
|
+
ttk.Checkbutton(modify_window, text=label_text, variable=var, style="TCheckbutton").grid(row=i, column=0, padx=10, pady=5, columnspan=2, sticky='w')
|
2840
|
+
|
2841
|
+
spleens_var.trace_add("write", lambda *args: toggle_spleens())
|
2842
|
+
|
2843
|
+
# Apply button
|
2844
|
+
apply_button = tk.Button(modify_window, text="Apply", command=apply_modifications, bg="#2E2E2E", fg="white")
|
2845
|
+
apply_button.grid(row=len(options) + len(checkboxes), column=0, columnspan=2, pady=10)
|