spacr 0.1.50__py3-none-any.whl → 0.1.61__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 +8 -2
- spacr/app_annotate.py +3 -3
- spacr/app_make_masks.py +3 -6
- spacr/app_make_masks_v2.py +4 -4
- spacr/app_sequencing.py +8 -0
- spacr/app_umap.py +8 -0
- spacr/core.py +9 -7
- spacr/gui.py +12 -6
- spacr/gui_core.py +608 -0
- spacr/gui_elements.py +322 -0
- spacr/gui_run.py +58 -0
- spacr/gui_utils.py +21 -1407
- spacr/gui_wrappers.py +137 -0
- spacr/measure.py +6 -8
- spacr/plot.py +53 -1
- spacr/sequencing.py +1 -17
- spacr/settings.py +428 -4
- spacr/utils.py +19 -11
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/METADATA +1 -1
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/RECORD +24 -18
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/LICENSE +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/WHEEL +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/entry_points.txt +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
ADDED
@@ -0,0 +1,322 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
from tkinter import ttk
|
3
|
+
import tkinter.font as tkFont
|
4
|
+
|
5
|
+
class spacrDropdownMenu(tk.OptionMenu):
|
6
|
+
def __init__(self, parent, variable, options, command=None, **kwargs):
|
7
|
+
self.variable = variable
|
8
|
+
self.variable.set("Select Category")
|
9
|
+
super().__init__(parent, self.variable, *options, command=command, **kwargs)
|
10
|
+
self.config(bg='black', fg='white', font=('Helvetica', 12), indicatoron=0)
|
11
|
+
self.menu = self['menu']
|
12
|
+
self.menu.config(bg='black', fg='white', font=('Helvetica', 12))
|
13
|
+
|
14
|
+
def update_styles(self, active_categories):
|
15
|
+
menu = self['menu']
|
16
|
+
for idx, option in enumerate(self['menu'].entrycget(idx, "label") for idx in range(self['menu'].index("end")+1)):
|
17
|
+
if option in active_categories:
|
18
|
+
menu.entryconfig(idx, background='teal', foreground='white')
|
19
|
+
else:
|
20
|
+
menu.entryconfig(idx, background='black', foreground='white')
|
21
|
+
|
22
|
+
class spacrCheckbutton(ttk.Checkbutton):
|
23
|
+
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
24
|
+
super().__init__(parent, *args, **kwargs)
|
25
|
+
self.text = text
|
26
|
+
self.variable = variable if variable else tk.BooleanVar()
|
27
|
+
self.command = command
|
28
|
+
self.configure(text=self.text, variable=self.variable, command=self.command, style='Spacr.TCheckbutton')
|
29
|
+
|
30
|
+
class spacrFrame(ttk.Frame):
|
31
|
+
def __init__(self, container, width=None, *args, bg='black', **kwargs):
|
32
|
+
super().__init__(container, *args, **kwargs)
|
33
|
+
self.configure(style='TFrame')
|
34
|
+
if width is None:
|
35
|
+
screen_width = self.winfo_screenwidth()
|
36
|
+
width = screen_width // 4
|
37
|
+
canvas = tk.Canvas(self, bg=bg, width=width)
|
38
|
+
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
|
39
|
+
|
40
|
+
self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
|
41
|
+
self.scrollable_frame.bind(
|
42
|
+
"<Configure>",
|
43
|
+
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
44
|
+
)
|
45
|
+
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
46
|
+
canvas.configure(yscrollcommand=scrollbar.set)
|
47
|
+
|
48
|
+
canvas.grid(row=0, column=0, sticky="nsew")
|
49
|
+
scrollbar.grid(row=0, column=1, sticky="ns")
|
50
|
+
|
51
|
+
self.grid_rowconfigure(0, weight=1)
|
52
|
+
self.grid_columnconfigure(0, weight=1)
|
53
|
+
self.grid_columnconfigure(1, weight=0)
|
54
|
+
|
55
|
+
for child in self.scrollable_frame.winfo_children():
|
56
|
+
child.configure(bg='black')
|
57
|
+
|
58
|
+
class spacrLabel(tk.Frame):
|
59
|
+
def __init__(self, parent, text="", font=None, style=None, align="right", **kwargs):
|
60
|
+
label_kwargs = {k: v for k, v in kwargs.items() if k in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
|
61
|
+
for key in label_kwargs.keys():
|
62
|
+
kwargs.pop(key)
|
63
|
+
super().__init__(parent, **kwargs)
|
64
|
+
self.text = text
|
65
|
+
self.kwargs = label_kwargs
|
66
|
+
self.align = align
|
67
|
+
screen_height = self.winfo_screenheight()
|
68
|
+
label_height = screen_height // 50
|
69
|
+
label_width = label_height * 10
|
70
|
+
self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=self.kwargs.get("background", "black"))
|
71
|
+
self.canvas.grid(row=0, column=0, sticky="ew")
|
72
|
+
|
73
|
+
self.font_style = font if font else tkFont.Font(family=self.kwargs.get("font_family", "Helvetica"), size=self.kwargs.get("font_size", 12), weight=tkFont.NORMAL)
|
74
|
+
self.style = style
|
75
|
+
|
76
|
+
if self.align == "center":
|
77
|
+
anchor_value = tk.CENTER
|
78
|
+
text_anchor = 'center'
|
79
|
+
else: # default to right alignment
|
80
|
+
anchor_value = tk.E
|
81
|
+
text_anchor = 'e'
|
82
|
+
|
83
|
+
if self.style:
|
84
|
+
ttk_style = ttk.Style()
|
85
|
+
ttk_style.configure(self.style, **label_kwargs)
|
86
|
+
self.label_text = ttk.Label(self.canvas, text=self.text, style=self.style, anchor=text_anchor, justify=text_anchor)
|
87
|
+
self.label_text.pack(fill=tk.BOTH, expand=True)
|
88
|
+
else:
|
89
|
+
self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
|
90
|
+
label_height // 2, text=self.text, fill=self.kwargs.get("foreground", "white"),
|
91
|
+
font=self.font_style, anchor=anchor_value, justify=tk.RIGHT)
|
92
|
+
|
93
|
+
def set_text(self, text):
|
94
|
+
if self.style:
|
95
|
+
self.label_text.config(text=text)
|
96
|
+
else:
|
97
|
+
self.canvas.itemconfig(self.label_text, text=text)
|
98
|
+
|
99
|
+
class spacrButton(tk.Frame):
|
100
|
+
def __init__(self, parent, text="", command=None, font=None, *args, **kwargs):
|
101
|
+
super().__init__(parent, *args, **kwargs)
|
102
|
+
self.text = text
|
103
|
+
self.command = command
|
104
|
+
screen_height = self.winfo_screenheight()
|
105
|
+
button_height = screen_height // 50
|
106
|
+
button_width = button_height * 3
|
107
|
+
|
108
|
+
# Increase the canvas size to accommodate the button and the rim
|
109
|
+
self.canvas = tk.Canvas(self, width=button_width + 4, height=button_height + 4, highlightthickness=0, bg="black")
|
110
|
+
self.canvas.grid(row=0, column=0)
|
111
|
+
|
112
|
+
self.button_bg = self.create_rounded_rectangle(2, 2, button_width + 2, button_height + 2, radius=20, fill="#000000", outline="#ffffff")
|
113
|
+
|
114
|
+
self.font_style = font if font else tkFont.Font(family="Helvetica", size=12, weight=tkFont.NORMAL)
|
115
|
+
self.button_text = self.canvas.create_text((button_width + 4) // 2, (button_height + 4) // 2, text=self.text, fill="white", font=self.font_style)
|
116
|
+
|
117
|
+
self.bind("<Enter>", self.on_enter)
|
118
|
+
self.bind("<Leave>", self.on_leave)
|
119
|
+
self.bind("<Button-1>", self.on_click)
|
120
|
+
self.canvas.bind("<Enter>", self.on_enter)
|
121
|
+
self.canvas.bind("<Leave>", self.on_leave)
|
122
|
+
self.canvas.bind("<Button-1>", self.on_click)
|
123
|
+
|
124
|
+
def on_enter(self, event=None):
|
125
|
+
self.canvas.itemconfig(self.button_bg, fill="#008080") # Teal color
|
126
|
+
|
127
|
+
def on_leave(self, event=None):
|
128
|
+
self.canvas.itemconfig(self.button_bg, fill="#000000") # Black color
|
129
|
+
|
130
|
+
def on_click(self, event=None):
|
131
|
+
if self.command:
|
132
|
+
self.command()
|
133
|
+
|
134
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
135
|
+
points = [
|
136
|
+
x1 + radius, y1,
|
137
|
+
x1 + radius, y1,
|
138
|
+
x2 - radius, y1,
|
139
|
+
x2 - radius, y1,
|
140
|
+
x2, y1,
|
141
|
+
x2, y1 + radius,
|
142
|
+
x2, y1 + radius,
|
143
|
+
x2, y2 - radius,
|
144
|
+
x2, y2 - radius,
|
145
|
+
x2, y2,
|
146
|
+
x2 - radius, y2,
|
147
|
+
x2 - radius, y2,
|
148
|
+
x1 + radius, y2,
|
149
|
+
x1 + radius, y2,
|
150
|
+
x1, y2,
|
151
|
+
x1, y2 - radius,
|
152
|
+
x1, y2 - radius,
|
153
|
+
x1, y1 + radius,
|
154
|
+
x1, y1 + radius,
|
155
|
+
x1, y1
|
156
|
+
]
|
157
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
158
|
+
|
159
|
+
class spacrSwitch(ttk.Frame):
|
160
|
+
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
161
|
+
super().__init__(parent, *args, **kwargs)
|
162
|
+
self.text = text
|
163
|
+
self.variable = variable if variable else tk.BooleanVar()
|
164
|
+
self.command = command
|
165
|
+
self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
|
166
|
+
self.canvas.grid(row=0, column=1, padx=(10, 0))
|
167
|
+
self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
|
168
|
+
self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
|
169
|
+
self.label = spacrLabel(self, text=self.text, background="black", foreground="white")
|
170
|
+
self.label.grid(row=0, column=0, padx=(0, 10))
|
171
|
+
self.bind("<Button-1>", self.toggle)
|
172
|
+
self.canvas.bind("<Button-1>", self.toggle)
|
173
|
+
self.label.bind("<Button-1>", self.toggle)
|
174
|
+
self.update_switch()
|
175
|
+
|
176
|
+
def toggle(self, event=None):
|
177
|
+
self.variable.set(not self.variable.get())
|
178
|
+
self.animate_switch()
|
179
|
+
if self.command:
|
180
|
+
self.command()
|
181
|
+
|
182
|
+
def update_switch(self):
|
183
|
+
if self.variable.get():
|
184
|
+
self.canvas.itemconfig(self.switch, fill="#008080") # Teal
|
185
|
+
self.canvas.coords(self.switch, 24, 4, 36, 16) # Move switch to the right
|
186
|
+
else:
|
187
|
+
self.canvas.itemconfig(self.switch, fill="#800080") # Purple
|
188
|
+
self.canvas.coords(self.switch, 4, 4, 16, 16) # Move switch to the left
|
189
|
+
|
190
|
+
def animate_switch(self):
|
191
|
+
if self.variable.get():
|
192
|
+
start_x, end_x = 4, 24
|
193
|
+
final_color = "#008080" # Teal
|
194
|
+
else:
|
195
|
+
start_x, end_x = 24, 4
|
196
|
+
final_color = "#800080" # Purple
|
197
|
+
|
198
|
+
self.animate_movement(start_x, end_x, final_color)
|
199
|
+
|
200
|
+
def animate_movement(self, start_x, end_x, final_color):
|
201
|
+
step = 1 if start_x < end_x else -1
|
202
|
+
for i in range(start_x, end_x, step):
|
203
|
+
self.canvas.coords(self.switch, i, 4, i + 12, 16)
|
204
|
+
self.canvas.update()
|
205
|
+
self.after(10) # Small delay for smooth animation
|
206
|
+
self.canvas.itemconfig(self.switch, fill=final_color)
|
207
|
+
|
208
|
+
def get(self):
|
209
|
+
return self.variable.get()
|
210
|
+
|
211
|
+
def set(self, value):
|
212
|
+
self.variable.set(value)
|
213
|
+
self.update_switch()
|
214
|
+
|
215
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs): # Smaller radius for smaller switch
|
216
|
+
points = [x1 + radius, y1,
|
217
|
+
x1 + radius, y1,
|
218
|
+
x2 - radius, y1,
|
219
|
+
x2 - radius, y1,
|
220
|
+
x2, y1,
|
221
|
+
x2, y1 + radius,
|
222
|
+
x2, y1 + radius,
|
223
|
+
x2, y2 - radius,
|
224
|
+
x2, y2 - radius,
|
225
|
+
x2, y2,
|
226
|
+
x2 - radius, y2,
|
227
|
+
x2 - radius, y2,
|
228
|
+
x1 + radius, y2,
|
229
|
+
x1 + radius, y2,
|
230
|
+
x1, y2,
|
231
|
+
x1, y2 - radius,
|
232
|
+
x1, y2 - radius,
|
233
|
+
x1, y1 + radius,
|
234
|
+
x1, y1 + radius,
|
235
|
+
x1, y1]
|
236
|
+
|
237
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
238
|
+
|
239
|
+
class spacrToolTip:
|
240
|
+
def __init__(self, widget, text):
|
241
|
+
self.widget = widget
|
242
|
+
self.text = text
|
243
|
+
self.tooltip_window = None
|
244
|
+
widget.bind("<Enter>", self.show_tooltip)
|
245
|
+
widget.bind("<Leave>", self.hide_tooltip)
|
246
|
+
|
247
|
+
def show_tooltip(self, event):
|
248
|
+
x = event.x_root + 20
|
249
|
+
y = event.y_root + 10
|
250
|
+
self.tooltip_window = tk.Toplevel(self.widget)
|
251
|
+
self.tooltip_window.wm_overrideredirect(True)
|
252
|
+
self.tooltip_window.wm_geometry(f"+{x}+{y}")
|
253
|
+
self.tooltip_window.configure(bg='black')
|
254
|
+
label = tk.Label(self.tooltip_window, text=self.text, background="#333333", foreground="white", relief='flat', borderwidth=0)
|
255
|
+
label.grid(row=0, column=0, padx=5, pady=5)
|
256
|
+
|
257
|
+
def hide_tooltip(self, event):
|
258
|
+
if self.tooltip_window:
|
259
|
+
self.tooltip_window.destroy()
|
260
|
+
self.tooltip_window = None
|
261
|
+
|
262
|
+
def create_menu_bar(root):
|
263
|
+
from .app_annotate import initiate_annotation_app_root
|
264
|
+
from .app_make_masks import initiate_mask_app_root
|
265
|
+
from .gui_utils import load_app
|
266
|
+
|
267
|
+
gui_apps = {
|
268
|
+
"Mask": 'mask',
|
269
|
+
"Measure": 'measure',
|
270
|
+
"Annotate": initiate_annotation_app_root,
|
271
|
+
"Make Masks": initiate_mask_app_root,
|
272
|
+
"Classify": 'classify',
|
273
|
+
"Sequencing": 'sequencing',
|
274
|
+
"Umap": 'umap'
|
275
|
+
}
|
276
|
+
|
277
|
+
def load_app_wrapper(app_name, app_func):
|
278
|
+
load_app(root, app_name, app_func)
|
279
|
+
|
280
|
+
# Create the menu bar
|
281
|
+
menu_bar = tk.Menu(root, bg="#008080", fg="white")
|
282
|
+
# Create a "SpaCr Applications" menu
|
283
|
+
app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
|
284
|
+
menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
|
285
|
+
# Add options to the "SpaCr Applications" menu
|
286
|
+
for app_name, app_func in gui_apps.items():
|
287
|
+
app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
|
288
|
+
# Add a separator and an exit option
|
289
|
+
app_menu.add_separator()
|
290
|
+
app_menu.add_command(label="Exit", command=root.quit)
|
291
|
+
# Configure the menu for the root window
|
292
|
+
root.config(menu=menu_bar)
|
293
|
+
|
294
|
+
def set_dark_style(style):
|
295
|
+
font_style = tkFont.Font(family="Helvetica", size=24)
|
296
|
+
style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style)
|
297
|
+
style.configure('TCombobox', fieldbackground='black', background='black', foreground='#ffffff', font=font_style)
|
298
|
+
style.map('TCombobox', fieldbackground=[('readonly', 'black')], foreground=[('readonly', '#ffffff')])
|
299
|
+
style.configure('Custom.TButton', background='black', foreground='white', bordercolor='white', focusthickness=3, focuscolor='white', font=('Helvetica', 12))
|
300
|
+
style.map('Custom.TButton', background=[('active', 'teal'), ('!active', 'black')], foreground=[('active', 'white'), ('!active', 'white')], bordercolor=[('active', 'white'), ('!active', 'white')])
|
301
|
+
style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='black', foreground='#ffffff', font=font_style)
|
302
|
+
style.configure('Spacr.TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font="15")
|
303
|
+
style.map('Spacr.TCheckbutton', background=[('selected', 'black'), ('active', 'black')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
|
304
|
+
style.configure('TLabel', background='black', foreground='#ffffff', font=font_style)
|
305
|
+
style.configure('TFrame', background='black')
|
306
|
+
style.configure('TPanedwindow', background='black')
|
307
|
+
style.configure('TNotebook', background='black', tabmargins=[2, 5, 2, 0])
|
308
|
+
style.configure('TNotebook.Tab', background='black', foreground='#ffffff', padding=[5, 5], font=font_style)
|
309
|
+
style.map('TNotebook.Tab', background=[('selected', '#555555'), ('active', '#555555')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
|
310
|
+
style.configure('TButton', background='black', foreground='#ffffff', padding='5 5 5 5', font=font_style)
|
311
|
+
style.map('TButton', background=[('active', '#555555'), ('disabled', '#333333')])
|
312
|
+
style.configure('Vertical.TScrollbar', background='black', troughcolor='black', bordercolor='black')
|
313
|
+
style.configure('Horizontal.TScrollbar', background='black', troughcolor='black', bordercolor='black')
|
314
|
+
style.configure('Custom.TLabelFrame', font=('Helvetica', 10, 'bold'), background='black', foreground='white', relief='solid', borderwidth=1)
|
315
|
+
style.configure('Custom.TLabelFrame.Label', background='black', foreground='white', font=('Helvetica', 10, 'bold'))
|
316
|
+
|
317
|
+
def set_default_font(root, font_name="Helvetica", size=12):
|
318
|
+
default_font = (font_name, size)
|
319
|
+
root.option_add("*Font", default_font)
|
320
|
+
root.option_add("*TButton.Font", default_font)
|
321
|
+
root.option_add("*TLabel.Font", default_font)
|
322
|
+
root.option_add("*TEntry.Font", default_font)
|
spacr/gui_run.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
import traceback
|
2
|
+
from .gui_wrappers import measure_crop_wrapper, preprocess_generate_masks_wrapper, train_test_model_wrapper, umap_wrapper, sequencing_wrapper
|
3
|
+
|
4
|
+
def run_mask_gui(settings, q, fig_queue, stop_requested):
|
5
|
+
from .gui_utils import process_stdout_stderr
|
6
|
+
process_stdout_stderr(q)
|
7
|
+
try:
|
8
|
+
preprocess_generate_masks_wrapper(settings, q, fig_queue)
|
9
|
+
except Exception as e:
|
10
|
+
q.put(f"Error during processing: {e}")
|
11
|
+
traceback.print_exc()
|
12
|
+
finally:
|
13
|
+
stop_requested.value = 1
|
14
|
+
|
15
|
+
def run_sequencing_gui(settings, q, fig_queue, stop_requested):
|
16
|
+
from .gui_utils import process_stdout_stderr
|
17
|
+
process_stdout_stderr(q)
|
18
|
+
try:
|
19
|
+
sequencing_wrapper(settings, q, fig_queue)
|
20
|
+
except Exception as e:
|
21
|
+
q.put(f"Error during processing: {e}")
|
22
|
+
traceback.print_exc()
|
23
|
+
finally:
|
24
|
+
stop_requested.value = 1
|
25
|
+
|
26
|
+
def run_umap_gui(settings, q, fig_queue, stop_requested):
|
27
|
+
from .gui_utils import process_stdout_stderr
|
28
|
+
process_stdout_stderr(q)
|
29
|
+
try:
|
30
|
+
umap_wrapper(settings, q, fig_queue)
|
31
|
+
except Exception as e:
|
32
|
+
q.put(f"Error during processing: {e}")
|
33
|
+
traceback.print_exc()
|
34
|
+
finally:
|
35
|
+
stop_requested.value = 1
|
36
|
+
|
37
|
+
def run_measure_gui(settings, q, fig_queue, stop_requested):
|
38
|
+
from .gui_utils import process_stdout_stderr
|
39
|
+
process_stdout_stderr(q)
|
40
|
+
try:
|
41
|
+
settings['input_folder'] = settings['src']
|
42
|
+
measure_crop_wrapper(settings=settings, q=q, fig_queue=fig_queue)
|
43
|
+
except Exception as e:
|
44
|
+
q.put(f"Error during processing: {e}")
|
45
|
+
traceback.print_exc()
|
46
|
+
finally:
|
47
|
+
stop_requested.value = 1
|
48
|
+
|
49
|
+
def run_classify_gui(settings, q, fig_queue, stop_requested):
|
50
|
+
from .gui_utils import process_stdout_stderr
|
51
|
+
process_stdout_stderr(q)
|
52
|
+
try:
|
53
|
+
train_test_model_wrapper(settings['src'], settings)
|
54
|
+
except Exception as e:
|
55
|
+
q.put(f"Error during processing: {e}")
|
56
|
+
traceback.print_exc()
|
57
|
+
finally:
|
58
|
+
stop_requested.value = 1
|