AutoMorphoTrack 2.1.0__tar.gz → 2.4.0__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.
- automorphotrack-2.4.0/MANIFEST.in +10 -0
- automorphotrack-2.4.0/PKG-INFO +93 -0
- automorphotrack-2.4.0/README.md +49 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/__init__.py +37 -22
- automorphotrack-2.4.0/automorphotrack/adaptive_segmentation.py +121 -0
- automorphotrack-2.4.0/automorphotrack/benchmarking.py +188 -0
- automorphotrack-2.4.0/automorphotrack/cli.py +250 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/colocalization.py +38 -14
- automorphotrack-2.4.0/automorphotrack/mcp_server.py +272 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/motility.py +51 -1
- automorphotrack-2.4.0/automorphotrack/napari.yaml +35 -0
- automorphotrack-2.4.0/automorphotrack/napari_plugin.py +272 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/shape_profiling.py +19 -4
- automorphotrack-2.4.0/automorphotrack/utils.py +127 -0
- automorphotrack-2.4.0/automorphotrack/validated/__init__.py +20 -0
- automorphotrack-2.4.0/automorphotrack/validated/__main__.py +22 -0
- automorphotrack-2.4.0/automorphotrack/validated/colocalization.py +11 -0
- automorphotrack-2.4.0/automorphotrack/validated/config.py +88 -0
- automorphotrack-2.4.0/automorphotrack/validated/core.py +225 -0
- automorphotrack-2.4.0/automorphotrack/validated/detection.py +22 -0
- automorphotrack-2.4.0/automorphotrack/validated/io.py +66 -0
- automorphotrack-2.4.0/automorphotrack/validated/lyso_count.py +11 -0
- automorphotrack-2.4.0/automorphotrack/validated/morphology.py +11 -0
- automorphotrack-2.4.0/automorphotrack/validated/motility.py +25 -0
- automorphotrack-2.4.0/automorphotrack/validated/pipeline.py +195 -0
- automorphotrack-2.4.0/automorphotrack/validated/plotting.py +57 -0
- automorphotrack-2.4.0/automorphotrack/validated/shape_features.py +22 -0
- automorphotrack-2.4.0/automorphotrack/validated/shape_profiling.py +16 -0
- automorphotrack-2.4.0/automorphotrack/validated/summary.py +36 -0
- automorphotrack-2.4.0/automorphotrack/validated/tracking.py +11 -0
- automorphotrack-2.4.0/automorphotrack/validated/tracking_overlay.py +11 -0
- automorphotrack-2.4.0/automorphotrack/validated/utils.py +39 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/validation.py +138 -20
- automorphotrack-2.4.0/automorphotrack.egg-info/PKG-INFO +93 -0
- automorphotrack-2.4.0/automorphotrack.egg-info/SOURCES.txt +87 -0
- automorphotrack-2.4.0/automorphotrack.egg-info/entry_points.txt +8 -0
- automorphotrack-2.4.0/automorphotrack.egg-info/requires.txt +25 -0
- automorphotrack-2.4.0/docs/GUI.md +75 -0
- automorphotrack-2.4.0/docs/MCP.md +82 -0
- automorphotrack-2.1.0/automorphotrack.egg-info/PKG-INFO → automorphotrack-2.4.0/docs/V2_INTERFACE.md +93 -53
- automorphotrack-2.4.0/docs/VALIDATED_CORE.md +14 -0
- automorphotrack-2.4.0/docs/reviewer_concerns.md +40 -0
- automorphotrack-2.4.0/pyproject.toml +66 -0
- automorphotrack-2.4.0/setup.py +3 -0
- automorphotrack-2.4.0/tests/__init__.py +0 -0
- automorphotrack-2.4.0/tests/test_benchmarking.py +61 -0
- automorphotrack-2.4.0/tests/test_gui_import.py +55 -0
- automorphotrack-2.4.0/tests/test_install_and_imports.py +41 -0
- automorphotrack-2.4.0/tests/test_validated_revision.py +223 -0
- automorphotrack-2.4.0/tests/test_validation_and_segmentation.py +74 -0
- automorphotrack-2.4.0/validation/api_reconciliation.json +28 -0
- automorphotrack-2.4.0/validation/check_installed_core.py +52 -0
- automorphotrack-2.4.0/validation/core_manifest.json +28 -0
- automorphotrack-2.4.0/validation/known_motion.ome.tif +0 -0
- automorphotrack-2.4.0/validation/known_motion_config.json +40 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Colocalization.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Frame_Correlation_N.csv +20 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Frame_Descriptive_Spearman.csv +20 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Frame_Summary.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_InstanceLabels.tif +0 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_Masks.tif +0 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_MotilitySteps.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_MotilitySummary.csv +2 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_ShapeMetrics.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Lyso_Tracks.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_InstanceLabels.tif +0 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_Masks.tif +0 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_MotilitySteps.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_MotilitySummary.csv +2 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_ShapeMetrics.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Mito_Tracks.csv +5 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Motility_PerFrame.csv +9 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Sample_Summary.csv +2 -0
- automorphotrack-2.4.0/validation/known_motion_expected/Thresholds.csv +9 -0
- automorphotrack-2.4.0/validation/known_motion_truth.json +61 -0
- automorphotrack-2.1.0/MANIFEST.in +0 -4
- automorphotrack-2.1.0/PKG-INFO +0 -258
- automorphotrack-2.1.0/README.md +0 -222
- automorphotrack-2.1.0/automorphotrack/utils.py +0 -37
- automorphotrack-2.1.0/automorphotrack.egg-info/SOURCES.txt +0 -26
- automorphotrack-2.1.0/automorphotrack.egg-info/requires.txt +0 -8
- automorphotrack-2.1.0/pyproject.toml +0 -40
- automorphotrack-2.1.0/setup.py +0 -46
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/LICENSE.md +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/detection.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/lyso_count.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/morphology.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/network_analysis.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/shape_features.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/spatial_statistics.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/summary.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/temporal_dynamics.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/tracking.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/tracking_overlay.py +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack.egg-info/dependency_links.txt +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack.egg-info/top_level.txt +0 -0
- {automorphotrack-2.1.0 → automorphotrack-2.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE.md
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
recursive-include automorphotrack *.py
|
|
5
|
+
|
|
6
|
+
recursive-include validation *.json *.py *.tif *.csv
|
|
7
|
+
recursive-include docs *.md
|
|
8
|
+
recursive-include tests *.py
|
|
9
|
+
prune integration
|
|
10
|
+
prune release_artifacts
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: automorphotrack
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: Automated pipeline for mitochondrial and lysosomal detection, tracking, morphology, and colocalization analysis in microscopy images.
|
|
5
|
+
Author-email: "Armin Bayati, Ph.D." <a.bayati.brain@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://github.com/abayatibrain/AutoMorphoTrack
|
|
8
|
+
Project-URL: Source, https://github.com/abayatibrain/AutoMorphoTrack
|
|
9
|
+
Project-URL: Tracker, https://github.com/abayatibrain/AutoMorphoTrack/issues
|
|
10
|
+
Keywords: image-processing,microscopy,organelle-tracking,mitochondria,lysosomes,colocalization,live-cell-imaging
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE.md
|
|
22
|
+
Requires-Dist: numpy>=2.0
|
|
23
|
+
Requires-Dist: pandas>=2.2
|
|
24
|
+
Requires-Dist: matplotlib>=3.6.0
|
|
25
|
+
Requires-Dist: seaborn>=0.12.0
|
|
26
|
+
Requires-Dist: opencv-python>=4.6.0
|
|
27
|
+
Requires-Dist: scikit-image>=0.25
|
|
28
|
+
Requires-Dist: scipy>=1.13
|
|
29
|
+
Requires-Dist: tifffile>=2024.8
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
32
|
+
Provides-Extra: compressed-tiff
|
|
33
|
+
Requires-Dist: imagecodecs>=2024.1; extra == "compressed-tiff"
|
|
34
|
+
Provides-Extra: napari
|
|
35
|
+
Requires-Dist: napari>=0.4.18; extra == "napari"
|
|
36
|
+
Requires-Dist: magicgui>=0.7.0; extra == "napari"
|
|
37
|
+
Requires-Dist: qtpy>=2.3.0; extra == "napari"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
41
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
42
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# AutoMorphoTrack
|
|
46
|
+
|
|
47
|
+
AutoMorphoTrack 2.4.0 includes the frozen quantitative core described in the revised eLife manuscript. It can be launched through the automorphotrack.validated namespace or via the amt-validated command. The current 2.x APIs, GUI, and MCP interface are also available and are not covered in this manuscript.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
python -m pip install "automorphotrack==2.4.0"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Python >=3.11
|
|
56
|
+
|
|
57
|
+
## Manuscript analysis
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from automorphotrack.validated import PipelineConfig, run_pipeline
|
|
61
|
+
config = PipelineConfig(axes="TCYX", mito_channel=0, lyso_channel=1)
|
|
62
|
+
result = run_pipeline("recording.tif", "results", config)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
amt-validated recording.tif --out results --config configuration.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Provide true axes and channel mappings. Physical calibration is not required; uncalibrated motion is in pixels per frame. The pipeline retains masks, objects, accepted links, tables, configuration, input identity, and source hashes. Adapters should invoke the validated namespace and leave the source unaltered. An unaltered core does not guarantee correctness on a new image domain.
|
|
70
|
+
|
|
71
|
+
## Validation scope
|
|
72
|
+
|
|
73
|
+
The revision assesses segmentation on a set of 7 public microscopy studies, of which 6 are confocal. It describes the study-specific limitations and provides failed cases. Representative example image crops are regions from a single recording. The benchmark does not prove membrane contact, or functional interaction, nor does it prove identity tracking in biological data in general.
|
|
74
|
+
|
|
75
|
+
The public release candidate passed 51 software tests on the local macOS arm64 host. An installed-wheel check verified 18 modules of the core and compared 19 CSV and TIFF files to the known-motion fixture. Both channels contained a tracked object with an expected mean speed of 1 pixel per frame. The separate command-line route was also successfully completed. Numerical and execution consistency for the known-motion fixture were evaluated; these tests do not prove correctness for new biological images.
|
|
76
|
+
|
|
77
|
+
## Release identities
|
|
78
|
+
|
|
79
|
+
| Item | Identifier |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| Public distribution | 2.4.0 |
|
|
82
|
+
| Manuscript namespace | automorphotrack.validated |
|
|
83
|
+
| Frozen core version | 1.1.0rc2 |
|
|
84
|
+
| Core digest | 8a4125a5ebe2b49e019f79b783503f31403a24ea3134d2d10b1bd4610ac3bce8 |
|
|
85
|
+
| Source manifest | validation/core_manifest.json |
|
|
86
|
+
| Earlier interfaces | docs/V2_INTERFACE.md |
|
|
87
|
+
| Release records | docs/VALIDATED_CORE.md |
|
|
88
|
+
|
|
89
|
+
## License and citation
|
|
90
|
+
|
|
91
|
+
MIT license
|
|
92
|
+
|
|
93
|
+
Bayati A, Schumacher JG, Chen X. AutoMorphoTrack. eLife reviewed preprint 109936. https://doi.org/10.7554/eLife.109936.1
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# AutoMorphoTrack
|
|
2
|
+
|
|
3
|
+
AutoMorphoTrack 2.4.0 includes the frozen quantitative core described in the revised eLife manuscript. It can be launched through the automorphotrack.validated namespace or via the amt-validated command. The current 2.x APIs, GUI, and MCP interface are also available and are not covered in this manuscript.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
python -m pip install "automorphotrack==2.4.0"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Python >=3.11
|
|
12
|
+
|
|
13
|
+
## Manuscript analysis
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from automorphotrack.validated import PipelineConfig, run_pipeline
|
|
17
|
+
config = PipelineConfig(axes="TCYX", mito_channel=0, lyso_channel=1)
|
|
18
|
+
result = run_pipeline("recording.tif", "results", config)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
amt-validated recording.tif --out results --config configuration.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Provide true axes and channel mappings. Physical calibration is not required; uncalibrated motion is in pixels per frame. The pipeline retains masks, objects, accepted links, tables, configuration, input identity, and source hashes. Adapters should invoke the validated namespace and leave the source unaltered. An unaltered core does not guarantee correctness on a new image domain.
|
|
26
|
+
|
|
27
|
+
## Validation scope
|
|
28
|
+
|
|
29
|
+
The revision assesses segmentation on a set of 7 public microscopy studies, of which 6 are confocal. It describes the study-specific limitations and provides failed cases. Representative example image crops are regions from a single recording. The benchmark does not prove membrane contact, or functional interaction, nor does it prove identity tracking in biological data in general.
|
|
30
|
+
|
|
31
|
+
The public release candidate passed 51 software tests on the local macOS arm64 host. An installed-wheel check verified 18 modules of the core and compared 19 CSV and TIFF files to the known-motion fixture. Both channels contained a tracked object with an expected mean speed of 1 pixel per frame. The separate command-line route was also successfully completed. Numerical and execution consistency for the known-motion fixture were evaluated; these tests do not prove correctness for new biological images.
|
|
32
|
+
|
|
33
|
+
## Release identities
|
|
34
|
+
|
|
35
|
+
| Item | Identifier |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Public distribution | 2.4.0 |
|
|
38
|
+
| Manuscript namespace | automorphotrack.validated |
|
|
39
|
+
| Frozen core version | 1.1.0rc2 |
|
|
40
|
+
| Core digest | 8a4125a5ebe2b49e019f79b783503f31403a24ea3134d2d10b1bd4610ac3bce8 |
|
|
41
|
+
| Source manifest | validation/core_manifest.json |
|
|
42
|
+
| Earlier interfaces | docs/V2_INTERFACE.md |
|
|
43
|
+
| Release records | docs/VALIDATED_CORE.md |
|
|
44
|
+
|
|
45
|
+
## License and citation
|
|
46
|
+
|
|
47
|
+
MIT license
|
|
48
|
+
|
|
49
|
+
Bayati A, Schumacher JG, Chen X. AutoMorphoTrack. eLife reviewed preprint 109936. https://doi.org/10.7554/eLife.109936.1
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
# )
|
|
34
34
|
# ============================================================
|
|
35
35
|
|
|
36
|
-
__version__ = "2.
|
|
36
|
+
__version__ = "2.4.0"
|
|
37
37
|
__author__ = "Armin Bayati"
|
|
38
38
|
|
|
39
39
|
# --- Utility Imports ---
|
|
@@ -64,30 +64,45 @@ from automorphotrack.validation import (
|
|
|
64
64
|
validate_segmentation,
|
|
65
65
|
sensitivity_analysis,
|
|
66
66
|
validate_tracking,
|
|
67
|
-
generate_validation_report
|
|
67
|
+
generate_validation_report,
|
|
68
|
+
generate_synthetic_ground_truth,
|
|
68
69
|
)
|
|
69
70
|
|
|
71
|
+
# --- Adaptive segmentation backends (R2.4) ---
|
|
72
|
+
from automorphotrack.adaptive_segmentation import (
|
|
73
|
+
segment,
|
|
74
|
+
segment_otsu,
|
|
75
|
+
segment_sauvola,
|
|
76
|
+
segment_niblack,
|
|
77
|
+
segment_local_otsu,
|
|
78
|
+
segment_subtracted,
|
|
79
|
+
BACKENDS as SEGMENTATION_BACKENDS,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# --- Benchmarking against other tools (R1.2 / R2.3) ---
|
|
83
|
+
from automorphotrack import benchmarking
|
|
84
|
+
|
|
85
|
+
# --- Stats annotation helper (R2.5.iv) ---
|
|
86
|
+
from automorphotrack.utils import annotate_stats
|
|
87
|
+
|
|
70
88
|
# --- Convenience Alias (optional shortcut API) ---
|
|
71
89
|
__all__ = [
|
|
72
|
-
|
|
73
|
-
"save_high_dpi",
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
"detect_organelles",
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"track_organelles",
|
|
82
|
-
"track_overlay",
|
|
83
|
-
"analyze_motility",
|
|
84
|
-
"analyze_colocalization",
|
|
85
|
-
"summarize_integrated_data",
|
|
86
|
-
"analyze_temporal_dynamics",
|
|
87
|
-
"analyze_spatial_statistics",
|
|
90
|
+
# utilities
|
|
91
|
+
"ensure_dir", "save_high_dpi", "upscale_frame", "write_video",
|
|
92
|
+
"annotate_stats",
|
|
93
|
+
# pipeline
|
|
94
|
+
"detect_organelles", "count_lysosomes_per_frame",
|
|
95
|
+
"classify_morphology", "analyze_shape_features", "profile_shape_data",
|
|
96
|
+
"track_organelles", "track_overlay", "analyze_motility",
|
|
97
|
+
"analyze_colocalization", "summarize_integrated_data",
|
|
98
|
+
"analyze_temporal_dynamics", "analyze_spatial_statistics",
|
|
88
99
|
"analyze_network_topology",
|
|
89
|
-
|
|
90
|
-
"sensitivity_analysis",
|
|
91
|
-
"
|
|
92
|
-
|
|
100
|
+
# validation
|
|
101
|
+
"validate_segmentation", "sensitivity_analysis", "validate_tracking",
|
|
102
|
+
"generate_validation_report", "generate_synthetic_ground_truth",
|
|
103
|
+
# adaptive segmentation backends
|
|
104
|
+
"segment", "segment_otsu", "segment_sauvola", "segment_niblack",
|
|
105
|
+
"segment_local_otsu", "segment_subtracted", "SEGMENTATION_BACKENDS",
|
|
106
|
+
# benchmarking
|
|
107
|
+
"benchmarking",
|
|
93
108
|
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# AutoMorphoTrack – Adaptive Segmentation Backends
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Author: Armin Bayati, Ph.D.
|
|
5
|
+
#
|
|
6
|
+
# Reviewer #2 (R2.4) noted that pure Otsu thresholding plus morphological
|
|
7
|
+
# opening is sensitive to SNR, background heterogeneity, bleaching, and
|
|
8
|
+
# organelle density. This module provides drop-in alternative backends so
|
|
9
|
+
# users can swap the segmentation step without modifying the rest of the
|
|
10
|
+
# pipeline:
|
|
11
|
+
#
|
|
12
|
+
# - segment_otsu : the default global-threshold path
|
|
13
|
+
# - segment_sauvola : Sauvola adaptive thresholding for uneven background
|
|
14
|
+
# - segment_niblack : Niblack adaptive thresholding
|
|
15
|
+
# - segment_local_otsu : Otsu inside a sliding window for very large fields
|
|
16
|
+
# - segment_subtracted : rolling-ball / top-hat background subtraction
|
|
17
|
+
# followed by Otsu (handles bleaching/heterogeneity)
|
|
18
|
+
#
|
|
19
|
+
# All backends expose the same signature::
|
|
20
|
+
#
|
|
21
|
+
# mask = segment_<name>(image, **kwargs)
|
|
22
|
+
#
|
|
23
|
+
# where ``image`` is a 2D float array in [0, 1] and ``mask`` is a 2D bool
|
|
24
|
+
# array. This lets the validation.sensitivity_analysis sweep run against any
|
|
25
|
+
# of them.
|
|
26
|
+
# ============================================================
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import numpy as np
|
|
31
|
+
from skimage.filters import (
|
|
32
|
+
threshold_otsu, threshold_sauvola, threshold_niblack, threshold_local
|
|
33
|
+
)
|
|
34
|
+
from skimage.morphology import (
|
|
35
|
+
remove_small_objects, binary_opening, disk, white_tophat
|
|
36
|
+
)
|
|
37
|
+
from skimage.segmentation import clear_border
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _clean(mask: np.ndarray, min_size: int = 3,
|
|
41
|
+
opening_radius: int = 1,
|
|
42
|
+
clear_borders: bool = True) -> np.ndarray:
|
|
43
|
+
if opening_radius > 0:
|
|
44
|
+
mask = binary_opening(mask, footprint=disk(opening_radius))
|
|
45
|
+
if clear_borders:
|
|
46
|
+
mask = clear_border(mask)
|
|
47
|
+
if min_size > 0:
|
|
48
|
+
mask = remove_small_objects(mask, max(1, int(min_size)))
|
|
49
|
+
return mask.astype(bool)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def segment_otsu(image: np.ndarray, thr_factor: float = 0.8,
|
|
53
|
+
min_size: int = 3, **clean_kwargs) -> np.ndarray:
|
|
54
|
+
"""Global Otsu (the default path), exposed for completeness."""
|
|
55
|
+
thr = threshold_otsu(image) * thr_factor
|
|
56
|
+
return _clean(image > thr, min_size=min_size, **clean_kwargs)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def segment_sauvola(image: np.ndarray, window_size: int = 25,
|
|
60
|
+
k: float = 0.2, min_size: int = 3,
|
|
61
|
+
**clean_kwargs) -> np.ndarray:
|
|
62
|
+
"""Sauvola adaptive thresholding (handles uneven illumination)."""
|
|
63
|
+
if window_size % 2 == 0:
|
|
64
|
+
window_size += 1
|
|
65
|
+
thr_map = threshold_sauvola(image, window_size=window_size, k=k)
|
|
66
|
+
return _clean(image > thr_map, min_size=min_size, **clean_kwargs)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def segment_niblack(image: np.ndarray, window_size: int = 25,
|
|
70
|
+
k: float = 0.2, min_size: int = 3,
|
|
71
|
+
**clean_kwargs) -> np.ndarray:
|
|
72
|
+
"""Niblack adaptive thresholding."""
|
|
73
|
+
if window_size % 2 == 0:
|
|
74
|
+
window_size += 1
|
|
75
|
+
thr_map = threshold_niblack(image, window_size=window_size, k=k)
|
|
76
|
+
return _clean(image > thr_map, min_size=min_size, **clean_kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def segment_local_otsu(image: np.ndarray, block_size: int = 51,
|
|
80
|
+
offset: float = 0.0, min_size: int = 3,
|
|
81
|
+
**clean_kwargs) -> np.ndarray:
|
|
82
|
+
"""Tile-wise local Otsu via skimage.filters.threshold_local."""
|
|
83
|
+
if block_size % 2 == 0:
|
|
84
|
+
block_size += 1
|
|
85
|
+
thr_map = threshold_local(image, block_size=block_size, offset=offset)
|
|
86
|
+
return _clean(image > thr_map, min_size=min_size, **clean_kwargs)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def segment_subtracted(image: np.ndarray, background_radius: int = 25,
|
|
90
|
+
thr_factor: float = 0.8, min_size: int = 3,
|
|
91
|
+
**clean_kwargs) -> np.ndarray:
|
|
92
|
+
"""Top-hat background subtraction + Otsu.
|
|
93
|
+
|
|
94
|
+
Equivalent to ImageJ/FIJI's rolling-ball background subtraction step. Use
|
|
95
|
+
when the field of view has slow intensity gradients from out-of-focus
|
|
96
|
+
haze, photobleaching, or uneven illumination.
|
|
97
|
+
"""
|
|
98
|
+
bg_subtracted = white_tophat(image, footprint=disk(background_radius))
|
|
99
|
+
if bg_subtracted.max() <= 0:
|
|
100
|
+
return np.zeros_like(image, dtype=bool)
|
|
101
|
+
bg_subtracted = bg_subtracted / bg_subtracted.max()
|
|
102
|
+
thr = threshold_otsu(bg_subtracted) * thr_factor
|
|
103
|
+
return _clean(bg_subtracted > thr, min_size=min_size, **clean_kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
BACKENDS = {
|
|
107
|
+
"otsu": segment_otsu,
|
|
108
|
+
"sauvola": segment_sauvola,
|
|
109
|
+
"niblack": segment_niblack,
|
|
110
|
+
"local_otsu": segment_local_otsu,
|
|
111
|
+
"subtracted": segment_subtracted,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def segment(image: np.ndarray, backend: str = "otsu", **kwargs) -> np.ndarray:
|
|
116
|
+
"""Dispatch to a named backend. ``backend`` is one of ``BACKENDS``."""
|
|
117
|
+
if backend not in BACKENDS:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Unknown backend '{backend}'. Choices: {sorted(BACKENDS)}"
|
|
120
|
+
)
|
|
121
|
+
return BACKENDS[backend](image, **kwargs)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# AutoMorphoTrack – Benchmarking & Comparative Positioning
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Author: Armin Bayati, Ph.D.
|
|
5
|
+
#
|
|
6
|
+
# Reviewer #1 (R1.2) and Reviewer #2 (R2.3) asked for explicit comparison to
|
|
7
|
+
# established tools: CellProfiler, MiNA, MitoGraph, Mitochondria Analyzer,
|
|
8
|
+
# and learning-based tools like CellPose / StarDist.
|
|
9
|
+
#
|
|
10
|
+
# AutoMorphoTrack does not re-implement those packages — that would
|
|
11
|
+
# duplicate excellent existing work. Instead, this module:
|
|
12
|
+
#
|
|
13
|
+
# 1. Provides exporters that translate AMT outputs into the CSV layouts
|
|
14
|
+
# those tools consume / produce, so users can directly compare results
|
|
15
|
+
# side-by-side.
|
|
16
|
+
# 2. Provides importers for the other tools' outputs so AMT can compute
|
|
17
|
+
# Dice/IoU/Pearson agreement between its segmentations and theirs.
|
|
18
|
+
# 3. Provides a "comparison table" generator that summarises pairwise
|
|
19
|
+
# agreement across morphology metrics, useful as supplementary material
|
|
20
|
+
# in publications.
|
|
21
|
+
#
|
|
22
|
+
# The supported tools are:
|
|
23
|
+
# - CellProfiler (CSV per object)
|
|
24
|
+
# - MiNA (FIJI plugin; tabular network stats)
|
|
25
|
+
# - MitoGraph (.gnet / .csv per cell)
|
|
26
|
+
# - Mitochondria Analyzer (FIJI; per-image summary)
|
|
27
|
+
# - CellPose / StarDist (label-image masks, np.uint16)
|
|
28
|
+
#
|
|
29
|
+
# Each importer is tolerant: it tries to find the relevant columns by
|
|
30
|
+
# regex-matching common naming conventions so the user does not have to
|
|
31
|
+
# rename anything by hand.
|
|
32
|
+
# ============================================================
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Mapping
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
import pandas as pd
|
|
42
|
+
|
|
43
|
+
from automorphotrack.validation import validate_segmentation
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
# AMT → other tools (exporters)
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
def export_for_cellprofiler(shape_metrics_csv: str | Path,
|
|
50
|
+
out_path: str | Path) -> Path:
|
|
51
|
+
"""Write a CSV in a CellProfiler-friendly schema.
|
|
52
|
+
|
|
53
|
+
CellProfiler MeasureObjectSizeShape output uses columns like
|
|
54
|
+
``AreaShape_Area``, ``AreaShape_Eccentricity``, etc. We map AMT columns
|
|
55
|
+
to that schema so users can drop the CSV into a CellProfiler analysis
|
|
56
|
+
workflow without rewriting downstream scripts.
|
|
57
|
+
"""
|
|
58
|
+
df = pd.read_csv(shape_metrics_csv)
|
|
59
|
+
mapping = {
|
|
60
|
+
"Area": "AreaShape_Area",
|
|
61
|
+
"Eccentricity": "AreaShape_Eccentricity",
|
|
62
|
+
"Solidity": "AreaShape_Solidity",
|
|
63
|
+
"Circularity": "AreaShape_FormFactor", # CellProfiler's name
|
|
64
|
+
"Aspect_Ratio": "AreaShape_MajorAxisLength_over_MinorAxisLength",
|
|
65
|
+
"Orientation": "AreaShape_Orientation",
|
|
66
|
+
"Frame": "Image_Metadata_Frame",
|
|
67
|
+
}
|
|
68
|
+
df = df.rename(columns={k: v for k, v in mapping.items() if k in df.columns})
|
|
69
|
+
df["ImageNumber"] = df.get("Image_Metadata_Frame", 0) + 1
|
|
70
|
+
df["ObjectNumber"] = range(1, len(df) + 1)
|
|
71
|
+
out_path = Path(out_path)
|
|
72
|
+
df.to_csv(out_path, index=False)
|
|
73
|
+
return out_path
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def export_for_mina(shape_metrics_csv: str | Path,
|
|
77
|
+
out_path: str | Path) -> Path:
|
|
78
|
+
"""Write a CSV in MiNA-style per-mitochondrion network table.
|
|
79
|
+
|
|
80
|
+
MiNA reports ``Mean Length``, ``Mean Area``, ``Footprint``, ``Mean
|
|
81
|
+
Branch Length`` — AMT does not compute branch topology, so the columns
|
|
82
|
+
we cannot fill are left blank (NaN). This keeps the schema compatible
|
|
83
|
+
while making the gap explicit.
|
|
84
|
+
"""
|
|
85
|
+
df = pd.read_csv(shape_metrics_csv)
|
|
86
|
+
out = pd.DataFrame({
|
|
87
|
+
"Frame": df.get("Frame"),
|
|
88
|
+
"Mean Length": np.nan,
|
|
89
|
+
"Mean Area": df.get("Area"),
|
|
90
|
+
"Footprint": df.get("Area"),
|
|
91
|
+
"Mean Branch Length": np.nan,
|
|
92
|
+
"Eccentricity": df.get("Eccentricity"),
|
|
93
|
+
"Solidity": df.get("Solidity"),
|
|
94
|
+
"Form Factor": df.get("Circularity"),
|
|
95
|
+
})
|
|
96
|
+
out_path = Path(out_path)
|
|
97
|
+
out.to_csv(out_path, index=False)
|
|
98
|
+
return out_path
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def export_for_mitograph(shape_metrics_csv: str | Path,
|
|
102
|
+
out_path: str | Path) -> Path:
|
|
103
|
+
"""Write a MitoGraph-style per-cell summary CSV."""
|
|
104
|
+
df = pd.read_csv(shape_metrics_csv)
|
|
105
|
+
grouped = df.groupby("Frame").agg(
|
|
106
|
+
total_area=("Area", "sum"),
|
|
107
|
+
mean_area=("Area", "mean"),
|
|
108
|
+
n_objects=("Area", "size"),
|
|
109
|
+
mean_aspect_ratio=("Aspect_Ratio", "mean"),
|
|
110
|
+
mean_solidity=("Solidity", "mean"),
|
|
111
|
+
).reset_index()
|
|
112
|
+
grouped.columns = [
|
|
113
|
+
"Image", "Total_Volume_um3", "Mean_Volume_um3",
|
|
114
|
+
"N_Mitochondria", "Mean_Aspect_Ratio", "Mean_Solidity",
|
|
115
|
+
]
|
|
116
|
+
out_path = Path(out_path)
|
|
117
|
+
grouped.to_csv(out_path, index=False)
|
|
118
|
+
return out_path
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
# Other tools → AMT (importers + agreement)
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
def load_cellprofiler_csv(path: str | Path) -> pd.DataFrame:
|
|
125
|
+
"""Parse a CellProfiler ``Per_Object.csv`` into AMT's shape-metric schema."""
|
|
126
|
+
df = pd.read_csv(path)
|
|
127
|
+
rename = {}
|
|
128
|
+
for c in df.columns:
|
|
129
|
+
if re.fullmatch(r"AreaShape_Area", c): rename[c] = "Area"
|
|
130
|
+
elif re.fullmatch(r"AreaShape_Eccentricity", c): rename[c] = "Eccentricity"
|
|
131
|
+
elif re.fullmatch(r"AreaShape_Solidity", c): rename[c] = "Solidity"
|
|
132
|
+
elif re.fullmatch(r"AreaShape_FormFactor", c): rename[c] = "Circularity"
|
|
133
|
+
elif re.fullmatch(r"AreaShape_Orientation", c): rename[c] = "Orientation"
|
|
134
|
+
elif "MajorAxisLength_over_MinorAxisLength" in c: rename[c] = "Aspect_Ratio"
|
|
135
|
+
elif "Frame" in c: rename[c] = "Frame"
|
|
136
|
+
return df.rename(columns=rename)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def load_label_mask(path: str | Path) -> np.ndarray:
|
|
140
|
+
"""Load a CellPose / StarDist label image as a binary mask."""
|
|
141
|
+
import tifffile
|
|
142
|
+
img = tifffile.imread(str(path))
|
|
143
|
+
return (img > 0).astype(bool)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def agreement(amt_mask: np.ndarray, other_mask: np.ndarray) -> Mapping[str, float]:
|
|
147
|
+
"""Compute Dice / IoU / precision / recall / F1 between two binary masks."""
|
|
148
|
+
return validate_segmentation(amt_mask.astype(bool), other_mask.astype(bool))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def comparison_table(amt_shape_csv: str | Path,
|
|
152
|
+
other_shape_csv: str | Path,
|
|
153
|
+
other_tool_name: str = "Other") -> pd.DataFrame:
|
|
154
|
+
"""Build a side-by-side morphology comparison table.
|
|
155
|
+
|
|
156
|
+
Per-metric this reports: mean ± std for AMT, mean ± std for the other
|
|
157
|
+
tool, the Spearman rank correlation between matched objects (where
|
|
158
|
+
matching is by ``Frame`` and row order), and a percentage absolute
|
|
159
|
+
difference of the means.
|
|
160
|
+
"""
|
|
161
|
+
from scipy.stats import spearmanr
|
|
162
|
+
|
|
163
|
+
a = pd.read_csv(amt_shape_csv)
|
|
164
|
+
b = pd.read_csv(other_shape_csv)
|
|
165
|
+
metrics = ["Area", "Eccentricity", "Solidity", "Circularity", "Aspect_Ratio"]
|
|
166
|
+
rows = []
|
|
167
|
+
for m in metrics:
|
|
168
|
+
if m not in a.columns or m not in b.columns:
|
|
169
|
+
continue
|
|
170
|
+
av, bv = a[m].dropna(), b[m].dropna()
|
|
171
|
+
n = min(len(av), len(bv))
|
|
172
|
+
if n < 3:
|
|
173
|
+
continue
|
|
174
|
+
rho, p = spearmanr(av.iloc[:n], bv.iloc[:n])
|
|
175
|
+
mean_a, mean_b = av.mean(), bv.mean()
|
|
176
|
+
pct_diff = 100 * abs(mean_a - mean_b) / max(abs(mean_a), abs(mean_b), 1e-9)
|
|
177
|
+
rows.append({
|
|
178
|
+
"Metric": m,
|
|
179
|
+
"AMT_mean": round(mean_a, 4),
|
|
180
|
+
"AMT_std": round(av.std(), 4),
|
|
181
|
+
f"{other_tool_name}_mean": round(mean_b, 4),
|
|
182
|
+
f"{other_tool_name}_std": round(bv.std(), 4),
|
|
183
|
+
"Spearman_rho": round(float(rho), 4),
|
|
184
|
+
"p_value": round(float(p), 6),
|
|
185
|
+
"Percent_diff_means": round(pct_diff, 2),
|
|
186
|
+
"N": n,
|
|
187
|
+
})
|
|
188
|
+
return pd.DataFrame(rows)
|