celldetective 1.1.1.post3__py3-none-any.whl → 1.1.1.post4__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.
- celldetective/__main__.py +17 -0
- celldetective/gui/classifier_widget.py +10 -3
- celldetective/gui/control_panel.py +11 -4
- celldetective/gui/layouts.py +9 -7
- celldetective/gui/neighborhood_options.py +11 -5
- celldetective/gui/signal_annotator.py +85 -25
- celldetective/gui/tableUI.py +15 -6
- celldetective/io.py +69 -3
- celldetective/neighborhood.py +96 -26
- celldetective/preprocessing.py +81 -61
- celldetective/segmentation.py +67 -29
- celldetective/utils.py +51 -12
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/METADATA +1 -1
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/RECORD +19 -19
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/WHEEL +1 -1
- tests/test_segmentation.py +1 -1
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.1.1.post4.dist-info}/top_level.txt +0 -0
celldetective/neighborhood.py
CHANGED
|
@@ -48,14 +48,14 @@ def set_live_status(setA,setB,status, not_status_option):
|
|
|
48
48
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
if status is None:
|
|
51
|
+
print(f"Provided statuses: {status}...")
|
|
52
|
+
if status is None or status==["live_status","live_status"] or status==[None,None]:
|
|
53
53
|
setA.loc[:,'live_status'] = 1
|
|
54
54
|
setB.loc[:,'live_status'] = 1
|
|
55
55
|
status = ['live_status', 'live_status']
|
|
56
56
|
elif isinstance(status,list):
|
|
57
57
|
assert len(status)==2,'Please provide only two columns to classify cells as alive or dead.'
|
|
58
|
-
if status[0] is None:
|
|
58
|
+
if status[0] is None or status[0]=='live_status':
|
|
59
59
|
setA.loc[:,'live_status'] = 1
|
|
60
60
|
status[0] = 'live_status'
|
|
61
61
|
elif status[0] is not None and isinstance(not_status_option,list):
|
|
@@ -63,7 +63,7 @@ def set_live_status(setA,setB,status, not_status_option):
|
|
|
63
63
|
if not_status_option[0]:
|
|
64
64
|
setA.loc[:,'not_'+status[0]] = [not a if a==0 or a==1 else np.nan for a in setA.loc[:,status[0]].values]
|
|
65
65
|
status[0] = 'not_'+status[0]
|
|
66
|
-
if status[1] is None:
|
|
66
|
+
if status[1] is None or status[1]=='live_status':
|
|
67
67
|
setB.loc[:,'live_status'] = 1
|
|
68
68
|
status[1] = 'live_status'
|
|
69
69
|
elif status[1] is not None and isinstance(not_status_option,list):
|
|
@@ -380,16 +380,39 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
380
380
|
if df_A_pkl is not None:
|
|
381
381
|
pkl_columns = np.array(df_A_pkl.columns)
|
|
382
382
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
383
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
383
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
384
|
+
|
|
385
|
+
if 'TRACK_ID' in list(pkl_columns):
|
|
386
|
+
cols.append('TRACK_ID')
|
|
387
|
+
on_cols = ['TRACK_ID','FRAME']
|
|
388
|
+
else:
|
|
389
|
+
cols.append('ID')
|
|
390
|
+
on_cols = ['ID','FRAME']
|
|
391
|
+
|
|
384
392
|
print(f'Recover {cols} from the pickle file...')
|
|
385
|
-
|
|
386
|
-
|
|
393
|
+
try:
|
|
394
|
+
df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
395
|
+
print(df_A.columns)
|
|
396
|
+
except Exception as e:
|
|
397
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
398
|
+
|
|
387
399
|
if df_B_pkl is not None and df_B is not None:
|
|
388
400
|
pkl_columns = np.array(df_B_pkl.columns)
|
|
389
401
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
390
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
402
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
403
|
+
|
|
404
|
+
if 'TRACK_ID' in list(pkl_columns):
|
|
405
|
+
cols.append('TRACK_ID')
|
|
406
|
+
on_cols = ['TRACK_ID','FRAME']
|
|
407
|
+
else:
|
|
408
|
+
cols.append('ID')
|
|
409
|
+
on_cols = ['ID','FRAME']
|
|
410
|
+
|
|
391
411
|
print(f'Recover {cols} from the pickle file...')
|
|
392
|
-
|
|
412
|
+
try:
|
|
413
|
+
df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
414
|
+
except Exception as e:
|
|
415
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
393
416
|
|
|
394
417
|
if clear_neigh:
|
|
395
418
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
@@ -408,17 +431,22 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
408
431
|
elif neighborhood_kwargs['mode']=='self':
|
|
409
432
|
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
410
433
|
|
|
411
|
-
edge_filter_A = (df_A['POSITION_X'] > td)&(df_A['POSITION_Y'] > td)&(df_A['POSITION_Y'] < (img_shape[0] - td))&(df_A['POSITION_X'] < (img_shape[1] - td))
|
|
412
|
-
edge_filter_B = (df_B['POSITION_X'] > td)&(df_B['POSITION_Y'] > td)&(df_B['POSITION_Y'] < (img_shape[0] - td))&(df_B['POSITION_X'] < (img_shape[1] - td))
|
|
413
|
-
df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
414
|
-
df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
434
|
+
# edge_filter_A = (df_A['POSITION_X'] > td)&(df_A['POSITION_Y'] > td)&(df_A['POSITION_Y'] < (img_shape[0] - td))&(df_A['POSITION_X'] < (img_shape[1] - td))
|
|
435
|
+
# edge_filter_B = (df_B['POSITION_X'] > td)&(df_B['POSITION_Y'] > td)&(df_B['POSITION_Y'] < (img_shape[0] - td))&(df_B['POSITION_X'] < (img_shape[1] - td))
|
|
436
|
+
# df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
437
|
+
# df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
415
438
|
|
|
439
|
+
print('Count neighborhood...')
|
|
416
440
|
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
417
441
|
if neighborhood_kwargs['symmetrize']:
|
|
418
442
|
df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
443
|
+
print('Done...')
|
|
419
444
|
|
|
420
|
-
|
|
421
|
-
|
|
445
|
+
if 'TRACK_ID' in list(df_A.columns):
|
|
446
|
+
print('Estimate average neighborhood before/after event...')
|
|
447
|
+
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col)
|
|
448
|
+
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col)
|
|
449
|
+
print('Done...')
|
|
422
450
|
|
|
423
451
|
df_A.to_pickle(path_A.replace('.csv','.pkl'))
|
|
424
452
|
if not population[0]==population[1]:
|
|
@@ -485,9 +513,14 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
485
513
|
|
|
486
514
|
neigh_table = neigh_table.reset_index(drop=True)
|
|
487
515
|
if 'position' in list(neigh_table.columns):
|
|
488
|
-
groupbycols = ['position'
|
|
516
|
+
groupbycols = ['position']
|
|
489
517
|
else:
|
|
490
|
-
groupbycols = [
|
|
518
|
+
groupbycols = []
|
|
519
|
+
if 'TRACK_ID' in list(neigh_table.columns):
|
|
520
|
+
groupbycols.append('TRACK_ID')
|
|
521
|
+
else:
|
|
522
|
+
groupbycols.append('ID')
|
|
523
|
+
|
|
491
524
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
492
525
|
|
|
493
526
|
for tid,group in neigh_table.groupby(groupbycols):
|
|
@@ -607,10 +640,16 @@ def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col, metri
|
|
|
607
640
|
"""
|
|
608
641
|
|
|
609
642
|
|
|
643
|
+
neigh_table = neigh_table.reset_index(drop=True)
|
|
610
644
|
if 'position' in list(neigh_table.columns):
|
|
611
|
-
groupbycols = ['position'
|
|
645
|
+
groupbycols = ['position']
|
|
612
646
|
else:
|
|
613
|
-
groupbycols = [
|
|
647
|
+
groupbycols = []
|
|
648
|
+
if 'TRACK_ID' in list(neigh_table.columns):
|
|
649
|
+
groupbycols.append('TRACK_ID')
|
|
650
|
+
else:
|
|
651
|
+
groupbycols.append('ID')
|
|
652
|
+
|
|
614
653
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
615
654
|
suffix = '_before_event'
|
|
616
655
|
|
|
@@ -681,10 +720,16 @@ def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col, metric
|
|
|
681
720
|
"""
|
|
682
721
|
|
|
683
722
|
|
|
723
|
+
neigh_table = neigh_table.reset_index(drop=True)
|
|
684
724
|
if 'position' in list(neigh_table.columns):
|
|
685
|
-
groupbycols = ['position'
|
|
725
|
+
groupbycols = ['position']
|
|
726
|
+
else:
|
|
727
|
+
groupbycols = []
|
|
728
|
+
if 'TRACK_ID' in list(neigh_table.columns):
|
|
729
|
+
groupbycols.append('TRACK_ID')
|
|
686
730
|
else:
|
|
687
|
-
groupbycols
|
|
731
|
+
groupbycols.append('ID')
|
|
732
|
+
|
|
688
733
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
689
734
|
suffix = '_after_event'
|
|
690
735
|
|
|
@@ -1096,6 +1141,8 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1096
1141
|
|
|
1097
1142
|
df_A, path_A = get_position_table(pos, population=population[0], return_path=True)
|
|
1098
1143
|
df_B, path_B = get_position_table(pos, population=population[1], return_path=True)
|
|
1144
|
+
if df_A is None or df_B is None:
|
|
1145
|
+
return None
|
|
1099
1146
|
|
|
1100
1147
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
1101
1148
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
@@ -1103,16 +1150,39 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1103
1150
|
if df_A_pkl is not None:
|
|
1104
1151
|
pkl_columns = np.array(df_A_pkl.columns)
|
|
1105
1152
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1106
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
1153
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1154
|
+
|
|
1155
|
+
if 'TRACK_ID' in list(pkl_columns):
|
|
1156
|
+
cols.append('TRACK_ID')
|
|
1157
|
+
on_cols = ['TRACK_ID','FRAME']
|
|
1158
|
+
else:
|
|
1159
|
+
cols.append('ID')
|
|
1160
|
+
on_cols = ['ID','FRAME']
|
|
1161
|
+
|
|
1107
1162
|
print(f'Recover {cols} from the pickle file...')
|
|
1108
|
-
|
|
1109
|
-
|
|
1163
|
+
try:
|
|
1164
|
+
df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
1165
|
+
print(df_A.columns)
|
|
1166
|
+
except Exception as e:
|
|
1167
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
1168
|
+
|
|
1110
1169
|
if df_B_pkl is not None and df_B is not None:
|
|
1111
1170
|
pkl_columns = np.array(df_B_pkl.columns)
|
|
1112
1171
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1113
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
1172
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1173
|
+
|
|
1174
|
+
if 'TRACK_ID' in list(pkl_columns):
|
|
1175
|
+
cols.append('TRACK_ID')
|
|
1176
|
+
on_cols = ['TRACK_ID','FRAME']
|
|
1177
|
+
else:
|
|
1178
|
+
cols.append('ID')
|
|
1179
|
+
on_cols = ['ID','FRAME']
|
|
1180
|
+
|
|
1114
1181
|
print(f'Recover {cols} from the pickle file...')
|
|
1115
|
-
|
|
1182
|
+
try:
|
|
1183
|
+
df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
1184
|
+
except Exception as e:
|
|
1185
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
1116
1186
|
|
|
1117
1187
|
labelsA = locate_labels(pos, population=population[0])
|
|
1118
1188
|
if population[1]==population[0]:
|
celldetective/preprocessing.py
CHANGED
|
@@ -15,8 +15,6 @@ from gc import collect
|
|
|
15
15
|
from lmfit import Parameters, Model, models
|
|
16
16
|
import tifffile.tifffile as tiff
|
|
17
17
|
|
|
18
|
-
from tifffile import imwrite
|
|
19
|
-
|
|
20
18
|
def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", activation_protocol=[['gauss',2],['std',4]], show_progress_per_pos=False, show_progress_per_well=True):
|
|
21
19
|
|
|
22
20
|
"""
|
|
@@ -98,53 +96,65 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
|
|
|
98
96
|
for l,pos_path in enumerate(tqdm(positions, disable=not show_progress_per_pos)):
|
|
99
97
|
|
|
100
98
|
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
99
|
+
if stack_path is not None:
|
|
100
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
101
|
+
if len_movie_auto is not None:
|
|
102
|
+
len_movie = len_movie_auto
|
|
103
|
+
img_num_channels = _get_img_num_per_channel(channel_indices, int(len_movie), nbr_channels)
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
frames = load_frames(img_num_channels[0,frame_range[0]:frame_range[1]], stack_path, normalize_input=False)
|
|
105
|
-
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
106
|
-
|
|
107
|
-
for i in range(len(frames)):
|
|
108
|
-
if np.all(frames[i].flatten()==0):
|
|
109
|
-
frames[i,:,:] = np.nan
|
|
110
|
-
|
|
111
|
-
frame_mean = np.nanmean(frames, axis=0)
|
|
112
|
-
|
|
113
|
-
frame = frame_mean.copy().astype(float)
|
|
114
|
-
std_frame = filter_image(frame.copy(),filters=activation_protocol)
|
|
115
|
-
edge = estimate_unreliable_edge(activation_protocol)
|
|
116
|
-
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
117
|
-
frame[np.where(mask.astype(int)==1)] = np.nan
|
|
118
|
-
|
|
119
|
-
elif mode=="tiles":
|
|
105
|
+
if mode=="timeseries":
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
frames = load_frames(img_num_channels[0,frame_range[0]:frame_range[1]], stack_path, normalize_input=False)
|
|
108
|
+
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
123
109
|
|
|
124
|
-
|
|
125
|
-
|
|
110
|
+
for i in range(len(frames)):
|
|
111
|
+
if np.all(frames[i].flatten()==0):
|
|
112
|
+
frames[i,:,:] = np.nan
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
continue
|
|
132
|
-
|
|
133
|
-
f = frames[i].copy()
|
|
134
|
-
std_frame = filter_image(f.copy(),filters=activation_protocol)
|
|
114
|
+
frame_mean = np.nanmean(frames, axis=0)
|
|
115
|
+
|
|
116
|
+
frame = frame_mean.copy().astype(float)
|
|
117
|
+
std_frame = filter_image(frame.copy(),filters=activation_protocol)
|
|
135
118
|
edge = estimate_unreliable_edge(activation_protocol)
|
|
136
119
|
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
frame[np.where(mask.astype(int)==1)] = np.nan
|
|
121
|
+
|
|
122
|
+
elif mode=="tiles":
|
|
123
|
+
|
|
124
|
+
frames = load_frames(img_num_channels[0,:], stack_path, normalize_input=False).astype(float)
|
|
125
|
+
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
126
|
+
|
|
127
|
+
new_frames = []
|
|
128
|
+
for i in range(len(frames)):
|
|
129
|
+
|
|
130
|
+
if np.all(frames[i].flatten()==0):
|
|
131
|
+
empty_frame = np.zeros_like(frames[i])
|
|
132
|
+
empty_frame[:,:] = np.nan
|
|
133
|
+
new_frames.append(empty_frame)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
f = frames[i].copy()
|
|
137
|
+
std_frame = filter_image(f.copy(),filters=activation_protocol)
|
|
138
|
+
edge = estimate_unreliable_edge(activation_protocol)
|
|
139
|
+
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
140
|
+
f[np.where(mask.astype(int)==1)] = np.nan
|
|
141
|
+
new_frames.append(f.copy())
|
|
142
|
+
|
|
143
|
+
frame = np.nanmedian(new_frames, axis=0)
|
|
144
|
+
else:
|
|
145
|
+
print(f'Stack not found for position {pos_path}...')
|
|
146
|
+
frame = []
|
|
141
147
|
|
|
142
148
|
# store
|
|
143
149
|
frame_mean_per_position.append(frame)
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
try:
|
|
152
|
+
background = np.nanmedian(frame_mean_per_position,axis=0)
|
|
153
|
+
backgrounds.append({"bg": background, "well": well_path})
|
|
154
|
+
print(f"Background successfully computed for well {well_name}...")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(e)
|
|
157
|
+
backgrounds.append(None)
|
|
148
158
|
|
|
149
159
|
return backgrounds
|
|
150
160
|
|
|
@@ -266,28 +276,35 @@ def correct_background_model_free(
|
|
|
266
276
|
|
|
267
277
|
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
268
278
|
print(f'Applying the correction to position {extract_position_name(pos_path)}...')
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
279
|
+
if stack_path is not None:
|
|
280
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
281
|
+
if len_movie_auto is not None:
|
|
282
|
+
len_movie = len_movie_auto
|
|
283
|
+
img_num_channels = _get_img_num_per_channel(channel_indices, int(len_movie), nbr_channels)
|
|
284
|
+
|
|
285
|
+
corrected_stack = apply_background_to_stack(stack_path,
|
|
286
|
+
background,
|
|
287
|
+
target_channel_index=channel_indices[0],
|
|
288
|
+
nbr_channels=nbr_channels,
|
|
289
|
+
stack_length=len_movie,
|
|
290
|
+
threshold_on_std=threshold_on_std,
|
|
291
|
+
optimize_option=optimize_option,
|
|
292
|
+
opt_coef_range=opt_coef_range,
|
|
293
|
+
opt_coef_nbr=opt_coef_nbr,
|
|
294
|
+
operation=operation,
|
|
295
|
+
clip=clip,
|
|
296
|
+
export=export,
|
|
297
|
+
activation_protocol=activation_protocol,
|
|
298
|
+
prefix=export_prefix,
|
|
299
|
+
)
|
|
300
|
+
print('Correction successful.')
|
|
301
|
+
if return_stacks:
|
|
302
|
+
stacks.append(corrected_stack)
|
|
303
|
+
else:
|
|
304
|
+
del corrected_stack
|
|
305
|
+
collect()
|
|
288
306
|
else:
|
|
289
|
-
|
|
290
|
-
collect()
|
|
307
|
+
stacks.append(None)
|
|
291
308
|
|
|
292
309
|
if return_stacks:
|
|
293
310
|
return stacks
|
|
@@ -770,7 +787,10 @@ def correct_background_model(
|
|
|
770
787
|
|
|
771
788
|
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
772
789
|
print(f'Applying the correction to position {extract_position_name(pos_path)}...')
|
|
773
|
-
|
|
790
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
791
|
+
if len_movie_auto is not None:
|
|
792
|
+
len_movie = len_movie_auto
|
|
793
|
+
img_num_channels = _get_img_num_per_channel(channel_indices, int(len_movie), nbr_channels)
|
|
774
794
|
|
|
775
795
|
corrected_stack = fit_and_apply_model_background_to_stack(stack_path,
|
|
776
796
|
target_channel_index=channel_indices[0],
|
celldetective/segmentation.py
CHANGED
|
@@ -20,6 +20,7 @@ from skimage.segmentation import watershed
|
|
|
20
20
|
from skimage.feature import peak_local_max
|
|
21
21
|
from skimage.measure import regionprops_table
|
|
22
22
|
from skimage.exposure import match_histograms
|
|
23
|
+
from scipy.ndimage import zoom
|
|
23
24
|
import pandas as pd
|
|
24
25
|
import subprocess
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ import subprocess
|
|
|
27
28
|
abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0],'celldetective'])
|
|
28
29
|
|
|
29
30
|
def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_napari=False,
|
|
30
|
-
use_gpu=True,
|
|
31
|
+
use_gpu=True, channel_axis=-1):
|
|
31
32
|
|
|
32
33
|
"""
|
|
33
34
|
|
|
@@ -85,7 +86,10 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
85
86
|
if not use_gpu:
|
|
86
87
|
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
87
88
|
else:
|
|
88
|
-
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
|
89
|
+
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
|
90
|
+
|
|
91
|
+
if channel_axis != -1:
|
|
92
|
+
stack = np.moveaxis(stack, channel_axis, -1)
|
|
89
93
|
|
|
90
94
|
if channels is not None:
|
|
91
95
|
assert len(channels)==stack.shape[-1],f'The channel names provided do not match with the expected number of channels in the stack: {stack.shape[-1]}.'
|
|
@@ -96,48 +100,83 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
96
100
|
required_spatial_calibration = input_config['spatial_calibration']
|
|
97
101
|
model_type = input_config['model_type']
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
normalize = True
|
|
103
|
+
normalization_percentile = input_config['normalization_percentile']
|
|
104
|
+
normalization_clip = input_config['normalization_clip']
|
|
105
|
+
normalization_values = input_config['normalization_values']
|
|
103
106
|
|
|
104
107
|
if model_type=='cellpose':
|
|
105
108
|
diameter = input_config['diameter']
|
|
106
|
-
if diameter!=30:
|
|
107
|
-
|
|
109
|
+
# if diameter!=30:
|
|
110
|
+
# required_spatial_calibration = None
|
|
108
111
|
cellprob_threshold = input_config['cellprob_threshold']
|
|
109
112
|
flow_threshold = input_config['flow_threshold']
|
|
110
113
|
|
|
111
114
|
scale = _estimate_scale_factor(spatial_calibration, required_spatial_calibration)
|
|
112
115
|
|
|
113
116
|
if model_type=='stardist':
|
|
117
|
+
|
|
114
118
|
model = StarDist2D(None, name=model_name, basedir=Path(model_path).parent)
|
|
115
|
-
|
|
119
|
+
model.config.use_gpu = use_gpu
|
|
120
|
+
model.use_gpu = use_gpu
|
|
121
|
+
print(f"StarDist model {model_name} successfully loaded.")
|
|
122
|
+
scale_model = scale
|
|
116
123
|
|
|
117
124
|
elif model_type=='cellpose':
|
|
118
|
-
|
|
125
|
+
|
|
126
|
+
import torch
|
|
127
|
+
if not use_gpu:
|
|
128
|
+
device = torch.device("cpu")
|
|
129
|
+
else:
|
|
130
|
+
device = torch.device("cuda")
|
|
131
|
+
|
|
132
|
+
model = CellposeModel(gpu=use_gpu, device=device, pretrained_model=model_path+model_path.split('/')[-2], model_type=None, nchan=len(required_channels))
|
|
119
133
|
if scale is None:
|
|
120
134
|
scale_model = model.diam_mean / model.diam_labels
|
|
121
135
|
else:
|
|
122
136
|
scale_model = scale * model.diam_mean / model.diam_labels
|
|
137
|
+
print(f"Diam mean: {model.diam_mean}; Diam labels: {model.diam_labels}; Final rescaling: {scale_model}...")
|
|
138
|
+
print(f'Cellpose model {model_name} successfully loaded.')
|
|
123
139
|
|
|
124
140
|
labels = []
|
|
125
|
-
if (time_flat_normalization)*normalize:
|
|
126
|
-
normalization_values = get_stack_normalization_values(stack[:,:,:,channel_indices], percentiles=time_flat_percentiles)
|
|
127
|
-
else:
|
|
128
|
-
normalization_values = [None]*len(channel_indices)
|
|
129
141
|
|
|
130
142
|
for t in tqdm(range(len(stack)),desc="frame"):
|
|
131
143
|
|
|
132
144
|
# normalize
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if
|
|
140
|
-
frame =
|
|
145
|
+
channel_indices = np.array(channel_indices)
|
|
146
|
+
none_channel_indices = np.where(channel_indices==None)[0]
|
|
147
|
+
channel_indices[channel_indices==None] = 0
|
|
148
|
+
print(channel_indices)
|
|
149
|
+
|
|
150
|
+
frame = stack[t,:,:,channel_indices.astype(int)].astype(float)
|
|
151
|
+
if frame.ndim==2:
|
|
152
|
+
frame = frame[:,:,np.newaxis]
|
|
153
|
+
if frame.ndim==3 and np.array(frame.shape).argmin()==0:
|
|
154
|
+
frame = np.moveaxis(frame,0,-1)
|
|
155
|
+
template = frame.copy()
|
|
156
|
+
|
|
157
|
+
values = []
|
|
158
|
+
percentiles = []
|
|
159
|
+
for k in range(len(normalization_percentile)):
|
|
160
|
+
if normalization_percentile[k]:
|
|
161
|
+
percentiles.append(normalization_values[k])
|
|
162
|
+
values.append(None)
|
|
163
|
+
else:
|
|
164
|
+
percentiles.append(None)
|
|
165
|
+
values.append(normalization_values[k])
|
|
166
|
+
|
|
167
|
+
frame = normalize_multichannel(frame, **{"percentiles": percentiles, 'values': values, 'clip': normalization_clip})
|
|
168
|
+
|
|
169
|
+
if scale_model is not None:
|
|
170
|
+
frame = [zoom(frame[:,:,c].copy(), [scale_model,scale_model], order=3, prefilter=False) for c in range(frame.shape[-1])]
|
|
171
|
+
frame = np.moveaxis(frame,0,-1)
|
|
172
|
+
|
|
173
|
+
for k in range(frame.shape[2]):
|
|
174
|
+
unique_values = np.unique(frame[:,:,k])
|
|
175
|
+
if len(unique_values)==1:
|
|
176
|
+
frame[0,0,k] += 1
|
|
177
|
+
|
|
178
|
+
frame = np.moveaxis([interpolate_nan(frame[:,:,c].copy()) for c in range(frame.shape[-1])],0,-1)
|
|
179
|
+
frame[:,:,none_channel_indices] = 0.
|
|
141
180
|
|
|
142
181
|
if model_type=="stardist":
|
|
143
182
|
|
|
@@ -145,16 +184,15 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
145
184
|
Y_pred = Y_pred.astype(np.uint16)
|
|
146
185
|
|
|
147
186
|
elif model_type=="cellpose":
|
|
148
|
-
|
|
149
|
-
|
|
187
|
+
|
|
188
|
+
img = np.moveaxis(frame, -1, 0)
|
|
189
|
+
Y_pred, _, _ = model.eval(img, diameter = diameter, cellprob_threshold=cellprob_threshold, flow_threshold=flow_threshold, channels=None, normalize=False)
|
|
150
190
|
Y_pred = Y_pred.astype(np.uint16)
|
|
151
191
|
|
|
152
|
-
if scale is not None:
|
|
153
|
-
Y_pred = ndi.zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
|
|
154
|
-
|
|
155
|
-
|
|
156
192
|
if Y_pred.shape != stack[0].shape[:2]:
|
|
157
|
-
Y_pred =
|
|
193
|
+
Y_pred = zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
|
|
194
|
+
if Y_pred.shape != template.shape[:2]:
|
|
195
|
+
Y_pred = resize(Y_pred, template.shape[:2], order=0)
|
|
158
196
|
|
|
159
197
|
labels.append(Y_pred)
|
|
160
198
|
|
celldetective/utils.py
CHANGED
|
@@ -23,6 +23,7 @@ from tqdm import tqdm
|
|
|
23
23
|
import shutil
|
|
24
24
|
import tempfile
|
|
25
25
|
from scipy.interpolate import griddata
|
|
26
|
+
import re
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def derivative(x, timeline, window, mode='bi'):
|
|
@@ -430,6 +431,31 @@ def mask_edges(binary_mask, border_size):
|
|
|
430
431
|
return binary_mask
|
|
431
432
|
|
|
432
433
|
|
|
434
|
+
def extract_cols_from_query(query: str):
|
|
435
|
+
|
|
436
|
+
# Track variables in a dictionary to be used as a dictionary of globals. From: https://stackoverflow.com/questions/64576913/extract-pandas-dataframe-column-names-from-query-string
|
|
437
|
+
|
|
438
|
+
variables = {}
|
|
439
|
+
|
|
440
|
+
while True:
|
|
441
|
+
try:
|
|
442
|
+
# Try creating a Expr object with the query string and dictionary of globals.
|
|
443
|
+
# This will raise an error as long as the dictionary of globals is incomplete.
|
|
444
|
+
env = pd.core.computation.scope.ensure_scope(level=0, global_dict=variables)
|
|
445
|
+
pd.core.computation.eval.Expr(query, env=env)
|
|
446
|
+
|
|
447
|
+
# Exit the loop when evaluation is successful.
|
|
448
|
+
break
|
|
449
|
+
except pd.errors.UndefinedVariableError as e:
|
|
450
|
+
# This relies on the format defined here: https://github.com/pandas-dev/pandas/blob/965ceca9fd796940050d6fc817707bba1c4f9bff/pandas/errors/__init__.py#L401
|
|
451
|
+
name = re.findall("name '(.+?)' is not defined", str(e))[0]
|
|
452
|
+
|
|
453
|
+
# Add the name to the globals dictionary with a dummy value.
|
|
454
|
+
variables[name] = None
|
|
455
|
+
|
|
456
|
+
return list(variables.keys())
|
|
457
|
+
|
|
458
|
+
|
|
433
459
|
def create_patch_mask(h, w, center=None, radius=None):
|
|
434
460
|
|
|
435
461
|
"""
|
|
@@ -1058,20 +1084,33 @@ def _extract_channel_indices(channels, required_channels):
|
|
|
1058
1084
|
# [0, 1]
|
|
1059
1085
|
"""
|
|
1060
1086
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1087
|
+
channel_indices = []
|
|
1088
|
+
for c in required_channels:
|
|
1089
|
+
if c!='None' and c is not None:
|
|
1065
1090
|
try:
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1091
|
+
ch_idx = channels.index(c)
|
|
1092
|
+
channel_indices.append(ch_idx)
|
|
1093
|
+
except Exception as e:
|
|
1094
|
+
print(f"Error {e}. The channel required by the model is not available in your data... Check the configuration file.")
|
|
1095
|
+
channels = None
|
|
1096
|
+
break
|
|
1097
|
+
else:
|
|
1098
|
+
channel_indices.append(None)
|
|
1070
1099
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1100
|
+
# if channels is not None:
|
|
1101
|
+
# channel_indices = []
|
|
1102
|
+
# for ch in required_channels:
|
|
1103
|
+
|
|
1104
|
+
# try:
|
|
1105
|
+
# idx = channels.index(ch)
|
|
1106
|
+
# except ValueError:
|
|
1107
|
+
# print('Mismatch between the channels required by the model and the provided channels.')
|
|
1108
|
+
# return None
|
|
1109
|
+
|
|
1110
|
+
# channel_indices.append(idx)
|
|
1111
|
+
# channel_indices = np.array(channel_indices)
|
|
1112
|
+
# else:
|
|
1113
|
+
# channel_indices = np.arange(len(required_channels))
|
|
1075
1114
|
|
|
1076
1115
|
return channel_indices
|
|
1077
1116
|
|