pycellin 0.3.5b3__tar.gz
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.
- pycellin-0.3.5b3/LICENSE +27 -0
- pycellin-0.3.5b3/PKG-INFO +124 -0
- pycellin-0.3.5b3/README.md +91 -0
- pycellin-0.3.5b3/pycellin/__init__.py +66 -0
- pycellin-0.3.5b3/pycellin/classes/__init__.py +24 -0
- pycellin-0.3.5b3/pycellin/classes/data.py +263 -0
- pycellin-0.3.5b3/pycellin/classes/exceptions.py +107 -0
- pycellin-0.3.5b3/pycellin/classes/feature.py +812 -0
- pycellin-0.3.5b3/pycellin/classes/feature_calculator.py +431 -0
- pycellin-0.3.5b3/pycellin/classes/lineage.py +1323 -0
- pycellin-0.3.5b3/pycellin/classes/model.py +1708 -0
- pycellin-0.3.5b3/pycellin/classes/updater.py +192 -0
- pycellin-0.3.5b3/pycellin/custom_types.py +18 -0
- pycellin-0.3.5b3/pycellin/graph/__init__.py +0 -0
- pycellin-0.3.5b3/pycellin/graph/features/__init__.py +14 -0
- pycellin-0.3.5b3/pycellin/graph/features/morphology.py +587 -0
- pycellin-0.3.5b3/pycellin/graph/features/motion.py +473 -0
- pycellin-0.3.5b3/pycellin/graph/features/tracking.py +331 -0
- pycellin-0.3.5b3/pycellin/graph/features/utils.py +52 -0
- pycellin-0.3.5b3/pycellin/io/__init__.py +4 -0
- pycellin-0.3.5b3/pycellin/io/cell_tracking_challenge/__init__.py +2 -0
- pycellin-0.3.5b3/pycellin/io/cell_tracking_challenge/exporter.py +286 -0
- pycellin-0.3.5b3/pycellin/io/cell_tracking_challenge/loader.py +305 -0
- pycellin-0.3.5b3/pycellin/io/trackmate/__init__.py +2 -0
- pycellin-0.3.5b3/pycellin/io/trackmate/exporter.py +587 -0
- pycellin-0.3.5b3/pycellin/io/trackmate/loader.py +1234 -0
- pycellin-0.3.5b3/pycellin/utils.py +10 -0
- pycellin-0.3.5b3/pycellin.egg-info/PKG-INFO +124 -0
- pycellin-0.3.5b3/pycellin.egg-info/SOURCES.txt +32 -0
- pycellin-0.3.5b3/pycellin.egg-info/dependency_links.txt +1 -0
- pycellin-0.3.5b3/pycellin.egg-info/requires.txt +13 -0
- pycellin-0.3.5b3/pycellin.egg-info/top_level.txt +1 -0
- pycellin-0.3.5b3/pyproject.toml +55 -0
- pycellin-0.3.5b3/setup.cfg +4 -0
pycellin-0.3.5b3/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright 2023 Institut Pasteur Paris, Laura Xénard
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
14
|
+
may be used to endorse or promote products derived from this software without
|
|
15
|
+
specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
20
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
21
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
22
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
23
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
24
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
25
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
26
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
27
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: pycellin
|
|
3
|
+
Version: 0.3.5b3
|
|
4
|
+
Summary: Graph-based framework to manipulate and analyze cell lineages from cell tracking data
|
|
5
|
+
Author-email: Laura Xénard <laura.xenard@pasteur.fr>
|
|
6
|
+
Project-URL: Documentation, https://Image-Analysis-Hub.github.io/pycellin/
|
|
7
|
+
Project-URL: Examples, https://Image-Analysis-Hub.github.io/pycellin/notebooks/
|
|
8
|
+
Project-URL: Issues, https://github.com/Image-Analysis-Hub/pycellin/issues
|
|
9
|
+
Project-URL: Source, https://github.com/Image-Analysis-Hub/pycellin/
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: igraph>=0.9
|
|
22
|
+
Requires-Dist: lxml>=5
|
|
23
|
+
Requires-Dist: matplotlib>=3
|
|
24
|
+
Requires-Dist: networkx>=3
|
|
25
|
+
Requires-Dist: pandas>=2
|
|
26
|
+
Requires-Dist: plotly>=5
|
|
27
|
+
Requires-Dist: scikit-image>=0.19
|
|
28
|
+
Requires-Dist: scipy>=1.15
|
|
29
|
+
Requires-Dist: shapely>=2
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=8.3; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-cov>=6.0; extra == "test"
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
[](https://github.com/psf/black)
|
|
36
|
+
|
|
37
|
+
# Pycellin
|
|
38
|
+
|
|
39
|
+
Pycellin is a graph-based Python framework to easily manipulate and extract information from cell tracking data, at the single-cell level. In Pycellin, cell lineages are modeled intuitively by directed acyclic graphs (DAG). Graph nodes represent cells at a specific point in time and space, and graph edges represent the time and space displacement of the cells. Please note that while Pycellin is built to support cell division events, **it does not authorize cell merging events**: a cell at a specific timepoint cannot have more than one parent.
|
|
40
|
+
|
|
41
|
+
Pycellin provides predefined features related to cell morphology, cell motion and tracking that can be automatically added to enrich lineages. More predefined features will be implemented in the future. The framework also facilitates the creation of new features defined by the user to accommodate the wide variety of experiments and biological questions.
|
|
42
|
+
|
|
43
|
+
Pycellin can read from and write to TrackMate XML and Cell Tracking Challenge text file formats. More tracking formats will progressively be supported.
|
|
44
|
+
|
|
45
|
+
While Pycellin has been designed with bacteria / cell lineages in mind, it could be used with more diverse tracking data provided the few conditions below are enforced:
|
|
46
|
+
- the tracking data can be modeled by a DAG, meaning no merging event
|
|
47
|
+
- time must flow homogeneously, i.e. all the edges of a lineage graph must represent the same elapsed time.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Pycellin supports Python 3.10 and above. It is tested with Python 3.10 and 3.13 on the latest versions of Ubuntu, Windows and MacOS. Please let me know if you encounter any compatibility issue with a different combination.
|
|
53
|
+
|
|
54
|
+
You can install Pycellin from [PyPI](https://pypi.org/):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
pip install pycellin
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
To install Pycellin with the optional test related dependencies:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
pip install pycellin[test]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Code Example
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import pycellin
|
|
71
|
+
|
|
72
|
+
# Import data from an external tool, here TrackMate.
|
|
73
|
+
xml_path = "sample_data/Ecoli_growth_on_agar_pad.xml"
|
|
74
|
+
model = pycellin.load_TrackMate_XML(xml_path)
|
|
75
|
+
|
|
76
|
+
# Plot the cell lineages.
|
|
77
|
+
for lin in model.get_cell_lineages():
|
|
78
|
+
plot(lin)
|
|
79
|
+
|
|
80
|
+
# Compute and plot the cell cycle lineages.
|
|
81
|
+
model.add_cycle_data()
|
|
82
|
+
for clin in model.get_cycle_lineages():
|
|
83
|
+
plot(clin)
|
|
84
|
+
|
|
85
|
+
# Enrich your lineages with additional predefined features.
|
|
86
|
+
model.add_pycellin_features([
|
|
87
|
+
"cell_length",
|
|
88
|
+
"cell_width",
|
|
89
|
+
"cell_displacement",
|
|
90
|
+
"cell_speed",
|
|
91
|
+
"branch_mean_speed",
|
|
92
|
+
"relative_age",
|
|
93
|
+
"division_time",
|
|
94
|
+
"division_rate",
|
|
95
|
+
"cell_cycle_completeness",
|
|
96
|
+
])
|
|
97
|
+
model.update()
|
|
98
|
+
|
|
99
|
+
# Export the enriched data as dataframes.
|
|
100
|
+
cell_df = model.to_cell_dataframe()
|
|
101
|
+
link_df = model.to_link_dataframe()
|
|
102
|
+
cycle_df = model.to_cycle_dataframe()
|
|
103
|
+
lineage_df = model.to_lineage_dataframe()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
Please note that the following notebooks are still a work in progress. There may be some mistakes in the code and some sections might move from one notebook to another.
|
|
110
|
+
|
|
111
|
+
| Notebook | Description | Level | State |
|
|
112
|
+
|------------------------------------------------------------------------------------------|-------------------------------------------------------------------|----------|-------|
|
|
113
|
+
| [Getting started](./notebooks/Getting%20started.ipynb) | The basics of Pycellin, through examples | Beginner | WIP |
|
|
114
|
+
| [Managing features](./notebooks/Managing%20features.ipynb) | How to add, compute and remove features from a model | Beginner | WIP |
|
|
115
|
+
| [Working with TrackMate data](./notebooks/Working%20with%20TrackMate%20data.ipynb) | How Pycellin can work with TrackMate, through an example | Beginner | WIP |
|
|
116
|
+
| [Creating a model from scratch](./notebooks/Creating%20a%20model%20from%20scratch.ipynb) | How to manually create a Pycellin model, including its lineages | Advanced | Stub |
|
|
117
|
+
| [Custom features](./notebooks/Custom%20features.ipynb) | How to create user-defined features and augment a model with them | Advanced | WIP |
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
## Credits
|
|
121
|
+
|
|
122
|
+
- [NetworkX](https://networkx.org/) for lineages modeling ([Hagberg, Schult and Swart, 2008](http://conference.scipy.org.s3-website-us-east-1.amazonaws.com/proceedings/scipy2008/paper_2/))
|
|
123
|
+
- [TrackMate](https://imagej.net/plugins/trackmate/) for the TrackMate data loader and exporter ([Tinevez et al., 2017](https://doi.org/10.1016/j.ymeth.2016.09.016), [Ershov et al., 2022](https://doi:10.1038/s41592-022-01507-1))
|
|
124
|
+
- The [Cell Tracking Challenge](https://celltrackingchallenge.net/) for the CTC data loader and exporter ([Maška et al., 2023](https://doi.org/10.1038/s41592-023-01879-y))
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+

|
|
2
|
+
[](https://github.com/psf/black)
|
|
3
|
+
|
|
4
|
+
# Pycellin
|
|
5
|
+
|
|
6
|
+
Pycellin is a graph-based Python framework to easily manipulate and extract information from cell tracking data, at the single-cell level. In Pycellin, cell lineages are modeled intuitively by directed acyclic graphs (DAG). Graph nodes represent cells at a specific point in time and space, and graph edges represent the time and space displacement of the cells. Please note that while Pycellin is built to support cell division events, **it does not authorize cell merging events**: a cell at a specific timepoint cannot have more than one parent.
|
|
7
|
+
|
|
8
|
+
Pycellin provides predefined features related to cell morphology, cell motion and tracking that can be automatically added to enrich lineages. More predefined features will be implemented in the future. The framework also facilitates the creation of new features defined by the user to accommodate the wide variety of experiments and biological questions.
|
|
9
|
+
|
|
10
|
+
Pycellin can read from and write to TrackMate XML and Cell Tracking Challenge text file formats. More tracking formats will progressively be supported.
|
|
11
|
+
|
|
12
|
+
While Pycellin has been designed with bacteria / cell lineages in mind, it could be used with more diverse tracking data provided the few conditions below are enforced:
|
|
13
|
+
- the tracking data can be modeled by a DAG, meaning no merging event
|
|
14
|
+
- time must flow homogeneously, i.e. all the edges of a lineage graph must represent the same elapsed time.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Pycellin supports Python 3.10 and above. It is tested with Python 3.10 and 3.13 on the latest versions of Ubuntu, Windows and MacOS. Please let me know if you encounter any compatibility issue with a different combination.
|
|
20
|
+
|
|
21
|
+
You can install Pycellin from [PyPI](https://pypi.org/):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install pycellin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To install Pycellin with the optional test related dependencies:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pip install pycellin[test]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Code Example
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import pycellin
|
|
38
|
+
|
|
39
|
+
# Import data from an external tool, here TrackMate.
|
|
40
|
+
xml_path = "sample_data/Ecoli_growth_on_agar_pad.xml"
|
|
41
|
+
model = pycellin.load_TrackMate_XML(xml_path)
|
|
42
|
+
|
|
43
|
+
# Plot the cell lineages.
|
|
44
|
+
for lin in model.get_cell_lineages():
|
|
45
|
+
plot(lin)
|
|
46
|
+
|
|
47
|
+
# Compute and plot the cell cycle lineages.
|
|
48
|
+
model.add_cycle_data()
|
|
49
|
+
for clin in model.get_cycle_lineages():
|
|
50
|
+
plot(clin)
|
|
51
|
+
|
|
52
|
+
# Enrich your lineages with additional predefined features.
|
|
53
|
+
model.add_pycellin_features([
|
|
54
|
+
"cell_length",
|
|
55
|
+
"cell_width",
|
|
56
|
+
"cell_displacement",
|
|
57
|
+
"cell_speed",
|
|
58
|
+
"branch_mean_speed",
|
|
59
|
+
"relative_age",
|
|
60
|
+
"division_time",
|
|
61
|
+
"division_rate",
|
|
62
|
+
"cell_cycle_completeness",
|
|
63
|
+
])
|
|
64
|
+
model.update()
|
|
65
|
+
|
|
66
|
+
# Export the enriched data as dataframes.
|
|
67
|
+
cell_df = model.to_cell_dataframe()
|
|
68
|
+
link_df = model.to_link_dataframe()
|
|
69
|
+
cycle_df = model.to_cycle_dataframe()
|
|
70
|
+
lineage_df = model.to_lineage_dataframe()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Please note that the following notebooks are still a work in progress. There may be some mistakes in the code and some sections might move from one notebook to another.
|
|
77
|
+
|
|
78
|
+
| Notebook | Description | Level | State |
|
|
79
|
+
|------------------------------------------------------------------------------------------|-------------------------------------------------------------------|----------|-------|
|
|
80
|
+
| [Getting started](./notebooks/Getting%20started.ipynb) | The basics of Pycellin, through examples | Beginner | WIP |
|
|
81
|
+
| [Managing features](./notebooks/Managing%20features.ipynb) | How to add, compute and remove features from a model | Beginner | WIP |
|
|
82
|
+
| [Working with TrackMate data](./notebooks/Working%20with%20TrackMate%20data.ipynb) | How Pycellin can work with TrackMate, through an example | Beginner | WIP |
|
|
83
|
+
| [Creating a model from scratch](./notebooks/Creating%20a%20model%20from%20scratch.ipynb) | How to manually create a Pycellin model, including its lineages | Advanced | Stub |
|
|
84
|
+
| [Custom features](./notebooks/Custom%20features.ipynb) | How to create user-defined features and augment a model with them | Advanced | WIP |
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Credits
|
|
88
|
+
|
|
89
|
+
- [NetworkX](https://networkx.org/) for lineages modeling ([Hagberg, Schult and Swart, 2008](http://conference.scipy.org.s3-website-us-east-1.amazonaws.com/proceedings/scipy2008/paper_2/))
|
|
90
|
+
- [TrackMate](https://imagej.net/plugins/trackmate/) for the TrackMate data loader and exporter ([Tinevez et al., 2017](https://doi.org/10.1016/j.ymeth.2016.09.016), [Ershov et al., 2022](https://doi:10.1038/s41592-022-01507-1))
|
|
91
|
+
- The [Cell Tracking Challenge](https://celltrackingchallenge.net/) for the CTC data loader and exporter ([Maška et al., 2023](https://doi.org/10.1038/s41592-023-01879-y))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from .classes.data import Data
|
|
2
|
+
from .classes.lineage import CellLineage, CycleLineage
|
|
3
|
+
from .classes.feature import FeaturesDeclaration, Feature
|
|
4
|
+
from .classes.feature import (
|
|
5
|
+
frame_Feature,
|
|
6
|
+
cell_ID_Feature,
|
|
7
|
+
lineage_ID_Feature,
|
|
8
|
+
cell_coord_Feature,
|
|
9
|
+
link_coord_Feature,
|
|
10
|
+
lineage_coord_Feature,
|
|
11
|
+
cycle_ID_Feature,
|
|
12
|
+
cells_Feature,
|
|
13
|
+
cycle_length_Feature,
|
|
14
|
+
level_Feature,
|
|
15
|
+
)
|
|
16
|
+
from .classes.model import Model
|
|
17
|
+
from .classes.feature_calculator import (
|
|
18
|
+
NodeLocalFeatureCalculator,
|
|
19
|
+
EdgeLocalFeatureCalculator,
|
|
20
|
+
LineageLocalFeatureCalculator,
|
|
21
|
+
NodeGlobalFeatureCalculator,
|
|
22
|
+
EdgeGlobalFeatureCalculator,
|
|
23
|
+
LineageGlobalFeatureCalculator,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from .io.cell_tracking_challenge.loader import load_CTC_file
|
|
27
|
+
from .io.cell_tracking_challenge.exporter import export_CTC_file
|
|
28
|
+
from .io.trackmate.loader import load_TrackMate_XML
|
|
29
|
+
from .io.trackmate.exporter import export_TrackMate_XML
|
|
30
|
+
|
|
31
|
+
from .graph.features.utils import (
|
|
32
|
+
get_pycellin_cell_lineage_features,
|
|
33
|
+
get_pycellin_cycle_lineage_features,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Data",
|
|
39
|
+
"CellLineage",
|
|
40
|
+
"CycleLineage",
|
|
41
|
+
"FeaturesDeclaration",
|
|
42
|
+
"Feature",
|
|
43
|
+
"frame_Feature",
|
|
44
|
+
"cell_ID_Feature",
|
|
45
|
+
"lineage_ID_Feature",
|
|
46
|
+
"cell_coord_Feature",
|
|
47
|
+
"link_coord_Feature",
|
|
48
|
+
"lineage_coord_Feature",
|
|
49
|
+
"cycle_ID_Feature",
|
|
50
|
+
"cells_Feature",
|
|
51
|
+
"cycle_length_Feature",
|
|
52
|
+
"level_Feature",
|
|
53
|
+
"Model",
|
|
54
|
+
"NodeLocalFeatureCalculator",
|
|
55
|
+
"EdgeLocalFeatureCalculator",
|
|
56
|
+
"LineageLocalFeatureCalculator",
|
|
57
|
+
"NodeGlobalFeatureCalculator",
|
|
58
|
+
"EdgeGlobalFeatureCalculator",
|
|
59
|
+
"LineageGlobalFeatureCalculator",
|
|
60
|
+
"load_CTC_file",
|
|
61
|
+
"export_CTC_file",
|
|
62
|
+
"load_TrackMate_XML",
|
|
63
|
+
"export_TrackMate_XML",
|
|
64
|
+
"get_pycellin_cell_lineage_features",
|
|
65
|
+
"get_pycellin_cycle_lineage_features",
|
|
66
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .data import Data
|
|
2
|
+
from .lineage import CellLineage, CycleLineage
|
|
3
|
+
from .feature import FeaturesDeclaration, Feature
|
|
4
|
+
from .feature import (
|
|
5
|
+
frame_Feature,
|
|
6
|
+
cell_ID_Feature,
|
|
7
|
+
lineage_ID_Feature,
|
|
8
|
+
cell_coord_Feature,
|
|
9
|
+
link_coord_Feature,
|
|
10
|
+
lineage_coord_Feature,
|
|
11
|
+
cycle_ID_Feature,
|
|
12
|
+
cells_Feature,
|
|
13
|
+
cycle_length_Feature,
|
|
14
|
+
level_Feature,
|
|
15
|
+
)
|
|
16
|
+
from .model import Model
|
|
17
|
+
from .feature_calculator import (
|
|
18
|
+
NodeLocalFeatureCalculator,
|
|
19
|
+
EdgeLocalFeatureCalculator,
|
|
20
|
+
LineageLocalFeatureCalculator,
|
|
21
|
+
NodeGlobalFeatureCalculator,
|
|
22
|
+
EdgeGlobalFeatureCalculator,
|
|
23
|
+
LineageGlobalFeatureCalculator,
|
|
24
|
+
)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
from typing import Literal
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
|
|
10
|
+
from pycellin.classes.lineage import Lineage, CellLineage, CycleLineage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Data:
|
|
14
|
+
"""
|
|
15
|
+
Class to store and manipulate cell lineages and cell cycle lineages.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, data: dict[str, CellLineage], add_cycle_data: bool = False
|
|
20
|
+
) -> None:
|
|
21
|
+
self.cell_data = data
|
|
22
|
+
if add_cycle_data:
|
|
23
|
+
self._compute_cycle_lineages()
|
|
24
|
+
else:
|
|
25
|
+
self.cycle_data = None
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
return f"Data(cell_data={self.cell_data!r}, cycle_data={self.cycle_data!r})"
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
if self.cycle_data:
|
|
32
|
+
txt = f" and {self.number_of_lineages()} cycle lineages"
|
|
33
|
+
else:
|
|
34
|
+
txt = ""
|
|
35
|
+
return f"Data object with {self.number_of_lineages()} cell lineages{txt}."
|
|
36
|
+
|
|
37
|
+
def _add_cycle_lineages(self, lineage_IDs: list[int] | None = None) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Add the cell cycle lineages from the cell lineages.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
lineage_IDs : list[int], optional
|
|
44
|
+
The IDs of the lineages to compute the cycle lineages for,
|
|
45
|
+
by default None i.e. all lineages.
|
|
46
|
+
"""
|
|
47
|
+
if lineage_IDs is None:
|
|
48
|
+
lineage_IDs = list(self.cell_data.keys())
|
|
49
|
+
self.cycle_data = {
|
|
50
|
+
lin_id: self._compute_cycle_lineage(lin_id) for lin_id in lineage_IDs
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def _compute_cycle_lineage(self, lineage_ID: int) -> CycleLineage:
|
|
54
|
+
"""
|
|
55
|
+
Compute and return the cycle lineage corresponding to a given cell lineage.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
lineage_ID : int
|
|
60
|
+
The ID of the cell lineage.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
CycleLineage
|
|
65
|
+
The cycle lineage corresponding to the cell lineage.
|
|
66
|
+
"""
|
|
67
|
+
return CycleLineage(self.cell_data[lineage_ID])
|
|
68
|
+
|
|
69
|
+
def _freeze_lineage_data(self):
|
|
70
|
+
"""
|
|
71
|
+
Freeze all cell lineages.
|
|
72
|
+
|
|
73
|
+
When a cell lineage is frozen, its structure cannot be modified:
|
|
74
|
+
nodes and edges cannot be added or removed. However, graph, node and edge
|
|
75
|
+
attributes can still be modified.
|
|
76
|
+
"""
|
|
77
|
+
for lineage in self.cell_data.values():
|
|
78
|
+
if not nx.is_frozen(lineage):
|
|
79
|
+
nx.freeze(lineage)
|
|
80
|
+
|
|
81
|
+
def _unfreeze_lineage_data(self):
|
|
82
|
+
"""
|
|
83
|
+
Unfreeze all cell lineages.
|
|
84
|
+
"""
|
|
85
|
+
for lineage in self.cell_data.values():
|
|
86
|
+
Lineage.unfreeze(lineage)
|
|
87
|
+
|
|
88
|
+
def number_of_lineages(self) -> int:
|
|
89
|
+
"""
|
|
90
|
+
Return the number of lineages in the data.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
int
|
|
95
|
+
The number of cell lineages in the data.
|
|
96
|
+
|
|
97
|
+
Raises
|
|
98
|
+
------
|
|
99
|
+
Warning
|
|
100
|
+
If the number of cell lineages and cycle lineages do not match.
|
|
101
|
+
"""
|
|
102
|
+
if self.cycle_data:
|
|
103
|
+
if len(self.cell_data) != len(self.cycle_data):
|
|
104
|
+
msg = (
|
|
105
|
+
f"Number of cell lineages ({len(self.cell_data)}) "
|
|
106
|
+
f"and cycle lineages ({len(self.cycle_data)}) do not match. "
|
|
107
|
+
"An update of the model is required. "
|
|
108
|
+
)
|
|
109
|
+
warnings.warn(msg)
|
|
110
|
+
return len(self.cell_data)
|
|
111
|
+
|
|
112
|
+
def get_closest_cell(
|
|
113
|
+
self,
|
|
114
|
+
noi: int,
|
|
115
|
+
lineage: CellLineage,
|
|
116
|
+
radius: float = 0,
|
|
117
|
+
time_window: int = 0,
|
|
118
|
+
time_window_type: Literal["before", "after", "symetric"] = "symetric",
|
|
119
|
+
lineages_to_search: list[CellLineage] = None,
|
|
120
|
+
reference: Literal["center", "border"] = "center",
|
|
121
|
+
) -> tuple[CellLineage, int]:
|
|
122
|
+
"""
|
|
123
|
+
Find the closest cell to a given cell of a lineage.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
noi : int
|
|
128
|
+
Node of interest, the one for which to find the closest cell.
|
|
129
|
+
lineage : CellLineage
|
|
130
|
+
The lineage the node belongs to.
|
|
131
|
+
radius : float, optional
|
|
132
|
+
The maximum distance to consider, by default 0.
|
|
133
|
+
If 0, the whole space is considered.
|
|
134
|
+
time_window : int, optional
|
|
135
|
+
The time window to consider, by default 0 i.e. only the current frame.
|
|
136
|
+
time_window_type : Literal["before", "after", "symetric"], optional
|
|
137
|
+
The type of time window to consider, by default "symetric".
|
|
138
|
+
lineages_to_search : list[CellLineage], optional
|
|
139
|
+
The lineages to search in, by default None i.e. all lineages.
|
|
140
|
+
reference : Literal["center", "border"], optional
|
|
141
|
+
The reference point to consider for the distance, by default "center".
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
tuple[int, CellLineage]
|
|
146
|
+
The node ID of the closest cell and the lineage it belongs to.
|
|
147
|
+
"""
|
|
148
|
+
distances = self.get_closest_cells(
|
|
149
|
+
noi=noi,
|
|
150
|
+
lineage=lineage,
|
|
151
|
+
radius=radius,
|
|
152
|
+
time_window=time_window,
|
|
153
|
+
time_window_type=time_window_type,
|
|
154
|
+
lineages_to_search=lineages_to_search,
|
|
155
|
+
reference=reference,
|
|
156
|
+
)
|
|
157
|
+
return distances[0]
|
|
158
|
+
|
|
159
|
+
def get_closest_cells(
|
|
160
|
+
self,
|
|
161
|
+
noi: int,
|
|
162
|
+
lineage: CellLineage,
|
|
163
|
+
radius: float = 0,
|
|
164
|
+
time_window: int = 0,
|
|
165
|
+
time_window_type: Literal["before", "after", "symetric"] = "symetric",
|
|
166
|
+
lineages_to_search: list[CellLineage] = None,
|
|
167
|
+
reference: Literal["center", "border"] = "center",
|
|
168
|
+
) -> list[tuple[int, CellLineage]]:
|
|
169
|
+
"""
|
|
170
|
+
Find the closest cells to a given cell of a lineage.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
noi : int
|
|
175
|
+
Node of interest, the one for which to find the closest cell.
|
|
176
|
+
lineage : CellLineage
|
|
177
|
+
The lineage the node belongs to.
|
|
178
|
+
radius : float, optional
|
|
179
|
+
The maximum distance to consider, by default 0.
|
|
180
|
+
If 0, the whole space is considered.
|
|
181
|
+
time_window : int, optional
|
|
182
|
+
The time window to consider, by default 0 i.e. only the current frame.
|
|
183
|
+
time_window_type : Literal["before", "after", "symetric"], optional
|
|
184
|
+
The type of time window to consider, by default "symetric".
|
|
185
|
+
lineages_to_search : list[CellLineage], optional
|
|
186
|
+
The lineages to search in, by default None i.e. all lineages.
|
|
187
|
+
reference : Literal["center", "border"], optional
|
|
188
|
+
The reference point to consider for the distance, by default "center".
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
tuple[int, CellLineage]
|
|
193
|
+
The node ID of the closest cells and the lineages it belongs to,
|
|
194
|
+
sorted by increasing distance.
|
|
195
|
+
"""
|
|
196
|
+
# TODO: implement the reference parameter
|
|
197
|
+
|
|
198
|
+
# Identification of the frames to search in.
|
|
199
|
+
center_frame = lineage.nodes[noi]["frame"]
|
|
200
|
+
if time_window == 0:
|
|
201
|
+
frames_to_search = [center_frame]
|
|
202
|
+
else:
|
|
203
|
+
if time_window_type == "symetric":
|
|
204
|
+
frames_to_search = list(
|
|
205
|
+
range(center_frame - time_window, center_frame + time_window + 1)
|
|
206
|
+
)
|
|
207
|
+
elif time_window_type == "before":
|
|
208
|
+
frames_to_search = list(
|
|
209
|
+
range(center_frame - time_window, center_frame + 1)
|
|
210
|
+
)
|
|
211
|
+
elif time_window_type == "after":
|
|
212
|
+
frames_to_search = list(
|
|
213
|
+
range(center_frame, center_frame + time_window + 1)
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Unknown time window type: '{time_window_type}'."
|
|
218
|
+
" Should be 'before', 'after' or 'symetric'."
|
|
219
|
+
)
|
|
220
|
+
frames_to_search.sort()
|
|
221
|
+
|
|
222
|
+
# Identification of nodes that are good candidates,
|
|
223
|
+
# i.e. nodes that are in the time window
|
|
224
|
+
# and in the lineages to search in.
|
|
225
|
+
if not lineages_to_search:
|
|
226
|
+
lineages_to_search = list(self.cell_data.values())
|
|
227
|
+
candidate_cells = {}
|
|
228
|
+
for lin in lineages_to_search:
|
|
229
|
+
nodes = [
|
|
230
|
+
node
|
|
231
|
+
for node, frame in lin.nodes(data="frame")
|
|
232
|
+
if frame in frames_to_search
|
|
233
|
+
]
|
|
234
|
+
if nodes:
|
|
235
|
+
candidate_cells[lin] = nodes
|
|
236
|
+
# Need to remove the node itself from the candidates.
|
|
237
|
+
candidate_cells[lineage].remove(noi)
|
|
238
|
+
|
|
239
|
+
# Identification of the closest cell.
|
|
240
|
+
distances = []
|
|
241
|
+
for lin, nodes in candidate_cells.items():
|
|
242
|
+
for node in nodes:
|
|
243
|
+
distance = math.dist(
|
|
244
|
+
lineage.nodes[noi]["location"], lin.nodes[node]["location"]
|
|
245
|
+
)
|
|
246
|
+
if radius == 0 or distance <= radius:
|
|
247
|
+
distances.append((node, lin, distance))
|
|
248
|
+
distances.sort(key=lambda x: x[2])
|
|
249
|
+
return [(node, lin) for node, lin, _ in distances]
|
|
250
|
+
|
|
251
|
+
# def get_neighbouring_cells(
|
|
252
|
+
# lineage: CellLineage,
|
|
253
|
+
# node: int,
|
|
254
|
+
# radius: float,
|
|
255
|
+
# time_window: int | tuple[int, int],
|
|
256
|
+
# ) -> list[tuple[CellLineage, int]]:
|
|
257
|
+
# """ """
|
|
258
|
+
# # TODO: implement get_neighbouring_cells()
|
|
259
|
+
# # Parameter to define sort order? By default closest to farthest
|
|
260
|
+
# # Need to implement get_distance() between 2 nodes, not necessarily
|
|
261
|
+
# # from the same lineage...
|
|
262
|
+
# # To identify a node, need to have lineage_ID and cell_ID
|
|
263
|
+
# pass
|