fucciphase 0.0.1__py3-none-any.whl → 0.0.3__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.
- fucciphase/__init__.py +7 -1
- fucciphase/__main__.py +12 -0
- fucciphase/fucci_phase.py +97 -51
- fucciphase/io.py +16 -12
- fucciphase/main_cli.py +263 -0
- fucciphase/napari/tracks_to_napari.py +14 -16
- fucciphase/phase.py +77 -74
- fucciphase/plot.py +238 -89
- fucciphase/sensor.py +31 -31
- fucciphase/tracking_utilities.py +63 -8
- fucciphase/utils/__init__.py +14 -1
- fucciphase/utils/checks.py +2 -5
- fucciphase/utils/dtw.py +2 -4
- fucciphase/utils/normalize.py +6 -8
- fucciphase/utils/simulator.py +1 -1
- fucciphase/utils/track_postprocessing.py +6 -8
- fucciphase/utils/trackmate.py +12 -12
- fucciphase-0.0.3.dist-info/METADATA +238 -0
- fucciphase-0.0.3.dist-info/RECORD +25 -0
- {fucciphase-0.0.1.dist-info → fucciphase-0.0.3.dist-info}/WHEEL +1 -1
- fucciphase-0.0.3.dist-info/entry_points.txt +3 -0
- fucciphase-0.0.1.dist-info/METADATA +0 -137
- fucciphase-0.0.1.dist-info/RECORD +0 -22
- {fucciphase-0.0.1.dist-info → fucciphase-0.0.3.dist-info}/licenses/LICENSE +0 -0
fucciphase/plot.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from itertools import cycle
|
|
2
|
-
from typing import List, Optional
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
5
4
|
import pandas as pd
|
|
@@ -16,7 +15,27 @@ from .utils import get_norm_channel_name
|
|
|
16
15
|
def set_phase_colors(
|
|
17
16
|
df: pd.DataFrame, colordict: dict, phase_column: str = "DISCRETE_PHASE_MAX"
|
|
18
17
|
) -> None:
|
|
19
|
-
"""Label each phase by fixed color.
|
|
18
|
+
"""Label each phase by fixed color.
|
|
19
|
+
|
|
20
|
+
This function adds a ``COLOR`` column to the dataframe by mapping each
|
|
21
|
+
entry in ``phase_column`` to a color specified in ``colordict``.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
df : pandas.DataFrame
|
|
26
|
+
Dataframe containing at least ``phase_column``.
|
|
27
|
+
colordict : dict
|
|
28
|
+
Mapping from phase label (str) to a valid matplotlib color.
|
|
29
|
+
phase_column : str, optional
|
|
30
|
+
Name of the column containing phase labels. Default is
|
|
31
|
+
``"DISCRETE_PHASE_MAX"``.
|
|
32
|
+
|
|
33
|
+
Raises
|
|
34
|
+
------
|
|
35
|
+
ValueError
|
|
36
|
+
If not all phase labels present in the dataframe have a color
|
|
37
|
+
entry in ``colordict``.
|
|
38
|
+
"""
|
|
20
39
|
phases = df[phase_column].unique()
|
|
21
40
|
if not all(phase in colordict for phase in phases):
|
|
22
41
|
raise ValueError(f"Provide a color for every phase in: {phases}")
|
|
@@ -32,10 +51,39 @@ def plot_feature(
|
|
|
32
51
|
feature_name: str,
|
|
33
52
|
interpolate_time: bool = False,
|
|
34
53
|
track_id_name: str = "TRACK_ID",
|
|
35
|
-
ylim:
|
|
36
|
-
yticks:
|
|
54
|
+
ylim: tuple | None = None,
|
|
55
|
+
yticks: list | None = None,
|
|
37
56
|
) -> Figure:
|
|
38
|
-
"""Plot features of individual tracks in one plot.
|
|
57
|
+
"""Plot features of individual tracks in one plot.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
df : pandas.DataFrame
|
|
62
|
+
Dataframe containing the feature and time columns.
|
|
63
|
+
time_column : str
|
|
64
|
+
Name of the column containing time or frame indices.
|
|
65
|
+
feature_name : str
|
|
66
|
+
Name of the feature column to plot.
|
|
67
|
+
interpolate_time : bool, optional
|
|
68
|
+
Currently unused; kept for API compatibility. Default is False.
|
|
69
|
+
track_id_name : str, optional
|
|
70
|
+
Name of the column containing track IDs. Default is ``"TRACK_ID"``.
|
|
71
|
+
ylim : tuple, optional
|
|
72
|
+
y-axis limits as ``(ymin, ymax)``.
|
|
73
|
+
yticks : list, optional
|
|
74
|
+
Explicit y-tick locations.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
matplotlib.figure.Figure
|
|
79
|
+
The figure containing the plot.
|
|
80
|
+
|
|
81
|
+
Raises
|
|
82
|
+
------
|
|
83
|
+
ValueError
|
|
84
|
+
If the feature or time columns are not found.
|
|
85
|
+
|
|
86
|
+
"""
|
|
39
87
|
if feature_name not in df:
|
|
40
88
|
raise ValueError(f"(Feature {feature_name} not in provided DataFrame.")
|
|
41
89
|
if time_column not in df:
|
|
@@ -63,20 +111,57 @@ def plot_feature_stacked(
|
|
|
63
111
|
feature_name: str,
|
|
64
112
|
interpolate_time: bool = False,
|
|
65
113
|
track_id_name: str = "TRACK_ID",
|
|
66
|
-
ylim:
|
|
67
|
-
yticks:
|
|
114
|
+
ylim: tuple | None = None,
|
|
115
|
+
yticks: list | None = None,
|
|
68
116
|
interpolation_steps: int = 1000,
|
|
69
|
-
figsize:
|
|
70
|
-
selected_tracks:
|
|
117
|
+
figsize: tuple | None = None,
|
|
118
|
+
selected_tracks: list[int] | None = None,
|
|
71
119
|
) -> Figure:
|
|
72
120
|
"""Stack features of individual tracks.
|
|
73
121
|
|
|
122
|
+
Each selected track is plotted in its own horizontal panel. If
|
|
123
|
+
``interpolate_time`` is True, an additional panel at the bottom shows
|
|
124
|
+
the mean interpolated feature across all tracks.
|
|
74
125
|
|
|
75
126
|
Notes
|
|
76
127
|
-----
|
|
77
|
-
If
|
|
78
|
-
is
|
|
79
|
-
|
|
128
|
+
If ``selected_tracks`` are chosen, the averaging is still performed on
|
|
129
|
+
all tracks. The selected subset is only used for stacked visualization.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
df : pandas.DataFrame
|
|
134
|
+
Dataframe containing the feature, time and COLOR columns.
|
|
135
|
+
time_column : str
|
|
136
|
+
Name of the column containing time or frame indices.
|
|
137
|
+
feature_name : str
|
|
138
|
+
Name of the feature column to plot.
|
|
139
|
+
interpolate_time : bool, optional
|
|
140
|
+
If True, add an extra panel showing the average interpolated
|
|
141
|
+
feature over time. Default is False.
|
|
142
|
+
track_id_name : str, optional
|
|
143
|
+
Name of the column containing track IDs. Default is ``"TRACK_ID"``.
|
|
144
|
+
ylim : tuple, optional
|
|
145
|
+
y-axis limits as ``(ymin, ymax)``.
|
|
146
|
+
yticks : list, optional
|
|
147
|
+
Explicit y-tick locations.
|
|
148
|
+
interpolation_steps : int, optional
|
|
149
|
+
Number of time points used for interpolation. Default is 1000.
|
|
150
|
+
figsize : tuple, optional
|
|
151
|
+
Figure size passed to ``plt.subplots``.
|
|
152
|
+
selected_tracks : list of int, optional
|
|
153
|
+
Subset of track IDs to plot in stacked panels.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
matplotlib.figure.Figure
|
|
158
|
+
The figure containing the stacked plots.
|
|
159
|
+
|
|
160
|
+
Raises
|
|
161
|
+
------
|
|
162
|
+
ValueError
|
|
163
|
+
If required columns are missing or ``selected_tracks`` contains
|
|
164
|
+
IDs not present in the dataframe.
|
|
80
165
|
"""
|
|
81
166
|
if feature_name not in df:
|
|
82
167
|
raise ValueError(f"(Feature {feature_name} not in provided DataFrame.")
|
|
@@ -156,7 +241,27 @@ def plot_raw_intensities(
|
|
|
156
241
|
time_label: str = "Frame #",
|
|
157
242
|
**plot_kwargs: bool,
|
|
158
243
|
) -> None:
|
|
159
|
-
"""Plot intensities of two-channel sensor.
|
|
244
|
+
"""Plot intensities of two-channel sensor over time.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
df : pandas.DataFrame
|
|
249
|
+
Dataframe containing intensity and time columns.
|
|
250
|
+
channel1 : str
|
|
251
|
+
Name of the first intensity column.
|
|
252
|
+
channel2 : str
|
|
253
|
+
Name of the second intensity column.
|
|
254
|
+
color1 : str, optional
|
|
255
|
+
Color used for ``channel1``. Default is ``"cyan"``.
|
|
256
|
+
color2 : str, optional
|
|
257
|
+
Color used for ``channel2``. Default is ``"magenta"``.
|
|
258
|
+
time_column : str, optional
|
|
259
|
+
Name of the time/frame column. Default is ``"FRAME"``.
|
|
260
|
+
time_label : str, optional
|
|
261
|
+
Label used for the x-axis. Default is ``"Frame #"``.
|
|
262
|
+
plot_kwargs : dict
|
|
263
|
+
Additional keyword arguments passed to ``matplotlib.pyplot.plot``.
|
|
264
|
+
"""
|
|
160
265
|
ch1_intensity = df[channel1]
|
|
161
266
|
ch2_intensity = df[channel2]
|
|
162
267
|
|
|
@@ -188,7 +293,27 @@ def plot_normalized_intensities(
|
|
|
188
293
|
time_label: str = "Frame #",
|
|
189
294
|
**plot_kwargs: bool,
|
|
190
295
|
) -> None:
|
|
191
|
-
"""Plot normalised intensities of two-channel sensor.
|
|
296
|
+
"""Plot normalised intensities of two-channel sensor.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
df : pandas.DataFrame
|
|
301
|
+
Dataframe containing normalized intensity and time columns.
|
|
302
|
+
channel1 : str
|
|
303
|
+
Name of the first channel (pre-normalization).
|
|
304
|
+
channel2 : str
|
|
305
|
+
Name of the second channel (pre-normalization).
|
|
306
|
+
color1 : str, optional
|
|
307
|
+
Color used for ``channel1``. Default is ``"cyan"``.
|
|
308
|
+
color2 : str, optional
|
|
309
|
+
Color used for ``channel2``. Default is ``"magenta"``.
|
|
310
|
+
time_column : str, optional
|
|
311
|
+
Name of the time/frame column. Default is ``"FRAME"``.
|
|
312
|
+
time_label : str, optional
|
|
313
|
+
Label used for the x-axis. Default is ``"Frame #"``.
|
|
314
|
+
plot_kwargs : dict
|
|
315
|
+
Additional keyword arguments passed to ``matplotlib.pyplot.plot``.
|
|
316
|
+
"""
|
|
192
317
|
ch1_intensity = df[get_norm_channel_name(channel1)]
|
|
193
318
|
ch2_intensity = df[get_norm_channel_name(channel2)]
|
|
194
319
|
|
|
@@ -200,30 +325,28 @@ def plot_normalized_intensities(
|
|
|
200
325
|
|
|
201
326
|
|
|
202
327
|
def plot_phase(df: pd.DataFrame, channel1: str, channel2: str) -> None:
|
|
203
|
-
"""Plot
|
|
204
|
-
corresponding to the change of phase.
|
|
328
|
+
"""Plot discrete cell-cycle phase-related signals over time.
|
|
205
329
|
|
|
206
|
-
|
|
207
|
-
|
|
330
|
+
Plot the two normalized channels and the unique intensity curve over
|
|
331
|
+
frame number. The dataframe must already contain:
|
|
208
332
|
|
|
209
|
-
-
|
|
210
|
-
- cell cycle percentage
|
|
211
|
-
- FRAME
|
|
333
|
+
- normalized channels (e.g. ``channel + "_NORM"``),
|
|
334
|
+
- the cell cycle percentage column,
|
|
335
|
+
- the ``FRAME`` column.
|
|
212
336
|
|
|
213
337
|
Parameters
|
|
214
338
|
----------
|
|
215
|
-
df :
|
|
216
|
-
|
|
339
|
+
df : pandas.DataFrame
|
|
340
|
+
Input dataframe.
|
|
217
341
|
channel1 : str
|
|
218
|
-
First channel
|
|
342
|
+
First channel name (pre-normalization).
|
|
219
343
|
channel2 : str
|
|
220
|
-
Second channel
|
|
344
|
+
Second channel name (pre-normalization).
|
|
221
345
|
|
|
222
346
|
Raises
|
|
223
347
|
------
|
|
224
348
|
ValueError
|
|
225
|
-
If the dataframe does not contain the
|
|
226
|
-
columns.
|
|
349
|
+
If the dataframe does not contain the required columns.
|
|
227
350
|
"""
|
|
228
351
|
# check if the FRAME column is present
|
|
229
352
|
if "FRAME" not in df.columns:
|
|
@@ -248,33 +371,49 @@ def plot_phase(df: pd.DataFrame, channel1: str, channel2: str) -> None:
|
|
|
248
371
|
def plot_dtw_query_vs_reference(
|
|
249
372
|
reference_df: pd.DataFrame,
|
|
250
373
|
df: pd.DataFrame,
|
|
251
|
-
channels:
|
|
374
|
+
channels: list[str],
|
|
252
375
|
ref_percentage_column: str = "percentage",
|
|
253
376
|
est_percentage_column: str = "CELL_CYCLE_PERC_DTW",
|
|
254
|
-
ground_truth:
|
|
255
|
-
colors:
|
|
377
|
+
ground_truth: pd.DataFrame | None = None,
|
|
378
|
+
colors: list[str] | None = None,
|
|
256
379
|
**plot_kwargs: bool,
|
|
257
380
|
) -> None:
|
|
258
|
-
"""
|
|
381
|
+
"""
|
|
382
|
+
Plot query curves and their alignment to a reference cell-cycle curve.
|
|
383
|
+
|
|
384
|
+
For each channel, this function plots:
|
|
385
|
+
|
|
386
|
+
- the query intensity as a function of estimated percentage,
|
|
387
|
+
- the reference intensity as a function of reference percentage,
|
|
388
|
+
- the reference curve re-sampled at the query percentages (match).
|
|
389
|
+
|
|
390
|
+
Optionally, ground truth curves can be overlaid.
|
|
259
391
|
|
|
260
392
|
Parameters
|
|
261
393
|
----------
|
|
262
|
-
reference_df:
|
|
263
|
-
|
|
264
|
-
df:
|
|
265
|
-
|
|
266
|
-
channels:
|
|
267
|
-
|
|
268
|
-
ref_percentage_column: str
|
|
269
|
-
|
|
270
|
-
est_percentage_column: str
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
394
|
+
reference_df : pandas.DataFrame
|
|
395
|
+
Dataframe with the reference curve (percentage vs intensity).
|
|
396
|
+
df : pandas.DataFrame
|
|
397
|
+
Dataframe used as query (estimated percentage vs intensity).
|
|
398
|
+
channels : list of str
|
|
399
|
+
Names of the channels to plot.
|
|
400
|
+
ref_percentage_column : str, optional
|
|
401
|
+
Column name for reference percentages. Default is ``"percentage"``.
|
|
402
|
+
est_percentage_column : str, optional
|
|
403
|
+
Column name for estimated percentages in the query dataframe.
|
|
404
|
+
Default is ``"CELL_CYCLE_PERC_DTW"``.
|
|
405
|
+
ground_truth : pandas.DataFrame, optional
|
|
406
|
+
Dataframe containing ground truth intensities, with the same
|
|
407
|
+
column names as ``reference_df``.
|
|
408
|
+
colors : list of str, optional
|
|
409
|
+
Colors to use for each channel in the reference plots.
|
|
410
|
+
plot_kwargs : dict
|
|
411
|
+
Additional keyword arguments passed to ``matplotlib.pyplot.plot``.
|
|
412
|
+
|
|
413
|
+
Raises
|
|
414
|
+
------
|
|
415
|
+
ValueError
|
|
416
|
+
If required columns are missing in the reference or query dataframes.
|
|
278
417
|
"""
|
|
279
418
|
for channel in channels:
|
|
280
419
|
if channel not in reference_df.columns:
|
|
@@ -293,7 +432,7 @@ def plot_dtw_query_vs_reference(
|
|
|
293
432
|
"Percentage column not found in reference DataFrame"
|
|
294
433
|
f", available options {reference_df.columns}"
|
|
295
434
|
)
|
|
296
|
-
|
|
435
|
+
_, ax = plt.subplots(1, len(channels))
|
|
297
436
|
for idx, channel in enumerate(channels):
|
|
298
437
|
ax[idx].plot(
|
|
299
438
|
df[est_percentage_column], df[channel], label="Query", **plot_kwargs
|
|
@@ -333,36 +472,41 @@ def plot_dtw_query_vs_reference(
|
|
|
333
472
|
def plot_query_vs_reference_in_time(
|
|
334
473
|
reference_df: pd.DataFrame,
|
|
335
474
|
df: pd.DataFrame,
|
|
336
|
-
channels:
|
|
475
|
+
channels: list[str],
|
|
337
476
|
ref_time_column: str = "time",
|
|
338
477
|
query_time_column: str = "time",
|
|
339
|
-
colors:
|
|
340
|
-
channel_titles:
|
|
341
|
-
fig_title:
|
|
478
|
+
colors: list[str] | None = None,
|
|
479
|
+
channel_titles: list[str] | None = None,
|
|
480
|
+
fig_title: str | None = None,
|
|
342
481
|
**plot_kwargs: bool,
|
|
343
482
|
) -> None:
|
|
344
|
-
"""
|
|
483
|
+
"""
|
|
484
|
+
Plot query and reference curves as a function of time.
|
|
485
|
+
|
|
486
|
+
For each channel, this function overlays the query intensity and the
|
|
487
|
+
reference intensity over time. This is useful to visually compare
|
|
488
|
+
dynamics in the original time domain.
|
|
345
489
|
|
|
346
490
|
Parameters
|
|
347
491
|
----------
|
|
348
|
-
reference_df:
|
|
349
|
-
|
|
350
|
-
df:
|
|
351
|
-
|
|
352
|
-
channels:
|
|
353
|
-
|
|
354
|
-
ref_time_column: str
|
|
355
|
-
|
|
356
|
-
query_time_column: str
|
|
357
|
-
|
|
358
|
-
colors:
|
|
359
|
-
Colors for
|
|
360
|
-
plot_kwargs: dict
|
|
361
|
-
|
|
362
|
-
channel_titles:
|
|
363
|
-
titles
|
|
364
|
-
fig_title:
|
|
365
|
-
|
|
492
|
+
reference_df : pandas.DataFrame
|
|
493
|
+
Dataframe with reference curve data (time vs intensity).
|
|
494
|
+
df : pandas.DataFrame
|
|
495
|
+
Dataframe with query data (time vs intensity).
|
|
496
|
+
channels : list of str
|
|
497
|
+
Names of the channels to plot.
|
|
498
|
+
ref_time_column : str, optional
|
|
499
|
+
Column name for reference time values. Default is ``"time"``.
|
|
500
|
+
query_time_column : str, optional
|
|
501
|
+
Column name for query time values. Default is ``"time"``.
|
|
502
|
+
colors : list of str, optional
|
|
503
|
+
Colors to use for the reference curves.
|
|
504
|
+
plot_kwargs : dict
|
|
505
|
+
Additional keyword arguments passed to ``matplotlib.pyplot.plot``.
|
|
506
|
+
channel_titles : list, optional
|
|
507
|
+
Per-channel titles to display above each subplot.
|
|
508
|
+
fig_title : str, optional
|
|
509
|
+
Overall figure title.
|
|
366
510
|
"""
|
|
367
511
|
for channel in channels:
|
|
368
512
|
if channel not in reference_df.columns:
|
|
@@ -438,13 +582,14 @@ def plot_cell_trajectory(
|
|
|
438
582
|
min_track_length: int = 30,
|
|
439
583
|
centroid0_name: str = "centroid-0",
|
|
440
584
|
centroid1_name: str = "centroid-1",
|
|
441
|
-
phase_column:
|
|
442
|
-
percentage_column:
|
|
585
|
+
phase_column: str | None = None,
|
|
586
|
+
percentage_column: str | None = None,
|
|
443
587
|
coloring_mode: str = "phase",
|
|
444
|
-
line_cycle:
|
|
588
|
+
line_cycle: list | None = None,
|
|
445
589
|
**kwargs: int,
|
|
446
590
|
) -> None:
|
|
447
|
-
"""
|
|
591
|
+
"""
|
|
592
|
+
Plot cell migration trajectories with phase- or percentage-based coloring.
|
|
448
593
|
|
|
449
594
|
Parameters
|
|
450
595
|
----------
|
|
@@ -453,27 +598,31 @@ def plot_cell_trajectory(
|
|
|
453
598
|
track_id_name : str
|
|
454
599
|
Column name containing unique track identifiers.
|
|
455
600
|
min_track_length : int, optional
|
|
456
|
-
Minimum number of timepoints required to include a track
|
|
601
|
+
Minimum number of timepoints required to include a track.
|
|
602
|
+
Default is 30.
|
|
457
603
|
centroid0_name : str, optional
|
|
458
|
-
Column name for x-coordinate of cell centroid
|
|
604
|
+
Column name for x-coordinate of the cell centroid.
|
|
459
605
|
centroid1_name : str, optional
|
|
460
|
-
Column name for y-coordinate of cell centroid
|
|
606
|
+
Column name for y-coordinate of the cell centroid.
|
|
461
607
|
phase_column : str, optional
|
|
462
|
-
Column name containing cell
|
|
608
|
+
Column name containing cell-cycle phase information. Required if
|
|
609
|
+
``coloring_mode == "phase"``.
|
|
463
610
|
percentage_column : str, optional
|
|
464
|
-
Column name containing percentage values for coloring
|
|
611
|
+
Column name containing percentage values for coloring. Required if
|
|
612
|
+
``coloring_mode == "percentage"``.
|
|
465
613
|
coloring_mode : str, optional
|
|
466
|
-
Color tracks by cell
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
614
|
+
Color tracks by cell-cycle phase (``"phase"``) or by percentage
|
|
615
|
+
(``"percentage"``). Default is ``"phase"``.
|
|
616
|
+
line_cycle : list, optional
|
|
617
|
+
List of linestyles to cycle through for successive tracks.
|
|
618
|
+
kwargs : dict, optional
|
|
619
|
+
Additional keyword arguments passed to
|
|
620
|
+
``matplotlib.collections.LineCollection``.
|
|
472
621
|
|
|
473
622
|
Notes
|
|
474
623
|
-----
|
|
475
|
-
Phase or percentage columns need to be provided for the respective
|
|
476
|
-
If not, an error will be raised.
|
|
624
|
+
Phase or percentage columns need to be provided for the respective
|
|
625
|
+
coloring mode. If not, an error will be raised.
|
|
477
626
|
|
|
478
627
|
"""
|
|
479
628
|
# inital checks
|
|
@@ -540,7 +689,7 @@ def plot_cell_trajectory(
|
|
|
540
689
|
**kwargs,
|
|
541
690
|
)
|
|
542
691
|
)
|
|
543
|
-
|
|
692
|
+
_, ax = plt.subplots()
|
|
544
693
|
for line_collection in line_collections:
|
|
545
694
|
ax.add_collection(line_collection)
|
|
546
695
|
ax.margins(0.05)
|
fucciphase/sensor.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Union
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
@@ -7,8 +7,8 @@ from scipy import optimize
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def logistic(
|
|
10
|
-
x:
|
|
11
|
-
) ->
|
|
10
|
+
x: float | np.ndarray, center: float, sigma: float, sign: float = 1.0
|
|
11
|
+
) -> float | np.ndarray:
|
|
12
12
|
"""Logistic function."""
|
|
13
13
|
tiny = 1.0e-15
|
|
14
14
|
arg = sign * (x - center) / max(tiny, sigma)
|
|
@@ -16,21 +16,21 @@ def logistic(
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def accumulation_function(
|
|
19
|
-
x:
|
|
19
|
+
x: float | np.ndarray,
|
|
20
20
|
center: float,
|
|
21
21
|
sigma: float,
|
|
22
22
|
offset_intensity: float = 0,
|
|
23
|
-
) ->
|
|
23
|
+
) -> float | np.ndarray:
|
|
24
24
|
"""Function to describe accumulation of sensor."""
|
|
25
25
|
return 1.0 - logistic(x, center, sigma) - offset_intensity
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def degradation_function(
|
|
29
|
-
x:
|
|
29
|
+
x: float | np.ndarray,
|
|
30
30
|
center: float,
|
|
31
31
|
sigma: float,
|
|
32
32
|
offset_intensity: float = 0,
|
|
33
|
-
) ->
|
|
33
|
+
) -> float | np.ndarray:
|
|
34
34
|
"""Function to describe degradation of sensor."""
|
|
35
35
|
return 1.0 - logistic(x, center, sigma, sign=-1.0) - offset_intensity
|
|
36
36
|
|
|
@@ -41,9 +41,9 @@ class FUCCISensor(ABC):
|
|
|
41
41
|
@abstractmethod
|
|
42
42
|
def __init__(
|
|
43
43
|
self,
|
|
44
|
-
phase_percentages:
|
|
45
|
-
center:
|
|
46
|
-
sigma:
|
|
44
|
+
phase_percentages: list[float],
|
|
45
|
+
center: list[float],
|
|
46
|
+
sigma: list[float],
|
|
47
47
|
) -> None:
|
|
48
48
|
pass
|
|
49
49
|
|
|
@@ -55,17 +55,17 @@ class FUCCISensor(ABC):
|
|
|
55
55
|
|
|
56
56
|
@property
|
|
57
57
|
@abstractmethod
|
|
58
|
-
def phases(self) ->
|
|
58
|
+
def phases(self) -> list[str]:
|
|
59
59
|
"""Function to hard-code the supported phases of a sensor."""
|
|
60
60
|
pass
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
|
-
def phase_percentages(self) ->
|
|
63
|
+
def phase_percentages(self) -> list[float]:
|
|
64
64
|
"""Percentage of individual phases."""
|
|
65
65
|
return self._phase_percentages
|
|
66
66
|
|
|
67
67
|
@phase_percentages.setter
|
|
68
|
-
def phase_percentages(self, values:
|
|
68
|
+
def phase_percentages(self, values: list[float]) -> None:
|
|
69
69
|
if len(values) != len(self.phases):
|
|
70
70
|
raise ValueError("Pass percentage for each phase.")
|
|
71
71
|
|
|
@@ -76,25 +76,25 @@ class FUCCISensor(ABC):
|
|
|
76
76
|
self._phase_percentages = values
|
|
77
77
|
|
|
78
78
|
@abstractmethod
|
|
79
|
-
def get_phase(self, phase_markers: Union[
|
|
79
|
+
def get_phase(self, phase_markers: Union[list[bool], "pd.Series[bool]"]) -> str:
|
|
80
80
|
"""Get the discrete phase based on phase markers.
|
|
81
81
|
|
|
82
82
|
Notes
|
|
83
83
|
-----
|
|
84
|
-
|
|
84
|
+
The discrete phase refers to, for example, G1 or S phase.
|
|
85
85
|
The phase_markers must match the number of used fluorophores.
|
|
86
86
|
"""
|
|
87
87
|
pass
|
|
88
88
|
|
|
89
89
|
@abstractmethod
|
|
90
90
|
def get_estimated_cycle_percentage(
|
|
91
|
-
self, phase: str, intensities:
|
|
91
|
+
self, phase: str, intensities: list[float]
|
|
92
92
|
) -> float:
|
|
93
93
|
"""Estimate percentage based on sensor intensities."""
|
|
94
94
|
pass
|
|
95
95
|
|
|
96
96
|
def set_accumulation_and_degradation_parameters(
|
|
97
|
-
self, center:
|
|
97
|
+
self, center: list[float], sigma: list[float]
|
|
98
98
|
) -> None:
|
|
99
99
|
"""Pass list of functions for logistic functions.
|
|
100
100
|
|
|
@@ -114,8 +114,8 @@ class FUCCISensor(ABC):
|
|
|
114
114
|
|
|
115
115
|
@abstractmethod
|
|
116
116
|
def get_expected_intensities(
|
|
117
|
-
self, percentage:
|
|
118
|
-
) ->
|
|
117
|
+
self, percentage: float | np.ndarray
|
|
118
|
+
) -> list[float | np.ndarray]:
|
|
119
119
|
"""Return value of calibrated curves."""
|
|
120
120
|
pass
|
|
121
121
|
|
|
@@ -124,7 +124,7 @@ class FUCCISASensor(FUCCISensor):
|
|
|
124
124
|
"""FUCCI(SA) sensor."""
|
|
125
125
|
|
|
126
126
|
def __init__(
|
|
127
|
-
self, phase_percentages:
|
|
127
|
+
self, phase_percentages: list[float], center: list[float], sigma: list[float]
|
|
128
128
|
) -> None:
|
|
129
129
|
self.phase_percentages = phase_percentages
|
|
130
130
|
self.set_accumulation_and_degradation_parameters(center, sigma)
|
|
@@ -135,11 +135,11 @@ class FUCCISASensor(FUCCISensor):
|
|
|
135
135
|
return 2
|
|
136
136
|
|
|
137
137
|
@property
|
|
138
|
-
def phases(self) ->
|
|
138
|
+
def phases(self) -> list[str]:
|
|
139
139
|
"""Function to hard-code the supported phases of a sensor."""
|
|
140
140
|
return ["G1", "G1/S", "S/G2/M"]
|
|
141
141
|
|
|
142
|
-
def get_phase(self, phase_markers: Union[
|
|
142
|
+
def get_phase(self, phase_markers: Union[list[bool], "pd.Series[bool]"]) -> str:
|
|
143
143
|
"""Return the discrete phase based channel ON / OFF data for the
|
|
144
144
|
FUCCI(SA) sensor.
|
|
145
145
|
"""
|
|
@@ -279,7 +279,7 @@ class FUCCISASensor(FUCCISensor):
|
|
|
279
279
|
return g1s_perc + 0.5 * (100.0 - g1s_perc - g1_perc)
|
|
280
280
|
|
|
281
281
|
def get_estimated_cycle_percentage(
|
|
282
|
-
self, phase: str, intensities:
|
|
282
|
+
self, phase: str, intensities: list[float]
|
|
283
283
|
) -> float:
|
|
284
284
|
"""Estimate a cell cycle percentage based on intensities.
|
|
285
285
|
|
|
@@ -300,8 +300,8 @@ class FUCCISASensor(FUCCISensor):
|
|
|
300
300
|
return self._find_sg2m_percentage(intensities[1])
|
|
301
301
|
|
|
302
302
|
def get_expected_intensities(
|
|
303
|
-
self, percentage:
|
|
304
|
-
) ->
|
|
303
|
+
self, percentage: float | np.ndarray
|
|
304
|
+
) -> list[float | np.ndarray]:
|
|
305
305
|
"""Return value of calibrated curves."""
|
|
306
306
|
g1_acc = accumulation_function(
|
|
307
307
|
percentage, self._center_values[0], self._sigma_values[0]
|
|
@@ -332,7 +332,7 @@ class PIPFUCCISensor(FUCCISensor):
|
|
|
332
332
|
"""PIP-FUCCI sensor."""
|
|
333
333
|
|
|
334
334
|
def __init__(
|
|
335
|
-
self, phase_percentages:
|
|
335
|
+
self, phase_percentages: list[float], center: list[float], sigma: list[float]
|
|
336
336
|
) -> None:
|
|
337
337
|
self.phase_percentages = phase_percentages
|
|
338
338
|
self.set_accumulation_and_degradation_parameters(center, sigma)
|
|
@@ -343,11 +343,11 @@ class PIPFUCCISensor(FUCCISensor):
|
|
|
343
343
|
return 2
|
|
344
344
|
|
|
345
345
|
@property
|
|
346
|
-
def phases(self) ->
|
|
346
|
+
def phases(self) -> list[str]:
|
|
347
347
|
"""Function to hard-code the supported phases of a sensor."""
|
|
348
348
|
return ["G1", "S", "G2/M"]
|
|
349
349
|
|
|
350
|
-
def get_phase(self, phase_markers: Union[
|
|
350
|
+
def get_phase(self, phase_markers: Union[list[bool], "pd.Series[bool]"]) -> str:
|
|
351
351
|
"""Return the discrete phase based channel ON / OFF data for the
|
|
352
352
|
FUCCI(SA) sensor.
|
|
353
353
|
"""
|
|
@@ -414,7 +414,7 @@ class PIPFUCCISensor(FUCCISensor):
|
|
|
414
414
|
raise NotImplementedError("Percentage estimate not yet implemented!")
|
|
415
415
|
|
|
416
416
|
def get_estimated_cycle_percentage(
|
|
417
|
-
self, phase: str, intensities:
|
|
417
|
+
self, phase: str, intensities: list[float]
|
|
418
418
|
) -> float:
|
|
419
419
|
"""Estimate a cell cycle percentage based on intensities.
|
|
420
420
|
|
|
@@ -437,8 +437,8 @@ class PIPFUCCISensor(FUCCISensor):
|
|
|
437
437
|
return self._find_g2m_percentage(intensities[1])
|
|
438
438
|
|
|
439
439
|
def get_expected_intensities(
|
|
440
|
-
self, percentage:
|
|
441
|
-
) ->
|
|
440
|
+
self, percentage: float | np.ndarray
|
|
441
|
+
) -> list[float | np.ndarray]:
|
|
442
442
|
"""Return value of calibrated curves."""
|
|
443
443
|
raise NotImplementedError("Intensity estimate not yet implemented!")
|
|
444
444
|
|