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.

Files changed (77) hide show
  1. {cellfinder-1.1.0 → cellfinder-1.1.2}/.github/workflows/test_and_deploy.yml +3 -7
  2. {cellfinder-1.1.0 → cellfinder-1.1.2}/.gitignore +1 -0
  3. cellfinder-1.1.2/CITATION.cff +60 -0
  4. {cellfinder-1.1.0 → cellfinder-1.1.2}/MANIFEST.in +1 -2
  5. {cellfinder-1.1.0 → cellfinder-1.1.2}/PKG-INFO +6 -6
  6. {cellfinder-1.1.0 → cellfinder-1.1.2}/README.md +1 -1
  7. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/cube_generator.py +2 -6
  8. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/detect.py +73 -4
  9. cellfinder-1.1.2/cellfinder/core/detect/filters/plane/classical_filter.py +45 -0
  10. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/ball_filter.py +5 -5
  11. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/structure_splitting.py +54 -6
  12. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/volume_filter.py +5 -0
  13. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/cli.py +2 -2
  14. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/download.py +39 -10
  15. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/main.py +1 -0
  16. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/prep.py +6 -5
  17. cellfinder-1.1.2/cellfinder/core/tools/source_files.py +27 -0
  18. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/train/train_yml.py +4 -1
  19. cellfinder-1.1.2/cellfinder/napari/__init__.py +0 -0
  20. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/curation.py +66 -22
  21. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/train.py +6 -0
  22. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/utils.py +1 -89
  23. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/PKG-INFO +6 -6
  24. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/SOURCES.txt +1 -0
  25. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/requires.txt +2 -2
  26. {cellfinder-1.1.0 → cellfinder-1.1.2}/pyproject.toml +4 -5
  27. cellfinder-1.1.0/cellfinder/core/detect/filters/plane/classical_filter.py +0 -20
  28. cellfinder-1.1.0/cellfinder/core/tools/source_files.py +0 -9
  29. cellfinder-1.1.0/cellfinder/napari/__init__.py +0 -3
  30. {cellfinder-1.1.0 → cellfinder-1.1.2}/.github/workflows/test_include_guard.yaml +0 -0
  31. {cellfinder-1.1.0 → cellfinder-1.1.2}/.napari/config.yml +0 -0
  32. {cellfinder-1.1.0 → cellfinder-1.1.2}/LICENSE +0 -0
  33. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/__init__.py +0 -0
  34. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/cli_migration_warning.py +0 -0
  35. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/__init__.py +0 -0
  36. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/__init__.py +0 -0
  37. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/augment.py +0 -0
  38. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/classify.py +0 -0
  39. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/resnet.py +0 -0
  40. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/classify/tools.py +0 -0
  41. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/config/__init__.py +0 -0
  42. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/config/cellfinder.conf +0 -0
  43. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/__init__.py +0 -0
  44. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/__init__.py +0 -0
  45. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/__init__.py +0 -0
  46. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/plane_filter.py +0 -0
  47. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/plane/tile_walker.py +0 -0
  48. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/setup_filters.py +0 -0
  49. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/__init__.py +0 -0
  50. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/detect/filters/volume/structure_detection.py +0 -0
  51. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/__init__.py +0 -0
  52. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/download/models.py +0 -0
  53. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/IO.py +0 -0
  54. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/__init__.py +0 -0
  55. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/array_operations.py +0 -0
  56. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/geometry.py +0 -0
  57. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/image_processing.py +0 -0
  58. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/system.py +0 -0
  59. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tf.py +0 -0
  60. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tiff.py +0 -0
  61. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/tools/tools.py +0 -0
  62. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/train/__init__.py +0 -0
  63. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/core/types.py +0 -0
  64. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/__init__.py +0 -0
  65. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/detect.py +0 -0
  66. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/detect_containers.py +0 -0
  67. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/detect/thread_worker.py +0 -0
  68. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/images/brainglobe.png +0 -0
  69. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/input_container.py +0 -0
  70. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/napari.yaml +0 -0
  71. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/sample_data.py +0 -0
  72. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/__init__.py +0 -0
  73. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder/napari/train/train_containers.py +0 -0
  74. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/dependency_links.txt +0 -0
  75. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/entry_points.txt +0 -0
  76. {cellfinder-1.1.0 → cellfinder-1.1.2}/cellfinder.egg-info/top_level.txt +0 -0
  77. {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/download-artifact@v3
143
+ - uses: neuroinformatics-unit/actions/upload_pypi@v2
143
144
  with:
144
- name: artifact
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 }}
@@ -143,3 +143,4 @@ benchmarks/env
143
143
  */_version.py
144
144
 
145
145
  .idea/
146
+ .vscode/
@@ -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,9 +1,8 @@
1
1
  include .napari/config.yml
2
+ include CITATION.cff
2
3
  include LICENSE
3
4
  include README.md
4
5
  include requirements.txt
5
- include CF-CORE-README.md
6
- include CF-NAPARI-README.md
7
6
 
8
7
  exclude .pre-commit-config.yaml
9
8
  exclude .codecov.yml
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellfinder
3
- Version: 1.1.0
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-core
9
- Project-URL: Bug Tracker, https://github.com/brainglobe/cellfinder-core/issues
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
  [![Downloads](https://pepy.tech/badge/cellfinder-core)](https://pepy.tech/project/cellfinder)
60
60
  [![Wheel](https://img.shields.io/pypi/wheel/cellfinder-core.svg)](https://pypi.org/project/cellfinder)
61
61
  [![Development Status](https://img.shields.io/pypi/status/cellfinder-core.svg)](https://github.com/brainglobe/cellfinder)
62
- [![Tests](https://img.shields.io/github/workflow/status/brainglobe/cellfinder-core/tests)](https://github.com/brainglobe/cellfinder/actions)
62
+ [![Tests](https://img.shields.io/github/actions/workflow/status/brainglobe/cellfinder/test_and_deploy.yml?branch=main)](https://github.com/brainglobe/cellfinder/actions)
63
63
  [![codecov](https://codecov.io/gh/brainglobe/cellfinder-core/branch/main/graph/badge.svg?token=nx1lhNI7ox)](https://codecov.io/gh/brainglobe/cellfinder)
64
64
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
65
65
  [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
@@ -3,7 +3,7 @@
3
3
  [![Downloads](https://pepy.tech/badge/cellfinder-core)](https://pepy.tech/project/cellfinder)
4
4
  [![Wheel](https://img.shields.io/pypi/wheel/cellfinder-core.svg)](https://pypi.org/project/cellfinder)
5
5
  [![Development Status](https://img.shields.io/pypi/status/cellfinder-core.svg)](https://github.com/brainglobe/cellfinder)
6
- [![Tests](https://img.shields.io/github/workflow/status/brainglobe/cellfinder-core/tests)](https://github.com/brainglobe/cellfinder/actions)
6
+ [![Tests](https://img.shields.io/github/actions/workflow/status/brainglobe/cellfinder/test_and_deploy.yml?branch=main)](https://github.com/brainglobe/cellfinder/actions)
7
7
  [![codecov](https://codecov.io/gh/brainglobe/cellfinder-core/branch/main/graph/badge.svg?token=nx1lhNI7ox)](https://codecov.io/gh/brainglobe/cellfinder)
8
8
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
9
9
  [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](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
- print(
173
- "Detection complete - all planes done in : {}".format(
174
- datetime.now() - start_time
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.uint16
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.uint16)
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
- ball_centre_x, ball_centre_y, middle_z
332
- ] = SOMA_CENTRE_VALUE
330
+ volume[ball_centre_x, ball_centre_y, middle_z] = (
331
+ SOMA_CENTRE_VALUE
332
+ )
@@ -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.uint16)
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] = 65534
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.uint16)
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.uint16), good_tiles_mask[:, :, z])
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 = 65534
96
- soma_centre_value = 65535
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]
@@ -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 amend_cfg
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
- amend_cfg(new_model_path=model_path)
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
- source_config_cellfinder,
11
- source_custom_config_cellfinder,
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 amend_cfg(new_model_path=None):
79
- print("Ensuring custom config file is correct")
80
-
81
- original_config = source_config_cellfinder()
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
- def write_model_to_cfg(new_model_path, orig_config, custom_config):
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"]
@@ -2,6 +2,7 @@
2
2
  N.B imports are within functions to prevent tensorflow being imported before
3
3
  it's warnings are silenced
4
4
  """
5
+
5
6
  import os
6
7
  from typing import Callable, List, Optional, Tuple
7
8
 
@@ -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 amend_cfg
17
- from cellfinder.core.tools.source_files import source_custom_config_cellfinder
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 = source_custom_config_cellfinder()
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
- amend_cfg(new_model_path=model_path)
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
- amend_cfg(new_model_path=model_path)
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
- install_path, model_weights, model, n_free_cpus
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