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.
Files changed (97) hide show
  1. automorphotrack-2.4.0/MANIFEST.in +10 -0
  2. automorphotrack-2.4.0/PKG-INFO +93 -0
  3. automorphotrack-2.4.0/README.md +49 -0
  4. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/__init__.py +37 -22
  5. automorphotrack-2.4.0/automorphotrack/adaptive_segmentation.py +121 -0
  6. automorphotrack-2.4.0/automorphotrack/benchmarking.py +188 -0
  7. automorphotrack-2.4.0/automorphotrack/cli.py +250 -0
  8. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/colocalization.py +38 -14
  9. automorphotrack-2.4.0/automorphotrack/mcp_server.py +272 -0
  10. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/motility.py +51 -1
  11. automorphotrack-2.4.0/automorphotrack/napari.yaml +35 -0
  12. automorphotrack-2.4.0/automorphotrack/napari_plugin.py +272 -0
  13. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/shape_profiling.py +19 -4
  14. automorphotrack-2.4.0/automorphotrack/utils.py +127 -0
  15. automorphotrack-2.4.0/automorphotrack/validated/__init__.py +20 -0
  16. automorphotrack-2.4.0/automorphotrack/validated/__main__.py +22 -0
  17. automorphotrack-2.4.0/automorphotrack/validated/colocalization.py +11 -0
  18. automorphotrack-2.4.0/automorphotrack/validated/config.py +88 -0
  19. automorphotrack-2.4.0/automorphotrack/validated/core.py +225 -0
  20. automorphotrack-2.4.0/automorphotrack/validated/detection.py +22 -0
  21. automorphotrack-2.4.0/automorphotrack/validated/io.py +66 -0
  22. automorphotrack-2.4.0/automorphotrack/validated/lyso_count.py +11 -0
  23. automorphotrack-2.4.0/automorphotrack/validated/morphology.py +11 -0
  24. automorphotrack-2.4.0/automorphotrack/validated/motility.py +25 -0
  25. automorphotrack-2.4.0/automorphotrack/validated/pipeline.py +195 -0
  26. automorphotrack-2.4.0/automorphotrack/validated/plotting.py +57 -0
  27. automorphotrack-2.4.0/automorphotrack/validated/shape_features.py +22 -0
  28. automorphotrack-2.4.0/automorphotrack/validated/shape_profiling.py +16 -0
  29. automorphotrack-2.4.0/automorphotrack/validated/summary.py +36 -0
  30. automorphotrack-2.4.0/automorphotrack/validated/tracking.py +11 -0
  31. automorphotrack-2.4.0/automorphotrack/validated/tracking_overlay.py +11 -0
  32. automorphotrack-2.4.0/automorphotrack/validated/utils.py +39 -0
  33. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/validation.py +138 -20
  34. automorphotrack-2.4.0/automorphotrack.egg-info/PKG-INFO +93 -0
  35. automorphotrack-2.4.0/automorphotrack.egg-info/SOURCES.txt +87 -0
  36. automorphotrack-2.4.0/automorphotrack.egg-info/entry_points.txt +8 -0
  37. automorphotrack-2.4.0/automorphotrack.egg-info/requires.txt +25 -0
  38. automorphotrack-2.4.0/docs/GUI.md +75 -0
  39. automorphotrack-2.4.0/docs/MCP.md +82 -0
  40. automorphotrack-2.1.0/automorphotrack.egg-info/PKG-INFO → automorphotrack-2.4.0/docs/V2_INTERFACE.md +93 -53
  41. automorphotrack-2.4.0/docs/VALIDATED_CORE.md +14 -0
  42. automorphotrack-2.4.0/docs/reviewer_concerns.md +40 -0
  43. automorphotrack-2.4.0/pyproject.toml +66 -0
  44. automorphotrack-2.4.0/setup.py +3 -0
  45. automorphotrack-2.4.0/tests/__init__.py +0 -0
  46. automorphotrack-2.4.0/tests/test_benchmarking.py +61 -0
  47. automorphotrack-2.4.0/tests/test_gui_import.py +55 -0
  48. automorphotrack-2.4.0/tests/test_install_and_imports.py +41 -0
  49. automorphotrack-2.4.0/tests/test_validated_revision.py +223 -0
  50. automorphotrack-2.4.0/tests/test_validation_and_segmentation.py +74 -0
  51. automorphotrack-2.4.0/validation/api_reconciliation.json +28 -0
  52. automorphotrack-2.4.0/validation/check_installed_core.py +52 -0
  53. automorphotrack-2.4.0/validation/core_manifest.json +28 -0
  54. automorphotrack-2.4.0/validation/known_motion.ome.tif +0 -0
  55. automorphotrack-2.4.0/validation/known_motion_config.json +40 -0
  56. automorphotrack-2.4.0/validation/known_motion_expected/Colocalization.csv +5 -0
  57. automorphotrack-2.4.0/validation/known_motion_expected/Frame_Correlation_N.csv +20 -0
  58. automorphotrack-2.4.0/validation/known_motion_expected/Frame_Descriptive_Spearman.csv +20 -0
  59. automorphotrack-2.4.0/validation/known_motion_expected/Frame_Summary.csv +5 -0
  60. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_InstanceLabels.tif +0 -0
  61. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_Masks.tif +0 -0
  62. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_MotilitySteps.csv +5 -0
  63. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_MotilitySummary.csv +2 -0
  64. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_ShapeMetrics.csv +5 -0
  65. automorphotrack-2.4.0/validation/known_motion_expected/Lyso_Tracks.csv +5 -0
  66. automorphotrack-2.4.0/validation/known_motion_expected/Mito_InstanceLabels.tif +0 -0
  67. automorphotrack-2.4.0/validation/known_motion_expected/Mito_Masks.tif +0 -0
  68. automorphotrack-2.4.0/validation/known_motion_expected/Mito_MotilitySteps.csv +5 -0
  69. automorphotrack-2.4.0/validation/known_motion_expected/Mito_MotilitySummary.csv +2 -0
  70. automorphotrack-2.4.0/validation/known_motion_expected/Mito_ShapeMetrics.csv +5 -0
  71. automorphotrack-2.4.0/validation/known_motion_expected/Mito_Tracks.csv +5 -0
  72. automorphotrack-2.4.0/validation/known_motion_expected/Motility_PerFrame.csv +9 -0
  73. automorphotrack-2.4.0/validation/known_motion_expected/Sample_Summary.csv +2 -0
  74. automorphotrack-2.4.0/validation/known_motion_expected/Thresholds.csv +9 -0
  75. automorphotrack-2.4.0/validation/known_motion_truth.json +61 -0
  76. automorphotrack-2.1.0/MANIFEST.in +0 -4
  77. automorphotrack-2.1.0/PKG-INFO +0 -258
  78. automorphotrack-2.1.0/README.md +0 -222
  79. automorphotrack-2.1.0/automorphotrack/utils.py +0 -37
  80. automorphotrack-2.1.0/automorphotrack.egg-info/SOURCES.txt +0 -26
  81. automorphotrack-2.1.0/automorphotrack.egg-info/requires.txt +0 -8
  82. automorphotrack-2.1.0/pyproject.toml +0 -40
  83. automorphotrack-2.1.0/setup.py +0 -46
  84. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/LICENSE.md +0 -0
  85. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/detection.py +0 -0
  86. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/lyso_count.py +0 -0
  87. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/morphology.py +0 -0
  88. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/network_analysis.py +0 -0
  89. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/shape_features.py +0 -0
  90. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/spatial_statistics.py +0 -0
  91. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/summary.py +0 -0
  92. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/temporal_dynamics.py +0 -0
  93. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/tracking.py +0 -0
  94. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack/tracking_overlay.py +0 -0
  95. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack.egg-info/dependency_links.txt +0 -0
  96. {automorphotrack-2.1.0 → automorphotrack-2.4.0}/automorphotrack.egg-info/top_level.txt +0 -0
  97. {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.1.0"
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
- "ensure_dir",
73
- "save_high_dpi",
74
- "upscale_frame",
75
- "write_video",
76
- "detect_organelles",
77
- "count_lysosomes_per_frame",
78
- "classify_morphology",
79
- "analyze_shape_features",
80
- "profile_shape_data",
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
- "validate_segmentation",
90
- "sensitivity_analysis",
91
- "validate_tracking",
92
- "generate_validation_report"
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)