spacr 0.0.20__py3-none-any.whl → 0.0.35__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/alpha.py +291 -14
- spacr/annotate_app.py +7 -5
- spacr/chris.py +50 -0
- spacr/core.py +1301 -426
- spacr/foldseek.py +793 -0
- spacr/get_alfafold_structures.py +72 -0
- spacr/gui.py +144 -0
- spacr/gui_classify_app.py +65 -74
- spacr/gui_mask_app.py +110 -87
- spacr/gui_measure_app.py +104 -81
- spacr/gui_utils.py +276 -31
- spacr/io.py +261 -102
- spacr/mask_app.py +6 -3
- spacr/measure.py +150 -64
- spacr/plot.py +151 -12
- spacr/sim.py +666 -119
- spacr/timelapse.py +139 -9
- spacr/train.py +18 -10
- spacr/utils.py +43 -49
- {spacr-0.0.20.dist-info → spacr-0.0.35.dist-info}/METADATA +5 -2
- spacr-0.0.35.dist-info/RECORD +35 -0
- spacr-0.0.35.dist-info/entry_points.txt +8 -0
- spacr-0.0.20.dist-info/RECORD +0 -31
- spacr-0.0.20.dist-info/entry_points.txt +0 -7
- {spacr-0.0.20.dist-info → spacr-0.0.35.dist-info}/LICENSE +0 -0
- {spacr-0.0.20.dist-info → spacr-0.0.35.dist-info}/WHEEL +0 -0
- {spacr-0.0.20.dist-info → spacr-0.0.35.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -1,18 +1,272 @@
|
|
1
|
-
import spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv
|
1
|
+
import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
|
2
2
|
import matplotlib.pyplot as plt
|
3
3
|
matplotlib.use('Agg')
|
4
4
|
import numpy as np
|
5
5
|
import tkinter as tk
|
6
6
|
from tkinter import ttk, messagebox
|
7
|
-
|
7
|
+
import tkinter.font as tkFont
|
8
8
|
from torchvision import models
|
9
9
|
|
10
|
+
from .logger import log_function_call
|
11
|
+
|
10
12
|
try:
|
11
13
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
12
14
|
except AttributeError:
|
13
15
|
pass
|
14
16
|
|
15
|
-
|
17
|
+
def load_app(root, app_name, app_func):
|
18
|
+
# Destroy the current window
|
19
|
+
root.destroy()
|
20
|
+
# Create a new window for the app
|
21
|
+
app_window = tk.Tk()
|
22
|
+
app_window.title(f"SpaCr - {app_name}")
|
23
|
+
app_window.geometry("1200x800")
|
24
|
+
#app_window.attributes('-fullscreen', True)
|
25
|
+
app_window.configure(bg="black")
|
26
|
+
create_menu_bar(app_window) # Add menu to the new window
|
27
|
+
app_func(app_window, app_window.winfo_width(), app_window.winfo_height())
|
28
|
+
|
29
|
+
def create_menu_bar(root):
|
30
|
+
|
31
|
+
from .gui_mask_app import initiate_mask_root
|
32
|
+
from .gui_measure_app import initiate_measure_root
|
33
|
+
from .annotate_app import initiate_annotation_app_root
|
34
|
+
from .mask_app import initiate_mask_app_root
|
35
|
+
from .gui_classify_app import initiate_classify_root
|
36
|
+
|
37
|
+
gui_apps = {
|
38
|
+
"Mask": initiate_mask_root,
|
39
|
+
"Measure": initiate_measure_root,
|
40
|
+
"Annotate": initiate_annotation_app_root,
|
41
|
+
"Make Masks": initiate_mask_app_root,
|
42
|
+
"Classify": initiate_classify_root
|
43
|
+
}
|
44
|
+
# Create the menu bar
|
45
|
+
menu_bar = tk.Menu(root, bg="#008080", fg="white")
|
46
|
+
# Create a "SpaCr Applications" menu
|
47
|
+
app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
|
48
|
+
menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
|
49
|
+
# Add options to the "SpaCr Applications" menu
|
50
|
+
for app_name, app_func in gui_apps.items():
|
51
|
+
app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app(root, app_name, app_func))
|
52
|
+
# Add a separator and an exit option
|
53
|
+
app_menu.add_separator()
|
54
|
+
app_menu.add_command(label="Exit", command=root.quit)
|
55
|
+
# Configure the menu for the root window
|
56
|
+
root.config(menu=menu_bar)
|
57
|
+
|
58
|
+
class CustomButton(tk.Frame):
|
59
|
+
def __init__(self, parent, text="", command=None, *args, **kwargs):
|
60
|
+
super().__init__(parent, *args, **kwargs)
|
61
|
+
self.text = text
|
62
|
+
self.command = command
|
63
|
+
|
64
|
+
self.canvas = tk.Canvas(self, width=200, height=50, highlightthickness=0, bg="black")
|
65
|
+
self.canvas.grid(row=0, column=0)
|
66
|
+
|
67
|
+
self.button_bg = self.create_rounded_rectangle(0, 0, 200, 50, radius=20, fill="#800080")
|
68
|
+
|
69
|
+
# Load the Open Sans font
|
70
|
+
self.font_path = 'fonts/OpenSans-Regular.ttf'
|
71
|
+
if not os.path.exists(self.font_path):
|
72
|
+
self.download_font()
|
73
|
+
|
74
|
+
self.open_sans = tkFont.Font(family="Open Sans", size=10)
|
75
|
+
self.button_text = self.canvas.create_text(100, 25, text=self.text, fill="white", font=self.open_sans)
|
76
|
+
|
77
|
+
self.bind("<Enter>", self.on_enter)
|
78
|
+
self.bind("<Leave>", self.on_leave)
|
79
|
+
self.bind("<Button-1>", self.on_click)
|
80
|
+
self.canvas.bind("<Enter>", self.on_enter)
|
81
|
+
self.canvas.bind("<Leave>", self.on_leave)
|
82
|
+
self.canvas.bind("<Button-1>", self.on_click)
|
83
|
+
|
84
|
+
def on_enter(self, event=None):
|
85
|
+
self.canvas.itemconfig(self.button_bg, fill="#993399")
|
86
|
+
|
87
|
+
def on_leave(self, event=None):
|
88
|
+
self.canvas.itemconfig(self.button_bg, fill="#800080")
|
89
|
+
|
90
|
+
def on_click(self, event=None):
|
91
|
+
if self.command:
|
92
|
+
self.command()
|
93
|
+
|
94
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
95
|
+
points = [x1 + radius, y1,
|
96
|
+
x1 + radius, y1,
|
97
|
+
x2 - radius, y1,
|
98
|
+
x2 - radius, y1,
|
99
|
+
x2, y1,
|
100
|
+
x2, y1 + radius,
|
101
|
+
x2, y1 + radius,
|
102
|
+
x2, y2 - radius,
|
103
|
+
x2, y2 - radius,
|
104
|
+
x2, y2,
|
105
|
+
x2 - radius, y2,
|
106
|
+
x2 - radius, y2,
|
107
|
+
x1 + radius, y2,
|
108
|
+
x1 + radius, y2,
|
109
|
+
x1, y2,
|
110
|
+
x1, y2 - radius,
|
111
|
+
x1, y2 - radius,
|
112
|
+
x1, y1 + radius,
|
113
|
+
x1, y1 + radius,
|
114
|
+
x1, y1]
|
115
|
+
|
116
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
117
|
+
|
118
|
+
class ToggleSwitch(ttk.Frame):
|
119
|
+
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
120
|
+
super().__init__(parent, *args, **kwargs)
|
121
|
+
self.text = text
|
122
|
+
self.variable = variable if variable else tk.BooleanVar()
|
123
|
+
self.command = command
|
124
|
+
|
125
|
+
self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
|
126
|
+
self.canvas.grid(row=0, column=1, padx=(10, 0))
|
127
|
+
|
128
|
+
# Background rounded rectangle with smaller dimensions and no outline
|
129
|
+
self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
|
130
|
+
|
131
|
+
# Switch ball with no outline
|
132
|
+
self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
|
133
|
+
|
134
|
+
self.label = ttk.Label(self, text=self.text, background="black", foreground="white")
|
135
|
+
self.label.grid(row=0, column=0, padx=(0, 10))
|
136
|
+
|
137
|
+
self.bind("<Button-1>", self.toggle)
|
138
|
+
self.canvas.bind("<Button-1>", self.toggle)
|
139
|
+
self.label.bind("<Button-1>", self.toggle)
|
140
|
+
|
141
|
+
self.update_switch()
|
142
|
+
|
143
|
+
def toggle(self, event=None):
|
144
|
+
self.variable.set(not self.variable.get())
|
145
|
+
self.animate_switch()
|
146
|
+
if self.command:
|
147
|
+
self.command()
|
148
|
+
|
149
|
+
def update_switch(self):
|
150
|
+
if self.variable.get():
|
151
|
+
self.canvas.itemconfig(self.switch, fill="#008080") # Teal
|
152
|
+
self.canvas.coords(self.switch, 24, 4, 36, 16) # Move switch to the right
|
153
|
+
else:
|
154
|
+
self.canvas.itemconfig(self.switch, fill="#800080") # Purple
|
155
|
+
self.canvas.coords(self.switch, 4, 4, 16, 16) # Move switch to the left
|
156
|
+
|
157
|
+
def animate_switch(self):
|
158
|
+
if self.variable.get():
|
159
|
+
start_x, end_x = 4, 24
|
160
|
+
final_color = "#008080" # Teal
|
161
|
+
else:
|
162
|
+
start_x, end_x = 24, 4
|
163
|
+
final_color = "#800080" # Purple
|
164
|
+
|
165
|
+
self.animate_movement(start_x, end_x, final_color)
|
166
|
+
|
167
|
+
def animate_movement(self, start_x, end_x, final_color):
|
168
|
+
step = 1 if start_x < end_x else -1
|
169
|
+
for i in range(start_x, end_x, step):
|
170
|
+
self.canvas.coords(self.switch, i, 4, i + 12, 16)
|
171
|
+
self.canvas.update()
|
172
|
+
self.after(10) # Small delay for smooth animation
|
173
|
+
self.canvas.itemconfig(self.switch, fill=final_color)
|
174
|
+
|
175
|
+
def get(self):
|
176
|
+
return self.variable.get()
|
177
|
+
|
178
|
+
def set(self, value):
|
179
|
+
self.variable.set(value)
|
180
|
+
self.update_switch()
|
181
|
+
|
182
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs): # Smaller radius for smaller switch
|
183
|
+
points = [x1 + radius, y1,
|
184
|
+
x1 + radius, y1,
|
185
|
+
x2 - radius, y1,
|
186
|
+
x2 - radius, y1,
|
187
|
+
x2, y1,
|
188
|
+
x2, y1 + radius,
|
189
|
+
x2, y1 + radius,
|
190
|
+
x2, y2 - radius,
|
191
|
+
x2, y2 - radius,
|
192
|
+
x2, y2,
|
193
|
+
x2 - radius, y2,
|
194
|
+
x2 - radius, y2,
|
195
|
+
x1 + radius, y2,
|
196
|
+
x1 + radius, y2,
|
197
|
+
x1, y2,
|
198
|
+
x1, y2 - radius,
|
199
|
+
x1, y2 - radius,
|
200
|
+
x1, y1 + radius,
|
201
|
+
x1, y1 + radius,
|
202
|
+
x1, y1]
|
203
|
+
|
204
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
205
|
+
|
206
|
+
def set_default_font(root, font_name="Helvetica", size=12):
|
207
|
+
default_font = (font_name, size)
|
208
|
+
root.option_add("*Font", default_font)
|
209
|
+
root.option_add("*TButton.Font", default_font)
|
210
|
+
root.option_add("*TLabel.Font", default_font)
|
211
|
+
root.option_add("*TEntry.Font", default_font)
|
212
|
+
|
213
|
+
def check_and_download_font():
|
214
|
+
font_name = "Open Sans"
|
215
|
+
font_dir = "fonts"
|
216
|
+
font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
|
217
|
+
|
218
|
+
# Check if the font is already available
|
219
|
+
available_fonts = list(tkFont.families())
|
220
|
+
if font_name not in available_fonts:
|
221
|
+
print(f"Font '{font_name}' not found. Downloading...")
|
222
|
+
if not os.path.exists(font_dir):
|
223
|
+
os.makedirs(font_dir)
|
224
|
+
|
225
|
+
if not os.path.exists(font_path):
|
226
|
+
url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
|
227
|
+
response = requests.get(url)
|
228
|
+
with open(font_path, "wb") as f:
|
229
|
+
f.write(response.content)
|
230
|
+
|
231
|
+
# Load the font
|
232
|
+
try:
|
233
|
+
tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
|
234
|
+
tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
|
235
|
+
tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
|
236
|
+
except tk.TclError:
|
237
|
+
tkFont.nametofont("TkDefaultFont").configure(family="Open Sans", size=10)
|
238
|
+
tkFont.nametofont("TkTextFont").configure(family="Open Sans", size=10)
|
239
|
+
tkFont.nametofont("TkHeadingFont").configure(family="Open Sans", size=12)
|
240
|
+
else:
|
241
|
+
tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
|
242
|
+
tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
|
243
|
+
tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
|
244
|
+
|
245
|
+
def style_text_boxes_v1(style):
|
246
|
+
style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff')
|
247
|
+
style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff')
|
248
|
+
style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=('Open Sans', 10, 'bold'))
|
249
|
+
style.map('Custom.TButton',
|
250
|
+
background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
|
251
|
+
foreground=[('active', '#ffffff'), ('disabled', '#888888')])
|
252
|
+
style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=('Open Sans', 10))
|
253
|
+
style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat')
|
254
|
+
style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
|
255
|
+
|
256
|
+
def style_text_boxes(style):
|
257
|
+
check_and_download_font()
|
258
|
+
open_sans = tkFont.Font(family="Open Sans", size=10) # Define the Open Sans font
|
259
|
+
style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=open_sans)
|
260
|
+
style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=open_sans)
|
261
|
+
style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=open_sans)
|
262
|
+
style.map('Custom.TButton',
|
263
|
+
background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
|
264
|
+
foreground=[('active', '#ffffff'), ('disabled', '#888888')])
|
265
|
+
style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=open_sans)
|
266
|
+
style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat', font=open_sans)
|
267
|
+
style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
|
268
|
+
|
269
|
+
|
16
270
|
|
17
271
|
def read_settings_from_csv(csv_file_path):
|
18
272
|
settings = {}
|
@@ -51,17 +305,8 @@ def disable_interactivity(fig):
|
|
51
305
|
for handler_id in list(handlers.keys()):
|
52
306
|
fig.canvas.mpl_disconnect(handler_id)
|
53
307
|
|
54
|
-
def set_default_font(app, font_name="Arial Bold", size=10):
|
55
|
-
default_font = nametofont("TkDefaultFont")
|
56
|
-
text_font = nametofont("TkTextFont")
|
57
|
-
fixed_font = nametofont("TkFixedFont")
|
58
|
-
|
59
|
-
# Set the family to Open Sans and size as desired
|
60
|
-
for font in (default_font, text_font, fixed_font):
|
61
|
-
font.config(family=font_name, size=size)
|
62
|
-
|
63
308
|
class ScrollableFrame(ttk.Frame):
|
64
|
-
def __init__(self, container, *args, bg='
|
309
|
+
def __init__(self, container, *args, bg='black', **kwargs):
|
65
310
|
super().__init__(container, *args, **kwargs)
|
66
311
|
self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
|
67
312
|
|
@@ -366,32 +611,31 @@ def classify_variables():
|
|
366
611
|
}
|
367
612
|
return variables
|
368
613
|
|
369
|
-
|
370
|
-
#@log_function_call
|
371
614
|
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
372
|
-
label = ttk.Label(frame, text=label_text, style='TLabel') #
|
615
|
+
label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
|
373
616
|
label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
|
374
617
|
|
375
618
|
if var_type == 'entry':
|
376
619
|
var = tk.StringVar(value=default_value) # Set default value
|
377
|
-
entry = ttk.Entry(frame, textvariable=var, style='TEntry') #
|
620
|
+
entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Apply TEntry style for entries
|
378
621
|
entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
622
|
+
return (label, entry, var) # Return both the label and the entry, and the variable
|
379
623
|
elif var_type == 'check':
|
380
624
|
var = tk.BooleanVar(value=default_value) # Set default value (True/False)
|
381
|
-
|
382
|
-
check = ttk.Checkbutton(frame, variable=var, style='Dark.TCheckbutton')
|
625
|
+
check = ToggleSwitch(frame, text=label_text, variable=var) # Use ToggleSwitch class
|
383
626
|
check.grid(column=1, row=row, sticky=tk.W, padx=5)
|
627
|
+
return (label, check, var) # Return both the label and the checkbutton, and the variable
|
384
628
|
elif var_type == 'combo':
|
385
629
|
var = tk.StringVar(value=default_value) # Set default value
|
386
|
-
combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') #
|
630
|
+
combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Apply TCombobox style
|
387
631
|
combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
388
632
|
if default_value:
|
389
633
|
combo.set(default_value)
|
634
|
+
return (label, combo, var) # Return both the label and the combobox, and the variable
|
390
635
|
else:
|
391
636
|
var = None # Placeholder in case of an undefined var_type
|
392
|
-
|
393
|
-
|
394
|
-
|
637
|
+
return (label, None, var)
|
638
|
+
|
395
639
|
def mask_variables():
|
396
640
|
variables = {
|
397
641
|
'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
|
@@ -458,7 +702,8 @@ def generate_fields(variables, scrollable_frame):
|
|
458
702
|
vars_dict = {}
|
459
703
|
row = 0
|
460
704
|
for key, (var_type, options, default_value) in variables.items():
|
461
|
-
|
705
|
+
label, widget, var = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
|
706
|
+
vars_dict[key] = (label, widget, var) # Store the label, widget, and variable
|
462
707
|
row += 1
|
463
708
|
return vars_dict
|
464
709
|
|
@@ -474,7 +719,7 @@ class TextRedirector(object):
|
|
474
719
|
pass
|
475
720
|
|
476
721
|
def create_dark_mode(root, style, console_output):
|
477
|
-
dark_bg = '
|
722
|
+
dark_bg = 'black'
|
478
723
|
light_text = 'white'
|
479
724
|
dark_text = 'black'
|
480
725
|
input_bg = '#555555' # Slightly lighter background for input fields
|
@@ -490,14 +735,14 @@ def create_dark_mode(root, style, console_output):
|
|
490
735
|
style.map('TCombobox', fieldbackground=[('readonly', input_bg)], selectbackground=[('readonly', input_bg)], foreground=[('readonly', dark_text)])
|
491
736
|
|
492
737
|
if console_output != None:
|
493
|
-
console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("
|
738
|
+
console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("Open Sans", 12)
|
494
739
|
root.configure(bg=dark_bg)
|
495
740
|
|
496
741
|
def set_dark_style(style):
|
497
|
-
style.configure('TFrame', background='
|
498
|
-
style.configure('TLabel', background='
|
499
|
-
style.configure('TEntry', background='
|
500
|
-
style.configure('TCheckbutton', background='
|
742
|
+
style.configure('TFrame', background='black')
|
743
|
+
style.configure('TLabel', background='black', foreground='white')
|
744
|
+
style.configure('TEntry', background='black', foreground='white')
|
745
|
+
style.configure('TCheckbutton', background='black', foreground='white')
|
501
746
|
|
502
747
|
#@log_function_call
|
503
748
|
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
@@ -580,7 +825,7 @@ def measure_crop_wrapper(settings, q, fig_queue):
|
|
580
825
|
|
581
826
|
try:
|
582
827
|
print('start')
|
583
|
-
spacr.measure.measure_crop(settings=settings
|
828
|
+
spacr.measure.measure_crop(settings=settings)
|
584
829
|
except Exception as e:
|
585
830
|
errorMessage = f"Error during processing: {e}"
|
586
831
|
q.put(errorMessage) # Send the error message to the GUI via the queue
|