spacr 0.3.1__py3-none-any.whl → 0.3.3__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 +335 -163
- spacr/gui.py +2 -0
- spacr/gui_core.py +85 -65
- spacr/gui_elements.py +110 -5
- spacr/gui_utils.py +375 -7
- spacr/io.py +680 -141
- spacr/logger.py +28 -9
- spacr/measure.py +108 -133
- 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 +181 -50
- spacr/sim.py +0 -2
- spacr/submodules.py +349 -0
- spacr/timelapse.py +0 -2
- spacr/toxo.py +238 -0
- spacr/utils.py +776 -182
- {spacr-0.3.1.dist-info → spacr-0.3.3.dist-info}/METADATA +31 -22
- {spacr-0.3.1.dist-info → spacr-0.3.3.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.3.dist-info}/LICENSE +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.3.dist-info}/WHEEL +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.3.dist-info}/entry_points.txt +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.3.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.
|
@@ -654,43 +652,6 @@ def img_list_to_grid(grid, titles=None):
|
|
654
652
|
plt.tight_layout(pad=0.1)
|
655
653
|
return fig
|
656
654
|
|
657
|
-
def filepaths_to_database(img_paths, settings, source_folder, crop_mode):
|
658
|
-
from. utils import _map_wells_png
|
659
|
-
png_df = pd.DataFrame(img_paths, columns=['png_path'])
|
660
|
-
|
661
|
-
png_df['file_name'] = png_df['png_path'].apply(lambda x: os.path.basename(x))
|
662
|
-
|
663
|
-
parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=settings['timelapse'])))
|
664
|
-
|
665
|
-
columns = ['plate', 'row', 'col', 'field']
|
666
|
-
|
667
|
-
if settings['timelapse']:
|
668
|
-
columns = columns + ['time_id']
|
669
|
-
|
670
|
-
columns = columns + ['prcfo']
|
671
|
-
|
672
|
-
if crop_mode == 'cell':
|
673
|
-
columns = columns + ['cell_id']
|
674
|
-
|
675
|
-
if crop_mode == 'nucleus':
|
676
|
-
columns = columns + ['nucleus_id']
|
677
|
-
|
678
|
-
if crop_mode == 'pathogen':
|
679
|
-
columns = columns + ['pathogen_id']
|
680
|
-
|
681
|
-
if crop_mode == 'cytoplasm':
|
682
|
-
columns = columns + ['cytoplasm_id']
|
683
|
-
|
684
|
-
png_df[columns] = parts
|
685
|
-
|
686
|
-
try:
|
687
|
-
conn = sqlite3.connect(f'{source_folder}/measurements/measurements.db', timeout=5)
|
688
|
-
png_df.to_sql('png_list', conn, if_exists='append', index=False)
|
689
|
-
conn.commit()
|
690
|
-
except sqlite3.OperationalError as e:
|
691
|
-
print(f"SQLite error: {e}", flush=True)
|
692
|
-
traceback.print_exc()
|
693
|
-
|
694
655
|
#@log_function_call
|
695
656
|
def _measure_crop_core(index, time_ls, file, settings):
|
696
657
|
|
@@ -713,7 +674,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
713
674
|
"""
|
714
675
|
|
715
676
|
from .plot import _plot_cropped_arrays
|
716
|
-
from .utils import _merge_overlapping_objects, _filter_object, _relabel_parent_with_child_labels, _exclude_objects, normalize_to_dtype
|
677
|
+
from .utils import _merge_overlapping_objects, _filter_object, _relabel_parent_with_child_labels, _exclude_objects, normalize_to_dtype, filepaths_to_database
|
717
678
|
from .utils import _merge_and_save_to_database, _crop_center, _find_bounding_box, _generate_names, _get_percentiles
|
718
679
|
|
719
680
|
figs = {}
|
@@ -984,112 +945,126 @@ def measure_crop(settings):
|
|
984
945
|
None
|
985
946
|
"""
|
986
947
|
|
987
|
-
from .io import _save_settings_to_db
|
948
|
+
from .io import _save_settings_to_db
|
988
949
|
from .timelapse import _timelapse_masks_to_gif
|
989
950
|
from .utils import measure_test_mode, print_progress
|
990
951
|
from .settings import get_measure_crop_settings
|
991
952
|
|
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')
|
953
|
+
if not isinstance(settings['src'], (str, list)):
|
954
|
+
ValueError(f'src must be a string or a list of strings')
|
955
|
+
return
|
1005
956
|
|
1006
|
-
if settings['
|
1007
|
-
settings['
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
957
|
+
if isinstance(settings['src'], str):
|
958
|
+
settings['src'] = [settings['src']]
|
959
|
+
|
960
|
+
if isinstance(settings['src'], list):
|
961
|
+
source_folders = settings['src']
|
962
|
+
for source_folder in source_folders:
|
963
|
+
print(f'Processing folder: {source_folder}')
|
964
|
+
settings['src'] = source_folder
|
965
|
+
src = source_folder
|
966
|
+
|
967
|
+
settings = get_measure_crop_settings(settings)
|
968
|
+
settings = measure_test_mode(settings)
|
969
|
+
|
970
|
+
src_fldr = settings['src']
|
971
|
+
if not os.path.basename(src_fldr).endswith('merged'):
|
972
|
+
print(f"WARNING: Source folder, settings: src: {src_fldr} should end with '/merged'")
|
973
|
+
src_fldr = os.path.join(src_fldr, 'merged')
|
974
|
+
print(f"Changed source folder to: {src_fldr}")
|
975
|
+
|
976
|
+
#if settings['save_measurements']:
|
977
|
+
#source_folder = os.path.dirname(settings['src'])
|
978
|
+
#os.makedirs(source_folder+'/measurements', exist_ok=True)
|
979
|
+
#_create_database(source_folder+'/measurements/measurements.db')
|
980
|
+
|
981
|
+
if settings['cell_mask_dim'] is None:
|
982
|
+
settings['include_uninfected'] = True
|
983
|
+
if settings['pathogen_mask_dim'] is None:
|
984
|
+
settings['include_uninfected'] = True
|
985
|
+
if settings['cell_mask_dim'] is not None and settings['pathogen_min_size'] is not None:
|
986
|
+
settings['cytoplasm'] = True
|
987
|
+
elif settings['cell_mask_dim'] is not None and settings['nucleus_min_size'] is not None:
|
988
|
+
settings['cytoplasm'] = True
|
989
|
+
else:
|
990
|
+
settings['cytoplasm'] = False
|
1024
991
|
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
1029
|
-
settings_df.to_csv(settings_csv, index=False)
|
992
|
+
spacr_cores = int(mp.cpu_count() - 6)
|
993
|
+
if spacr_cores <= 2:
|
994
|
+
spacr_cores = 1
|
1030
995
|
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
996
|
+
if settings['n_jobs'] > spacr_cores:
|
997
|
+
print(f'Warning reserving 6 CPU cores for other processes, setting n_jobs to {spacr_cores}')
|
998
|
+
settings['n_jobs'] = spacr_cores
|
1035
999
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
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
|
1046
|
-
|
1047
|
-
if not all(isinstance(settings[key], int) or settings[key] is None for key in int_setting_keys):
|
1048
|
-
print(f"WARNING: {int_setting_keys} must all be integers")
|
1049
|
-
return
|
1000
|
+
dirname = os.path.dirname(settings['src'])
|
1001
|
+
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
1002
|
+
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
1003
|
+
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
1004
|
+
settings_df.to_csv(settings_csv, index=False)
|
1050
1005
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1006
|
+
if settings['timelapse_objects'] == 'nucleus':
|
1007
|
+
if not settings['cell_mask_dim'] is None:
|
1008
|
+
tlo = settings['timelapse_objects']
|
1009
|
+
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
1054
1010
|
|
1055
|
-
|
1056
|
-
print(f"WARNING: crop_mode should be a list with at least one element e.g. ['cell'] or ['cell','nucleus'] or [None]")
|
1057
|
-
return
|
1058
|
-
|
1059
|
-
_save_settings_to_db(settings)
|
1060
|
-
files = [f for f in os.listdir(settings['src']) if f.endswith('.npy')]
|
1061
|
-
n_jobs = settings['n_jobs']
|
1062
|
-
print(f'using {n_jobs} cpu cores')
|
1063
|
-
print_progress(files_processed=0, files_to_process=len(files), n_jobs=n_jobs, time_ls=[], operation_type='Measure and Crop')
|
1064
|
-
|
1065
|
-
def job_callback(result):
|
1066
|
-
completed_jobs.add(result[0])
|
1067
|
-
process_meassure_crop_results([result], settings)
|
1068
|
-
files_processed = len(completed_jobs)
|
1069
|
-
files_to_process = len(files)
|
1070
|
-
print_progress(files_processed, files_to_process, n_jobs, time_ls=time_ls, operation_type='Measure and Crop')
|
1071
|
-
if files_processed >= files_to_process:
|
1072
|
-
pool.terminate()
|
1073
|
-
|
1074
|
-
with mp.Manager() as manager:
|
1075
|
-
time_ls = manager.list()
|
1076
|
-
completed_jobs = set() # Set to keep track of completed jobs
|
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)
|
1011
|
+
int_setting_keys = ['cell_mask_dim', 'nucleus_mask_dim', 'pathogen_mask_dim', 'cell_min_size', 'nucleus_min_size', 'pathogen_min_size', 'cytoplasm_min_size']
|
1081
1012
|
|
1082
|
-
|
1083
|
-
|
1013
|
+
if isinstance(settings['normalize'], bool) and settings['normalize']:
|
1014
|
+
print(f'WARNING: to notmalize single object pngs set normalize to a list of 2 integers, e.g. [1,99] (lower and upper percentiles)')
|
1015
|
+
return
|
1016
|
+
|
1017
|
+
if isinstance(settings['normalize'], list) or isinstance(settings['normalize'], bool) and settings['normalize']:
|
1018
|
+
if settings['normalize_by'] not in ['png', 'fov']:
|
1019
|
+
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 ")
|
1020
|
+
return
|
1021
|
+
|
1022
|
+
if not all(isinstance(settings[key], int) or settings[key] is None for key in int_setting_keys):
|
1023
|
+
print(f"WARNING: {int_setting_keys} must all be integers")
|
1024
|
+
return
|
1025
|
+
|
1026
|
+
if not isinstance(settings['channels'], list):
|
1027
|
+
print(f"WARNING: channels should be a list of integers representing channels e.g. [0,1,2,3]")
|
1028
|
+
return
|
1029
|
+
|
1030
|
+
if not isinstance(settings['crop_mode'], list):
|
1031
|
+
print(f"WARNING: crop_mode should be a list with at least one element e.g. ['cell'] or ['cell','nucleus'] or [None]")
|
1032
|
+
return
|
1033
|
+
|
1034
|
+
_save_settings_to_db(settings)
|
1035
|
+
files = [f for f in os.listdir(settings['src']) if f.endswith('.npy')]
|
1036
|
+
n_jobs = settings['n_jobs']
|
1037
|
+
print(f'using {n_jobs} cpu cores')
|
1038
|
+
print_progress(files_processed=0, files_to_process=len(files), n_jobs=n_jobs, time_ls=[], operation_type='Measure and Crop')
|
1039
|
+
|
1040
|
+
def job_callback(result):
|
1041
|
+
completed_jobs.add(result[0])
|
1042
|
+
process_meassure_crop_results([result], settings)
|
1043
|
+
files_processed = len(completed_jobs)
|
1044
|
+
files_to_process = len(files)
|
1045
|
+
print_progress(files_processed, files_to_process, n_jobs, time_ls=time_ls, operation_type='Measure and Crop')
|
1046
|
+
if files_processed >= files_to_process:
|
1047
|
+
pool.terminate()
|
1048
|
+
|
1049
|
+
with mp.Manager() as manager:
|
1050
|
+
time_ls = manager.list()
|
1051
|
+
completed_jobs = set() # Set to keep track of completed jobs
|
1052
|
+
|
1053
|
+
with mp.Pool(n_jobs) as pool:
|
1054
|
+
for index, file in enumerate(files):
|
1055
|
+
pool.apply_async(_measure_crop_core, args=(index, time_ls, file, settings), callback=job_callback)
|
1056
|
+
|
1057
|
+
pool.close()
|
1058
|
+
pool.join()
|
1084
1059
|
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1060
|
+
if settings['timelapse']:
|
1061
|
+
if settings['timelapse_objects'] == 'nucleus':
|
1062
|
+
folder_path = settings['src']
|
1063
|
+
mask_channels = [settings['nucleus_mask_dim'], settings['pathogen_mask_dim'], settings['cell_mask_dim']]
|
1064
|
+
object_types = ['nucleus', 'pathogen', 'cell']
|
1065
|
+
_timelapse_masks_to_gif(folder_path, mask_channels, object_types)
|
1091
1066
|
|
1092
|
-
|
1067
|
+
print("Successfully completed run")
|
1093
1068
|
|
1094
1069
|
def process_meassure_crop_results(partial_results, settings):
|
1095
1070
|
"""
|
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):
|