cellfinder 1.1.0__tar.gz → 1.1.2__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.
Potentially problematic release.
This version of cellfinder might be problematic. Click here for more details.
- {cellfinder-1.1.0 → cellfinder-1.1.2}/.github/workflows/test_and_deploy.yml +3 -7
- {cellfinder-1.1.0 → cellfinder-1.1.2}/.gitignore +1 -0
- cellfinder-1.1.2/CITATION.cff +60 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/MANIFEST.in +1 -2
- {cellfinder-1.1.0 → cellfinder-1.1.2}/PKG-INFO +6 -6
- {cellfinder-1.1.0 → cellfinder-1.1.2}/README.md +1 -1
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/cube_generator.py +2 -6
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/detect.py +73 -4
- cellfinder-1.1.2/cellfinder/core/detect/filters/plane/classical_filter.py +45 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/ball_filter.py +5 -5
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/structure_splitting.py +54 -6
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/volume_filter.py +5 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/cli.py +2 -2
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/download.py +39 -10
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/main.py +1 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/prep.py +6 -5
- cellfinder-1.1.2/cellfinder/core/tools/source_files.py +27 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/train/train_yml.py +4 -1
- cellfinder-1.1.2/cellfinder/napari/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/curation.py +66 -22
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/train.py +6 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/utils.py +1 -89
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/PKG-INFO +6 -6
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/SOURCES.txt +1 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/requires.txt +2 -2
- {cellfinder-1.1.0 → cellfinder-1.1.2}/pyproject.toml +4 -5
- cellfinder-1.1.0/cellfinder/core/detect/filters/plane/classical_filter.py +0 -20
- cellfinder-1.1.0/cellfinder/core/tools/source_files.py +0 -9
- cellfinder-1.1.0/cellfinder/napari/__init__.py +0 -3
- {cellfinder-1.1.0 → cellfinder-1.1.2}/.github/workflows/test_include_guard.yaml +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/.napari/config.yml +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/LICENSE +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/cli_migration_warning.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/augment.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/classify.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/resnet.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/tools.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/config/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/config/cellfinder.conf +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/plane_filter.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/tile_walker.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/setup_filters.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/structure_detection.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/models.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/IO.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/array_operations.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/geometry.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/image_processing.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/system.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tf.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tiff.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tools.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/train/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/types.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/detect.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/detect_containers.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/thread_worker.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/images/brainglobe.png +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/input_container.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/napari.yaml +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/sample_data.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/__init__.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/train_containers.py +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/dependency_links.txt +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/entry_points.txt +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/top_level.txt +0 -0
- {cellfinder-1.1.0 → cellfinder-1.1.2}/setup.cfg +0 -0
|
@@ -35,6 +35,7 @@ jobs:
|
|
|
35
35
|
test:
|
|
36
36
|
needs: [linting, manifest]
|
|
37
37
|
name: Run package tests
|
|
38
|
+
timeout-minutes: 60
|
|
38
39
|
runs-on: ${{ matrix.os }}
|
|
39
40
|
strategy:
|
|
40
41
|
matrix:
|
|
@@ -139,11 +140,6 @@ jobs:
|
|
|
139
140
|
if: github.event_name == 'push' && github.ref_type == 'tag'
|
|
140
141
|
runs-on: ubuntu-latest
|
|
141
142
|
steps:
|
|
142
|
-
- uses: actions/
|
|
143
|
+
- uses: neuroinformatics-unit/actions/upload_pypi@v2
|
|
143
144
|
with:
|
|
144
|
-
|
|
145
|
-
path: dist
|
|
146
|
-
- uses: pypa/gh-action-pypi-publish@v1.5.0
|
|
147
|
-
with:
|
|
148
|
-
user: __token__
|
|
149
|
-
password: ${{ secrets.TWINE_API_KEY }}
|
|
145
|
+
secret-pypi-key: ${{ secrets.TWINE_API_KEY }}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# This CITATION.cff file was generated with cffinit.
|
|
2
|
+
# Visit https://bit.ly/cffinit to generate yours today!
|
|
3
|
+
|
|
4
|
+
cff-version: 1.2.0
|
|
5
|
+
title: cellfinder
|
|
6
|
+
message: >-
|
|
7
|
+
If you use this software, please cite it using the
|
|
8
|
+
metadata from this file.
|
|
9
|
+
type: software
|
|
10
|
+
authors:
|
|
11
|
+
- given-names: Adam
|
|
12
|
+
family-names: Tyson
|
|
13
|
+
email: hello@brainglobe.info
|
|
14
|
+
affiliation: 'Sainsbury Wellcome Centre, University College London'
|
|
15
|
+
- given-names: Christian
|
|
16
|
+
family-names: Niedworok
|
|
17
|
+
- given-names: Charly
|
|
18
|
+
family-names: Rousseau
|
|
19
|
+
- given-names: BrainGlobe
|
|
20
|
+
family-names: Developers
|
|
21
|
+
email: hello@brainglobe.info
|
|
22
|
+
repository-code: 'https://github.com/brainglobe/cellfinder'
|
|
23
|
+
url: 'https://brainglobe.info'
|
|
24
|
+
abstract: >-
|
|
25
|
+
Automated 3D cell detection in very large 3D images (e.g.,
|
|
26
|
+
serial two-photon or lightsheet volumes of whole mouse
|
|
27
|
+
brains).
|
|
28
|
+
license: BSD-3-Clause
|
|
29
|
+
date-released: '2024-01-05'
|
|
30
|
+
year: 2024
|
|
31
|
+
preferred-citation:
|
|
32
|
+
type: article
|
|
33
|
+
authors:
|
|
34
|
+
- given-names: Adam
|
|
35
|
+
family-names: Tyson
|
|
36
|
+
affiliation: 'Sainsbury Wellcome Centre, University College London'
|
|
37
|
+
- given-names: Charly V.
|
|
38
|
+
family-names: Rousseau
|
|
39
|
+
- given-names: Christian J.
|
|
40
|
+
family-names: Niedworok
|
|
41
|
+
- given-names: Sepiedeh
|
|
42
|
+
family-names: Keshavarzi
|
|
43
|
+
- given-names: Chryssanthi
|
|
44
|
+
family-names: Tsitoura
|
|
45
|
+
- given-names: Lee
|
|
46
|
+
family-names: Cossell
|
|
47
|
+
- given-names: Molly
|
|
48
|
+
family-names: Strom
|
|
49
|
+
- given-names: Troy W.
|
|
50
|
+
family-names: Margrie
|
|
51
|
+
doi: "10.1371/journal.pcbi.1009074"
|
|
52
|
+
url: "https://doi.org/10.1371/journal.pcbi.1009074"
|
|
53
|
+
journal: "PLOS Computational Biology"
|
|
54
|
+
month: 5
|
|
55
|
+
year: 2021
|
|
56
|
+
title: "A deep learning algorithm for 3D cell detection in whole mouse brain image datasets"
|
|
57
|
+
issue: 5
|
|
58
|
+
volume: 17
|
|
59
|
+
pages: 1-17
|
|
60
|
+
citation-sentence: "This work makes use of the cellfinder detection algorithm,"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cellfinder
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Automated 3D cell detection in large microscopy images
|
|
5
5
|
Author-email: "Adam Tyson, Christian Niedworok, Charly Rousseau" <code@adamltyson.com>
|
|
6
6
|
License: BSD-3-Clause
|
|
7
7
|
Project-URL: Homepage, https://brainglobe.info/documentation/cellfinder/index.html
|
|
8
|
-
Project-URL: Source Code, https://github.com/brainglobe/cellfinder
|
|
9
|
-
Project-URL: Bug Tracker, https://github.com/brainglobe/cellfinder
|
|
8
|
+
Project-URL: Source Code, https://github.com/brainglobe/cellfinder
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/brainglobe/cellfinder/issues
|
|
10
10
|
Project-URL: Documentation, https://brainglobe.info/documentation/cellfinder/index.html
|
|
11
11
|
Project-URL: User Support, https://forum.image.sc/tag/brainglobe
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -22,7 +22,8 @@ Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
|
22
22
|
Requires-Python: >=3.9
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: brainglobe-utils
|
|
25
|
+
Requires-Dist: brainglobe-utils>=0.4.2
|
|
26
|
+
Requires-Dist: brainglobe-napari-io>=0.3.4
|
|
26
27
|
Requires-Dist: dask[array]
|
|
27
28
|
Requires-Dist: fancylog>=0.0.7
|
|
28
29
|
Requires-Dist: natsort
|
|
@@ -39,7 +40,6 @@ Requires-Dist: black; extra == "dev"
|
|
|
39
40
|
Requires-Dist: pre-commit; extra == "dev"
|
|
40
41
|
Requires-Dist: pyinstrument; extra == "dev"
|
|
41
42
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
42
|
-
Requires-Dist: pytest-lazy-fixture; extra == "dev"
|
|
43
43
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
44
44
|
Requires-Dist: pytest-qt; extra == "dev"
|
|
45
45
|
Requires-Dist: pytest-timeout; extra == "dev"
|
|
@@ -59,7 +59,7 @@ Requires-Dist: qtpy; extra == "napari"
|
|
|
59
59
|
[](https://pepy.tech/project/cellfinder)
|
|
60
60
|
[](https://pypi.org/project/cellfinder)
|
|
61
61
|
[](https://github.com/brainglobe/cellfinder)
|
|
62
|
-
[](https://github.com/brainglobe/cellfinder/actions)
|
|
63
63
|
[](https://codecov.io/gh/brainglobe/cellfinder)
|
|
64
64
|
[](https://github.com/python/black)
|
|
65
65
|
[](https://pycqa.github.io/isort/)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://pepy.tech/project/cellfinder)
|
|
4
4
|
[](https://pypi.org/project/cellfinder)
|
|
5
5
|
[](https://github.com/brainglobe/cellfinder)
|
|
6
|
-
[](https://github.com/brainglobe/cellfinder/actions)
|
|
7
7
|
[](https://codecov.io/gh/brainglobe/cellfinder)
|
|
8
8
|
[](https://github.com/python/black)
|
|
9
9
|
[](https://pycqa.github.io/isort/)
|
|
@@ -197,9 +197,7 @@ class CubeGeneratorFromFile(Sequence):
|
|
|
197
197
|
"""
|
|
198
198
|
return len(self.batches)
|
|
199
199
|
|
|
200
|
-
def __getitem__(
|
|
201
|
-
self, index: int
|
|
202
|
-
) -> Union[
|
|
200
|
+
def __getitem__(self, index: int) -> Union[
|
|
203
201
|
np.ndarray,
|
|
204
202
|
Tuple[np.ndarray, List[Dict[str, float]]],
|
|
205
203
|
Tuple[np.ndarray, Dict],
|
|
@@ -389,9 +387,7 @@ class CubeGeneratorFromDisk(Sequence):
|
|
|
389
387
|
"""
|
|
390
388
|
return int(np.ceil(len(self.signal_list) / self.batch_size))
|
|
391
389
|
|
|
392
|
-
def __getitem__(
|
|
393
|
-
self, index: int
|
|
394
|
-
) -> Union[
|
|
390
|
+
def __getitem__(self, index: int) -> Union[
|
|
395
391
|
np.ndarray,
|
|
396
392
|
Tuple[np.ndarray, List[Dict[str, float]]],
|
|
397
393
|
Tuple[np.ndarray, Dict],
|
|
@@ -51,6 +51,16 @@ def calculate_parameters_in_pixels(
|
|
|
51
51
|
ball_xy_size = int(round(ball_xy_size_um / mean_in_plane_pixel_size))
|
|
52
52
|
ball_z_size = int(round(ball_z_size_um / float(voxel_sizes[0])))
|
|
53
53
|
|
|
54
|
+
if ball_z_size == 0:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"Ball z size has been calculated to be 0 voxels."
|
|
57
|
+
" This may be due to large axial spacing of your data or the "
|
|
58
|
+
"ball_z_size_um parameter being too small. "
|
|
59
|
+
"Please check input parameters are correct. "
|
|
60
|
+
"Note that cellfinder requires high resolution data in all "
|
|
61
|
+
"dimensions, so that cells can be detected in multiple "
|
|
62
|
+
"image planes."
|
|
63
|
+
)
|
|
54
64
|
return soma_diameter, max_cluster_size, ball_xy_size, ball_z_size
|
|
55
65
|
|
|
56
66
|
|
|
@@ -76,11 +86,69 @@ def main(
|
|
|
76
86
|
callback: Optional[Callable[[int], None]] = None,
|
|
77
87
|
) -> List[Cell]:
|
|
78
88
|
"""
|
|
89
|
+
Perform cell candidate detection on a 3D signal array.
|
|
90
|
+
|
|
79
91
|
Parameters
|
|
80
92
|
----------
|
|
93
|
+
signal_array : numpy.ndarray
|
|
94
|
+
3D array representing the signal data.
|
|
95
|
+
|
|
96
|
+
start_plane : int
|
|
97
|
+
Index of the starting plane for detection.
|
|
98
|
+
|
|
99
|
+
end_plane : int
|
|
100
|
+
Index of the ending plane for detection.
|
|
101
|
+
|
|
102
|
+
voxel_sizes : Tuple[float, float, float]
|
|
103
|
+
Tuple of voxel sizes in each dimension (x, y, z).
|
|
104
|
+
|
|
105
|
+
soma_diameter : float
|
|
106
|
+
Diameter of the soma in physical units.
|
|
107
|
+
|
|
108
|
+
max_cluster_size : float
|
|
109
|
+
Maximum size of a cluster in physical units.
|
|
110
|
+
|
|
111
|
+
ball_xy_size : float
|
|
112
|
+
Size of the XY ball used for filtering in physical units.
|
|
113
|
+
|
|
114
|
+
ball_z_size : float
|
|
115
|
+
Size of the Z ball used for filtering in physical units.
|
|
116
|
+
|
|
117
|
+
ball_overlap_fraction : float
|
|
118
|
+
Fraction of overlap allowed between balls.
|
|
119
|
+
|
|
120
|
+
soma_spread_factor : float
|
|
121
|
+
Spread factor for soma size.
|
|
122
|
+
|
|
123
|
+
n_free_cpus : int
|
|
124
|
+
Number of free CPU cores available for parallel processing.
|
|
125
|
+
|
|
126
|
+
log_sigma_size : float
|
|
127
|
+
Size of the sigma for the log filter.
|
|
128
|
+
|
|
129
|
+
n_sds_above_mean_thresh : float
|
|
130
|
+
Number of standard deviations above the mean threshold.
|
|
131
|
+
|
|
132
|
+
outlier_keep : bool, optional
|
|
133
|
+
Whether to keep outliers during detection. Defaults to False.
|
|
134
|
+
|
|
135
|
+
artifact_keep : bool, optional
|
|
136
|
+
Whether to keep artifacts during detection. Defaults to False.
|
|
137
|
+
|
|
138
|
+
save_planes : bool, optional
|
|
139
|
+
Whether to save the planes during detection. Defaults to False.
|
|
140
|
+
|
|
141
|
+
plane_directory : str, optional
|
|
142
|
+
Directory path to save the planes. Defaults to None.
|
|
143
|
+
|
|
81
144
|
callback : Callable[int], optional
|
|
82
145
|
A callback function that is called every time a plane has finished
|
|
83
146
|
being processed. Called with the plane number that has finished.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
List[Cell]
|
|
151
|
+
List of detected cells.
|
|
84
152
|
"""
|
|
85
153
|
if not np.issubdtype(signal_array.dtype, np.integer):
|
|
86
154
|
raise ValueError(
|
|
@@ -107,6 +175,7 @@ def main(
|
|
|
107
175
|
if end_plane == -1:
|
|
108
176
|
end_plane = len(signal_array)
|
|
109
177
|
signal_array = signal_array[start_plane:end_plane]
|
|
178
|
+
signal_array = signal_array.astype(np.uint32)
|
|
110
179
|
|
|
111
180
|
callback = callback or (lambda *args, **kwargs: None)
|
|
112
181
|
|
|
@@ -169,11 +238,11 @@ def main(
|
|
|
169
238
|
# processes.
|
|
170
239
|
cells = mp_3d_filter.process(async_results, locks, callback=callback)
|
|
171
240
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
241
|
+
time_elapsed = datetime.now() - start_time
|
|
242
|
+
logger.debug(
|
|
243
|
+
f"All Planes done. Found {len(cells)} cells in {format(time_elapsed)}"
|
|
176
244
|
)
|
|
245
|
+
print("Detection complete - all planes done in : {}".format(time_elapsed))
|
|
177
246
|
return cells
|
|
178
247
|
|
|
179
248
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.ndimage import gaussian_filter, laplace
|
|
3
|
+
from scipy.signal import medfilt2d
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def enhance_peaks(
|
|
7
|
+
img: np.ndarray, clipping_value: float, gaussian_sigma: float = 2.5
|
|
8
|
+
) -> np.ndarray:
|
|
9
|
+
"""
|
|
10
|
+
Enhances the peaks (bright pixels) in an input image.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
----------
|
|
14
|
+
img : np.ndarray
|
|
15
|
+
Input image.
|
|
16
|
+
clipping_value : float
|
|
17
|
+
Maximum value for the enhanced image.
|
|
18
|
+
gaussian_sigma : float, optional
|
|
19
|
+
Standard deviation for the Gaussian filter. Default is 2.5.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
-------
|
|
23
|
+
np.ndarray
|
|
24
|
+
Enhanced image with peaks.
|
|
25
|
+
|
|
26
|
+
Notes:
|
|
27
|
+
------
|
|
28
|
+
The enhancement process includes the following steps:
|
|
29
|
+
1. Applying a 2D median filter.
|
|
30
|
+
2. Applying a Laplacian of Gaussian filter (LoG).
|
|
31
|
+
3. Multiplying by -1 (bright spots respond negative in a LoG).
|
|
32
|
+
4. Rescaling image values to range from 0 to clipping value.
|
|
33
|
+
"""
|
|
34
|
+
type_in = img.dtype
|
|
35
|
+
filtered_img = medfilt2d(img.astype(np.float64))
|
|
36
|
+
filtered_img = gaussian_filter(filtered_img, gaussian_sigma)
|
|
37
|
+
filtered_img = laplace(filtered_img)
|
|
38
|
+
filtered_img *= -1
|
|
39
|
+
|
|
40
|
+
filtered_img -= filtered_img.min()
|
|
41
|
+
filtered_img /= filtered_img.max()
|
|
42
|
+
|
|
43
|
+
# To leave room to label in the 3d detection.
|
|
44
|
+
filtered_img *= clipping_value
|
|
45
|
+
return filtered_img.astype(type_in)
|
|
@@ -104,7 +104,7 @@ class BallFilter:
|
|
|
104
104
|
|
|
105
105
|
# Stores the current planes that are being filtered
|
|
106
106
|
self.volume = np.empty(
|
|
107
|
-
(plane_width, plane_height, ball_z_size), dtype=np.
|
|
107
|
+
(plane_width, plane_height, ball_z_size), dtype=np.uint32
|
|
108
108
|
)
|
|
109
109
|
# Index of the middle plane in the volume
|
|
110
110
|
self.middle_z_idx = int(np.floor(ball_z_size / 2))
|
|
@@ -165,7 +165,7 @@ class BallFilter:
|
|
|
165
165
|
Get the plane in the middle of self.volume.
|
|
166
166
|
"""
|
|
167
167
|
z = self.middle_z_idx
|
|
168
|
-
return np.array(self.volume[:, :, z], dtype=np.
|
|
168
|
+
return np.array(self.volume[:, :, z], dtype=np.uint32)
|
|
169
169
|
|
|
170
170
|
def walk(self) -> None: # Highly optimised because most time critical
|
|
171
171
|
ball_radius = self.ball_xy_size // 2
|
|
@@ -327,6 +327,6 @@ def _walk(
|
|
|
327
327
|
THRESHOLD_VALUE,
|
|
328
328
|
kernel,
|
|
329
329
|
):
|
|
330
|
-
volume[
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
volume[ball_centre_x, ball_centre_y, middle_z] = (
|
|
331
|
+
SOMA_CENTRE_VALUE
|
|
332
|
+
)
|
{cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/structure_splitting.py
RENAMED
|
@@ -28,7 +28,7 @@ def coords_to_volume(
|
|
|
28
28
|
expanded_shape = [
|
|
29
29
|
dim_size + ball_diameter for dim_size in get_shape(xs, ys, zs)
|
|
30
30
|
]
|
|
31
|
-
volume = np.zeros(expanded_shape, dtype=np.
|
|
31
|
+
volume = np.zeros(expanded_shape, dtype=np.uint32)
|
|
32
32
|
|
|
33
33
|
x_min, y_min, z_min = xs.min(), ys.min(), zs.min()
|
|
34
34
|
|
|
@@ -38,7 +38,7 @@ def coords_to_volume(
|
|
|
38
38
|
|
|
39
39
|
# OPTIMISE: vectorize
|
|
40
40
|
for rel_x, rel_y, rel_z in zip(relative_xs, relative_ys, relative_zs):
|
|
41
|
-
volume[rel_x, rel_y, rel_z] =
|
|
41
|
+
volume[rel_x, rel_y, rel_z] = np.iinfo(volume.dtype).max - 1
|
|
42
42
|
return volume
|
|
43
43
|
|
|
44
44
|
|
|
@@ -49,6 +49,26 @@ def ball_filter_imgs(
|
|
|
49
49
|
ball_xy_size: int = 3,
|
|
50
50
|
ball_z_size: int = 3,
|
|
51
51
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
52
|
+
"""
|
|
53
|
+
Apply ball filtering to a 3D volume and detect cell centres.
|
|
54
|
+
|
|
55
|
+
Uses the `BallFilter` class to perform ball filtering on the volume
|
|
56
|
+
and the `CellDetector` class to detect cell centres.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
volume (np.ndarray): The 3D volume to be filtered.
|
|
60
|
+
threshold_value (int): The threshold value for ball filtering.
|
|
61
|
+
soma_centre_value (int): The value representing the soma centre.
|
|
62
|
+
ball_xy_size (int, optional):
|
|
63
|
+
The size of the ball filter in the XY plane. Defaults to 3.
|
|
64
|
+
ball_z_size (int, optional):
|
|
65
|
+
The size of the ball filter in the Z plane. Defaults to 3.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Tuple[np.ndarray, np.ndarray]:
|
|
69
|
+
A tuple containing the filtered volume and the cell centres.
|
|
70
|
+
|
|
71
|
+
"""
|
|
52
72
|
# OPTIMISE: reuse ball filter instance
|
|
53
73
|
|
|
54
74
|
good_tiles_mask = np.ones((1, 1, volume.shape[2]), dtype=bool)
|
|
@@ -71,10 +91,10 @@ def ball_filter_imgs(
|
|
|
71
91
|
)
|
|
72
92
|
|
|
73
93
|
# FIXME: hard coded type
|
|
74
|
-
ball_filtered_volume = np.zeros(volume.shape, dtype=np.
|
|
94
|
+
ball_filtered_volume = np.zeros(volume.shape, dtype=np.uint32)
|
|
75
95
|
previous_plane = None
|
|
76
96
|
for z in range(volume.shape[2]):
|
|
77
|
-
bf.append(volume[:, :, z].astype(np.
|
|
97
|
+
bf.append(volume[:, :, z].astype(np.uint32), good_tiles_mask[:, :, z])
|
|
78
98
|
if bf.ready:
|
|
79
99
|
bf.walk()
|
|
80
100
|
middle_plane = bf.get_middle_plane()
|
|
@@ -89,11 +109,24 @@ def ball_filter_imgs(
|
|
|
89
109
|
def iterative_ball_filter(
|
|
90
110
|
volume: np.ndarray, n_iter: int = 10
|
|
91
111
|
) -> Tuple[List[int], List[np.ndarray]]:
|
|
112
|
+
"""
|
|
113
|
+
Apply iterative ball filtering to the given volume.
|
|
114
|
+
The volume is eroded at each iteration, by subtracting 1 from the volume.
|
|
115
|
+
|
|
116
|
+
Parameters:
|
|
117
|
+
volume (np.ndarray): The input volume.
|
|
118
|
+
n_iter (int): The number of iterations to perform. Default is 10.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Tuple[List[int], List[np.ndarray]]: A tuple containing two lists:
|
|
122
|
+
The structures found in each iteration.
|
|
123
|
+
The cell centres found in each iteration.
|
|
124
|
+
"""
|
|
92
125
|
ns = []
|
|
93
126
|
centres = []
|
|
94
127
|
|
|
95
|
-
threshold_value =
|
|
96
|
-
soma_centre_value =
|
|
128
|
+
threshold_value = np.iinfo(volume.dtype).max - 1
|
|
129
|
+
soma_centre_value = np.iinfo(volume.dtype).max
|
|
97
130
|
|
|
98
131
|
vol = volume.copy() # TODO: check if required
|
|
99
132
|
|
|
@@ -131,6 +164,21 @@ def check_centre_in_cuboid(centre: np.ndarray, max_coords: np.ndarray) -> bool:
|
|
|
131
164
|
def split_cells(
|
|
132
165
|
cell_points: np.ndarray, outlier_keep: bool = False
|
|
133
166
|
) -> np.ndarray:
|
|
167
|
+
"""
|
|
168
|
+
Split the given cell points into individual cell centres.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
cell_points (np.ndarray): Array of cell points with shape (N, 3),
|
|
172
|
+
where N is the number of cell points and each point is represented
|
|
173
|
+
by its x, y, and z coordinates.
|
|
174
|
+
outlier_keep (bool, optional): Flag indicating whether to keep outliers
|
|
175
|
+
during the splitting process. Defaults to False.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
np.ndarray: Array of absolute cell centres with shape (M, 3),
|
|
179
|
+
where M is the number of individual cells and each centre is
|
|
180
|
+
represented by its x, y, and z coordinates.
|
|
181
|
+
"""
|
|
134
182
|
orig_centre = get_structure_centre(cell_points)
|
|
135
183
|
|
|
136
184
|
xs = cell_points[:, 0]
|
{cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/volume_filter.py
RENAMED
|
@@ -142,6 +142,10 @@ class VolumeFilter(object):
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
cells = []
|
|
145
|
+
|
|
146
|
+
logger.debug(
|
|
147
|
+
f"Processing {len(self.cell_detector.coords_maps.items())} cells"
|
|
148
|
+
)
|
|
145
149
|
for cell_id, cell_points in self.cell_detector.coords_maps.items():
|
|
146
150
|
cell_volume = len(cell_points)
|
|
147
151
|
|
|
@@ -191,6 +195,7 @@ class VolumeFilter(object):
|
|
|
191
195
|
)
|
|
192
196
|
)
|
|
193
197
|
|
|
198
|
+
logger.debug("Finished splitting cell clusters.")
|
|
194
199
|
return cells
|
|
195
200
|
|
|
196
201
|
|
|
@@ -3,7 +3,7 @@ from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
from cellfinder.core.download import models
|
|
6
|
-
from cellfinder.core.download.download import
|
|
6
|
+
from cellfinder.core.download.download import amend_user_configuration
|
|
7
7
|
|
|
8
8
|
home = Path.home()
|
|
9
9
|
DEFAULT_DOWNLOAD_DIRECTORY = home / ".cellfinder"
|
|
@@ -65,7 +65,7 @@ def main():
|
|
|
65
65
|
model_path = models.main(args.model, args.install_path)
|
|
66
66
|
|
|
67
67
|
if not args.no_amend_config:
|
|
68
|
-
|
|
68
|
+
amend_user_configuration(new_model_path=model_path)
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
if __name__ == "__main__":
|
|
@@ -7,8 +7,8 @@ from brainglobe_utils.general.config import get_config_obj
|
|
|
7
7
|
from brainglobe_utils.general.system import disk_free_gb
|
|
8
8
|
|
|
9
9
|
from cellfinder.core.tools.source_files import (
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
default_configuration_path,
|
|
11
|
+
user_specific_configuration_path,
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
|
|
@@ -75,16 +75,45 @@ def download(
|
|
|
75
75
|
os.remove(download_path)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
new_config = source_custom_config_cellfinder()
|
|
83
|
-
if new_model_path is not None:
|
|
84
|
-
write_model_to_cfg(new_model_path, original_config, new_config)
|
|
78
|
+
def amend_user_configuration(new_model_path=None) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Amends the user configuration to contain the configuration
|
|
81
|
+
in new_model_path, if specified.
|
|
85
82
|
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
new_model_path : str, optional
|
|
86
|
+
The path to the new model configuration.
|
|
87
|
+
"""
|
|
88
|
+
print("(Over-)writing custom user configuration")
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
original_config = default_configuration_path()
|
|
91
|
+
new_config = user_specific_configuration_path()
|
|
92
|
+
if new_model_path is not None:
|
|
93
|
+
write_model_to_config(new_model_path, original_config, new_config)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def write_model_to_config(new_model_path, orig_config, custom_config):
|
|
97
|
+
"""
|
|
98
|
+
Update the model path in the custom configuration file, by
|
|
99
|
+
reading the lines in the original configuration file, replacing
|
|
100
|
+
the line starting with "model_path =" and writing these
|
|
101
|
+
lines to the custom file.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
new_model_path : str
|
|
106
|
+
The new path to the model.
|
|
107
|
+
orig_config : str
|
|
108
|
+
The path to the original configuration file.
|
|
109
|
+
custom_config : str
|
|
110
|
+
The path to the custom configuration file to be created.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
None
|
|
115
|
+
|
|
116
|
+
"""
|
|
88
117
|
config_obj = get_config_obj(orig_config)
|
|
89
118
|
model_conf = config_obj["model"]
|
|
90
119
|
orig_path = model_conf["model_path"]
|
|
@@ -3,6 +3,7 @@ prep
|
|
|
3
3
|
==================
|
|
4
4
|
Functions to prepare files and directories needed for other functions
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import os
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Optional
|
|
@@ -13,8 +14,8 @@ from brainglobe_utils.general.system import get_num_processes
|
|
|
13
14
|
import cellfinder.core.tools.tf as tf_tools
|
|
14
15
|
from cellfinder.core import logger
|
|
15
16
|
from cellfinder.core.download import models as model_download
|
|
16
|
-
from cellfinder.core.download.download import
|
|
17
|
-
from cellfinder.core.tools.source_files import
|
|
17
|
+
from cellfinder.core.download.download import amend_user_configuration
|
|
18
|
+
from cellfinder.core.tools.source_files import user_specific_configuration_path
|
|
18
19
|
|
|
19
20
|
home = Path.home()
|
|
20
21
|
DEFAULT_INSTALL_PATH = home / ".cellfinder"
|
|
@@ -48,18 +49,18 @@ def prep_models(
|
|
|
48
49
|
if model_weights_path is None:
|
|
49
50
|
logger.debug("No model supplied, so using the default")
|
|
50
51
|
|
|
51
|
-
config_file =
|
|
52
|
+
config_file = user_specific_configuration_path()
|
|
52
53
|
|
|
53
54
|
if not Path(config_file).exists():
|
|
54
55
|
logger.debug("Custom config does not exist, downloading models")
|
|
55
56
|
model_path = model_download.main(model_name, install_path)
|
|
56
|
-
|
|
57
|
+
amend_user_configuration(new_model_path=model_path)
|
|
57
58
|
|
|
58
59
|
model_weights = get_model_weights(config_file)
|
|
59
60
|
if not model_weights.exists():
|
|
60
61
|
logger.debug("Model weights do not exist, downloading")
|
|
61
62
|
model_path = model_download.main(model_name, install_path)
|
|
62
|
-
|
|
63
|
+
amend_user_configuration(new_model_path=model_path)
|
|
63
64
|
model_weights = get_model_weights(config_file)
|
|
64
65
|
else:
|
|
65
66
|
model_weights = Path(model_weights_path)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def default_configuration_path():
|
|
5
|
+
"""
|
|
6
|
+
Returns the default configuration path for cellfinder.
|
|
7
|
+
|
|
8
|
+
Returns:
|
|
9
|
+
Path: The default configuration path.
|
|
10
|
+
"""
|
|
11
|
+
return Path(__file__).parent.parent / "config" / "cellfinder.conf"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def user_specific_configuration_path():
|
|
15
|
+
"""
|
|
16
|
+
Returns the path to the user-specific configuration file for cellfinder.
|
|
17
|
+
|
|
18
|
+
This function returns the path to the user-specific configuration file
|
|
19
|
+
for cellfinder. The user-specific configuration file is located in the
|
|
20
|
+
user's home directory under the ".cellfinder" folder and is named
|
|
21
|
+
"cellfinder.conf.custom".
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Path: The path to the custom configuration file.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
return Path.home() / ".cellfinder" / "cellfinder.conf.custom"
|
|
@@ -338,7 +338,10 @@ def run(
|
|
|
338
338
|
|
|
339
339
|
ensure_directory_exists(output_dir)
|
|
340
340
|
model_weights = prep_model_weights(
|
|
341
|
-
|
|
341
|
+
model_weights=model_weights,
|
|
342
|
+
install_path=install_path,
|
|
343
|
+
model_name=model,
|
|
344
|
+
n_free_cpus=n_free_cpus,
|
|
342
345
|
)
|
|
343
346
|
|
|
344
347
|
yaml_contents = parse_yaml(yaml_file)
|
|
File without changes
|