tinybrain 1.6.0__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinybrain-1.8.0/.github/workflows/build_wheel.yml +39 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/.github/workflows/run_tests.yml +3 -3
- {tinybrain-1.6.0 → tinybrain-1.8.0}/ChangeLog +22 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/PKG-INFO +22 -8
- {tinybrain-1.6.0 → tinybrain-1.8.0}/README.md +8 -3
- {tinybrain-1.6.0 → tinybrain-1.8.0}/automated_test.py +18 -3
- {tinybrain-1.6.0 → tinybrain-1.8.0}/perf.py +47 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/setup.cfg +1 -1
- {tinybrain-1.6.0 → tinybrain-1.8.0}/setup.py +3 -3
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain/__init__.py +1 -1
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain/accelerated.hpp +28 -12
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain/accelerated.pyx +352 -1
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain/downsample.py +11 -2
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/PKG-INFO +22 -8
- tinybrain-1.8.0/tinybrain.egg-info/pbr.json +1 -0
- tinybrain-1.6.0/.github/workflows/build_wheel.yml +0 -39
- tinybrain-1.6.0/tinybrain.egg-info/pbr.json +0 -1
- {tinybrain-1.6.0 → tinybrain-1.8.0}/AUTHORS +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/LICENSE +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/build_linux.sh +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/manylinux1.Dockerfile +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/manylinux2010.Dockerfile +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/manylinux2014.Dockerfile +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/pyproject.toml +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/requirements.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/requirements_dev.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/SOURCES.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/dependency_links.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/not-zip-safe +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/requires.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tinybrain.egg-info/top_level.txt +0 -0
- {tinybrain-1.6.0 → tinybrain-1.8.0}/tox.ini +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Build Wheels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- '*'
|
|
8
|
+
env:
|
|
9
|
+
CIBW_SKIP: pp* cp315t*
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build_wheels:
|
|
13
|
+
name: Build wheels on ${{matrix.arch}} for ${{ matrix.os }}
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
18
|
+
arch: [auto]
|
|
19
|
+
include:
|
|
20
|
+
- os: ubuntu-24.04-arm
|
|
21
|
+
arch: aarch64
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Build wheels
|
|
27
|
+
uses: joerick/cibuildwheel@v4.2.0
|
|
28
|
+
# to supply options, put them in 'env', like:
|
|
29
|
+
env:
|
|
30
|
+
CIBW_BEFORE_BUILD: pip install numpy pybind11 setuptools wheel pkginfo twine
|
|
31
|
+
CIBW_ARCHS_MACOS: "x86_64 arm64"
|
|
32
|
+
CIBW_ARCHS: auto64
|
|
33
|
+
|
|
34
|
+
- name: Upload built wheels
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: built-wheels-${{ matrix.os }}-${{ matrix.arch }}
|
|
38
|
+
path: ./wheelhouse/*.whl
|
|
39
|
+
if-no-files-found: warn
|
|
@@ -14,8 +14,8 @@ jobs:
|
|
|
14
14
|
runs-on: ${{ matrix.os }}
|
|
15
15
|
strategy:
|
|
16
16
|
matrix:
|
|
17
|
-
os: [ubuntu-
|
|
18
|
-
python-version: ["3.
|
|
17
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
18
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- name: Set up Python ${{ matrix.python-version }}
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
with:
|
|
24
24
|
python-version: ${{ matrix.python-version }}
|
|
25
25
|
|
|
26
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
27
|
|
|
28
28
|
- name: Install dependencies
|
|
29
29
|
run: |
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
CHANGES
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
1.8.0
|
|
5
|
+
-----
|
|
6
|
+
|
|
7
|
+
* ci: skip building cp315t
|
|
8
|
+
* release(1.8.0): support boolean images, faster 2x2x2 mode downsampling
|
|
9
|
+
* ci: update build
|
|
10
|
+
* fix: error and missing optimization
|
|
11
|
+
* test: add compliance test for end of 2x2x2
|
|
12
|
+
* docs: update performance numbers for 2x2x2 mode
|
|
13
|
+
* perf: faster 2x2x2 mode downsampling
|
|
14
|
+
* feat: support mode pooling for boolean images
|
|
15
|
+
* test: update benchmark with Macbook Pro M3 + add pillow, opencv
|
|
16
|
+
|
|
17
|
+
1.7.0
|
|
18
|
+
-----
|
|
19
|
+
|
|
20
|
+
* fix: warnings about type comparison
|
|
21
|
+
* install: bump to c++17
|
|
22
|
+
* feat: add int8, int16 to downsample\_with\_averaging (#23)
|
|
23
|
+
* chore: update python compatible versions to drop 3.8 (EOL)
|
|
24
|
+
* docs: remove travis badge
|
|
25
|
+
|
|
4
26
|
1.6.0
|
|
5
27
|
-----
|
|
6
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybrain
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Image pyramid generation specialized for connectomics data types and procedures.
|
|
5
5
|
Home-page: https://github.com/seung-lab/tinybrain/
|
|
6
6
|
Author: William Silversmith
|
|
@@ -9,19 +9,28 @@ Classifier: Intended Audience :: Developers
|
|
|
9
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
License-File: AUTHORS
|
|
22
22
|
Requires-Dist: numpy
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: author-email
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
[](https://badge.fury.io/py/tinybrain)
|
|
25
34
|
|
|
26
35
|
# tinybrain
|
|
27
36
|
|
|
@@ -37,7 +46,12 @@ img = load_3d_em_stack()
|
|
|
37
46
|
img_pyramid = tinybrain.downsample_with_averaging(img, factor=(2,2,1), num_mips=5, sparse=False)
|
|
38
47
|
|
|
39
48
|
labels = load_3d_labels()
|
|
40
|
-
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
49
|
+
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
50
|
+
|
|
51
|
+
# We also have a few other types
|
|
52
|
+
img_pyramid = tinybrain.downsample_with_min_pooling(image, factor=(2,2,1), num_mips=5)
|
|
53
|
+
img_pyramid = tinybrain.downsample_with_max_pooling(image, factor=(2,2,1), num_mips=5)
|
|
54
|
+
img_pyramid = tinybrain.downsample_with_striding(image, factor=(2,2,1), num_mips=5)
|
|
41
55
|
```
|
|
42
56
|
|
|
43
57
|
## Installation
|
|
@@ -50,7 +64,7 @@ pip install tinybrain
|
|
|
50
64
|
## Motivation
|
|
51
65
|
|
|
52
66
|
Image hierarchy generation in connectomics uses a few different techniques for
|
|
53
|
-
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to
|
|
67
|
+
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to visualize upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
|
|
54
68
|
|
|
55
69
|
It's possible to compute both of these using numpy, however as multiple packages found it useful to copy the downsample functions, it makes sense to formalize these functions into a seperate library located on PyPI.
|
|
56
70
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://badge.fury.io/py/tinybrain)
|
|
2
2
|
|
|
3
3
|
# tinybrain
|
|
4
4
|
|
|
@@ -14,7 +14,12 @@ img = load_3d_em_stack()
|
|
|
14
14
|
img_pyramid = tinybrain.downsample_with_averaging(img, factor=(2,2,1), num_mips=5, sparse=False)
|
|
15
15
|
|
|
16
16
|
labels = load_3d_labels()
|
|
17
|
-
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
17
|
+
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
18
|
+
|
|
19
|
+
# We also have a few other types
|
|
20
|
+
img_pyramid = tinybrain.downsample_with_min_pooling(image, factor=(2,2,1), num_mips=5)
|
|
21
|
+
img_pyramid = tinybrain.downsample_with_max_pooling(image, factor=(2,2,1), num_mips=5)
|
|
22
|
+
img_pyramid = tinybrain.downsample_with_striding(image, factor=(2,2,1), num_mips=5)
|
|
18
23
|
```
|
|
19
24
|
|
|
20
25
|
## Installation
|
|
@@ -27,7 +32,7 @@ pip install tinybrain
|
|
|
27
32
|
## Motivation
|
|
28
33
|
|
|
29
34
|
Image hierarchy generation in connectomics uses a few different techniques for
|
|
30
|
-
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to
|
|
35
|
+
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to visualize upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
|
|
31
36
|
|
|
32
37
|
It's possible to compute both of these using numpy, however as multiple packages found it useful to copy the downsample functions, it makes sense to formalize these functions into a seperate library located on PyPI.
|
|
33
38
|
|
|
@@ -125,7 +125,7 @@ def test_even_odd2d():
|
|
|
125
125
|
assert np.array_equal(oddimg, ans3x3x3)
|
|
126
126
|
assert np.array_equal(oddimgf, ans3x3x3)
|
|
127
127
|
|
|
128
|
-
@pytest.mark.parametrize("dtype", (np.uint8, np.uint16, np.float32, np.float64))
|
|
128
|
+
@pytest.mark.parametrize("dtype", (np.int8, np.int16, np.uint8, np.uint16, np.float32, np.float64))
|
|
129
129
|
@pytest.mark.parametrize("sparse", [False, True])
|
|
130
130
|
def test_accelerated_vs_numpy_avg_pooling_2x2x1(dtype, sparse):
|
|
131
131
|
image = np.random.randint(0,255, size=(512, 512, 6), dtype=np.uint8).astype(dtype, copy=False)
|
|
@@ -147,7 +147,7 @@ def test_accelerated_vs_numpy_avg_pooling_2x2x1(dtype, sparse):
|
|
|
147
147
|
|
|
148
148
|
assert np.all(mips[-1] == npimg)
|
|
149
149
|
|
|
150
|
-
@pytest.mark.parametrize("dtype", (np.uint8, np.uint16, np.float32, np.float64))
|
|
150
|
+
@pytest.mark.parametrize("dtype", (np.int8, np.int16, np.uint8, np.uint16, np.float32, np.float64))
|
|
151
151
|
def test_accelerated_vs_numpy_avg_pooling_2x2x1_simple_sparse(dtype):
|
|
152
152
|
for x in [0,1]:
|
|
153
153
|
for y in [0,1]:
|
|
@@ -189,7 +189,7 @@ def test_accelerated_vs_numpy_avg_pooling_2x2x1_simple_sparse(dtype):
|
|
|
189
189
|
assert np.all(res[1] == ans)
|
|
190
190
|
|
|
191
191
|
|
|
192
|
-
@pytest.mark.parametrize("dtype", (np.uint8, np.uint16, np.float32, np.float64))
|
|
192
|
+
@pytest.mark.parametrize("dtype", (np.int8, np.int16, np.uint8, np.uint16, np.float32, np.float64))
|
|
193
193
|
@pytest.mark.parametrize("sx", (6,7,1024,1025))
|
|
194
194
|
@pytest.mark.parametrize("sy", (6,7,1024,1025))
|
|
195
195
|
@pytest.mark.parametrize("sz", (4,5,32,33))
|
|
@@ -700,6 +700,21 @@ def test_sparse_2x2x2_mode_downsampling(dtype):
|
|
|
700
700
|
assert res[0][0][0] == 0
|
|
701
701
|
assert res.shape == (1,1,1)
|
|
702
702
|
|
|
703
|
+
test7 = np.array([
|
|
704
|
+
[
|
|
705
|
+
[1,2],
|
|
706
|
+
[3,4],
|
|
707
|
+
],
|
|
708
|
+
[
|
|
709
|
+
[5,6],
|
|
710
|
+
[7,8],
|
|
711
|
+
],
|
|
712
|
+
], dtype=dtype, order='F')
|
|
713
|
+
|
|
714
|
+
res = tinybrain.accelerated.mode_pooling_2x2x2(test7, sparse=True)[0]
|
|
715
|
+
assert res[0][0][0] == 1
|
|
716
|
+
assert res.shape == (1,1,1)
|
|
717
|
+
|
|
703
718
|
# test7 = np.array([
|
|
704
719
|
# [
|
|
705
720
|
# [1,0,1],
|
|
@@ -27,6 +27,26 @@
|
|
|
27
27
|
# 2x2x2 1 mip sparse: 0.656s, 2041.96 MVx/sec, N=10
|
|
28
28
|
# 2x2x2 2 mip sparse: 0.762s, 1758.42 MVx/sec, N=10
|
|
29
29
|
|
|
30
|
+
# Results on a 2023 Macbook Pro M3
|
|
31
|
+
# AVG POOLING ((2048, 2048, 64), uint8)
|
|
32
|
+
# 2x2 1 mip: 0.084s, 31954.51 MVx/sec, N=10
|
|
33
|
+
# 2x2 2 mip: 0.223s, 11995.13 MVx/sec, N=10
|
|
34
|
+
# 2x2x2 1 mip: 0.042s, 63958.66 MVx/sec, N=10
|
|
35
|
+
# 2x2x2 2 mip: 0.218s, 12303.19 MVx/sec, N=10
|
|
36
|
+
# 2x2x2 1 mip sparse: 0.581s, 4612.79 MVx/sec, N=10
|
|
37
|
+
# 2x2x2 2 mip sparse: 0.686s, 3908.00 MVx/sec, N=10
|
|
38
|
+
# 2x2 1 mip pillow: 10.526s, 254.61 MVx/sec, N=10
|
|
39
|
+
# 2x2 1 mip opencv: 5.352s, 500.79 MVx/sec, N=10
|
|
40
|
+
# MODE POOLING ((2048, 2048, 64), uint64)
|
|
41
|
+
# 2x2 1 mip: 1.030s, 2602.65 MVx/sec, N=10
|
|
42
|
+
# 2x2 2 mip: 1.181s, 2269.54 MVx/sec, N=10
|
|
43
|
+
# 2x2 1 mip sparse: 14.294s, 187.49 MVx/sec, N=10
|
|
44
|
+
# 2x2 2 mip sparse: 25.615s, 104.63 MVx/sec, N=10
|
|
45
|
+
# 2x2x2 1 mip: 3.171s, 845.10 MVx/sec, N=10
|
|
46
|
+
# 2x2x2 2 mip: 3.479s, 770.30 MVx/sec, N=10
|
|
47
|
+
# 2x2x2 1 mip sparse: 3.461s, 774.31 MVx/sec, N=10
|
|
48
|
+
# 2x2x2 2 mip sparse: 3.916s, 684.42 MVx/sec, N=10
|
|
49
|
+
|
|
30
50
|
import numpy as np
|
|
31
51
|
import tinybrain
|
|
32
52
|
import time
|
|
@@ -80,6 +100,33 @@ def benchmark_avg_pooling():
|
|
|
80
100
|
end = time.time()
|
|
81
101
|
result("2x2x2 2 mip sparse", end - start, data, N)
|
|
82
102
|
|
|
103
|
+
try:
|
|
104
|
+
from PIL import Image
|
|
105
|
+
s = time.time()
|
|
106
|
+
out = np.zeros([1024,1024,64], dtype=np.uint8, order="F")
|
|
107
|
+
for _ in range(N):
|
|
108
|
+
for z in range(data.shape[2]):
|
|
109
|
+
pilimg = Image.fromarray(data[:,:,z])
|
|
110
|
+
out[:,:,z] = pilimg.resize( (1024, 1024) )
|
|
111
|
+
end = time.time()
|
|
112
|
+
result("2x2 1 mip pillow", end - start, data, N)
|
|
113
|
+
del out
|
|
114
|
+
except ImportError:
|
|
115
|
+
print("Pillow not installed.")
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
import cv2
|
|
119
|
+
start = time.time()
|
|
120
|
+
out = np.zeros(shape=(1024,1024,64))
|
|
121
|
+
for _ in range(N):
|
|
122
|
+
for z in range(data.shape[2]):
|
|
123
|
+
out[:,:,z] = cv2.resize(data[:,:,z], dsize=(1024, 1024) )
|
|
124
|
+
end = time.time()
|
|
125
|
+
result("2x2 1 mip opencv", end - start, data, N)
|
|
126
|
+
del out
|
|
127
|
+
except ImportError:
|
|
128
|
+
print("opencv not installed.")
|
|
129
|
+
|
|
83
130
|
def benchmark_mode_pooling():
|
|
84
131
|
data = np.random.randint(1000,1255, size=(2048,2048,64), dtype=np.uint64)
|
|
85
132
|
data = np.asfortranarray(data)
|
|
@@ -13,11 +13,11 @@ classifier =
|
|
|
13
13
|
Development Status :: 5 - Production/Stable
|
|
14
14
|
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
15
|
Programming Language :: Python :: 3
|
|
16
|
-
Programming Language :: Python :: 3.8
|
|
17
16
|
Programming Language :: Python :: 3.9
|
|
18
17
|
Programming Language :: Python :: 3.10
|
|
19
18
|
Programming Language :: Python :: 3.11
|
|
20
19
|
Programming Language :: Python :: 3.12
|
|
20
|
+
Programming Language :: Python :: 3.13
|
|
21
21
|
Topic :: Scientific/Engineering
|
|
22
22
|
|
|
23
23
|
[global]
|
|
@@ -17,13 +17,13 @@ class NumpyImport:
|
|
|
17
17
|
extra_compile_args = []
|
|
18
18
|
if sys.platform == 'win32':
|
|
19
19
|
extra_compile_args += [
|
|
20
|
-
'/std:c++
|
|
20
|
+
'/std:c++17', '/O2'
|
|
21
21
|
]
|
|
22
22
|
if platform.machine() == "x86_64":
|
|
23
23
|
extra_compile_args += [ "/arch:SSE3" ]
|
|
24
24
|
else:
|
|
25
25
|
extra_compile_args += [
|
|
26
|
-
'-std=c++
|
|
26
|
+
'-std=c++17', '-O3' # '-DCYTHON_TRACE=1'
|
|
27
27
|
]
|
|
28
28
|
if platform.machine() == "x86_64":
|
|
29
29
|
extra_compile_args += [ '-msse3' ]
|
|
@@ -34,7 +34,7 @@ if sys.platform == 'darwin':
|
|
|
34
34
|
setuptools.setup(
|
|
35
35
|
setup_requires=['pbr', 'numpy','cython'],
|
|
36
36
|
install_requires=['numpy'],
|
|
37
|
-
python_requires=">=3.
|
|
37
|
+
python_requires=">=3.9",
|
|
38
38
|
ext_modules=[
|
|
39
39
|
setuptools.Extension(
|
|
40
40
|
'tinybrain.accelerated',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2019, William Silversmith
|
|
2
|
+
Copyright (C) 2019,2025 William Silversmith
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify
|
|
5
5
|
it under the terms of the GNU General Public License as published by
|
|
@@ -359,7 +359,7 @@ T* _average_pooling_2x2_single_mip(
|
|
|
359
359
|
+ static_cast<T2>(d)
|
|
360
360
|
) / static_cast<T2>(
|
|
361
361
|
std::max(
|
|
362
|
-
static_cast<T>((a
|
|
362
|
+
static_cast<T>((a != 0) + (b != 0) + (c != 0) + (d != 0)),
|
|
363
363
|
static_cast<T>(1)
|
|
364
364
|
)
|
|
365
365
|
)
|
|
@@ -375,7 +375,7 @@ T* _average_pooling_2x2_single_mip(
|
|
|
375
375
|
+ static_cast<T2>(b)
|
|
376
376
|
) / static_cast<T2>(
|
|
377
377
|
std::max(
|
|
378
|
-
static_cast<T>((a
|
|
378
|
+
static_cast<T>((a != 0) + (b != 0)),
|
|
379
379
|
static_cast<T>(1)
|
|
380
380
|
)
|
|
381
381
|
));
|
|
@@ -931,8 +931,17 @@ U* denominator_2x2x2(
|
|
|
931
931
|
|
|
932
932
|
template <typename T, typename U>
|
|
933
933
|
inline void render_image(T* accum, U* oimg, const uint32_t bitshift, const size_t ovoxels) {
|
|
934
|
-
|
|
935
|
-
|
|
934
|
+
if constexpr (std::is_signed<T>::value) {
|
|
935
|
+
for (size_t i = 0; i < ovoxels; i++) {
|
|
936
|
+
oimg[i] = (accum[i] < 0)
|
|
937
|
+
? -static_cast<U>(std::abs(accum[i]) >> bitshift)
|
|
938
|
+
: static_cast<U>(accum[i] >> bitshift);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
for (size_t i = 0; i < ovoxels; i++) {
|
|
943
|
+
oimg[i] = static_cast<U>(accum[i] >> bitshift);
|
|
944
|
+
}
|
|
936
945
|
}
|
|
937
946
|
}
|
|
938
947
|
|
|
@@ -1105,19 +1114,26 @@ inline void _mode_pooling_2x2x2(
|
|
|
1105
1114
|
continue;
|
|
1106
1115
|
}
|
|
1107
1116
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1117
|
+
if (sparse && vals[0] == 0) {
|
|
1118
|
+
max_ct = 0;
|
|
1119
|
+
max_val = 0;
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
max_ct = 1;
|
|
1123
|
+
max_val = vals[0];
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// no need to evaluate t=0, as if max_ct
|
|
1127
|
+
// is 1 at the last entry, it can't go higher than
|
|
1128
|
+
// 1, so we'll just take the last entry as good
|
|
1129
|
+
for (short int t = 7; t >= 1; t--) {
|
|
1111
1130
|
cur_val = vals[t];
|
|
1112
1131
|
if (sparse && cur_val == 0) {
|
|
1113
1132
|
continue;
|
|
1114
1133
|
}
|
|
1115
1134
|
|
|
1116
1135
|
cur_ct = 1;
|
|
1117
|
-
for (short int p =
|
|
1118
|
-
cur_ct += (cur_val == vals[p]);
|
|
1119
|
-
}
|
|
1120
|
-
for (short int p = t + 1; p < 8; p++) {
|
|
1136
|
+
for (short int p = t-1; p >= 0; p--) {
|
|
1121
1137
|
cur_ct += (cur_val == vals[p]);
|
|
1122
1138
|
}
|
|
1123
1139
|
|
|
@@ -4,7 +4,7 @@ Cython accelerated routines for common downsampling operations.
|
|
|
4
4
|
|
|
5
5
|
Author: William Silversmith
|
|
6
6
|
Affiliation: Seung Lab, Princeton Neuroscience Institute
|
|
7
|
-
Date: March 2019
|
|
7
|
+
Date: March 2019, Februrary 2025
|
|
8
8
|
"""
|
|
9
9
|
cimport cython
|
|
10
10
|
from cython cimport floating
|
|
@@ -94,8 +94,12 @@ def average_pooling_2x2(channel, size_t num_mips=1, sparse=False):
|
|
|
94
94
|
results = _average_pooling_2x2_single_mip_py(channel, sparse)
|
|
95
95
|
elif channel.dtype == np.uint8:
|
|
96
96
|
results = _average_pooling_2x2_uint8(channel, num_mips, sparse)
|
|
97
|
+
elif channel.dtype == np.int8:
|
|
98
|
+
results = _average_pooling_2x2_int8(channel, num_mips, sparse)
|
|
97
99
|
elif channel.dtype == np.uint16:
|
|
98
100
|
results = _average_pooling_2x2_uint16(channel, num_mips, sparse)
|
|
101
|
+
elif channel.dtype == np.int16:
|
|
102
|
+
results = _average_pooling_2x2_int16(channel, num_mips, sparse)
|
|
99
103
|
elif channel.dtype == np.float32:
|
|
100
104
|
results = _average_pooling_2x2_float(channel, num_mips, sparse)
|
|
101
105
|
elif channel.dtype == np.float64:
|
|
@@ -109,17 +113,29 @@ def average_pooling_2x2(channel, size_t num_mips=1, sparse=False):
|
|
|
109
113
|
return results
|
|
110
114
|
|
|
111
115
|
def _average_pooling_2x2_single_mip_py(np.ndarray[NUMBER, ndim=5] channel, sparse):
|
|
116
|
+
cdef int8_t[:,:,:,:,:] arr_memview8i
|
|
117
|
+
cdef int16_t[:,:,:,:,:] arr_memview16i
|
|
118
|
+
cdef int32_t[:,:,:,:,:] arr_memview32i
|
|
119
|
+
cdef int64_t[:,:,:,:,:] arr_memview64i
|
|
120
|
+
|
|
112
121
|
cdef uint8_t[:,:,:,:,:] arr_memview8u
|
|
113
122
|
cdef uint16_t[:,:,:,:,:] arr_memview16u
|
|
114
123
|
cdef uint32_t[:,:,:,:,:] arr_memview32u
|
|
115
124
|
cdef uint64_t[:,:,:,:,:] arr_memview64u
|
|
125
|
+
|
|
116
126
|
cdef float[:,:,:,:,:] arr_memviewf
|
|
117
127
|
cdef double[:,:,:,:,:] arr_memviewd
|
|
118
128
|
|
|
129
|
+
cdef int8_t[:,:,:,:,:] out_memview8i
|
|
130
|
+
cdef int16_t[:,:,:,:,:] out_memview16i
|
|
131
|
+
cdef int32_t[:,:,:,:,:] out_memview32i
|
|
132
|
+
cdef int64_t[:,:,:,:,:] out_memview64i
|
|
133
|
+
|
|
119
134
|
cdef uint8_t[:,:,:,:,:] out_memview8u
|
|
120
135
|
cdef uint16_t[:,:,:,:,:] out_memview16u
|
|
121
136
|
cdef uint32_t[:,:,:,:,:] out_memview32u
|
|
122
137
|
cdef uint64_t[:,:,:,:,:] out_memview64u
|
|
138
|
+
|
|
123
139
|
cdef float[:,:,:,:,:] out_memviewf
|
|
124
140
|
cdef double[:,:,:,:,:] out_memviewd
|
|
125
141
|
|
|
@@ -149,6 +165,22 @@ def _average_pooling_2x2_single_mip_py(np.ndarray[NUMBER, ndim=5] channel, spars
|
|
|
149
165
|
arr_memview64u = channel
|
|
150
166
|
out_memview64u = out
|
|
151
167
|
_average_pooling_2x2_single_mip[uint64_t](&arr_memview64u[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview64u[0,0,0,0,0], bool(sparse))
|
|
168
|
+
elif channel.dtype == np.int8:
|
|
169
|
+
arr_memview8i = channel
|
|
170
|
+
out_memview8i = out
|
|
171
|
+
_average_pooling_2x2_single_mip[int8_t](&arr_memview8i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview8i[0,0,0,0,0], bool(sparse))
|
|
172
|
+
elif channel.dtype == np.int16:
|
|
173
|
+
arr_memview16i = channel
|
|
174
|
+
out_memview16i = out
|
|
175
|
+
_average_pooling_2x2_single_mip[int16_t](&arr_memview16i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview16i[0,0,0,0,0], bool(sparse))
|
|
176
|
+
elif channel.dtype == np.int32:
|
|
177
|
+
arr_memview32i = channel
|
|
178
|
+
out_memview32i = out
|
|
179
|
+
_average_pooling_2x2_single_mip[int32_t](&arr_memview32i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview32i[0,0,0,0,0], bool(sparse))
|
|
180
|
+
elif channel.dtype == np.int64:
|
|
181
|
+
arr_memview64i = channel
|
|
182
|
+
out_memview64i = out
|
|
183
|
+
_average_pooling_2x2_single_mip[int64_t](&arr_memview64i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview64i[0,0,0,0,0], bool(sparse))
|
|
152
184
|
elif channel.dtype == np.float32:
|
|
153
185
|
arr_memviewf = channel
|
|
154
186
|
out_memviewf = out
|
|
@@ -230,6 +262,142 @@ def _average_pooling_2x2_uint8(np.ndarray[uint8_t, ndim=5] channel, uint32_t num
|
|
|
230
262
|
|
|
231
263
|
return results
|
|
232
264
|
|
|
265
|
+
def _average_pooling_2x2_int8(np.ndarray[int8_t, ndim=5] channel, uint32_t num_mips, sparse):
|
|
266
|
+
cdef size_t sx = channel.shape[0]
|
|
267
|
+
cdef size_t sy = channel.shape[1]
|
|
268
|
+
cdef size_t sz = channel.shape[2]
|
|
269
|
+
cdef size_t sw = channel.shape[3]
|
|
270
|
+
cdef size_t sv = channel.shape[4]
|
|
271
|
+
cdef size_t sxy = sx * sy
|
|
272
|
+
|
|
273
|
+
cdef size_t osx = (sx + 1) // 2
|
|
274
|
+
cdef size_t osy = (sy + 1) // 2
|
|
275
|
+
cdef size_t osxy = osx * osy
|
|
276
|
+
cdef size_t ovoxels = osxy * sz * sw * sv
|
|
277
|
+
|
|
278
|
+
cdef int8_t[:,:,:,:,:] channelview = channel
|
|
279
|
+
cdef int16_t* accum = accumulate_2x2[int8_t, int16_t](&channelview[0,0,0,0,0], sx, sy, sz, sw, sv)
|
|
280
|
+
cdef int16_t[:] accumview = <int16_t[:ovoxels]>accum
|
|
281
|
+
cdef int16_t* tmp
|
|
282
|
+
cdef uint32_t mip, bitshift
|
|
283
|
+
|
|
284
|
+
cdef int16_t* denominator
|
|
285
|
+
if sparse:
|
|
286
|
+
denominator = denominator_2x2[int8_t, int16_t](&channelview[0,0,0,0,0], sx, sy, sz, sw, sv)
|
|
287
|
+
|
|
288
|
+
cdef int8_t[:] oimgview
|
|
289
|
+
|
|
290
|
+
results = []
|
|
291
|
+
for mip in range(num_mips):
|
|
292
|
+
bitshift = 2 * ((mip % 4) + 1) # integer truncation every 4 mip levels
|
|
293
|
+
oimg = np.zeros( (ovoxels,), dtype=np.int8, order='F')
|
|
294
|
+
oimgview = oimg
|
|
295
|
+
|
|
296
|
+
if sparse:
|
|
297
|
+
render_image_sparse[int16_t, int8_t](&accumview[0], denominator, &oimgview[0], ovoxels)
|
|
298
|
+
else:
|
|
299
|
+
render_image[int16_t, int8_t](&accumview[0], &oimgview[0], bitshift, ovoxels)
|
|
300
|
+
|
|
301
|
+
results.append(
|
|
302
|
+
oimg.reshape( (osx, osy, sz, sw, sv), order='F' )
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if mip == num_mips - 1:
|
|
306
|
+
break
|
|
307
|
+
|
|
308
|
+
if bitshift == 8:
|
|
309
|
+
shift_right[int16_t](accum, ovoxels, bitshift)
|
|
310
|
+
|
|
311
|
+
sx = osx
|
|
312
|
+
sy = osy
|
|
313
|
+
sxy = sx * sy
|
|
314
|
+
osx = (sx + 1) // 2
|
|
315
|
+
osy = (sy + 1) // 2
|
|
316
|
+
osxy = osx * osy
|
|
317
|
+
ovoxels = osxy * sz * sw * sv
|
|
318
|
+
|
|
319
|
+
tmp = accum
|
|
320
|
+
accum = accumulate_2x2[int16_t, int16_t](accum, sx, sy, sz, sw, sv)
|
|
321
|
+
accumview = <int16_t[:ovoxels]>accum
|
|
322
|
+
PyMem_Free(tmp)
|
|
323
|
+
|
|
324
|
+
if sparse:
|
|
325
|
+
tmp = denominator
|
|
326
|
+
denominator = accumulate_2x2[int16_t, int16_t](denominator, sx, sy, sz, sw, sv)
|
|
327
|
+
PyMem_Free(tmp)
|
|
328
|
+
|
|
329
|
+
PyMem_Free(accum)
|
|
330
|
+
|
|
331
|
+
return results
|
|
332
|
+
|
|
333
|
+
def _average_pooling_2x2_int16(np.ndarray[int16_t, ndim=5] channel, uint32_t num_mips, sparse):
|
|
334
|
+
cdef size_t sx = channel.shape[0]
|
|
335
|
+
cdef size_t sy = channel.shape[1]
|
|
336
|
+
cdef size_t sz = channel.shape[2]
|
|
337
|
+
cdef size_t sw = channel.shape[3]
|
|
338
|
+
cdef size_t sv = channel.shape[4]
|
|
339
|
+
cdef size_t sxy = sx * sy
|
|
340
|
+
|
|
341
|
+
cdef size_t osx = (sx + 1) // 2
|
|
342
|
+
cdef size_t osy = (sy + 1) // 2
|
|
343
|
+
cdef size_t osxy = osx * osy
|
|
344
|
+
cdef size_t ovoxels = osxy * sz * sw * sv
|
|
345
|
+
|
|
346
|
+
cdef int16_t[:,:,:,:,:] channelview = channel
|
|
347
|
+
cdef int32_t* accum = accumulate_2x2[int16_t, int32_t](&channelview[0,0,0,0,0], sx, sy, sz, sw, sv)
|
|
348
|
+
cdef int32_t[:] accumview = <int32_t[:ovoxels]>accum
|
|
349
|
+
cdef int32_t* tmp
|
|
350
|
+
cdef uint32_t mip, bitshift
|
|
351
|
+
|
|
352
|
+
cdef int32_t* denominator
|
|
353
|
+
if sparse:
|
|
354
|
+
denominator = denominator_2x2[int16_t, int32_t](&channelview[0,0,0,0,0], sx, sy, sz, sw, sv)
|
|
355
|
+
|
|
356
|
+
cdef int16_t[:] oimgview
|
|
357
|
+
|
|
358
|
+
results = []
|
|
359
|
+
for mip in range(num_mips):
|
|
360
|
+
bitshift = 2 * ((mip % 4) + 1) # integer truncation every 4 mip levels
|
|
361
|
+
oimg = np.zeros( (ovoxels,), dtype=np.int16, order='F')
|
|
362
|
+
oimgview = oimg
|
|
363
|
+
|
|
364
|
+
if sparse:
|
|
365
|
+
render_image_sparse[int32_t, int16_t](&accumview[0], denominator, &oimgview[0], ovoxels)
|
|
366
|
+
else:
|
|
367
|
+
render_image[int32_t, int16_t](&accumview[0], &oimgview[0], bitshift, ovoxels)
|
|
368
|
+
|
|
369
|
+
results.append(
|
|
370
|
+
oimg.reshape( (osx, osy, sz, sw, sv), order='F' )
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if mip == num_mips - 1:
|
|
374
|
+
break
|
|
375
|
+
|
|
376
|
+
if bitshift == 8:
|
|
377
|
+
shift_right[int32_t](accum, ovoxels, bitshift)
|
|
378
|
+
|
|
379
|
+
sx = osx
|
|
380
|
+
sy = osy
|
|
381
|
+
sxy = sx * sy
|
|
382
|
+
osx = (sx + 1) // 2
|
|
383
|
+
osy = (sy + 1) // 2
|
|
384
|
+
osxy = osx * osy
|
|
385
|
+
ovoxels = osxy * sz * sw * sv
|
|
386
|
+
|
|
387
|
+
tmp = accum
|
|
388
|
+
accum = accumulate_2x2[int32_t, int32_t](accum, sx, sy, sz, sw, sv)
|
|
389
|
+
accumview = <int32_t[:ovoxels]>accum
|
|
390
|
+
PyMem_Free(tmp)
|
|
391
|
+
|
|
392
|
+
if sparse:
|
|
393
|
+
tmp = denominator
|
|
394
|
+
denominator = accumulate_2x2[int32_t, int32_t](denominator, sx, sy, sz, sw, sv)
|
|
395
|
+
PyMem_Free(tmp)
|
|
396
|
+
|
|
397
|
+
PyMem_Free(accum)
|
|
398
|
+
|
|
399
|
+
return results
|
|
400
|
+
|
|
233
401
|
def _average_pooling_2x2_uint16(np.ndarray[uint16_t, ndim=5] channel, uint32_t num_mips, sparse):
|
|
234
402
|
cdef size_t sx = channel.shape[0]
|
|
235
403
|
cdef size_t sy = channel.shape[1]
|
|
@@ -451,8 +619,12 @@ def average_pooling_2x2x2(channel, size_t num_mips=1, sparse=False):
|
|
|
451
619
|
results = _average_pooling_2x2x2_single_mip_py(channel)
|
|
452
620
|
elif channel.dtype == np.uint8:
|
|
453
621
|
results = _average_pooling_2x2x2_uint8(channel, num_mips, sparse)
|
|
622
|
+
elif channel.dtype == np.int8:
|
|
623
|
+
results = _average_pooling_2x2x2_int8(channel, num_mips, sparse)
|
|
454
624
|
elif channel.dtype == np.uint16:
|
|
455
625
|
results = _average_pooling_2x2x2_uint16(channel, num_mips, sparse)
|
|
626
|
+
elif channel.dtype == np.int16:
|
|
627
|
+
results = _average_pooling_2x2x2_int16(channel, num_mips, sparse)
|
|
456
628
|
elif channel.dtype == np.float32:
|
|
457
629
|
results = _average_pooling_2x2x2_float(channel, num_mips)
|
|
458
630
|
elif channel.dtype == np.float64:
|
|
@@ -466,6 +638,8 @@ def average_pooling_2x2x2(channel, size_t num_mips=1, sparse=False):
|
|
|
466
638
|
return results
|
|
467
639
|
|
|
468
640
|
def _average_pooling_2x2x2_single_mip_py(np.ndarray[NUMBER, ndim=5] channel):
|
|
641
|
+
cdef int8_t[:,:,:,:,:] arr_memview8i
|
|
642
|
+
cdef int16_t[:,:,:,:,:] arr_memview16i
|
|
469
643
|
cdef uint8_t[:,:,:,:,:] arr_memview8u
|
|
470
644
|
cdef uint16_t[:,:,:,:,:] arr_memview16u
|
|
471
645
|
cdef uint32_t[:,:,:,:,:] arr_memview32u
|
|
@@ -473,6 +647,8 @@ def _average_pooling_2x2x2_single_mip_py(np.ndarray[NUMBER, ndim=5] channel):
|
|
|
473
647
|
cdef float[:,:,:,:,:] arr_memviewf
|
|
474
648
|
cdef double[:,:,:,:,:] arr_memviewd
|
|
475
649
|
|
|
650
|
+
cdef int8_t[:,:,:,:,:] out_memview8i
|
|
651
|
+
cdef int16_t[:,:,:,:,:] out_memview16i
|
|
476
652
|
cdef uint8_t[:,:,:,:,:] out_memview8u
|
|
477
653
|
cdef uint16_t[:,:,:,:,:] out_memview16u
|
|
478
654
|
cdef uint32_t[:,:,:,:,:] out_memview32u
|
|
@@ -494,10 +670,18 @@ def _average_pooling_2x2x2_single_mip_py(np.ndarray[NUMBER, ndim=5] channel):
|
|
|
494
670
|
arr_memview8u = channel
|
|
495
671
|
out_memview8u = out
|
|
496
672
|
_average_pooling_2x2x2_single_mip[uint8_t](&arr_memview8u[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview8u[0,0,0,0,0])
|
|
673
|
+
elif channel.dtype == np.int8:
|
|
674
|
+
arr_memview8i = channel
|
|
675
|
+
out_memview8i = out
|
|
676
|
+
_average_pooling_2x2x2_single_mip[int8_t](&arr_memview8i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview8i[0,0,0,0,0])
|
|
497
677
|
elif channel.dtype == np.uint16:
|
|
498
678
|
arr_memview16u = channel
|
|
499
679
|
out_memview16u = out
|
|
500
680
|
_average_pooling_2x2x2_single_mip[uint16_t](&arr_memview16u[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview16u[0,0,0,0,0])
|
|
681
|
+
elif channel.dtype == np.int16:
|
|
682
|
+
arr_memview16i = channel
|
|
683
|
+
out_memview16i = out
|
|
684
|
+
_average_pooling_2x2x2_single_mip[int16_t](&arr_memview16i[0,0,0,0,0], sx, sy, sz, sw, sv, &out_memview16i[0,0,0,0,0])
|
|
501
685
|
elif channel.dtype == np.uint32:
|
|
502
686
|
arr_memview32u = channel
|
|
503
687
|
out_memview32u = out
|
|
@@ -603,6 +787,90 @@ def _average_pooling_2x2x2_uint8(np.ndarray[uint8_t, ndim=5] channel, uint32_t n
|
|
|
603
787
|
|
|
604
788
|
return results
|
|
605
789
|
|
|
790
|
+
def _average_pooling_2x2x2_int8(np.ndarray[int8_t, ndim=5] channel, uint32_t num_mips, sparse=False):
|
|
791
|
+
cdef size_t sx = channel.shape[0]
|
|
792
|
+
cdef size_t sy = channel.shape[1]
|
|
793
|
+
cdef size_t sz = channel.shape[2]
|
|
794
|
+
cdef size_t sw = channel.shape[3]
|
|
795
|
+
cdef size_t sv = channel.shape[4]
|
|
796
|
+
cdef size_t sxy = sx * sy
|
|
797
|
+
|
|
798
|
+
cdef size_t osx = (sx + 1) // 2
|
|
799
|
+
cdef size_t osy = (sy + 1) // 2
|
|
800
|
+
cdef size_t osz = (sz + 1) // 2
|
|
801
|
+
cdef size_t osxy = osx * osy
|
|
802
|
+
cdef size_t ovoxels = osxy * osz * sw * sv
|
|
803
|
+
|
|
804
|
+
cdef int8_t[:,:,:,:,:] channelview = channel
|
|
805
|
+
cdef int32_t* accum = accumulate_2x2x2[int8_t, int32_t](
|
|
806
|
+
&channelview[0,0,0,0,0], sx, sy, sz, sw, sv
|
|
807
|
+
)
|
|
808
|
+
cdef int32_t[:] accumview = <int32_t[:ovoxels]>accum
|
|
809
|
+
|
|
810
|
+
# "denominator"
|
|
811
|
+
cdef int32_t* denom
|
|
812
|
+
cdef int32_t[:] denomview
|
|
813
|
+
if sparse:
|
|
814
|
+
denom = denominator_2x2x2[int8_t, int32_t](
|
|
815
|
+
&channelview[0,0,0,0,0], sx, sy, sz, sw, sv
|
|
816
|
+
)
|
|
817
|
+
denomview = <int32_t[:ovoxels]>denom
|
|
818
|
+
|
|
819
|
+
cdef int32_t* tmp
|
|
820
|
+
cdef uint32_t mip, bitshift
|
|
821
|
+
|
|
822
|
+
cdef int8_t[:] oimgview
|
|
823
|
+
|
|
824
|
+
results = []
|
|
825
|
+
for mip in range(num_mips):
|
|
826
|
+
bitshift = 3 * ((mip % 8) + 1) # integer truncation every 8 mip levels
|
|
827
|
+
oimg = np.zeros( (ovoxels,), dtype=np.int8, order='F')
|
|
828
|
+
oimgview = oimg
|
|
829
|
+
|
|
830
|
+
if sparse:
|
|
831
|
+
render_image_sparse[int32_t, int8_t](&accumview[0], &denomview[0], &oimgview[0], ovoxels)
|
|
832
|
+
else:
|
|
833
|
+
render_image[int32_t, int8_t](&accumview[0], &oimgview[0], bitshift, ovoxels)
|
|
834
|
+
|
|
835
|
+
results.append(
|
|
836
|
+
oimg.reshape( (osx, osy, osz, sw), order='F' )
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
if mip == num_mips - 1:
|
|
840
|
+
break
|
|
841
|
+
|
|
842
|
+
if bitshift == 24:
|
|
843
|
+
shift_right[int32_t](accum, ovoxels, bitshift)
|
|
844
|
+
if sparse:
|
|
845
|
+
shift_right[int32_t](denom, ovoxels, bitshift)
|
|
846
|
+
|
|
847
|
+
sx = osx
|
|
848
|
+
sy = osy
|
|
849
|
+
sz = osz
|
|
850
|
+
sxy = sx * sy
|
|
851
|
+
osx = (sx + 1) // 2
|
|
852
|
+
osy = (sy + 1) // 2
|
|
853
|
+
osz = (sz + 1) // 2
|
|
854
|
+
osxy = osx * osy
|
|
855
|
+
ovoxels = osxy * osz * sw * sv
|
|
856
|
+
|
|
857
|
+
tmp = accum
|
|
858
|
+
accum = accumulate_2x2x2[int32_t, int32_t](accum, sx, sy, sz, sw, sv)
|
|
859
|
+
accumview = <int32_t[:ovoxels]>accum
|
|
860
|
+
PyMem_Free(tmp)
|
|
861
|
+
|
|
862
|
+
if sparse:
|
|
863
|
+
tmp = denom
|
|
864
|
+
denom = accumulate_2x2x2[int32_t, int32_t](denom, sx, sy, sz, sw, sv)
|
|
865
|
+
denomview = <int32_t[:ovoxels]>denom
|
|
866
|
+
PyMem_Free(tmp)
|
|
867
|
+
|
|
868
|
+
PyMem_Free(accum)
|
|
869
|
+
if sparse:
|
|
870
|
+
PyMem_Free(denom)
|
|
871
|
+
|
|
872
|
+
return results
|
|
873
|
+
|
|
606
874
|
def _average_pooling_2x2x2_uint16(np.ndarray[uint16_t, ndim=5] channel, uint32_t num_mips, sparse):
|
|
607
875
|
cdef size_t sx = channel.shape[0]
|
|
608
876
|
cdef size_t sy = channel.shape[1]
|
|
@@ -686,6 +954,89 @@ def _average_pooling_2x2x2_uint16(np.ndarray[uint16_t, ndim=5] channel, uint32_t
|
|
|
686
954
|
|
|
687
955
|
return results
|
|
688
956
|
|
|
957
|
+
def _average_pooling_2x2x2_int16(np.ndarray[int16_t, ndim=5] channel, uint32_t num_mips, sparse):
|
|
958
|
+
cdef size_t sx = channel.shape[0]
|
|
959
|
+
cdef size_t sy = channel.shape[1]
|
|
960
|
+
cdef size_t sz = channel.shape[2]
|
|
961
|
+
cdef size_t sw = channel.shape[3]
|
|
962
|
+
cdef size_t sv = channel.shape[4]
|
|
963
|
+
cdef size_t sxy = sx * sy
|
|
964
|
+
|
|
965
|
+
cdef size_t osx = (sx + 1) // 2
|
|
966
|
+
cdef size_t osy = (sy + 1) // 2
|
|
967
|
+
cdef size_t osz = (sz + 1) // 2
|
|
968
|
+
cdef size_t osxy = osx * osy
|
|
969
|
+
cdef size_t ovoxels = osxy * osz * sw * sv
|
|
970
|
+
|
|
971
|
+
cdef int16_t[:,:,:,:,:] channelview = channel
|
|
972
|
+
cdef int32_t* accum = accumulate_2x2x2[int16_t, int32_t](
|
|
973
|
+
&channelview[0,0,0,0,0], sx, sy, sz, sw, sv
|
|
974
|
+
)
|
|
975
|
+
cdef int32_t[:] accumview = <int32_t[:ovoxels]>accum
|
|
976
|
+
|
|
977
|
+
# "denominator"
|
|
978
|
+
cdef int32_t* denom
|
|
979
|
+
cdef int32_t[:] denomview
|
|
980
|
+
if sparse:
|
|
981
|
+
denom = denominator_2x2x2[int16_t, int32_t](
|
|
982
|
+
&channelview[0,0,0,0,0], sx, sy, sz, sw, sv
|
|
983
|
+
)
|
|
984
|
+
denomview = <int32_t[:ovoxels]>denom
|
|
985
|
+
|
|
986
|
+
cdef int32_t* tmp
|
|
987
|
+
cdef uint32_t mip, bitshift
|
|
988
|
+
|
|
989
|
+
cdef int16_t[:] oimgview
|
|
990
|
+
|
|
991
|
+
results = []
|
|
992
|
+
for mip in range(num_mips):
|
|
993
|
+
bitshift = 3 * ((mip % 5) + 1) # integer truncation every 5 mip levels
|
|
994
|
+
oimg = np.zeros( (ovoxels,), dtype=np.uint16, order='F')
|
|
995
|
+
oimgview = oimg
|
|
996
|
+
if sparse:
|
|
997
|
+
render_image_sparse[int32_t, int16_t](&accumview[0], &denomview[0], &oimgview[0], ovoxels)
|
|
998
|
+
else:
|
|
999
|
+
render_image[int32_t, int16_t](&accumview[0], &oimgview[0], bitshift, ovoxels)
|
|
1000
|
+
|
|
1001
|
+
results.append(
|
|
1002
|
+
oimg.reshape( (osx, osy, osz, sw, sv), order='F' )
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
if mip == num_mips - 1:
|
|
1006
|
+
break
|
|
1007
|
+
|
|
1008
|
+
if bitshift == 15:
|
|
1009
|
+
shift_right[int32_t](accum, ovoxels, bitshift)
|
|
1010
|
+
if sparse:
|
|
1011
|
+
shift_right[int32_t](denom, ovoxels, bitshift)
|
|
1012
|
+
|
|
1013
|
+
sx = osx
|
|
1014
|
+
sy = osy
|
|
1015
|
+
sz = osz
|
|
1016
|
+
sxy = sx * sy
|
|
1017
|
+
osx = (sx + 1) // 2
|
|
1018
|
+
osy = (sy + 1) // 2
|
|
1019
|
+
osz = (sz + 1) // 2
|
|
1020
|
+
osxy = osx * osy
|
|
1021
|
+
ovoxels = osxy * osz * sw * sv
|
|
1022
|
+
|
|
1023
|
+
tmp = accum
|
|
1024
|
+
accum = accumulate_2x2x2[int32_t, int32_t](accum, sx, sy, sz, sw, sv)
|
|
1025
|
+
accumview = <int32_t[:ovoxels]>accum
|
|
1026
|
+
PyMem_Free(tmp)
|
|
1027
|
+
|
|
1028
|
+
if sparse:
|
|
1029
|
+
tmp = denom
|
|
1030
|
+
denom = accumulate_2x2x2[int32_t, int32_t](denom, sx, sy, sz, sw, sv)
|
|
1031
|
+
denomview = <int32_t[:ovoxels]>denom
|
|
1032
|
+
PyMem_Free(tmp)
|
|
1033
|
+
|
|
1034
|
+
PyMem_Free(accum)
|
|
1035
|
+
if sparse:
|
|
1036
|
+
PyMem_Free(denom)
|
|
1037
|
+
|
|
1038
|
+
return results
|
|
1039
|
+
|
|
689
1040
|
def _average_pooling_2x2x2_float(np.ndarray[float, ndim=5] channel, uint32_t num_mips):
|
|
690
1041
|
cdef size_t sx = channel.shape[0]
|
|
691
1042
|
cdef size_t sy = channel.shape[1]
|
|
@@ -45,7 +45,7 @@ def downsample_with_averaging(img, factor, num_mips=1, sparse=False):
|
|
|
45
45
|
Returns: [ mip0, mip1, mip2, ..., num_mip ]
|
|
46
46
|
"""
|
|
47
47
|
if (
|
|
48
|
-
img.dtype in (np.uint8, np.uint16, np.float32, np.float64)
|
|
48
|
+
img.dtype in (np.int8, np.int16, np.uint8, np.uint16, np.float32, np.float64)
|
|
49
49
|
or num_mips == 1 # _average_pooling_2x2_single_mip_py supports all primative types
|
|
50
50
|
):
|
|
51
51
|
img = np.asfortranarray(img)
|
|
@@ -136,7 +136,7 @@ def downsample_with_averaging_numpy(array, factor, sparse=False):
|
|
|
136
136
|
indexing_expr = tuple(np.s_[:s] for s in part.shape)
|
|
137
137
|
temp[indexing_expr] += part
|
|
138
138
|
if sparse:
|
|
139
|
-
counts[indexing_expr] += part
|
|
139
|
+
counts[indexing_expr] += part != 0
|
|
140
140
|
else:
|
|
141
141
|
counts[indexing_expr] += 1
|
|
142
142
|
|
|
@@ -230,6 +230,11 @@ def downsample_segmentation(img, factor, sparse=False, num_mips=1):
|
|
|
230
230
|
if np.all(np.array(factor, int) == 1):
|
|
231
231
|
return [ img ] * num_mips
|
|
232
232
|
|
|
233
|
+
binary_image = isinstance(img, (bool, np.bool_))
|
|
234
|
+
|
|
235
|
+
if binary_image:
|
|
236
|
+
img = img.view(np.uint8)
|
|
237
|
+
|
|
233
238
|
if tuple(factor) in ( (2,2), (2,2,1), (2,2,1,1) ) and not sparse:
|
|
234
239
|
return tinybrain.accelerated.mode_pooling_2x2(img, num_mips=num_mips)
|
|
235
240
|
elif tuple(factor) in ( (2,2,2), (2,2,2,1) ):
|
|
@@ -244,6 +249,10 @@ def downsample_segmentation(img, factor, sparse=False, num_mips=1):
|
|
|
244
249
|
results.append(img)
|
|
245
250
|
|
|
246
251
|
results = [ squeeze_dims(img, ndim) for img in results ]
|
|
252
|
+
|
|
253
|
+
if binary_image:
|
|
254
|
+
results = [ mip_img.view(bool) for mip_img in results ]
|
|
255
|
+
|
|
247
256
|
return results
|
|
248
257
|
|
|
249
258
|
def _downsample_segmentation(data, factor, sparse=False):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybrain
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Image pyramid generation specialized for connectomics data types and procedures.
|
|
5
5
|
Home-page: https://github.com/seung-lab/tinybrain/
|
|
6
6
|
Author: William Silversmith
|
|
@@ -9,19 +9,28 @@ Classifier: Intended Audience :: Developers
|
|
|
9
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
License-File: AUTHORS
|
|
22
22
|
Requires-Dist: numpy
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: author-email
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
[](https://badge.fury.io/py/tinybrain)
|
|
25
34
|
|
|
26
35
|
# tinybrain
|
|
27
36
|
|
|
@@ -37,7 +46,12 @@ img = load_3d_em_stack()
|
|
|
37
46
|
img_pyramid = tinybrain.downsample_with_averaging(img, factor=(2,2,1), num_mips=5, sparse=False)
|
|
38
47
|
|
|
39
48
|
labels = load_3d_labels()
|
|
40
|
-
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
49
|
+
label_pyramid = tinybrain.downsample_segmentation(labels, factor=(2,2,1), num_mips=5, sparse=False)
|
|
50
|
+
|
|
51
|
+
# We also have a few other types
|
|
52
|
+
img_pyramid = tinybrain.downsample_with_min_pooling(image, factor=(2,2,1), num_mips=5)
|
|
53
|
+
img_pyramid = tinybrain.downsample_with_max_pooling(image, factor=(2,2,1), num_mips=5)
|
|
54
|
+
img_pyramid = tinybrain.downsample_with_striding(image, factor=(2,2,1), num_mips=5)
|
|
41
55
|
```
|
|
42
56
|
|
|
43
57
|
## Installation
|
|
@@ -50,7 +64,7 @@ pip install tinybrain
|
|
|
50
64
|
## Motivation
|
|
51
65
|
|
|
52
66
|
Image hierarchy generation in connectomics uses a few different techniques for
|
|
53
|
-
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to
|
|
67
|
+
visualizing data, but predominantly we create image pyramids of uint8 grayscale images using 2x2 average pooling and of uint8 to uint64 segmentation labels using 2x2 mode pooling. When images become very large and people wish to visualize upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
|
|
54
68
|
|
|
55
69
|
It's possible to compute both of these using numpy, however as multiple packages found it useful to copy the downsample functions, it makes sense to formalize these functions into a seperate library located on PyPI.
|
|
56
70
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"git_version": "c656201", "is_release": true}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
name: Build Wheels
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
push:
|
|
6
|
-
tags:
|
|
7
|
-
- '*'
|
|
8
|
-
env:
|
|
9
|
-
CIBW_SKIP: pp* *-musllinux*
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build_wheels:
|
|
13
|
-
name: Build wheels on ${{matrix.arch}} for ${{ matrix.os }}
|
|
14
|
-
runs-on: ${{ matrix.os }}
|
|
15
|
-
strategy:
|
|
16
|
-
matrix:
|
|
17
|
-
os: [ubuntu-latest, windows-2019, macos-latest]
|
|
18
|
-
arch: [auto]
|
|
19
|
-
include:
|
|
20
|
-
- os: ubuntu-latest
|
|
21
|
-
arch: aarch64
|
|
22
|
-
|
|
23
|
-
steps:
|
|
24
|
-
- uses: actions/checkout@v4
|
|
25
|
-
|
|
26
|
-
- name: Set up QEMU
|
|
27
|
-
if: ${{ matrix.arch == 'aarch64' }}
|
|
28
|
-
uses: docker/setup-qemu-action@v1
|
|
29
|
-
|
|
30
|
-
- name: Build wheels
|
|
31
|
-
uses: pypa/cibuildwheel@v2.19.1
|
|
32
|
-
# to supply options, put them in 'env', like:
|
|
33
|
-
env:
|
|
34
|
-
CIBW_ARCHS_LINUX: ${{matrix.arch}}
|
|
35
|
-
CIBW_BEFORE_BUILD: pip install numpy setuptools wheel cython
|
|
36
|
-
|
|
37
|
-
- uses: actions/upload-artifact@v2
|
|
38
|
-
with:
|
|
39
|
-
path: ./wheelhouse/*.whl
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"git_version": "3539c4e", "is_release": true}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|