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/__init__.py CHANGED
@@ -1,6 +1,4 @@
1
- from spacr.version import version, version_str
2
- import logging
3
- import torch
1
+ import logging, os
4
2
 
5
3
  from . import core
6
4
  from . import io
@@ -24,6 +22,11 @@ from . import app_classify
24
22
  from . import app_sequencing
25
23
  from . import app_umap
26
24
  from . import mediar
25
+ from . import submodules
26
+ from . import openai
27
+ from . import ml
28
+ from . import toxo
29
+ from . import cellpose
27
30
  from . import logger
28
31
 
29
32
  __all__ = [
@@ -49,8 +52,21 @@ __all__ = [
49
52
  "app_sequencing",
50
53
  "app_umap",
51
54
  "mediar",
55
+ "submodules",
56
+ "openai",
57
+ "ml",
58
+ "toxo",
59
+ "cellpose",
52
60
  "logger"
53
61
  ]
54
62
 
55
63
  logging.basicConfig(filename='spacr.log', level=logging.INFO,
56
64
  format='%(asctime)s:%(levelname)s:%(message)s')
65
+
66
+ from spacr.utils import download_models
67
+
68
+ # Check if models already exist
69
+ models_dir = os.path.join(os.path.dirname(__file__), 'resources', 'models', 'cp')
70
+ if not os.path.exists(models_dir) or not os.listdir(models_dir):
71
+ print("Models not found, downloading...")
72
+ download_models(local_dir=models_dir)
spacr/cellpose.py ADDED
@@ -0,0 +1,311 @@
1
+ import os, gc, torch, time, random, cv2
2
+ import numpy as np
3
+ import pandas as pd
4
+ from cellpose import models as cp_models
5
+ from IPython.display import display
6
+ from multiprocessing import Pool
7
+ from skimage.transform import resize as resizescikit
8
+
9
+ def identify_masks_finetune(settings):
10
+
11
+ from .plot import print_mask_and_flows
12
+ from .utils import resize_images_and_labels, print_progress
13
+ from .io import _load_normalized_images_and_labels, _load_images_and_labels
14
+ from .settings import get_identify_masks_finetune_default_settings
15
+
16
+ settings = get_identify_masks_finetune_default_settings(settings)
17
+ src=settings['src']
18
+ dst=settings['dst']
19
+ model_name=settings['model_name']
20
+ custom_model=settings['custom_model']
21
+ channels = settings['channels']
22
+ background = settings['background']
23
+ remove_background=settings['remove_background']
24
+ Signal_to_noise = settings['Signal_to_noise']
25
+ CP_prob = settings['CP_prob']
26
+ diameter=settings['diameter']
27
+ batch_size=settings['batch_size']
28
+ flow_threshold=settings['flow_threshold']
29
+ save=settings['save']
30
+ verbose=settings['verbose']
31
+
32
+ # static settings
33
+ normalize = settings['normalize']
34
+ percentiles = settings['percentiles']
35
+ circular = settings['circular']
36
+ invert = settings['invert']
37
+ resize = settings['resize']
38
+
39
+ if resize:
40
+ target_height = settings['target_height']
41
+ target_width = settings['target_width']
42
+
43
+ rescale = settings['rescale']
44
+ resample = settings['resample']
45
+ grayscale = settings['grayscale']
46
+
47
+ os.makedirs(dst, exist_ok=True)
48
+
49
+ if not custom_model is None:
50
+ if not os.path.exists(custom_model):
51
+ print(f'Custom model not found: {custom_model}')
52
+ return
53
+
54
+ if not torch.cuda.is_available():
55
+ print(f'Torch CUDA is not available, using CPU')
56
+
57
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
58
+
59
+ if custom_model == None:
60
+ model = cp_models.CellposeModel(gpu=True, model_type=model_name, device=device)
61
+ print(f'Loaded model: {model_name}')
62
+ else:
63
+ model = cp_models.CellposeModel(gpu=torch.cuda.is_available(), model_type=None, pretrained_model=custom_model, diam_mean=diameter, device=device)
64
+ print("Pretrained Model Loaded:", model.pretrained_model)
65
+
66
+ chans = [2, 1] if model_name == 'cyto2' else [0,0] if model_name == 'nucleus' else [1,0] if model_name == 'cyto' else [2, 0]
67
+
68
+ if grayscale:
69
+ chans=[0, 0]
70
+
71
+ print(f'Using channels: {chans} for model of type {model_name}')
72
+
73
+ if verbose == True:
74
+ print(f'Cellpose settings: Model: {model_name}, channels: {channels}, cellpose_chans: {chans}, diameter:{diameter}, flow_threshold:{flow_threshold}, cellprob_threshold:{CP_prob}')
75
+
76
+ all_image_files = [os.path.join(src, f) for f in os.listdir(src) if f.endswith('.tif')]
77
+ mask_files = set(os.listdir(os.path.join(src, 'masks')))
78
+ all_image_files = [f for f in all_image_files if os.path.basename(f) not in mask_files]
79
+ random.shuffle(all_image_files)
80
+
81
+ time_ls = []
82
+ for i in range(0, len(all_image_files), batch_size):
83
+ gc.collect()
84
+ image_files = all_image_files[i:i+batch_size]
85
+
86
+ if normalize:
87
+ images, _, image_names, _, orig_dims = _load_normalized_images_and_labels(image_files=image_files, label_files=None, channels=channels, percentiles=percentiles, circular=circular, invert=invert, visualize=verbose, remove_background=remove_background, background=background, Signal_to_noise=Signal_to_noise, target_height=target_height, target_width=target_width)
88
+ images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
89
+ #orig_dims = [(image.shape[0], image.shape[1]) for image in images]
90
+ else:
91
+ images, _, image_names, _ = _load_images_and_labels(image_files=image_files, label_files=None, circular=circular, invert=invert)
92
+ images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
93
+ orig_dims = [(image.shape[0], image.shape[1]) for image in images]
94
+ if resize:
95
+ images, _ = resize_images_and_labels(images, None, target_height, target_width, True)
96
+
97
+ for file_index, stack in enumerate(images):
98
+ start = time.time()
99
+ output = model.eval(x=stack,
100
+ normalize=False,
101
+ channels=chans,
102
+ channel_axis=3,
103
+ diameter=diameter,
104
+ flow_threshold=flow_threshold,
105
+ cellprob_threshold=CP_prob,
106
+ rescale=rescale,
107
+ resample=resample,
108
+ progress=True)
109
+
110
+ if len(output) == 4:
111
+ mask, flows, _, _ = output
112
+ elif len(output) == 3:
113
+ mask, flows, _ = output
114
+ else:
115
+ raise ValueError("Unexpected number of return values from model.eval()")
116
+
117
+ if resize:
118
+ dims = orig_dims[file_index]
119
+ mask = resizescikit(mask, dims, order=0, preserve_range=True, anti_aliasing=False).astype(mask.dtype)
120
+
121
+ stop = time.time()
122
+ duration = (stop - start)
123
+ time_ls.append(duration)
124
+ files_processed = len(images)
125
+ files_to_process = file_index+1
126
+ print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls)
127
+ print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="")
128
+
129
+
130
+ if verbose:
131
+ if resize:
132
+ stack = resizescikit(stack, dims, preserve_range=True, anti_aliasing=False).astype(stack.dtype)
133
+ print_mask_and_flows(stack, mask, flows, overlay=True)
134
+ if save:
135
+ os.makedirs(dst, exist_ok=True)
136
+ output_filename = os.path.join(dst, image_names[file_index])
137
+ cv2.imwrite(output_filename, mask)
138
+ del images, output, mask, flows
139
+ gc.collect()
140
+ return
141
+
142
+ def generate_masks_from_imgs(src, model, model_name, batch_size, diameter, cellprob_threshold, flow_threshold, grayscale, save, normalize, channels, percentiles, circular, invert, plot, resize, target_height, target_width, remove_background, background, Signal_to_noise, verbose):
143
+
144
+ from .io import _load_images_and_labels, _load_normalized_images_and_labels
145
+ from .utils import resize_images_and_labels, resizescikit, print_progress
146
+ from .plot import print_mask_and_flows
147
+
148
+ dst = os.path.join(src, model_name)
149
+ os.makedirs(dst, exist_ok=True)
150
+
151
+ chans = [2, 1] if model_name == 'cyto2' else [0,0] if model_name == 'nucleus' else [1,0] if model_name == 'cyto' else [2, 0]
152
+
153
+ if grayscale:
154
+ chans=[0, 0]
155
+
156
+ all_image_files = [os.path.join(src, f) for f in os.listdir(src) if f.endswith('.tif')]
157
+ random.shuffle(all_image_files)
158
+
159
+ if verbose == True:
160
+ print(f'Cellpose settings: Model: {model_name}, channels: {channels}, cellpose_chans: {chans}, diameter:{diameter}, flow_threshold:{flow_threshold}, cellprob_threshold:{cellprob_threshold}')
161
+
162
+ time_ls = []
163
+ for i in range(0, len(all_image_files), batch_size):
164
+ image_files = all_image_files[i:i+batch_size]
165
+
166
+ if normalize:
167
+ images, _, image_names, _, orig_dims = _load_normalized_images_and_labels(image_files, None, channels, percentiles, circular, invert, plot, remove_background, background, Signal_to_noise, target_height, target_width)
168
+ images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
169
+ orig_dims = [(image.shape[0], image.shape[1]) for image in images]
170
+ else:
171
+ images, _, image_names, _ = _load_images_and_labels(image_files, None, circular, invert)
172
+ images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
173
+ orig_dims = [(image.shape[0], image.shape[1]) for image in images]
174
+ if resize:
175
+ images, _ = resize_images_and_labels(images, None, target_height, target_width, True)
176
+
177
+ for file_index, stack in enumerate(images):
178
+ start = time.time()
179
+ output = model.eval(x=stack,
180
+ normalize=False,
181
+ channels=chans,
182
+ channel_axis=3,
183
+ diameter=diameter,
184
+ flow_threshold=flow_threshold,
185
+ cellprob_threshold=cellprob_threshold,
186
+ rescale=False,
187
+ resample=False,
188
+ progress=False)
189
+
190
+ if len(output) == 4:
191
+ mask, flows, _, _ = output
192
+ elif len(output) == 3:
193
+ mask, flows, _ = output
194
+ else:
195
+ raise ValueError("Unexpected number of return values from model.eval()")
196
+
197
+ if resize:
198
+ dims = orig_dims[file_index]
199
+ mask = resizescikit(mask, dims, order=0, preserve_range=True, anti_aliasing=False).astype(mask.dtype)
200
+
201
+ stop = time.time()
202
+ duration = (stop - start)
203
+ time_ls.append(duration)
204
+ files_processed = file_index+1
205
+ files_to_process = len(images)
206
+
207
+ print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Generating masks")
208
+
209
+ if plot:
210
+ if resize:
211
+ stack = resizescikit(stack, dims, preserve_range=True, anti_aliasing=False).astype(stack.dtype)
212
+ print_mask_and_flows(stack, mask, flows, overlay=True)
213
+ if save:
214
+ output_filename = os.path.join(dst, image_names[file_index])
215
+ cv2.imwrite(output_filename, mask)
216
+
217
+
218
+ def check_cellpose_models(settings):
219
+
220
+ from .settings import get_check_cellpose_models_default_settings
221
+
222
+ settings = get_check_cellpose_models_default_settings(settings)
223
+ src = settings['src']
224
+
225
+ settings_df = pd.DataFrame(list(settings.items()), columns=['setting_key', 'setting_value'])
226
+ settings_df['setting_value'] = settings_df['setting_value'].apply(str)
227
+ display(settings_df)
228
+
229
+ cellpose_models = ['cyto', 'nuclei', 'cyto2', 'cyto3']
230
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
231
+
232
+ for model_name in cellpose_models:
233
+
234
+ model = cp_models.CellposeModel(gpu=True, model_type=model_name, device=device)
235
+ print(f'Using {model_name}')
236
+ generate_masks_from_imgs(src, model, model_name, settings['batch_size'], settings['diameter'], settings['CP_prob'], settings['flow_threshold'], settings['grayscale'], settings['save'], settings['normalize'], settings['channels'], settings['percentiles'], settings['circular'], settings['invert'], settings['plot'], settings['resize'], settings['target_height'], settings['target_width'], settings['remove_background'], settings['background'], settings['Signal_to_noise'], settings['verbose'])
237
+
238
+ return
239
+
240
+ def save_results_and_figure(src, fig, results):
241
+
242
+ if not isinstance(results, pd.DataFrame):
243
+ results = pd.DataFrame(results)
244
+
245
+ results_dir = os.path.join(src, 'results')
246
+ os.makedirs(results_dir, exist_ok=True)
247
+ results_path = os.path.join(results_dir,f'results.csv')
248
+ fig_path = os.path.join(results_dir, f'model_comparison_plot.pdf')
249
+ results.to_csv(results_path, index=False)
250
+ fig.savefig(fig_path, format='pdf')
251
+ print(f'Saved figure to {fig_path} and results to {results_path}')
252
+
253
+ def compare_mask(args):
254
+ src, filename, dirs, conditions = args
255
+ paths = [os.path.join(d, filename) for d in dirs]
256
+
257
+ if not all(os.path.exists(path) for path in paths):
258
+ return None
259
+
260
+ from .io import _read_mask
261
+ from .utils import boundary_f1_score, compute_segmentation_ap, jaccard_index
262
+
263
+ masks = [_read_mask(path) for path in paths]
264
+ file_results = {'filename': filename}
265
+
266
+ for i in range(len(masks)):
267
+ for j in range(i + 1, len(masks)):
268
+ mask_i, mask_j = masks[i], masks[j]
269
+ f1_score = boundary_f1_score(mask_i, mask_j)
270
+ jac_index = jaccard_index(mask_i, mask_j)
271
+ ap_score = compute_segmentation_ap(mask_i, mask_j)
272
+
273
+ file_results.update({
274
+ f'jaccard_{conditions[i]}_{conditions[j]}': jac_index,
275
+ f'boundary_f1_{conditions[i]}_{conditions[j]}': f1_score,
276
+ f'ap_{conditions[i]}_{conditions[j]}': ap_score
277
+ })
278
+
279
+ return file_results
280
+
281
+ def compare_cellpose_masks(src, verbose=False, processes=None, save=True):
282
+ from .plot import visualize_cellpose_masks, plot_comparison_results
283
+ from .io import _read_mask
284
+
285
+ dirs = [os.path.join(src, d) for d in os.listdir(src) if os.path.isdir(os.path.join(src, d)) and d != 'results']
286
+ dirs.sort()
287
+ conditions = [os.path.basename(d) for d in dirs]
288
+
289
+ # Get common files in all directories
290
+ common_files = set(os.listdir(dirs[0]))
291
+ for d in dirs[1:]:
292
+ common_files.intersection_update(os.listdir(d))
293
+ common_files = list(common_files)
294
+
295
+ # Create a pool of n_jobs
296
+ with Pool(processes=processes) as pool:
297
+ args = [(src, filename, dirs, conditions) for filename in common_files]
298
+ results = pool.map(compare_mask, args)
299
+
300
+ # Filter out None results (from skipped files)
301
+ results = [res for res in results if res is not None]
302
+ print(results)
303
+ if verbose:
304
+ for result in results:
305
+ filename = result['filename']
306
+ masks = [_read_mask(os.path.join(d, filename)) for d in dirs]
307
+ visualize_cellpose_masks(masks, titles=conditions, filename=filename, save=save, src=src)
308
+
309
+ fig = plot_comparison_results(results)
310
+ save_results_and_figure(src, fig, results)
311
+ return