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/__init__.py +37 -0
- spacr/__main__.py +15 -0
- spacr/annotate_app.py +495 -0
- spacr/cli.py +203 -0
- spacr/core.py +2250 -0
- spacr/gui_mask_app.py +247 -0
- spacr/gui_measure_app.py +214 -0
- spacr/gui_utils.py +488 -0
- spacr/io.py +2271 -0
- spacr/logger.py +20 -0
- spacr/mask_app.py +818 -0
- spacr/measure.py +1014 -0
- spacr/old_code.py +104 -0
- spacr/plot.py +1273 -0
- spacr/sim.py +1187 -0
- spacr/timelapse.py +576 -0
- spacr/train.py +494 -0
- spacr/umap.py +689 -0
- spacr/utils.py +2726 -0
- spacr/version.py +19 -0
- spacr-0.0.1.dist-info/LICENSE +21 -0
- spacr-0.0.1.dist-info/METADATA +64 -0
- spacr-0.0.1.dist-info/RECORD +26 -0
- spacr-0.0.1.dist-info/WHEEL +5 -0
- spacr-0.0.1.dist-info/entry_points.txt +5 -0
- spacr-0.0.1.dist-info/top_level.txt +1 -0
spacr/gui_utils.py
ADDED
@@ -0,0 +1,488 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
from tkinter import ttk, messagebox
|
3
|
+
from tkinter.font import nametofont
|
4
|
+
import matplotlib
|
5
|
+
#matplotlib.use('TkAgg')
|
6
|
+
matplotlib.use('Agg')
|
7
|
+
import ctypes
|
8
|
+
import ast
|
9
|
+
import matplotlib.pyplot as plt
|
10
|
+
import sys
|
11
|
+
import io
|
12
|
+
import traceback
|
13
|
+
import spacr
|
14
|
+
|
15
|
+
try:
|
16
|
+
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
17
|
+
except AttributeError:
|
18
|
+
pass
|
19
|
+
|
20
|
+
from .logger import log_function_call
|
21
|
+
|
22
|
+
def safe_literal_eval(value):
|
23
|
+
try:
|
24
|
+
# First, try to evaluate as a literal
|
25
|
+
return ast.literal_eval(value)
|
26
|
+
except (ValueError, SyntaxError):
|
27
|
+
# If it fails, return the value as it is (a string)
|
28
|
+
return value
|
29
|
+
|
30
|
+
def disable_interactivity(fig):
|
31
|
+
if hasattr(fig.canvas, 'toolbar'):
|
32
|
+
fig.canvas.toolbar.pack_forget()
|
33
|
+
|
34
|
+
event_handlers = fig.canvas.callbacks.callbacks
|
35
|
+
for event, handlers in list(event_handlers.items()):
|
36
|
+
for handler_id in list(handlers.keys()):
|
37
|
+
fig.canvas.mpl_disconnect(handler_id)
|
38
|
+
|
39
|
+
def set_default_font(app, font_name="Arial Bold", size=10):
|
40
|
+
default_font = nametofont("TkDefaultFont")
|
41
|
+
text_font = nametofont("TkTextFont")
|
42
|
+
fixed_font = nametofont("TkFixedFont")
|
43
|
+
|
44
|
+
# Set the family to Open Sans and size as desired
|
45
|
+
for font in (default_font, text_font, fixed_font):
|
46
|
+
font.config(family=font_name, size=size)
|
47
|
+
|
48
|
+
class ScrollableFrame(ttk.Frame):
|
49
|
+
def __init__(self, container, *args, bg='#333333', **kwargs):
|
50
|
+
super().__init__(container, *args, **kwargs)
|
51
|
+
self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
|
52
|
+
|
53
|
+
canvas = tk.Canvas(self, bg=bg) # Set canvas background to match dark mode
|
54
|
+
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
|
55
|
+
|
56
|
+
self.scrollable_frame = ttk.Frame(canvas, style='TFrame') # Ensure it uses the styled frame
|
57
|
+
self.scrollable_frame.bind(
|
58
|
+
"<Configure>",
|
59
|
+
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
60
|
+
)
|
61
|
+
|
62
|
+
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
63
|
+
canvas.configure(yscrollcommand=scrollbar.set)
|
64
|
+
|
65
|
+
canvas.pack(side="left", fill="both", expand=True)
|
66
|
+
scrollbar.pack(side="right", fill="y")
|
67
|
+
|
68
|
+
class StdoutRedirector(object):
|
69
|
+
def __init__(self, text_widget):
|
70
|
+
self.text_widget = text_widget
|
71
|
+
|
72
|
+
def write(self, string):
|
73
|
+
self.text_widget.insert(tk.END, string)
|
74
|
+
self.text_widget.see(tk.END)
|
75
|
+
|
76
|
+
def flush(self):
|
77
|
+
pass
|
78
|
+
|
79
|
+
def check_mask_gui_settings(vars_dict):
|
80
|
+
settings = {}
|
81
|
+
for key, var in vars_dict.items():
|
82
|
+
value = var.get()
|
83
|
+
|
84
|
+
# Handle conversion for specific types if necessary
|
85
|
+
if key in ['channels', 'timelapse_frame_limits']: # Assuming these should be lists
|
86
|
+
try:
|
87
|
+
# Convert string representation of a list into an actual list
|
88
|
+
settings[key] = eval(value)
|
89
|
+
except:
|
90
|
+
messagebox.showerror("Error", f"Invalid format for {key}. Please enter a valid list.")
|
91
|
+
return
|
92
|
+
elif key in ['nucleus_channel', 'cell_channel', 'pathogen_channel', 'examples_to_plot', 'batch_size', 'timelapse_memory', 'workers', 'fps', 'magnification']: # Assuming these should be integers
|
93
|
+
try:
|
94
|
+
settings[key] = int(value) if value else None
|
95
|
+
except ValueError:
|
96
|
+
messagebox.showerror("Error", f"Invalid number for {key}.")
|
97
|
+
return
|
98
|
+
elif key in ['nucleus_background', 'cell_background', 'pathogen_background', 'nucleus_Signal_to_noise', 'cell_Signal_to_noise', 'pathogen_Signal_to_noise', 'nucleus_CP_prob', 'cell_CP_prob', 'pathogen_CP_prob', 'lower_quantile']: # Assuming these should be floats
|
99
|
+
try:
|
100
|
+
settings[key] = float(value) if value else None
|
101
|
+
except ValueError:
|
102
|
+
messagebox.showerror("Error", f"Invalid number for {key}.")
|
103
|
+
return
|
104
|
+
else:
|
105
|
+
settings[key] = value
|
106
|
+
return settings
|
107
|
+
|
108
|
+
def check_measure_gui_settings_v1(vars_dict):
|
109
|
+
settings = {}
|
110
|
+
for key, var in vars_dict.items():
|
111
|
+
value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
|
112
|
+
|
113
|
+
try:
|
114
|
+
# Special handling for 'channels' to convert into a list of integers
|
115
|
+
if key == 'channels':
|
116
|
+
settings[key] = [int(chan) for chan in eval(value)] if value else []
|
117
|
+
|
118
|
+
elif key == 'png_size':
|
119
|
+
temp_val = ast.literal_eval(value) if value else []
|
120
|
+
settings[key] = [list(map(int, dim)) for dim in temp_val] if temp_val else None
|
121
|
+
|
122
|
+
elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
|
123
|
+
settings[key] = ast.literal_eval(value) if value else None
|
124
|
+
|
125
|
+
elif key == 'dialate_png_ratios':
|
126
|
+
settings[key] = [float(num) for num in eval(value)] if value else None
|
127
|
+
|
128
|
+
elif key in ['pathogens', 'treatments', 'cells', 'crop_mode']:
|
129
|
+
settings[key] = eval(value) if value else None
|
130
|
+
|
131
|
+
elif key == 'timelapse_objects':
|
132
|
+
# Ensure it's a list of strings
|
133
|
+
settings[key] = eval(value) if value else []
|
134
|
+
|
135
|
+
# Handling for keys that should be treated as strings directly
|
136
|
+
elif key in ['normalize_by', 'experiment', 'measurement']:
|
137
|
+
settings[key] = str(value) if value else None
|
138
|
+
|
139
|
+
# Handling for single values that are not strings (int, float, bool)
|
140
|
+
elif key in ['cell_mask_dim', 'cell_min_size', 'nucleus_mask_dim', 'nucleus_min_size', 'pathogen_mask_dim', 'pathogen_min_size', 'cytoplasm_min_size', 'max_workers', 'channel_of_interest', 'nr_imgs']:
|
141
|
+
settings[key] = int(value) if value else None
|
142
|
+
|
143
|
+
elif key == 'um_per_pixel':
|
144
|
+
settings[key] = float(value) if value else None
|
145
|
+
|
146
|
+
# Direct handling of boolean values based on checkboxes
|
147
|
+
elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
|
148
|
+
settings[key] = bool(value)
|
149
|
+
|
150
|
+
else:
|
151
|
+
settings[key] = value
|
152
|
+
|
153
|
+
except SyntaxError as e:
|
154
|
+
messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
|
155
|
+
return None
|
156
|
+
except Exception as e:
|
157
|
+
messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
|
158
|
+
return None
|
159
|
+
|
160
|
+
return settings
|
161
|
+
|
162
|
+
def check_measure_gui_settings(vars_dict):
|
163
|
+
settings = {}
|
164
|
+
for key, var in vars_dict.items():
|
165
|
+
value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
|
166
|
+
|
167
|
+
try:
|
168
|
+
if key == 'channels' or key == 'png_dims':
|
169
|
+
# Converts string representation to a list of integers
|
170
|
+
settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
|
171
|
+
|
172
|
+
elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
|
173
|
+
settings[key] = ast.literal_eval(value) if value else None
|
174
|
+
|
175
|
+
elif key == 'dialate_png_ratios':
|
176
|
+
settings[key] = [float(num) for num in eval(value)] if value else None
|
177
|
+
|
178
|
+
elif key == 'normalize':
|
179
|
+
# Converts 'normalize' into a list of two integers
|
180
|
+
settings[key] = [int(num) for num in ast.literal_eval(value)] if value else None
|
181
|
+
|
182
|
+
elif key == 'normalize_by':
|
183
|
+
# 'normalize_by' is a string, so directly assign the value
|
184
|
+
settings[key] = value
|
185
|
+
|
186
|
+
elif key == 'png_size':
|
187
|
+
# Converts string representation into a list of lists of integers
|
188
|
+
temp_val = ast.literal_eval(value) if value else []
|
189
|
+
settings[key] = [list(map(int, dim)) for dim in temp_val] if temp_val else None
|
190
|
+
|
191
|
+
# Handling for other keys as in your original function...
|
192
|
+
|
193
|
+
elif key in ['pathogens', 'treatments', 'cells', 'crop_mode', 'timelapse_objects']:
|
194
|
+
# Ensuring these are evaluated correctly as lists or other structures
|
195
|
+
settings[key] = ast.literal_eval(value) if value else None
|
196
|
+
|
197
|
+
elif key == 'timelapse_objects':
|
198
|
+
# Ensure it's a list of strings
|
199
|
+
settings[key] = eval(value) if value else []
|
200
|
+
|
201
|
+
# Handling for keys that should be treated as strings directly
|
202
|
+
elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
|
203
|
+
settings[key] = str(value) if value else None
|
204
|
+
|
205
|
+
# Handling for single values that are not strings (int, float, bool)
|
206
|
+
elif key in ['cell_mask_dim', 'cell_min_size', 'nucleus_mask_dim', 'nucleus_min_size', 'pathogen_mask_dim', 'pathogen_min_size', 'cytoplasm_min_size', 'max_workers', 'channel_of_interest', 'nr_imgs']:
|
207
|
+
settings[key] = int(value) if value else None
|
208
|
+
|
209
|
+
elif key == 'um_per_pixel':
|
210
|
+
settings[key] = float(value) if value else None
|
211
|
+
|
212
|
+
# Direct handling of boolean values based on checkboxes
|
213
|
+
elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
|
214
|
+
settings[key] = bool(value)
|
215
|
+
|
216
|
+
except SyntaxError as e:
|
217
|
+
messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
|
218
|
+
return None
|
219
|
+
except Exception as e:
|
220
|
+
messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
|
221
|
+
return None
|
222
|
+
|
223
|
+
return settings
|
224
|
+
|
225
|
+
def measure_variables():
|
226
|
+
variables = {
|
227
|
+
'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
|
228
|
+
'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
|
229
|
+
'cell_mask_dim':('entry', None, 4),
|
230
|
+
'cell_min_size':('entry', None, 0),
|
231
|
+
'nucleus_mask_dim':('entry', None, 5),
|
232
|
+
'nucleus_min_size':('entry', None, 0),
|
233
|
+
'pathogen_mask_dim':('entry', None, 6),
|
234
|
+
'pathogen_min_size':('entry', None, 0),
|
235
|
+
'cytoplasm_min_size':('entry', None, 0),
|
236
|
+
'save_png':('check', None, True),
|
237
|
+
'crop_mode':('entry', None, '["cell"]'),
|
238
|
+
'use_bounding_box':('check', None, True),
|
239
|
+
'png_size': ('entry', None, '[[224,224]]'),
|
240
|
+
'normalize':('entry', None, '[2,98]'),
|
241
|
+
'png_dims':('entry', None, '[1,2,3]'),
|
242
|
+
'normalize_by':('combo', ['fov', 'png'], 'png'),
|
243
|
+
'save_measurements':('check', None, True),
|
244
|
+
'representative_images':('check', None, True),
|
245
|
+
'plot':('check', None, True),
|
246
|
+
'plot_filtration':('check', None, True),
|
247
|
+
'include_uninfected':('check', None, True),
|
248
|
+
'dialate_pngs':('check', None, False),
|
249
|
+
'dialate_png_ratios':('entry', None, '[0.2]'),
|
250
|
+
'timelapse':('check', None, False),
|
251
|
+
'timelapse_objects':('combo', ['["cell"]', '["nucleus"]', '["pathogen"]', '["cytoplasm"]'], '["cell"]'),
|
252
|
+
'max_workers':('entry', None, 30),
|
253
|
+
'experiment':('entry', None, 'experiment name'),
|
254
|
+
'cells':('entry', None, ['HeLa']),
|
255
|
+
'cell_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
|
256
|
+
'pathogens':('entry', None, '["wt","mutant"]'),
|
257
|
+
'pathogen_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
|
258
|
+
'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
|
259
|
+
'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
|
260
|
+
'channel_of_interest':('entry', None, 3),
|
261
|
+
'measurement':('entry', None, 'mean_intensity'),
|
262
|
+
'nr_imgs':('entry', None, 32),
|
263
|
+
'um_per_pixel':('entry', None, 0.1)
|
264
|
+
}
|
265
|
+
return variables
|
266
|
+
|
267
|
+
|
268
|
+
@log_function_call
|
269
|
+
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
270
|
+
label = ttk.Label(frame, text=label_text, style='TLabel') # Assuming you have a dark mode style for labels too
|
271
|
+
label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
|
272
|
+
|
273
|
+
if var_type == 'entry':
|
274
|
+
var = tk.StringVar(value=default_value) # Set default value
|
275
|
+
entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Assuming you have a dark mode style for entries
|
276
|
+
entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
277
|
+
elif var_type == 'check':
|
278
|
+
var = tk.BooleanVar(value=default_value) # Set default value (True/False)
|
279
|
+
# Use the custom style for Checkbutton
|
280
|
+
check = ttk.Checkbutton(frame, variable=var, style='Dark.TCheckbutton')
|
281
|
+
check.grid(column=1, row=row, sticky=tk.W, padx=5)
|
282
|
+
elif var_type == 'combo':
|
283
|
+
var = tk.StringVar(value=default_value) # Set default value
|
284
|
+
combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Assuming you have a dark mode style for comboboxes
|
285
|
+
combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
286
|
+
if default_value:
|
287
|
+
combo.set(default_value)
|
288
|
+
else:
|
289
|
+
var = None # Placeholder in case of an undefined var_type
|
290
|
+
|
291
|
+
return var
|
292
|
+
|
293
|
+
def add_measure_gui_defaults(settings):
|
294
|
+
settings['compartments'] = ['pathogen', 'cytoplasm']
|
295
|
+
return settings
|
296
|
+
|
297
|
+
def mask_variables():
|
298
|
+
variables = {
|
299
|
+
'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
|
300
|
+
'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
|
301
|
+
'custom_regex': ('entry', None, None),
|
302
|
+
'experiment': ('entry', None, 'exp'),
|
303
|
+
'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
|
304
|
+
'magnification': ('combo', [20, 40, 60,], 40),
|
305
|
+
'nucleus_channel': ('combo', [0,1,2,3, None], 0),
|
306
|
+
'nucleus_background': ('entry', None, 100),
|
307
|
+
'nucleus_Signal_to_noise': ('entry', None, 5),
|
308
|
+
'nucleus_CP_prob': ('entry', None, 0),
|
309
|
+
'cell_channel': ('combo', [0,1,2,3, None], 3),
|
310
|
+
'cell_background': ('entry', None, 100),
|
311
|
+
'cell_Signal_to_noise': ('entry', None, 5),
|
312
|
+
'cell_CP_prob': ('entry', None, 0),
|
313
|
+
'pathogen_channel': ('combo', [0,1,2,3, None], 2),
|
314
|
+
'pathogen_background': ('entry', None, 100),
|
315
|
+
'pathogen_Signal_to_noise': ('entry', None, 3),
|
316
|
+
'pathogen_CP_prob': ('entry', None, 0),
|
317
|
+
#'preprocess': ('check', None, True),
|
318
|
+
#'masks': ('check', None, True),
|
319
|
+
#'examples_to_plot': ('entry', None, 1),
|
320
|
+
#'randomize': ('check', None, True),
|
321
|
+
'batch_size': ('entry', None, 50),
|
322
|
+
'timelapse': ('check', None, False),
|
323
|
+
'timelapse_displacement': ('entry', None, None),
|
324
|
+
'timelapse_memory': ('entry', None, 0),
|
325
|
+
'timelapse_frame_limits': ('entry', None, '[0,1000]'),
|
326
|
+
'timelapse_remove_transient': ('check', None, True),
|
327
|
+
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
328
|
+
'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
|
329
|
+
#'fps': ('entry', None, 2),
|
330
|
+
#'remove_background': ('check', None, True),
|
331
|
+
'lower_quantile': ('entry', None, 0.01),
|
332
|
+
#'merge': ('check', None, False),
|
333
|
+
#'normalize_plots': ('check', None, True),
|
334
|
+
#'all_to_mip': ('check', None, False),
|
335
|
+
#'pick_slice': ('check', None, False),
|
336
|
+
#'skip_mode': ('entry', None, None),
|
337
|
+
'save': ('check', None, True),
|
338
|
+
'plot': ('check', None, True),
|
339
|
+
'workers': ('entry', None, 30),
|
340
|
+
'verbose': ('check', None, True),
|
341
|
+
}
|
342
|
+
return variables
|
343
|
+
|
344
|
+
def add_mask_gui_defaults(settings):
|
345
|
+
settings['remove_background'] = True
|
346
|
+
settings['fps'] = 2
|
347
|
+
settings['all_to_mip'] = False
|
348
|
+
settings['pick_slice'] = False
|
349
|
+
settings['merge'] = False
|
350
|
+
settings['skip_mode'] = ''
|
351
|
+
settings['verbose'] = False
|
352
|
+
settings['normalize_plots'] = True
|
353
|
+
settings['randomize'] = True
|
354
|
+
settings['preprocess'] = True
|
355
|
+
settings['masks'] = True
|
356
|
+
settings['examples_to_plot'] = 1
|
357
|
+
return settings
|
358
|
+
|
359
|
+
def generate_fields(variables, scrollable_frame):
|
360
|
+
vars_dict = {}
|
361
|
+
row = 0
|
362
|
+
for key, (var_type, options, default_value) in variables.items():
|
363
|
+
vars_dict[key] = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
|
364
|
+
row += 1
|
365
|
+
return vars_dict
|
366
|
+
|
367
|
+
class TextRedirector(object):
|
368
|
+
def __init__(self, widget, queue):
|
369
|
+
self.widget = widget
|
370
|
+
self.queue = queue
|
371
|
+
|
372
|
+
def write(self, str):
|
373
|
+
self.queue.put(str)
|
374
|
+
|
375
|
+
def flush(self):
|
376
|
+
pass
|
377
|
+
|
378
|
+
def create_dark_mode(root, style, console_output):
|
379
|
+
dark_bg = '#333333'
|
380
|
+
light_text = 'white'
|
381
|
+
dark_text = 'black'
|
382
|
+
input_bg = '#555555' # Slightly lighter background for input fields
|
383
|
+
|
384
|
+
# Configure ttkcompartments('TFrame', background=dark_bg)
|
385
|
+
style.configure('TLabel', background=dark_bg, foreground=light_text)
|
386
|
+
style.configure('TEntry', fieldbackground=input_bg, foreground=dark_text, background=dark_bg)
|
387
|
+
style.configure('TButton', background=dark_bg, foreground=dark_text)
|
388
|
+
style.map('TButton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
|
389
|
+
style.configure('Dark.TCheckbutton', background=dark_bg, foreground=dark_text)
|
390
|
+
style.map('Dark.TCheckbutton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
|
391
|
+
style.configure('TCombobox', fieldbackground=input_bg, foreground=dark_text, background=dark_bg, selectbackground=input_bg, selectforeground=dark_text)
|
392
|
+
style.map('TCombobox', fieldbackground=[('readonly', input_bg)], selectbackground=[('readonly', input_bg)], foreground=[('readonly', dark_text)])
|
393
|
+
|
394
|
+
if console_output != None:
|
395
|
+
console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("Arial", 12)
|
396
|
+
root.configure(bg=dark_bg)
|
397
|
+
|
398
|
+
def set_dark_style(style):
|
399
|
+
style.configure('TFrame', background='#333333')
|
400
|
+
style.configure('TLabel', background='#333333', foreground='white')
|
401
|
+
style.configure('TEntry', background='#333333', foreground='white')
|
402
|
+
style.configure('TCheckbutton', background='#333333', foreground='white')
|
403
|
+
|
404
|
+
|
405
|
+
|
406
|
+
@log_function_call
|
407
|
+
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
408
|
+
try:
|
409
|
+
while not q.empty():
|
410
|
+
message = q.get_nowait()
|
411
|
+
if message.startswith("Progress"):
|
412
|
+
progress_label.config(text=message)
|
413
|
+
elif message == "" or message == "\r":
|
414
|
+
pass
|
415
|
+
else:
|
416
|
+
print(message) # Or handle other messages differently
|
417
|
+
# For non-progress messages, you can still print them to the console or handle them as needed.
|
418
|
+
|
419
|
+
while not fig_queue.empty():
|
420
|
+
fig = fig_queue.get_nowait()
|
421
|
+
clear_canvas(canvas_widget)
|
422
|
+
except Exception as e:
|
423
|
+
print(f"Error updating GUI: {e}")
|
424
|
+
finally:
|
425
|
+
root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
|
426
|
+
|
427
|
+
def process_stdout_stderr(q):
|
428
|
+
"""
|
429
|
+
Redirect stdout and stderr to the queue q.
|
430
|
+
"""
|
431
|
+
sys.stdout = WriteToQueue(q)
|
432
|
+
sys.stderr = WriteToQueue(q)
|
433
|
+
|
434
|
+
class WriteToQueue(io.TextIOBase):
|
435
|
+
"""
|
436
|
+
A custom file-like class that writes any output to a given queue.
|
437
|
+
This can be used to redirect stdout and stderr.
|
438
|
+
"""
|
439
|
+
def __init__(self, q):
|
440
|
+
self.q = q
|
441
|
+
|
442
|
+
def write(self, msg):
|
443
|
+
self.q.put(msg)
|
444
|
+
|
445
|
+
def flush(self):
|
446
|
+
pass
|
447
|
+
|
448
|
+
def clear_canvas(canvas):
|
449
|
+
# Clear each plot (axes) in the figure
|
450
|
+
for ax in canvas.figure.get_axes():
|
451
|
+
ax.clear()
|
452
|
+
|
453
|
+
# Redraw the now empty canvas without changing its size
|
454
|
+
canvas.draw_idle()
|
455
|
+
|
456
|
+
@log_function_call
|
457
|
+
def measure_crop_wrapper(settings, q, fig_queue):
|
458
|
+
"""
|
459
|
+
Wraps the measure_crop function to integrate with GUI processes.
|
460
|
+
|
461
|
+
Parameters:
|
462
|
+
- settings: dict, The settings for the measure_crop function.
|
463
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
464
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
465
|
+
"""
|
466
|
+
|
467
|
+
def my_show():
|
468
|
+
"""
|
469
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
470
|
+
"""
|
471
|
+
fig = plt.gcf()
|
472
|
+
fig_queue.put(fig) # Queue the figure for GUI display
|
473
|
+
plt.close(fig) # Prevent the figure from being shown by plt.show()
|
474
|
+
|
475
|
+
# Temporarily override plt.show
|
476
|
+
original_show = plt.show
|
477
|
+
plt.show = my_show
|
478
|
+
|
479
|
+
try:
|
480
|
+
# Assuming spacr.measure.measure_crop is your function that potentially generates matplotlib figures
|
481
|
+
# Pass settings as a named argument, along with any other needed arguments
|
482
|
+
spacr.measure.measure_crop(settings=settings, annotation_settings={}, advanced_settings={})
|
483
|
+
except Exception as e:
|
484
|
+
errorMessage = f"Error during processing: {e}"
|
485
|
+
q.put(errorMessage) # Send the error message to the GUI via the queue
|
486
|
+
traceback.print_exc()
|
487
|
+
finally:
|
488
|
+
plt.show = original_show # Restore the original plt.show function
|