spacr 0.0.1__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_mask_app.py ADDED
@@ -0,0 +1,247 @@
1
+ import spacr, sys, queue, ctypes, csv, matplotlib
2
+ import tkinter as tk
3
+ from tkinter import ttk, scrolledtext
4
+ from ttkthemes import ThemedTk
5
+ from matplotlib.figure import Figure
6
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
7
+ import matplotlib.pyplot as plt
8
+ from matplotlib.figure import Figure
9
+ from threading import Thread
10
+ matplotlib.use('Agg')
11
+ from threading import Thread
12
+ from tkinter import filedialog
13
+
14
+ try:
15
+ ctypes.windll.shcore.SetProcessDpiAwareness(True)
16
+ except AttributeError:
17
+ pass
18
+
19
+ from .logger import log_function_call
20
+ from .gui_utils import ScrollableFrame, StdoutRedirector, create_dark_mode, set_dark_style, set_default_font, mask_variables, generate_fields, check_mask_gui_settings, add_mask_gui_defaults
21
+ from .gui_utils import safe_literal_eval
22
+
23
+ thread_control = {"run_thread": None, "stop_requested": False}
24
+
25
+ class ScrollableFrame(ttk.Frame):
26
+ def __init__(self, container, *args, bg='#333333', **kwargs):
27
+ super().__init__(container, *args, **kwargs)
28
+ self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
29
+
30
+ canvas = tk.Canvas(self, bg=bg) # Set canvas background to match dark mode
31
+ scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
32
+
33
+ self.scrollable_frame = ttk.Frame(canvas, style='TFrame') # Ensure it uses the styled frame
34
+ self.scrollable_frame.bind(
35
+ "<Configure>",
36
+ lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
37
+ )
38
+
39
+ canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
40
+ canvas.configure(yscrollcommand=scrollbar.set)
41
+
42
+ canvas.pack(side="left", fill="both", expand=True)
43
+ scrollbar.pack(side="right", fill="y")
44
+
45
+ def clear_canvas():
46
+ global canvas
47
+ # Clear each plot (axes) in the figure
48
+ for ax in canvas.figure.get_axes():
49
+ ax.clear() # Clears the axes, but keeps them visible for new plots
50
+
51
+ # Redraw the now empty canvas without changing its size
52
+ canvas.draw_idle() # Using draw_idle for efficiency in redrawing
53
+
54
+ def initiate_abort():
55
+ global thread_control, q
56
+ thread_control["stop_requested"] = True
57
+ if thread_control["run_thread"] is not None:
58
+ thread_control["run_thread"].join(timeout=1) # Timeout after 1 second
59
+ if thread_control["run_thread"].is_alive():
60
+ q.put("Thread didn't terminate within timeout.")
61
+ thread_control["run_thread"] = None
62
+
63
+ def preprocess_generate_masks_wrapper(*args, **kwargs):
64
+ global fig_queue
65
+ def my_show():
66
+ fig = plt.gcf()
67
+ fig_queue.put(fig) # Put the figure into the queue
68
+ plt.close(fig) # Close the figure to prevent it from being shown by plt.show()
69
+
70
+ original_show = plt.show
71
+ plt.show = my_show
72
+
73
+ try:
74
+ spacr.core.preprocess_generate_masks(*args, **kwargs)
75
+ except Exception as e:
76
+ pass
77
+ finally:
78
+ plt.show = original_show
79
+
80
+ def run_mask_gui(q, fig_queue):
81
+ global vars_dict, thread_control
82
+ try:
83
+ while not thread_control["stop_requested"]:
84
+ settings = check_mask_gui_settings(vars_dict)
85
+ settings = add_mask_gui_defaults(settings)
86
+ preprocess_generate_masks_wrapper(settings['src'], settings=settings, advanced_settings={})
87
+ thread_control["stop_requested"] = True
88
+ except Exception as e:
89
+ pass
90
+ #q.put(f"Error during processing: {e}")
91
+ finally:
92
+ # Ensure the thread is marked as not running anymore
93
+ thread_control["run_thread"] = None
94
+ # Reset the stop_requested flag for future operations
95
+ thread_control["stop_requested"] = False
96
+
97
+ def start_thread(q, fig_queue):
98
+ global thread_control
99
+ thread_control["stop_requested"] = False # Reset the stop signal
100
+ thread_control["run_thread"] = Thread(target=run_mask_gui, args=(q, fig_queue))
101
+ thread_control["run_thread"].start()
102
+
103
+ def import_settings(scrollable_frame):
104
+ global vars_dict, original_variables_structure
105
+
106
+ csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
107
+
108
+ if not csv_file_path:
109
+ return
110
+
111
+ imported_variables = {}
112
+
113
+ with open(csv_file_path, newline='') as csvfile:
114
+ reader = csv.DictReader(csvfile)
115
+ for row in reader:
116
+ key = row['Key']
117
+ value = row['Value']
118
+ # Evaluate the value safely using safe_literal_eval
119
+ imported_variables[key] = safe_literal_eval(value)
120
+
121
+ # Track changed variables and apply the imported ones, printing changes as we go
122
+ for key, var in vars_dict.items():
123
+ if key in imported_variables and var.get() != imported_variables[key]:
124
+ print(f"Updating '{key}' from '{var.get()}' to '{imported_variables[key]}'")
125
+ var.set(imported_variables[key])
126
+
127
+ @log_function_call
128
+ def initiate_mask_root(width, height):
129
+ global root, vars_dict, q, canvas, fig_queue, canvas_widget, thread_control
130
+
131
+ theme = 'breeze'
132
+
133
+ if theme in ['clam']:
134
+ root = tk.Tk()
135
+ style = ttk.Style(root)
136
+ style.theme_use(theme) #plastik, clearlooks, elegance, default was clam #alt, breeze, arc
137
+ set_dark_style(style)
138
+ elif theme in ['breeze']:
139
+ root = ThemedTk(theme="breeze")
140
+ style = ttk.Style(root)
141
+ set_dark_style(style)
142
+
143
+ set_default_font(root, font_name="Arial", size=10)
144
+ #root.state('zoomed') # For Windows to maximize the window
145
+ root.attributes('-fullscreen', True)
146
+ root.geometry(f"{width}x{height}")
147
+ root.title("SpaCer: generate masks")
148
+ fig_queue = queue.Queue()
149
+
150
+ def _process_fig_queue():
151
+ global canvas
152
+ try:
153
+ while not fig_queue.empty():
154
+ clear_canvas()
155
+ fig = fig_queue.get_nowait()
156
+ #set_fig_text_properties(fig, font_size=8)
157
+ for ax in fig.get_axes():
158
+ ax.set_xticks([]) # Remove x-axis ticks
159
+ ax.set_yticks([]) # Remove y-axis ticks
160
+ ax.xaxis.set_visible(False) # Hide the x-axis
161
+ ax.yaxis.set_visible(False) # Hide the y-axis
162
+ #ax.title.set_fontsize(14)
163
+ #disable_interactivity(fig)
164
+ fig.tight_layout()
165
+ fig.set_facecolor('#333333')
166
+ canvas.figure = fig
167
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
168
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
169
+ canvas.draw_idle()
170
+ except queue.Empty:
171
+ pass
172
+ except Exception as e:
173
+ pass
174
+ finally:
175
+ canvas_widget.after(100, _process_fig_queue)
176
+
177
+ # Process queue for console output
178
+ def _process_console_queue():
179
+ while not q.empty():
180
+ message = q.get_nowait()
181
+ console_output.insert(tk.END, message)
182
+ console_output.see(tk.END)
183
+ console_output.after(100, _process_console_queue)
184
+
185
+ # Vertical container for settings and console
186
+ vertical_container = tk.PanedWindow(root, orient=tk.HORIZONTAL) #VERTICAL
187
+ vertical_container.pack(fill=tk.BOTH, expand=True)
188
+
189
+ # Scrollable Frame for user settings
190
+ scrollable_frame = ScrollableFrame(vertical_container, bg='#333333')
191
+ vertical_container.add(scrollable_frame, stretch="always")
192
+
193
+ # Setup for user input fields (variables)
194
+ variables = mask_variables()
195
+ vars_dict = generate_fields(variables, scrollable_frame)
196
+
197
+ # Horizontal container for Matplotlib figure and the vertical pane (for settings and console)
198
+ horizontal_container = tk.PanedWindow(vertical_container, orient=tk.VERTICAL) #HORIZONTAL
199
+ vertical_container.add(horizontal_container, stretch="always")
200
+
201
+ # Matplotlib figure setup
202
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='#333333')
203
+ plot = figure.add_subplot(111)
204
+ plot.plot([], []) # This creates an empty plot.
205
+ plot.axis('off')
206
+
207
+ # Embedding the Matplotlib figure in the Tkinter window
208
+ canvas = FigureCanvasTkAgg(figure, master=horizontal_container)
209
+ canvas.get_tk_widget().configure(cursor='arrow', background='#333333', highlightthickness=0)
210
+ #canvas.get_tk_widget().configure(cursor='arrow')
211
+ canvas_widget = canvas.get_tk_widget()
212
+ horizontal_container.add(canvas_widget, stretch="always")
213
+ canvas.draw()
214
+
215
+ # Console output setup below the settings
216
+ console_output = scrolledtext.ScrolledText(vertical_container, height=10)
217
+ vertical_container.add(console_output, stretch="always")
218
+
219
+ # Queue and redirection setup for updating console output safely
220
+ q = queue.Queue()
221
+ sys.stdout = StdoutRedirector(console_output)
222
+ sys.stderr = StdoutRedirector(console_output)
223
+
224
+ # This is your GUI setup where you create the Run button
225
+ run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run",command=lambda: start_thread(q, fig_queue))
226
+ run_button.grid(row=40, column=0, pady=10)
227
+
228
+ abort_button = ttk.Button(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
229
+ abort_button.grid(row=40, column=1, pady=10)
230
+
231
+ # Create the Import Settings button
232
+ import_btn = tk.Button(root, text="Import Settings", command=lambda: import_settings(scrollable_frame))
233
+ import_btn.pack(pady=20)
234
+
235
+ _process_console_queue()
236
+ _process_fig_queue()
237
+ create_dark_mode(root, style, console_output)
238
+
239
+ return root, vars_dict
240
+
241
+ def gui_mask():
242
+ global vars_dict, root
243
+ root, vars_dict = initiate_mask_root(1000, 1500)
244
+ root.mainloop()
245
+
246
+ if __name__ == "__main__":
247
+ gui_mask()
@@ -0,0 +1,214 @@
1
+ import sys, traceback, matplotlib, ctypes, csv
2
+ import tkinter as tk
3
+ from tkinter import ttk, scrolledtext
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
6
+ import matplotlib.pyplot as plt
7
+ matplotlib.use('Agg') # Use the non-GUI Agg backend
8
+ from multiprocessing import Process, Queue, Value
9
+ from ttkthemes import ThemedTk
10
+ from tkinter import filedialog
11
+
12
+ try:
13
+ ctypes.windll.shcore.SetProcessDpiAwareness(True)
14
+ except AttributeError:
15
+ pass
16
+
17
+ from .logger import log_function_call
18
+ from .gui_utils import ScrollableFrame, StdoutRedirector, set_dark_style, set_default_font, measure_variables, generate_fields, create_dark_mode, check_measure_gui_settings, add_measure_gui_defaults, main_thread_update_function
19
+ from .gui_utils import process_stdout_stderr, measure_crop_wrapper, clear_canvas, safe_literal_eval
20
+
21
+ thread_control = {"run_thread": None, "stop_requested": False}
22
+
23
+
24
+ @log_function_call
25
+ def run_measure_gui(q, fig_queue, stop_requested):
26
+ global vars_dict
27
+ process_stdout_stderr(q)
28
+ try:
29
+ settings = check_measure_gui_settings(vars_dict)
30
+ settings = add_measure_gui_defaults(settings)
31
+ #for key in settings:
32
+ # value = settings[key]
33
+ # print(key, value, type(value))
34
+ measure_crop_wrapper(settings=settings, q=q, fig_queue=fig_queue)
35
+ except Exception as e:
36
+ q.put(f"Error during processing: {e}")
37
+ traceback.print_exc()
38
+ finally:
39
+ stop_requested.value = 1
40
+
41
+ @log_function_call
42
+ def start_process(q, fig_queue):
43
+ global thread_control
44
+ if thread_control.get("run_thread") is not None:
45
+ initiate_abort()
46
+
47
+ stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
48
+ thread_control["stop_requested"] = stop_requested
49
+ thread_control["run_thread"] = Process(target=run_measure_gui, args=(q, fig_queue, stop_requested))
50
+ thread_control["run_thread"].start()
51
+
52
+ @log_function_call
53
+ def initiate_abort():
54
+ global thread_control
55
+ if thread_control.get("stop_requested") is not None:
56
+ thread_control["stop_requested"].value = 1
57
+
58
+ if thread_control.get("run_thread") is not None:
59
+ thread_control["run_thread"].join(timeout=5)
60
+ if thread_control["run_thread"].is_alive():
61
+ thread_control["run_thread"].terminate()
62
+ thread_control["run_thread"] = None
63
+
64
+ def import_settings(scrollable_frame):
65
+ global vars_dict, original_variables_structure
66
+
67
+ csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
68
+
69
+ if not csv_file_path:
70
+ return
71
+
72
+ imported_variables = {}
73
+
74
+ with open(csv_file_path, newline='') as csvfile:
75
+ reader = csv.DictReader(csvfile)
76
+ for row in reader:
77
+ key = row['Key']
78
+ value = row['Value']
79
+ # Evaluate the value safely using safe_literal_eval
80
+ imported_variables[key] = safe_literal_eval(value)
81
+
82
+ # Track changed variables and apply the imported ones, printing changes as we go
83
+ for key, var in vars_dict.items():
84
+ if key in imported_variables and var.get() != imported_variables[key]:
85
+ print(f"Updating '{key}' from '{var.get()}' to '{imported_variables[key]}'")
86
+ var.set(imported_variables[key])
87
+
88
+ @log_function_call
89
+ def initiate_measure_root(width, height):
90
+ global root, vars_dict, q, canvas, fig_queue, canvas_widget, thread_control, variables
91
+
92
+ theme = 'breeze'
93
+
94
+ if theme in ['clam']:
95
+ root = tk.Tk()
96
+ style = ttk.Style(root)
97
+ style.theme_use(theme) #plastik, clearlooks, elegance, default was clam #alt, breeze, arc
98
+ set_dark_style(style)
99
+
100
+ elif theme in ['breeze']:
101
+ root = ThemedTk(theme="breeze")
102
+ style = ttk.Style(root)
103
+ set_dark_style(style)
104
+
105
+ set_default_font(root, font_name="Arial", size=10)
106
+ #root.state('zoomed') # For Windows to maximize the window
107
+ root.attributes('-fullscreen', True)
108
+ root.geometry(f"{width}x{height}")
109
+ root.configure(bg='#333333')
110
+ root.title("SpaCer: generate masks")
111
+ fig_queue = Queue()
112
+
113
+ def _process_fig_queue():
114
+ global canvas
115
+ try:
116
+ while not fig_queue.empty():
117
+ clear_canvas(canvas)
118
+ fig = fig_queue.get_nowait()
119
+ #set_fig_text_properties(fig, font_size=8)
120
+ for ax in fig.get_axes():
121
+ ax.set_xticks([]) # Remove x-axis ticks
122
+ ax.set_yticks([]) # Remove y-axis ticks
123
+ ax.xaxis.set_visible(False) # Hide the x-axis
124
+ ax.yaxis.set_visible(False) # Hide the y-axis
125
+ #ax.title.set_fontsize(14)
126
+ #disable_interactivity(fig)
127
+ fig.tight_layout()
128
+ fig.set_facecolor('#333333')
129
+ canvas.figure = fig
130
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
131
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
132
+ canvas.draw_idle()
133
+ except Exception as e:
134
+ traceback.print_exc()
135
+ #pass
136
+ finally:
137
+ canvas_widget.after(100, _process_fig_queue)
138
+
139
+ # Process queue for console output
140
+ def _process_console_queue():
141
+ while not q.empty():
142
+ message = q.get_nowait()
143
+ console_output.insert(tk.END, message)
144
+ console_output.see(tk.END)
145
+ console_output.after(100, _process_console_queue)
146
+
147
+ # Vertical container for settings and console
148
+ vertical_container = tk.PanedWindow(root, orient=tk.HORIZONTAL) #VERTICAL
149
+ vertical_container.pack(fill=tk.BOTH, expand=True)
150
+
151
+ # Scrollable Frame for user settings
152
+ scrollable_frame = ScrollableFrame(vertical_container)
153
+ vertical_container.add(scrollable_frame, stretch="always")
154
+
155
+ # Setup for user input fields (variables)
156
+ variables = measure_variables()
157
+ vars_dict = generate_fields(variables, scrollable_frame)
158
+
159
+ # Horizontal container for Matplotlib figure and the vertical pane (for settings and console)
160
+ horizontal_container = tk.PanedWindow(vertical_container, orient=tk.VERTICAL) #HORIZONTAL
161
+ vertical_container.add(horizontal_container, stretch="always")
162
+
163
+ # Matplotlib figure setup
164
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='#333333')
165
+ plot = figure.add_subplot(111)
166
+ plot.plot([], []) # This creates an empty plot.
167
+ plot.axis('off')
168
+
169
+ # Embedding the Matplotlib figure in the Tkinter window
170
+ canvas = FigureCanvasTkAgg(figure, master=horizontal_container)
171
+ canvas.get_tk_widget().configure(cursor='arrow', background='#333333', highlightthickness=0)
172
+ #canvas.get_tk_widget().configure(cursor='arrow')
173
+ canvas_widget = canvas.get_tk_widget()
174
+ horizontal_container.add(canvas_widget, stretch="always")
175
+ canvas.draw()
176
+
177
+ # Console output setup below the settings
178
+ console_output = scrolledtext.ScrolledText(vertical_container, height=10)
179
+ vertical_container.add(console_output, stretch="always")
180
+
181
+ # Queue and redirection setup for updating console output safely
182
+ q = Queue()
183
+ sys.stdout = StdoutRedirector(console_output)
184
+ sys.stderr = StdoutRedirector(console_output)
185
+
186
+ # This is your GUI setup where you create the Run button
187
+ run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run",command=lambda: start_process(q, fig_queue))
188
+ run_button.grid(row=40, column=0, pady=10)
189
+
190
+ abort_button = ttk.Button(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
191
+ abort_button.grid(row=40, column=1, pady=10)
192
+
193
+ progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Progress: 0%", background="#333333", foreground="white")
194
+ progress_label.grid(row=41, column=0, columnspan=2, sticky="ew", pady=(5, 0))
195
+
196
+ # Create the Import Settings button
197
+ import_btn = tk.Button(root, text="Import Settings", command=lambda: import_settings(scrollable_frame))
198
+ import_btn.pack(pady=20)
199
+
200
+ _process_console_queue()
201
+ _process_fig_queue()
202
+ create_dark_mode(root, style, console_output)
203
+
204
+ root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
205
+
206
+ return root, vars_dict
207
+
208
+ def gui_measure():
209
+ global vars_dict, root
210
+ root, vars_dict = initiate_measure_root(1000, 1500)
211
+ root.mainloop()
212
+
213
+ if __name__ == "__main__":
214
+ gui_measure()