spacr 1.0.9__py3-none-any.whl → 1.1.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 +3 -2
- 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 +304 -12
- 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.9.dist-info → spacr-1.1.1.dist-info}/METADATA +31 -17
- {spacr-1.0.9.dist-info → spacr-1.1.1.dist-info}/RECORD +30 -30
- {spacr-1.0.9.dist-info → spacr-1.1.1.dist-info}/LICENSE +0 -0
- {spacr-1.0.9.dist-info → spacr-1.1.1.dist-info}/WHEEL +0 -0
- {spacr-1.0.9.dist-info → spacr-1.1.1.dist-info}/entry_points.txt +0 -0
- {spacr-1.0.9.dist-info → spacr-1.1.1.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -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,31 +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
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.
|
353
|
+
|
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).
|
358
|
+
|
359
|
+
Raises:
|
360
|
+
tk.TclError: If the root window does not exist and needs to be reinitialized.
|
361
|
+
"""
|
212
362
|
# Get the next app function and arguments
|
213
363
|
next_app_func = root.next_app_func
|
214
364
|
next_app_args = root.next_app_args
|
@@ -228,6 +378,29 @@ def load_next_app(root):
|
|
228
378
|
next_app_func(new_root, *next_app_args)
|
229
379
|
|
230
380
|
def convert_settings_dict_for_gui(settings):
|
381
|
+
"""
|
382
|
+
Convert a dictionary of settings into a format suitable for GUI rendering.
|
383
|
+
|
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.
|
393
|
+
|
394
|
+
Special keys are mapped to pre-defined configurations with known option sets
|
395
|
+
(e.g., 'metadata_type', 'channels', 'model_type').
|
396
|
+
|
397
|
+
:param settings: Dictionary where keys are setting names and values are their current values.
|
398
|
+
:type settings: dict
|
399
|
+
|
400
|
+
:return: Dictionary mapping setting names to tuples for GUI rendering.
|
401
|
+
:rtype: dict
|
402
|
+
"""
|
403
|
+
|
231
404
|
from torchvision import models as torch_models
|
232
405
|
torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
|
233
406
|
chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
|
@@ -280,10 +453,21 @@ def convert_settings_dict_for_gui(settings):
|
|
280
453
|
|
281
454
|
return variables
|
282
455
|
|
283
|
-
|
284
456
|
def spacrFigShow(fig_queue=None):
|
285
457
|
"""
|
286
|
-
|
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
|
287
471
|
"""
|
288
472
|
fig = plt.gcf()
|
289
473
|
if fig_queue:
|
@@ -297,7 +481,7 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
|
|
297
481
|
"""
|
298
482
|
Wraps the run_multiple_simulations function to integrate with GUI processes.
|
299
483
|
|
300
|
-
|
484
|
+
Args:
|
301
485
|
- settings: dict, The settings for the run_multiple_simulations function.
|
302
486
|
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
303
487
|
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
@@ -322,7 +506,44 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
|
|
322
506
|
plt.show = original_show
|
323
507
|
|
324
508
|
def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
325
|
-
|
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
|
+
"""
|
326
547
|
from .core import preprocess_generate_masks
|
327
548
|
from .spacr_cellpose import identify_masks_finetune, check_cellpose_models
|
328
549
|
from .submodules import analyze_recruitment
|
@@ -387,7 +608,7 @@ def hide_all_settings(vars_dict, categories):
|
|
387
608
|
"""
|
388
609
|
Function to initially hide all settings in the GUI.
|
389
610
|
|
390
|
-
|
611
|
+
Args:
|
391
612
|
- categories: dict, The categories of settings with their corresponding settings.
|
392
613
|
- vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
|
393
614
|
"""
|
@@ -409,6 +630,32 @@ def hide_all_settings(vars_dict, categories):
|
|
409
630
|
return vars_dict
|
410
631
|
|
411
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
|
+
"""
|
412
659
|
from .gui_elements import set_dark_style, set_element_size
|
413
660
|
|
414
661
|
style = ttk.Style(parent_frame)
|
@@ -457,8 +704,29 @@ def setup_frame(parent_frame):
|
|
457
704
|
|
458
705
|
return parent_frame, vertical_container, horizontal_container, settings_container
|
459
706
|
|
460
|
-
|
461
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
|
+
"""
|
462
730
|
dataset_repo_id = "einarolafsson/toxo_mito"
|
463
731
|
settings_repo_id = "einarolafsson/spacr_settings"
|
464
732
|
dataset_subfolder = "plate1"
|
@@ -629,6 +897,31 @@ def display_gif_in_plot_frame(gif_path, parent_frame):
|
|
629
897
|
update_frame(0)
|
630
898
|
|
631
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
|
+
"""
|
632
925
|
"""Display an MP4, AVI, or GIF and play it on repeat in the parent_frame, fully filling the frame while maintaining aspect ratio."""
|
633
926
|
# Clear parent_frame if it contains any previous widgets
|
634
927
|
for widget in parent_frame.winfo_children():
|
@@ -773,8 +1066,7 @@ def display_media_in_plot_frame(media_path, parent_frame):
|
|
773
1066
|
else:
|
774
1067
|
raise ValueError("Unsupported file format. Only .mp4, .avi, and .gif are supported.")
|
775
1068
|
|
776
|
-
def print_widget_structure(widget, indent=0):
|
777
|
-
"""Recursively print the widget structure."""
|
1069
|
+
def print_widget_structure(widget, indent=0):
|
778
1070
|
# Print the widget's name and class
|
779
1071
|
print(" " * indent + f"{widget}: {widget.winfo_class()}")
|
780
1072
|
|