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/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
- from tkinter.font import nametofont
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
- from .logger import log_function_call
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='#333333', **kwargs):
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') # Assuming you have a dark mode style for labels too
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') # Assuming you have a dark mode style for entries
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
- # Use the custom style for Checkbutton
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') # Assuming you have a dark mode style for comboboxes
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
- return var
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
- vars_dict[key] = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
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 = '#333333'
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=("Arial", 12)
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='#333333')
498
- style.configure('TLabel', background='#333333', foreground='white')
499
- style.configure('TEntry', background='#333333', foreground='white')
500
- style.configure('TCheckbutton', background='#333333', foreground='white')
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, annotation_settings={}, advanced_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