spacr 0.2.3__py3-none-any.whl → 0.2.5__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 +100 -282
- spacr/gui.py +20 -38
- spacr/gui_core.py +406 -499
- spacr/gui_elements.py +395 -60
- spacr/gui_utils.py +393 -73
- spacr/io.py +130 -50
- spacr/measure.py +199 -154
- spacr/plot.py +108 -42
- spacr/resources/font/open_sans/OFL.txt +93 -0
- spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/README.txt +100 -0
- spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/settings.py +12 -87
- spacr/utils.py +45 -10
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/METADATA +5 -1
- spacr-0.2.5.dist-info/RECORD +100 -0
- spacr-0.2.3.dist-info/RECORD +0 -58
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/LICENSE +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/WHEEL +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import os, threading, time, sqlite3
|
1
|
+
import os, threading, time, sqlite3, webbrowser
|
2
2
|
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
|
@@ -17,7 +17,21 @@ from scipy.ndimage import binary_fill_holes, label
|
|
17
17
|
from tkinter import ttk, scrolledtext
|
18
18
|
|
19
19
|
def set_default_font(root, font_name="Arial", size=12):
|
20
|
-
|
20
|
+
|
21
|
+
# Define default_font before the condition
|
22
|
+
default_font = ("Arial", size)
|
23
|
+
|
24
|
+
if font_name == 'OpenSans':
|
25
|
+
try:
|
26
|
+
font_path = os.path.join(os.path.dirname(__file__), 'resources/font/open_sans/static/OpenSans-Regular.ttf')
|
27
|
+
default_font = tkFont.Font(family=font_name, size=size, file=font_path)
|
28
|
+
except Exception as e:
|
29
|
+
print(f"Failed to load OpenSans font: {e}")
|
30
|
+
# Fallback to a default font in case of an error
|
31
|
+
default_font = ("Arial", size)
|
32
|
+
else:
|
33
|
+
default_font = (font_name, size)
|
34
|
+
|
21
35
|
root.option_add("*Font", default_font)
|
22
36
|
root.option_add("*TButton.Font", default_font)
|
23
37
|
root.option_add("*TLabel.Font", default_font)
|
@@ -47,29 +61,11 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
47
61
|
style.configure('TEntry', padding=padding)
|
48
62
|
style.configure('Spacr.TEntry', padding=padding)
|
49
63
|
style.configure('Custom.TLabel', padding=padding)
|
50
|
-
#style.configure('Spacr.TCheckbutton', padding=padding)
|
51
64
|
style.configure('TButton', padding=padding)
|
52
|
-
|
53
65
|
style.configure('TFrame', background=bg_color)
|
54
66
|
style.configure('TPanedwindow', background=bg_color)
|
55
67
|
style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_style)
|
56
68
|
|
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'))
|
72
|
-
|
73
69
|
if parent_frame:
|
74
70
|
parent_frame.configure(bg=bg_color)
|
75
71
|
parent_frame.grid_rowconfigure(0, weight=1)
|
@@ -99,8 +95,101 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
99
95
|
|
100
96
|
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}
|
101
97
|
|
98
|
+
class spacrContainer(tk.Frame):
|
99
|
+
def __init__(self, parent, orient=tk.VERTICAL, bg=None, *args, **kwargs):
|
100
|
+
super().__init__(parent, *args, **kwargs)
|
101
|
+
self.orient = orient
|
102
|
+
self.bg = bg if bg else 'lightgrey'
|
103
|
+
self.sash_thickness = 10
|
104
|
+
|
105
|
+
self.panes = []
|
106
|
+
self.sashes = []
|
107
|
+
self.bind("<Configure>", self.on_configure)
|
108
|
+
|
109
|
+
self.grid_rowconfigure(0, weight=1)
|
110
|
+
self.grid_columnconfigure(0, weight=1)
|
111
|
+
|
112
|
+
def add(self, widget, stretch='always'):
|
113
|
+
print(f"Adding widget: {widget} with stretch: {stretch}")
|
114
|
+
pane = tk.Frame(self, bg=self.bg)
|
115
|
+
pane.grid_propagate(False)
|
116
|
+
widget.grid(in_=pane, sticky="nsew") # Use grid for the widget within the pane
|
117
|
+
self.panes.append((pane, widget))
|
118
|
+
|
119
|
+
if len(self.panes) > 1:
|
120
|
+
self.create_sash()
|
121
|
+
|
122
|
+
self.reposition_panes()
|
123
|
+
|
124
|
+
def create_sash(self):
|
125
|
+
sash = tk.Frame(self, bg=self.bg, cursor='sb_v_double_arrow' if self.orient == tk.VERTICAL else 'sb_h_double_arrow', height=self.sash_thickness, width=self.sash_thickness)
|
126
|
+
sash.bind("<Enter>", self.on_enter_sash)
|
127
|
+
sash.bind("<Leave>", self.on_leave_sash)
|
128
|
+
sash.bind("<ButtonPress-1>", self.start_resize)
|
129
|
+
self.sashes.append(sash)
|
130
|
+
|
131
|
+
def reposition_panes(self):
|
132
|
+
if not self.panes:
|
133
|
+
return
|
134
|
+
|
135
|
+
total_size = self.winfo_height() if self.orient == tk.VERTICAL else self.winfo_width()
|
136
|
+
pane_size = total_size // len(self.panes)
|
137
|
+
|
138
|
+
print(f"Total size: {total_size}, Pane size: {pane_size}, Number of panes: {len(self.panes)}")
|
139
|
+
|
140
|
+
for i, (pane, widget) in enumerate(self.panes):
|
141
|
+
if self.orient == tk.VERTICAL:
|
142
|
+
pane.grid(row=i * 2, column=0, sticky="nsew", pady=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
|
143
|
+
else:
|
144
|
+
pane.grid(row=0, column=i * 2, sticky="nsew", padx=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
|
145
|
+
|
146
|
+
for i, sash in enumerate(self.sashes):
|
147
|
+
if self.orient == tk.VERTICAL:
|
148
|
+
sash.grid(row=(i * 2) + 1, column=0, sticky="ew")
|
149
|
+
else:
|
150
|
+
sash.grid(row=0, column=(i * 2) + 1, sticky="ns")
|
151
|
+
|
152
|
+
def on_configure(self, event):
|
153
|
+
print(f"Configuring container: {self}")
|
154
|
+
self.reposition_panes()
|
155
|
+
|
156
|
+
def on_enter_sash(self, event):
|
157
|
+
event.widget.config(bg='blue')
|
158
|
+
|
159
|
+
def on_leave_sash(self, event):
|
160
|
+
event.widget.config(bg=self.bg)
|
161
|
+
|
162
|
+
def start_resize(self, event):
|
163
|
+
sash = event.widget
|
164
|
+
self.start_pos = event.y_root if self.orient == tk.VERTICAL else event.x_root
|
165
|
+
self.start_size = sash.winfo_y() if self.orient == tk.VERTICAL else sash.winfo_x()
|
166
|
+
sash.bind("<B1-Motion>", self.perform_resize)
|
167
|
+
|
168
|
+
def perform_resize(self, event):
|
169
|
+
sash = event.widget
|
170
|
+
delta = (event.y_root - self.start_pos) if self.orient == tk.VERTICAL else (event.x_root - self.start_pos)
|
171
|
+
new_size = self.start_size + delta
|
172
|
+
|
173
|
+
for i, (pane, widget) in enumerate(self.panes):
|
174
|
+
if self.orient == tk.VERTICAL:
|
175
|
+
new_row = max(0, new_size // self.sash_thickness)
|
176
|
+
if pane.winfo_y() >= new_size:
|
177
|
+
pane.grid_configure(row=new_row)
|
178
|
+
elif pane.winfo_y() < new_size and i > 0:
|
179
|
+
previous_row = max(0, (new_size - pane.winfo_height()) // self.sash_thickness)
|
180
|
+
self.panes[i - 1][0].grid_configure(row=previous_row)
|
181
|
+
else:
|
182
|
+
new_col = max(0, new_size // self.sash_thickness)
|
183
|
+
if pane.winfo_x() >= new_size:
|
184
|
+
pane.grid_configure(column=new_col)
|
185
|
+
elif pane.winfo_x() < new_size and i > 0:
|
186
|
+
previous_col = max(0, (new_size - pane.winfo_width()) // self.sash_thickness)
|
187
|
+
self.panes[i - 1][0].grid_configure(column=previous_col)
|
188
|
+
|
189
|
+
self.reposition_panes()
|
190
|
+
|
102
191
|
class spacrEntry(tk.Frame):
|
103
|
-
def __init__(self, parent, textvariable=None, outline=False, *args, **kwargs):
|
192
|
+
def __init__(self, parent, textvariable=None, outline=False, width=None, *args, **kwargs):
|
104
193
|
super().__init__(parent, *args, **kwargs)
|
105
194
|
|
106
195
|
# Set dark style
|
@@ -116,13 +205,17 @@ class spacrEntry(tk.Frame):
|
|
116
205
|
self.configure(bg=style_out['bg_color'])
|
117
206
|
|
118
207
|
# Create a canvas for the rounded rectangle background
|
119
|
-
|
208
|
+
if width is None:
|
209
|
+
self.canvas_width = 220 # Adjusted for padding
|
210
|
+
else:
|
211
|
+
self.canvas_width = width
|
120
212
|
self.canvas_height = 40 # Adjusted for padding
|
121
213
|
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
214
|
self.canvas.pack()
|
123
215
|
|
216
|
+
# Create the entry widget
|
124
217
|
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=
|
218
|
+
self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=self.canvas_width - 30, height=20) # Centered positioning
|
126
219
|
|
127
220
|
# Bind events to change the background color on focus
|
128
221
|
self.entry.bind("<FocusIn>", self.on_focus_in)
|
@@ -133,7 +226,7 @@ class spacrEntry(tk.Frame):
|
|
133
226
|
def draw_rounded_rectangle(self, color):
|
134
227
|
radius = 15 # Increased radius for more rounded corners
|
135
228
|
x0, y0 = 10, 5
|
136
|
-
x1, y1 =
|
229
|
+
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
137
230
|
self.canvas.delete("all")
|
138
231
|
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
139
232
|
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
@@ -201,7 +294,7 @@ class spacrCheck(tk.Frame):
|
|
201
294
|
self.variable.set(not self.variable.get())
|
202
295
|
|
203
296
|
class spacrCombo(tk.Frame):
|
204
|
-
def __init__(self, parent, textvariable=None, values=None, *args, **kwargs):
|
297
|
+
def __init__(self, parent, textvariable=None, values=None, width=None, *args, **kwargs):
|
205
298
|
super().__init__(parent, *args, **kwargs)
|
206
299
|
|
207
300
|
# Set dark style
|
@@ -216,7 +309,7 @@ class spacrCombo(tk.Frame):
|
|
216
309
|
self.values = values or []
|
217
310
|
|
218
311
|
# Create a canvas for the rounded rectangle background
|
219
|
-
self.canvas_width = 220 # Adjusted for padding
|
312
|
+
self.canvas_width = width if width is not None else 220 # Adjusted for padding
|
220
313
|
self.canvas_height = 40 # Adjusted for padding
|
221
314
|
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=self.bg_color)
|
222
315
|
self.canvas.pack()
|
@@ -239,7 +332,7 @@ class spacrCombo(tk.Frame):
|
|
239
332
|
def draw_rounded_rectangle(self, color):
|
240
333
|
radius = 15 # Increased radius for more rounded corners
|
241
334
|
x0, y0 = 10, 5
|
242
|
-
x1, y1 =
|
335
|
+
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
243
336
|
self.canvas.delete("all")
|
244
337
|
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
245
338
|
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
@@ -305,6 +398,73 @@ class spacrDropdownMenu(tk.OptionMenu):
|
|
305
398
|
# Create custom button
|
306
399
|
self.create_custom_button()
|
307
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)
|
429
|
+
|
430
|
+
self.label.config(bg=color) # Update label background to match rectangle color
|
431
|
+
|
432
|
+
def on_click(self, event):
|
433
|
+
self.post_menu()
|
434
|
+
|
435
|
+
def post_menu(self):
|
436
|
+
x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
|
437
|
+
self.menu.post(x, y + height)
|
438
|
+
|
439
|
+
def update_styles(self, active_categories=None):
|
440
|
+
style = ttk.Style()
|
441
|
+
style_out = set_dark_style(style, widgets=[self])
|
442
|
+
self.menu = self['menu']
|
443
|
+
style_out = set_dark_style(style, widgets=[self.menu])
|
444
|
+
|
445
|
+
if active_categories is not None:
|
446
|
+
for idx in range(self.menu.index("end") + 1):
|
447
|
+
option = self.menu.entrycget(idx, "label")
|
448
|
+
if option in active_categories:
|
449
|
+
self.menu.entryconfig(idx, background=style_out['active_color'], foreground=style_out['fg_color'])
|
450
|
+
else:
|
451
|
+
self.menu.entryconfig(idx, background=style_out['bg_color'], foreground=style_out['fg_color'])
|
452
|
+
|
453
|
+
|
454
|
+
|
455
|
+
class spacrDropdownMenu_v1(tk.OptionMenu):
|
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()
|
461
|
+
|
462
|
+
# Hide the original button
|
463
|
+
self.configure(highlightthickness=0, relief='flat', bg='#2B2B2B', fg='#2B2B2B')
|
464
|
+
|
465
|
+
# Create custom button
|
466
|
+
self.create_custom_button()
|
467
|
+
|
308
468
|
def create_custom_button(self):
|
309
469
|
self.canvas_width = self.winfo_reqwidth() # Use the required width of the widget
|
310
470
|
self.canvas_height = 40 # Adjust the height as needed
|
@@ -363,14 +523,16 @@ class spacrCheckbutton(ttk.Checkbutton):
|
|
363
523
|
_ = set_dark_style(style, widgets=[self])
|
364
524
|
|
365
525
|
class spacrProgressBar(ttk.Progressbar):
|
366
|
-
def __init__(self, parent, *args, **kwargs):
|
526
|
+
def __init__(self, parent, label=True, *args, **kwargs):
|
367
527
|
super().__init__(parent, *args, **kwargs)
|
368
528
|
|
369
529
|
# Get the style colors
|
370
530
|
style_out = set_dark_style(ttk.Style())
|
371
|
-
|
531
|
+
|
532
|
+
self.fg_color = style_out['fg_color']
|
372
533
|
self.bg_color = style_out['bg_color']
|
373
534
|
self.active_color = style_out['active_color']
|
535
|
+
self.inactive_color = style_out['inactive_color']
|
374
536
|
|
375
537
|
# Configure the style for the progress bar
|
376
538
|
self.style = ttk.Style()
|
@@ -387,44 +549,155 @@ class spacrProgressBar(ttk.Progressbar):
|
|
387
549
|
# Set initial value to 0
|
388
550
|
self['value'] = 0
|
389
551
|
|
552
|
+
# Track whether to show the progress label
|
553
|
+
self.label = label
|
554
|
+
|
555
|
+
# Create the progress label (defer placement)
|
556
|
+
if self.label:
|
557
|
+
self.progress_label = tk.Label(parent, text="Processing: 0/0", anchor='w', justify='left', bg=self.inactive_color, fg=self.fg_color)
|
558
|
+
self.progress_label.grid_forget() # Temporarily hide it
|
559
|
+
|
560
|
+
# Initialize attributes for time and operation
|
561
|
+
self.operation_type = None
|
562
|
+
self.time_image = None
|
563
|
+
self.time_batch = None
|
564
|
+
self.time_left = None
|
565
|
+
|
566
|
+
def set_label_position(self):
|
567
|
+
if self.label and self.progress_label:
|
568
|
+
row_info = self.grid_info().get('row', 0)
|
569
|
+
col_info = self.grid_info().get('column', 0)
|
570
|
+
col_span = self.grid_info().get('columnspan', 1)
|
571
|
+
self.progress_label.grid(row=row_info + 1, column=col_info, columnspan=col_span, pady=5, padx=5, sticky='ew')
|
572
|
+
|
573
|
+
def update_label(self):
|
574
|
+
if self.label and self.progress_label:
|
575
|
+
# Update the progress label with current progress and additional info
|
576
|
+
label_text = f"Processing: {self['value']}/{self['maximum']}"
|
577
|
+
if self.operation_type:
|
578
|
+
label_text += f", {self.operation_type}"
|
579
|
+
if self.time_image:
|
580
|
+
label_text += f", Time/image: {self.time_image:.3f} sec"
|
581
|
+
if self.time_batch:
|
582
|
+
label_text += f", Time/batch: {self.time_batch:.3f} sec"
|
583
|
+
if self.time_left:
|
584
|
+
label_text += f", Time_left: {self.time_left:.3f} min"
|
585
|
+
self.progress_label.config(text=label_text)
|
586
|
+
|
587
|
+
def spacrScrollbarStyle(style, inactive_color, active_color):
|
588
|
+
# Check if custom elements already exist to avoid duplication
|
589
|
+
if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
|
590
|
+
style.element_create('custom.Vertical.Scrollbar.trough', 'from', 'clam')
|
591
|
+
if not style.element_names().count('custom.Vertical.Scrollbar.thumb'):
|
592
|
+
style.element_create('custom.Vertical.Scrollbar.thumb', 'from', 'clam')
|
593
|
+
|
594
|
+
style.layout('Custom.Vertical.TScrollbar',
|
595
|
+
[('Vertical.Scrollbar.trough', {'children': [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})], 'sticky': 'ns'})])
|
596
|
+
|
597
|
+
style.configure('Custom.Vertical.TScrollbar',
|
598
|
+
background=inactive_color,
|
599
|
+
troughcolor=inactive_color,
|
600
|
+
bordercolor=inactive_color,
|
601
|
+
lightcolor=inactive_color,
|
602
|
+
darkcolor=inactive_color)
|
603
|
+
|
604
|
+
style.map('Custom.Vertical.TScrollbar',
|
605
|
+
background=[('!active', inactive_color), ('active', active_color)],
|
606
|
+
troughcolor=[('!active', inactive_color), ('active', inactive_color)],
|
607
|
+
bordercolor=[('!active', inactive_color), ('active', inactive_color)],
|
608
|
+
lightcolor=[('!active', inactive_color), ('active', active_color)],
|
609
|
+
darkcolor=[('!active', inactive_color), ('active', active_color)])
|
610
|
+
|
390
611
|
class spacrFrame(ttk.Frame):
|
391
|
-
def __init__(self, container, width=None, *args, bg='black', **kwargs):
|
612
|
+
def __init__(self, container, width=None, *args, bg='black', radius=20, scrollbar=True, textbox=False, **kwargs):
|
392
613
|
super().__init__(container, *args, **kwargs)
|
393
614
|
self.configure(style='TFrame')
|
394
615
|
if width is None:
|
395
616
|
screen_width = self.winfo_screenwidth()
|
396
617
|
width = screen_width // 4
|
397
|
-
|
398
|
-
|
618
|
+
|
619
|
+
# Create the canvas
|
620
|
+
canvas = tk.Canvas(self, bg=bg, width=width, highlightthickness=0)
|
621
|
+
self.rounded_rectangle(canvas, 0, 0, width, self.winfo_screenheight(), radius, fill=bg)
|
622
|
+
|
623
|
+
# Define scrollbar styles
|
624
|
+
style_out = set_dark_style(ttk.Style())
|
625
|
+
self.inactive_color = style_out['inactive_color']
|
626
|
+
self.active_color = style_out['active_color']
|
627
|
+
self.fg_color = style_out['fg_color'] # Foreground color for text
|
628
|
+
|
629
|
+
# Set custom scrollbar style
|
630
|
+
style = ttk.Style()
|
631
|
+
spacrScrollbarStyle(style, self.inactive_color, self.active_color)
|
632
|
+
|
633
|
+
# Create scrollbar with custom style if scrollbar option is True
|
634
|
+
if scrollbar:
|
635
|
+
scrollbar_widget = ttk.Scrollbar(self, orient="vertical", command=canvas.yview, style='Custom.Vertical.TScrollbar')
|
636
|
+
|
637
|
+
if textbox:
|
638
|
+
self.scrollable_frame = tk.Text(canvas, bg=bg, fg=self.fg_color, wrap=tk.WORD)
|
639
|
+
else:
|
640
|
+
self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
|
399
641
|
|
400
|
-
self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
|
401
642
|
self.scrollable_frame.bind(
|
402
643
|
"<Configure>",
|
403
644
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
404
645
|
)
|
405
646
|
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
406
|
-
|
647
|
+
if scrollbar:
|
648
|
+
canvas.configure(yscrollcommand=scrollbar_widget.set)
|
407
649
|
|
408
650
|
canvas.grid(row=0, column=0, sticky="nsew")
|
409
|
-
scrollbar
|
651
|
+
if scrollbar:
|
652
|
+
scrollbar_widget.grid(row=0, column=1, sticky="ns")
|
410
653
|
|
411
654
|
self.grid_rowconfigure(0, weight=1)
|
412
655
|
self.grid_columnconfigure(0, weight=1)
|
413
|
-
|
656
|
+
if scrollbar:
|
657
|
+
self.grid_columnconfigure(1, weight=0)
|
414
658
|
|
415
|
-
style =
|
416
|
-
|
659
|
+
_ = set_dark_style(style, containers=[self], widgets=[canvas, self.scrollable_frame])
|
660
|
+
if scrollbar:
|
661
|
+
_ = set_dark_style(style, widgets=[scrollbar_widget])
|
662
|
+
|
663
|
+
def rounded_rectangle(self, canvas, x1, y1, x2, y2, radius=20, **kwargs):
|
664
|
+
points = [
|
665
|
+
x1 + radius, y1,
|
666
|
+
x2 - radius, y1,
|
667
|
+
x2 - radius, y1,
|
668
|
+
x2, y1,
|
669
|
+
x2, y1 + radius,
|
670
|
+
x2, y2 - radius,
|
671
|
+
x2, y2 - radius,
|
672
|
+
x2, y2,
|
673
|
+
x2 - radius, y2,
|
674
|
+
x1 + radius, y2,
|
675
|
+
x1 + radius, y2,
|
676
|
+
x1, y2,
|
677
|
+
x1, y2 - radius,
|
678
|
+
x1, y2 - radius,
|
679
|
+
x1, y1 + radius,
|
680
|
+
x1, y1 + radius,
|
681
|
+
x1, y1
|
682
|
+
]
|
683
|
+
return canvas.create_polygon(points, **kwargs, smooth=True)
|
417
684
|
|
418
685
|
class spacrLabel(tk.Frame):
|
419
|
-
def __init__(self, parent, text="", font=None, style=None, align="right", **kwargs):
|
686
|
+
def __init__(self, parent, text="", font=None, style=None, align="right", height=None, **kwargs):
|
420
687
|
valid_kwargs = {k: v for k, v in kwargs.items() if k not in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
|
421
688
|
super().__init__(parent, **valid_kwargs)
|
422
689
|
|
423
690
|
self.text = text
|
424
691
|
self.align = align
|
425
|
-
|
426
|
-
|
427
|
-
|
692
|
+
|
693
|
+
if height is None:
|
694
|
+
screen_height = self.winfo_screenheight()
|
695
|
+
label_height = screen_height // 50
|
696
|
+
label_width = label_height * 10
|
697
|
+
else:
|
698
|
+
label_height = height
|
699
|
+
label_width = label_height * 10
|
700
|
+
|
428
701
|
style_out = set_dark_style(ttk.Style())
|
429
702
|
|
430
703
|
self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=style_out['bg_color'])
|
@@ -459,7 +732,7 @@ class spacrLabel(tk.Frame):
|
|
459
732
|
self.canvas.itemconfig(self.label_text, text=text)
|
460
733
|
|
461
734
|
class spacrButton(tk.Frame):
|
462
|
-
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, *args, **kwargs):
|
735
|
+
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, animation=True, *args, **kwargs):
|
463
736
|
super().__init__(parent, *args, **kwargs)
|
464
737
|
|
465
738
|
self.text = text.capitalize() # Capitalize only the first letter of the text
|
@@ -468,6 +741,7 @@ class spacrButton(tk.Frame):
|
|
468
741
|
self.size = size
|
469
742
|
self.show_text = show_text
|
470
743
|
self.outline = outline
|
744
|
+
self.animation = animation # Add animation attribute
|
471
745
|
|
472
746
|
style_out = set_dark_style(ttk.Style())
|
473
747
|
|
@@ -534,13 +808,13 @@ class spacrButton(tk.Frame):
|
|
534
808
|
def on_enter(self, event=None):
|
535
809
|
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
536
810
|
self.update_description(event)
|
537
|
-
if not self.is_zoomed_in:
|
811
|
+
if self.animation and not self.is_zoomed_in:
|
538
812
|
self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
|
539
813
|
|
540
814
|
def on_leave(self, event=None):
|
541
815
|
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
542
816
|
self.clear_description(event)
|
543
|
-
if self.is_zoomed_in:
|
817
|
+
if self.animation and self.is_zoomed_in:
|
544
818
|
self.animate_zoom(0.65) # Reset the icon size to 65% of button size
|
545
819
|
|
546
820
|
def on_click(self, event=None):
|
@@ -1561,14 +1835,19 @@ class ModifyMaskApp:
|
|
1561
1835
|
self.update_display()
|
1562
1836
|
|
1563
1837
|
class AnnotateApp:
|
1564
|
-
def __init__(self, root, db_path, src, image_type=None, channels=None,
|
1838
|
+
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):
|
1565
1839
|
self.root = root
|
1566
1840
|
self.db_path = db_path
|
1567
1841
|
self.src = src
|
1568
1842
|
self.index = 0
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1843
|
+
|
1844
|
+
if isinstance(image_size, list):
|
1845
|
+
self.image_size = (int(image_size[0]), int(image_size[0]))
|
1846
|
+
elif isinstance(image_size, int):
|
1847
|
+
self.image_size = (image_size, image_size)
|
1848
|
+
else:
|
1849
|
+
raise ValueError("Invalid image size")
|
1850
|
+
|
1572
1851
|
self.annotation_column = annotation_column
|
1573
1852
|
self.image_type = image_type
|
1574
1853
|
self.channels = channels
|
@@ -1580,22 +1859,72 @@ class AnnotateApp:
|
|
1580
1859
|
self.adjusted_to_original_paths = {}
|
1581
1860
|
self.terminate = False
|
1582
1861
|
self.update_queue = Queue()
|
1583
|
-
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
1584
|
-
self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
|
1585
1862
|
self.measurement = measurement
|
1586
1863
|
self.threshold = threshold
|
1587
1864
|
|
1865
|
+
style_out = set_dark_style(ttk.Style())
|
1866
|
+
self.root.configure(bg=style_out['inactive_color'])
|
1867
|
+
|
1588
1868
|
self.filtered_paths_annotations = []
|
1589
1869
|
self.prefilter_paths_annotations()
|
1590
1870
|
|
1591
1871
|
self.db_update_thread = threading.Thread(target=self.update_database_worker)
|
1592
1872
|
self.db_update_thread.start()
|
1593
1873
|
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1874
|
+
# Set the initial window size and make it fit the screen size
|
1875
|
+
self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}")
|
1876
|
+
self.root.update_idletasks()
|
1877
|
+
|
1878
|
+
# Create the status label
|
1879
|
+
self.status_label = Label(root, text="", font=("Arial", 12), bg=self.root.cget('bg'))
|
1880
|
+
self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
1881
|
+
|
1882
|
+
# Place the buttons at the bottom right
|
1883
|
+
self.button_frame = Frame(root, bg=self.root.cget('bg'))
|
1884
|
+
self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
|
1885
|
+
|
1886
|
+
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1887
|
+
self.next_button.pack(side="right", padx=5)
|
1888
|
+
|
1889
|
+
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1890
|
+
self.previous_button.pack(side="right", padx=5)
|
1891
|
+
|
1892
|
+
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1893
|
+
self.exit_button.pack(side="right", padx=5)
|
1894
|
+
|
1895
|
+
# Calculate grid rows and columns based on the root window size and image size
|
1896
|
+
self.calculate_grid_dimensions()
|
1897
|
+
|
1898
|
+
# Create a frame to hold the image grid
|
1899
|
+
self.grid_frame = Frame(root, bg=self.root.cget('bg'))
|
1900
|
+
self.grid_frame.grid(row=0, column=0, columnspan=2, padx=0, pady=0, sticky="nsew")
|
1901
|
+
|
1902
|
+
for i in range(self.grid_rows * self.grid_cols):
|
1903
|
+
label = Label(self.grid_frame, bg=self.root.cget('bg'))
|
1904
|
+
label.grid(row=i // self.grid_cols, column=i % self.grid_cols, padx=2, pady=2, sticky="nsew")
|
1597
1905
|
self.labels.append(label)
|
1598
1906
|
|
1907
|
+
# Make the grid frame resize with the window
|
1908
|
+
self.root.grid_rowconfigure(0, weight=1)
|
1909
|
+
self.root.grid_columnconfigure(0, weight=1)
|
1910
|
+
self.root.grid_columnconfigure(1, weight=1)
|
1911
|
+
|
1912
|
+
for row in range(self.grid_rows):
|
1913
|
+
self.grid_frame.grid_rowconfigure(row, weight=1)
|
1914
|
+
for col in range(self.grid_cols):
|
1915
|
+
self.grid_frame.grid_columnconfigure(col, weight=1)
|
1916
|
+
|
1917
|
+
def calculate_grid_dimensions(self):
|
1918
|
+
window_width = self.root.winfo_width()
|
1919
|
+
window_height = self.root.winfo_height()
|
1920
|
+
|
1921
|
+
self.grid_cols = window_width // (self.image_size[0] + 4)
|
1922
|
+
self.grid_rows = (window_height - self.button_frame.winfo_height() - 4) // (self.image_size[1] + 4)
|
1923
|
+
|
1924
|
+
# Update to make sure grid_rows and grid_cols are at least 1
|
1925
|
+
self.grid_cols = max(1, self.grid_cols)
|
1926
|
+
self.grid_rows = max(1, self.grid_rows)
|
1927
|
+
|
1599
1928
|
def prefilter_paths_annotations(self):
|
1600
1929
|
from .io import _read_and_join_tables
|
1601
1930
|
from .utils import is_list_of_lists
|
@@ -1849,6 +2178,7 @@ class AnnotateApp:
|
|
1849
2178
|
self.update_queue.put(self.pending_updates.copy())
|
1850
2179
|
self.pending_updates.clear()
|
1851
2180
|
self.index += self.grid_rows * self.grid_cols
|
2181
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1852
2182
|
self.load_images()
|
1853
2183
|
|
1854
2184
|
def previous_page(self):
|
@@ -1858,16 +2188,20 @@ class AnnotateApp:
|
|
1858
2188
|
self.index -= self.grid_rows * self.grid_cols
|
1859
2189
|
if self.index < 0:
|
1860
2190
|
self.index = 0
|
2191
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1861
2192
|
self.load_images()
|
1862
2193
|
|
1863
2194
|
def shutdown(self):
|
1864
2195
|
self.terminate = True
|
1865
2196
|
self.update_queue.put(self.pending_updates.copy())
|
1866
|
-
self.pending_updates
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
2197
|
+
if not self.pending_updates:
|
2198
|
+
self.pending_updates.clear()
|
2199
|
+
self.db_update_thread.join()
|
2200
|
+
self.root.quit()
|
2201
|
+
self.root.destroy()
|
2202
|
+
print(f'Quit application')
|
2203
|
+
else:
|
2204
|
+
print('Waiting for pending updates to finish before quitting')
|
1871
2205
|
|
1872
2206
|
def create_menu_bar(root):
|
1873
2207
|
from .gui import initiate_root
|
@@ -1905,6 +2239,7 @@ def create_menu_bar(root):
|
|
1905
2239
|
|
1906
2240
|
# Add a separator and an exit option
|
1907
2241
|
app_menu.add_separator()
|
2242
|
+
app_menu.add_command(label="Help", command=lambda: webbrowser.open("https://readthedocs.org/projects/spacr/badge/?version=latest"))
|
1908
2243
|
app_menu.add_command(label="Exit", command=root.quit)
|
1909
2244
|
|
1910
2245
|
# Configure the menu for the root window
|