celldetective 1.0.2.post1__py3-none-any.whl → 1.1.1__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 (63) hide show
  1. celldetective/__main__.py +7 -21
  2. celldetective/events.py +2 -44
  3. celldetective/extra_properties.py +62 -52
  4. celldetective/filters.py +4 -5
  5. celldetective/gui/__init__.py +1 -1
  6. celldetective/gui/analyze_block.py +37 -10
  7. celldetective/gui/btrack_options.py +24 -23
  8. celldetective/gui/classifier_widget.py +62 -19
  9. celldetective/gui/configure_new_exp.py +32 -35
  10. celldetective/gui/control_panel.py +120 -81
  11. celldetective/gui/gui_utils.py +674 -396
  12. celldetective/gui/json_readers.py +7 -6
  13. celldetective/gui/layouts.py +756 -0
  14. celldetective/gui/measurement_options.py +98 -513
  15. celldetective/gui/neighborhood_options.py +322 -270
  16. celldetective/gui/plot_measurements.py +1114 -0
  17. celldetective/gui/plot_signals_ui.py +21 -20
  18. celldetective/gui/process_block.py +449 -169
  19. celldetective/gui/retrain_segmentation_model_options.py +27 -26
  20. celldetective/gui/retrain_signal_model_options.py +25 -24
  21. celldetective/gui/seg_model_loader.py +31 -27
  22. celldetective/gui/signal_annotator.py +2326 -2295
  23. celldetective/gui/signal_annotator_options.py +18 -16
  24. celldetective/gui/styles.py +16 -1
  25. celldetective/gui/survival_ui.py +67 -39
  26. celldetective/gui/tableUI.py +337 -48
  27. celldetective/gui/thresholds_gui.py +75 -71
  28. celldetective/gui/viewers.py +743 -0
  29. celldetective/io.py +247 -27
  30. celldetective/measure.py +43 -263
  31. celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
  32. celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  33. celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
  34. celldetective/neighborhood.py +498 -27
  35. celldetective/preprocessing.py +1023 -0
  36. celldetective/scripts/analyze_signals.py +7 -0
  37. celldetective/scripts/measure_cells.py +12 -0
  38. celldetective/scripts/segment_cells.py +20 -4
  39. celldetective/scripts/track_cells.py +11 -0
  40. celldetective/scripts/train_segmentation_model.py +35 -34
  41. celldetective/segmentation.py +14 -9
  42. celldetective/signals.py +234 -329
  43. celldetective/tracking.py +2 -2
  44. celldetective/utils.py +602 -49
  45. celldetective-1.1.1.dist-info/METADATA +305 -0
  46. celldetective-1.1.1.dist-info/RECORD +84 -0
  47. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/top_level.txt +1 -0
  48. tests/__init__.py +0 -0
  49. tests/test_events.py +28 -0
  50. tests/test_filters.py +24 -0
  51. tests/test_io.py +70 -0
  52. tests/test_measure.py +141 -0
  53. tests/test_neighborhood.py +70 -0
  54. tests/test_preprocessing.py +37 -0
  55. tests/test_segmentation.py +93 -0
  56. tests/test_signals.py +135 -0
  57. tests/test_tracking.py +164 -0
  58. tests/test_utils.py +118 -0
  59. celldetective-1.0.2.post1.dist-info/METADATA +0 -221
  60. celldetective-1.0.2.post1.dist-info/RECORD +0 -66
  61. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/LICENSE +0 -0
  62. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/WHEEL +0 -0
  63. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,7 @@ Copright © 2022 Laboratoire Adhesion et Inflammation, Authored by Remy Torro.
3
3
  """
4
4
 
5
5
  import argparse
6
+ import datetime
6
7
  import os
7
8
  import gc
8
9
  from art import tprint
@@ -45,6 +46,12 @@ else:
45
46
  print('The trajectories table could not be found. Abort.')
46
47
  os.abort()
47
48
 
49
+ log=f'segmentation model: {model} \n'
50
+
51
+ with open(pos+f'log_{mode}.json', 'a') as f:
52
+ f.write(f'{datetime.datetime.now()} SIGNAL ANALYSIS \n')
53
+ f.write(log)
54
+
48
55
  trajectories = analyze_signals(trajectories.copy(), model, interpolate_na=True, selected_signals=None, column_labels = column_labels, plot_outcome=True,output_dir=pos+'output/')
49
56
  trajectories = trajectories.sort_values(by=[column_labels['track'], column_labels['time']])
50
57
  trajectories.to_csv(pos+os.sep.join(['output','tables', table_name]), index=False)
@@ -20,6 +20,7 @@ from natsort import natsorted
20
20
  from art import tprint
21
21
  from tifffile import imread
22
22
  import threading
23
+ import datetime
23
24
 
24
25
  tprint("Measure")
25
26
 
@@ -195,6 +196,17 @@ if trajectories is None:
195
196
  print('Use features as a substitute for the trajectory table.')
196
197
  if 'label' not in features:
197
198
  features.append('label')
199
+ features_log=f'features: {features}'
200
+ border_distances_log=f'border_distances: {border_distances}'
201
+ haralick_options_log=f'haralick_options: {haralick_options}'
202
+ background_correction_log=f'background_correction: {background_correction}'
203
+ spot_detection_log=f'spot_detection: {spot_detection}'
204
+ intensity_measurement_radii_log=f'intensity_measurement_radii: {intensity_measurement_radii}'
205
+ isotropic_options_log=f'isotropic_operations: {isotropic_operations} \n'
206
+ log='\n'.join([features_log,border_distances_log,haralick_options_log,background_correction_log,spot_detection_log,intensity_measurement_radii_log,isotropic_options_log])
207
+ with open(pos + f'log_{mode}.json', 'a') as f:
208
+ f.write(f'{datetime.datetime.now()} MEASURE \n')
209
+ f.write(log+'\n')
198
210
 
199
211
 
200
212
  def measure_index(indices):
@@ -3,6 +3,7 @@ Copright © 2022 Laboratoire Adhesion et Inflammation, Authored by Remy Torro.
3
3
  """
4
4
 
5
5
  import argparse
6
+ import datetime
6
7
  import os
7
8
  import json
8
9
  from stardist.models import StarDist2D
@@ -21,6 +22,9 @@ from art import tprint
21
22
  from scipy.ndimage import zoom
22
23
  import threading
23
24
 
25
+ import matplotlib.pyplot as plt
26
+ import time
27
+
24
28
  tprint("Segment")
25
29
 
26
30
  parser = argparse.ArgumentParser(description="Segment a movie in position with the selected model",
@@ -128,6 +132,10 @@ if os.path.exists(os.sep.join([pos,label_folder])):
128
132
  rmtree(os.sep.join([pos,label_folder]))
129
133
  os.mkdir(os.sep.join([pos,label_folder]))
130
134
  print(f'Folder {os.sep.join([pos,label_folder])} successfully generated.')
135
+ log=f'segmentation model: {modelname}\n'
136
+ with open(pos+f'log_{mode}.json', 'a') as f:
137
+ f.write(f'{datetime.datetime.now()} SEGMENT \n')
138
+ f.write(log)
131
139
 
132
140
 
133
141
  # Loop over all frames and segment
@@ -154,10 +162,18 @@ def segment_index(indices):
154
162
  for t in tqdm(indices,desc="frame"):
155
163
 
156
164
  # Load channels at time t
157
- f = load_frames(img_num_channels[:,t], file, scale=scale, normalize_input=False)
158
- f = normalize_per_channel([f], normalization_percentile_mode=normalization_percentile, normalization_values=normalization_values,
159
- normalization_clipping=normalization_clip)
160
- f = f[0]
165
+ values = []
166
+ percentiles = []
167
+ for k in range(len(normalization_percentile)):
168
+ if normalization_percentile[k]:
169
+ percentiles.append(normalization_values[k])
170
+ values.append(None)
171
+ else:
172
+ percentiles.append(None)
173
+ values.append(normalization_values[k])
174
+
175
+ f = load_frames(img_num_channels[:,t], file, scale=scale, normalize_input=True, normalize_kwargs={"percentiles": percentiles, 'values': values, 'clip': normalization_clip})
176
+
161
177
  if np.any(img_num_channels[:,t]==-1):
162
178
  f[:,:,np.where(img_num_channels[:,t]==-1)[0]] = 0.
163
179
 
@@ -3,6 +3,7 @@ Copright © 2022 Laboratoire Adhesion et Inflammation, Authored by Remy Torro.
3
3
  """
4
4
 
5
5
  import argparse
6
+ import datetime
6
7
  import os
7
8
  import json
8
9
  from celldetective.io import auto_load_number_of_frames, load_frames, interpret_tracking_configuration
@@ -139,6 +140,16 @@ img_num_channels = _get_img_num_per_channel(channel_indices, len_movie, nbr_chan
139
140
  #######################################
140
141
 
141
142
  timestep_dataframes = []
143
+ features_log=f'features: {features}'
144
+ mask_channels_log=f'mask_channels: {mask_channels}'
145
+ haralick_option_log=f'haralick_options: {haralick_options}'
146
+ post_processing_option_log=f'post_processing_options: {post_processing_options}'
147
+ log_list=[features_log, mask_channels_log, haralick_option_log, post_processing_option_log]
148
+ log='\n'.join(log_list)
149
+
150
+ with open(pos+f'log_{mode}.json', 'a') as f:
151
+ f.write(f'{datetime.datetime.now()} TRACK \n')
152
+ f.write(log+"\n")
142
153
 
143
154
  def measure_index(indices):
144
155
  for t in tqdm(indices,desc="frame"):
@@ -11,28 +11,15 @@ from tqdm import tqdm
11
11
  import numpy as np
12
12
  import random
13
13
 
14
- from celldetective.utils import load_image_dataset, normalize_per_channel, augmenter
14
+ from celldetective.utils import load_image_dataset, normalize_per_channel, augmenter, interpolate_nan
15
+ from celldetective.io import normalize_multichannel
15
16
  from stardist import fill_label_holes
16
17
  from art import tprint
17
18
  import matplotlib.pyplot as plt
18
19
 
19
- def interpolate_nan(array_like):
20
- array = array_like.copy()
21
-
22
- isnan_array = ~np.isnan(array)
23
-
24
- xp = isnan_array.ravel().nonzero()[0]
25
-
26
- fp = array[~np.isnan(array)]
27
- x = np.isnan(array).ravel().nonzero()[0]
28
-
29
- array[np.isnan(array)] = np.interp(x, xp, fp)
30
-
31
- return array
32
20
 
33
21
  tprint("Train")
34
22
 
35
-
36
23
  parser = argparse.ArgumentParser(description="Train a signal model from instructions.",
37
24
  formatter_class=argparse.ArgumentDefaultsHelpFormatter)
38
25
  parser.add_argument('-c',"--config", required=True,help="Training instructions")
@@ -78,25 +65,39 @@ X,Y = load_image_dataset(datasets, target_channels, train_spatial_calibration=sp
78
65
  print('Dataset loaded...')
79
66
 
80
67
  # Normalize images
81
- X = normalize_per_channel(X,
82
- normalization_percentile_mode=normalization_percentile,
83
- normalization_values=normalization_values,
84
- normalization_clipping=normalization_clip
85
- )
86
-
87
- for x in X:
88
- plt.imshow(x[:,:,0])
89
- plt.xlim(0,1004)
90
- plt.ylim(0,1002)
91
- plt.colorbar()
92
- plt.pause(2)
93
- plt.close()
94
- print(x.shape)
95
- interp = interpolate_nan(x)
96
- print(interp.shape)
97
- print(np.any(np.isnan(x).flatten()))
98
- print(np.any(np.isnan(interp).flatten()))
99
-
68
+ # X = normalize_per_channel(X,
69
+ # normalization_percentile_mode=normalization_percentile,
70
+ # normalization_values=normalization_values,
71
+ # normalization_clipping=normalization_clip
72
+ # )
73
+
74
+ values = []
75
+ percentiles = []
76
+ for k in range(len(normalization_percentile)):
77
+ if normalization_percentile[k]:
78
+ percentiles.append(normalization_values[k])
79
+ values.append(None)
80
+ else:
81
+ percentiles.append(None)
82
+ values.append(normalization_values[k])
83
+
84
+ X = [normalize_multichannel(x, **{"percentiles": percentiles, 'values': values, 'clip': normalization_clip}) for x in X]
85
+
86
+ for k in range(len(X)):
87
+ x = X[k].copy()
88
+ x_interp = np.moveaxis([interpolate_nan(x[:,:,c].copy()) for c in range(x.shape[-1])],0,-1)
89
+ X[k] = x_interp
90
+
91
+ # for x in X[:10]:
92
+ # plt.imshow(x[:,:,0])
93
+ # plt.colorbar()
94
+ # plt.pause(2)
95
+ # plt.close()
96
+
97
+ # plt.imshow(x[:,:,1])
98
+ # plt.colorbar()
99
+ # plt.pause(2)
100
+ # plt.close()
100
101
 
101
102
  Y = [fill_label_holes(y) for y in tqdm(Y)]
102
103
 
@@ -13,7 +13,8 @@ from cellpose.models import CellposeModel
13
13
  from skimage.transform import resize
14
14
  from celldetective.io import _view_on_napari, locate_labels, locate_stack, _view_on_napari
15
15
  from celldetective.filters import * #rework this to give a name
16
- from celldetective.utils import rename_intensity_column
16
+ from celldetective.utils import rename_intensity_column, mask_edges, estimate_unreliable_edge
17
+ from stardist import fill_label_holes
17
18
  import scipy.ndimage as ndi
18
19
  from skimage.segmentation import watershed
19
20
  from skimage.feature import peak_local_max
@@ -132,7 +133,7 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
132
133
  frame = normalize_multichannel(frame, values=normalization_values)
133
134
 
134
135
  if scale is not None:
135
- frame = ndi.zoom(frame, [scale,scale,1], order=3)
136
+ frame = [ndi.zoom(frame[:,:,c].copy(), [scale,scale], order=3, prefilter=False) for c in range(frame.shape[-1])]
136
137
 
137
138
  if model_type=="stardist":
138
139
 
@@ -273,7 +274,8 @@ def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equa
273
274
  img = match_histograms(img, equalize_reference)
274
275
  img_mc = frame.copy()
275
276
  img = filter_image(img, filters=filters)
276
- binary_image = threshold_image(img, thresholds[0], thresholds[1])
277
+ edge = estimate_unreliable_edge(filters)
278
+ binary_image = threshold_image(img, thresholds[0], thresholds[1], fill_holes=True, edge_exclusion=edge)
277
279
  coords,distance = identify_markers_from_binary(binary_image, marker_min_distance, footprint_size=marker_footprint_size, footprint=marker_footprint, return_edt=True)
278
280
  instance_seg = apply_watershed(binary_image, coords, distance)
279
281
  instance_seg = filter_on_property(instance_seg, intensity_image=img_mc, queries=feature_queries, channel_names=channel_names)
@@ -357,7 +359,7 @@ def filter_on_property(labels, intensity_image=None, queries=None, channel_names
357
359
  return labels
358
360
 
359
361
 
360
- def apply_watershed(binary_image, coords, distance):
362
+ def apply_watershed(binary_image, coords, distance, fill_holes=True):
361
363
 
362
364
  """
363
365
  Applies the watershed algorithm to segment objects in a binary image using given markers and distance map.
@@ -404,7 +406,8 @@ def apply_watershed(binary_image, coords, distance):
404
406
  mask[tuple(coords.T)] = True
405
407
  markers, _ = ndi.label(mask)
406
408
  labels = watershed(-distance, markers, mask=binary_image)
407
-
409
+ if fill_holes:
410
+ labels = fill_label_holes(labels)
408
411
  return labels
409
412
 
410
413
  def identify_markers_from_binary(binary_image, min_distance, footprint_size=20, footprint=None, return_edt=False):
@@ -457,7 +460,7 @@ def identify_markers_from_binary(binary_image, min_distance, footprint_size=20,
457
460
  return coords
458
461
 
459
462
 
460
- def threshold_image(img, min_threshold, max_threshold, foreground_value=255., fill_holes=True):
463
+ def threshold_image(img, min_threshold, max_threshold, foreground_value=255., fill_holes=True, edge_exclusion=None):
461
464
 
462
465
  """
463
466
 
@@ -496,10 +499,12 @@ def threshold_image(img, min_threshold, max_threshold, foreground_value=255., fi
496
499
 
497
500
  """
498
501
 
499
-
500
- binary = (img>=min_threshold)*(img<=max_threshold) * foreground_value
502
+ binary = np.zeros_like(img).astype(bool)
503
+ binary[img==img] = (img[img==img]>=min_threshold)*(img[img==img]<=max_threshold) * foreground_value
504
+ if isinstance(edge_exclusion, (int,np.int_)):
505
+ binary = mask_edges(binary, edge_exclusion)
501
506
  if fill_holes:
502
- binary = ndi.binary_fill_holes(binary)
507
+ binary = ndi.binary_fill_holes(binary.astype(int))
503
508
  return binary
504
509
 
505
510
  def filter_image(img, filters=None):