tinybrain 1.6.0__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {tinybrain-1.6.0 → tinybrain-1.7.0}/.github/workflows/build_wheel.yml +8 -4
  2. {tinybrain-1.6.0 → tinybrain-1.7.0}/.github/workflows/run_tests.yml +1 -1
  3. {tinybrain-1.6.0 → tinybrain-1.7.0}/ChangeLog +9 -0
  4. {tinybrain-1.6.0 → tinybrain-1.7.0}/PKG-INFO +22 -8
  5. {tinybrain-1.6.0 → tinybrain-1.7.0}/README.md +8 -3
  6. {tinybrain-1.6.0 → tinybrain-1.7.0}/automated_test.py +3 -3
  7. {tinybrain-1.6.0 → tinybrain-1.7.0}/setup.cfg +1 -1
  8. {tinybrain-1.6.0 → tinybrain-1.7.0}/setup.py +3 -3
  9. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain/accelerated.hpp +14 -5
  10. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain/accelerated.pyx +352 -1
  11. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain/downsample.py +2 -2
  12. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/PKG-INFO +22 -8
  13. tinybrain-1.7.0/tinybrain.egg-info/pbr.json +1 -0
  14. tinybrain-1.6.0/tinybrain.egg-info/pbr.json +0 -1
  15. {tinybrain-1.6.0 → tinybrain-1.7.0}/AUTHORS +0 -0
  16. {tinybrain-1.6.0 → tinybrain-1.7.0}/LICENSE +0 -0
  17. {tinybrain-1.6.0 → tinybrain-1.7.0}/build_linux.sh +0 -0
  18. {tinybrain-1.6.0 → tinybrain-1.7.0}/manylinux1.Dockerfile +0 -0
  19. {tinybrain-1.6.0 → tinybrain-1.7.0}/manylinux2010.Dockerfile +0 -0
  20. {tinybrain-1.6.0 → tinybrain-1.7.0}/manylinux2014.Dockerfile +0 -0
  21. {tinybrain-1.6.0 → tinybrain-1.7.0}/perf.py +0 -0
  22. {tinybrain-1.6.0 → tinybrain-1.7.0}/pyproject.toml +0 -0
  23. {tinybrain-1.6.0 → tinybrain-1.7.0}/requirements.txt +0 -0
  24. {tinybrain-1.6.0 → tinybrain-1.7.0}/requirements_dev.txt +0 -0
  25. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain/__init__.py +0 -0
  26. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/SOURCES.txt +0 -0
  27. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/dependency_links.txt +0 -0
  28. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/not-zip-safe +0 -0
  29. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/requires.txt +0 -0
  30. {tinybrain-1.6.0 → tinybrain-1.7.0}/tinybrain.egg-info/top_level.txt +0 -0
  31. {tinybrain-1.6.0 → tinybrain-1.7.0}/tox.ini +0 -0
@@ -28,12 +28,16 @@ jobs:
28
28
  uses: docker/setup-qemu-action@v1
29
29
 
30
30
  - name: Build wheels
31
- uses: pypa/cibuildwheel@v2.19.1
31
+ uses: joerick/cibuildwheel@v2.22.0
32
32
  # to supply options, put them in 'env', like:
33
33
  env:
34
- CIBW_ARCHS_LINUX: ${{matrix.arch}}
35
- CIBW_BEFORE_BUILD: pip install numpy setuptools wheel cython
34
+ CIBW_BEFORE_BUILD: pip install numpy pybind11 setuptools wheel pkginfo twine
35
+ CIBW_ARCHS_MACOS: "x86_64 arm64"
36
+ CIBW_ARCHS: auto64
36
37
 
37
- - uses: actions/upload-artifact@v2
38
+ - name: Upload built wheels
39
+ uses: actions/upload-artifact@v4
38
40
  with:
41
+ name: built-wheels-${{ matrix.os }}-${{ matrix.arch }}
39
42
  path: ./wheelhouse/*.whl
43
+ if-no-files-found: warn
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ${{ matrix.os }}
15
15
  strategy:
16
16
  matrix:
17
- os: [ubuntu-20.04, macos-latest, windows-2019]
17
+ os: [ubuntu-latest, macos-latest, windows-latest]
18
18
  python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
19
19
 
20
20
  steps:
@@ -1,6 +1,15 @@
1
1
  CHANGES
2
2
  =======
3
3
 
4
+ 1.7.0
5
+ -----
6
+
7
+ * fix: warnings about type comparison
8
+ * install: bump to c++17
9
+ * feat: add int8, int16 to downsample\_with\_averaging (#23)
10
+ * chore: update python compatible versions to drop 3.8 (EOL)
11
+ * docs: remove travis badge
12
+
4
13
  1.6.0
5
14
  -----
6
15
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: tinybrain
3
- Version: 1.6.0
3
+ Version: 1.7.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.8
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
- [![Build Status](https://travis-ci.org/seung-lab/tinybrain.svg?branch=master)](https://travis-ci.org/seung-lab/tinybrain) [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](https://badge.fury.io/py/tinybrain)
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
+ [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](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 visualze upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
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
- [![Build Status](https://travis-ci.org/seung-lab/tinybrain.svg?branch=master)](https://travis-ci.org/seung-lab/tinybrain) [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](https://badge.fury.io/py/tinybrain)
1
+ [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](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 visualze upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
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))
@@ -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++11', '/O2'
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++11', '-O3' # '-DCYTHON_TRACE=1'
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.8",
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 > 0) + (b > 0) + (c > 0) + (d > 0)),
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 > 0) + (b > 0)),
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
- for (size_t i = 0; i < ovoxels; i++) {
935
- oimg[i] = static_cast<U>(accum[i] >> bitshift);
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
 
@@ -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 > 0
139
+ counts[indexing_expr] += part != 0
140
140
  else:
141
141
  counts[indexing_expr] += 1
142
142
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: tinybrain
3
- Version: 1.6.0
3
+ Version: 1.7.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.8
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
- [![Build Status](https://travis-ci.org/seung-lab/tinybrain.svg?branch=master)](https://travis-ci.org/seung-lab/tinybrain) [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](https://badge.fury.io/py/tinybrain)
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
+ [![PyPI version](https://badge.fury.io/py/tinybrain.svg)](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 visualze upper mip levels using three axes at once, it becomes desirable to perform 2x2x2 downsamples to maintain isotropy.
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": "860d1fd", "is_release": true}
@@ -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