spacr 0.2.4__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.
Files changed (63) hide show
  1. spacr/__init__.py +1 -11
  2. spacr/core.py +277 -349
  3. spacr/deep_spacr.py +248 -269
  4. spacr/gui.py +58 -54
  5. spacr/gui_core.py +689 -535
  6. spacr/gui_elements.py +1002 -153
  7. spacr/gui_utils.py +452 -107
  8. spacr/io.py +158 -91
  9. spacr/measure.py +199 -151
  10. spacr/plot.py +159 -47
  11. spacr/resources/font/open_sans/OFL.txt +93 -0
  12. spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  13. spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  14. spacr/resources/font/open_sans/README.txt +100 -0
  15. spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
  16. spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
  17. spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
  18. spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
  19. spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
  20. spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
  21. spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
  22. spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
  23. spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
  24. spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
  25. spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
  26. spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
  27. spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
  28. spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
  29. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
  30. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  31. spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
  32. spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
  33. spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
  34. spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
  35. spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
  36. spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
  37. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
  38. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  39. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
  40. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  41. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  42. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  43. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
  44. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
  45. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  46. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
  47. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  48. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
  49. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  50. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  51. spacr/resources/icons/logo.pdf +2786 -6
  52. spacr/resources/icons/logo_spacr.png +0 -0
  53. spacr/resources/icons/logo_spacr_1.png +0 -0
  54. spacr/sequencing.py +477 -587
  55. spacr/settings.py +217 -144
  56. spacr/utils.py +46 -46
  57. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -35
  58. spacr-0.2.8.dist-info/RECORD +100 -0
  59. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
  60. spacr-0.2.4.dist-info/RECORD +0 -58
  61. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
  62. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
  63. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py CHANGED
@@ -1,7 +1,9 @@
1
- import os, threading, time, sqlite3
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,15 +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):
20
- default_font = (font_name, size)
21
- root.option_add("*Font", default_font)
22
- root.option_add("*TButton.Font", default_font)
23
- root.option_add("*TLabel.Font", default_font)
24
- root.option_add("*TEntry.Font", default_font)
22
+ def set_element_size():
23
+
24
+ screen_width, screen_height = pyautogui.size()
25
+ screen_area = screen_width * screen_height
26
+
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
33
+
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
25
42
 
26
- 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'):
27
44
 
28
45
  if active_color == 'teal':
29
46
  active_color = '#008080'
@@ -39,6 +56,11 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
39
56
  padding = '5 5 5 5'
40
57
  font_style = tkFont.Font(family=font_family, size=font_size)
41
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
+
42
64
  style.theme_use('clam')
43
65
 
44
66
  style.configure('TEntry', padding=padding)
@@ -47,28 +69,13 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
47
69
  style.configure('TEntry', padding=padding)
48
70
  style.configure('Spacr.TEntry', padding=padding)
49
71
  style.configure('Custom.TLabel', padding=padding)
50
- #style.configure('Spacr.TCheckbutton', padding=padding)
51
72
  style.configure('TButton', padding=padding)
52
-
53
73
  style.configure('TFrame', background=bg_color)
54
74
  style.configure('TPanedwindow', background=bg_color)
55
- style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_style)
56
-
57
-
58
- #style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background=bg_color, foreground=fg_color, font=font_style)
59
- #style.configure('Spacr.TCheckbutton', background=bg_color, foreground=fg_color, indicatoron=False, relief='flat', font="15")
60
- #style.map('Spacr.TCheckbutton', background=[('selected', bg_color), ('active', bg_color)], foreground=[('selected', fg_color), ('active', fg_color)])
61
-
62
-
63
- #style.configure('TNotebook', background=bg_color, tabmargins=[2, 5, 2, 0])
64
- #style.configure('TNotebook.Tab', background=bg_color, foreground=fg_color, padding=[5, 5], font=font_style)
65
- #style.map('TNotebook.Tab', background=[('selected', active_color), ('active', active_color)], foreground=[('selected', fg_color), ('active', fg_color)])
66
- #style.configure('TButton', background=bg_color, foreground=fg_color, padding='5 5 5 5', font=font_style)
67
- #style.map('TButton', background=[('active', active_color), ('disabled', inactive_color)])
68
- #style.configure('Vertical.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
69
- #style.configure('Horizontal.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
70
- #style.configure('Custom.TLabelFrame', font=(font_family, font_size, 'bold'), background=bg_color, foreground='white', relief='solid', borderwidth=1)
71
- #style.configure('Custom.TLabelFrame.Label', background=bg_color, foreground='white', font=(font_family, font_size, 'bold'))
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))
72
79
 
73
80
  if parent_frame:
74
81
  parent_frame.configure(bg=bg_color)
@@ -89,18 +96,193 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
89
96
  if isinstance(widget, (tk.Label, tk.Button, tk.Frame, ttk.LabelFrame, tk.Canvas)):
90
97
  widget.configure(bg=bg_color)
91
98
  if isinstance(widget, (tk.Label, tk.Button)):
92
- 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))
93
103
  if isinstance(widget, scrolledtext.ScrolledText):
94
104
  widget.configure(bg=bg_color, fg=fg_color, insertbackground=fg_color)
95
105
  if isinstance(widget, tk.OptionMenu):
96
- 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))
97
110
  menu = widget['menu']
98
- 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.")
162
+
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)
190
+
191
+ class spacrContainer(tk.Frame):
192
+ def __init__(self, parent, orient=tk.VERTICAL, bg=None, *args, **kwargs):
193
+ super().__init__(parent, *args, **kwargs)
194
+ self.orient = orient
195
+ self.bg = bg if bg else 'lightgrey'
196
+ self.sash_thickness = 10
99
197
 
100
- 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}
198
+ self.panes = []
199
+ self.sashes = []
200
+ self.bind("<Configure>", self.on_configure)
201
+
202
+ self.grid_rowconfigure(0, weight=1)
203
+ self.grid_columnconfigure(0, weight=1)
204
+
205
+ def add(self, widget, stretch='always'):
206
+ print(f"Adding widget: {widget} with stretch: {stretch}")
207
+ pane = tk.Frame(self, bg=self.bg)
208
+ pane.grid_propagate(False)
209
+ widget.grid(in_=pane, sticky="nsew") # Use grid for the widget within the pane
210
+ self.panes.append((pane, widget))
211
+
212
+ if len(self.panes) > 1:
213
+ self.create_sash()
214
+
215
+ self.reposition_panes()
216
+
217
+ def create_sash(self):
218
+ sash = tk.Frame(self, bg=self.bg, cursor='sb_v_double_arrow' if self.orient == tk.VERTICAL else 'sb_h_double_arrow', height=self.sash_thickness, width=self.sash_thickness)
219
+ sash.bind("<Enter>", self.on_enter_sash)
220
+ sash.bind("<Leave>", self.on_leave_sash)
221
+ sash.bind("<ButtonPress-1>", self.start_resize)
222
+ self.sashes.append(sash)
223
+
224
+ def reposition_panes(self):
225
+ if not self.panes:
226
+ return
227
+
228
+ total_size = self.winfo_height() if self.orient == tk.VERTICAL else self.winfo_width()
229
+ pane_size = total_size // len(self.panes)
230
+
231
+ print(f"Total size: {total_size}, Pane size: {pane_size}, Number of panes: {len(self.panes)}")
232
+
233
+ for i, (pane, widget) in enumerate(self.panes):
234
+ if self.orient == tk.VERTICAL:
235
+ pane.grid(row=i * 2, column=0, sticky="nsew", pady=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
236
+ else:
237
+ pane.grid(row=0, column=i * 2, sticky="nsew", padx=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
238
+
239
+ for i, sash in enumerate(self.sashes):
240
+ if self.orient == tk.VERTICAL:
241
+ sash.grid(row=(i * 2) + 1, column=0, sticky="ew")
242
+ else:
243
+ sash.grid(row=0, column=(i * 2) + 1, sticky="ns")
244
+
245
+ def on_configure(self, event):
246
+ print(f"Configuring container: {self}")
247
+ self.reposition_panes()
248
+
249
+ def on_enter_sash(self, event):
250
+ event.widget.config(bg='blue')
251
+
252
+ def on_leave_sash(self, event):
253
+ event.widget.config(bg=self.bg)
254
+
255
+ def start_resize(self, event):
256
+ sash = event.widget
257
+ self.start_pos = event.y_root if self.orient == tk.VERTICAL else event.x_root
258
+ self.start_size = sash.winfo_y() if self.orient == tk.VERTICAL else sash.winfo_x()
259
+ sash.bind("<B1-Motion>", self.perform_resize)
260
+
261
+ def perform_resize(self, event):
262
+ sash = event.widget
263
+ delta = (event.y_root - self.start_pos) if self.orient == tk.VERTICAL else (event.x_root - self.start_pos)
264
+ new_size = self.start_size + delta
265
+
266
+ for i, (pane, widget) in enumerate(self.panes):
267
+ if self.orient == tk.VERTICAL:
268
+ new_row = max(0, new_size // self.sash_thickness)
269
+ if pane.winfo_y() >= new_size:
270
+ pane.grid_configure(row=new_row)
271
+ elif pane.winfo_y() < new_size and i > 0:
272
+ previous_row = max(0, (new_size - pane.winfo_height()) // self.sash_thickness)
273
+ self.panes[i - 1][0].grid_configure(row=previous_row)
274
+ else:
275
+ new_col = max(0, new_size // self.sash_thickness)
276
+ if pane.winfo_x() >= new_size:
277
+ pane.grid_configure(column=new_col)
278
+ elif pane.winfo_x() < new_size and i > 0:
279
+ previous_col = max(0, (new_size - pane.winfo_width()) // self.sash_thickness)
280
+ self.panes[i - 1][0].grid_configure(column=previous_col)
281
+
282
+ self.reposition_panes()
101
283
 
102
284
  class spacrEntry(tk.Frame):
103
- def __init__(self, parent, textvariable=None, outline=False, *args, **kwargs):
285
+ def __init__(self, parent, textvariable=None, outline=False, width=None, *args, **kwargs):
104
286
  super().__init__(parent, *args, **kwargs)
105
287
 
106
288
  # Set dark style
@@ -111,18 +293,26 @@ class spacrEntry(tk.Frame):
111
293
  self.outline = outline
112
294
  self.font_family = style_out['font_family']
113
295
  self.font_size = style_out['font_size']
296
+ self.font_loader = style_out['font_loader']
114
297
 
115
298
  # Set the background color of the frame
116
299
  self.configure(bg=style_out['bg_color'])
117
300
 
118
301
  # Create a canvas for the rounded rectangle background
119
- self.canvas_width = 220 # Adjusted for padding
302
+ if width is None:
303
+ self.canvas_width = 220 # Adjusted for padding
304
+ else:
305
+ self.canvas_width = width
120
306
  self.canvas_height = 40 # Adjusted for padding
121
307
  self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=style_out['bg_color'])
122
308
  self.canvas.pack()
123
309
 
124
- self.entry = tk.Entry(self, textvariable=textvariable, bd=0, highlightthickness=0, fg=self.fg_color, font=(self.font_family, self.font_size), bg=self.bg_color)
125
- self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=190, height=20) # Centered positioning
310
+ # Create the entry widget
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)
315
+ self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=self.canvas_width - 30, height=20) # Centered positioning
126
316
 
127
317
  # Bind events to change the background color on focus
128
318
  self.entry.bind("<FocusIn>", self.on_focus_in)
@@ -133,7 +323,7 @@ class spacrEntry(tk.Frame):
133
323
  def draw_rounded_rectangle(self, color):
134
324
  radius = 15 # Increased radius for more rounded corners
135
325
  x0, y0 = 10, 5
136
- x1, y1 = 210, 35
326
+ x1, y1 = self.canvas_width - 10, self.canvas_height - 5
137
327
  self.canvas.delete("all")
138
328
  self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
139
329
  self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
@@ -201,7 +391,7 @@ class spacrCheck(tk.Frame):
201
391
  self.variable.set(not self.variable.get())
202
392
 
203
393
  class spacrCombo(tk.Frame):
204
- def __init__(self, parent, textvariable=None, values=None, *args, **kwargs):
394
+ def __init__(self, parent, textvariable=None, values=None, width=None, *args, **kwargs):
205
395
  super().__init__(parent, *args, **kwargs)
206
396
 
207
397
  # Set dark style
@@ -212,11 +402,12 @@ class spacrCombo(tk.Frame):
212
402
  self.inactive_color = style_out['inactive_color']
213
403
  self.font_family = style_out['font_family']
214
404
  self.font_size = style_out['font_size']
405
+ self.font_loader = style_out['font_loader']
215
406
 
216
407
  self.values = values or []
217
408
 
218
409
  # Create a canvas for the rounded rectangle background
219
- self.canvas_width = 220 # Adjusted for padding
410
+ self.canvas_width = width if width is not None else 220 # Adjusted for padding
220
411
  self.canvas_height = 40 # Adjusted for padding
221
412
  self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=self.bg_color)
222
413
  self.canvas.pack()
@@ -225,7 +416,10 @@ class spacrCombo(tk.Frame):
225
416
  self.selected_value = self.var.get()
226
417
 
227
418
  # Create the label to display the selected value
228
- 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))
229
423
  self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
230
424
 
231
425
  # Bind events to open the dropdown menu
@@ -239,7 +433,7 @@ class spacrCombo(tk.Frame):
239
433
  def draw_rounded_rectangle(self, color):
240
434
  radius = 15 # Increased radius for more rounded corners
241
435
  x0, y0 = 10, 5
242
- x1, y1 = 210, 35
436
+ x1, y1 = self.canvas_width - 10, self.canvas_height - 5
243
437
  self.canvas.delete("all")
244
438
  self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
245
439
  self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
@@ -266,7 +460,10 @@ class spacrCombo(tk.Frame):
266
460
 
267
461
  for index, value in enumerate(self.values):
268
462
  display_text = value if value is not None else 'None'
269
- 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')
270
467
  item.pack(fill='both')
271
468
  item.bind("<Button-1>", lambda e, v=value: self.on_select(v))
272
469
  item.bind("<Enter>", lambda e, w=item: w.config(bg=self.active_color))
@@ -292,57 +489,102 @@ class spacrCombo(tk.Frame):
292
489
  self.label.config(text=display_text)
293
490
  self.selected_value = value
294
491
 
295
- class spacrDropdownMenu(tk.OptionMenu):
296
- def __init__(self, parent, variable, options, command=None, **kwargs):
492
+ class spacrDropdownMenu(tk.Frame):
493
+
494
+ def __init__(self, parent, variable, options, command=None, font=None, size=50, **kwargs):
495
+ super().__init__(parent, **kwargs)
297
496
  self.variable = variable
298
- self.variable.set("Settings Category")
299
- super().__init__(parent, self.variable, *options, command=command, **kwargs)
300
- self.update_styles()
497
+ self.options = options
498
+ self.command = command
499
+ self.text = "Settings"
500
+ self.size = size
301
501
 
302
- # Hide the original button
303
- self.configure(highlightthickness=0, relief='flat', bg='#2B2B2B', fg='#2B2B2B')
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']
304
506
 
305
- # Create custom button
306
- self.create_custom_button()
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
307
511
 
308
- def create_custom_button(self):
309
- self.canvas_width = self.winfo_reqwidth() # Use the required width of the widget
310
- self.canvas_height = 40 # Adjust the height as needed
311
- self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg='#2B2B2B')
312
- self.canvas.pack()
313
- self.label = tk.Label(self.canvas, text="Settings Category", bg='#2B2B2B', fg='#ffffff', font=('Arial', 12))
314
- self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
315
- self.draw_rounded_rectangle('#2B2B2B')
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)
515
+
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']
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)
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)
531
+
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")
316
533
 
317
- # Bind the click event to open the dropdown menu
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)
318
540
  self.canvas.bind("<Button-1>", self.on_click)
319
- self.label.bind("<Button-1>", self.on_click)
320
541
 
321
- def draw_rounded_rectangle(self, color):
322
- radius = 15
323
- x0, y0 = 10, 5
324
- x1, y1 = self.canvas_width - 10, self.canvas_height - 5 # Adjust based on canvas size
325
- self.canvas.delete("all")
326
- self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
327
- self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
328
- self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
329
- self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
330
- self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
331
- self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
332
- self.label.config(bg=color) # Update label background to match rectangle color
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))
333
546
 
334
- def on_click(self, event):
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)
568
+
569
+ def on_enter(self, event=None):
570
+ self.canvas.itemconfig(self.button_bg, fill=self.active_color)
571
+
572
+ def on_leave(self, event=None):
573
+ self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
574
+
575
+ def on_click(self, event=None):
335
576
  self.post_menu()
336
577
 
337
578
  def post_menu(self):
338
579
  x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
339
580
  self.menu.post(x, y + height)
340
581
 
582
+ def on_select(self, option):
583
+ if self.command:
584
+ self.command(option)
585
+
341
586
  def update_styles(self, active_categories=None):
342
- style = ttk.Style()
343
- style_out = set_dark_style(style, widgets=[self])
344
- self.menu = self['menu']
345
- style_out = set_dark_style(style, widgets=[self.menu])
587
+ style_out = set_dark_style(ttk.Style(), widgets=[self.menu])
346
588
 
347
589
  if active_categories is not None:
348
590
  for idx in range(self.menu.index("end") + 1):
@@ -363,99 +605,378 @@ class spacrCheckbutton(ttk.Checkbutton):
363
605
  _ = set_dark_style(style, widgets=[self])
364
606
 
365
607
  class spacrProgressBar(ttk.Progressbar):
366
- def __init__(self, parent, *args, **kwargs):
608
+ def __init__(self, parent, label=True, *args, **kwargs):
367
609
  super().__init__(parent, *args, **kwargs)
368
610
 
369
611
  # Get the style colors
370
612
  style_out = set_dark_style(ttk.Style())
371
-
613
+
372
614
  self.fg_color = style_out['fg_color']
373
615
  self.bg_color = style_out['bg_color']
374
616
  self.active_color = style_out['active_color']
375
617
  self.inactive_color = style_out['inactive_color']
618
+ self.font_size = style_out['font_size']
619
+ self.font_loader = style_out['font_loader']
376
620
 
377
621
  # Configure the style for the progress bar
378
622
  self.style = ttk.Style()
623
+
624
+ # Remove any borders and ensure the active color fills the entire space
379
625
  self.style.configure(
380
626
  "spacr.Horizontal.TProgressbar",
381
- troughcolor=self.bg_color,
382
- background=self.active_color,
383
- thickness=20,
384
- troughrelief='flat',
385
- 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
386
636
  )
637
+
387
638
  self.configure(style="spacr.Horizontal.TProgressbar")
388
639
 
389
640
  # Set initial value to 0
390
641
  self['value'] = 0
391
642
 
392
- # Create the progress label
393
- self.progress_label = tk.Label(parent, text="Processing: 0/0", anchor='w', justify='left', bg=self.inactive_color, fg=self.fg_color)
394
- self.progress_label.grid(row=1, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
643
+ # Track whether to show the progress label
644
+ self.label = label
645
+
646
+ # Create the progress label with text wrapping
647
+ if self.label:
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()
395
659
 
396
660
  # Initialize attributes for time and operation
397
661
  self.operation_type = None
398
- self.time_image = None
399
- self.time_batch = None
400
- self.time_left = None
662
+ self.additional_info = None
663
+
664
+ def set_label_position(self):
665
+ if self.label and self.progress_label:
666
+ row_info = self.grid_info().get('row', 0)
667
+ col_info = self.grid_info().get('column', 0)
668
+ col_span = self.grid_info().get('columnspan', 1)
669
+ self.progress_label.grid(row=row_info + 1, column=col_info, columnspan=col_span, pady=5, padx=5, sticky='ew')
401
670
 
402
671
  def update_label(self):
403
- # Update the progress label with current progress and additional info
404
- label_text = f"Processing: {self['value']}/{self['maximum']}"
405
- if self.operation_type:
406
- label_text += f", {self.operation_type}"
407
- if self.time_image:
408
- label_text += f", Time/image: {self.time_image:.3f} sec"
409
- if self.time_batch:
410
- label_text += f", Time/batch: {self.time_batch:.3f} sec"
411
- if self.time_left:
412
- label_text += f", Time_left: {self.time_left:.3f} min"
413
- self.progress_label.config(text=label_text)
672
+ if self.label and self.progress_label:
673
+ # Start with the base progress information
674
+ label_text = f"Processing: {self['value']}/{self['maximum']}"
675
+
676
+ # Include the operation type if it exists
677
+ if self.operation_type:
678
+ label_text += f", {self.operation_type}"
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
690
+ self.progress_label.config(text=label_text)
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
+
856
+ def spacrScrollbarStyle(style, inactive_color, active_color):
857
+ # Check if custom elements already exist to avoid duplication
858
+ if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
859
+ style.element_create('custom.Vertical.Scrollbar.trough', 'from', 'clam')
860
+ if not style.element_names().count('custom.Vertical.Scrollbar.thumb'):
861
+ style.element_create('custom.Vertical.Scrollbar.thumb', 'from', 'clam')
862
+
863
+ style.layout('Custom.Vertical.TScrollbar',
864
+ [('Vertical.Scrollbar.trough', {'children': [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})], 'sticky': 'ns'})])
865
+
866
+ style.configure('Custom.Vertical.TScrollbar',
867
+ background=inactive_color,
868
+ troughcolor=inactive_color,
869
+ bordercolor=inactive_color,
870
+ lightcolor=inactive_color,
871
+ darkcolor=inactive_color)
872
+
873
+ style.map('Custom.Vertical.TScrollbar',
874
+ background=[('!active', inactive_color), ('active', active_color)],
875
+ troughcolor=[('!active', inactive_color), ('active', inactive_color)],
876
+ bordercolor=[('!active', inactive_color), ('active', inactive_color)],
877
+ lightcolor=[('!active', inactive_color), ('active', active_color)],
878
+ darkcolor=[('!active', inactive_color), ('active', active_color)])
414
879
 
415
880
  class spacrFrame(ttk.Frame):
416
- def __init__(self, container, width=None, *args, bg='black', **kwargs):
881
+ def __init__(self, container, width=None, *args, bg='black', radius=20, scrollbar=True, textbox=False, **kwargs):
417
882
  super().__init__(container, *args, **kwargs)
418
883
  self.configure(style='TFrame')
419
884
  if width is None:
420
885
  screen_width = self.winfo_screenwidth()
421
886
  width = screen_width // 4
422
- canvas = tk.Canvas(self, bg=bg, width=width)
423
- scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
887
+
888
+ # Create the canvas
889
+ canvas = tk.Canvas(self, bg=bg, width=width, highlightthickness=0)
890
+ self.rounded_rectangle(canvas, 0, 0, width, self.winfo_screenheight(), radius, fill=bg)
891
+
892
+ # Define scrollbar styles
893
+ style_out = set_dark_style(ttk.Style())
894
+ self.inactive_color = style_out['inactive_color']
895
+ self.active_color = style_out['active_color']
896
+ self.fg_color = style_out['fg_color'] # Foreground color for text
897
+
898
+ # Set custom scrollbar style
899
+ style = ttk.Style()
900
+ spacrScrollbarStyle(style, self.inactive_color, self.active_color)
901
+
902
+ # Create scrollbar with custom style if scrollbar option is True
903
+ if scrollbar:
904
+ scrollbar_widget = ttk.Scrollbar(self, orient="vertical", command=canvas.yview, style='Custom.Vertical.TScrollbar')
905
+
906
+ if textbox:
907
+ self.scrollable_frame = tk.Text(canvas, bg=bg, fg=self.fg_color, wrap=tk.WORD)
908
+ else:
909
+ self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
424
910
 
425
- self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
426
911
  self.scrollable_frame.bind(
427
912
  "<Configure>",
428
913
  lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
429
914
  )
430
915
  canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
431
- canvas.configure(yscrollcommand=scrollbar.set)
916
+ if scrollbar:
917
+ canvas.configure(yscrollcommand=scrollbar_widget.set)
432
918
 
433
919
  canvas.grid(row=0, column=0, sticky="nsew")
434
- scrollbar.grid(row=0, column=1, sticky="ns")
920
+ if scrollbar:
921
+ scrollbar_widget.grid(row=0, column=1, sticky="ns")
435
922
 
436
923
  self.grid_rowconfigure(0, weight=1)
437
924
  self.grid_columnconfigure(0, weight=1)
438
- self.grid_columnconfigure(1, weight=0)
925
+ if scrollbar:
926
+ self.grid_columnconfigure(1, weight=0)
439
927
 
440
- style = ttk.Style()
441
- _ = set_dark_style(style, containers=[self], widgets=[canvas, scrollbar, self.scrollable_frame])
928
+ _ = set_dark_style(style, containers=[self], widgets=[canvas, self.scrollable_frame])
929
+ if scrollbar:
930
+ _ = set_dark_style(style, widgets=[scrollbar_widget])
931
+
932
+ def rounded_rectangle(self, canvas, x1, y1, x2, y2, radius=20, **kwargs):
933
+ points = [
934
+ x1 + radius, y1,
935
+ x2 - radius, y1,
936
+ x2 - radius, y1,
937
+ x2, y1,
938
+ x2, y1 + radius,
939
+ x2, y2 - radius,
940
+ x2, y2 - radius,
941
+ x2, y2,
942
+ x2 - radius, y2,
943
+ x1 + radius, y2,
944
+ x1 + radius, y2,
945
+ x1, y2,
946
+ x1, y2 - radius,
947
+ x1, y2 - radius,
948
+ x1, y1 + radius,
949
+ x1, y1 + radius,
950
+ x1, y1
951
+ ]
952
+ return canvas.create_polygon(points, **kwargs, smooth=True)
442
953
 
443
954
  class spacrLabel(tk.Frame):
444
- def __init__(self, parent, text="", font=None, style=None, align="right", **kwargs):
955
+ def __init__(self, parent, text="", font=None, style=None, align="right", height=None, **kwargs):
445
956
  valid_kwargs = {k: v for k, v in kwargs.items() if k not in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
446
957
  super().__init__(parent, **valid_kwargs)
447
958
 
448
959
  self.text = text
449
960
  self.align = align
450
- screen_height = self.winfo_screenheight()
451
- label_height = screen_height // 50
452
- label_width = label_height * 10
453
- style_out = set_dark_style(ttk.Style())
454
961
 
455
- self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=style_out['bg_color'])
456
- self.canvas.grid(row=0, column=0, sticky="ew")
962
+ if height is None:
963
+ screen_height = self.winfo_screenheight()
964
+ label_height = screen_height // 50
965
+ label_width = label_height * 10
966
+ else:
967
+ label_height = height
968
+ label_width = label_height * 10
969
+
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']
457
975
 
458
- self.font_style = font if font else tkFont.Font(family=style_out['font_family'], size=style_out['font_size'], weight=tkFont.NORMAL)
976
+ self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=self.style_out['bg_color'])
977
+ self.canvas.grid(row=0, column=0, sticky="ew")
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)
459
980
  self.style = style
460
981
 
461
982
  if self.align == "center":
@@ -467,13 +988,21 @@ class spacrLabel(tk.Frame):
467
988
 
468
989
  if self.style:
469
990
  ttk_style = ttk.Style()
470
- 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'])
471
995
  self.label_text = ttk.Label(self.canvas, text=self.text, style=self.style, anchor=text_anchor)
472
996
  self.label_text.pack(fill=tk.BOTH, expand=True)
473
997
  else:
474
- self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
475
- label_height // 2, text=self.text, fill=style_out['fg_color'],
476
- 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)
477
1006
 
478
1007
  _ = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
479
1008
 
@@ -484,7 +1013,7 @@ class spacrLabel(tk.Frame):
484
1013
  self.canvas.itemconfig(self.label_text, text=text)
485
1014
 
486
1015
  class spacrButton(tk.Frame):
487
- def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, *args, **kwargs):
1016
+ def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, animation=True, *args, **kwargs):
488
1017
  super().__init__(parent, *args, **kwargs)
489
1018
 
490
1019
  self.text = text.capitalize() # Capitalize only the first letter of the text
@@ -493,8 +1022,11 @@ class spacrButton(tk.Frame):
493
1022
  self.size = size
494
1023
  self.show_text = show_text
495
1024
  self.outline = outline
1025
+ self.animation = animation # Add animation attribute
496
1026
 
497
1027
  style_out = set_dark_style(ttk.Style())
1028
+ self.font_size = style_out['font_size']
1029
+ self.font_loader = style_out['font_loader']
498
1030
 
499
1031
  if self.show_text:
500
1032
  self.button_width = int(size * 3)
@@ -516,7 +1048,10 @@ class spacrButton(tk.Frame):
516
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)
517
1049
 
518
1050
  self.load_icon()
519
- 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)
520
1055
 
521
1056
  if self.show_text:
522
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
@@ -559,13 +1094,13 @@ class spacrButton(tk.Frame):
559
1094
  def on_enter(self, event=None):
560
1095
  self.canvas.itemconfig(self.button_bg, fill=self.active_color)
561
1096
  self.update_description(event)
562
- if not self.is_zoomed_in:
1097
+ if self.animation and not self.is_zoomed_in:
563
1098
  self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
564
1099
 
565
1100
  def on_leave(self, event=None):
566
1101
  self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
567
1102
  self.clear_description(event)
568
- if self.is_zoomed_in:
1103
+ if self.animation and self.is_zoomed_in:
569
1104
  self.animate_zoom(0.65) # Reset the icon size to 65% of button size
570
1105
 
571
1106
  def on_click(self, event=None):
@@ -1614,6 +2149,19 @@ class AnnotateApp:
1614
2149
  self.threshold = threshold
1615
2150
 
1616
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
+
1617
2165
  self.root.configure(bg=style_out['inactive_color'])
1618
2166
 
1619
2167
  self.filtered_paths_annotations = []
@@ -1627,20 +2175,20 @@ class AnnotateApp:
1627
2175
  self.root.update_idletasks()
1628
2176
 
1629
2177
  # Create the status label
1630
- 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'))
1631
2179
  self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
1632
2180
 
1633
2181
  # Place the buttons at the bottom right
1634
2182
  self.button_frame = Frame(root, bg=self.root.cget('bg'))
1635
2183
  self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
1636
2184
 
1637
- 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)
1638
2186
  self.next_button.pack(side="right", padx=5)
1639
2187
 
1640
- 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)
1641
2189
  self.previous_button.pack(side="right", padx=5)
1642
2190
 
1643
- 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)
1644
2192
  self.exit_button.pack(side="right", padx=5)
1645
2193
 
1646
2194
  # Calculate grid rows and columns based on the root window size and image size
@@ -1704,6 +2252,7 @@ class AnnotateApp:
1704
2252
  print(f"median threshold measurement: {np.median(df[self.measurement])}")
1705
2253
  df = df[df[f'threshold_measurement_{idx}'] > thd]
1706
2254
  after = len(df)
2255
+
1707
2256
  elif isinstance(self.measurement, list):
1708
2257
  df['threshold_measurement'] = df[self.measurement[0]]/df[self.measurement[1]]
1709
2258
  print(f"mean threshold measurement: {np.mean(df['threshold_measurement'])}")
@@ -1712,6 +2261,7 @@ class AnnotateApp:
1712
2261
  after = len(df)
1713
2262
  self.measurement = 'threshold_measurement'
1714
2263
  print(f'Removed: {before-after} rows, retained {after}')
2264
+
1715
2265
  else:
1716
2266
  print(f"mean threshold measurement: {np.mean(df[self.measurement])}")
1717
2267
  print(f"median threshold measurement: {np.median(df[self.measurement])}")
@@ -1788,7 +2338,7 @@ class AnnotateApp:
1788
2338
 
1789
2339
  for i, (img, annotation) in enumerate(loaded_images):
1790
2340
  if annotation:
1791
- border_color = 'teal' if annotation == 1 else 'red'
2341
+ border_color = self.active_color if annotation == 1 else 'red'
1792
2342
  img = self.add_colored_border(img, border_width=5, border_color=border_color)
1793
2343
 
1794
2344
  photo = ImageTk.PhotoImage(img)
@@ -1834,7 +2384,7 @@ class AnnotateApp:
1834
2384
  left_border = Image.new('RGB', (border_width, img.height), color=border_color)
1835
2385
  right_border = Image.new('RGB', (border_width, img.height), color=border_color)
1836
2386
 
1837
- 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)
1838
2388
  bordered_img.paste(top_border, (border_width, 0))
1839
2389
  bordered_img.paste(bottom_border, (border_width, img.height + border_width))
1840
2390
  bordered_img.paste(left_border, (0, border_width))
@@ -1874,7 +2424,7 @@ class AnnotateApp:
1874
2424
  print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
1875
2425
 
1876
2426
  img_ = img.crop((5, 5, img.width-5, img.height-5))
1877
- 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)
1878
2428
  img_ = ImageOps.expand(img_, border=5, fill=border_fill) if border_fill else img_
1879
2429
 
1880
2430
  photo = ImageTk.PhotoImage(img_)
@@ -1957,25 +2507,22 @@ class AnnotateApp:
1957
2507
  def create_menu_bar(root):
1958
2508
  from .gui import initiate_root
1959
2509
  gui_apps = {
1960
- "Mask": (lambda frame: initiate_root(frame, settings_type='mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
1961
- "Measure": (lambda frame: initiate_root(frame, settings_type='measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
1962
- "Annotate": (lambda frame: initiate_root(frame, settings_type='annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
1963
- "Make Masks": (lambda frame: initiate_root(frame, settings_type='make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
1964
- "Classify": (lambda frame: initiate_root(frame, settings_type='classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
1965
- "Sequencing": (lambda frame: initiate_root(frame, settings_type='sequencing'), "Analyze sequencing data."),
1966
- "Umap": (lambda frame: initiate_root(frame, settings_type='umap'), "Generate UMAP embeddings with datapoints represented as images."),
1967
- "Train Cellpose": (lambda frame: initiate_root(frame, settings_type='train_cellpose'), "Train custom Cellpose models."),
1968
- "ML Analyze": (lambda frame: initiate_root(frame, settings_type='ml_analyze'), "Machine learning analysis of data."),
1969
- "Cellpose Masks": (lambda frame: initiate_root(frame, settings_type='cellpose_masks'), "Generate Cellpose masks."),
1970
- "Cellpose All": (lambda frame: initiate_root(frame, settings_type='cellpose_all'), "Run Cellpose on all images."),
1971
- "Map Barcodes": (lambda frame: initiate_root(frame, settings_type='map_barcodes'), "Map barcodes to data."),
1972
- "Regression": (lambda frame: initiate_root(frame, settings_type='regression'), "Perform regression analysis."),
1973
- "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')
1974
2524
  }
1975
2525
 
1976
- def load_app_wrapper(app_name, app_func):
1977
- root.load_app(app_name, app_func)
1978
-
1979
2526
  # Create the menu bar
1980
2527
  menu_bar = tk.Menu(root, bg="#008080", fg="white")
1981
2528
 
@@ -1984,13 +2531,315 @@ def create_menu_bar(root):
1984
2531
  menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
1985
2532
 
1986
2533
  # Add options to the "SpaCr Applications" menu
1987
- for app_name, app_data in gui_apps.items():
1988
- app_func, app_desc = app_data
1989
- 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
+ )
1990
2539
 
1991
2540
  # Add a separator and an exit option
1992
2541
  app_menu.add_separator()
2542
+ app_menu.add_command(label="Help", command=lambda: webbrowser.open("https://spacr.readthedocs.io/en/latest/?badge=latest"))
1993
2543
  app_menu.add_command(label="Exit", command=root.quit)
1994
2544
 
1995
2545
  # Configure the menu for the root window
1996
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)