spacr 0.3.1__py3-none-any.whl → 0.3.2__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.
Files changed (41) hide show
  1. spacr/__init__.py +19 -3
  2. spacr/cellpose.py +311 -0
  3. spacr/core.py +140 -2493
  4. spacr/deep_spacr.py +151 -29
  5. spacr/gui.py +1 -0
  6. spacr/gui_core.py +74 -63
  7. spacr/gui_elements.py +110 -5
  8. spacr/gui_utils.py +346 -6
  9. spacr/io.py +624 -44
  10. spacr/logger.py +28 -9
  11. spacr/measure.py +107 -95
  12. spacr/mediar.py +0 -3
  13. spacr/ml.py +964 -0
  14. spacr/openai.py +37 -0
  15. spacr/plot.py +280 -15
  16. spacr/resources/data/lopit.csv +3833 -0
  17. spacr/resources/data/toxoplasma_metadata.csv +8843 -0
  18. spacr/resources/icons/convert.png +0 -0
  19. spacr/resources/{models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model → icons/dna_matrix.mp4} +0 -0
  20. spacr/sequencing.py +241 -1311
  21. spacr/settings.py +129 -43
  22. spacr/sim.py +0 -2
  23. spacr/submodules.py +348 -0
  24. spacr/timelapse.py +0 -2
  25. spacr/toxo.py +233 -0
  26. spacr/utils.py +271 -171
  27. {spacr-0.3.1.dist-info → spacr-0.3.2.dist-info}/METADATA +7 -1
  28. {spacr-0.3.1.dist-info → spacr-0.3.2.dist-info}/RECORD +32 -33
  29. spacr/chris.py +0 -50
  30. spacr/graph_learning.py +0 -340
  31. spacr/resources/MEDIAR/.git +0 -1
  32. spacr/resources/MEDIAR_weights/.DS_Store +0 -0
  33. spacr/resources/icons/.DS_Store +0 -0
  34. spacr/resources/icons/spacr_logo_rotation.gif +0 -0
  35. spacr/resources/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
  36. spacr/resources/models/cp/toxo_pv_lumen.CP_model +0 -0
  37. spacr/sim_app.py +0 -0
  38. {spacr-0.3.1.dist-info → spacr-0.3.2.dist-info}/LICENSE +0 -0
  39. {spacr-0.3.1.dist-info → spacr-0.3.2.dist-info}/WHEEL +0 -0
  40. {spacr-0.3.1.dist-info → spacr-0.3.2.dist-info}/entry_points.txt +0 -0
  41. {spacr-0.3.1.dist-info → spacr-0.3.2.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, _create_database
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
- settings = get_measure_crop_settings(settings)
993
- settings = measure_test_mode(settings)
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['cell_mask_dim'] is None:
1007
- settings['include_uninfected'] = True
1008
- if settings['pathogen_mask_dim'] is None:
1009
- settings['include_uninfected'] = True
1010
- if settings['cell_mask_dim'] is not None and settings['pathogen_min_size'] is not None:
1011
- settings['cytoplasm'] = True
1012
- elif settings['cell_mask_dim'] is not None and settings['nucleus_min_size'] is not None:
1013
- settings['cytoplasm'] = True
1014
- else:
1015
- settings['cytoplasm'] = False
1016
-
1017
- spacr_cores = int(mp.cpu_count() - 6)
1018
- if spacr_cores <= 2:
1019
- spacr_cores = 1
1020
-
1021
- if settings['n_jobs'] > spacr_cores:
1022
- print(f'Warning reserving 6 CPU cores for other processes, setting n_jobs to {spacr_cores}')
1023
- settings['n_jobs'] = spacr_cores
1024
-
1025
- dirname = os.path.dirname(settings['src'])
1026
- settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
1027
- settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
1028
- os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
1029
- settings_df.to_csv(settings_csv, index=False)
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
- if settings['timelapse_objects'] == 'nucleus':
1032
- if not settings['cell_mask_dim'] is None:
1033
- tlo = settings['timelapse_objects']
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
- int_setting_keys = ['cell_mask_dim', 'nucleus_mask_dim', 'pathogen_mask_dim', 'cell_min_size', 'nucleus_min_size', 'pathogen_min_size', 'cytoplasm_min_size']
1037
-
1038
- if isinstance(settings['normalize'], bool) and settings['normalize']:
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
- 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
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
- if not isinstance(settings['channels'], list):
1052
- print(f"WARNING: channels should be a list of integers representing channels e.g. [0,1,2,3]")
1053
- return
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
- if not isinstance(settings['crop_mode'], list):
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)
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
- pool.close()
1083
- pool.join()
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
- if settings['timelapse']:
1086
- if settings['timelapse_objects'] == 'nucleus':
1087
- folder_path = settings['src']
1088
- mask_channels = [settings['nucleus_mask_dim'], settings['pathogen_mask_dim'], settings['cell_mask_dim']]
1089
- object_types = ['nucleus', 'pathogen', 'cell']
1090
- _timelapse_masks_to_gif(folder_path, mask_channels, object_types)
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
- print("Successfully completed run")
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):