cellfinder 1.1.3__py3-none-any.whl → 1.3.0rc0__py3-none-any.whl
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.
- cellfinder/__init__.py +20 -11
- cellfinder/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/__pycache__/main.cpython-310.pyc +0 -0
- cellfinder/core/__pycache__/main.cpython-311.pyc +0 -0
- cellfinder/core/__pycache__/main.cpython-312.pyc +0 -0
- cellfinder/core/__pycache__/types.cpython-310.pyc +0 -0
- cellfinder/core/__pycache__/types.cpython-311.pyc +0 -0
- cellfinder/core/__pycache__/types.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/augment.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/augment.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/augment.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/classify.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/classify.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/classify.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/cube_generator.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/cube_generator.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/cube_generator.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/resnet.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/resnet.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/resnet.cpython-312.pyc +0 -0
- cellfinder/core/classify/__pycache__/tools.cpython-310.pyc +0 -0
- cellfinder/core/classify/__pycache__/tools.cpython-311.pyc +0 -0
- cellfinder/core/classify/__pycache__/tools.cpython-312.pyc +0 -0
- cellfinder/core/classify/classify.py +5 -6
- cellfinder/core/classify/cube_generator.py +25 -9
- cellfinder/core/classify/resnet.py +9 -6
- cellfinder/core/classify/tools.py +13 -11
- cellfinder/core/config/cellfinder.conf.custom +3 -0
- cellfinder/core/detect/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/detect/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/detect/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/detect/__pycache__/detect.cpython-310.pyc +0 -0
- cellfinder/core/detect/__pycache__/detect.cpython-311.pyc +0 -0
- cellfinder/core/detect/__pycache__/detect.cpython-312.pyc +0 -0
- cellfinder/core/detect/detect.py +12 -1
- cellfinder/core/detect/filters/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/__pycache__/setup_filters.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/__pycache__/setup_filters.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/__pycache__/setup_filters.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/classical_filter.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/classical_filter.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/classical_filter.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/plane_filter.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/plane_filter.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/plane_filter.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/tile_walker.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/tile_walker.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/plane/__pycache__/tile_walker.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-199.py310.1.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-199.py310.nbi +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py310.1.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py310.2.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py310.nbi +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py311.1.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py311.2.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py311.nbi +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py312.1.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py312.2.nbc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter._cube_overlaps-263.py312.nbi +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/ball_filter.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_detection.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_detection.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_detection.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_splitting.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_splitting.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/structure_splitting.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/volume_filter.cpython-310.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/volume_filter.cpython-311.pyc +0 -0
- cellfinder/core/detect/filters/volume/__pycache__/volume_filter.cpython-312.pyc +0 -0
- cellfinder/core/detect/filters/volume/ball_filter.py +198 -113
- cellfinder/core/detect/filters/volume/structure_detection.py +105 -41
- cellfinder/core/detect/filters/volume/structure_splitting.py +1 -1
- cellfinder/core/detect/filters/volume/volume_filter.py +48 -49
- cellfinder/core/download/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/download/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/download/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/download/__pycache__/cli.cpython-310.pyc +0 -0
- cellfinder/core/download/__pycache__/cli.cpython-312.pyc +0 -0
- cellfinder/core/download/__pycache__/download.cpython-310.pyc +0 -0
- cellfinder/core/download/__pycache__/download.cpython-311.pyc +0 -0
- cellfinder/core/download/__pycache__/download.cpython-312.pyc +0 -0
- cellfinder/core/download/cli.py +39 -32
- cellfinder/core/download/download.py +44 -56
- cellfinder/core/main.py +52 -67
- cellfinder/core/tools/__pycache__/IO.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/array_operations.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/array_operations.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/array_operations.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/geometry.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/geometry.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/geometry.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/image_processing.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/image_processing.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/prep.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/prep.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/prep.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/source_files.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/source_files.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/source_files.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/system.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/system.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/tf.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/tiff.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/tiff.cpython-312.pyc +0 -0
- cellfinder/core/tools/__pycache__/tools.cpython-310.pyc +0 -0
- cellfinder/core/tools/__pycache__/tools.cpython-311.pyc +0 -0
- cellfinder/core/tools/__pycache__/tools.cpython-312.pyc +0 -0
- cellfinder/core/tools/prep.py +12 -20
- cellfinder/core/tools/source_files.py +5 -3
- cellfinder/core/train/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/core/train/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/core/train/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/core/train/__pycache__/train_yml.cpython-310.pyc +0 -0
- cellfinder/core/train/__pycache__/train_yml.cpython-311.pyc +0 -0
- cellfinder/core/train/__pycache__/train_yml.cpython-312.pyc +0 -0
- cellfinder/core/train/train_yml.py +29 -27
- cellfinder/napari/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/napari/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/napari/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/napari/__pycache__/curation.cpython-310.pyc +0 -0
- cellfinder/napari/__pycache__/curation.cpython-312.pyc +0 -0
- cellfinder/napari/__pycache__/input_container.cpython-310.pyc +0 -0
- cellfinder/napari/__pycache__/input_container.cpython-311.pyc +0 -0
- cellfinder/napari/__pycache__/input_container.cpython-312.pyc +0 -0
- cellfinder/napari/__pycache__/sample_data.cpython-310.pyc +0 -0
- cellfinder/napari/__pycache__/sample_data.cpython-312.pyc +0 -0
- cellfinder/napari/__pycache__/utils.cpython-310.pyc +0 -0
- cellfinder/napari/__pycache__/utils.cpython-311.pyc +0 -0
- cellfinder/napari/__pycache__/utils.cpython-312.pyc +0 -0
- cellfinder/napari/detect/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/napari/detect/__pycache__/__init__.cpython-311.pyc +0 -0
- cellfinder/napari/detect/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect.cpython-310.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect.cpython-311.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect.cpython-312.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect_containers.cpython-310.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect_containers.cpython-311.pyc +0 -0
- cellfinder/napari/detect/__pycache__/detect_containers.cpython-312.pyc +0 -0
- cellfinder/napari/detect/__pycache__/thread_worker.cpython-310.pyc +0 -0
- cellfinder/napari/detect/__pycache__/thread_worker.cpython-311.pyc +0 -0
- cellfinder/napari/detect/__pycache__/thread_worker.cpython-312.pyc +0 -0
- cellfinder/napari/detect/detect.py +252 -57
- cellfinder/napari/detect/detect_containers.py +9 -1
- cellfinder/napari/detect/thread_worker.py +14 -0
- cellfinder/napari/train/__pycache__/__init__.cpython-310.pyc +0 -0
- cellfinder/napari/train/__pycache__/__init__.cpython-312.pyc +0 -0
- cellfinder/napari/train/__pycache__/train.cpython-310.pyc +0 -0
- cellfinder/napari/train/__pycache__/train.cpython-312.pyc +0 -0
- cellfinder/napari/train/__pycache__/train_containers.cpython-310.pyc +0 -0
- cellfinder/napari/train/__pycache__/train_containers.cpython-312.pyc +0 -0
- cellfinder/napari/train/train.py +2 -9
- cellfinder/napari/train/train_containers.py +3 -3
- cellfinder/napari/utils.py +88 -47
- {cellfinder-1.1.3.dist-info → cellfinder-1.3.0rc0.dist-info}/METADATA +11 -11
- cellfinder-1.3.0rc0.dist-info/RECORD +211 -0
- cellfinder/core/download/models.py +0 -49
- cellfinder/core/tools/IO.py +0 -48
- cellfinder/core/tools/tf.py +0 -46
- cellfinder/napari/images/brainglobe.png +0 -0
- cellfinder-1.1.3.dist-info/RECORD +0 -63
- {cellfinder-1.1.3.dist-info → cellfinder-1.3.0rc0.dist-info}/LICENSE +0 -0
- {cellfinder-1.1.3.dist-info → cellfinder-1.3.0rc0.dist-info}/WHEEL +0 -0
- {cellfinder-1.1.3.dist-info → cellfinder-1.3.0rc0.dist-info}/entry_points.txt +0 -0
- {cellfinder-1.1.3.dist-info → cellfinder-1.3.0rc0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,83 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
|
-
from numba import njit
|
|
4
|
+
from numba import njit, objmode, prange
|
|
5
|
+
from numba.core import types
|
|
6
|
+
from numba.experimental import jitclass
|
|
3
7
|
|
|
4
8
|
from cellfinder.core.tools.array_operations import bin_mean_3d
|
|
5
9
|
from cellfinder.core.tools.geometry import make_sphere
|
|
6
10
|
|
|
7
11
|
DEBUG = False
|
|
8
12
|
|
|
13
|
+
uint32_3d_type = types.uint32[:, :, :]
|
|
14
|
+
bool_3d_type = types.bool_[:, :, :]
|
|
15
|
+
float_3d_type = types.float64[:, :, :]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@lru_cache(maxsize=50)
|
|
19
|
+
def get_kernel(ball_xy_size: int, ball_z_size: int) -> np.ndarray:
|
|
20
|
+
# Create a spherical kernel.
|
|
21
|
+
#
|
|
22
|
+
# This is done by:
|
|
23
|
+
# 1. Generating a binary sphere at a resolution *upscale_factor* larger
|
|
24
|
+
# than desired.
|
|
25
|
+
# 2. Downscaling the binary sphere to get a 'fuzzy' sphere at the
|
|
26
|
+
# original intended scale
|
|
27
|
+
upscale_factor: int = 7
|
|
28
|
+
upscaled_kernel_shape = (
|
|
29
|
+
upscale_factor * ball_xy_size,
|
|
30
|
+
upscale_factor * ball_xy_size,
|
|
31
|
+
upscale_factor * ball_z_size,
|
|
32
|
+
)
|
|
33
|
+
upscaled_ball_centre_position = (
|
|
34
|
+
np.floor(upscaled_kernel_shape[0] / 2),
|
|
35
|
+
np.floor(upscaled_kernel_shape[1] / 2),
|
|
36
|
+
np.floor(upscaled_kernel_shape[2] / 2),
|
|
37
|
+
)
|
|
38
|
+
upscaled_ball_radius = upscaled_kernel_shape[0] / 2.0
|
|
39
|
+
|
|
40
|
+
sphere_kernel = make_sphere(
|
|
41
|
+
upscaled_kernel_shape,
|
|
42
|
+
upscaled_ball_radius,
|
|
43
|
+
upscaled_ball_centre_position,
|
|
44
|
+
)
|
|
45
|
+
sphere_kernel = sphere_kernel.astype(np.float64)
|
|
46
|
+
kernel = bin_mean_3d(
|
|
47
|
+
sphere_kernel,
|
|
48
|
+
bin_height=upscale_factor,
|
|
49
|
+
bin_width=upscale_factor,
|
|
50
|
+
bin_depth=upscale_factor,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
assert (
|
|
54
|
+
kernel.shape[2] == ball_z_size
|
|
55
|
+
), "Kernel z dimension should be {}, got {}".format(
|
|
56
|
+
ball_z_size, kernel.shape[2]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return kernel
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# volume indices/size is 64 bit for very large brains(!)
|
|
63
|
+
spec = [
|
|
64
|
+
("ball_xy_size", types.uint32),
|
|
65
|
+
("ball_z_size", types.uint32),
|
|
66
|
+
("tile_step_width", types.uint64),
|
|
67
|
+
("tile_step_height", types.uint64),
|
|
68
|
+
("THRESHOLD_VALUE", types.uint32),
|
|
69
|
+
("SOMA_CENTRE_VALUE", types.uint32),
|
|
70
|
+
("overlap_fraction", types.float64),
|
|
71
|
+
("overlap_threshold", types.float64),
|
|
72
|
+
("middle_z_idx", types.uint32),
|
|
73
|
+
("_num_z_added", types.uint32),
|
|
74
|
+
("kernel", float_3d_type),
|
|
75
|
+
("volume", uint32_3d_type),
|
|
76
|
+
("inside_brain_tiles", bool_3d_type),
|
|
77
|
+
]
|
|
78
|
+
|
|
9
79
|
|
|
80
|
+
@jitclass(spec=spec)
|
|
10
81
|
class BallFilter:
|
|
11
82
|
"""
|
|
12
83
|
A 3D ball filter.
|
|
@@ -62,72 +133,39 @@ class BallFilter:
|
|
|
62
133
|
self.THRESHOLD_VALUE = threshold_value
|
|
63
134
|
self.SOMA_CENTRE_VALUE = soma_centre_value
|
|
64
135
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# than desired.
|
|
70
|
-
# 2. Downscaling the binary sphere to get a 'fuzzy' sphere at the
|
|
71
|
-
# original intended scale
|
|
72
|
-
upscale_factor: int = 7
|
|
73
|
-
upscaled_kernel_shape = (
|
|
74
|
-
upscale_factor * ball_xy_size,
|
|
75
|
-
upscale_factor * ball_xy_size,
|
|
76
|
-
upscale_factor * ball_z_size,
|
|
77
|
-
)
|
|
78
|
-
upscaled_ball_centre_position = (
|
|
79
|
-
np.floor(upscaled_kernel_shape[0] / 2),
|
|
80
|
-
np.floor(upscaled_kernel_shape[1] / 2),
|
|
81
|
-
np.floor(upscaled_kernel_shape[2] / 2),
|
|
82
|
-
)
|
|
83
|
-
upscaled_ball_radius = upscaled_kernel_shape[0] / 2.0
|
|
84
|
-
sphere_kernel = make_sphere(
|
|
85
|
-
upscaled_kernel_shape,
|
|
86
|
-
upscaled_ball_radius,
|
|
87
|
-
upscaled_ball_centre_position,
|
|
88
|
-
)
|
|
89
|
-
sphere_kernel = sphere_kernel.astype(np.float64)
|
|
90
|
-
self.kernel = bin_mean_3d(
|
|
91
|
-
sphere_kernel,
|
|
92
|
-
bin_height=upscale_factor,
|
|
93
|
-
bin_width=upscale_factor,
|
|
94
|
-
bin_depth=upscale_factor,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
assert (
|
|
98
|
-
self.kernel.shape[2] == ball_z_size
|
|
99
|
-
), "Kernel z dimension should be {}, got {}".format(
|
|
100
|
-
ball_z_size, self.kernel.shape[2]
|
|
101
|
-
)
|
|
136
|
+
# getting kernel is not jitted
|
|
137
|
+
with objmode(kernel=float_3d_type):
|
|
138
|
+
kernel = get_kernel(ball_xy_size, ball_z_size)
|
|
139
|
+
self.kernel = kernel
|
|
102
140
|
|
|
103
141
|
self.overlap_threshold = np.sum(self.overlap_fraction * self.kernel)
|
|
104
142
|
|
|
105
143
|
# Stores the current planes that are being filtered
|
|
144
|
+
# first axis is z for faster rotating the z-axis
|
|
106
145
|
self.volume = np.empty(
|
|
107
|
-
(plane_width, plane_height
|
|
146
|
+
(ball_z_size, plane_width, plane_height),
|
|
147
|
+
dtype=np.uint32,
|
|
108
148
|
)
|
|
109
149
|
# Index of the middle plane in the volume
|
|
110
150
|
self.middle_z_idx = int(np.floor(ball_z_size / 2))
|
|
151
|
+
self._num_z_added = 0
|
|
111
152
|
|
|
112
|
-
#
|
|
153
|
+
# first axis is z
|
|
113
154
|
self.inside_brain_tiles = np.empty(
|
|
114
155
|
(
|
|
156
|
+
ball_z_size,
|
|
115
157
|
int(np.ceil(plane_width / tile_step_width)),
|
|
116
158
|
int(np.ceil(plane_height / tile_step_height)),
|
|
117
|
-
ball_z_size,
|
|
118
159
|
),
|
|
119
|
-
dtype=
|
|
160
|
+
dtype=np.bool_,
|
|
120
161
|
)
|
|
121
|
-
# Stores the z-index in volume at which new planes are inserted when
|
|
122
|
-
# append() is called
|
|
123
|
-
self.__current_z = -1
|
|
124
162
|
|
|
125
163
|
@property
|
|
126
164
|
def ready(self) -> bool:
|
|
127
165
|
"""
|
|
128
166
|
Return `True` if enough planes have been appended to run the filter.
|
|
129
167
|
"""
|
|
130
|
-
return self.
|
|
168
|
+
return self._num_z_added >= self.ball_z_size
|
|
131
169
|
|
|
132
170
|
def append(self, plane: np.ndarray, mask: np.ndarray) -> None:
|
|
133
171
|
"""
|
|
@@ -135,76 +173,106 @@ class BallFilter:
|
|
|
135
173
|
"""
|
|
136
174
|
if DEBUG:
|
|
137
175
|
assert [e for e in plane.shape[:2]] == [
|
|
138
|
-
e for e in self.volume.shape[:
|
|
176
|
+
e for e in self.volume.shape[1:]
|
|
139
177
|
], 'plane shape mismatch, expected "{}", got "{}"'.format(
|
|
140
|
-
[e for e in self.volume.shape[:
|
|
178
|
+
[e for e in self.volume.shape[1:]],
|
|
141
179
|
[e for e in plane.shape[:2]],
|
|
142
180
|
)
|
|
143
181
|
assert [e for e in mask.shape[:2]] == [
|
|
144
|
-
e for e in self.inside_brain_tiles.shape[:
|
|
182
|
+
e for e in self.inside_brain_tiles.shape[1:]
|
|
145
183
|
], 'mask shape mismatch, expected"{}", got {}"'.format(
|
|
146
|
-
[e for e in self.inside_brain_tiles.shape[:
|
|
184
|
+
[e for e in self.inside_brain_tiles.shape[1:]],
|
|
147
185
|
[e for e in mask.shape[:2]],
|
|
148
186
|
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
else:
|
|
187
|
+
|
|
188
|
+
if self.ready:
|
|
152
189
|
# Shift everything down by one to make way for the new plane
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
# this is faster than np.roll, especially with z-axis first
|
|
191
|
+
self.volume[:-1, :, :] = self.volume[1:, :, :]
|
|
192
|
+
self.inside_brain_tiles[:-1, :, :] = self.inside_brain_tiles[
|
|
193
|
+
1:, :, :
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
# index for *next* slice is num we added *so far* until max
|
|
197
|
+
idx = min(self._num_z_added, self.ball_z_size - 1)
|
|
198
|
+
self._num_z_added += 1
|
|
199
|
+
|
|
159
200
|
# Add the new plane to the top of volume and inside_brain_tiles
|
|
160
|
-
self.volume[
|
|
161
|
-
self.inside_brain_tiles[
|
|
201
|
+
self.volume[idx, :, :] = plane
|
|
202
|
+
self.inside_brain_tiles[idx, :, :] = mask
|
|
162
203
|
|
|
163
204
|
def get_middle_plane(self) -> np.ndarray:
|
|
164
205
|
"""
|
|
165
206
|
Get the plane in the middle of self.volume.
|
|
166
207
|
"""
|
|
167
|
-
|
|
168
|
-
return np.array(self.volume[:, :, z], dtype=np.uint32)
|
|
208
|
+
return self.volume[self.middle_z_idx, :, :].copy()
|
|
169
209
|
|
|
170
|
-
def walk(self
|
|
210
|
+
def walk(self, parallel: bool = False) -> None:
|
|
211
|
+
# **don't** pass parallel as keyword arg - numba struggles with it
|
|
212
|
+
# Highly optimised because most time critical
|
|
171
213
|
ball_radius = self.ball_xy_size // 2
|
|
172
214
|
# Get extents of image that are covered by tiles
|
|
173
215
|
tile_mask_covered_img_width = (
|
|
174
|
-
self.inside_brain_tiles.shape[
|
|
216
|
+
self.inside_brain_tiles.shape[1] * self.tile_step_width
|
|
175
217
|
)
|
|
176
218
|
tile_mask_covered_img_height = (
|
|
177
|
-
self.inside_brain_tiles.shape[
|
|
219
|
+
self.inside_brain_tiles.shape[2] * self.tile_step_height
|
|
178
220
|
)
|
|
179
221
|
# Get maximum offsets for the ball
|
|
180
222
|
max_width = tile_mask_covered_img_width - self.ball_xy_size
|
|
181
223
|
max_height = tile_mask_covered_img_height - self.ball_xy_size
|
|
182
224
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
# we have to pass the raw volume so walk doesn't use its edits as it
|
|
226
|
+
# processes the volume. self.volume is the one edited in place
|
|
227
|
+
input_volume = self.volume.copy()
|
|
228
|
+
|
|
229
|
+
if parallel:
|
|
230
|
+
_walk_parallel(
|
|
231
|
+
max_height,
|
|
232
|
+
max_width,
|
|
233
|
+
self.tile_step_width,
|
|
234
|
+
self.tile_step_height,
|
|
235
|
+
self.inside_brain_tiles,
|
|
236
|
+
input_volume,
|
|
237
|
+
self.volume,
|
|
238
|
+
self.kernel,
|
|
239
|
+
ball_radius,
|
|
240
|
+
self.middle_z_idx,
|
|
241
|
+
self.overlap_threshold,
|
|
242
|
+
self.THRESHOLD_VALUE,
|
|
243
|
+
self.SOMA_CENTRE_VALUE,
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
_walk_single(
|
|
247
|
+
max_height,
|
|
248
|
+
max_width,
|
|
249
|
+
self.tile_step_width,
|
|
250
|
+
self.tile_step_height,
|
|
251
|
+
self.inside_brain_tiles,
|
|
252
|
+
input_volume,
|
|
253
|
+
self.volume,
|
|
254
|
+
self.kernel,
|
|
255
|
+
ball_radius,
|
|
256
|
+
self.middle_z_idx,
|
|
257
|
+
self.overlap_threshold,
|
|
258
|
+
self.THRESHOLD_VALUE,
|
|
259
|
+
self.SOMA_CENTRE_VALUE,
|
|
260
|
+
)
|
|
197
261
|
|
|
198
262
|
|
|
199
263
|
@njit(cache=True)
|
|
200
264
|
def _cube_overlaps(
|
|
201
|
-
|
|
265
|
+
volume: np.ndarray,
|
|
266
|
+
x_start: int,
|
|
267
|
+
x_end: int,
|
|
268
|
+
y_start: int,
|
|
269
|
+
y_end: int,
|
|
202
270
|
overlap_threshold: float,
|
|
203
|
-
|
|
271
|
+
threshold_value: int,
|
|
204
272
|
kernel: np.ndarray,
|
|
205
273
|
) -> bool: # Highly optimised because most time critical
|
|
206
274
|
"""
|
|
207
|
-
For each pixel in cube that is greater than THRESHOLD_VALUE, sum
|
|
275
|
+
For each pixel in cube in volume that is greater than THRESHOLD_VALUE, sum
|
|
208
276
|
up the corresponding pixels in *kernel*. If the total is less than
|
|
209
277
|
overlap_threshold, return False, otherwise return True.
|
|
210
278
|
|
|
@@ -214,23 +282,26 @@ def _cube_overlaps(
|
|
|
214
282
|
|
|
215
283
|
Parameters
|
|
216
284
|
----------
|
|
217
|
-
|
|
285
|
+
volume :
|
|
218
286
|
3D array.
|
|
287
|
+
x_start, x_end, y_start, y_end :
|
|
288
|
+
The start and end indices in volume that form the cube. End is
|
|
289
|
+
exclusive
|
|
219
290
|
overlap_threshold :
|
|
220
291
|
Threshold above which to return True.
|
|
221
|
-
|
|
292
|
+
threshold_value :
|
|
222
293
|
Value above which a pixel is marked as being part of a cell.
|
|
223
294
|
kernel :
|
|
224
|
-
3D array, with the same shape as *cube
|
|
295
|
+
3D array, with the same shape as *cube* in the volume.
|
|
225
296
|
"""
|
|
226
|
-
current_overlap_value = 0
|
|
297
|
+
current_overlap_value = 0.0
|
|
227
298
|
|
|
228
|
-
middle = np.floor(
|
|
299
|
+
middle = np.floor(volume.shape[0] / 2) + 1
|
|
229
300
|
halfway_overlap_thresh = (
|
|
230
301
|
overlap_threshold * 0.4
|
|
231
302
|
) # FIXME: do not hard code value
|
|
232
303
|
|
|
233
|
-
for z in range(
|
|
304
|
+
for z in range(volume.shape[0]):
|
|
234
305
|
# TODO: OPTIMISE: step from middle to outer boundaries to check
|
|
235
306
|
# more data first
|
|
236
307
|
#
|
|
@@ -238,11 +309,17 @@ def _cube_overlaps(
|
|
|
238
309
|
# 0.4 * the overlap threshold, return
|
|
239
310
|
if z == middle and current_overlap_value < halfway_overlap_thresh:
|
|
240
311
|
return False # DEBUG: optimisation attempt
|
|
241
|
-
|
|
242
|
-
|
|
312
|
+
|
|
313
|
+
for y in range(y_start, y_end):
|
|
314
|
+
for x in range(x_start, x_end):
|
|
243
315
|
# includes self.SOMA_CENTRE_VALUE
|
|
244
|
-
if
|
|
245
|
-
|
|
316
|
+
if volume[z, x, y] >= threshold_value:
|
|
317
|
+
# x/y must be shifted in kernel because we x/y is relative
|
|
318
|
+
# to the full volume, so shift it to relative to the cube
|
|
319
|
+
current_overlap_value += kernel[
|
|
320
|
+
x - x_start, y - y_start, z
|
|
321
|
+
]
|
|
322
|
+
|
|
246
323
|
return current_overlap_value > overlap_threshold
|
|
247
324
|
|
|
248
325
|
|
|
@@ -260,23 +337,23 @@ def _is_tile_to_check(
|
|
|
260
337
|
"""
|
|
261
338
|
x_in_mask = x // tile_step_width # TEST: test bounds (-1 range)
|
|
262
339
|
y_in_mask = y // tile_step_height # TEST: test bounds (-1 range)
|
|
263
|
-
return inside_brain_tiles[x_in_mask, y_in_mask
|
|
340
|
+
return inside_brain_tiles[middle_z, x_in_mask, y_in_mask]
|
|
264
341
|
|
|
265
342
|
|
|
266
|
-
|
|
267
|
-
def _walk(
|
|
343
|
+
def _walk_base(
|
|
268
344
|
max_height: int,
|
|
269
345
|
max_width: int,
|
|
270
346
|
tile_step_width: int,
|
|
271
347
|
tile_step_height: int,
|
|
272
348
|
inside_brain_tiles: np.ndarray,
|
|
349
|
+
input_volume: np.ndarray,
|
|
273
350
|
volume: np.ndarray,
|
|
274
351
|
kernel: np.ndarray,
|
|
275
352
|
ball_radius: int,
|
|
276
353
|
middle_z: int,
|
|
277
354
|
overlap_threshold: float,
|
|
278
|
-
|
|
279
|
-
|
|
355
|
+
threshold_value: int,
|
|
356
|
+
soma_centre_value: int,
|
|
280
357
|
) -> None:
|
|
281
358
|
"""
|
|
282
359
|
Scan through *volume*, and mark pixels where there are enough surrounding
|
|
@@ -289,23 +366,28 @@ def _walk(
|
|
|
289
366
|
max_height, max_width :
|
|
290
367
|
Maximum offsets for the ball filter.
|
|
291
368
|
inside_brain_tiles :
|
|
292
|
-
|
|
293
|
-
or not. Tiles outside the brain are skipped.
|
|
369
|
+
3d array containing information on whether a tile is
|
|
370
|
+
inside the brain or not. Tiles outside the brain are skipped.
|
|
371
|
+
input_volume :
|
|
372
|
+
3D array containing the plane-filtered data passed to the function
|
|
373
|
+
before walking. volume is edited in place, so this is the original
|
|
374
|
+
volume to prevent the changes for some cubes affective other cubes
|
|
375
|
+
during a single walk call.
|
|
294
376
|
volume :
|
|
295
|
-
3D array containing the plane-filtered data.
|
|
377
|
+
3D array containing the plane-filtered data - edited in place.
|
|
296
378
|
kernel :
|
|
297
379
|
3D array
|
|
298
380
|
ball_radius :
|
|
299
381
|
Radius of the ball in the xy plane.
|
|
300
|
-
|
|
382
|
+
soma_centre_value :
|
|
301
383
|
Value that is used to mark pixels in *volume*.
|
|
302
384
|
|
|
303
385
|
Notes
|
|
304
386
|
-----
|
|
305
387
|
Warning: modifies volume in place!
|
|
306
388
|
"""
|
|
307
|
-
for y in
|
|
308
|
-
for x in
|
|
389
|
+
for y in prange(max_height):
|
|
390
|
+
for x in prange(max_width):
|
|
309
391
|
ball_centre_x = x + ball_radius
|
|
310
392
|
ball_centre_y = y + ball_radius
|
|
311
393
|
if _is_tile_to_check(
|
|
@@ -316,17 +398,20 @@ def _walk(
|
|
|
316
398
|
tile_step_height,
|
|
317
399
|
inside_brain_tiles,
|
|
318
400
|
):
|
|
319
|
-
cube = volume[
|
|
320
|
-
x : x + kernel.shape[0],
|
|
321
|
-
y : y + kernel.shape[1],
|
|
322
|
-
:,
|
|
323
|
-
]
|
|
324
401
|
if _cube_overlaps(
|
|
325
|
-
|
|
402
|
+
input_volume,
|
|
403
|
+
x,
|
|
404
|
+
x + kernel.shape[0],
|
|
405
|
+
y,
|
|
406
|
+
y + kernel.shape[1],
|
|
326
407
|
overlap_threshold,
|
|
327
|
-
|
|
408
|
+
threshold_value,
|
|
328
409
|
kernel,
|
|
329
410
|
):
|
|
330
|
-
volume[ball_centre_x, ball_centre_y
|
|
331
|
-
|
|
411
|
+
volume[middle_z, ball_centre_x, ball_centre_y] = (
|
|
412
|
+
soma_centre_value
|
|
332
413
|
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
_walk_parallel = njit(parallel=True)(_walk_base)
|
|
417
|
+
_walk_single = njit(parallel=False)(_walk_base)
|