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.
- celldetective/__main__.py +7 -21
- celldetective/events.py +2 -44
- celldetective/extra_properties.py +62 -52
- celldetective/filters.py +4 -5
- celldetective/gui/__init__.py +1 -1
- celldetective/gui/analyze_block.py +37 -10
- celldetective/gui/btrack_options.py +24 -23
- celldetective/gui/classifier_widget.py +62 -19
- celldetective/gui/configure_new_exp.py +32 -35
- celldetective/gui/control_panel.py +120 -81
- celldetective/gui/gui_utils.py +674 -396
- celldetective/gui/json_readers.py +7 -6
- celldetective/gui/layouts.py +756 -0
- celldetective/gui/measurement_options.py +98 -513
- celldetective/gui/neighborhood_options.py +322 -270
- celldetective/gui/plot_measurements.py +1114 -0
- celldetective/gui/plot_signals_ui.py +21 -20
- celldetective/gui/process_block.py +449 -169
- celldetective/gui/retrain_segmentation_model_options.py +27 -26
- celldetective/gui/retrain_signal_model_options.py +25 -24
- celldetective/gui/seg_model_loader.py +31 -27
- celldetective/gui/signal_annotator.py +2326 -2295
- celldetective/gui/signal_annotator_options.py +18 -16
- celldetective/gui/styles.py +16 -1
- celldetective/gui/survival_ui.py +67 -39
- celldetective/gui/tableUI.py +337 -48
- celldetective/gui/thresholds_gui.py +75 -71
- celldetective/gui/viewers.py +743 -0
- celldetective/io.py +247 -27
- celldetective/measure.py +43 -263
- celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
- celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
- celldetective/neighborhood.py +498 -27
- celldetective/preprocessing.py +1023 -0
- celldetective/scripts/analyze_signals.py +7 -0
- celldetective/scripts/measure_cells.py +12 -0
- celldetective/scripts/segment_cells.py +20 -4
- celldetective/scripts/track_cells.py +11 -0
- celldetective/scripts/train_segmentation_model.py +35 -34
- celldetective/segmentation.py +14 -9
- celldetective/signals.py +234 -329
- celldetective/tracking.py +2 -2
- celldetective/utils.py +602 -49
- celldetective-1.1.1.dist-info/METADATA +305 -0
- celldetective-1.1.1.dist-info/RECORD +84 -0
- {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/test_events.py +28 -0
- tests/test_filters.py +24 -0
- tests/test_io.py +70 -0
- tests/test_measure.py +141 -0
- tests/test_neighborhood.py +70 -0
- tests/test_preprocessing.py +37 -0
- tests/test_segmentation.py +93 -0
- tests/test_signals.py +135 -0
- tests/test_tracking.py +164 -0
- tests/test_utils.py +118 -0
- celldetective-1.0.2.post1.dist-info/METADATA +0 -221
- celldetective-1.0.2.post1.dist-info/RECORD +0 -66
- {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/LICENSE +0 -0
- {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/WHEEL +0 -0
- {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()
|