spacr 0.3.0__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.
- spacr/__init__.py +19 -3
- spacr/cellpose.py +311 -0
- spacr/core.py +142 -2495
- spacr/deep_spacr.py +151 -29
- 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 +631 -51
- spacr/logger.py +28 -9
- spacr/measure.py +107 -95
- spacr/mediar.py +0 -5
- spacr/ml.py +964 -0
- spacr/openai.py +37 -0
- spacr/plot.py +281 -16
- 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 +129 -43
- spacr/sim.py +0 -2
- spacr/submodules.py +348 -0
- spacr/timelapse.py +0 -2
- spacr/toxo.py +233 -0
- spacr/utils.py +275 -173
- {spacr-0.3.0.dist-info → spacr-0.3.2.dist-info}/METADATA +7 -1
- {spacr-0.3.0.dist-info → spacr-0.3.2.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.0.dist-info → spacr-0.3.2.dist-info}/LICENSE +0 -0
- {spacr-0.3.0.dist-info → spacr-0.3.2.dist-info}/WHEEL +0 -0
- {spacr-0.3.0.dist-info → spacr-0.3.2.dist-info}/entry_points.txt +0 -0
- {spacr-0.3.0.dist-info → spacr-0.3.2.dist-info}/top_level.txt +0 -0
spacr/__init__.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
|
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
|