spacr 0.3.1__py3-none-any.whl → 0.3.22__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 +19 -3
- spacr/cellpose.py +311 -0
- spacr/core.py +245 -2494
- spacr/deep_spacr.py +316 -48
- spacr/gui.py +1 -0
- spacr/gui_core.py +74 -63
- spacr/gui_elements.py +110 -5
- spacr/gui_utils.py +346 -6
- spacr/io.py +680 -141
- spacr/logger.py +28 -9
- spacr/measure.py +107 -95
- spacr/mediar.py +0 -3
- spacr/ml.py +1051 -0
- spacr/openai.py +37 -0
- spacr/plot.py +707 -20
- spacr/resources/data/lopit.csv +3833 -0
- spacr/resources/data/toxoplasma_metadata.csv +8843 -0
- spacr/resources/icons/convert.png +0 -0
- spacr/resources/{models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model → icons/dna_matrix.mp4} +0 -0
- spacr/sequencing.py +241 -1311
- spacr/settings.py +134 -47
- spacr/sim.py +0 -2
- spacr/submodules.py +349 -0
- spacr/timelapse.py +0 -2
- spacr/toxo.py +238 -0
- spacr/utils.py +419 -180
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/METADATA +31 -22
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/RECORD +32 -33
- spacr/chris.py +0 -50
- spacr/graph_learning.py +0 -340
- spacr/resources/MEDIAR/.git +0 -1
- spacr/resources/MEDIAR_weights/.DS_Store +0 -0
- spacr/resources/icons/.DS_Store +0 -0
- spacr/resources/icons/spacr_logo_rotation.gif +0 -0
- spacr/resources/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
- spacr/resources/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/sim_app.py +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/LICENSE +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/WHEEL +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/entry_points.txt +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/top_level.txt +0 -0
spacr/logger.py
CHANGED
@@ -1,20 +1,39 @@
|
|
1
1
|
import logging
|
2
2
|
import functools
|
3
|
+
import os
|
3
4
|
|
5
|
+
# Automatically configure logging
|
6
|
+
def configure_logger(log_file_name='spacr.log'):
|
7
|
+
# Determine a safe location for the log file
|
8
|
+
home_dir = os.path.expanduser("~") # Get the user's home directory
|
9
|
+
log_file_path = os.path.join(home_dir, log_file_name) # Save log file in home directory
|
10
|
+
|
11
|
+
# Setup logging configuration
|
12
|
+
logging.basicConfig(
|
13
|
+
filename=log_file_path,
|
14
|
+
level=logging.INFO,
|
15
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
16
|
+
)
|
17
|
+
|
18
|
+
# Call the function to configure the logger
|
19
|
+
configure_logger()
|
20
|
+
|
21
|
+
# Create a logger instance for this module
|
4
22
|
logger = logging.getLogger(__name__)
|
5
23
|
|
24
|
+
# Decorator to log function calls
|
6
25
|
def log_function_call(func):
|
7
26
|
@functools.wraps(func)
|
8
27
|
def wrapper(*args, **kwargs):
|
9
|
-
args_repr = [repr(a) for a in args]
|
10
|
-
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
|
11
|
-
signature = ", ".join(args_repr + kwargs_repr)
|
12
|
-
logger.info(f"Calling {func.__name__}({signature})")
|
28
|
+
args_repr = [repr(a) for a in args] # Arguments passed to the function
|
29
|
+
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] # Keyword arguments
|
30
|
+
signature = ", ".join(args_repr + kwargs_repr) # Construct the signature for logging
|
31
|
+
logger.info(f"Calling {func.__name__}({signature})") # Log function call
|
13
32
|
try:
|
14
|
-
result = func(*args, **kwargs)
|
15
|
-
logger.info(f"{func.__name__} returned {result!r}")
|
33
|
+
result = func(*args, **kwargs) # Execute the function
|
34
|
+
logger.info(f"{func.__name__} returned {result!r}") # Log the result
|
16
35
|
return result
|
17
36
|
except Exception as e:
|
18
|
-
logger.exception(f"Exception occurred in {func.__name__}")
|
19
|
-
raise
|
20
|
-
return wrapper
|
37
|
+
logger.exception(f"Exception occurred in {func.__name__}") # Log any exceptions
|
38
|
+
raise # Re-raise the exception after logging it
|
39
|
+
return wrapper
|
spacr/measure.py
CHANGED
@@ -16,8 +16,6 @@ from skimage.util import img_as_bool
|
|
16
16
|
import matplotlib.pyplot as plt
|
17
17
|
from math import ceil, sqrt
|
18
18
|
|
19
|
-
from .logger import log_function_call
|
20
|
-
|
21
19
|
def get_components(cell_mask, nucleus_mask, pathogen_mask):
|
22
20
|
"""
|
23
21
|
Get the components (nucleus and pathogens) for each cell in the given masks.
|
@@ -984,112 +982,126 @@ def measure_crop(settings):
|
|
984
982
|
None
|
985
983
|
"""
|
986
984
|
|
987
|
-
from .io import _save_settings_to_db
|
985
|
+
from .io import _save_settings_to_db
|
988
986
|
from .timelapse import _timelapse_masks_to_gif
|
989
987
|
from .utils import measure_test_mode, print_progress
|
990
988
|
from .settings import get_measure_crop_settings
|
991
989
|
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
src_fldr = settings['src']
|
996
|
-
if not os.path.basename(src_fldr).endswith('merged'):
|
997
|
-
print(f"WARNING: Source folder, settings: src: {src_fldr} should end with '/merged'")
|
998
|
-
src_fldr = os.path.join(src_fldr, 'merged')
|
999
|
-
print(f"Changed source folder to: {src_fldr}")
|
1000
|
-
|
1001
|
-
#if settings['save_measurements']:
|
1002
|
-
#source_folder = os.path.dirname(settings['src'])
|
1003
|
-
#os.makedirs(source_folder+'/measurements', exist_ok=True)
|
1004
|
-
#_create_database(source_folder+'/measurements/measurements.db')
|
990
|
+
if not isinstance(settings['src'], (str, list)):
|
991
|
+
ValueError(f'src must be a string or a list of strings')
|
992
|
+
return
|
1005
993
|
|
1006
|
-
if settings['
|
1007
|
-
settings['
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
994
|
+
if isinstance(settings['src'], str):
|
995
|
+
settings['src'] = [settings['src']]
|
996
|
+
|
997
|
+
if isinstance(settings['src'], list):
|
998
|
+
source_folders = settings['src']
|
999
|
+
for source_folder in source_folders:
|
1000
|
+
print(f'Processing folder: {source_folder}')
|
1001
|
+
settings['src'] = source_folder
|
1002
|
+
src = source_folder
|
1003
|
+
|
1004
|
+
settings = get_measure_crop_settings(settings)
|
1005
|
+
settings = measure_test_mode(settings)
|
1006
|
+
|
1007
|
+
src_fldr = settings['src']
|
1008
|
+
if not os.path.basename(src_fldr).endswith('merged'):
|
1009
|
+
print(f"WARNING: Source folder, settings: src: {src_fldr} should end with '/merged'")
|
1010
|
+
src_fldr = os.path.join(src_fldr, 'merged')
|
1011
|
+
print(f"Changed source folder to: {src_fldr}")
|
1012
|
+
|
1013
|
+
#if settings['save_measurements']:
|
1014
|
+
#source_folder = os.path.dirname(settings['src'])
|
1015
|
+
#os.makedirs(source_folder+'/measurements', exist_ok=True)
|
1016
|
+
#_create_database(source_folder+'/measurements/measurements.db')
|
1017
|
+
|
1018
|
+
if settings['cell_mask_dim'] is None:
|
1019
|
+
settings['include_uninfected'] = True
|
1020
|
+
if settings['pathogen_mask_dim'] is None:
|
1021
|
+
settings['include_uninfected'] = True
|
1022
|
+
if settings['cell_mask_dim'] is not None and settings['pathogen_min_size'] is not None:
|
1023
|
+
settings['cytoplasm'] = True
|
1024
|
+
elif settings['cell_mask_dim'] is not None and settings['nucleus_min_size'] is not None:
|
1025
|
+
settings['cytoplasm'] = True
|
1026
|
+
else:
|
1027
|
+
settings['cytoplasm'] = False
|
1030
1028
|
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
1029
|
+
spacr_cores = int(mp.cpu_count() - 6)
|
1030
|
+
if spacr_cores <= 2:
|
1031
|
+
spacr_cores = 1
|
1035
1032
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
print(f'WARNING: to notmalize single object pngs set normalize to a list of 2 integers, e.g. [1,99] (lower and upper percentiles)')
|
1040
|
-
return
|
1041
|
-
|
1042
|
-
if isinstance(settings['normalize'], list) or isinstance(settings['normalize'], bool) and settings['normalize']:
|
1043
|
-
if settings['normalize_by'] not in ['png', 'fov']:
|
1044
|
-
print("Warning: normalize_by should be either 'png' to notmalize each png to its own percentiles or 'fov' to normalize each png to the fov percentiles ")
|
1045
|
-
return
|
1033
|
+
if settings['n_jobs'] > spacr_cores:
|
1034
|
+
print(f'Warning reserving 6 CPU cores for other processes, setting n_jobs to {spacr_cores}')
|
1035
|
+
settings['n_jobs'] = spacr_cores
|
1046
1036
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1037
|
+
dirname = os.path.dirname(settings['src'])
|
1038
|
+
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
1039
|
+
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
1040
|
+
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
1041
|
+
settings_df.to_csv(settings_csv, index=False)
|
1050
1042
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1043
|
+
if settings['timelapse_objects'] == 'nucleus':
|
1044
|
+
if not settings['cell_mask_dim'] is None:
|
1045
|
+
tlo = settings['timelapse_objects']
|
1046
|
+
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
1054
1047
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
with mp.Pool(n_jobs) as pool:
|
1079
|
-
for index, file in enumerate(files):
|
1080
|
-
pool.apply_async(_measure_crop_core, args=(index, time_ls, file, settings), callback=job_callback)
|
1048
|
+
int_setting_keys = ['cell_mask_dim', 'nucleus_mask_dim', 'pathogen_mask_dim', 'cell_min_size', 'nucleus_min_size', 'pathogen_min_size', 'cytoplasm_min_size']
|
1049
|
+
|
1050
|
+
if isinstance(settings['normalize'], bool) and settings['normalize']:
|
1051
|
+
print(f'WARNING: to notmalize single object pngs set normalize to a list of 2 integers, e.g. [1,99] (lower and upper percentiles)')
|
1052
|
+
return
|
1053
|
+
|
1054
|
+
if isinstance(settings['normalize'], list) or isinstance(settings['normalize'], bool) and settings['normalize']:
|
1055
|
+
if settings['normalize_by'] not in ['png', 'fov']:
|
1056
|
+
print("Warning: normalize_by should be either 'png' to notmalize each png to its own percentiles or 'fov' to normalize each png to the fov percentiles ")
|
1057
|
+
return
|
1058
|
+
|
1059
|
+
if not all(isinstance(settings[key], int) or settings[key] is None for key in int_setting_keys):
|
1060
|
+
print(f"WARNING: {int_setting_keys} must all be integers")
|
1061
|
+
return
|
1062
|
+
|
1063
|
+
if not isinstance(settings['channels'], list):
|
1064
|
+
print(f"WARNING: channels should be a list of integers representing channels e.g. [0,1,2,3]")
|
1065
|
+
return
|
1066
|
+
|
1067
|
+
if not isinstance(settings['crop_mode'], list):
|
1068
|
+
print(f"WARNING: crop_mode should be a list with at least one element e.g. ['cell'] or ['cell','nucleus'] or [None]")
|
1069
|
+
return
|
1081
1070
|
|
1082
|
-
|
1083
|
-
|
1071
|
+
_save_settings_to_db(settings)
|
1072
|
+
files = [f for f in os.listdir(settings['src']) if f.endswith('.npy')]
|
1073
|
+
n_jobs = settings['n_jobs']
|
1074
|
+
print(f'using {n_jobs} cpu cores')
|
1075
|
+
print_progress(files_processed=0, files_to_process=len(files), n_jobs=n_jobs, time_ls=[], operation_type='Measure and Crop')
|
1076
|
+
|
1077
|
+
def job_callback(result):
|
1078
|
+
completed_jobs.add(result[0])
|
1079
|
+
process_meassure_crop_results([result], settings)
|
1080
|
+
files_processed = len(completed_jobs)
|
1081
|
+
files_to_process = len(files)
|
1082
|
+
print_progress(files_processed, files_to_process, n_jobs, time_ls=time_ls, operation_type='Measure and Crop')
|
1083
|
+
if files_processed >= files_to_process:
|
1084
|
+
pool.terminate()
|
1085
|
+
|
1086
|
+
with mp.Manager() as manager:
|
1087
|
+
time_ls = manager.list()
|
1088
|
+
completed_jobs = set() # Set to keep track of completed jobs
|
1089
|
+
|
1090
|
+
with mp.Pool(n_jobs) as pool:
|
1091
|
+
for index, file in enumerate(files):
|
1092
|
+
pool.apply_async(_measure_crop_core, args=(index, time_ls, file, settings), callback=job_callback)
|
1093
|
+
|
1094
|
+
pool.close()
|
1095
|
+
pool.join()
|
1084
1096
|
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1097
|
+
if settings['timelapse']:
|
1098
|
+
if settings['timelapse_objects'] == 'nucleus':
|
1099
|
+
folder_path = settings['src']
|
1100
|
+
mask_channels = [settings['nucleus_mask_dim'], settings['pathogen_mask_dim'], settings['cell_mask_dim']]
|
1101
|
+
object_types = ['nucleus', 'pathogen', 'cell']
|
1102
|
+
_timelapse_masks_to_gif(folder_path, mask_channels, object_types)
|
1091
1103
|
|
1092
|
-
|
1104
|
+
print("Successfully completed run")
|
1093
1105
|
|
1094
1106
|
def process_meassure_crop_results(partial_results, settings):
|
1095
1107
|
"""
|
spacr/mediar.py
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
import os, sys, gdown, cv2, torch
|
2
2
|
import numpy as np
|
3
3
|
import matplotlib.pyplot as plt
|
4
|
-
from monai.inferers import sliding_window_inference
|
5
4
|
import skimage.io as io
|
6
5
|
|
7
6
|
# Path to the MEDIAR directory
|
8
7
|
mediar_path = os.path.join(os.path.dirname(__file__), 'resources', 'MEDIAR')
|
9
8
|
|
10
|
-
print('mediar path', mediar_path)
|
11
|
-
|
12
9
|
# Temporarily create __init__.py to make MEDIAR a package
|
13
10
|
init_file = os.path.join(mediar_path, '__init__.py')
|
14
11
|
if not os.path.exists(init_file):
|