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
@@ -0,0 +1,70 @@
1
+ import unittest
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import os
5
+
6
+ # class TestPatchMask(unittest.TestCase):
7
+
8
+ # @classmethod
9
+ # def setUpClass(self):
10
+ # self.radius = 3
11
+
12
+ # def test_correct_shape(self):
13
+ # self.patch = create_patch_mask(self.radius, self.radius)
14
+ # self.assertEqual(self.patch.shape,(3,3))
15
+
16
+ # def test_correct_ring(self):
17
+ # self.patch = create_patch_mask(5, 5,radius=[1,2])
18
+ # self.assertFalse(self.patch[2,2])
19
+
20
+ # class TestRemoveRedundantFeatures(unittest.TestCase):
21
+
22
+ # @classmethod
23
+ # def setUpClass(self):
24
+ # self.list_a = ['feat1','feat2','feat3','feat4','intensity_mean']
25
+ # self.list_b = ['feat5','feat2','feat1','feat6','test_channel_mean']
26
+ # self.expected = ['feat3','feat4']
27
+
28
+ # def test_remove_red_features(self):
29
+ # self.assertEqual(remove_redundant_features(self.list_a, self.list_b, channel_names=['test_channel']), self.expected)
30
+
31
+
32
+ # class TestExtractChannelIndices(unittest.TestCase):
33
+
34
+ # @classmethod
35
+ # def setUpClass(self):
36
+ # self.channels = ['ch1','ch2','ch3','ch4']
37
+ # self.required_channels = ['ch4','ch2']
38
+ # self.expected_indices = [3,1]
39
+
40
+ # def test_extracted_channels_are_correct(self):
41
+ # self.assertEqual(list(_extract_channel_indices(self.channels, self.required_channels)), self.expected_indices)
42
+
43
+
44
+ # class TestImgIndexPerChannel(unittest.TestCase):
45
+
46
+ # @classmethod
47
+ # def setUpClass(self):
48
+ # self.channels_indices = [1]
49
+ # self.len_movie = 5
50
+ # self.nbr_channels = 3
51
+ # self.expected_indices = [1,4,7,10,13]
52
+
53
+ # def test_index_sequence_is_correct(self):
54
+ # self.assertEqual(list(_get_img_num_per_channel(self.channels_indices, self.len_movie, self.nbr_channels)[0]), self.expected_indices)
55
+
56
+
57
+ # class TestSplitArrayByRatio(unittest.TestCase):
58
+
59
+ # @classmethod
60
+ # def setUpClass(self):
61
+ # self.array_length = 100
62
+ # self.array = np.ones(self.array_length)
63
+
64
+ # def test_ratio_split_is_correct(self):
65
+ # split_array = split_by_ratio(self.array,0.5,0.25,0.1)
66
+ # self.assertTrue(np.all([len(split_array[0])==50, len(split_array[1])==25, len(split_array[2])==10]))
67
+
68
+
69
+ # if __name__=="__main__":
70
+ # unittest.main()
@@ -0,0 +1,37 @@
1
+ import unittest
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import os
5
+ from celldetective.preprocessing import fit_background_model, field_correction
6
+
7
+ import matplotlib.pyplot as plt
8
+
9
+ class TestFitPlane(unittest.TestCase):
10
+
11
+ @classmethod
12
+ def setUpClass(self):
13
+ a = 5.
14
+ self.img = np.full((100,100),5.0)
15
+ self.img_with_cell = self.img.copy()
16
+ self.img_with_cell[:10,:10] = 25.0
17
+
18
+ def test_plane_is_well_fit(self):
19
+ mat = np.array(fit_background_model(self.img, cell_masks=None, model='plane', edge_exclusion=None))
20
+ self.assertTrue(np.allclose(self.img, mat))
21
+
22
+ def test_plane_is_well_fit_and_applied_with_division(self):
23
+ result = field_correction(self.img, threshold_on_std=1.0E05, operation='divide', model='plane', clip=False, return_bg=False, activation_protocol=[])
24
+ self.assertTrue(np.allclose(result, np.full((100,100), 1.0)))
25
+
26
+ def test_plane_is_well_fit_and_applied_with_subtraction(self):
27
+ result = field_correction(self.img, threshold_on_std=1.0E05, operation='subtract', model='plane', clip=False, return_bg=False, activation_protocol=[])
28
+ self.assertTrue(np.allclose(result, np.zeros((100,100))))
29
+
30
+ def test_plane_is_well_fit_with_cell(self):
31
+ cell_masks = np.zeros_like(self.img)
32
+ cell_masks[:10,:10] = 1.0
33
+ mat = np.array(fit_background_model(self.img, cell_masks=cell_masks, model='plane', edge_exclusion=None))
34
+ self.assertTrue(np.allclose(self.img, mat))
35
+
36
+ if __name__=="__main__":
37
+ unittest.main()
@@ -0,0 +1,93 @@
1
+ import unittest
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import os
5
+ import json
6
+ from tifffile import imread
7
+ from celldetective.segmentation import segment, segment_frame_from_thresholds
8
+ from tensorflow.keras.metrics import BinaryIoU
9
+
10
+ TEST_IMAGE_FILENAME = os.path.join(os.path.dirname(__file__), os.sep.join(['assets','sample.tif']))
11
+ TEST_LABEL_FILENAME = os.path.join(os.path.dirname(__file__), os.sep.join(['assets','sample_labelled.tif']))
12
+ TEST_CONFIG_FILENAME = os.path.join(os.path.dirname(__file__), os.sep.join(['assets','sample.json']))
13
+
14
+ class TestDLMCF7Segmentation(unittest.TestCase):
15
+
16
+ @classmethod
17
+ def setUpClass(self):
18
+ self.img = imread(TEST_IMAGE_FILENAME)
19
+ self.label_true = imread(TEST_LABEL_FILENAME)
20
+ self.stack = np.moveaxis([self.img, self.img, self.img],1,-1)
21
+ with open(TEST_CONFIG_FILENAME) as config_file:
22
+ self.config = json.load(config_file)
23
+ self.channels = self.config['channels']
24
+ self.spatial_calibration = self.config['spatial_calibration']
25
+
26
+ def test_correct_segmentation_with_multimodal_model(self):
27
+
28
+ labels = segment(self.stack, "MCF7_bf_pi_cfse_h", channels=self.channels, spatial_calibration=self.spatial_calibration, view_on_napari=False,
29
+ use_gpu=False)
30
+ np.testing.assert_array_equal(labels[0], labels[1])
31
+
32
+ self.binary_label_true = self.label_true.copy().astype(float)
33
+ self.binary_label_true[self.binary_label_true>0] = 1.
34
+
35
+ label_binary = labels[0].copy().astype(float)
36
+ label_binary[label_binary>0] = 1.
37
+
38
+ m = BinaryIoU(target_class_ids=[1])
39
+ m.update_state(self.binary_label_true, label_binary)
40
+ score = m.result().numpy()
41
+
42
+ self.assertGreater(score,0.9)
43
+
44
+ def test_correct_segmentation_with_transferred_model(self):
45
+
46
+ labels = segment(self.stack, "MCF7_h_versatile", channels=self.channels, spatial_calibration=self.spatial_calibration, view_on_napari=False,
47
+ use_gpu=True, time_flat_normalization=False, time_flat_percentiles=(0.0,99.99))
48
+ np.testing.assert_array_equal(labels[0], labels[1])
49
+
50
+ self.binary_label_true = self.label_true.copy().astype(float)
51
+ self.binary_label_true[self.binary_label_true>0] = 1.
52
+
53
+ label_binary = labels[0].copy().astype(float)
54
+ label_binary[label_binary>0] = 1.
55
+
56
+ m = BinaryIoU(target_class_ids=[1])
57
+ m.update_state(self.binary_label_true, label_binary)
58
+ score = m.result().numpy()
59
+
60
+ self.assertGreater(score,0.9)
61
+
62
+
63
+ class TestThresholdMCF7Segmentation(unittest.TestCase):
64
+
65
+ @classmethod
66
+ def setUpClass(self):
67
+ self.img = imread(TEST_IMAGE_FILENAME)
68
+ self.label_true = imread(TEST_LABEL_FILENAME)
69
+ with open(TEST_CONFIG_FILENAME) as config_file:
70
+ self.config = json.load(config_file)
71
+ self.channels = self.config['channels']
72
+ self.spatial_calibration = self.config['spatial_calibration']
73
+
74
+ def test_correct_segmentation_with_threshold(self):
75
+
76
+ label = segment_frame_from_thresholds(np.moveaxis(self.img,0,-1), target_channel=3, thresholds=[8000,1.0E10], equalize_reference=None,
77
+ filters=[['variance',4],['gauss',2]], marker_min_distance=13, marker_footprint_size=34, marker_footprint=None, feature_queries=["area < 80"], channel_names=None)
78
+
79
+ self.binary_label_true = self.label_true.copy().astype(float)
80
+ self.binary_label_true[self.binary_label_true>0] = 1.
81
+
82
+ label_binary = label.copy().astype(float)
83
+ label_binary[label_binary>0] = 1.
84
+
85
+ m = BinaryIoU(target_class_ids=[1])
86
+ m.update_state(self.binary_label_true, label_binary)
87
+ score = m.result().numpy()
88
+
89
+ self.assertGreater(score,0.7)
90
+
91
+
92
+ if __name__=="__main__":
93
+ unittest.main()
tests/test_signals.py ADDED
@@ -0,0 +1,135 @@
1
+ import unittest
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import os
5
+ import random
6
+ import pandas as pd
7
+ import shutil
8
+
9
+ def sigmoid(t,t0,dt,A,offset):
10
+ return A/(1+np.exp(-(t-t0)/dt)) + offset
11
+
12
+ def generate_fake_signal_data(n_signals):
13
+
14
+ timeline = np.linspace(0,100,100)
15
+ amplitudes = list(np.linspace(2000,3000,100))
16
+ slopes = list(np.linspace(0.5,5,100))
17
+ means = list(np.linspace(-100,200,100))
18
+ random_cut = list(np.linspace(25,200,176,dtype=int))
19
+ noise_levels = list(np.linspace(1,100,100,dtype=int))
20
+
21
+ trajectories = []
22
+ for i in range(n_signals):
23
+
24
+ a = random.sample(amplitudes,k=1)[0]
25
+ dt = random.sample(slopes,k=1)[0]
26
+ mu = random.sample(means,k=1)[0]
27
+ cut = random.sample(random_cut,k=1)[0]
28
+ n = random.sample(noise_levels,k=1)[0]
29
+
30
+ if mu<=0.:
31
+ cclass=2
32
+ t0=-1
33
+ elif (mu>0)*(mu<=100):
34
+ cclass=0
35
+ t0=mu
36
+ else:
37
+ cclass=1
38
+ t0=-1
39
+
40
+ noise = [random.random()*n for i in range(len(timeline))]
41
+ signal = sigmoid(timeline, mu, dt,a,0)+noise
42
+ signal = signal[:cut]
43
+ if mu>=cut:
44
+ cclass=1
45
+ t0=-1
46
+
47
+ for j in range(len(signal)):
48
+ trajectories.append({'TRACK_ID': i, 'POSITION_X': 0., 'POSITION_Y': 0., 'FRAME': j,'signal': signal[j], 't0': t0, 'cclass': cclass})
49
+
50
+ trajectories = pd.DataFrame(trajectories)
51
+
52
+ return trajectories
53
+
54
+ def export_set(trajectories, name='set.npy', output_folder='.'):
55
+
56
+ training_set = []
57
+ cols = trajectories.columns
58
+ tracks = np.unique(trajectories["TRACK_ID"].to_numpy())
59
+
60
+ for track in tracks:
61
+ signals = {}
62
+ for c in cols:
63
+ signals.update({c: trajectories.loc[trajectories["TRACK_ID"] == track, c].to_numpy()})
64
+ time_of_interest = trajectories.loc[trajectories["TRACK_ID"] == track, "t0"].to_numpy()[0]
65
+ cclass = trajectories.loc[trajectories["TRACK_ID"] == track, "cclass"].to_numpy()[0]
66
+ signals.update({"time_of_interest": time_of_interest, "class": cclass})
67
+ training_set.append(signals)
68
+
69
+ np.save(os.sep.join([output_folder,name]), training_set)
70
+
71
+
72
+ class TestCreateSignalModel(unittest.TestCase):
73
+
74
+ def test_create_model(self):
75
+
76
+ from celldetective.signals import SignalDetectionModel
77
+
78
+ model = SignalDetectionModel(
79
+ channel_option=["signal"],
80
+ model_signal_length=128,
81
+ n_channels=1,
82
+ n_conv=2,
83
+ n_classes=3,
84
+ dense_collection=512,
85
+ dropout_rate=0.1,
86
+ label='test',
87
+ )
88
+
89
+
90
+ class TestTrainSignalModel(unittest.TestCase):
91
+
92
+ @classmethod
93
+ def setUpClass(self):
94
+
95
+ from celldetective.signals import SignalDetectionModel
96
+
97
+ self.trajectories = generate_fake_signal_data(300)
98
+ if not os.path.exists('temp'):
99
+ os.mkdir('temp')
100
+ export_set(self.trajectories, name='set.npy', output_folder='temp')
101
+ self.model = SignalDetectionModel(
102
+ channel_option=["signal"],
103
+ model_signal_length=128,
104
+ n_channels=1,
105
+ n_conv=2,
106
+ n_classes=3,
107
+ dense_collection=512,
108
+ dropout_rate=0.1,
109
+ label='test',
110
+ )
111
+
112
+ def test_train_signal_model(self):
113
+
114
+ self.model.fit_from_directory(
115
+ ['temp'],
116
+ normalize=True,
117
+ normalization_percentile=None,
118
+ normalization_values = None,
119
+ normalization_clip = None,
120
+ channel_option=["signal"],
121
+ target_directory='temp',
122
+ augment=False,
123
+ model_name='None',
124
+ validation_split=0.2,
125
+ test_split=0.1,
126
+ batch_size = 16,
127
+ epochs=1,
128
+ recompile_pretrained=False,
129
+ learning_rate=0.01,
130
+ show_plots=False,
131
+ )
132
+ shutil.rmtree('temp')
133
+
134
+ if __name__=="__main__":
135
+ unittest.main()
tests/test_tracking.py ADDED
@@ -0,0 +1,164 @@
1
+ import unittest
2
+ import numpy as np
3
+ import pandas as pd
4
+ from celldetective.tracking import filter_by_endpoints, extrapolate_tracks, filter_by_tracklength, interpolate_time_gaps
5
+
6
+ class TestTrackFilteringByEndpoint(unittest.TestCase):
7
+
8
+ @classmethod
9
+ def setUpClass(self):
10
+ self.tracks = pd.DataFrame([{"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
11
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
12
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 30, "POSITION_Y": 5},
13
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 40, "POSITION_Y": 0},
14
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
15
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
16
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
17
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 10, "POSITION_Y": 25}
18
+
19
+ ])
20
+
21
+ def test_filter_not_in_last(self):
22
+ self.filtered_tracks = filter_by_endpoints(self.tracks, remove_not_in_first=False, remove_not_in_last=True)
23
+ track_ids = list(self.filtered_tracks['TRACK_ID'].unique())
24
+ self.assertEqual(track_ids,[0.])
25
+
26
+ def test_filter_not_in_first(self):
27
+ self.filtered_tracks = filter_by_endpoints(self.tracks, remove_not_in_first=True, remove_not_in_last=False)
28
+ track_ids = list(self.filtered_tracks['TRACK_ID'].unique())
29
+ self.assertEqual(track_ids,[0.,2.])
30
+
31
+ def test_no_filter_does_nothing(self):
32
+ self.filtered_tracks = filter_by_endpoints(self.tracks, remove_not_in_first=False, remove_not_in_last=False)
33
+ track_ids = list(self.filtered_tracks['TRACK_ID'].unique())
34
+ self.assertEqual(track_ids,list(self.tracks['TRACK_ID'].unique()))
35
+
36
+
37
+ class TestTrackFilteringByLength(unittest.TestCase):
38
+
39
+ @classmethod
40
+ def setUpClass(self):
41
+ self.tracks = pd.DataFrame([{"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
42
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
43
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 30, "POSITION_Y": 5},
44
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 40, "POSITION_Y": 0},
45
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
46
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
47
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
48
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 10, "POSITION_Y": 25}
49
+ ])
50
+
51
+ def test_filter_by_tracklength_of_zero(self):
52
+ self.filtered_tracks = filter_by_tracklength(self.tracks, minimum_tracklength=0)
53
+ track_ids = list(self.filtered_tracks['TRACK_ID'].unique())
54
+ self.assertEqual(track_ids,[0.,1.,2.])
55
+
56
+ def test_filter_by_tracklength_of_three(self):
57
+ self.filtered_tracks = filter_by_tracklength(self.tracks, minimum_tracklength=3)
58
+ track_ids = list(self.filtered_tracks['TRACK_ID'].unique())
59
+ self.assertEqual(track_ids,[0.])
60
+
61
+
62
+ class TestTrackInterpolation(unittest.TestCase):
63
+
64
+ @classmethod
65
+ def setUpClass(self):
66
+
67
+ self.tracks = pd.DataFrame([{"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
68
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
69
+ #{"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
70
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
71
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
72
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
73
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
74
+ #{"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
75
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25}
76
+ ])
77
+ self.tracks_real_intep = pd.DataFrame([{"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
78
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
79
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
80
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
81
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
82
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
83
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
84
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
85
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25}
86
+ ])
87
+
88
+
89
+ def test_interpolate_tracks_as_expected(self):
90
+ self.interpolated_tracks = interpolate_time_gaps(self.tracks)
91
+ self.assertTrue(np.array_equal(self.interpolated_tracks.to_numpy(), self.tracks_real_intep.to_numpy(), equal_nan=True))
92
+
93
+ class TestTrackExtrapolation(unittest.TestCase):
94
+
95
+ @classmethod
96
+ def setUpClass(self):
97
+
98
+ self.tracks = pd.DataFrame([{"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
99
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
100
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
101
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
102
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
103
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
104
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
105
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
106
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25}
107
+ ])
108
+ self.tracks_pre_extrapol = pd.DataFrame([
109
+ {"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
110
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
111
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
112
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
113
+ {"TRACK_ID": 1., "FRAME": 0, "POSITION_X": 5, "POSITION_Y": 20},
114
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
115
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
116
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
117
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
118
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25}
119
+ ])
120
+ self.tracks_post_extrapol = pd.DataFrame([
121
+ {"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
122
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
123
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
124
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
125
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
126
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
127
+ {"TRACK_ID": 1., "FRAME": 3, "POSITION_X": 10, "POSITION_Y": 25},
128
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
129
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
130
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25},
131
+ {"TRACK_ID": 2., "FRAME": 3, "POSITION_X": 0, "POSITION_Y": 25}
132
+ ])
133
+
134
+ self.tracks_full_extrapol = pd.DataFrame([
135
+ {"TRACK_ID": 0., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 15},
136
+ {"TRACK_ID": 0., "FRAME": 1, "POSITION_X": 15, "POSITION_Y": 10},
137
+ {"TRACK_ID": 0., "FRAME": 2, "POSITION_X": 20, "POSITION_Y": 5},
138
+ {"TRACK_ID": 0., "FRAME": 3, "POSITION_X": 25, "POSITION_Y": 0},
139
+ {"TRACK_ID": 1., "FRAME": 0, "POSITION_X": 5, "POSITION_Y": 20},
140
+ {"TRACK_ID": 1., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 20},
141
+ {"TRACK_ID": 1., "FRAME": 2, "POSITION_X": 10, "POSITION_Y": 25},
142
+ {"TRACK_ID": 1., "FRAME": 3, "POSITION_X": 10, "POSITION_Y": 25},
143
+ {"TRACK_ID": 2., "FRAME": 0, "POSITION_X": 10, "POSITION_Y": 25},
144
+ {"TRACK_ID": 2., "FRAME": 1, "POSITION_X": 5, "POSITION_Y": 25},
145
+ {"TRACK_ID": 2., "FRAME": 2, "POSITION_X": 0, "POSITION_Y": 25},
146
+ {"TRACK_ID": 2., "FRAME": 3, "POSITION_X": 0, "POSITION_Y": 25}
147
+ ])
148
+
149
+
150
+ def test_pre_extrapolate(self):
151
+ self.extrapolated_tracks = extrapolate_tracks(self.tracks,post=False, pre=True)
152
+ self.assertTrue(np.array_equal(self.extrapolated_tracks.to_numpy(), self.tracks_pre_extrapol.to_numpy(), equal_nan=True))
153
+
154
+ def test_post_extrapolate(self):
155
+ self.extrapolated_tracks = extrapolate_tracks(self.tracks,post=True, pre=False)
156
+ self.assertTrue(np.array_equal(self.extrapolated_tracks.to_numpy(), self.tracks_post_extrapol.to_numpy(), equal_nan=True))
157
+
158
+ def test_full_extrapolate(self):
159
+ self.extrapolated_tracks = extrapolate_tracks(self.tracks,post=True, pre=True)
160
+ self.assertTrue(np.array_equal(self.extrapolated_tracks.to_numpy(), self.tracks_full_extrapol.to_numpy(), equal_nan=True))
161
+
162
+
163
+ if __name__=="__main__":
164
+ unittest.main()
tests/test_utils.py ADDED
@@ -0,0 +1,118 @@
1
+ import unittest
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import os
5
+ from celldetective.utils import create_patch_mask, remove_redundant_features, _extract_channel_indices, _get_img_num_per_channel, split_by_ratio,extract_experiment_channels, estimate_unreliable_edge, unpad, mask_edges
6
+
7
+ class TestPatchMask(unittest.TestCase):
8
+
9
+ @classmethod
10
+ def setUpClass(self):
11
+ self.radius = 3
12
+
13
+ def test_correct_shape(self):
14
+ self.patch = create_patch_mask(self.radius, self.radius)
15
+ self.assertEqual(self.patch.shape,(3,3))
16
+
17
+ def test_correct_ring(self):
18
+ self.patch = create_patch_mask(5, 5,radius=[1,2])
19
+ self.assertFalse(self.patch[2,2])
20
+
21
+ class TestRemoveRedundantFeatures(unittest.TestCase):
22
+
23
+ @classmethod
24
+ def setUpClass(self):
25
+ self.list_a = ['feat1','feat2','feat3','feat4','intensity_mean']
26
+ self.list_b = ['feat5','feat2','feat1','feat6','test_channel_mean']
27
+ self.expected = ['feat3','feat4']
28
+
29
+ def test_remove_red_features(self):
30
+ self.assertEqual(remove_redundant_features(self.list_a, self.list_b, channel_names=['test_channel']), self.expected)
31
+
32
+
33
+ class TestExtractChannelIndices(unittest.TestCase):
34
+
35
+ @classmethod
36
+ def setUpClass(self):
37
+ self.channels = ['ch1','ch2','ch3','ch4']
38
+ self.required_channels = ['ch4','ch2']
39
+ self.expected_indices = [3,1]
40
+
41
+ def test_extracted_channels_are_correct(self):
42
+ self.assertEqual(list(_extract_channel_indices(self.channels, self.required_channels)), self.expected_indices)
43
+
44
+
45
+ class TestImgIndexPerChannel(unittest.TestCase):
46
+
47
+ @classmethod
48
+ def setUpClass(self):
49
+ self.channels_indices = [1]
50
+ self.len_movie = 5
51
+ self.nbr_channels = 3
52
+ self.expected_indices = [1,4,7,10,13]
53
+
54
+ def test_index_sequence_is_correct(self):
55
+ self.assertEqual(list(_get_img_num_per_channel(self.channels_indices, self.len_movie, self.nbr_channels)[0]), self.expected_indices)
56
+
57
+
58
+ class TestSplitArrayByRatio(unittest.TestCase):
59
+
60
+ @classmethod
61
+ def setUpClass(self):
62
+ self.array_length = 100
63
+ self.array = np.ones(self.array_length)
64
+
65
+ def test_ratio_split_is_correct(self):
66
+ split_array = split_by_ratio(self.array,0.5,0.25,0.1)
67
+ self.assertTrue(np.all([len(split_array[0])==50, len(split_array[1])==25, len(split_array[2])==10]))
68
+
69
+ class TestUnpad(unittest.TestCase):
70
+
71
+ @classmethod
72
+ def setUpClass(self):
73
+ self.array = np.array([[0,0,0],
74
+ [0,1,0],
75
+ [0,0,0]])
76
+
77
+ def test_unpad(self):
78
+ expected_unpad_array = np.array([[1]])
79
+ test_array = unpad(self.array, 1)
80
+ self.assertTrue(np.array_equal(test_array, expected_unpad_array))
81
+
82
+ class TestMaskEdge(unittest.TestCase):
83
+
84
+ @classmethod
85
+ def setUpClass(self):
86
+ self.binary_mask = np.array([[1, 1, 1, 1, 1],
87
+ [1, 1, 1, 1, 1],
88
+ [1, 1, 1, 1, 1],
89
+ [1, 1, 1, 1, 1],
90
+ [1, 1, 1, 1, 1]])
91
+
92
+ def test_mask_edge_properly(self):
93
+ expected_output = np.array([[False, False, False, False, False],
94
+ [False, True, True, True, False],
95
+ [False, True, True, True, False],
96
+ [False, True, True, True, False],
97
+ [False, False, False, False, False]])
98
+ actual_output = mask_edges(self.binary_mask, 1)
99
+ self.assertTrue(np.array_equal(actual_output, expected_output))
100
+
101
+ class TestEstimateFilterEdge(unittest.TestCase):
102
+
103
+ @classmethod
104
+ def setUpClass(self):
105
+ self.protocol1 = [['gauss',2],['std',4]]
106
+ self.expected1 = 6
107
+ self.protocol2 = [['gauss',4],['variance','string_arg']]
108
+ self.expected2 = 4
109
+
110
+ def test_edge_is_estimated_properly_with_only_number_arguments(self):
111
+ self.assertEqual(self.expected1, estimate_unreliable_edge(self.protocol1))
112
+
113
+ def test_edge_is_estimated_properly_with_mixed_arguments(self):
114
+ self.assertEqual(self.expected2, estimate_unreliable_edge(self.protocol2))
115
+
116
+
117
+ if __name__=="__main__":
118
+ unittest.main()