spacr 0.2.5__py3-none-any.whl → 0.2.8__py3-none-any.whl

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