spacr 1.0.7__py3-none-any.whl → 1.1.0__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/app_classify.py +10 -0
- spacr/app_mask.py +9 -0
- spacr/app_measure.py +9 -0
- spacr/app_sequencing.py +9 -0
- spacr/core.py +172 -1
- spacr/deep_spacr.py +296 -7
- spacr/gui.py +68 -0
- spacr/gui_core.py +319 -10
- spacr/gui_elements.py +772 -13
- spacr/gui_utils.py +301 -151
- spacr/io.py +887 -71
- spacr/logger.py +36 -0
- spacr/measure.py +206 -28
- spacr/ml.py +606 -142
- spacr/plot.py +797 -131
- spacr/sequencing.py +363 -8
- spacr/settings.py +1158 -38
- spacr/sp_stats.py +80 -12
- spacr/spacr_cellpose.py +115 -2
- spacr/submodules.py +747 -19
- spacr/timelapse.py +237 -53
- spacr/toxo.py +132 -6
- spacr/utils.py +2422 -80
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/METADATA +31 -17
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/RECORD +29 -29
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/LICENSE +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/WHEEL +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/entry_points.txt +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -9,7 +9,7 @@ import psutil
|
|
9
9
|
from PIL import Image, ImageTk
|
10
10
|
from screeninfo import get_monitors
|
11
11
|
|
12
|
-
from .gui_elements import
|
12
|
+
from .gui_elements import spacrEntry, spacrCheck, spacrCombo
|
13
13
|
|
14
14
|
try:
|
15
15
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
@@ -18,7 +18,20 @@ except AttributeError:
|
|
18
18
|
|
19
19
|
def initialize_cuda():
|
20
20
|
"""
|
21
|
-
Initializes CUDA
|
21
|
+
Initializes CUDA for the main process if a compatible GPU is available.
|
22
|
+
|
23
|
+
This function checks if CUDA is available on the system. If it is, it allocates
|
24
|
+
a small tensor on the GPU to ensure that CUDA is properly initialized. A message
|
25
|
+
is printed to indicate whether CUDA was successfully initialized or if it is not
|
26
|
+
available.
|
27
|
+
|
28
|
+
Note:
|
29
|
+
This function is intended to be used in environments where CUDA-enabled GPUs
|
30
|
+
are present and PyTorch is installed.
|
31
|
+
|
32
|
+
Prints:
|
33
|
+
- "CUDA initialized in the main process." if CUDA is available and initialized.
|
34
|
+
- "CUDA is not available." if no compatible GPU is detected.
|
22
35
|
"""
|
23
36
|
if torch.cuda.is_available():
|
24
37
|
# Allocate a small tensor on the GPU
|
@@ -28,6 +41,27 @@ def initialize_cuda():
|
|
28
41
|
print("CUDA is not available.")
|
29
42
|
|
30
43
|
def set_high_priority(process):
|
44
|
+
"""
|
45
|
+
Sets the priority of a given process to high.
|
46
|
+
|
47
|
+
On Windows systems, the process priority is set to HIGH_PRIORITY_CLASS.
|
48
|
+
On Unix-like systems, the process priority is adjusted to a higher level
|
49
|
+
by setting its niceness value to -10.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
process (psutil.Process): The process whose priority is to be adjusted.
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
psutil.AccessDenied: If the current user does not have permission to change
|
56
|
+
the priority of the process.
|
57
|
+
psutil.NoSuchProcess: If the specified process does not exist.
|
58
|
+
Exception: For any other errors encountered during the operation.
|
59
|
+
|
60
|
+
Notes:
|
61
|
+
- This function requires the `psutil` library to interact with system processes.
|
62
|
+
- Adjusting process priority may require elevated privileges depending on the
|
63
|
+
operating system and user permissions.
|
64
|
+
"""
|
31
65
|
try:
|
32
66
|
p = psutil.Process(process.pid)
|
33
67
|
if os.name == 'nt': # Windows
|
@@ -43,10 +77,49 @@ def set_high_priority(process):
|
|
43
77
|
print(f"Failed to set high priority for process {process.pid}: {e}")
|
44
78
|
|
45
79
|
def set_cpu_affinity(process):
|
80
|
+
"""
|
81
|
+
Set the CPU affinity for a given process to use all available CPUs.
|
82
|
+
|
83
|
+
This function modifies the CPU affinity of the specified process, allowing
|
84
|
+
it to run on all CPUs available on the system.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
process (psutil.Process): A psutil.Process object representing the process
|
88
|
+
whose CPU affinity is to be set.
|
89
|
+
|
90
|
+
Raises:
|
91
|
+
psutil.NoSuchProcess: If the process does not exist.
|
92
|
+
psutil.AccessDenied: If the process cannot be accessed due to insufficient permissions.
|
93
|
+
psutil.ZombieProcess: If the process is a zombie process.
|
94
|
+
"""
|
46
95
|
p = psutil.Process(process.pid)
|
47
96
|
p.cpu_affinity(list(range(os.cpu_count())))
|
48
97
|
|
49
98
|
def proceed_with_app(root, app_name, app_func):
|
99
|
+
"""
|
100
|
+
Prepares the application window to load a new app by clearing the current
|
101
|
+
content frame and initializing the specified app.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
root (tk.Tk or tk.Toplevel): The root window or parent container that
|
105
|
+
contains the content frame.
|
106
|
+
app_name (str): The name of the application to be loaded (not used in
|
107
|
+
the current implementation but could be useful for logging or
|
108
|
+
debugging purposes).
|
109
|
+
app_func (callable): A function that initializes the new application
|
110
|
+
within the content frame. It should accept the content frame as
|
111
|
+
its only argument.
|
112
|
+
|
113
|
+
Behavior:
|
114
|
+
- Destroys all widgets in the `content_frame` attribute of `root`
|
115
|
+
(if it exists).
|
116
|
+
- Calls `app_func` with `root.content_frame` to initialize the new
|
117
|
+
application.
|
118
|
+
|
119
|
+
Note:
|
120
|
+
Ensure that `root` has an attribute `content_frame` that is a valid
|
121
|
+
tkinter container (e.g., a `tk.Frame`) before calling this function.
|
122
|
+
"""
|
50
123
|
# Clear the current content frame
|
51
124
|
if hasattr(root, 'content_frame'):
|
52
125
|
for widget in root.content_frame.winfo_children():
|
@@ -59,6 +132,31 @@ def proceed_with_app(root, app_name, app_func):
|
|
59
132
|
app_func(root.content_frame)
|
60
133
|
|
61
134
|
def load_app(root, app_name, app_func):
|
135
|
+
"""
|
136
|
+
Load a new application into the GUI framework.
|
137
|
+
|
138
|
+
This function handles the transition between applications in the GUI by
|
139
|
+
clearing the current canvas, canceling scheduled tasks, and invoking
|
140
|
+
exit functionality for specific applications if necessary.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
root: The root object of the GUI, which contains the canvas,
|
144
|
+
after_tasks, and other application state.
|
145
|
+
app_name (str): The name of the application to load.
|
146
|
+
app_func (callable): The function to initialize the new application.
|
147
|
+
|
148
|
+
Behavior:
|
149
|
+
- Clears the current canvas if it exists.
|
150
|
+
- Cancels all scheduled `after` tasks associated with the root object.
|
151
|
+
- If the current application has an exit function and the new app is
|
152
|
+
not "Annotate" or "make_masks", the exit function is invoked before
|
153
|
+
proceeding to the new application.
|
154
|
+
- Proceeds to load the new application using the provided `app_func`.
|
155
|
+
|
156
|
+
Note:
|
157
|
+
The `proceed_with_app` function is used internally to finalize the
|
158
|
+
transition to the new application.
|
159
|
+
"""
|
62
160
|
# Clear the canvas if it exists
|
63
161
|
if root.canvas is not None:
|
64
162
|
root.clear_frame(root.canvas)
|
@@ -184,139 +282,83 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
|
|
184
282
|
|
185
283
|
def process_stdout_stderr(q):
|
186
284
|
"""
|
187
|
-
|
285
|
+
Redirects the standard output (stdout) and standard error (stderr) streams
|
286
|
+
to a queue for processing.
|
287
|
+
|
288
|
+
This function replaces the default `sys.stdout` and `sys.stderr` with
|
289
|
+
instances of `WriteToQueue`, which write all output to the provided queue.
|
290
|
+
|
291
|
+
:param q: A queue object where the redirected output will be stored.
|
292
|
+
:type q: queue.Queue
|
188
293
|
"""
|
189
294
|
sys.stdout = WriteToQueue(q)
|
190
295
|
sys.stderr = WriteToQueue(q)
|
191
296
|
|
192
297
|
class WriteToQueue(io.TextIOBase):
|
193
298
|
"""
|
194
|
-
A
|
195
|
-
|
299
|
+
A file-like object that redirects writes to a queue.
|
300
|
+
|
301
|
+
:param q: The queue to write output to.
|
302
|
+
:type q: queue.Queue
|
196
303
|
"""
|
197
304
|
def __init__(self, q):
|
198
305
|
self.q = q
|
199
306
|
def write(self, msg):
|
307
|
+
"""
|
308
|
+
Write string to stream.
|
309
|
+
|
310
|
+
:param msg: The string message to write.
|
311
|
+
:type msg: str
|
312
|
+
:returns: Number of characters written.
|
313
|
+
:rtype: int
|
314
|
+
"""
|
200
315
|
if msg.strip(): # Avoid empty messages
|
201
316
|
self.q.put(msg)
|
202
317
|
def flush(self):
|
318
|
+
"""
|
319
|
+
Flush write buffers, if applicable.
|
320
|
+
|
321
|
+
This is a no-op in this implementation.
|
322
|
+
"""
|
203
323
|
pass
|
204
324
|
|
205
325
|
def cancel_after_tasks(frame):
|
326
|
+
"""
|
327
|
+
Cancels all scheduled 'after' tasks associated with a given frame.
|
328
|
+
|
329
|
+
This function checks if the provided frame object has an attribute
|
330
|
+
named 'after_tasks', which is expected to be a list of task IDs
|
331
|
+
scheduled using the `after` method (e.g., in a Tkinter application).
|
332
|
+
If such tasks exist, it cancels each of them using the `after_cancel`
|
333
|
+
method and then clears the list.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
frame: An object (typically a Tkinter widget) that may have an
|
337
|
+
'after_tasks' attribute containing scheduled task IDs.
|
338
|
+
|
339
|
+
Raises:
|
340
|
+
AttributeError: If the frame does not have the required methods
|
341
|
+
(`after_cancel` or `after_tasks` attribute).
|
342
|
+
"""
|
206
343
|
if hasattr(frame, 'after_tasks'):
|
207
344
|
for task in frame.after_tasks:
|
208
345
|
frame.after_cancel(task)
|
209
346
|
frame.after_tasks.clear()
|
210
347
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
db = os.path.join(src, 'measurements/measurements.db')
|
217
|
-
conn = sqlite3.connect(db)
|
218
|
-
c = conn.cursor()
|
219
|
-
c.execute('PRAGMA table_info(png_list)')
|
220
|
-
cols = c.fetchall()
|
221
|
-
if settings['annotation_column'] not in [col[1] for col in cols]:
|
222
|
-
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
223
|
-
conn.commit()
|
224
|
-
conn.close()
|
225
|
-
|
226
|
-
root = tk.Tk()
|
227
|
-
|
228
|
-
root.geometry(f"{root.winfo_screenwidth()}x{root.winfo_screenheight()}")
|
229
|
-
|
230
|
-
db_path = os.path.join(settings['src'], 'measurements/measurements.db')
|
231
|
-
|
232
|
-
app = AnnotateApp(root,
|
233
|
-
db_path=db_path,
|
234
|
-
src=settings['src'],
|
235
|
-
image_type=settings['image_type'],
|
236
|
-
channels=settings['channels'],
|
237
|
-
image_size=settings['img_size'],
|
238
|
-
annotation_column=settings['annotation_column'],
|
239
|
-
normalize=settings['normalize'],
|
240
|
-
percentiles=settings['percentiles'],
|
241
|
-
measurement=settings['measurement'],
|
242
|
-
threshold=settings['threshold'],
|
243
|
-
normalize_channels=settings['normalize_channels'])
|
244
|
-
|
245
|
-
app.load_images()
|
246
|
-
root.mainloop()
|
247
|
-
|
248
|
-
def generate_annotate_fields(frame):
|
249
|
-
from .settings import set_annotate_default_settings
|
250
|
-
from .gui_elements import set_dark_style
|
251
|
-
|
252
|
-
style_out = set_dark_style(ttk.Style())
|
253
|
-
font_loader = style_out['font_loader']
|
254
|
-
font_size = style_out['font_size'] - 2
|
255
|
-
|
256
|
-
vars_dict = {}
|
257
|
-
settings = set_annotate_default_settings(settings={})
|
258
|
-
|
259
|
-
for setting in settings:
|
260
|
-
vars_dict[setting] = {
|
261
|
-
'entry': ttk.Entry(frame),
|
262
|
-
'value': settings[setting]
|
263
|
-
}
|
264
|
-
|
265
|
-
# Arrange input fields and labels
|
266
|
-
for row, (name, data) in enumerate(vars_dict.items()):
|
267
|
-
tk.Label(
|
268
|
-
frame,
|
269
|
-
text=f"{name.replace('_', ' ').capitalize()}:",
|
270
|
-
bg=style_out['bg_color'],
|
271
|
-
fg=style_out['fg_color'],
|
272
|
-
font=font_loader.get_font(size=font_size)
|
273
|
-
).grid(row=row, column=0)
|
274
|
-
|
275
|
-
value = data['value']
|
276
|
-
if isinstance(value, list):
|
277
|
-
string_value = ','.join(map(str, value))
|
278
|
-
elif isinstance(value, (int, float, bool)):
|
279
|
-
string_value = str(value)
|
280
|
-
elif value is None:
|
281
|
-
string_value = ''
|
282
|
-
else:
|
283
|
-
string_value = value
|
284
|
-
|
285
|
-
data['entry'].insert(0, string_value)
|
286
|
-
data['entry'].grid(row=row, column=1)
|
287
|
-
|
288
|
-
return vars_dict
|
348
|
+
def load_next_app(root):
|
349
|
+
"""
|
350
|
+
Loads the next application by invoking the function stored in the `next_app_func`
|
351
|
+
attribute of the provided `root` object. If the current root window has been
|
352
|
+
destroyed, a new root window is initialized before invoking the next application.
|
289
353
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
settings['percentiles'] = list(map(int, settings['percentiles'].split(','))) # Convert string to list of integers
|
295
|
-
settings['normalize'] = settings['normalize'].lower() == 'true'
|
296
|
-
settings['normalize_channels'] = settings['channels'].split(',')
|
297
|
-
settings['rows'] = int(settings['rows'])
|
298
|
-
settings['columns'] = int(settings['columns'])
|
299
|
-
settings['measurement'] = settings['measurement'].split(',')
|
300
|
-
settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
|
301
|
-
|
302
|
-
# Clear previous content instead of destroying the root
|
303
|
-
if hasattr(parent_frame, 'winfo_children'):
|
304
|
-
for widget in parent_frame.winfo_children():
|
305
|
-
widget.destroy()
|
306
|
-
|
307
|
-
# Start the annotate application in the same root window
|
308
|
-
annotate_app(parent_frame, settings)
|
309
|
-
|
310
|
-
# Global list to keep references to PhotoImage objects
|
311
|
-
global_image_refs = []
|
312
|
-
|
313
|
-
def annotate_app(parent_frame, settings):
|
314
|
-
global global_image_refs
|
315
|
-
global_image_refs.clear()
|
316
|
-
root = parent_frame.winfo_toplevel()
|
317
|
-
annotate_with_image_refs(settings, root, lambda: load_next_app(root))
|
354
|
+
Args:
|
355
|
+
root (tk.Tk): The current root window object, which contains the attributes
|
356
|
+
`next_app_func` (a callable for the next application) and
|
357
|
+
`next_app_args` (a tuple of arguments to pass to the callable).
|
318
358
|
|
319
|
-
|
359
|
+
Raises:
|
360
|
+
tk.TclError: If the root window does not exist and needs to be reinitialized.
|
361
|
+
"""
|
320
362
|
# Get the next app function and arguments
|
321
363
|
next_app_func = root.next_app_func
|
322
364
|
next_app_args = root.next_app_args
|
@@ -335,38 +377,30 @@ def load_next_app(root):
|
|
335
377
|
new_root.title("SpaCr Application")
|
336
378
|
next_app_func(new_root, *next_app_args)
|
337
379
|
|
338
|
-
def
|
339
|
-
|
340
|
-
|
341
|
-
settings = set_annotate_default_settings(settings)
|
342
|
-
src = settings['src']
|
343
|
-
|
344
|
-
db = os.path.join(src, 'measurements/measurements.db')
|
345
|
-
conn = sqlite3.connect(db)
|
346
|
-
c = conn.cursor()
|
347
|
-
c.execute('PRAGMA table_info(png_list)')
|
348
|
-
cols = c.fetchall()
|
349
|
-
if settings['annotation_column'] not in [col[1] for col in cols]:
|
350
|
-
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
351
|
-
conn.commit()
|
352
|
-
conn.close()
|
353
|
-
|
354
|
-
screen_width = root.winfo_screenwidth()
|
355
|
-
screen_height = root.winfo_screenheight()
|
356
|
-
root.geometry(f"{screen_width}x{screen_height}")
|
380
|
+
def convert_settings_dict_for_gui(settings):
|
381
|
+
"""
|
382
|
+
Convert a dictionary of settings into a format suitable for GUI rendering.
|
357
383
|
|
358
|
-
|
384
|
+
Each key in the input dictionary is mapped to a tuple of the form:
|
385
|
+
(input_type, options, default_value), where:
|
386
|
+
|
387
|
+
- input_type (str): The type of GUI element. One of:
|
388
|
+
* 'combo' for dropdown menus
|
389
|
+
* 'check' for checkboxes
|
390
|
+
* 'entry' for entry fields
|
391
|
+
- options (list or None): A list of selectable options for 'combo' types, or None for other types.
|
392
|
+
- default_value: The current or default value to be displayed in the GUI.
|
359
393
|
|
360
|
-
|
361
|
-
|
394
|
+
Special keys are mapped to pre-defined configurations with known option sets
|
395
|
+
(e.g., 'metadata_type', 'channels', 'model_type').
|
362
396
|
|
363
|
-
|
364
|
-
|
397
|
+
:param settings: Dictionary where keys are setting names and values are their current values.
|
398
|
+
:type settings: dict
|
365
399
|
|
366
|
-
|
367
|
-
|
400
|
+
:return: Dictionary mapping setting names to tuples for GUI rendering.
|
401
|
+
:rtype: dict
|
402
|
+
"""
|
368
403
|
|
369
|
-
def convert_settings_dict_for_gui(settings):
|
370
404
|
from torchvision import models as torch_models
|
371
405
|
torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
|
372
406
|
chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
|
@@ -419,10 +453,21 @@ def convert_settings_dict_for_gui(settings):
|
|
419
453
|
|
420
454
|
return variables
|
421
455
|
|
422
|
-
|
423
456
|
def spacrFigShow(fig_queue=None):
|
424
457
|
"""
|
425
|
-
|
458
|
+
Displays the current matplotlib figure or adds it to a queue.
|
459
|
+
|
460
|
+
This function retrieves the current matplotlib figure using `plt.gcf()`.
|
461
|
+
If a `fig_queue` is provided, the figure is added to the queue.
|
462
|
+
Otherwise, the figure is displayed using the `show()` method.
|
463
|
+
After the figure is either queued or displayed, it is closed using `plt.close()`.
|
464
|
+
|
465
|
+
Args:
|
466
|
+
fig_queue (queue.Queue, optional): A queue to store the figure.
|
467
|
+
If None, the figure is displayed instead.
|
468
|
+
|
469
|
+
Returns:
|
470
|
+
None
|
426
471
|
"""
|
427
472
|
fig = plt.gcf()
|
428
473
|
if fig_queue:
|
@@ -436,7 +481,7 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
|
|
436
481
|
"""
|
437
482
|
Wraps the run_multiple_simulations function to integrate with GUI processes.
|
438
483
|
|
439
|
-
|
484
|
+
Args:
|
440
485
|
- settings: dict, The settings for the run_multiple_simulations function.
|
441
486
|
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
442
487
|
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
@@ -461,8 +506,45 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
|
|
461
506
|
plt.show = original_show
|
462
507
|
|
463
508
|
def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
464
|
-
|
465
|
-
|
509
|
+
"""
|
510
|
+
Executes a specified processing function in the GUI context based on `settings_type`.
|
511
|
+
|
512
|
+
This function selects and runs one of the core `spaCR` processing functions
|
513
|
+
(e.g., segmentation, measurement, classification, barcode mapping) based on the
|
514
|
+
provided `settings_type` string. It wraps the execution with a logging mechanism
|
515
|
+
to redirect stdout/stderr to the GUI console and handles exceptions cleanly.
|
516
|
+
|
517
|
+
Args
|
518
|
+
----------
|
519
|
+
settings_type : str
|
520
|
+
A string indicating which processing function to execute. Supported values include:
|
521
|
+
'mask', 'measure', 'classify', 'train_cellpose', 'ml_analyze', 'cellpose_masks',
|
522
|
+
'cellpose_all', 'map_barcodes', 'regression', 'recruitment', 'analyze_plaques', 'convert'.
|
523
|
+
|
524
|
+
settings : dict
|
525
|
+
A dictionary of parameters required by the selected function.
|
526
|
+
|
527
|
+
q : multiprocessing.Queue
|
528
|
+
Queue for redirecting standard output and errors to the GUI console.
|
529
|
+
|
530
|
+
fig_queue : multiprocessing.Queue
|
531
|
+
Queue used to transfer figures (e.g., plots) from the worker process to the GUI.
|
532
|
+
|
533
|
+
stop_requested : multiprocessing.Value
|
534
|
+
A shared value to signal whether execution has completed or was interrupted.
|
535
|
+
|
536
|
+
Raises
|
537
|
+
------
|
538
|
+
ValueError
|
539
|
+
If an invalid `settings_type` is provided.
|
540
|
+
|
541
|
+
Notes
|
542
|
+
-----
|
543
|
+
- Redirects stdout/stderr to the GUI using `process_stdout_stderr`.
|
544
|
+
- Catches and reports any exceptions to the GUI queue.
|
545
|
+
- Sets `stop_requested.value = 1` when the task finishes (whether successful or not).
|
546
|
+
"""
|
547
|
+
from .core import preprocess_generate_masks
|
466
548
|
from .spacr_cellpose import identify_masks_finetune, check_cellpose_models
|
467
549
|
from .submodules import analyze_recruitment
|
468
550
|
from .ml import generate_ml_scores, perform_regression
|
@@ -506,9 +588,6 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
|
506
588
|
elif settings_type == 'recruitment':
|
507
589
|
function = analyze_recruitment
|
508
590
|
imports = 1
|
509
|
-
elif settings_type == 'umap':
|
510
|
-
function = generate_image_umap
|
511
|
-
imports = 1
|
512
591
|
elif settings_type == 'analyze_plaques':
|
513
592
|
function = analyze_plaques
|
514
593
|
imports = 1
|
@@ -529,7 +608,7 @@ def hide_all_settings(vars_dict, categories):
|
|
529
608
|
"""
|
530
609
|
Function to initially hide all settings in the GUI.
|
531
610
|
|
532
|
-
|
611
|
+
Args:
|
533
612
|
- categories: dict, The categories of settings with their corresponding settings.
|
534
613
|
- vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
|
535
614
|
"""
|
@@ -551,6 +630,32 @@ def hide_all_settings(vars_dict, categories):
|
|
551
630
|
return vars_dict
|
552
631
|
|
553
632
|
def setup_frame(parent_frame):
|
633
|
+
"""
|
634
|
+
Set up the main GUI layout within the given parent frame.
|
635
|
+
|
636
|
+
This function initializes a dark-themed, resizable GUI layout using `PanedWindow`
|
637
|
+
containers. It organizes the layout into left-hand settings, central vertical content,
|
638
|
+
and bottom horizontal panels. It also sets initial sash positions and layout weights.
|
639
|
+
|
640
|
+
Args
|
641
|
+
----------
|
642
|
+
parent_frame : tk.Frame
|
643
|
+
The parent Tkinter frame to populate with the GUI layout.
|
644
|
+
|
645
|
+
Returns
|
646
|
+
-------
|
647
|
+
tuple
|
648
|
+
A tuple containing:
|
649
|
+
- parent_frame (tk.Frame): The modified parent frame with the layout initialized.
|
650
|
+
- vertical_container (tk.PanedWindow): Top container in the right-hand area for main content.
|
651
|
+
- horizontal_container (tk.PanedWindow): Bottom container for additional widgets.
|
652
|
+
- settings_container (tk.PanedWindow): Left-hand container for GUI settings.
|
653
|
+
|
654
|
+
Notes
|
655
|
+
-----
|
656
|
+
- Uses `set_dark_style` and `set_element_size` from `gui_elements` to theme and size widgets.
|
657
|
+
- Dynamically positions the sash between the left and right panes to 25% of the screen width.
|
658
|
+
"""
|
554
659
|
from .gui_elements import set_dark_style, set_element_size
|
555
660
|
|
556
661
|
style = ttk.Style(parent_frame)
|
@@ -599,8 +704,29 @@ def setup_frame(parent_frame):
|
|
599
704
|
|
600
705
|
return parent_frame, vertical_container, horizontal_container, settings_container
|
601
706
|
|
602
|
-
|
603
707
|
def download_hug_dataset(q, vars_dict):
|
708
|
+
"""
|
709
|
+
Downloads a dataset and settings files from the Hugging Face Hub and updates the provided variables dictionary.
|
710
|
+
|
711
|
+
Args:
|
712
|
+
q (queue.Queue): A queue object used for logging messages during the download process.
|
713
|
+
vars_dict (dict): A dictionary containing variables to be updated. If 'src' is present in the dictionary,
|
714
|
+
the third element of 'src' will be updated with the downloaded dataset path.
|
715
|
+
|
716
|
+
The function performs the following steps:
|
717
|
+
1. Downloads a dataset from the Hugging Face Hub using the specified repository ID and subfolder.
|
718
|
+
2. Updates the 'src' variable in `vars_dict` with the local path of the downloaded dataset, if applicable.
|
719
|
+
3. Logs the dataset download status to the provided queue.
|
720
|
+
4. Downloads settings files from another repository on the Hugging Face Hub.
|
721
|
+
5. Logs the settings download status to the provided queue.
|
722
|
+
|
723
|
+
Notes:
|
724
|
+
- The dataset is downloaded to a local directory under the user's home directory named "datasets".
|
725
|
+
- Any exceptions during the download process are caught and logged to the queue.
|
726
|
+
|
727
|
+
Raises:
|
728
|
+
None: All exceptions are handled internally and logged to the queue.
|
729
|
+
"""
|
604
730
|
dataset_repo_id = "einarolafsson/toxo_mito"
|
605
731
|
settings_repo_id = "einarolafsson/spacr_settings"
|
606
732
|
dataset_subfolder = "plate1"
|
@@ -771,6 +897,31 @@ def display_gif_in_plot_frame(gif_path, parent_frame):
|
|
771
897
|
update_frame(0)
|
772
898
|
|
773
899
|
def display_media_in_plot_frame(media_path, parent_frame):
|
900
|
+
"""
|
901
|
+
Display a media file (MP4, AVI, or GIF) in a Tkinter frame, playing it on repeat
|
902
|
+
while fully filling the frame and maintaining the aspect ratio.
|
903
|
+
|
904
|
+
Args:
|
905
|
+
media_path (str): The file path to the media file (MP4, AVI, or GIF).
|
906
|
+
parent_frame (tk.Frame): The Tkinter frame where the media will be displayed.
|
907
|
+
|
908
|
+
Behavior:
|
909
|
+
- For MP4 and AVI files:
|
910
|
+
- Uses OpenCV to read and play the video.
|
911
|
+
- Resizes and crops the video to fully fill the parent frame while maintaining aspect ratio.
|
912
|
+
- Plays the video on repeat.
|
913
|
+
- For GIF files:
|
914
|
+
- Uses PIL to read and play the GIF.
|
915
|
+
- Resizes and crops the GIF to fully fill the parent frame while maintaining aspect ratio.
|
916
|
+
- Plays the GIF on repeat.
|
917
|
+
|
918
|
+
Raises:
|
919
|
+
ValueError: If the file format is not supported (only MP4, AVI, and GIF are supported).
|
920
|
+
|
921
|
+
Notes:
|
922
|
+
- The function clears any existing widgets in the parent frame before displaying the media.
|
923
|
+
- The parent frame is configured to expand and adapt to the media's aspect ratio.
|
924
|
+
"""
|
774
925
|
"""Display an MP4, AVI, or GIF and play it on repeat in the parent_frame, fully filling the frame while maintaining aspect ratio."""
|
775
926
|
# Clear parent_frame if it contains any previous widgets
|
776
927
|
for widget in parent_frame.winfo_children():
|
@@ -915,8 +1066,7 @@ def display_media_in_plot_frame(media_path, parent_frame):
|
|
915
1066
|
else:
|
916
1067
|
raise ValueError("Unsupported file format. Only .mp4, .avi, and .gif are supported.")
|
917
1068
|
|
918
|
-
def print_widget_structure(widget, indent=0):
|
919
|
-
"""Recursively print the widget structure."""
|
1069
|
+
def print_widget_structure(widget, indent=0):
|
920
1070
|
# Print the widget's name and class
|
921
1071
|
print(" " * indent + f"{widget}: {widget.winfo_class()}")
|
922
1072
|
|