spacr 0.2.32__py3-none-any.whl → 0.2.45__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/core.py +46 -59
- spacr/gui.py +20 -38
- spacr/gui_core.py +389 -474
- spacr/gui_elements.py +198 -56
- spacr/gui_utils.py +315 -57
- spacr/io.py +29 -38
- spacr/measure.py +6 -9
- spacr/plot.py +106 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/settings.py +1 -1
- spacr/utils.py +4 -24
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/METADATA +4 -1
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/RECORD +19 -17
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/LICENSE +0 -0
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/WHEEL +0 -0
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.32.dist-info → spacr-0.2.45.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -1,48 +1,19 @@
|
|
1
|
-
import os, io, sys, ast, ctypes,
|
1
|
+
import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback
|
2
|
+
import traceback
|
2
3
|
import tkinter as tk
|
3
4
|
from tkinter import ttk
|
5
|
+
import matplotlib
|
6
|
+
import matplotlib.pyplot as plt
|
7
|
+
matplotlib.use('Agg')
|
8
|
+
from huggingface_hub import list_repo_files
|
4
9
|
|
5
10
|
from . gui_core import initiate_root
|
6
|
-
from .gui_elements import
|
11
|
+
from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
|
7
12
|
|
8
13
|
try:
|
9
14
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
10
15
|
except AttributeError:
|
11
16
|
pass
|
12
|
-
|
13
|
-
def proceed_with_app_v1(root, app_name, app_func):
|
14
|
-
from .gui import gui_app
|
15
|
-
|
16
|
-
# Clear the current content frame
|
17
|
-
if hasattr(root, 'content_frame'):
|
18
|
-
for widget in root.content_frame.winfo_children():
|
19
|
-
try:
|
20
|
-
widget.destroy()
|
21
|
-
except tk.TclError as e:
|
22
|
-
print(f"Error destroying widget: {e}")
|
23
|
-
else:
|
24
|
-
root.content_frame = tk.Frame(root)
|
25
|
-
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
26
|
-
root.grid_rowconfigure(1, weight=1)
|
27
|
-
root.grid_columnconfigure(0, weight=1)
|
28
|
-
|
29
|
-
# Initialize the new app in the content frame
|
30
|
-
if app_name == "Mask":
|
31
|
-
initiate_root(root.content_frame, 'mask')
|
32
|
-
elif app_name == "Measure":
|
33
|
-
initiate_root(root.content_frame, 'measure')
|
34
|
-
elif app_name == "Classify":
|
35
|
-
initiate_root(root.content_frame, 'classify')
|
36
|
-
elif app_name == "Sequencing":
|
37
|
-
initiate_root(root.content_frame, 'sequencing')
|
38
|
-
elif app_name == "Umap":
|
39
|
-
initiate_root(root.content_frame, 'umap')
|
40
|
-
elif app_name == "Annotate":
|
41
|
-
initiate_root(root.content_frame, 'annotate')
|
42
|
-
elif app_name == "Make Masks":
|
43
|
-
initiate_root(root.content_frame, 'make_masks')
|
44
|
-
else:
|
45
|
-
raise ValueError(f"Invalid app name: {app_name}")
|
46
17
|
|
47
18
|
def proceed_with_app(root, app_name, app_func):
|
48
19
|
# Clear the current content frame
|
@@ -93,19 +64,25 @@ def parse_list(value):
|
|
93
64
|
|
94
65
|
# Usage example in your create_input_field function
|
95
66
|
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
67
|
+
from .gui_elements import set_dark_style
|
96
68
|
label_column = 0
|
97
69
|
widget_column = 1
|
98
70
|
|
71
|
+
style_out = set_dark_style(ttk.Style())
|
72
|
+
|
73
|
+
# Replace underscores with spaces and capitalize the first letter
|
74
|
+
label_text = label_text.replace('_', ' ').capitalize()
|
75
|
+
|
99
76
|
# Configure the column widths
|
100
77
|
frame.grid_columnconfigure(label_column, weight=0) # Allow the label column to expand
|
101
78
|
frame.grid_columnconfigure(widget_column, weight=1) # Allow the widget column to expand
|
102
79
|
|
103
|
-
#
|
104
|
-
label = ttk.Label(frame, text=label_text, background=
|
105
|
-
label.grid(column=label_column, row=row, sticky=tk.E, padx=(5, 2), pady=5)
|
80
|
+
# Create and configure the label
|
81
|
+
label = ttk.Label(frame, text=label_text, background=style_out['bg_color'], foreground=style_out['fg_color'], font=(style_out['font_family'], style_out['font_size']), anchor='e', justify='right')
|
82
|
+
label.grid(column=label_column, row=row, sticky=tk.E, padx=(5, 2), pady=5)
|
106
83
|
|
107
84
|
if var_type == 'entry':
|
108
|
-
var = tk.StringVar(value=default_value)
|
85
|
+
var = tk.StringVar(value=default_value)
|
109
86
|
entry = spacrEntry(frame, textvariable=var, outline=False)
|
110
87
|
entry.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
|
111
88
|
return (label, entry, var) # Return both the label and the entry, and the variable
|
@@ -157,23 +134,6 @@ def main_thread_update_function(root, q, fig_queue, canvas_widget):
|
|
157
134
|
#ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
158
135
|
while not q.empty():
|
159
136
|
message = q.get_nowait()
|
160
|
-
#clean_message = ansi_escape_pattern.sub('', message)
|
161
|
-
#if clean_message.startswith("Progress"):
|
162
|
-
# progress_label.config(text=clean_message)
|
163
|
-
#if clean_message.startswith("\rProgress"):
|
164
|
-
# progress_label.config(text=clean_message)
|
165
|
-
#elif clean_message.startswith("Successfully"):
|
166
|
-
# progress_label.config(text=clean_message)
|
167
|
-
#elif clean_message.startswith("Processing"):
|
168
|
-
# progress_label.config(text=clean_message)
|
169
|
-
#elif clean_message.startswith("scale"):
|
170
|
-
# pass
|
171
|
-
#elif clean_message.startswith("plot_cropped_arrays"):
|
172
|
-
# pass
|
173
|
-
#elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
|
174
|
-
# pass
|
175
|
-
#else:
|
176
|
-
# print(clean_message)
|
177
137
|
except Exception as e:
|
178
138
|
print(f"Error updating GUI canvas: {e}")
|
179
139
|
finally:
|
@@ -349,4 +309,302 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
|
|
349
309
|
# Call load_images after setting up the root window
|
350
310
|
app.load_images()
|
351
311
|
|
312
|
+
def set_element_size(widget):
|
313
|
+
screen_width = widget.winfo_screenwidth()
|
314
|
+
screen_height = widget.winfo_screenheight()
|
315
|
+
btn_size = screen_width // 40
|
316
|
+
bar_size = screen_width // 50
|
317
|
+
settings_width = screen_width // 6
|
318
|
+
panel_height = screen_height // 12
|
319
|
+
panel_width = settings_width
|
320
|
+
size_dict = {
|
321
|
+
'btn_size': btn_size,
|
322
|
+
'bar_size': bar_size,
|
323
|
+
'settings_width': settings_width,
|
324
|
+
'panel_width': panel_width,
|
325
|
+
'panel_height': panel_height
|
326
|
+
}
|
327
|
+
return size_dict
|
328
|
+
|
329
|
+
def convert_settings_dict_for_gui(settings):
|
330
|
+
from torchvision import models as torch_models
|
331
|
+
torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
|
332
|
+
chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
|
333
|
+
chans_v2 = [0, 1, 2, 3, None]
|
334
|
+
variables = {}
|
335
|
+
special_cases = {
|
336
|
+
'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
|
337
|
+
'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
338
|
+
'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
339
|
+
'cell_mask_dim': ('combo', chans, None),
|
340
|
+
'cell_chann_dim': ('combo', chans, None),
|
341
|
+
'nucleus_mask_dim': ('combo', chans, None),
|
342
|
+
'nucleus_chann_dim': ('combo', chans, None),
|
343
|
+
'pathogen_mask_dim': ('combo', chans, None),
|
344
|
+
'pathogen_chann_dim': ('combo', chans, None),
|
345
|
+
'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
|
346
|
+
'magnification': ('combo', [20, 40, 60], 20),
|
347
|
+
'nucleus_channel': ('combo', chans_v2, None),
|
348
|
+
'cell_channel': ('combo', chans_v2, None),
|
349
|
+
'channel_of_interest': ('combo', chans_v2, None),
|
350
|
+
'pathogen_channel': ('combo', chans_v2, None),
|
351
|
+
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
352
|
+
'train_mode': ('combo', ['erm', 'irm'], 'erm'),
|
353
|
+
'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
|
354
|
+
'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
|
355
|
+
'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
|
356
|
+
'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
|
357
|
+
'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
|
358
|
+
'model_type': ('combo', torchvision_models, 'resnet50'),
|
359
|
+
'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
|
360
|
+
'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
|
361
|
+
'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
|
362
|
+
'normalize_by': ('combo', ['fov', 'png'], 'png'),
|
363
|
+
'agg_type': ('combo', ['mean', 'median'], 'mean'),
|
364
|
+
'grouping': ('combo', ['mean', 'median'], 'mean'),
|
365
|
+
'min_max': ('combo', ['allq', 'all'], 'allq'),
|
366
|
+
'transform': ('combo', ['log', 'sqrt', 'square', None], None)
|
367
|
+
}
|
368
|
+
|
369
|
+
for key, value in settings.items():
|
370
|
+
if key in special_cases:
|
371
|
+
variables[key] = special_cases[key]
|
372
|
+
elif isinstance(value, bool):
|
373
|
+
variables[key] = ('check', None, value)
|
374
|
+
elif isinstance(value, int) or isinstance(value, float):
|
375
|
+
variables[key] = ('entry', None, value)
|
376
|
+
elif isinstance(value, str):
|
377
|
+
variables[key] = ('entry', None, value)
|
378
|
+
elif value is None:
|
379
|
+
variables[key] = ('entry', None, value)
|
380
|
+
elif isinstance(value, list):
|
381
|
+
variables[key] = ('entry', None, str(value))
|
382
|
+
else:
|
383
|
+
variables[key] = ('entry', None, str(value))
|
384
|
+
return variables
|
385
|
+
|
386
|
+
def spacrFigShow(fig_queue=None):
|
387
|
+
"""
|
388
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
389
|
+
"""
|
390
|
+
fig = plt.gcf()
|
391
|
+
if fig_queue:
|
392
|
+
fig_queue.put(fig)
|
393
|
+
else:
|
394
|
+
fig.show()
|
395
|
+
plt.close(fig)
|
396
|
+
|
397
|
+
def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
|
352
398
|
|
399
|
+
"""
|
400
|
+
Wraps the run_multiple_simulations function to integrate with GUI processes.
|
401
|
+
|
402
|
+
Parameters:
|
403
|
+
- settings: dict, The settings for the run_multiple_simulations function.
|
404
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
405
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
406
|
+
"""
|
407
|
+
|
408
|
+
# Temporarily override plt.show
|
409
|
+
original_show = plt.show
|
410
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
411
|
+
|
412
|
+
try:
|
413
|
+
if imports == 1:
|
414
|
+
function(settings=settings)
|
415
|
+
elif imports == 2:
|
416
|
+
function(src=settings['src'], settings=settings)
|
417
|
+
except Exception as e:
|
418
|
+
# Send the error message to the GUI via the queue
|
419
|
+
errorMessage = f"Error during processing: {e}"
|
420
|
+
q.put(errorMessage)
|
421
|
+
traceback.print_exc()
|
422
|
+
finally:
|
423
|
+
# Restore the original plt.show function
|
424
|
+
plt.show = original_show
|
425
|
+
|
426
|
+
def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
427
|
+
from .gui_utils import process_stdout_stderr
|
428
|
+
from .core import preprocess_generate_masks, generate_ml_scores, identify_masks_finetune, check_cellpose_models, analyze_recruitment, train_cellpose, compare_cellpose_masks, analyze_plaques, generate_dataset, apply_model_to_tar
|
429
|
+
from .io import generate_cellpose_train_test
|
430
|
+
from .measure import measure_crop
|
431
|
+
from .sim import run_multiple_simulations
|
432
|
+
from .deep_spacr import train_test_model
|
433
|
+
from .sequencing import analyze_reads, map_barcodes_folder, perform_regression
|
434
|
+
process_stdout_stderr(q)
|
435
|
+
|
436
|
+
print(f'run_function_gui settings_type: {settings_type}')
|
437
|
+
|
438
|
+
if settings_type == 'mask':
|
439
|
+
function = preprocess_generate_masks
|
440
|
+
imports = 2
|
441
|
+
elif settings_type == 'measure':
|
442
|
+
function = measure_crop
|
443
|
+
imports = 1
|
444
|
+
elif settings_type == 'simulation':
|
445
|
+
function = run_multiple_simulations
|
446
|
+
imports = 1
|
447
|
+
elif settings_type == 'sequencing':
|
448
|
+
function = analyze_reads
|
449
|
+
imports = 1
|
450
|
+
elif settings_type == 'classify':
|
451
|
+
function = train_test_model
|
452
|
+
imports = 2
|
453
|
+
elif settings_type == 'train_cellpose':
|
454
|
+
function = train_cellpose
|
455
|
+
imports = 1
|
456
|
+
elif settings_type == 'ml_analyze':
|
457
|
+
function = generate_ml_scores
|
458
|
+
imports = 2
|
459
|
+
elif settings_type == 'cellpose_masks':
|
460
|
+
function = identify_masks_finetune
|
461
|
+
imports = 1
|
462
|
+
elif settings_type == 'cellpose_all':
|
463
|
+
function = check_cellpose_models
|
464
|
+
imports = 1
|
465
|
+
elif settings_type == 'map_barcodes':
|
466
|
+
function = map_barcodes_folder
|
467
|
+
imports = 2
|
468
|
+
elif settings_type == 'regression':
|
469
|
+
function = perform_regression
|
470
|
+
imports = 2
|
471
|
+
elif settings_type == 'recruitment':
|
472
|
+
function = analyze_recruitment
|
473
|
+
imports = 2
|
474
|
+
else:
|
475
|
+
raise ValueError(f"Invalid settings type: {settings_type}")
|
476
|
+
try:
|
477
|
+
function_gui_wrapper(function, settings, q, fig_queue, imports)
|
478
|
+
except Exception as e:
|
479
|
+
q.put(f"Error during processing: {e}")
|
480
|
+
traceback.print_exc()
|
481
|
+
finally:
|
482
|
+
stop_requested.value = 1
|
483
|
+
|
484
|
+
def hide_all_settings(vars_dict, categories):
|
485
|
+
"""
|
486
|
+
Function to initially hide all settings in the GUI.
|
487
|
+
|
488
|
+
Parameters:
|
489
|
+
- categories: dict, The categories of settings with their corresponding settings.
|
490
|
+
- vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
|
491
|
+
"""
|
492
|
+
|
493
|
+
if categories is None:
|
494
|
+
from .settings import categories
|
495
|
+
|
496
|
+
for category, settings in categories.items():
|
497
|
+
if any(setting in vars_dict for setting in settings):
|
498
|
+
vars_dict[category] = (None, None, tk.IntVar(value=0))
|
499
|
+
|
500
|
+
# Initially hide all settings
|
501
|
+
for setting in settings:
|
502
|
+
if setting in vars_dict:
|
503
|
+
label, widget, _ = vars_dict[setting]
|
504
|
+
label.grid_remove()
|
505
|
+
widget.grid_remove()
|
506
|
+
return vars_dict
|
507
|
+
|
508
|
+
def setup_frame(parent_frame):
|
509
|
+
from .gui_elements import set_dark_style, set_default_font
|
510
|
+
style = ttk.Style(parent_frame)
|
511
|
+
size_dict = set_element_size(parent_frame)
|
512
|
+
|
513
|
+
settings_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, width=size_dict['settings_width'])
|
514
|
+
vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL)
|
515
|
+
horizontal_container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, height=size_dict['panel_height'])
|
516
|
+
|
517
|
+
parent_frame.grid_rowconfigure(0, weight=1)
|
518
|
+
parent_frame.grid_rowconfigure(1, weight=0)
|
519
|
+
parent_frame.grid_columnconfigure(0, weight=0) # Change this line
|
520
|
+
parent_frame.grid_columnconfigure(1, weight=1) # Change this line
|
521
|
+
|
522
|
+
settings_container.grid(row=0, column=0, rowspan=2, sticky="nsew") # Change this line
|
523
|
+
vertical_container.grid(row=0, column=1, sticky="nsew") # Change this line
|
524
|
+
horizontal_container.grid(row=1, column=1, sticky="ew") # Change this line
|
525
|
+
|
526
|
+
set_dark_style(style, parent_frame, [settings_container, vertical_container, horizontal_container])
|
527
|
+
set_default_font(parent_frame, font_name="Helvetica", size=8)
|
528
|
+
|
529
|
+
return parent_frame, vertical_container, horizontal_container, settings_container
|
530
|
+
|
531
|
+
def download_hug_dataset(q, vars_dict):
|
532
|
+
dataset_repo_id = "einarolafsson/toxo_mito"
|
533
|
+
settings_repo_id = "einarolafsson/spacr_settings"
|
534
|
+
dataset_subfolder = "plate1"
|
535
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
|
536
|
+
|
537
|
+
# Download the dataset
|
538
|
+
try:
|
539
|
+
dataset_path = download_dataset(q, dataset_repo_id, dataset_subfolder, local_dir)
|
540
|
+
if 'src' in vars_dict:
|
541
|
+
vars_dict['src'][2].set(dataset_path)
|
542
|
+
q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
|
543
|
+
q.put(f"Dataset downloaded to: {dataset_path}\n")
|
544
|
+
except Exception as e:
|
545
|
+
q.put(f"Failed to download dataset: {e}\n")
|
546
|
+
|
547
|
+
# Download the settings files
|
548
|
+
try:
|
549
|
+
settings_path = download_dataset(q, settings_repo_id, "", local_dir)
|
550
|
+
q.put(f"Settings downloaded to: {settings_path}\n")
|
551
|
+
except Exception as e:
|
552
|
+
q.put(f"Failed to download settings: {e}\n")
|
553
|
+
|
554
|
+
def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
|
555
|
+
"""
|
556
|
+
Downloads a dataset or settings files from Hugging Face and returns the local path.
|
557
|
+
|
558
|
+
Args:
|
559
|
+
repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
|
560
|
+
subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
|
561
|
+
local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
|
562
|
+
retries (int): Number of retry attempts in case of failure.
|
563
|
+
delay (int): Delay in seconds between retries.
|
564
|
+
|
565
|
+
Returns:
|
566
|
+
str: The local path to the downloaded files.
|
567
|
+
"""
|
568
|
+
if local_dir is None:
|
569
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets")
|
570
|
+
|
571
|
+
local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
|
572
|
+
if not os.path.exists(local_subfolder_dir):
|
573
|
+
os.makedirs(local_subfolder_dir)
|
574
|
+
elif len(os.listdir(local_subfolder_dir)) > 0:
|
575
|
+
q.put(f"Files already downloaded to: {local_subfolder_dir}")
|
576
|
+
return local_subfolder_dir
|
577
|
+
|
578
|
+
attempt = 0
|
579
|
+
while attempt < retries:
|
580
|
+
try:
|
581
|
+
files = list_repo_files(repo_id, repo_type="dataset")
|
582
|
+
subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
|
583
|
+
|
584
|
+
for file_name in subfolder_files:
|
585
|
+
for download_attempt in range(retries):
|
586
|
+
try:
|
587
|
+
url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
|
588
|
+
response = requests.get(url, stream=True)
|
589
|
+
response.raise_for_status()
|
590
|
+
|
591
|
+
local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
|
592
|
+
with open(local_file_path, 'wb') as file:
|
593
|
+
for chunk in response.iter_content(chunk_size=8192):
|
594
|
+
file.write(chunk)
|
595
|
+
q.put(f"Downloaded file: {file_name}")
|
596
|
+
break
|
597
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
598
|
+
q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
|
599
|
+
time.sleep(delay)
|
600
|
+
else:
|
601
|
+
raise Exception(f"Failed to download {file_name} after multiple attempts.")
|
602
|
+
|
603
|
+
return local_subfolder_dir
|
604
|
+
|
605
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
606
|
+
q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
|
607
|
+
attempt += 1
|
608
|
+
time.sleep(delay)
|
609
|
+
|
610
|
+
raise Exception("Failed to download files after multiple attempts.")
|
spacr/io.py
CHANGED
@@ -588,20 +588,21 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
588
588
|
regular_expression = re.compile(regex)
|
589
589
|
images_by_key = defaultdict(list)
|
590
590
|
stack_path = os.path.join(src, 'stack')
|
591
|
+
files_processed = 0
|
591
592
|
if not os.path.exists(stack_path) or (os.path.isdir(stack_path) and len(os.listdir(stack_path)) == 0):
|
592
593
|
all_filenames = [filename for filename in os.listdir(src) if filename.endswith(img_format)]
|
593
|
-
print(f'All_files:{len(all_filenames)} in {src}')
|
594
|
+
print(f'All_files: {len(all_filenames)} in {src}')
|
594
595
|
time_ls = []
|
595
|
-
|
596
|
+
|
596
597
|
for i in range(0, len(all_filenames), batch_size):
|
597
598
|
start = time.time()
|
598
599
|
batch_filenames = all_filenames[i:i+batch_size]
|
599
|
-
|
600
|
+
files_processed = 0
|
600
601
|
for filename in batch_filenames:
|
601
602
|
images_by_key = _extract_filename_metadata(batch_filenames, src, images_by_key, regular_expression, metadata_type, pick_slice, skip_mode)
|
602
|
-
|
603
|
+
|
603
604
|
if pick_slice:
|
604
|
-
for key in images_by_key:
|
605
|
+
for i, key in enumerate(images_by_key):
|
605
606
|
plate, well, field, channel, mode = key
|
606
607
|
max_intensity_slice = max(images_by_key[key], key=lambda x: np.percentile(x, 90))
|
607
608
|
mip_image = Image.fromarray(max_intensity_slice)
|
@@ -614,16 +615,8 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
614
615
|
print(f'WARNING: A file with the same name already exists at location {output_filename}')
|
615
616
|
else:
|
616
617
|
mip_image.save(output_path)
|
617
|
-
|
618
|
-
stop = time.time()
|
619
|
-
duration = stop - start
|
620
|
-
time_ls.append(duration)
|
621
|
-
files_processed = processed
|
622
|
-
files_to_process = len(all_filenames)
|
623
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=batch_size, operation_type='Preprocessing filenames')
|
624
|
-
|
625
618
|
else:
|
626
|
-
for key, images in images_by_key.items():
|
619
|
+
for i, (key, images) in enumerate(images_by_key.items()):
|
627
620
|
mip = np.max(np.stack(images), axis=0)
|
628
621
|
mip_image = Image.fromarray(mip)
|
629
622
|
plate, well, field, channel = key[:4]
|
@@ -636,14 +629,13 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
636
629
|
print(f'WARNING: A file with the same name already exists at location {output_filename}')
|
637
630
|
else:
|
638
631
|
mip_image.save(output_path)
|
639
|
-
stop = time.time()
|
640
|
-
duration = stop - start
|
641
|
-
time_ls.append(duration)
|
642
|
-
files_processed = processed
|
643
|
-
files_to_process = len(all_filenames)
|
644
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=batch_size, operation_type='Preprocessing filenames')
|
645
|
-
|
646
632
|
images_by_key.clear()
|
633
|
+
stop = time.time()
|
634
|
+
duration = stop - start
|
635
|
+
time_ls.append(duration)
|
636
|
+
files_processed += len(batch_filenames)
|
637
|
+
files_to_process = len(all_filenames)
|
638
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=batch_size, operation_type='Preprocessing filenames')
|
647
639
|
|
648
640
|
# Move original images to a new directory
|
649
641
|
valid_exts = [img_format]
|
@@ -656,6 +648,7 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
656
648
|
print(f'WARNING: A file with the same name already exists at location {move}')
|
657
649
|
else:
|
658
650
|
shutil.move(os.path.join(src, filename), move)
|
651
|
+
files_processed = 0
|
659
652
|
return
|
660
653
|
|
661
654
|
def _merge_file(chan_dirs, stack_dir, file_name):
|
@@ -975,7 +968,7 @@ def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_s
|
|
975
968
|
time_ls.append(duration)
|
976
969
|
files_processed = i+1
|
977
970
|
files_to_process = time_stack_path_lists
|
978
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
971
|
+
#print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
979
972
|
stack = np.stack(stack_region)
|
980
973
|
save_loc = os.path.join(channel_stack_loc, f'{name}.npz')
|
981
974
|
np.savez(save_loc, data=stack, filenames=filenames_region)
|
@@ -1104,7 +1097,7 @@ def _normalize_img_batch(stack, channels, save_dtype, settings):
|
|
1104
1097
|
time_ls.append(duration)
|
1105
1098
|
files_processed = i+1
|
1106
1099
|
files_to_process = len(channels)
|
1107
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type=f"Normalizing: Channel: {channel}")
|
1100
|
+
#print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type=f"Normalizing: Channel: {channel}")
|
1108
1101
|
|
1109
1102
|
return normalized_stack.astype(save_dtype)
|
1110
1103
|
|
@@ -1191,7 +1184,6 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1191
1184
|
for i, path in enumerate(paths):
|
1192
1185
|
start = time.time()
|
1193
1186
|
array = np.load(path)
|
1194
|
-
#array = np.take(array, channels, axis=2)
|
1195
1187
|
stack_ls.append(array)
|
1196
1188
|
filenames_batch.append(os.path.basename(path))
|
1197
1189
|
stop = time.time()
|
@@ -1199,7 +1191,7 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1199
1191
|
time_ls.append(duration)
|
1200
1192
|
files_processed = i+1
|
1201
1193
|
files_to_process = nr_files
|
1202
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=
|
1194
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Concatinating")
|
1203
1195
|
|
1204
1196
|
if (i + 1) % settings['batch_size'] == 0 or i + 1 == nr_files:
|
1205
1197
|
unique_shapes = {arr.shape[:-1] for arr in stack_ls}
|
@@ -1350,12 +1342,12 @@ def _normalize_stack(src, backgrounds=[100, 100, 100], remove_backgrounds=[False
|
|
1350
1342
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
1351
1343
|
print(f'channels:{chan_index}/{stack.shape[-1] - 1}, arrays:{array_index + 1}/{single_channel.shape[0]}, Signal:{upper:.1f}, noise:{lower:.1f}, Signal-to-noise:{average_stnr:.1f}, Time/channel:{average_time:.2f}sec')
|
1352
1344
|
|
1353
|
-
stop = time.time()
|
1354
|
-
duration = stop - start
|
1355
|
-
time_ls.append(duration)
|
1356
|
-
files_processed = file_index + 1
|
1357
|
-
files_to_process = len(paths)
|
1358
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1345
|
+
#stop = time.time()
|
1346
|
+
#duration = stop - start
|
1347
|
+
#time_ls.append(duration)
|
1348
|
+
#files_processed = file_index + 1
|
1349
|
+
#files_to_process = len(paths)
|
1350
|
+
#print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1359
1351
|
|
1360
1352
|
normalized_stack[:, :, :, channel] = arr_2d_normalized
|
1361
1353
|
|
@@ -1405,12 +1397,12 @@ def _normalize_timelapse(src, lower_percentile=2, save_dtype=np.float32):
|
|
1405
1397
|
|
1406
1398
|
print(f'channels:{chan_index+1}/{stack.shape[-1]}, arrays:{array_index+1}/{single_channel.shape[0]}', end='\r')
|
1407
1399
|
|
1408
|
-
stop = time.time()
|
1409
|
-
duration = stop - start
|
1410
|
-
time_ls.append(duration)
|
1411
|
-
files_processed = file_index+1
|
1412
|
-
files_to_process = len(paths)
|
1413
|
-
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1400
|
+
#stop = time.time()
|
1401
|
+
#duration = stop - start
|
1402
|
+
#time_ls.append(duration)
|
1403
|
+
#files_processed = file_index+1
|
1404
|
+
#files_to_process = len(paths)
|
1405
|
+
#print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1414
1406
|
|
1415
1407
|
save_loc = os.path.join(output_fldr, f'{name}_norm_timelapse.npz')
|
1416
1408
|
np.savez(save_loc, data=normalized_stack, filenames=filenames)
|
@@ -2550,7 +2542,6 @@ def _read_mask(mask_path):
|
|
2550
2542
|
mask = img_as_uint(mask)
|
2551
2543
|
return mask
|
2552
2544
|
|
2553
|
-
|
2554
2545
|
def convert_numpy_to_tiff(folder_path, limit=None):
|
2555
2546
|
"""
|
2556
2547
|
Converts all numpy files in a folder to TIFF format and saves them in a subdirectory 'tiff'.
|
spacr/measure.py
CHANGED
@@ -941,14 +941,11 @@ def measure_crop(settings):
|
|
941
941
|
settings = get_measure_crop_settings(settings)
|
942
942
|
settings = measure_test_mode(settings)
|
943
943
|
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
#if not os.path.exists(settings['src']):
|
950
|
-
# print(f'src: {settings["src"]} does not exist')
|
951
|
-
# return
|
944
|
+
src_fldr = settings['src']
|
945
|
+
if not os.path.basename(src_fldr).endswith('merged'):
|
946
|
+
print(f"WARNING: Source folder, settings: src: {src_fldr} should end with 'merged'")
|
947
|
+
src_fldr = os.path.join(src_fldr, 'merged')
|
948
|
+
print(f"Changed source folder to: {src_fldr}")
|
952
949
|
|
953
950
|
if settings['cell_mask_dim'] is None:
|
954
951
|
settings['include_uninfected'] = True
|
@@ -1009,7 +1006,7 @@ def measure_crop(settings):
|
|
1009
1006
|
time.sleep(1)
|
1010
1007
|
files_processed = len(time_ls)
|
1011
1008
|
files_to_process = len(files)
|
1012
|
-
print_progress(files_processed, files_to_process, n_jobs, time_ls=
|
1009
|
+
print_progress(files_processed, files_to_process, n_jobs, time_ls=time_ls, operation_type='Measure and Crop')
|
1013
1010
|
result.get()
|
1014
1011
|
|
1015
1012
|
if settings['representative_images']:
|