celldetective 1.3.0.post1__py3-none-any.whl → 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/_version.py +1 -1
- celldetective/events.py +88 -11
- celldetective/extra_properties.py +5 -1
- celldetective/gui/InitWindow.py +35 -9
- celldetective/gui/classifier_widget.py +99 -23
- celldetective/gui/control_panel.py +7 -1
- celldetective/gui/generic_signal_plot.py +161 -2
- celldetective/gui/gui_utils.py +90 -1
- celldetective/gui/layouts.py +128 -7
- celldetective/gui/measurement_options.py +3 -3
- celldetective/gui/plot_signals_ui.py +8 -3
- celldetective/gui/process_block.py +77 -32
- celldetective/gui/retrain_segmentation_model_options.py +24 -10
- celldetective/gui/signal_annotator.py +53 -26
- celldetective/gui/signal_annotator2.py +17 -30
- celldetective/gui/survival_ui.py +24 -3
- celldetective/gui/tableUI.py +300 -183
- celldetective/gui/viewers.py +263 -3
- celldetective/io.py +56 -3
- celldetective/links/zenodo.json +136 -123
- celldetective/measure.py +3 -0
- celldetective/models/tracking_configs/biased_motion.json +68 -0
- celldetective/models/tracking_configs/no_z_motion.json +202 -0
- celldetective/neighborhood.py +154 -69
- celldetective/preprocessing.py +172 -3
- celldetective/relative_measurements.py +128 -4
- celldetective/scripts/measure_cells.py +3 -3
- celldetective/signals.py +212 -215
- celldetective/tracking.py +7 -3
- celldetective/utils.py +22 -6
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/METADATA +3 -3
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/RECORD +36 -34
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/WHEEL +1 -1
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/LICENSE +0 -0
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/top_level.txt +0 -0
celldetective/signals.py
CHANGED
|
@@ -17,6 +17,7 @@ from sklearn.metrics import confusion_matrix, classification_report
|
|
|
17
17
|
from sklearn.metrics import jaccard_score, balanced_accuracy_score, precision_score, recall_score
|
|
18
18
|
from scipy.interpolate import interp1d
|
|
19
19
|
from scipy.ndimage import shift
|
|
20
|
+
from sklearn.metrics import ConfusionMatrixDisplay
|
|
20
21
|
|
|
21
22
|
from celldetective.io import locate_signal_model, get_position_pickle, get_position_table
|
|
22
23
|
from celldetective.tracking import clean_trajectories, interpolate_nan_properties
|
|
@@ -149,7 +150,7 @@ def analyze_signals(trajectories, model, interpolate_na=True,
|
|
|
149
150
|
assert os.path.exists(model_config_path),f'Model configuration could not be located in folder {model_path}... Abort.'
|
|
150
151
|
|
|
151
152
|
available_signals = list(trajectories.columns)
|
|
152
|
-
print('The available_signals are : ',available_signals)
|
|
153
|
+
#print('The available_signals are : ',available_signals)
|
|
153
154
|
|
|
154
155
|
f = open(model_config_path)
|
|
155
156
|
config = json.load(f)
|
|
@@ -167,22 +168,11 @@ def analyze_signals(trajectories, model, interpolate_na=True,
|
|
|
167
168
|
selected_signals = []
|
|
168
169
|
for s in required_signals:
|
|
169
170
|
pattern_test = [s in a or s==a for a in available_signals]
|
|
170
|
-
print(f'Pattern test for signal {s}: ', pattern_test)
|
|
171
|
+
#print(f'Pattern test for signal {s}: ', pattern_test)
|
|
171
172
|
assert np.any(pattern_test),f'No signal matches with the requirements of the model {required_signals}. Please pass the signals manually with the argument selected_signals or add measurements. Abort.'
|
|
172
|
-
valid_columns = np.array(available_signals)[np.array(pattern_test)]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
else:
|
|
176
|
-
#print(test_number_of_nan(trajectories, valid_columns))
|
|
177
|
-
print(f'Found several candidate signals: {valid_columns}')
|
|
178
|
-
for vc in natsorted(valid_columns):
|
|
179
|
-
if 'circle' in vc:
|
|
180
|
-
selected_signals.append(vc)
|
|
181
|
-
break
|
|
182
|
-
else:
|
|
183
|
-
selected_signals.append(valid_columns[0])
|
|
184
|
-
# do something more complicated in case of one to many columns
|
|
185
|
-
#pass
|
|
173
|
+
valid_columns = natsorted(np.array(available_signals)[np.array(pattern_test)])
|
|
174
|
+
print(f"Selecting the first time series among: {valid_columns} for input requirement {s}...")
|
|
175
|
+
selected_signals.append(valid_columns[0])
|
|
186
176
|
else:
|
|
187
177
|
assert len(selected_signals)==len(required_signals),f'Mismatch between the number of required signals {required_signals} and the provided signals {selected_signals}... Abort.'
|
|
188
178
|
|
|
@@ -202,13 +192,7 @@ def analyze_signals(trajectories, model, interpolate_na=True,
|
|
|
202
192
|
signals[i,frames,j] = signal
|
|
203
193
|
signals[i,max(frames):,j] = signal[-1]
|
|
204
194
|
|
|
205
|
-
# for i in range(5):
|
|
206
|
-
# print('pre model')
|
|
207
|
-
# plt.plot(signals[i,:,0])
|
|
208
|
-
# plt.show()
|
|
209
|
-
|
|
210
195
|
model = SignalDetectionModel(pretrained=complete_path)
|
|
211
|
-
print('signal shape: ', signals.shape)
|
|
212
196
|
|
|
213
197
|
classes = model.predict_class(signals)
|
|
214
198
|
times_recast = model.predict_time_of_interest(signals)
|
|
@@ -378,194 +362,193 @@ def analyze_pair_signals_at_position(pos, model, use_gpu=True):
|
|
|
378
362
|
return None
|
|
379
363
|
|
|
380
364
|
|
|
381
|
-
def analyze_signals(trajectories, model, interpolate_na=True,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return trajectories
|
|
365
|
+
# def analyze_signals(trajectories, model, interpolate_na=True,
|
|
366
|
+
# selected_signals=None,
|
|
367
|
+
# model_path=None,
|
|
368
|
+
# column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'},
|
|
369
|
+
# plot_outcome=False, output_dir=None):
|
|
370
|
+
# """
|
|
371
|
+
# Analyzes signals from trajectory data using a specified signal detection model and configuration.
|
|
372
|
+
|
|
373
|
+
# This function preprocesses trajectory data, selects specified signals, and applies a pretrained signal detection
|
|
374
|
+
# model to predict classes and times of interest for each trajectory. It supports custom column labeling, interpolation
|
|
375
|
+
# of missing values, and plotting of analysis outcomes.
|
|
376
|
+
|
|
377
|
+
# Parameters
|
|
378
|
+
# ----------
|
|
379
|
+
# trajectories : pandas.DataFrame
|
|
380
|
+
# DataFrame containing trajectory data with columns for track ID, frame, position, and signals.
|
|
381
|
+
# model : str
|
|
382
|
+
# The name of the signal detection model to be used for analysis.
|
|
383
|
+
# interpolate_na : bool, optional
|
|
384
|
+
# Whether to interpolate missing values in the trajectories (default is True).
|
|
385
|
+
# selected_signals : list of str, optional
|
|
386
|
+
# A list of column names from `trajectories` representing the signals to be analyzed. If None, signals will
|
|
387
|
+
# be automatically selected based on the model configuration (default is None).
|
|
388
|
+
# column_labels : dict, optional
|
|
389
|
+
# A dictionary mapping the default column names ('track', 'time', 'x', 'y') to the corresponding column names
|
|
390
|
+
# in `trajectories` (default is {'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'}).
|
|
391
|
+
# plot_outcome : bool, optional
|
|
392
|
+
# If True, generates and saves a plot of the signal analysis outcome (default is False).
|
|
393
|
+
# output_dir : str, optional
|
|
394
|
+
# The directory where the outcome plot will be saved. Required if `plot_outcome` is True (default is None).
|
|
395
|
+
|
|
396
|
+
# Returns
|
|
397
|
+
# -------
|
|
398
|
+
# pandas.DataFrame
|
|
399
|
+
# The input `trajectories` DataFrame with additional columns for predicted classes, times of interest, and
|
|
400
|
+
# corresponding colors based on status and class.
|
|
401
|
+
|
|
402
|
+
# Raises
|
|
403
|
+
# ------
|
|
404
|
+
# AssertionError
|
|
405
|
+
# If the model or its configuration file cannot be located.
|
|
406
|
+
|
|
407
|
+
# Notes
|
|
408
|
+
# -----
|
|
409
|
+
# - The function relies on an external model configuration file (`config_input.json`) located in the model's directory.
|
|
410
|
+
# - Signal selection and preprocessing are based on the requirements specified in the model's configuration.
|
|
411
|
+
|
|
412
|
+
# """
|
|
413
|
+
|
|
414
|
+
# model_path = locate_signal_model(model, path=model_path)
|
|
415
|
+
# complete_path = model_path # +model
|
|
416
|
+
# complete_path = rf"{complete_path}"
|
|
417
|
+
# model_config_path = os.sep.join([complete_path, 'config_input.json'])
|
|
418
|
+
# model_config_path = rf"{model_config_path}"
|
|
419
|
+
# assert os.path.exists(complete_path), f'Model {model} could not be located in folder {model_path}... Abort.'
|
|
420
|
+
# assert os.path.exists(
|
|
421
|
+
# model_config_path), f'Model configuration could not be located in folder {model_path}... Abort.'
|
|
422
|
+
|
|
423
|
+
# available_signals = list(trajectories.columns)
|
|
424
|
+
|
|
425
|
+
# f = open(model_config_path)
|
|
426
|
+
# config = json.load(f)
|
|
427
|
+
# required_signals = config["channels"]
|
|
428
|
+
|
|
429
|
+
# try:
|
|
430
|
+
# label = config['label']
|
|
431
|
+
# if label == '':
|
|
432
|
+
# label = None
|
|
433
|
+
# except:
|
|
434
|
+
# label = None
|
|
435
|
+
|
|
436
|
+
# if selected_signals is None:
|
|
437
|
+
# selected_signals = []
|
|
438
|
+
# for s in required_signals:
|
|
439
|
+
# pattern_test = [s in a or s == a for a in available_signals]
|
|
440
|
+
# #print(f'Pattern test for signal {s}: ', pattern_test)
|
|
441
|
+
# assert np.any(
|
|
442
|
+
# pattern_test), f'No signal matches with the requirements of the model {required_signals}. Please pass the signals manually with the argument selected_signals or add measurements. Abort.'
|
|
443
|
+
# valid_columns = np.array(available_signals)[np.array(pattern_test)]
|
|
444
|
+
# if len(valid_columns) == 1:
|
|
445
|
+
# selected_signals.append(valid_columns[0])
|
|
446
|
+
# else:
|
|
447
|
+
# # print(test_number_of_nan(trajectories, valid_columns))
|
|
448
|
+
# print(f'Found several candidate signals: {valid_columns}')
|
|
449
|
+
# for vc in natsorted(valid_columns):
|
|
450
|
+
# if 'circle' in vc:
|
|
451
|
+
# selected_signals.append(vc)
|
|
452
|
+
# break
|
|
453
|
+
# else:
|
|
454
|
+
# selected_signals.append(valid_columns[0])
|
|
455
|
+
# # do something more complicated in case of one to many columns
|
|
456
|
+
# # pass
|
|
457
|
+
# else:
|
|
458
|
+
# assert len(selected_signals) == len(
|
|
459
|
+
# required_signals), f'Mismatch between the number of required signals {required_signals} and the provided signals {selected_signals}... Abort.'
|
|
460
|
+
|
|
461
|
+
# print(f'The following channels will be passed to the model: {selected_signals}')
|
|
462
|
+
# trajectories_clean = clean_trajectories(trajectories, interpolate_na=interpolate_na,
|
|
463
|
+
# interpolate_position_gaps=interpolate_na, column_labels=column_labels)
|
|
464
|
+
|
|
465
|
+
# max_signal_size = int(trajectories_clean[column_labels['time']].max()) + 2
|
|
466
|
+
# tracks = trajectories_clean[column_labels['track']].unique()
|
|
467
|
+
# signals = np.zeros((len(tracks), max_signal_size, len(selected_signals)))
|
|
468
|
+
|
|
469
|
+
# for i, (tid, group) in enumerate(trajectories_clean.groupby(column_labels['track'])):
|
|
470
|
+
# frames = group[column_labels['time']].to_numpy().astype(int)
|
|
471
|
+
# for j, col in enumerate(selected_signals):
|
|
472
|
+
# signal = group[col].to_numpy()
|
|
473
|
+
# signals[i, frames, j] = signal
|
|
474
|
+
# signals[i, max(frames):, j] = signal[-1]
|
|
475
|
+
|
|
476
|
+
# # for i in range(5):
|
|
477
|
+
# # print('pre model')
|
|
478
|
+
# # plt.plot(signals[i,:,0])
|
|
479
|
+
# # plt.show()
|
|
480
|
+
|
|
481
|
+
# model = SignalDetectionModel(pretrained=complete_path)
|
|
482
|
+
# print('signal shape: ', signals.shape)
|
|
483
|
+
|
|
484
|
+
# classes = model.predict_class(signals)
|
|
485
|
+
# times_recast = model.predict_time_of_interest(signals)
|
|
486
|
+
|
|
487
|
+
# if label is None:
|
|
488
|
+
# class_col = 'class'
|
|
489
|
+
# time_col = 't0'
|
|
490
|
+
# status_col = 'status'
|
|
491
|
+
# else:
|
|
492
|
+
# class_col = 'class_' + label
|
|
493
|
+
# time_col = 't_' + label
|
|
494
|
+
# status_col = 'status_' + label
|
|
495
|
+
|
|
496
|
+
# for i, (tid, group) in enumerate(trajectories.groupby(column_labels['track'])):
|
|
497
|
+
# indices = group.index
|
|
498
|
+
# trajectories.loc[indices, class_col] = classes[i]
|
|
499
|
+
# trajectories.loc[indices, time_col] = times_recast[i]
|
|
500
|
+
# print('Done.')
|
|
501
|
+
|
|
502
|
+
# for tid, group in trajectories.groupby(column_labels['track']):
|
|
503
|
+
|
|
504
|
+
# indices = group.index
|
|
505
|
+
# t0 = group[time_col].to_numpy()[0]
|
|
506
|
+
# cclass = group[class_col].to_numpy()[0]
|
|
507
|
+
# timeline = group[column_labels['time']].to_numpy()
|
|
508
|
+
# status = np.zeros_like(timeline)
|
|
509
|
+
# if t0 > 0:
|
|
510
|
+
# status[timeline >= t0] = 1.
|
|
511
|
+
# if cclass == 2:
|
|
512
|
+
# status[:] = 2
|
|
513
|
+
# if cclass > 2:
|
|
514
|
+
# status[:] = 42
|
|
515
|
+
# status_color = [color_from_status(s) for s in status]
|
|
516
|
+
# class_color = [color_from_class(cclass) for i in range(len(status))]
|
|
517
|
+
|
|
518
|
+
# trajectories.loc[indices, status_col] = status
|
|
519
|
+
# trajectories.loc[indices, 'status_color'] = status_color
|
|
520
|
+
# trajectories.loc[indices, 'class_color'] = class_color
|
|
521
|
+
|
|
522
|
+
# if plot_outcome:
|
|
523
|
+
# fig, ax = plt.subplots(1, len(selected_signals), figsize=(10, 5))
|
|
524
|
+
# for i, s in enumerate(selected_signals):
|
|
525
|
+
# for k, (tid, group) in enumerate(trajectories.groupby(column_labels['track'])):
|
|
526
|
+
# cclass = group[class_col].to_numpy()[0]
|
|
527
|
+
# t0 = group[time_col].to_numpy()[0]
|
|
528
|
+
# timeline = group[column_labels['time']].to_numpy()
|
|
529
|
+
# if cclass == 0:
|
|
530
|
+
# if len(selected_signals) > 1:
|
|
531
|
+
# ax[i].plot(timeline - t0, group[s].to_numpy(), c='tab:blue', alpha=0.1)
|
|
532
|
+
# else:
|
|
533
|
+
# ax.plot(timeline - t0, group[s].to_numpy(), c='tab:blue', alpha=0.1)
|
|
534
|
+
# if len(selected_signals) > 1:
|
|
535
|
+
# for a, s in zip(ax, selected_signals):
|
|
536
|
+
# a.set_title(s)
|
|
537
|
+
# a.set_xlabel(r'time - t$_0$ [frame]')
|
|
538
|
+
# a.spines['top'].set_visible(False)
|
|
539
|
+
# a.spines['right'].set_visible(False)
|
|
540
|
+
# else:
|
|
541
|
+
# ax.set_title(s)
|
|
542
|
+
# ax.set_xlabel(r'time - t$_0$ [frame]')
|
|
543
|
+
# ax.spines['top'].set_visible(False)
|
|
544
|
+
# ax.spines['right'].set_visible(False)
|
|
545
|
+
# plt.tight_layout()
|
|
546
|
+
# if output_dir is not None:
|
|
547
|
+
# plt.savefig(output_dir + 'signal_collapse.png', bbox_inches='tight', dpi=300)
|
|
548
|
+
# plt.pause(3)
|
|
549
|
+
# plt.close()
|
|
550
|
+
|
|
551
|
+
# return trajectories
|
|
569
552
|
|
|
570
553
|
|
|
571
554
|
def analyze_pair_signals(trajectories_pairs,trajectories_reference,trajectories_neighbors, model, interpolate_na=True, selected_signals=None,
|
|
@@ -1405,7 +1388,10 @@ class SignalDetectionModel(object):
|
|
|
1405
1388
|
|
|
1406
1389
|
if self.show_plots:
|
|
1407
1390
|
try:
|
|
1408
|
-
|
|
1391
|
+
ConfusionMatrixDisplay.from_predictions(ground_truth, predictions, cmap="Blues", normalize="pred", display_labels=["event","no event","left censored"])
|
|
1392
|
+
plt.savefig(os.sep.join([self.model_folder,"test_confusion_matrix.png"]),bbox_inches='tight',dpi=300)
|
|
1393
|
+
plt.pause(3)
|
|
1394
|
+
plt.close()
|
|
1409
1395
|
except Exception as e:
|
|
1410
1396
|
print(e)
|
|
1411
1397
|
pass
|
|
@@ -1434,8 +1420,12 @@ class SignalDetectionModel(object):
|
|
|
1434
1420
|
|
|
1435
1421
|
if self.show_plots:
|
|
1436
1422
|
try:
|
|
1437
|
-
|
|
1438
|
-
|
|
1423
|
+
ConfusionMatrixDisplay.from_predictions(ground_truth, predictions, cmap="Blues", normalize="pred", display_labels=["event","no event","left censored"])
|
|
1424
|
+
plt.savefig(os.sep.join([self.model_folder,"validation_confusion_matrix.png"]),bbox_inches='tight',dpi=300)
|
|
1425
|
+
plt.pause(3)
|
|
1426
|
+
plt.close()
|
|
1427
|
+
except Exception as e:
|
|
1428
|
+
print(e)
|
|
1439
1429
|
pass
|
|
1440
1430
|
print("Validation set: ",classification_report(ground_truth,predictions))
|
|
1441
1431
|
|
|
@@ -3077,9 +3067,13 @@ def mean_signal(df, signal_name, class_col, time_col=None, class_value=[0], retu
|
|
|
3077
3067
|
assert signal_name in list(df.columns),"The signal you want to plot is not one of the measured features."
|
|
3078
3068
|
if isinstance(class_value,int):
|
|
3079
3069
|
class_value = [class_value]
|
|
3070
|
+
elif class_value is None or class_col is None:
|
|
3071
|
+
class_col = 'class_temp'
|
|
3072
|
+
df['class_temp'] = 1
|
|
3073
|
+
class_value = [1]
|
|
3080
3074
|
|
|
3081
3075
|
if forced_max_duration is None:
|
|
3082
|
-
max_duration = ceil(np.amax(df.groupby(['position','TRACK_ID']).size().values))
|
|
3076
|
+
max_duration = int(df['FRAME'].max())+1 #ceil(np.amax(df.groupby(['position','TRACK_ID']).size().values))
|
|
3083
3077
|
else:
|
|
3084
3078
|
max_duration = forced_max_duration
|
|
3085
3079
|
|
|
@@ -3115,9 +3109,12 @@ def mean_signal(df, signal_name, class_col, time_col=None, class_value=[0], retu
|
|
|
3115
3109
|
else:
|
|
3116
3110
|
signal = track_group[signal_name].to_numpy()
|
|
3117
3111
|
|
|
3112
|
+
if ref_time <=0:
|
|
3113
|
+
ref_time = 0
|
|
3114
|
+
|
|
3118
3115
|
timeline = track_group['FRAME'].unique().astype(int)
|
|
3119
3116
|
timeline_shifted = timeline - ref_time + max_duration
|
|
3120
|
-
signal_matrix[trackid,timeline_shifted] = signal
|
|
3117
|
+
signal_matrix[trackid,timeline_shifted.astype(int)] = signal
|
|
3121
3118
|
trackid+=1
|
|
3122
3119
|
|
|
3123
3120
|
mean_signal, std_signal = columnwise_mean(signal_matrix, min_nbr_values=min_nbr_values)
|
celldetective/tracking.py
CHANGED
|
@@ -609,7 +609,7 @@ def interpolate_time_gaps(trajectories, column_labels={'track': "TRACK_ID", 'tim
|
|
|
609
609
|
|
|
610
610
|
trajectories[column_labels['time']] = pd.to_datetime(trajectories[column_labels['time']], unit='s')
|
|
611
611
|
trajectories.set_index(column_labels['track'], inplace=True)
|
|
612
|
-
trajectories = trajectories.groupby(column_labels['track'], group_keys=True).apply(lambda x: x.set_index(column_labels['time']).resample('
|
|
612
|
+
trajectories = trajectories.groupby(column_labels['track'], group_keys=True).apply(lambda x: x.set_index(column_labels['time']).resample('1s').asfreq()).reset_index()
|
|
613
613
|
trajectories[[column_labels['x'], column_labels['y']]] = trajectories.groupby(column_labels['track'], group_keys=False)[[column_labels['x'], column_labels['y']]].apply(lambda x: x.interpolate(method='linear'))
|
|
614
614
|
trajectories.reset_index(drop=True, inplace=True)
|
|
615
615
|
trajectories[column_labels['time']] = trajectories[column_labels['time']].astype('int64').astype(float) / 10**9
|
|
@@ -679,7 +679,11 @@ def extrapolate_tracks(trajectories, post=False, pre=False, column_labels={'trac
|
|
|
679
679
|
extrapolated_positions = pd.DataFrame({column_labels['x']: last_known_position[0][1], column_labels['y']: last_known_position[0][2]}, index=np.arange(last_known_position[0][0] + 1, max_time + 1))
|
|
680
680
|
track_data = extrapolated_frames.join(extrapolated_positions, how="inner", on=column_labels['time'])
|
|
681
681
|
track_data[column_labels['track']] = track_id
|
|
682
|
-
|
|
682
|
+
|
|
683
|
+
if len(df_extrapolated)==0:
|
|
684
|
+
df_extrapolated = track_data
|
|
685
|
+
elif len(track_data)!=0:
|
|
686
|
+
df_extrapolated = pd.concat([df_extrapolated, track_data])
|
|
683
687
|
|
|
684
688
|
|
|
685
689
|
# concatenate the original dataframe and the extrapolated dataframe
|
|
@@ -960,7 +964,7 @@ def write_first_detection_class(tab, column_labels={'track': "TRACK_ID", 'time':
|
|
|
960
964
|
if np.any(detection==detection):
|
|
961
965
|
t_first = timeline[detection==detection][0]
|
|
962
966
|
cclass = 0
|
|
963
|
-
if t_first
|
|
967
|
+
if t_first<=0:
|
|
964
968
|
t_first = -1
|
|
965
969
|
cclass = 2
|
|
966
970
|
else:
|
celldetective/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
import numpy as np
|
|
2
3
|
import pandas as pd
|
|
3
4
|
import matplotlib.pyplot as plt
|
|
@@ -29,6 +30,21 @@ from skimage.morphology import disk
|
|
|
29
30
|
from scipy.stats import ks_2samp
|
|
30
31
|
from cliffs_delta import cliffs_delta
|
|
31
32
|
|
|
33
|
+
def safe_log(array):
|
|
34
|
+
|
|
35
|
+
if isinstance(array,int) or isinstance(array,float):
|
|
36
|
+
if value<=0.:
|
|
37
|
+
return np.nan
|
|
38
|
+
else:
|
|
39
|
+
return np.log10(value)
|
|
40
|
+
else:
|
|
41
|
+
if isinstance(array, list):
|
|
42
|
+
array = np.array(array)
|
|
43
|
+
output_array = np.zeros_like(array).astype(float)
|
|
44
|
+
output_array[:] = np.nan
|
|
45
|
+
output_array[array==array] = np.log10(array[array==array])
|
|
46
|
+
return output_array
|
|
47
|
+
|
|
32
48
|
def contour_of_instance_segmentation(label, distance):
|
|
33
49
|
|
|
34
50
|
"""
|
|
@@ -2347,12 +2363,12 @@ def download_zenodo_file(file, output_dir):
|
|
|
2347
2363
|
if len(file_to_rename)>0 and not file_to_rename[0].endswith(os.sep) and not file.startswith('demo'):
|
|
2348
2364
|
os.rename(file_to_rename[0], os.sep.join([output_dir,file,file]))
|
|
2349
2365
|
|
|
2350
|
-
if file.startswith('db_'):
|
|
2351
|
-
|
|
2352
|
-
if file=='db-si-NucPI':
|
|
2353
|
-
|
|
2354
|
-
if file=='db-si-NucCondensation':
|
|
2355
|
-
|
|
2366
|
+
#if file.startswith('db_'):
|
|
2367
|
+
# os.rename(os.sep.join([output_dir,file.replace('db_','')]), os.sep.join([output_dir,file]))
|
|
2368
|
+
#if file=='db-si-NucPI':
|
|
2369
|
+
# os.rename(os.sep.join([output_dir,'db2-NucPI']), os.sep.join([output_dir,file]))
|
|
2370
|
+
#if file=='db-si-NucCondensation':
|
|
2371
|
+
# os.rename(os.sep.join([output_dir,'db1-NucCondensation']), os.sep.join([output_dir,file]))
|
|
2356
2372
|
|
|
2357
2373
|
os.remove(path_to_zip_file)
|
|
2358
2374
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: celldetective
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: description
|
|
5
5
|
Home-page: http://github.com/remyeltorro/celldetective
|
|
6
6
|
Author: Rémy Torro
|
|
@@ -68,7 +68,7 @@ analysis on multimodal time lapse microscopy images.
|
|
|
68
68
|
|
|
69
69
|
## Overview
|
|
70
70
|
|
|
71
|
-

|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
Celldetective was designed to analyze time-lapse microscopy images in difficult situations: mixed cell populations that are only separable through multimodal information. This software provides a toolkit for the analysis of cell population interactions.
|
|
@@ -87,7 +87,7 @@ Instead of reinventing the wheel and out of respect for the amazing work done by
|
|
|
87
87
|
|
|
88
88
|
**Target Audience**: The software is targeted to scientists who are interested in quantifying dynamically (or not) cell populations from microscopy images. Experimental scientists who produce such images can also analyze their data, thanks to the graphical interface, that completely removes the need for coding, and the many helper functions that guide the user in the analysis steps. Finally, the modular structure of Celldetective welcomes users with a partial need.
|
|
89
89
|
|
|
90
|
-

|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
# System requirements
|