├── .dockerignore ├── .github └── workflows │ ├── build_wheel.yml │ └── test.yml ├── .gitignore ├── AUTHORS ├── ChangeLog ├── LICENSE ├── MANIFEST.in ├── README.md ├── automated_test.py ├── build_linux.sh ├── manylinux1.Dockerfile ├── manylinux2010.Dockerfile ├── manylinux2014.Dockerfile ├── pyproject.toml ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── src ├── fastremap.pxd ├── fastremap.pyx ├── ipt.hpp └── ska_flat_hash_map.hpp ├── test.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .tox 2 | .pytest_cache 3 | build 4 | dist 5 | .eggs -------------------------------------------------------------------------------- /.github/workflows/build_wheel.yml: -------------------------------------------------------------------------------- 1 | name: Build Wheels 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | env: 9 | CIBW_SKIP: cp27-* cp33-* cp34-* cp35-* cp36-* cp37-* cp38* pp* *-musllinux* cp312-manylinux_i686 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-2019] 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.22.0 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 | CIBW_ARCHS_MACOS: "x86_64 arm64" 37 | 38 | 39 | - name: Upload built wheels 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: built-wheels-${{ matrix.os }}-${{ matrix.arch }} 43 | path: ./wheelhouse/*.whl 44 | if-no-files-found: warn 45 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | run_tests: 13 | name: Test ${{ matrix.os }} Python ${{ matrix.python-version }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 19 | 20 | steps: 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - uses: actions/checkout@v2 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install pytest -r requirements.txt -r requirements_dev.txt setuptools wheel 32 | 33 | - name: Compile 34 | run: python setup.py develop 35 | 36 | - name: Test with pytest 37 | run: pytest -v -x automated_test.py 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | fastremap.cpp 107 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Andreas Schwab 2 | Max 3 | William Silversmith 4 | William Silversmith 5 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | CHANGES 2 | ======= 3 | 4 | * ci: add arm64 and x86\_64 to macos build 5 | * perf: add return\_inverse to fr.unique 6 | * perf(unique/array): avoid two operations for return\_index 7 | * feat: point\_cloud supports 2d images 8 | 9 | 1.14.2 10 | ------ 11 | 12 | * install: bump python to 3.8, add cnp.import\_array() 13 | * ci: re-add macos, remove travis, appveyor 14 | * ci: use numpy without qualification 15 | * ci: properly specify numpy 2.0 and use working artifact uploader 16 | * ci: switch to compiling with numpy 2.0 17 | * ci: use non-buggy version of cibuildwheel 18 | * refactor: remove COUNT\_T 19 | * redesign: remove \_\_version\_\_ attribute 20 | * perf: reduce memory usage during finaly copy for unique\_via\_sort 21 | * fix: unique supports large arrays for the counts 22 | * fix: accurate unique counts for large arrays 23 | * build: more modernizations 24 | 25 | 1.14.1 26 | ------ 27 | 28 | * build: temporarily exclude macos until ecosystem fixes itself 29 | * chore: ignore fastremap.cpp 30 | * chore: ignore fastremap.cpp 31 | * build: update for py312 32 | * build: update build 33 | 34 | 1.14.0 35 | ------ 36 | 37 | * release(1.14.0): adds tobytes function 38 | * docs: add README info and docstring 39 | * docs: add tobytes example 40 | * feat: bulk tobytes (#35) 41 | 42 | 1.13.5 43 | ------ 44 | 45 | * release(1.13.5): updated build for py311 46 | 47 | 1.13.4 48 | ------ 49 | 50 | * release(1.13.4): easier installation 51 | * Delayed numpy import during setup (#33) 52 | * fix: handle all LP64 platforms generically (#32) 53 | 54 | 1.13.3 55 | ------ 56 | 57 | * release(1.13.3): rerelease of binaries 58 | 59 | 1.13.2 60 | ------ 61 | 62 | * release(1.13.2): (inverse\_)component\_map accepts non-aligned inputs 63 | * fix: component\_map and inverse\_component\_map accept non-aligned inputs 64 | 65 | 1.13.1 66 | ------ 67 | 68 | * fixtest: elements in inv\_component\_map are now sorted 69 | * release(1.13.1): unique accepts lists, inverse\_component\_map uniq vals 70 | * feat: unique accepts lists 71 | * fix: remove duplicates from inverse\_component\_map 72 | * build: update build system with aarch64 and py310 73 | 74 | 1.13.0 75 | ------ 76 | 77 | * release(1.13.0): using improved map for faster renumber, remap, more 78 | * perf: switch to ska::flat\_hash\_map instead of std::unordered\_map (#30) 79 | 80 | 1.12.2 81 | ------ 82 | 83 | * release(1.12.2): release binaries with broader numpy ABI compat 84 | * chore: use oldest-supported-numpy when building wheels 85 | 86 | 1.12.1 87 | ------ 88 | 89 | * release(1.12.1): unique accepts empty arrays 90 | * fix: fastremap.unique doesn't accept empty arrays 91 | 92 | 1.12.0 93 | ------ 94 | 95 | * release(1.12.0): new point\_cloud and foreground functions 96 | * feat(point\_cloud): new point\_cloud and foreground functions (#28) 97 | * fix(renumber): preserve\_zero=False w/ arrays starting with 0 98 | 99 | 1.11.4 100 | ------ 101 | 102 | * release(1.11.4): fixes issue with inverse\_component\_map 103 | * chore: update changelog 104 | * chore: support only py36 - py39 105 | * chore: drop py27 from build 106 | * fix(inverse\_component\_map): not checking component for continue cond 107 | 108 | 1.11.2 109 | ------ 110 | 111 | * release(1.11.2): fix numpy deprecation of np.bool 112 | * fix: further fix numpy deprecation of np.bool 113 | * chore: fix numpy 1.20 deprecation of np.bool in favor of bool 114 | * chore: update setup.cfg 115 | 116 | 1.11.1 117 | ------ 118 | 119 | * release(1.11.1): python39 support 120 | * chore: make build\_linux.sh script generic 121 | * chore: add updated build system 122 | * fix: pbr couldn't see git repo 123 | 124 | 1.11.0 125 | ------ 126 | 127 | * release(1.11.0): unique(..., return\_index=True) now accelerated 128 | * feat(unique): adds return\_index to high performance paths (#25) 129 | * chore: add .dockerignore 130 | * chore: update ChangeLog, tox.ini 131 | 132 | 1.10.2 133 | ------ 134 | 135 | * release(1.10.2): fixed bug in remap 136 | * fix: remap was not handling missing labels correctly (#23) 137 | * Revert "feat: adds argminmax, argmin, argmax, ravel\_index (#22)" 138 | * feat: adds argminmax, argmin, argmax, ravel\_index (#22) 139 | * chore: update changelog 140 | * chore: adds tox 141 | 142 | 1.10.1 143 | ------ 144 | 145 | * release(1.10.1): faster renumber and by proxy unique in some circumstances 146 | * chore: add tox.ini 147 | * perf: faster renumber by using unordered\_map (#21) 148 | 149 | 1.10.0 150 | ------ 151 | 152 | * release(1.10.0): adds component\_map and inverse\_component\_map 153 | * fix: should use int instead of uint 154 | * test: abort appveyor early if a test fails 155 | * fix: test for renumber dying on uint64 assertion 156 | * fix: remove 'build.cmd' as we are not supporting 3.3 or 3.4 157 | * chore: add appveyor.yml 158 | * docs: show how to use component\_map and inverse\_component\_map 159 | * feat: add components\_map and inverse\_components\_map 160 | * Update README.md 161 | 162 | 1.9.2 163 | ----- 164 | 165 | * release(1.9.2): fixed performance issue with fastremap.unique 166 | * perf: faster unique\_va\_array 167 | 168 | 1.9.1 169 | ----- 170 | 171 | * release(1.9.1): ipt handles 64-bit addressable arrays 172 | * fix: support 64-bit addressable volumes for IPT 173 | 174 | 1.9.0 175 | ----- 176 | 177 | * release(1.9.0): adds unique, minmax, refit, fit\_dtype, pixel\_pairs 178 | * chore: add py3.8 to dockerfile 179 | * feat: unique, minmax, refit, fit\_dtype, pixel\_pairs (#20) 180 | 181 | 1.8.0 182 | ----- 183 | 184 | * release(1.8.0): faster remap and mask operators 185 | * perf: faster remap and mask operators (#19) 186 | 187 | 1.7.0 188 | ----- 189 | 190 | * feat: add support for single precision complex numbers to IPT (#17) 191 | * docs: move "all available functions" higher 192 | 193 | 1.6.2 194 | ----- 195 | 196 | * release(1.6.2): mask\_except can accept maximum unsigned values 197 | * fix: mask\_except can use maximum values for unsigned ints 198 | * install: add -stdlib=libc++ for mac builds (#14) 199 | 200 | 1.6.1 201 | ----- 202 | 203 | * release(1.6.1): much faster renumber 204 | * perf: much faster renumber (#10) 205 | 206 | 1.6.0 207 | ----- 208 | 209 | * release: 1.6.0 210 | * feat: add mask\_except (complement of mask) (#9) 211 | 212 | 1.5.2 213 | ----- 214 | 215 | * release: Version 1.5.2 216 | * perf: use unordered\_map to prevent any possibility of python interaction (#7) 217 | 218 | 1.5.1 219 | ----- 220 | 221 | * release: version 1.5.1 222 | * perf: faster fastremap.remap (#6) 223 | 224 | 1.5.0 225 | ----- 226 | 227 | * chore: bump version to 1.5.0 228 | * feat: add mask function 229 | * docs: update changelog 230 | 231 | 1.4.1 232 | ----- 233 | 234 | * chore: bump version to 1.4.1 235 | 236 | 1.4.0 237 | ----- 238 | 239 | * chore: bump version to 1.4.0 240 | * feat: n-dimensional remap support (#5) 241 | * docs: example for renumber's in\_place=True parameter 242 | * Update README.md 243 | 244 | 1.3.0 245 | ----- 246 | 247 | * chore: bump version to 1.3.0 248 | * feat+perf: add in\_place argument to renumber 249 | * perf: remove memory copy for bool to uint8 in renumber 250 | * perf: use stride tricks to avoid copies in renumber 251 | 252 | 1.2.2 253 | ----- 254 | 255 | * fix: renumber was broken due to incomplete boolean logic 256 | * docs: updating documentation 257 | 258 | 1.2.1 259 | ----- 260 | 261 | * chore: bump version to 1.2.1 262 | * docs: update in-place description in README example 263 | 264 | 1.2.0 265 | ----- 266 | 267 | * chore: drop py34 support 268 | * chore: version 1.2.0 269 | * feat: 2D, 3D, 4D rectangular in-place transposition (#3) 270 | * refactor: use fused types to reduce code duplication (#4) 271 | 272 | 1.1.0 273 | ----- 274 | 275 | * docs: add authors, changelog 276 | * chore: bump version to 1.1.0 277 | * feat: faster in place transpose for symmetric 2d and 3d matricies. (#2) 278 | 279 | 1.0.1 280 | ----- 281 | 282 | * chore: bump version to 1.0.1 283 | * test: test multiple data types for remap 284 | * fix: segmentation fault when calling "help(fastremap)" 285 | * Update README.md 286 | * docs: discuss numpy version compatibility with binaries 287 | * docs: showed that renumber returns a tuple 288 | * docs: added PyPI badge 289 | 290 | 1.0.0 291 | ----- 292 | 293 | * feat: added \_\_version\_\_ to pyx file 294 | * docs: more love 295 | * docs: added function description to remap\_from\_array\_kv and remap\_from\_array 296 | * docs: grammar fix 297 | * docs: described the problem that fastremap solves 298 | * fix: needed to install pytest for Travis CI 299 | * docs: installation and usage 300 | * refactor: move fastremap.remap higher in the file 301 | * test: Travis CI automated testing 302 | * test: cover more datatypes, add remap 1d test 303 | * test: 2d and 3d renumber 304 | * test: add 1d renumber tests w/ dtype support 305 | * wip: creating fastremap library 306 | * Initial commit 307 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src * 2 | exclude src/fastremap.cpp 3 | include LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI version](https://badge.fury.io/py/fastremap.svg)](https://badge.fury.io/py/fastremap) 2 | 3 | # fastremap 4 | 5 | Renumber and relabel Numpy arrays at C++ speed and physically convert rectangular Numpy arrays between C and Fortran order using an in-place transposition. 6 | 7 | ```python 8 | import fastremap 9 | 10 | uniq, cts = fastremap.unique(labels, return_counts=True) # may be much faster than np.unique 11 | 12 | idxs = fastremap.indices(labels, 1231) # important for huge arrays 13 | 14 | labels, remapping = fastremap.renumber(labels, in_place=True) # relabel values from 1 and refit data type 15 | ptc = fastremap.point_cloud(labels) # dict of coordinates by label 16 | 17 | labels = fastremap.refit(labels) # resize the data type of the array to fit extrema 18 | labels = fastremap.refit(labels, value=-35) # resize the data type to fit the value provided 19 | 20 | wider_dtype = fastremap.widen_dtype(np.uint32) # np.uint64 21 | narrower_dtype = fastremap.narrow_dtype(np.uint32) # np.uint16 22 | 23 | # remap all occurances of 1 -> 2 24 | labels = fastremap.remap(labels, { 1: 2 }, preserve_missing_labels=True, in_place=True) 25 | 26 | labels = fastremap.mask(labels, [1,5,13]) # set all occurances of 1,5,13 to 0 27 | labels = fastremap.mask_except(labels, [1,5,13]) # set all labels except 1,5,13 to 0 28 | 29 | mapping = fastremap.component_map([ 1, 2, 3, 4 ], [ 5, 5, 6, 7 ]) # { 1: 5, 2: 5, 3: 6, 4: 7 } 30 | mapping = fastremap.inverse_component_map([ 1, 2, 1, 3 ], [ 4, 4, 5, 6 ]) # { 1: [ 4, 5 ], 2: [ 4 ], 3: [ 6 ] } 31 | 32 | fastremap.transpose(labels) # physically transpose labels in-place 33 | fastremap.ascontiguousarray(labels) # try to perform a physical in-place transposition to C order 34 | fastremap.asfortranarray(labels) # try to perform a physical in-place transposition to F order 35 | 36 | minval, maxval = fastremap.minmax(labels) # faster version of (np.min(labels), np.max(labels)) 37 | 38 | # computes number of matching adjacent pixel pairs in an image 39 | num_pairs = fastremap.pixel_pairs(labels) 40 | n_foreground = fastremap.foreground(labels) # number of nonzero voxels 41 | 42 | # computes the cutout.tobytes(order) of each chunk and returns 43 | # the binaries indexed by fortran order in the order specified (C or F) 44 | # If the input image is F contiguous and F is requested, or C and C order, 45 | # and the image is larger than a single chunk, this will be significantly 46 | # faster than iterating and using tobytes. 47 | binaries = fastremap.tobytes(labels, (64,64,64), order="F") 48 | ``` 49 | 50 | ## All Available Functions 51 | - **unique:** Faster implementation of `np.unique`. 52 | - **renumber:** Relabel array from 1 to N which can often use smaller datatypes. 53 | - **indices:** Optimized search for matching values. 54 | - **remap:** Custom relabeling of values in an array from a dictionary. 55 | - **refit:** Resize the data type of an array to the smallest that can contain the most extreme values in it. 56 | - **narrow_dtype:** Find the next sized up dtype. e.g. uint16 -> uint32 57 | - **widen_dtype:** Find the next sized down dtype. e.g. uint16 -> uint8 58 | - **mask:** Zero out labels in an array specified by a given list. 59 | - **mask_except**: Zero out all labels except those specified in a given list. 60 | - **component_map**: Extract an int-to-int dictionary mapping of labels from one image containing component labels to another parent labels. 61 | - **inverse_component_map**: Extract an int-to-list-of-ints dictionary mapping from an image containing groups of components to an image containing the components. 62 | - **remap_from_array:** Same as remap, but the map is an array where the key is the array index and the value is the value. 63 | - **remap_from_array_kv:** Same as remap, but the map consists of two equal sized arrays, the first containing keys, the second containing values. 64 | - **asfortranarray:** Perform an in-place matrix transposition for rectangular arrays if memory is contiguous, standard numpy otherwise. 65 | - **ascontiguousarray:** Perform an in-place matrix transposition for rectangular arrays if memory is contiguous, standard numpy algorithm otherwise. 66 | - **minmax:** Compute the min and max of an array in one pass. 67 | - **pixel_pairs:** Computes the number of adjacent matching memory locations in an image. A quick heuristic for understanding if the image statistics are roughly similar to a connectomics segmentation. 68 | - **foreground:** Count the number of non-zero voxels rapidly. 69 | - **point_cloud:** Get the X,Y,Z locations of each foreground voxel grouped by label. 70 | - **tobytes**: Compute the tobytes of an image divided into a grid and return the resultant binaries indexed by their gridpoint in fortran order with the binary in the order requested (C or F). 71 | 72 | ## `pip` Installation 73 | 74 | ```bash 75 | pip install fastremap 76 | ``` 77 | 78 | *If not, a C++ compiler is required.* 79 | 80 | ```bash 81 | pip install numpy 82 | pip install fastremap --no-binary :all: 83 | ``` 84 | 85 | ## Manual Installation 86 | 87 | *A C++ compiler is required.* 88 | 89 | ```bash 90 | sudo apt-get install g++ python3-dev 91 | mkvirtualenv -p python3 fastremap 92 | pip install numpy 93 | 94 | # Choose one: 95 | python setup.py develop 96 | python setup.py install 97 | ``` 98 | 99 | ## The Problem of Remapping 100 | 101 | Python loops are slow, so Numpy is often used to perform remapping on large arrays (hundreds of megabytes or gigabytes). In order to efficiently remap an array in Numpy you need a key-value array where the index is the key and the value is the contents of that index. 102 | 103 | ```python 104 | import numpy as np 105 | 106 | original = np.array([ 1, 3, 5, 5, 10 ]) 107 | remap = np.array([ 0, -5, 0, 6, 0, 0, 2, 0, 0, 0, -100 ]) 108 | # Keys: 0 1 2 3 4 5 6 7 8 9 10 109 | 110 | remapped = remap[ original ] 111 | >>> [ -5, 6, 2, 2, -100 ] 112 | ``` 113 | 114 | If there are 32 or 64 bit labels in the array, this becomes impractical as the size of the array can grow larger than RAM. Therefore, it would be helpful to be able to perform this mapping using a C speed loop. Numba can be used for this in some circumstances. However, this library provides an alternative. 115 | 116 | ```python 117 | import numpy as np 118 | import fastremap 119 | 120 | mappings = { 121 | 1: 100, 122 | 2: 200, 123 | -3: 7, 124 | } 125 | 126 | arr = np.array([5, 1, 2, -5, -3, 10, 6]) 127 | # Custom remapping of -3, 5, and 6 leaving the rest alone 128 | arr = fastremap.remap(arr, mappings, preserve_missing_labels=True) 129 | # result: [ 5, 100, 200, -5, 7, 10, 6 ] 130 | ``` 131 | 132 | ## The Problem of Renumbering 133 | 134 | Sometimes a 64-bit array contains values that could be represented by an 8-bit array. However, similarly to the remapping problem, Python loops can be too slow to do this. Numpy doesn't provide a convenient way to do it either. Therefore this library provides an alternative solution. 135 | 136 | ```python 137 | import fastremap 138 | import numpy as np 139 | 140 | arr = np.array([ 283732875, 439238823, 283732875, 182812404, 0 ], dtype=np.int64) 141 | 142 | arr, remapping = fastremap.renumber(arr, preserve_zero=True) # Returns uint8 array 143 | >>> arr = [ 1, 2, 1, 3, 0 ] 144 | >>> remapping = { 0: 0, 283732875: 1, 439238823: 2, 182812404: 3 } 145 | 146 | arr, remapping = fastremap.renumber(arr, preserve_zero=False) # Returns uint8 array 147 | >>> arr = [ 1, 2, 1, 3, 4 ] 148 | >>> remapping = { 0: 4, 283732875: 1, 439238823: 2, 182812404: 3 } 149 | 150 | arr, remapping = fastremap.renumber(arr, preserve_zero=False, in_place=True) # Mutate arr to use less memory 151 | >>> arr = [ 1, 2, 1, 3, 4 ] 152 | >>> remapping = { 0: 4, 283732875: 1, 439238823: 2, 182812404: 3 } 153 | ``` 154 | 155 | ## The Problem of In-Place Transposition 156 | 157 | When transitioning between different media, e.g. CPU to GPU, CPU to Network, CPU to disk, it's often necessary to physically transpose multi-dimensional arrays to reformat as C or Fortran order. Tranposing matrices is also a common action in linear algebra, but often you can get away with just changing the strides. 158 | 159 | An out-of-place transposition is easy to write, and often faster, but it will spike peak memory consumption. This library grants the user the option of performing an in-place transposition which trades CPU time for peak memory usage. In the special case of square or cubic arrays, the in-place transpisition is both lower memory and faster. 160 | 161 | - **fastremap.asfortranarray:** Same as np.asfortranarray but will perform the transposition in-place for 1, 2, 3, and 4D arrays. 2D and 3D square matrices are faster to process than with Numpy. 162 | - **fastremap.ascontiguousarray:** Same as np.ascontiguousarray but will perform the transposition in-place for 1, 2, 3, and 4D arrays. 2D and 3D square matrices are faster to process than with Numpy. 163 | 164 | ```python 165 | import fastremap 166 | import numpy as np 167 | 168 | arr = np.ones((512,512,512), dtype=np.float32) 169 | arr = fastremap.asfortranarray(x) 170 | 171 | arr = np.ones((512,512,512), dtype=np.float32, order='F') 172 | arr = fastremap.ascontiguousarray(x) 173 | ``` 174 | 175 | ## C++ Usage 176 | 177 | The in-place matrix transposition is implemented in ipt.hpp. If you're working in C++, you can also use it directly like so: 178 | 179 | ```cpp 180 | #include "ipt.hpp" 181 | 182 | int main() { 183 | 184 | int sx = 128; 185 | int sy = 124; 186 | int sz = 103; 187 | int sw = 3; 188 | 189 | auto* arr = ....; 190 | 191 | // All primitive number types supported 192 | // The array will be modified in place, 193 | // so these functions are void type. 194 | ipt::ipt(arr, sx, sy); // 2D 195 | ipt::ipt(arr, sx, sy, sz); // 3D 196 | ipt::ipt(arr, sx, sy, sz, sw); // 4D 197 | 198 | return 0; 199 | } 200 | ``` 201 | 202 | -- 203 | Made with <3 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /automated_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | 5 | import fastremap 6 | 7 | DTYPES = ( 8 | np.uint8, np.uint16, np.uint32, np.uint64, 9 | np.int8, np.int16, np.int32, np.int64 10 | ) 11 | 12 | def test_empty_renumber(): 13 | for dtype in DTYPES: 14 | data = np.array([], dtype=dtype) 15 | data2, remapdict = fastremap.renumber(data, preserve_zero=False) 16 | 17 | assert np.all(data2 == []) 18 | assert remapdict == {} 19 | 20 | def test_1d_renumber(): 21 | for dtype in DTYPES: 22 | print(dtype) 23 | data = np.arange(8).astype(dtype) 24 | data = np.flip(data) 25 | 26 | data2 = np.copy(data) 27 | data2, remapdict = fastremap.renumber(data2, preserve_zero=False) 28 | 29 | assert np.all(data2 == np.arange(1,9)) 30 | assert len(remapdict) > 0 31 | 32 | data2 = np.copy(data) 33 | data2, remapdict = fastremap.renumber(data2, preserve_zero=True) 34 | 35 | assert data2[-1] == 0 36 | assert np.all(data2 == [1,2,3,4,5,6,7,0]) 37 | assert len(remapdict) > 0 38 | 39 | data = np.arange(8).astype(bool) 40 | data = np.flip(data) 41 | 42 | data2 = np.copy(data) 43 | data2, remapdict = fastremap.renumber(data2, preserve_zero=False) 44 | 45 | assert np.all(data2 == [1,1,1,1,1,1,1,2]) 46 | assert len(remapdict) > 0 47 | 48 | data2 = np.copy(data) 49 | data2, remapdict = fastremap.renumber(data2, preserve_zero=True) 50 | 51 | assert np.all(data2 == [1,1,1,1,1,1,1,0]) 52 | assert len(remapdict) > 0 53 | 54 | def test_2d_renumber(): 55 | for dtype in DTYPES: 56 | data = np.array([ 57 | [ 5, 5, 5, 2], 58 | [ 3, 5, 5, 0], 59 | [ 1, 2, 4, 1], 60 | [20, 19, 20, 1], 61 | ], dtype=dtype) 62 | 63 | data2 = np.copy(data, order='C') 64 | data2, remapdict = fastremap.renumber(data2, preserve_zero=True) 65 | 66 | assert np.all(data2 == [ 67 | [1, 1, 1, 2], 68 | [3, 1, 1, 0], 69 | [4, 2, 5, 4], 70 | [6, 7, 6, 4], 71 | ]) 72 | 73 | data2 = np.copy(data, order='F') 74 | data2, remapdict = fastremap.renumber(data2, preserve_zero=True) 75 | 76 | assert np.all(data2 == [ 77 | [1, 1, 1, 5], 78 | [2, 1, 1, 0], 79 | [3, 5, 7, 3], 80 | [4, 6, 4, 3], 81 | ]) 82 | 83 | @pytest.mark.parametrize("dtype", DTYPES) 84 | def test_3d_renumber(dtype): 85 | bits = np.dtype(dtype).itemsize * 8 86 | big = (2 ** (bits - 1)) - 1 # cover ints and uints 87 | data = np.array([ 88 | [ 89 | [big, 0], 90 | [2, big], 91 | ], 92 | [ 93 | [big-5, big-1], 94 | [big-7, big-3], 95 | ], 96 | ], dtype=dtype) 97 | 98 | data2 = np.copy(data, order='C') 99 | data2, remapdict = fastremap.renumber(data2, preserve_zero=False) 100 | 101 | assert np.all(data2 == [ 102 | [ 103 | [1, 2], 104 | [3, 1] 105 | ], 106 | [ 107 | [4, 5], 108 | [6, 7], 109 | ], 110 | ]) 111 | 112 | data2 = np.copy(data, order='F') 113 | data2, remapdict = fastremap.renumber(data2, preserve_zero=False) 114 | 115 | assert np.all(data2 == [ 116 | [ 117 | [1, 5], 118 | [3, 1] 119 | ], 120 | [ 121 | [2, 6], 122 | [4, 7], 123 | ], 124 | ]) 125 | 126 | def test_3d_renumber_dtype_shift(): 127 | big = np.random.randint(0, (2**64)-1, size=(128,128,100), dtype=np.uint64) 128 | big, remapdict = fastremap.renumber(big, preserve_zero=True, in_place=True) 129 | assert np.dtype(big.dtype).itemsize <= 4 130 | assert np.dtype(big.dtype).itemsize > 1 131 | 132 | def test_renumber_no_preserve_zero(): 133 | data = np.array([ 134 | [0, 1], 135 | [1, 2] 136 | ]) 137 | gt = np.array([ 138 | [0, 1], 139 | [1, 2] 140 | ]) 141 | 142 | res, remap = fastremap.renumber(data, start=0, preserve_zero=False) 143 | print(res) 144 | print(remap) 145 | assert remap == { 0: 0, 1: 1, 2: 2 } 146 | assert np.all(res == gt) 147 | 148 | 149 | @pytest.mark.parametrize("dtype", list(DTYPES) + [ np.float32, np.float64 ]) 150 | def test_remap_1d(dtype): 151 | empty = fastremap.remap([], {}) 152 | assert len(empty) == 0 153 | 154 | data = np.array([1, 2, 2, 2, 3, 4, 5], dtype=dtype) 155 | remap = { 156 | 1: 10, 157 | 2: 30, 158 | 3: 15, 159 | 4: 0, 160 | 5: 5, 161 | } 162 | 163 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=False) 164 | assert np.all(result == [10, 30, 30, 30, 15, 0, 5]) 165 | 166 | del remap[2] 167 | try: 168 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=False) 169 | assert False 170 | except KeyError: 171 | pass 172 | 173 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=True) 174 | assert np.all(result == [10, 2, 2, 2, 15, 0, 5]) 175 | 176 | @pytest.mark.parametrize("dtype", DTYPES) 177 | def test_remap_2d(dtype): 178 | data = np.array([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1]], dtype=dtype) 179 | remap = { 180 | 1: 10, 181 | 2: 30, 182 | 3: 15, 183 | 4: 0, 184 | 5: 5, 185 | } 186 | 187 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=False) 188 | assert np.all(result == [[10, 30, 15, 0, 5], [5, 0, 15, 30, 10]]) 189 | 190 | del remap[2] 191 | try: 192 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=False) 193 | assert False 194 | except KeyError: 195 | pass 196 | 197 | result = fastremap.remap(np.copy(data), remap, preserve_missing_labels=True) 198 | assert np.all(result == [[10, 2, 15, 0, 5], [5, 0, 15, 2, 10]]) 199 | 200 | def test_remap_broken(): 201 | labels = np.zeros((256, 256, 256), dtype=np.uint32) 202 | 203 | labels[:50, :40, :30] = 2 204 | labels[50:100, 40:100, 30:80] = 5 205 | 206 | res = fastremap.remap(labels, {5:5}, preserve_missing_labels=True) 207 | assert np.all(res == labels) 208 | 209 | @pytest.mark.parametrize("dtype", DTYPES) 210 | @pytest.mark.parametrize("in_place", [ True, False ]) 211 | def test_mask(dtype, in_place): 212 | data = np.arange(100, dtype=dtype) 213 | data = fastremap.mask(data, [5, 10, 15, 20], in_place=in_place) 214 | 215 | labels, cts = np.unique(data, return_counts=True) 216 | assert cts[0] == 5 217 | assert labels[0] == 0 218 | assert np.all(cts[1:] == 1) 219 | assert len(labels == 95) 220 | 221 | @pytest.mark.parametrize("dtype", DTYPES) 222 | @pytest.mark.parametrize("in_place", [ True, False ]) 223 | def test_mask_except(dtype, in_place): 224 | for value in (0, 7, np.iinfo(dtype).max): 225 | data = np.arange(100, dtype=dtype) 226 | data = fastremap.mask_except( 227 | data, [5, 10, 15, 20], 228 | in_place=in_place, value=value 229 | ) 230 | 231 | labels, cts = np.unique(data, return_counts=True) 232 | print(labels, cts) 233 | res = { lbl: ct for lbl, ct in zip(labels, cts) } 234 | assert res == { 235 | value: 96, 236 | 5: 1, 237 | 10: 1, 238 | 15: 1, 239 | 20: 1, 240 | } 241 | 242 | @pytest.mark.parametrize("dtype", list(DTYPES) + [ np.float32, np.float64, bool, np.complex64 ]) 243 | @pytest.mark.parametrize("dim", [1, 4, 7, 9, 27, 31, 100, 127, 200] ) 244 | def test_asfortranarray(dtype, dim): 245 | x = np.arange(dim**1).reshape((dim)).astype(dtype) 246 | y = np.copy(x) 247 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 248 | 249 | x = np.arange(dim**2).reshape((dim,dim)).astype(dtype) 250 | y = np.copy(x) 251 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 252 | 253 | x = np.arange(dim**3).reshape((dim,dim,dim)).astype(dtype) 254 | y = np.copy(x) 255 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 256 | 257 | x = np.arange(dim**2+dim).reshape((dim,dim+1)).astype(dtype) 258 | y = np.copy(x) 259 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 260 | 261 | x = np.arange(dim**3+dim*dim).reshape((dim,dim+1,dim)).astype(dtype) 262 | y = np.copy(x) 263 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 264 | 265 | if dim < 100: 266 | x = np.arange(dim**4).reshape((dim,dim,dim,dim)).astype(dtype) 267 | y = np.copy(x) 268 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 269 | 270 | x = np.arange(dim**4 + dim*dim*dim).reshape((dim+1,dim,dim,dim)).astype(dtype) 271 | y = np.copy(x) 272 | assert np.all(np.asfortranarray(x) == fastremap.asfortranarray(y)) 273 | 274 | 275 | @pytest.mark.parametrize("dtype", list(DTYPES) + [ np.float32, np.float64, bool, np.complex64 ]) 276 | @pytest.mark.parametrize("dim", [1, 4, 7, 9, 27, 31, 100, 127, 200] ) 277 | def test_ascontiguousarray(dtype, dim): 278 | x = np.arange(dim**2).reshape((dim,dim), order='F').astype(dtype) 279 | y = np.copy(x, order='F') 280 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 281 | 282 | x = np.arange(dim**3).reshape((dim,dim,dim), order='F').astype(dtype) 283 | y = np.copy(x, order='F') 284 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 285 | 286 | x = np.arange(dim**2+dim).reshape((dim,dim+1), order='F').astype(dtype) 287 | y = np.copy(x, order='F') 288 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 289 | 290 | x = np.arange(dim**3+dim*dim).reshape((dim,dim+1,dim), order='F').astype(dtype) 291 | y = np.copy(x, order='F') 292 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 293 | 294 | if dim < 100: 295 | x = np.arange(dim**4).reshape((dim,dim,dim,dim)).astype(dtype) 296 | y = np.copy(x, order='F') 297 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 298 | 299 | x = np.arange(dim**4 + dim*dim*dim).reshape((dim+1,dim,dim,dim)).astype(dtype) 300 | y = np.copy(x, order='F') 301 | assert np.all(np.ascontiguousarray(x) == fastremap.ascontiguousarray(y)) 302 | 303 | @pytest.mark.parametrize("dtype", [ np.uint8, np.uint16, np.uint32, np.uint64 ]) 304 | def test_fit_dtype_uint(dtype): 305 | assert fastremap.fit_dtype(dtype, 0) == np.uint8 306 | assert fastremap.fit_dtype(dtype, 255) == np.uint8 307 | assert fastremap.fit_dtype(dtype, 256) == np.uint16 308 | assert fastremap.fit_dtype(dtype, 10000) == np.uint16 309 | assert fastremap.fit_dtype(dtype, 2**16 - 1) == np.uint16 310 | assert fastremap.fit_dtype(dtype, 2**16) == np.uint32 311 | assert fastremap.fit_dtype(dtype, 2**32) == np.uint64 312 | assert fastremap.fit_dtype(dtype, 2**64 - 1) == np.uint64 313 | 314 | try: 315 | fastremap.fit_dtype(dtype, -1) 316 | assert False 317 | except ValueError: 318 | pass 319 | 320 | try: 321 | fastremap.fit_dtype(dtype, 2**64) 322 | except ValueError: 323 | pass 324 | 325 | @pytest.mark.parametrize("dtype", [ np.int8, np.int16, np.int32, np.int64 ]) 326 | def test_fit_dtype_int(dtype): 327 | assert fastremap.fit_dtype(dtype, 0) == np.int8 328 | assert fastremap.fit_dtype(dtype, 127) == np.int8 329 | assert fastremap.fit_dtype(dtype, -128) == np.int8 330 | assert fastremap.fit_dtype(dtype, 128) == np.int16 331 | assert fastremap.fit_dtype(dtype, 10000) == np.int16 332 | assert fastremap.fit_dtype(dtype, 2**15 - 1) == np.int16 333 | assert fastremap.fit_dtype(dtype, 2**15) == np.int32 334 | assert fastremap.fit_dtype(dtype, 2**32) == np.int64 335 | assert fastremap.fit_dtype(dtype, 2**63 - 1) == np.int64 336 | 337 | try: 338 | fastremap.fit_dtype(dtype, 2**63) 339 | except ValueError: 340 | pass 341 | 342 | try: 343 | fastremap.fit_dtype(dtype, -2**63) 344 | except ValueError: 345 | pass 346 | 347 | @pytest.mark.parametrize("dtype", [ np.float16, np.float32, np.float64 ]) 348 | def test_fit_dtype_float(dtype): 349 | assert fastremap.fit_dtype(dtype, 0) == np.float32 350 | assert fastremap.fit_dtype(dtype, 127) == np.float32 351 | assert fastremap.fit_dtype(dtype, 128) == np.float32 352 | assert fastremap.fit_dtype(dtype, 10000) == np.float32 353 | assert fastremap.fit_dtype(dtype, 2**15 - 1) == np.float32 354 | assert fastremap.fit_dtype(dtype, 2**15) == np.float32 355 | assert fastremap.fit_dtype(dtype, 2**32) == np.float32 356 | assert fastremap.fit_dtype(dtype, 2**63 - 1) == np.float32 357 | assert fastremap.fit_dtype(dtype, -2**63) == np.float32 358 | assert fastremap.fit_dtype(dtype, 2**128) == np.float64 359 | 360 | assert fastremap.fit_dtype(dtype, 0, exotics=True) == np.float16 361 | assert fastremap.fit_dtype(dtype, 127, exotics=True) == np.float16 362 | assert fastremap.fit_dtype(dtype, 128, exotics=True) == np.float16 363 | assert fastremap.fit_dtype(dtype, 10000, exotics=True) == np.float16 364 | assert fastremap.fit_dtype(dtype, 2**15 - 1, exotics=True) == np.float16 365 | assert fastremap.fit_dtype(dtype, 2**15, exotics=True) == np.float16 366 | assert fastremap.fit_dtype(dtype, 2**32, exotics=True) == np.float32 367 | assert fastremap.fit_dtype(dtype, 2**63 - 1, exotics=True) == np.float32 368 | assert fastremap.fit_dtype(dtype, -2**63, exotics=True) == np.float32 369 | 370 | @pytest.mark.parametrize("dtype", [ np.csingle, np.cdouble ]) 371 | @pytest.mark.parametrize("sign", [ 1, -1, 1j, -1j ]) 372 | def test_fit_dtype_float(dtype, sign): 373 | assert fastremap.fit_dtype(dtype, sign * 0+0j) == np.csingle 374 | assert fastremap.fit_dtype(dtype, sign * 127) == np.csingle 375 | assert fastremap.fit_dtype(dtype, sign * 127) == np.csingle 376 | assert fastremap.fit_dtype(dtype, sign * 128) == np.csingle 377 | assert fastremap.fit_dtype(dtype, sign * 128) == np.csingle 378 | assert fastremap.fit_dtype(dtype, sign * 10000) == np.csingle 379 | assert fastremap.fit_dtype(dtype, sign * 10000) == np.csingle 380 | assert fastremap.fit_dtype(dtype, sign * 2**15 - 1) == np.csingle 381 | assert fastremap.fit_dtype(dtype, sign * 2**15) == np.csingle 382 | assert fastremap.fit_dtype(dtype, sign * 2**32) == np.csingle 383 | assert fastremap.fit_dtype(dtype, sign * 2**63 - 1) == np.csingle 384 | assert fastremap.fit_dtype(dtype, -2**63) == np.csingle 385 | 386 | try: 387 | fastremap.fit_dtype(dtype, sign * 2**128) 388 | assert False 389 | except ValueError: 390 | pass 391 | 392 | assert fastremap.fit_dtype(dtype, sign * 2**128, exotics=True) == np.cdouble 393 | 394 | def test_minmax(): 395 | volume = np.random.randint(-500, 500, size=(128,128,128)) 396 | minval, maxval = fastremap.minmax(volume) 397 | assert minval == np.min(volume) 398 | assert maxval == np.max(volume) 399 | 400 | @pytest.mark.parametrize("dtype", DTYPES) 401 | def test_unique_axis_0(dtype): 402 | arr = np.array([ 403 | [0,1], 404 | [0,2], 405 | [0,3], 406 | [0,2], 407 | [1,2], 408 | [2,3], 409 | ], dtype=dtype) 410 | 411 | res = fastremap.unique(arr, axis=0) 412 | ans = np.unique(arr, axis=0) 413 | 414 | assert np.all(res == ans) 415 | 416 | def test_unique_axis_0_random(): 417 | arr = np.random.randint(0,100000, size=[1000000,2], dtype=np.uint32) 418 | a1 = fastremap.unique(arr, axis=0) 419 | a2 = np.unique(arr, axis=0) 420 | assert np.all(a1 == a2) 421 | 422 | 423 | @pytest.mark.parametrize("order", [ "C", "F" ]) 424 | def test_unique(order): 425 | def reorder(arr): 426 | if order == "F": 427 | return np.asfortranarray(arr) 428 | return np.ascontiguousarray(arr) 429 | 430 | assert len(fastremap.unique(np.array([], dtype=np.uint8))) == 0 431 | 432 | # array_unique 433 | labels = reorder(np.random.randint(0, 500, size=(128,128,128))) 434 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 435 | uniq_fr, idx_fr, inv_fr, cts_fr = fastremap.unique(labels, return_counts=True, return_index=True, return_inverse=True) 436 | assert np.all(uniq_np == uniq_fr) 437 | assert np.all(inv_np == inv_fr) 438 | assert np.all(cts_np == cts_fr) 439 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 440 | 441 | labels = reorder(np.random.randint(0, 500, size=(128,128,128))) 442 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 443 | uniq_fr, idx_fr, cts_fr, inv_fr = fastremap.unique_via_array(labels.flatten(), np.max(labels), return_index=True, return_inverse=True) 444 | assert np.all(uniq_np == uniq_fr) 445 | assert np.all(inv_np.flatten() == inv_fr) 446 | assert np.all(cts_np == cts_fr) 447 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 448 | 449 | # array_unique + shift 450 | labels = reorder(np.random.randint(-500, 500, size=(128,128,128))) 451 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 452 | uniq_fr, idx_fr, inv_fr, cts_fr = fastremap.unique(labels, return_counts=True, return_index=True, return_inverse=True) 453 | assert np.all(uniq_np == uniq_fr) 454 | assert np.all(inv_np == inv_fr) 455 | assert np.all(cts_np == cts_fr) 456 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 457 | 458 | labels = reorder(np.random.randint(-500, 500, size=(128,128,128))) 459 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 460 | uniq_fr, idx_fr, cts_fr, inv_fr = fastremap.unique_via_shifted_array(labels.flatten(), return_index=True, return_inverse=True) 461 | assert np.all(uniq_np == uniq_fr) 462 | assert np.all(inv_np.flatten() == inv_fr) 463 | assert np.all(cts_np == cts_fr) 464 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 465 | 466 | # array_unique + shift 467 | labels = reorder(np.random.randint(128**3 - 500, 128**3 + 500, size=(128,128,128))) 468 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 469 | uniq_fr, idx_fr, inv_fr, cts_fr = fastremap.unique(labels, return_counts=True, return_index=True, return_inverse=True) 470 | assert np.all(uniq_np == uniq_fr) 471 | assert np.all(inv_np == inv_fr) 472 | assert np.all(cts_np == cts_fr) 473 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 474 | 475 | # array_unique + shift 476 | labels = reorder(np.random.randint(128**3 - 500, 128**3 + 500, size=(128,128,128))) 477 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 478 | uniq_fr, idx_fr, cts_fr, inv_fr = fastremap.unique_via_shifted_array(labels.flatten(), return_index=True, return_inverse=True) 479 | assert np.all(uniq_np == uniq_fr) 480 | assert np.all(inv_np.flatten() == inv_fr) 481 | assert np.all(cts_np == cts_fr) 482 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 483 | 484 | # renumber + array_unique 485 | labels = reorder(np.random.randint(0, 1, size=(128,128,128))) 486 | labels[0,0,0] = 128**3 + 10 487 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 488 | uniq_fr, idx_fr, inv_fr, cts_fr = fastremap.unique(labels, return_counts=True, return_index=True, return_inverse=True) 489 | assert np.all(uniq_np == uniq_fr) 490 | assert np.all(inv_np == inv_fr) 491 | assert np.all(cts_np == cts_fr) 492 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 493 | 494 | labels = reorder(np.random.randint(0, 1, size=(128,128,128))) 495 | labels[0,0,0] = 128**3 + 10 496 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 497 | uniq_fr, idx_fr, cts_fr, inv_fr = fastremap.unique_via_renumber(labels.flatten(), return_index=True, return_inverse=True) 498 | assert np.all(uniq_np == uniq_fr) 499 | assert np.all(inv_np.flatten() == inv_fr) 500 | assert np.all(cts_np == cts_fr) 501 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 502 | 503 | # sort 504 | labels = reorder(np.random.randint(-1000, 128**3, size=(100,100,100))) 505 | uniq_np, idx_np, inv_np, cts_np = np.unique(labels, return_counts=True, return_index=True, return_inverse=True) 506 | uniq_fr, idx_fr, inv_fr, cts_fr = fastremap.unique(labels, return_counts=True, return_index=True, return_inverse=True) 507 | assert np.all(uniq_np == uniq_fr) 508 | assert np.all(inv_np == inv_fr) 509 | assert np.all(cts_np == cts_fr) 510 | assert np.all(labels.flatten()[idx_np] == labels.flatten()[idx_fr]) 511 | 512 | labels = reorder(np.random.randint(-1000, 128**3, size=(100,100,100))) 513 | uniq_np, cts_np = np.unique(labels, return_counts=True) 514 | uniq_fr, cts_fr = fastremap.unique_via_sort(labels.flatten()) 515 | assert np.all(uniq_np == uniq_fr) 516 | assert np.all(cts_np == cts_fr) 517 | 518 | labels = [ 1, 1, 2, 3, 2, 4, 3 ] 519 | uniq = fastremap.unique(labels) 520 | assert np.all(uniq == np.array([1,2,3,4])) 521 | 522 | 523 | def test_renumber_remap(): 524 | labels = np.random.randint(-500, 500, size=(128,128,128)).astype(np.int64) 525 | new_labels, remap = fastremap.renumber(labels, in_place=False) 526 | remap = { v:k for k,v in remap.items() } 527 | new_labels = fastremap.remap(new_labels, remap, in_place=True) 528 | assert np.all(labels == new_labels) 529 | assert new_labels.dtype in (np.int8, np.int16) 530 | assert labels.dtype == np.int64 531 | 532 | @pytest.mark.parametrize("dtype_cc", DTYPES) 533 | @pytest.mark.parametrize("dtype_p", DTYPES) 534 | def test_component_map(dtype_cc, dtype_p): 535 | shape = (128,128,128) 536 | cc_labels = np.random.randint(0, 100, size=shape).astype(dtype_cc) 537 | parent_labels = (cc_labels + 1).astype(dtype_p) 538 | 539 | mapping = fastremap.component_map(cc_labels, parent_labels) 540 | for k,v in mapping.items(): 541 | assert k == v - 1 542 | 543 | mapping = fastremap.component_map([ 1, 2, 3, 4 ], [ 5, 5, 6, 7 ]) 544 | assert mapping == { 1: 5, 2: 5, 3: 6, 4: 7 } 545 | 546 | mapping = fastremap.component_map([], []) 547 | 548 | 549 | @pytest.mark.parametrize("dtype_cc", DTYPES) 550 | @pytest.mark.parametrize("dtype_p", DTYPES) 551 | def test_inverse_component_map(dtype_cc, dtype_p): 552 | mapping = fastremap.inverse_component_map([ 1, 2, 1, 3 ], [ 4, 4, 5, 6 ]) 553 | assert mapping == { 1: [ 4, 5 ], 2: [ 4 ], 3: [ 6 ] } 554 | 555 | mapping = fastremap.inverse_component_map([ 1, 1, 1, 3 ], [ 4, 4, 5, 6 ]) 556 | assert mapping == {1: [4, 5], 3: [6]} 557 | 558 | mapping = fastremap.inverse_component_map([ 1, 1, 1, 1 ], [ 4, 2, 5, 6 ]) 559 | assert mapping == {1: [2, 4, 5, 6]} 560 | 561 | mapping = fastremap.inverse_component_map([], []) 562 | 563 | def test_point_cloud(): 564 | x = np.ones((2,2,2), dtype=np.uint8) 565 | ptc = fastremap.point_cloud(x) 566 | assert len(ptc) == 1 567 | 568 | gt = np.array([ 569 | [0, 0, 0], 570 | [0, 0, 1], 571 | [0, 1, 0], 572 | [0, 1, 1], 573 | [1, 0, 0], 574 | [1, 0, 1], 575 | [1, 1, 0], 576 | [1, 1, 1], 577 | ]) 578 | assert np.all(ptc[1] == gt) 579 | 580 | x[0,0,0] = 2 581 | ptc = fastremap.point_cloud(x) 582 | assert len(ptc) == 2 583 | assert np.all(ptc[1] == gt[1:,:]) 584 | assert np.all(ptc[2] == gt[:1,:]) 585 | 586 | x[1,1,1] = 3 587 | ptc = fastremap.point_cloud(x) 588 | assert len(ptc) == 3 589 | assert np.all(ptc[1] == gt[1:7,:]) 590 | assert np.all(ptc[2] == gt[:1,:]) 591 | assert np.all(ptc[3] == gt[7:,:]) 592 | 593 | x = np.ones((0,0,0), dtype=np.uint8) 594 | ptc = fastremap.point_cloud(x) 595 | assert len(ptc) == 0 596 | 597 | 598 | @pytest.mark.parametrize("dtype", DTYPES) 599 | @pytest.mark.parametrize("input_order", ['C','F']) 600 | @pytest.mark.parametrize("output_order", ['F', 'C']) 601 | @pytest.mark.parametrize("size", [16,64,128]) 602 | @pytest.mark.parametrize("chunk_size", [4,8,16]) 603 | def test_tobytes(size, chunk_size, dtype, input_order, output_order): 604 | cs = chunk_size 605 | image = np.arange(size*size*size, dtype=dtype).reshape((size,size,size), order=input_order) 606 | 607 | res1 = fastremap.tobytes(image, (cs,cs,cs), order=output_order) 608 | 609 | N = size // cs 610 | 611 | res2 = [] 612 | for z in range(N): 613 | for y in range(N): 614 | for x in range(N): 615 | cutout = image[x*cs:(x+1)*cs, y*cs:(y+1)*cs, z*cs:(z+1)*cs] 616 | res2.append(cutout.tobytes(output_order)) 617 | 618 | for i, (enc1, enc2) in enumerate(zip(res1, res2)): 619 | assert enc1 == enc2, i 620 | 621 | @pytest.mark.parametrize("order", ['C','F']) 622 | def test_tobytes_misaligned(order): 623 | size = 128 624 | cs = 32 625 | image = np.arange(size*size*size, dtype=np.uint8).reshape((size,size,size), order=order) 626 | 627 | res1 = fastremap.tobytes(image, (cs,cs,cs)) 628 | 629 | cs = 17 630 | try: 631 | res1 = fastremap.tobytes(image, (cs,cs,cs)) 632 | assert False 633 | except ValueError: 634 | pass 635 | 636 | def test_narrow_dtype(): 637 | assert fastremap.narrow_dtype(np.uint64) == np.uint32 638 | assert fastremap.narrow_dtype(np.uint32) == np.uint16 639 | assert fastremap.narrow_dtype(np.uint16) == np.uint8 640 | assert fastremap.narrow_dtype(np.uint8) == np.uint8 641 | 642 | assert fastremap.narrow_dtype(np.int64) == np.int32 643 | assert fastremap.narrow_dtype(np.int32) == np.int16 644 | assert fastremap.narrow_dtype(np.int16) == np.int8 645 | assert fastremap.narrow_dtype(np.int8) == np.int8 646 | 647 | assert fastremap.narrow_dtype(np.uint64, exotics=True) == np.uint32 648 | assert fastremap.narrow_dtype(np.uint32, exotics=True) == np.uint16 649 | assert fastremap.narrow_dtype(np.uint16, exotics=True) == np.uint8 650 | assert fastremap.narrow_dtype(np.uint8, exotics=True) == np.uint8 651 | 652 | assert fastremap.narrow_dtype(np.int64, exotics=True) == np.int32 653 | assert fastremap.narrow_dtype(np.int32, exotics=True) == np.int16 654 | assert fastremap.narrow_dtype(np.int16, exotics=True) == np.int8 655 | assert fastremap.narrow_dtype(np.int8, exotics=True) == np.int8 656 | 657 | assert fastremap.narrow_dtype(np.float64) == np.float32 658 | assert fastremap.narrow_dtype(np.float32) == np.float32 659 | 660 | assert fastremap.narrow_dtype(np.float64, exotics=True) == np.float32 661 | assert fastremap.narrow_dtype(np.float32, exotics=True) == np.float16 662 | 663 | assert fastremap.narrow_dtype(np.complex128) == np.complex64 664 | assert fastremap.narrow_dtype(np.complex64) == np.complex64 665 | 666 | def test_widen_dtype(): 667 | assert fastremap.widen_dtype(np.uint64) == np.uint64 668 | assert fastremap.widen_dtype(np.uint32) == np.uint64 669 | assert fastremap.widen_dtype(np.uint16) == np.uint32 670 | assert fastremap.widen_dtype(np.uint8) == np.uint16 671 | 672 | assert fastremap.widen_dtype(np.int64) == np.int64 673 | assert fastremap.widen_dtype(np.int32) == np.int64 674 | assert fastremap.widen_dtype(np.int16) == np.int32 675 | assert fastremap.widen_dtype(np.int8) == np.int16 676 | 677 | assert fastremap.widen_dtype(np.float64, exotics=True) == np.longdouble 678 | assert fastremap.widen_dtype(np.float64) == np.float64 679 | assert fastremap.widen_dtype(np.float32) == np.float64 680 | assert fastremap.widen_dtype(np.float16) == np.float32 681 | 682 | assert fastremap.widen_dtype(np.complex64) == np.complex64 683 | assert fastremap.widen_dtype(np.complex64, exotics=True) == np.complex128 684 | assert fastremap.widen_dtype(np.complex128, exotics=True) == np.clongdouble 685 | assert fastremap.widen_dtype(np.clongdouble, exotics=True) == np.clongdouble 686 | 687 | def test_unique_order(): 688 | data = np.array([ 689 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 690 | 1704, 1704, 1704, 1704, 1704, 1704, 106, 106, 106, 106, 106, 691 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 692 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 693 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 694 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 695 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 696 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 697 | 106, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 698 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 699 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 700 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 701 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 702 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 703 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 704 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 705 | 1704, 1704, 1704, 1704, 1704, 106, 106, 106, 106, 106, 106, 706 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 707 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 708 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 709 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 710 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 711 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 712 | 106, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 713 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 714 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 715 | 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 1704, 716 | 1704, 1704, 1704, 106, 106, 106, 106, 106, 106, 106, 106, 717 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 718 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 719 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 720 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 721 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 722 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 723 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 724 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 725 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 726 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 727 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 728 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 729 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 730 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 731 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 732 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 733 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 734 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 735 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 736 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 737 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 738 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 739 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 740 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 741 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 742 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 743 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 744 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 745 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 746 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 747 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 748 | 106, 106, 106, 106, 106, 106, 106, 1873, 106, 106, 106, 749 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 750 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 751 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 752 | 106, 106, 106, 106, 1873, 1873, 106, 106, 106, 106, 106, 753 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 754 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 755 | 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 756 | 106, 1873, 1873, 106, 106, 106, 106, 106, 106, 106, 106, 757 | 106, 106, 106, 106, 106, 106, 106 758 | ]) 759 | tup = fastremap.unique(data, return_counts=True, return_index=True, return_inverse=True) 760 | tup2 = np.unique(data, return_counts=True, return_index=True, return_inverse=True) 761 | 762 | for res, ans in zip(tup, tup2): 763 | assert np.all(res == ans) 764 | -------------------------------------------------------------------------------- /build_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | container_name=$(basename $(dirname $(realpath $0))) 3 | echo "Building seunglab/$container_name" 4 | docker build . -f manylinux1.Dockerfile --tag "seunglab/$container_name:manylinux1" 5 | docker build . -f manylinux2010.Dockerfile --tag "seunglab/$container_name:manylinux2010" 6 | docker build . -f manylinux2014.Dockerfile --tag "seunglab/$container_name:manylinux2014" 7 | docker run -v $PWD/dist:/output "seunglab/$container_name:manylinux1" /bin/bash -c "cp -r wheelhouse/* /output" 8 | docker run -v $PWD/dist:/output "seunglab/$container_name:manylinux2010" /bin/bash -c "cp -r wheelhouse/* /output" 9 | docker run -v $PWD/dist:/output "seunglab/$container_name:manylinux2014" /bin/bash -c "cp -r wheelhouse/* /output" -------------------------------------------------------------------------------- /manylinux1.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux1_x86_64 2 | LABEL maintainer="William Silversmith" 3 | 4 | ADD . /build 5 | 6 | WORKDIR "/build" 7 | 8 | ENV CXX "g++" 9 | 10 | RUN rm -rf *.so build __pycache__ dist 11 | 12 | # RUN /opt/python/cp27-cp27m/bin/pip2.7 install pip --upgrade 13 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install pip --upgrade 14 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install pip --upgrade 15 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install pip --upgrade 16 | RUN /opt/python/cp38-cp38/bin/pip3.8 install pip --upgrade 17 | 18 | # RUN /opt/python/cp27-cp27m/bin/pip2.7 install oldest-supported-numpy 19 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install oldest-supported-numpy 20 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install oldest-supported-numpy 21 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install oldest-supported-numpy 22 | RUN /opt/python/cp38-cp38/bin/pip3.8 install oldest-supported-numpy 23 | 24 | # RUN /opt/python/cp27-cp27m/bin/python2.7 setup.py bdist_wheel 25 | # RUN /opt/python/cp35-cp35m/bin/python3.5 setup.py bdist_wheel 26 | RUN /opt/python/cp36-cp36m/bin/python3.6 setup.py bdist_wheel 27 | RUN /opt/python/cp37-cp37m/bin/python3.7 setup.py bdist_wheel 28 | RUN /opt/python/cp38-cp38/bin/python3.8 setup.py bdist_wheel 29 | 30 | RUN for whl in `ls dist/*.whl`; do auditwheel repair $whl; done 31 | -------------------------------------------------------------------------------- /manylinux2010.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2010_x86_64 2 | MAINTAINER William Silversmith 3 | 4 | ADD . /build 5 | WORKDIR "/build" 6 | 7 | ENV CXX "g++" 8 | 9 | RUN rm -rf *.so build __pycache__ dist 10 | 11 | # RUN /opt/python/cp27-cp27m/bin/pip2.7 install pip --upgrade 12 | # RUN /opt/python/cp27-cp27m/bin/pip2.7 install oldest-supported-numpy 13 | # RUN /opt/python/cp27-cp27m/bin/pip2.7 install -r requirements_dev.txt 14 | # RUN /opt/python/cp27-cp27m/bin/python2.7 setup.py develop 15 | # RUN /opt/python/cp27-cp27m/bin/python2.7 -m pytest -v -x automated_test.py 16 | 17 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install pip --upgrade 18 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install oldest-supported-numpy 19 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install -r requirements_dev.txt 20 | # RUN /opt/python/cp35-cp35m/bin/python3.5 setup.py develop 21 | # RUN /opt/python/cp35-cp35m/bin/python3.5 -m pytest -v -x automated_test.py 22 | 23 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install pip --upgrade 24 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install oldest-supported-numpy 25 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install -r requirements_dev.txt 26 | RUN /opt/python/cp36-cp36m/bin/python3.6 setup.py develop 27 | RUN /opt/python/cp36-cp36m/bin/python3.6 -m pytest -v -x automated_test.py 28 | 29 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install pip --upgrade 30 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install oldest-supported-numpy 31 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install -r requirements_dev.txt 32 | RUN /opt/python/cp37-cp37m/bin/python3.7 setup.py develop 33 | RUN /opt/python/cp37-cp37m/bin/python3.7 -m pytest -v -x automated_test.py 34 | 35 | RUN /opt/python/cp38-cp38/bin/pip3.8 install pip --upgrade 36 | RUN /opt/python/cp38-cp38/bin/pip3.8 install oldest-supported-numpy 37 | RUN /opt/python/cp38-cp38/bin/pip3.8 install -r requirements_dev.txt # no binaries for scipy 38 | RUN /opt/python/cp38-cp38/bin/python3.8 setup.py develop 39 | RUN /opt/python/cp38-cp38/bin/python3.8 -m pytest -v -x automated_test.py 40 | 41 | # RUN /opt/python/cp27-cp27m/bin/python2.7 setup.py bdist_wheel 42 | # RUN /opt/python/cp35-cp35m/bin/python3.5 setup.py bdist_wheel 43 | RUN /opt/python/cp36-cp36m/bin/python3.6 setup.py bdist_wheel 44 | RUN /opt/python/cp37-cp37m/bin/python3.7 setup.py bdist_wheel 45 | RUN /opt/python/cp38-cp38/bin/python3.8 setup.py bdist_wheel 46 | 47 | RUN for whl in `ls dist/*.whl`; do auditwheel repair $whl --plat manylinux2010_x86_64; done -------------------------------------------------------------------------------- /manylinux2014.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2014_x86_64 2 | MAINTAINER William Silversmith 3 | 4 | ADD . /build 5 | 6 | WORKDIR "/build" 7 | 8 | ENV CXX "g++" 9 | 10 | RUN rm -rf *.so build __pycache__ dist 11 | 12 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install pip --upgrade 13 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install oldest-supported-numpy 14 | # RUN /opt/python/cp35-cp35m/bin/pip3.5 install -r requirements_dev.txt 15 | # RUN /opt/python/cp35-cp35m/bin/python3.5 setup.py develop 16 | # RUN /opt/python/cp35-cp35m/bin/python3.5 -m pytest -v -x automated_test.py 17 | 18 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install pip --upgrade 19 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install oldest-supported-numpy 20 | RUN /opt/python/cp36-cp36m/bin/pip3.6 install -r requirements_dev.txt 21 | RUN /opt/python/cp36-cp36m/bin/python3.6 setup.py develop 22 | RUN /opt/python/cp36-cp36m/bin/python3.6 -m pytest -v -x automated_test.py 23 | 24 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install pip --upgrade 25 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install oldest-supported-numpy 26 | RUN /opt/python/cp37-cp37m/bin/pip3.7 install -r requirements_dev.txt 27 | RUN /opt/python/cp37-cp37m/bin/python3.7 setup.py develop 28 | RUN /opt/python/cp37-cp37m/bin/python3.7 -m pytest -v -x automated_test.py 29 | 30 | RUN /opt/python/cp38-cp38/bin/pip3.8 install pip --upgrade 31 | RUN /opt/python/cp38-cp38/bin/pip3.8 install oldest-supported-numpy 32 | RUN /opt/python/cp38-cp38/bin/pip3.8 install -r requirements_dev.txt 33 | RUN /opt/python/cp38-cp38/bin/python3.8 setup.py develop 34 | RUN /opt/python/cp38-cp38/bin/python3.8 -m pytest -v -x automated_test.py 35 | 36 | RUN /opt/python/cp39-cp39/bin/pip3.9 install pip --upgrade 37 | RUN /opt/python/cp39-cp39/bin/pip3.9 install oldest-supported-numpy pytest 38 | RUN /opt/python/cp39-cp39/bin/pip3.9 install -r requirements_dev.txt 39 | RUN /opt/python/cp39-cp39/bin/python3.9 setup.py develop 40 | RUN /opt/python/cp39-cp39/bin/python3.9 -m pytest -v automated_test.py 41 | 42 | # RUN /opt/python/cp35-cp35m/bin/python3.5 setup.py bdist_wheel 43 | RUN /opt/python/cp36-cp36m/bin/python3.6 setup.py bdist_wheel 44 | RUN /opt/python/cp37-cp37m/bin/python3.7 setup.py bdist_wheel 45 | RUN /opt/python/cp38-cp38/bin/python3.8 setup.py bdist_wheel 46 | RUN /opt/python/cp39-cp39/bin/python3.9 setup.py bdist_wheel 47 | 48 | RUN for whl in `ls dist/*.whl`; do auditwheel repair $whl --plat manylinux2014_x86_64; done 49 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | "numpy", 6 | "cython", 7 | ] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | cython 2 | pytest 3 | tox 4 | twine -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = fastremap 3 | url = https://github.com/seung-lab/fastremap/ 4 | summary = Remap, mask, renumber, unique, and in-place transposition of 3D labeled images. Point cloud too. 5 | description_file_content_type = text/markdown 6 | description_file = README.md 7 | author = William Silversmith 8 | author_email = ws9@princeton.edu 9 | home_page = https://github.com/seung-lab/fastremap/ 10 | licenses = LGPLv3 11 | classifier = 12 | Intended Audience :: Developers 13 | Development Status :: 5 - Production/Stable 14 | License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) 15 | Programming Language :: Python 16 | Programming Language :: Python :: 3 17 | Programming Language :: Python :: 3.7 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Programming Language :: Python :: 3.11 22 | Topic :: Utilities 23 | 24 | [global] 25 | setup_hooks = 26 | pbr.hooks.setup_hook 27 | 28 | [files] 29 | packages = fastremap 30 | 31 | [bdist_wheel] 32 | universal = 0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import setuptools 3 | import sys 4 | 5 | class NumpyImport: 6 | def __repr__(self): 7 | import numpy as np 8 | 9 | return np.get_include() 10 | 11 | __fspath__ = __repr__ 12 | 13 | 14 | # NOTE: If fastremap.cpp does not exist, you must run 15 | # cython -3 --cplus fastremap.pyx 16 | 17 | extra_compile_args = [ 18 | '-std=c++11', '-O3', 19 | ] 20 | 21 | if sys.platform == 'darwin': 22 | extra_compile_args += ['-stdlib=libc++', '-mmacosx-version-min=10.9'] 23 | 24 | setuptools.setup( 25 | setup_requires=['pbr', 'cython', 'numpy'], 26 | python_requires=">=3.9,<4.0", 27 | pbr=True, 28 | ext_modules=[ 29 | setuptools.Extension( 30 | 'fastremap', 31 | sources=['src/fastremap.pyx'], 32 | depends=[], 33 | language='c++', 34 | language_level=3, 35 | include_dirs=["src", str(NumpyImport())], 36 | extra_compile_args=extra_compile_args, 37 | ) 38 | ], 39 | long_description_content_type='text/markdown', 40 | ) 41 | -------------------------------------------------------------------------------- /src/fastremap.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | from libcpp.utility cimport pair 3 | 4 | cdef extern from "ska_flat_hash_map.hpp" namespace "ska" nogil: 5 | cdef cppclass flat_hash_map[T, U, HASH=*, PRED=*, ALLOCATOR=*]: 6 | ctypedef T key_type 7 | ctypedef U mapped_type 8 | ctypedef pair[const T, U] value_type 9 | ctypedef ALLOCATOR allocator_type 10 | 11 | # these should really be allocator_type.size_type and 12 | # allocator_type.difference_type to be true to the C++ definition 13 | # but cython doesn't support deferred access on template arguments 14 | ctypedef size_t size_type 15 | ctypedef ptrdiff_t difference_type 16 | 17 | cppclass iterator 18 | cppclass iterator: 19 | iterator() except + 20 | iterator(iterator&) except + 21 | # correct would be value_type& but this does not work 22 | # well with cython's code gen 23 | pair[T, U]& operator*() 24 | iterator operator++() 25 | iterator operator--() 26 | iterator operator++(int) 27 | iterator operator--(int) 28 | bint operator==(iterator) 29 | bint operator==(const_iterator) 30 | bint operator!=(iterator) 31 | bint operator!=(const_iterator) 32 | cppclass const_iterator: 33 | const_iterator() except + 34 | const_iterator(iterator&) except + 35 | operator=(iterator&) except + 36 | # correct would be const value_type& but this does not work 37 | # well with cython's code gen 38 | const pair[T, U]& operator*() 39 | const_iterator operator++() 40 | const_iterator operator--() 41 | const_iterator operator++(int) 42 | const_iterator operator--(int) 43 | bint operator==(iterator) 44 | bint operator==(const_iterator) 45 | bint operator!=(iterator) 46 | bint operator!=(const_iterator) 47 | 48 | flat_hash_map() except + 49 | flat_hash_map(flat_hash_map&) except + 50 | #flat_hash_map(key_compare&) 51 | U& operator[](const T&) 52 | #flat_hash_map& operator=(flat_hash_map&) 53 | bint operator==(flat_hash_map&, flat_hash_map&) 54 | bint operator!=(flat_hash_map&, flat_hash_map&) 55 | bint operator<(flat_hash_map&, flat_hash_map&) 56 | bint operator>(flat_hash_map&, flat_hash_map&) 57 | bint operator<=(flat_hash_map&, flat_hash_map&) 58 | bint operator>=(flat_hash_map&, flat_hash_map&) 59 | U& at(const T&) except + 60 | const U& const_at "at"(const T&) except + 61 | iterator begin() 62 | const_iterator const_begin "begin"() 63 | const_iterator cbegin() 64 | void clear() 65 | size_t count(const T&) 66 | bint empty() 67 | iterator end() 68 | const_iterator const_end "end"() 69 | const_iterator cend() 70 | pair[iterator, iterator] equal_range(const T&) 71 | pair[const_iterator, const_iterator] const_equal_range "equal_range"(const T&) 72 | iterator erase(iterator) 73 | iterator const_erase "erase"(const_iterator) 74 | iterator erase(const_iterator, const_iterator) 75 | size_t erase(const T&) 76 | iterator find(const T&) 77 | const_iterator const_find "find"(const T&) 78 | pair[iterator, bint] insert(const pair[T, U]&) except + 79 | iterator insert(const_iterator, const pair[T, U]&) except + 80 | void insert[InputIt](InputIt, InputIt) except + 81 | #key_compare key_comp() 82 | iterator lower_bound(const T&) 83 | const_iterator const_lower_bound "lower_bound"(const T&) 84 | size_t max_size() 85 | size_t size() 86 | void swap(flat_hash_map&) 87 | iterator upper_bound(const T&) 88 | const_iterator const_upper_bound "upper_bound"(const T&) 89 | #value_compare value_comp() 90 | void max_load_factor(float) 91 | float max_load_factor() 92 | float load_factor() 93 | void rehash(size_t) 94 | void reserve(size_t) 95 | size_t bucket_count() 96 | size_t max_bucket_count() 97 | size_t bucket_size(size_t) 98 | size_t bucket(const T&) 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/fastremap.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | """ 3 | Functions related to remapping image volumes. 4 | 5 | Renumber volumes into smaller data types, mask out labels 6 | or their complement, and remap the values of image volumes. 7 | 8 | This module also constains the facilities for performing 9 | and in-place matrix transposition for up to 4D arrays. This is 10 | helpful for converting between C and Fortran order in memory 11 | constrained environments when format shifting. 12 | 13 | Author: William Silversmith 14 | Affiliation: Seung Lab, Princeton Neuroscience Institute 15 | Date: August 2018 - May 2025 16 | """ 17 | from typing import Sequence, List 18 | cimport cython 19 | from libc.stdint cimport ( 20 | uint8_t, uint16_t, uint32_t, uint64_t, 21 | int8_t, int16_t, int32_t, int64_t, 22 | uintptr_t 23 | ) 24 | cimport fastremap 25 | 26 | from collections import defaultdict 27 | from functools import reduce 28 | import operator 29 | 30 | import numpy as np 31 | cimport numpy as cnp 32 | cnp.import_array() 33 | 34 | from libcpp.vector cimport vector 35 | 36 | ctypedef fused UINT: 37 | uint8_t 38 | uint16_t 39 | uint32_t 40 | uint64_t 41 | 42 | ctypedef fused ALLINT: 43 | UINT 44 | int8_t 45 | int16_t 46 | int32_t 47 | int64_t 48 | 49 | ctypedef fused ALLINT_2: 50 | ALLINT 51 | 52 | ctypedef fused NUMBER: 53 | ALLINT 54 | float 55 | double 56 | 57 | ctypedef fused COMPLEX_NUMBER: 58 | NUMBER 59 | float complex 60 | 61 | cdef extern from "ipt.hpp" namespace "pyipt": 62 | cdef void _ipt2d[T](T* arr, size_t sx, size_t sy) 63 | cdef void _ipt3d[T]( 64 | T* arr, size_t sx, size_t sy, size_t sz 65 | ) 66 | cdef void _ipt4d[T]( 67 | T* arr, size_t sx, size_t sy, size_t sz, size_t sw 68 | ) 69 | 70 | def minmax(arr): 71 | """ 72 | Returns (min(arr), max(arr)) computed in a single pass. 73 | Returns (None, None) if array is size zero. 74 | """ 75 | return _minmax(reshape(arr, (arr.size,))) 76 | 77 | def _minmax(cnp.ndarray[NUMBER, ndim=1] arr): 78 | cdef size_t i = 0 79 | cdef size_t size = arr.size 80 | 81 | if size == 0: 82 | return None, None 83 | 84 | cdef NUMBER minval = arr[0] 85 | cdef NUMBER maxval = arr[0] 86 | 87 | for i in range(1, size): 88 | if minval > arr[i]: 89 | minval = arr[i] 90 | if maxval < arr[i]: 91 | maxval = arr[i] 92 | 93 | return minval, maxval 94 | 95 | def match_array_orders(*arrs, order="K"): 96 | if len(arrs) == 0: 97 | return [] 98 | 99 | if order == "C" or (order == "K" and arrs[0].flags.c_contiguous): 100 | return [ np.ascontiguousarray(arr) for arr in arrs ] 101 | else: 102 | return [ np.asfortranarray(arr) for arr in arrs ] 103 | 104 | @cython.boundscheck(False) 105 | @cython.wraparound(False) # turn off negative index wrapping for entire function 106 | @cython.nonecheck(False) 107 | def indices(cnp.ndarray[NUMBER, cast=True, ndim=1] arr, NUMBER value): 108 | """ 109 | Search through an array and identify the indices where value matches the array. 110 | """ 111 | cdef vector[uint64_t] all_indices 112 | cdef uint64_t i = 0 113 | cdef uint64_t size = arr.size 114 | 115 | for i in range(size): 116 | if arr[i] == value: 117 | all_indices.push_back(i) 118 | 119 | return np.asarray(all_indices, dtype=np.uint64) 120 | 121 | def renumber(arr, start=1, preserve_zero=True, in_place=False): 122 | """ 123 | renumber(arr, start=1, preserve_zero=True, in_place=False) 124 | 125 | Given an array of integers, renumber all the unique values starting 126 | from 1. This can allow us to reduce the size of the data width required 127 | to represent it. 128 | 129 | arr: A numpy array 130 | start (default: 1): Start renumbering from this value 131 | preserve_zero (default: True): Don't renumber zero. 132 | in_place (default: False): Perform the renumbering in-place to avoid 133 | an extra copy. This option depends on a fortran or C contiguous 134 | array. A copy will be made if the array is not contiguous. 135 | 136 | Return: a renumbered array, dict with remapping of oldval => newval 137 | """ 138 | if arr.size == 0: 139 | return arr, {} 140 | 141 | if arr.dtype == bool and preserve_zero: 142 | return arr, { 0: 0, 1: start } 143 | elif arr.dtype == bool: 144 | arr = arr.view(np.uint8) 145 | 146 | cdef int nbytes = np.dtype(arr.dtype).itemsize 147 | 148 | shape = arr.shape 149 | order = 'F' if arr.flags['F_CONTIGUOUS'] else 'C' 150 | in_place = in_place and (arr.flags['F_CONTIGUOUS'] or arr.flags['C_CONTIGUOUS']) 151 | 152 | if not in_place: 153 | arr = np.copy(arr, order=order) 154 | 155 | arr = np.lib.stride_tricks.as_strided(arr, shape=(arr.size,), strides=(nbytes,)) 156 | arr, remap_dict = _renumber(arr, start, preserve_zero) 157 | arr = reshape(arr, shape, order) 158 | 159 | return arr, remap_dict 160 | 161 | def reshape(arr, shape, order=None): 162 | """ 163 | If the array is contiguous, attempt an in place reshape 164 | rather than potentially making a copy. 165 | 166 | Required: 167 | arr: The input numpy array. 168 | shape: The desired shape (must be the same size as arr) 169 | 170 | Optional: 171 | order: 'C', 'F', or None (determine automatically) 172 | 173 | Returns: reshaped array 174 | """ 175 | if order is None: 176 | if arr.flags['F_CONTIGUOUS']: 177 | order = 'F' 178 | elif arr.flags['C_CONTIGUOUS']: 179 | order = 'C' 180 | else: 181 | return arr.reshape(shape) 182 | 183 | cdef int nbytes = np.dtype(arr.dtype).itemsize 184 | 185 | if order == 'C': 186 | strides = [ reduce(operator.mul, shape[i:]) * nbytes for i in range(1, len(shape)) ] 187 | strides += [ nbytes ] 188 | return np.lib.stride_tricks.as_strided(arr, shape=shape, strides=strides) 189 | else: 190 | strides = [ reduce(operator.mul, shape[:i]) * nbytes for i in range(1, len(shape)) ] 191 | strides = [ nbytes ] + strides 192 | return np.lib.stride_tricks.as_strided(arr, shape=shape, strides=strides) 193 | 194 | @cython.boundscheck(False) 195 | @cython.wraparound(False) # turn off negative index wrapping for entire function 196 | @cython.nonecheck(False) 197 | def _renumber(cnp.ndarray[NUMBER, cast=True, ndim=1] arr, int64_t start=1, preserve_zero=True): 198 | """ 199 | renumber(arr, int64_t start=1, preserve_zero=True) 200 | 201 | Given an array of integers, renumber all the unique values starting 202 | from 1. This can allow us to reduce the size of the data width required 203 | to represent it. 204 | 205 | arr: A numpy array 206 | start (default: 1): Start renumbering from this value 207 | preserve_zero (default ): 208 | 209 | Return: a renumbered array, dict with remapping of oldval => newval 210 | """ 211 | cdef flat_hash_map[NUMBER, NUMBER] remap_dict 212 | 213 | if arr.size == 0: 214 | return refit(np.zeros((0,), dtype=arr.dtype), 0), {} 215 | 216 | remap_dict.reserve(1024) 217 | 218 | if preserve_zero: 219 | remap_dict[0] = 0 220 | 221 | cdef NUMBER[:] arrview = arr 222 | 223 | cdef NUMBER remap_id = start 224 | cdef NUMBER elem 225 | 226 | # some value that isn't the first value 227 | # and won't cause an overflow 228 | cdef NUMBER last_elem = (~arr[0]) 229 | cdef NUMBER last_remap_id = start 230 | 231 | cdef size_t size = arr.size 232 | cdef size_t i = 0 233 | 234 | for i in range(size): 235 | elem = arrview[i] 236 | 237 | if elem == last_elem: 238 | arrview[i] = last_remap_id 239 | continue 240 | 241 | if remap_dict.find(elem) == remap_dict.end(): 242 | arrview[i] = remap_id 243 | remap_dict[elem] = remap_id 244 | remap_id += 1 245 | else: 246 | arrview[i] = remap_dict[elem] 247 | 248 | last_elem = elem 249 | last_remap_id = arrview[i] 250 | 251 | factor = remap_id 252 | if abs(start) > abs(factor): 253 | factor = start 254 | 255 | return refit(arr, factor), { k:v for k,v in remap_dict } 256 | 257 | def refit(arr, value=None, increase_only=False, exotics=False): 258 | """ 259 | Resize the array to the smallest dtype of the 260 | same kind that will fit a given value. 261 | 262 | For example, if the input array is uint8 and 263 | the value is 2^20 return the array as a 264 | uint32. 265 | 266 | Works for standard floating, integer, 267 | unsigned integer, and complex types. 268 | 269 | arr: numpy array 270 | value: value to fit array to. if None, 271 | it is set to the value of the absolutely 272 | larger of the min and max value in the array. 273 | increase_only: if true, only resize the array if it can't 274 | contain value. if false, always resize to the 275 | smallest size that fits. 276 | exotics: if true, allow e.g. half precision floats (16-bit) 277 | or double complex (128-bit) 278 | 279 | Return: refitted array 280 | """ 281 | 282 | if value is None: 283 | min_value, max_value = minmax(arr) 284 | if min_value is None or max_value is None: 285 | min_value = 0 286 | max_value = 0 287 | 288 | if abs(max_value) > abs(min_value): 289 | value = max_value 290 | else: 291 | value = min_value 292 | 293 | dtype = fit_dtype(arr.dtype, value, exotics=exotics) 294 | 295 | if increase_only and np.dtype(dtype).itemsize <= np.dtype(arr.dtype).itemsize: 296 | return arr 297 | elif dtype == arr.dtype: 298 | return arr 299 | return arr.astype(dtype) 300 | 301 | def fit_dtype(dtype, value, exotics=False): 302 | """ 303 | Find the smallest dtype of the 304 | same kind that will fit a given value. 305 | 306 | For example, if the input array is uint8 and 307 | the value is 2^20 return the array as a 308 | uint32. 309 | 310 | Works for standard floating, integer, 311 | unsigned integer, and complex types. 312 | 313 | exotics: if True, allow fitting to 314 | e.g. float16 (half-precision, 16-bits) 315 | or double complex (which takes 128-bits). 316 | 317 | Return: refitted array 318 | """ 319 | dtype = np.dtype(dtype) 320 | if np.issubdtype(dtype, np.floating): 321 | if exotics: 322 | sequence = [ np.float16, np.float32, np.float64 ] 323 | else: 324 | sequence = [ np.float32, np.float64 ] 325 | infofn = np.finfo 326 | elif np.issubdtype(dtype, np.unsignedinteger): 327 | sequence = [ np.uint8, np.uint16, np.uint32, np.uint64 ] 328 | infofn = np.iinfo 329 | if value < 0: 330 | raise ValueError(str(value) + " is negative but unsigned data type {} is selected.".format(dtype)) 331 | elif np.issubdtype(dtype, np.complexfloating): 332 | if exotics: 333 | sequence = [ np.csingle, np.cdouble ] 334 | else: 335 | sequence = [ np.csingle ] 336 | infofn = np.finfo 337 | elif np.issubdtype(dtype, np.integer): 338 | sequence = [ np.int8, np.int16, np.int32, np.int64 ] 339 | infofn = np.iinfo 340 | else: 341 | raise ValueError( 342 | "Unsupported dtype: {} Only standard floats, integers, and complex types are supported.".format(dtype) 343 | ) 344 | 345 | test_value = np.real(value) 346 | if abs(np.real(value)) < abs(np.imag(value)): 347 | test_value = np.imag(value) 348 | 349 | for seq_dtype in sequence: 350 | if test_value >= 0 and infofn(seq_dtype).max >= test_value: 351 | return seq_dtype 352 | elif test_value < 0 and infofn(seq_dtype).min <= test_value: 353 | return seq_dtype 354 | 355 | raise ValueError("Unable to find a compatible dtype for {} that can fit {}".format( 356 | dtype, value 357 | )) 358 | 359 | def widen_dtype(dtype, exotics:bool = False): 360 | """ 361 | Widen the given dtype to the next size 362 | of the same type. For example, 363 | int8 -> int16 or uint32 -> uint64 364 | 365 | 64-bit types will map to themselves. 366 | 367 | Return: upgraded dtype 368 | """ 369 | dtype = np.dtype(dtype) 370 | 371 | if np.issubdtype(dtype, np.floating): 372 | sequence = [ np.float16, np.float32, np.float64 ] 373 | if exotics: 374 | sequence += [ np.longdouble ] 375 | elif np.issubdtype(dtype, np.unsignedinteger): 376 | sequence = [ np.uint8, np.uint16, np.uint32, np.uint64 ] 377 | elif np.issubdtype(dtype, np.complexfloating): 378 | sequence = [ np.complex64 ] 379 | if exotics: 380 | sequence += [ np.complex128, np.clongdouble ] 381 | elif np.issubdtype(dtype, np.integer): 382 | sequence = [ np.int8, np.int16, np.int32, np.int64 ] 383 | elif np.issubdtype(dtype, (np.intp, np.uintp)): 384 | return dtype 385 | elif exotics: 386 | raise ValueError( 387 | f"Unsupported dtype: {dtype}\n" 388 | ) 389 | else: 390 | raise ValueError( 391 | f"Unsupported dtype: {dtype}\n" 392 | f"Only standard floats, integers, and complex types are supported." 393 | f"For additional types (e.g. long double, complex128, clongdouble), enable exotics." 394 | ) 395 | 396 | idx = sequence.index(dtype) 397 | return sequence[min(idx+1, len(sequence) - 1)] 398 | 399 | def narrow_dtype(dtype, exotics:bool = False): 400 | """ 401 | Widen the given dtype to the next size 402 | of the same type. For example, 403 | int16 -> int8 or uint64 -> uint32 404 | 405 | 8-bit types will map to themselves. 406 | 407 | exotics: include float16 408 | 409 | Return: upgraded dtype 410 | """ 411 | dtype = np.dtype(dtype) 412 | if dtype.itemsize == 1: 413 | return dtype 414 | 415 | if np.issubdtype(dtype, np.floating): 416 | sequence = [ np.float32, np.float64, np.longdouble ] 417 | if exotics: 418 | sequence = [ np.float16 ] + sequence 419 | elif np.issubdtype(dtype, np.unsignedinteger): 420 | sequence = [ np.uint8, np.uint16, np.uint32, np.uint64 ] 421 | elif np.issubdtype(dtype, np.complexfloating): 422 | sequence = [ np.complex64, np.complex128, np.clongdouble ] 423 | elif np.issubdtype(dtype, np.integer): 424 | sequence = [ np.int8, np.int16, np.int32, np.int64 ] 425 | elif np.issubdtype(dtype, (np.intp, np.uintp)): 426 | return dtype 427 | else: 428 | raise ValueError( 429 | f"Unsupported dtype: {dtype}\n" 430 | f"Only standard floats, integers, and complex types are supported." 431 | ) 432 | 433 | idx = sequence.index(dtype) 434 | return sequence[max(idx-1, 0)] 435 | 436 | def mask(arr, labels, in_place=False, value=0): 437 | """ 438 | mask(arr, labels, in_place=False, value=0) 439 | 440 | Mask out designated labels in an array with the 441 | given value. 442 | 443 | Alternative implementation of: 444 | 445 | arr[np.isin(labels)] = value 446 | 447 | arr: an N-dimensional numpy array 448 | labels: an iterable list of integers 449 | in_place: if True, modify the input array to reduce 450 | memory consumption. 451 | value: mask value 452 | 453 | Returns: arr with `labels` masked out 454 | """ 455 | labels = { lbl: value for lbl in labels } 456 | return remap(arr, labels, preserve_missing_labels=True, in_place=in_place) 457 | 458 | def mask_except(arr, labels, in_place=False, value=0): 459 | """ 460 | mask_except(arr, labels, in_place=False, value=0) 461 | 462 | Mask out all labels except the provided list. 463 | 464 | Alternative implementation of: 465 | 466 | arr[~np.isin(labels)] = value 467 | 468 | arr: an N-dimensional numpy array 469 | labels: an iterable list of integers 470 | in_place: if True, modify the input array to reduce 471 | memory consumption. 472 | value: mask value 473 | 474 | Returns: arr with all labels except `labels` masked out 475 | """ 476 | shape = arr.shape 477 | 478 | if arr.flags['F_CONTIGUOUS']: 479 | order = 'F' 480 | else: 481 | order = 'C' 482 | 483 | if not in_place: 484 | arr = np.copy(arr, order=order) 485 | 486 | arr = reshape(arr, (arr.size,)) 487 | arr = _mask_except(arr, labels, value) 488 | return reshape(arr, shape, order=order) 489 | 490 | @cython.boundscheck(False) 491 | @cython.wraparound(False) # turn off negative index wrapping for entire function 492 | @cython.nonecheck(False) 493 | def _mask_except(cnp.ndarray[ALLINT] arr, list labels, ALLINT value): 494 | cdef ALLINT[:] arrview = arr 495 | cdef size_t i = 0 496 | cdef size_t size = arr.size 497 | 498 | if size == 0: 499 | return arr 500 | 501 | cdef flat_hash_map[ALLINT, ALLINT] tbl 502 | 503 | for label in labels: 504 | tbl[label] = label 505 | 506 | cdef ALLINT last_elem = arrview[0] 507 | cdef ALLINT last_elem_value = 0 508 | 509 | if tbl.find(last_elem) == tbl.end(): 510 | last_elem_value = value 511 | else: 512 | last_elem_value = last_elem 513 | 514 | for i in range(size): 515 | if arrview[i] == last_elem: 516 | arrview[i] = last_elem_value 517 | elif tbl.find(arrview[i]) == tbl.end(): 518 | last_elem = arrview[i] 519 | last_elem_value = value 520 | arrview[i] = value 521 | else: 522 | last_elem = arrview[i] 523 | last_elem_value = arrview[i] 524 | 525 | return arr 526 | 527 | def component_map(component_labels, parent_labels): 528 | """ 529 | Given two sets of images that have a surjective mapping between their labels, 530 | generate a dictionary for that mapping. 531 | 532 | For example, generate a mapping from connected components of labels to their 533 | parent labels. 534 | 535 | e.g. component_map([ 1, 2, 3, 4 ], [ 5, 5, 6, 7 ]) 536 | returns { 1: 5, 2: 5, 3: 6, 4: 7 } 537 | 538 | Returns: { $COMPONENT_LABEL: $PARENT_LABEL } 539 | """ 540 | if not isinstance(component_labels, np.ndarray): 541 | component_labels = np.array(component_labels) 542 | if not isinstance(parent_labels, np.ndarray): 543 | parent_labels = np.array(parent_labels) 544 | 545 | if component_labels.size == 0: 546 | return {} 547 | 548 | if component_labels.shape != parent_labels.shape: 549 | raise ValueError("The size of the inputs must match: {} vs {}".format( 550 | component_labels.shape, parent_labels.shape 551 | )) 552 | 553 | shape = component_labels.shape 554 | 555 | component_labels, parent_labels = match_array_orders( 556 | component_labels, parent_labels 557 | ) 558 | 559 | component_labels = reshape(component_labels, (component_labels.size,)) 560 | parent_labels = reshape(parent_labels, (parent_labels.size,)) 561 | return _component_map(component_labels, parent_labels) 562 | 563 | @cython.boundscheck(False) 564 | @cython.wraparound(False) # turn off negative index wrapping for entire function 565 | @cython.nonecheck(False) 566 | def _component_map( 567 | cnp.ndarray[ALLINT, ndim=1, cast=True] component_labels, 568 | cnp.ndarray[ALLINT_2, ndim=1, cast=True] parent_labels 569 | ): 570 | cdef size_t size = component_labels.size 571 | if size == 0: 572 | return {} 573 | 574 | cdef dict remap = {} 575 | cdef size_t i = 0 576 | 577 | cdef ALLINT last_label = component_labels[0] 578 | remap[component_labels[0]] = parent_labels[0] 579 | for i in range(size): 580 | if last_label == component_labels[i]: 581 | continue 582 | remap[component_labels[i]] = parent_labels[i] 583 | last_label = component_labels[i] 584 | 585 | return remap 586 | 587 | def inverse_component_map(parent_labels, component_labels): 588 | """ 589 | Given two sets of images that have a mapping between their labels, 590 | generate a dictionary for that mapping. 591 | 592 | For example, generate a mapping from connected components of labels to their 593 | parent labels. 594 | 595 | e.g. inverse_component_map([ 1, 2, 1, 3 ], [ 4, 4, 5, 6 ]) 596 | returns { 1: [ 4, 5 ], 2: [ 4 ], 3: [ 6 ] } 597 | 598 | Returns: { $PARENT_LABEL: [ $COMPONENT_LABELS, ... ] } 599 | """ 600 | if not isinstance(component_labels, np.ndarray): 601 | component_labels = np.array(component_labels) 602 | if not isinstance(parent_labels, np.ndarray): 603 | parent_labels = np.array(parent_labels) 604 | 605 | if component_labels.size == 0: 606 | return {} 607 | 608 | if component_labels.shape != parent_labels.shape: 609 | raise ValueError("The size of the inputs must match: {} vs {}".format( 610 | component_labels.shape, parent_labels.shape 611 | )) 612 | 613 | shape = component_labels.shape 614 | component_labels, parent_labels = match_array_orders( 615 | component_labels, parent_labels 616 | ) 617 | component_labels = reshape(component_labels, (component_labels.size,)) 618 | parent_labels = reshape(parent_labels, (parent_labels.size,)) 619 | return _inverse_component_map(parent_labels, component_labels) 620 | 621 | @cython.boundscheck(False) 622 | @cython.wraparound(False) # turn off negative index wrapping for entire function 623 | @cython.nonecheck(False) 624 | def _inverse_component_map( 625 | cnp.ndarray[ALLINT, ndim=1, cast=True] parent_labels, 626 | cnp.ndarray[ALLINT_2, ndim=1, cast=True] component_labels 627 | ): 628 | cdef size_t size = parent_labels.size 629 | if size == 0: 630 | return {} 631 | 632 | remap = defaultdict(set) 633 | cdef size_t i = 0 634 | 635 | cdef ALLINT last_label = parent_labels[0] 636 | cdef ALLINT_2 last_component = component_labels[0] 637 | remap[parent_labels[0]].add(component_labels[0]) 638 | for i in range(size): 639 | if last_label == parent_labels[i] and last_component == component_labels[i]: 640 | continue 641 | remap[parent_labels[i]].add(component_labels[i]) 642 | last_label = parent_labels[i] 643 | last_component = component_labels[i] 644 | 645 | # for backwards compatibility 646 | for key in remap: 647 | remap[key] = list(remap[key]) 648 | remap.default_factory = list 649 | 650 | return remap 651 | 652 | def remap(arr, table, preserve_missing_labels=False, in_place=False): 653 | """ 654 | remap(cnp.ndarray[COMPLEX_NUMBER] arr, dict table, 655 | preserve_missing_labels=False, in_place=False) 656 | 657 | Remap an input numpy array in-place according to the values in the given 658 | dictionary "table". 659 | 660 | arr: an N-dimensional numpy array 661 | table: { label: new_label_value, ... } 662 | preserve_missing_labels: If an array value is not present in "table"... 663 | True: Leave it alone. 664 | False: Throw a KeyError. 665 | in_place: if True, modify the input array to reduce 666 | memory consumption. 667 | 668 | Returns: remapped array 669 | """ 670 | if type(arr) == list: 671 | arr = np.array(arr) 672 | 673 | shape = arr.shape 674 | 675 | if arr.flags['F_CONTIGUOUS']: 676 | order = 'F' 677 | else: 678 | order = 'C' 679 | 680 | original_dtype = arr.dtype 681 | if len(table): 682 | min_label, max_label = min(table.values()), max(table.values()) 683 | fit_value = min_label if abs(min_label) > abs(max_label) else max_label 684 | arr = refit(arr, fit_value, increase_only=True) 685 | 686 | if not in_place and original_dtype == arr.dtype: 687 | arr = np.copy(arr, order=order) 688 | 689 | if all([ k == v for k,v in table.items() ]) and preserve_missing_labels: 690 | return arr 691 | 692 | arr = reshape(arr, (arr.size,)) 693 | arr = _remap(arr, table, preserve_missing_labels) 694 | return reshape(arr, shape, order=order) 695 | 696 | @cython.boundscheck(False) 697 | @cython.wraparound(False) # turn off negative index wrapping for entire function 698 | @cython.nonecheck(False) 699 | def _remap(cnp.ndarray[NUMBER] arr, dict table, uint8_t preserve_missing_labels): 700 | cdef NUMBER[:] arrview = arr 701 | cdef size_t i = 0 702 | cdef size_t size = arr.size 703 | cdef NUMBER elem = 0 704 | 705 | if size == 0: 706 | return arr 707 | 708 | # fast path for remapping only a single label 709 | # e.g. for masking something out 710 | cdef NUMBER before = 0 711 | cdef NUMBER after = 0 712 | if preserve_missing_labels and len(table) == 1: 713 | before = next(iter(table.keys())) 714 | after = table[before] 715 | if before == after: 716 | return arr 717 | for i in range(size): 718 | if arr[i] == before: 719 | arr[i] = after 720 | return arr 721 | 722 | cdef flat_hash_map[NUMBER, NUMBER] tbl 723 | 724 | for k, v in table.items(): 725 | tbl[k] = v 726 | 727 | cdef NUMBER last_elem = arrview[0] 728 | cdef NUMBER last_remap_id = 0 729 | 730 | with nogil: 731 | if tbl.find(last_elem) == tbl.end(): 732 | if not preserve_missing_labels: 733 | raise KeyError("{} was not in the remap table.".format(last_elem)) 734 | else: 735 | last_remap_id = last_elem 736 | else: 737 | arrview[0] = tbl[last_elem] 738 | last_remap_id = arrview[0] 739 | 740 | for i in range(1, size): 741 | elem = arrview[i] 742 | 743 | if elem == last_elem: 744 | arrview[i] = last_remap_id 745 | continue 746 | 747 | if tbl.find(elem) == tbl.end(): 748 | if preserve_missing_labels: 749 | last_elem = elem 750 | last_remap_id = elem 751 | continue 752 | else: 753 | raise KeyError("{} was not in the remap table.".format(elem)) 754 | else: 755 | arrview[i] = tbl[elem] 756 | 757 | last_elem = elem 758 | last_remap_id = arrview[i] 759 | 760 | return arr 761 | 762 | @cython.boundscheck(False) 763 | def remap_from_array(cnp.ndarray[UINT] arr, cnp.ndarray[UINT] vals, in_place=True): 764 | """ 765 | remap_from_array(cnp.ndarray[UINT] arr, cnp.ndarray[UINT] vals) 766 | """ 767 | cdef size_t i = 0 768 | cdef size_t size = arr.size 769 | cdef size_t maxkey = vals.size - 1 770 | cdef UINT elem 771 | 772 | if not in_place: 773 | arr = np.copy(arr) 774 | 775 | with nogil: 776 | for i in range(size): 777 | elem = arr[i] 778 | if elem < 0 or elem > maxkey: 779 | continue 780 | arr[i] = vals[elem] 781 | 782 | return arr 783 | 784 | @cython.boundscheck(False) 785 | def remap_from_array_kv(cnp.ndarray[ALLINT] arr, cnp.ndarray[ALLINT] keys, cnp.ndarray[ALLINT] vals, bint preserve_missing_labels=True, in_place=True): 786 | """ 787 | remap_from_array_kv(cnp.ndarray[ALLINT] arr, cnp.ndarray[ALLINT] keys, cnp.ndarray[ALLINT] vals) 788 | """ 789 | cdef flat_hash_map[ALLINT, ALLINT] remap_dict 790 | 791 | assert keys.size == vals.size 792 | 793 | cdef size_t i = 0 794 | cdef size_t size = keys.size 795 | cdef ALLINT elem 796 | 797 | if not in_place: 798 | arr = np.copy(arr) 799 | 800 | with nogil: 801 | for i in range(size): 802 | remap_dict[keys[i]] = vals[i] 803 | 804 | i = 0 805 | size = arr.size 806 | 807 | with nogil: 808 | for i in range(size): 809 | elem = arr[i] 810 | if remap_dict.find(elem) == remap_dict.end(): 811 | if preserve_missing_labels: 812 | continue 813 | else: 814 | raise KeyError("{} was not in the remap keys.".format(elem)) 815 | else: 816 | arr[i] = remap_dict[elem] 817 | 818 | return arr 819 | 820 | def pixel_pairs(labels): 821 | """ 822 | Computes the number of matching adjacent memory locations. 823 | 824 | This is useful for rapidly evaluating whether an image is 825 | more binary or more connectomics like. 826 | """ 827 | if labels.size == 0: 828 | return 0 829 | return _pixel_pairs(reshape(labels, (labels.size,))) 830 | 831 | def _pixel_pairs(cnp.ndarray[ALLINT, ndim=1] labels): 832 | cdef size_t voxels = labels.size 833 | 834 | cdef size_t pairs = 0 835 | cdef ALLINT label = labels[0] 836 | 837 | cdef size_t i = 0 838 | for i in range(1, voxels): 839 | if label == labels[i]: 840 | pairs += 1 841 | else: 842 | label = labels[i] 843 | 844 | return pairs 845 | 846 | @cython.binding(True) 847 | def unique(labels, return_index=False, return_inverse=False, return_counts=False, axis=None): 848 | """ 849 | Compute the sorted set of unique labels in the input array. 850 | 851 | return_index: also return the index of the first detected occurance 852 | of each label. 853 | return_inverse: If True, also return the indices of the unique array 854 | (for the specified axis, if provided) that can be used to reconstruct 855 | the input array. 856 | return_counts: also return the unique label frequency as an array. 857 | 858 | Returns: 859 | unique ndarray 860 | The sorted unique values. 861 | 862 | unique_indices ndarray, optional 863 | The indices of the first occurrences of the unique values in the original array. 864 | Only provided if return_index is True. 865 | 866 | unique_inverse ndarray, optional 867 | The indices to reconstruct the original array from the unique array. 868 | Only provided if return_inverse is True. 869 | 870 | unique_counts ndarray, optional 871 | The number of times each of the unique values comes up in the original array. 872 | Only provided if return_counts is True. 873 | """ 874 | if not isinstance(labels, np.ndarray): 875 | labels = np.array(labels) 876 | 877 | # These flags are currently unsupported so call uncle and 878 | # use the standard implementation instead. 879 | if (axis is not None) or (not np.issubdtype(labels.dtype, np.integer)): 880 | if ( 881 | axis == 0 882 | and ( 883 | labels.ndim == 2 884 | and labels.shape[1] == 2 885 | and np.dtype(labels.dtype).itemsize < 8 886 | and np.issubdtype(labels.dtype, np.integer) 887 | ) 888 | and not (return_index or return_inverse or return_counts) 889 | and labels.flags.c_contiguous 890 | ): 891 | return two_axis_unique(labels) 892 | else: 893 | return np.unique( 894 | labels, 895 | return_index=return_index, 896 | return_inverse=return_inverse, 897 | return_counts=return_counts, 898 | axis=axis 899 | ) 900 | 901 | cdef size_t voxels = labels.size 902 | 903 | shape = labels.shape 904 | fortran_order = labels.flags.f_contiguous 905 | order = "F" if fortran_order else "C" 906 | labels_orig = labels 907 | labels = reshape(labels, (voxels,)) 908 | 909 | max_label = 0 910 | min_label = 0 911 | if voxels > 0: 912 | min_label, max_label = minmax(labels) 913 | 914 | def c_order_index(arr): 915 | if len(shape) > 1 and fortran_order: 916 | return np.ravel_multi_index( 917 | np.unravel_index(arr, shape, order='F'), 918 | shape, order='C' 919 | ) 920 | return arr 921 | 922 | if voxels == 0: 923 | uniq = np.array([], dtype=labels.dtype) 924 | counts = np.array([], dtype=np.uint32) 925 | index = np.array([], dtype=np.uint64) 926 | inverse = np.array([], dtype=np.uintp) 927 | elif min_label >= 0 and max_label < int(voxels): 928 | uniq, index, counts, inverse = unique_via_array(labels, max_label, return_index=return_index, return_inverse=return_inverse) 929 | elif (max_label - min_label) <= int(voxels): 930 | uniq, index, counts, inverse = unique_via_shifted_array(labels, min_label, max_label, return_index=return_index, return_inverse=return_inverse) 931 | elif float(pixel_pairs(labels)) / float(voxels) > 0.66: 932 | uniq, index, counts, inverse = unique_via_renumber(labels, return_index=return_index, return_inverse=return_inverse) 933 | elif return_index or return_inverse: 934 | return np.unique(labels_orig, return_index=return_index, return_counts=return_counts, return_inverse=return_inverse) 935 | else: 936 | uniq, counts = unique_via_sort(labels) 937 | index = None 938 | inverse = None 939 | 940 | results = [ uniq ] 941 | if return_index: 942 | # This is required to match numpy's behavior 943 | results.append(c_order_index(index)) 944 | if return_inverse: 945 | results.append(reshape(inverse, shape, order=order)) 946 | if return_counts: 947 | results.append(counts) 948 | 949 | if len(results) > 1: 950 | return tuple(results) 951 | return uniq 952 | 953 | def two_axis_unique(labels): 954 | """ 955 | Faster replacement for np.unique(labels, axis=0) 956 | when ndim = 2 and the dtype can be widened. 957 | 958 | This special case is useful for sorting edge lists. 959 | """ 960 | dtype = labels.dtype 961 | wide_dtype = widen_dtype(dtype) 962 | 963 | labels = labels[:, [1,0]].reshape(-1, order="C") 964 | labels = labels.view(wide_dtype) 965 | labels = unique(labels) 966 | N = len(labels) 967 | labels = labels.view(dtype).reshape((N, 2), order="C") 968 | return labels[:,[1,0]] 969 | 970 | def unique_via_shifted_array(labels, min_label=None, max_label=None, return_index=False, return_inverse=False): 971 | if min_label is None or max_label is None: 972 | min_label, max_label = minmax(labels) 973 | 974 | labels -= min_label 975 | uniq, idx, counts, inverse = unique_via_array(labels, max_label - min_label + 1, return_index, return_inverse) 976 | labels += min_label 977 | uniq += min_label 978 | return uniq, idx, counts, inverse 979 | 980 | def unique_via_renumber(labels, return_index=False, return_inverse=False): 981 | dtype = labels.dtype 982 | labels, remap = renumber(labels) 983 | remap = { v:k for k,v in remap.items() } 984 | uniq, idx, counts, inverse = unique_via_array(labels, max(remap.keys()), return_index, return_inverse) 985 | uniq = np.array([ remap[segid] for segid in uniq ], dtype=dtype) 986 | 987 | if not return_index and not return_inverse: 988 | uniq.sort() 989 | return uniq, idx, counts, inverse 990 | 991 | uniq, idx2 = np.unique(uniq, return_index=return_index) 992 | if idx is not None: 993 | idx = idx[idx2] 994 | if counts is not None: 995 | counts = counts[idx2] 996 | if inverse is not None: 997 | inverse = idx2[inverse] 998 | 999 | return uniq, idx, counts, inverse 1000 | 1001 | @cython.boundscheck(False) 1002 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1003 | @cython.nonecheck(False) 1004 | def unique_via_sort(cnp.ndarray[ALLINT, ndim=1] labels): 1005 | """Slower than unique_via_array but can handle any label.""" 1006 | labels = np.copy(labels) 1007 | labels.sort() 1008 | 1009 | cdef size_t voxels = labels.size 1010 | 1011 | cdef vector[ALLINT] uniq 1012 | uniq.reserve(100) 1013 | 1014 | cdef vector[uint64_t] counts 1015 | counts.reserve(100) 1016 | 1017 | cdef size_t i = 0 1018 | 1019 | cdef ALLINT cur = labels[0] 1020 | cdef uint64_t accum = 1 1021 | for i in range(1, voxels): 1022 | if cur == labels[i]: 1023 | accum += 1 1024 | else: 1025 | uniq.push_back(cur) 1026 | counts.push_back(accum) 1027 | accum = 1 1028 | cur = labels[i] 1029 | 1030 | uniq.push_back(cur) 1031 | counts.push_back(accum) 1032 | 1033 | dtype = labels.dtype 1034 | del labels 1035 | 1036 | return np.array(uniq, dtype=dtype), np.array(counts, dtype=np.uint64) 1037 | 1038 | @cython.boundscheck(False) 1039 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1040 | @cython.nonecheck(False) 1041 | def unique_via_array( 1042 | cnp.ndarray[ALLINT, ndim=1] labels, 1043 | size_t max_label, 1044 | return_index, return_inverse, 1045 | ): 1046 | cdef cnp.ndarray[uint64_t, ndim=1] counts = np.zeros( 1047 | (max_label+1,), dtype=np.uint64 1048 | ) 1049 | cdef cnp.ndarray[uintptr_t, ndim=1] index 1050 | 1051 | cdef uintptr_t sentinel = np.iinfo(np.uintp).max 1052 | if return_index: 1053 | index = np.full( 1054 | (max_label+1,), sentinel, dtype=np.uintp 1055 | ) 1056 | 1057 | cdef size_t voxels = labels.shape[0] 1058 | cdef size_t i = 0 1059 | for i in range(voxels): 1060 | counts[labels[i]] += 1 1061 | 1062 | if return_index: 1063 | for i in range(voxels): 1064 | if index[labels[i]] == sentinel: 1065 | index[labels[i]] = i 1066 | 1067 | cdef size_t real_size = 0 1068 | for i in range(max_label + 1): 1069 | if counts[i] > 0: 1070 | real_size += 1 1071 | 1072 | cdef cnp.ndarray[ALLINT, ndim=1] segids = np.zeros( 1073 | (real_size,), dtype=labels.dtype 1074 | ) 1075 | cdef cnp.ndarray[uint64_t, ndim=1] cts = np.zeros( 1076 | (real_size,), dtype=np.uint64 1077 | ) 1078 | cdef cnp.ndarray[uintptr_t, ndim=1] idx 1079 | 1080 | cdef size_t j = 0 1081 | for i in range(max_label + 1): 1082 | if counts[i] > 0: 1083 | segids[j] = i 1084 | cts[j] = counts[i] 1085 | j += 1 1086 | 1087 | if return_index: 1088 | idx = np.zeros( (real_size,), dtype=np.uintp) 1089 | j = 0 1090 | for i in range(max_label + 1): 1091 | if counts[i] > 0: 1092 | idx[j] = index[i] 1093 | j += 1 1094 | 1095 | cdef cnp.ndarray[uintptr_t, ndim=1] mapping 1096 | 1097 | if return_inverse: 1098 | if segids.size: 1099 | mapping = np.zeros([segids[segids.size - 1] + 1], dtype=np.uintp) 1100 | for i in range(real_size): 1101 | mapping[segids[i]] = i 1102 | inverse_idx = mapping[labels] 1103 | else: 1104 | inverse_idx = np.zeros([0], dtype=np.uintp) 1105 | 1106 | ret = [ segids, None, cts, None ] 1107 | if return_index: 1108 | ret[1] = idx 1109 | if return_inverse: 1110 | ret[3] = inverse_idx 1111 | 1112 | return ret 1113 | 1114 | def transpose(arr): 1115 | """ 1116 | transpose(arr) 1117 | 1118 | For up to four dimensional matrices, perform in-place transposition. 1119 | Square matrices up to three dimensions are faster than numpy's out-of-place 1120 | algorithm. Default to the out-of-place implementation numpy uses for cases 1121 | that aren't specially handled. 1122 | 1123 | Returns: transposed numpy array 1124 | """ 1125 | if not arr.flags['F_CONTIGUOUS'] and not arr.flags['C_CONTIGUOUS']: 1126 | arr = np.copy(arr, order='C') 1127 | 1128 | shape = arr.shape 1129 | strides = arr.strides 1130 | 1131 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1132 | 1133 | dtype = arr.dtype 1134 | if arr.dtype == bool: 1135 | arr = arr.view(np.uint8) 1136 | 1137 | if arr.ndim == 2: 1138 | arr = ipt2d(arr) 1139 | return arr.view(dtype) 1140 | elif arr.ndim == 3: 1141 | arr = ipt3d(arr) 1142 | return arr.view(dtype) 1143 | elif arr.ndim == 4: 1144 | arr = ipt4d(arr) 1145 | return arr.view(dtype) 1146 | else: 1147 | return arr.T 1148 | 1149 | def asfortranarray(arr): 1150 | """ 1151 | asfortranarray(arr) 1152 | 1153 | For up to four dimensional matrices, perform in-place transposition. 1154 | Square matrices up to three dimensions are faster than numpy's out-of-place 1155 | algorithm. Default to the out-of-place implementation numpy uses for cases 1156 | that aren't specially handled. 1157 | 1158 | Returns: transposed numpy array 1159 | """ 1160 | if arr.flags['F_CONTIGUOUS']: 1161 | return arr 1162 | elif not arr.flags['C_CONTIGUOUS']: 1163 | return np.asfortranarray(arr) 1164 | elif arr.ndim == 1: 1165 | return arr 1166 | 1167 | shape = arr.shape 1168 | strides = arr.strides 1169 | 1170 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1171 | 1172 | dtype = arr.dtype 1173 | if arr.dtype == bool: 1174 | arr = arr.view(np.uint8) 1175 | 1176 | if arr.ndim == 2: 1177 | arr = ipt2d(arr) 1178 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, strides=(nbytes, shape[0] * nbytes)) 1179 | return arr.view(dtype) 1180 | elif arr.ndim == 3: 1181 | arr = ipt3d(arr) 1182 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, strides=(nbytes, shape[0] * nbytes, shape[0] * shape[1] * nbytes)) 1183 | return arr.view(dtype) 1184 | elif arr.ndim == 4: 1185 | arr = ipt4d(arr) 1186 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, 1187 | strides=( 1188 | nbytes, 1189 | shape[0] * nbytes, 1190 | shape[0] * shape[1] * nbytes, 1191 | shape[0] * shape[1] * shape[2] * nbytes 1192 | )) 1193 | return arr.view(dtype) 1194 | else: 1195 | return np.asfortranarray(arr) 1196 | 1197 | def ascontiguousarray(arr): 1198 | """ 1199 | ascontiguousarray(arr) 1200 | 1201 | For up to four dimensional matrices, perform in-place transposition. 1202 | Square matrices up to three dimensions are faster than numpy's out-of-place 1203 | algorithm. Default to the out-of-place implementation numpy uses for cases 1204 | that aren't specially handled. 1205 | 1206 | Returns: transposed numpy array 1207 | """ 1208 | if arr.flags['C_CONTIGUOUS']: 1209 | return arr 1210 | elif not arr.flags['F_CONTIGUOUS']: 1211 | return np.ascontiguousarray(arr) 1212 | elif arr.ndim == 1: 1213 | return arr 1214 | 1215 | shape = arr.shape 1216 | strides = arr.strides 1217 | 1218 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1219 | 1220 | dtype = arr.dtype 1221 | if arr.dtype == bool: 1222 | arr = arr.view(np.uint8) 1223 | 1224 | if arr.ndim == 2: 1225 | arr = ipt2d(arr) 1226 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, strides=(shape[1] * nbytes, nbytes)) 1227 | return arr.view(dtype) 1228 | elif arr.ndim == 3: 1229 | arr = ipt3d(arr) 1230 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, strides=( 1231 | shape[2] * shape[1] * nbytes, 1232 | shape[2] * nbytes, 1233 | nbytes, 1234 | )) 1235 | return arr.view(dtype) 1236 | elif arr.ndim == 4: 1237 | arr = ipt4d(arr) 1238 | arr = np.lib.stride_tricks.as_strided(arr, shape=shape, 1239 | strides=( 1240 | shape[3] * shape[2] * shape[1] * nbytes, 1241 | shape[3] * shape[2] * nbytes, 1242 | shape[3] * nbytes, 1243 | nbytes, 1244 | )) 1245 | return arr.view(dtype) 1246 | else: 1247 | return np.ascontiguousarray(arr) 1248 | 1249 | def ipt2d(cnp.ndarray[COMPLEX_NUMBER, cast=True, ndim=2] arr): 1250 | cdef COMPLEX_NUMBER[:,:] arrview = arr 1251 | 1252 | cdef size_t sx 1253 | cdef size_t sy 1254 | 1255 | if arr.flags['F_CONTIGUOUS']: 1256 | sx = arr.shape[0] 1257 | sy = arr.shape[1] 1258 | else: 1259 | sx = arr.shape[1] 1260 | sy = arr.shape[0] 1261 | 1262 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1263 | 1264 | # ipt doesn't do anything with values, 1265 | # just moves them around, so only bit width matters 1266 | # int, uint, float, bool who cares 1267 | if nbytes == 1: 1268 | _ipt2d[uint8_t]( 1269 | &arrview[0,0], 1270 | sx, sy 1271 | ) 1272 | elif nbytes == 2: 1273 | _ipt2d[uint16_t]( 1274 | &arrview[0,0], 1275 | sx, sy 1276 | ) 1277 | elif nbytes == 4: 1278 | _ipt2d[uint32_t]( 1279 | &arrview[0,0], 1280 | sx, sy 1281 | ) 1282 | else: 1283 | _ipt2d[uint64_t]( 1284 | &arrview[0,0], 1285 | sx, sy 1286 | ) 1287 | 1288 | return arr 1289 | 1290 | def ipt3d(cnp.ndarray[COMPLEX_NUMBER, cast=True, ndim=3] arr): 1291 | cdef COMPLEX_NUMBER[:,:,:] arrview = arr 1292 | 1293 | cdef size_t sx 1294 | cdef size_t sy 1295 | cdef size_t sz 1296 | 1297 | if arr.flags['F_CONTIGUOUS']: 1298 | sx = arr.shape[0] 1299 | sy = arr.shape[1] 1300 | sz = arr.shape[2] 1301 | else: 1302 | sx = arr.shape[2] 1303 | sy = arr.shape[1] 1304 | sz = arr.shape[0] 1305 | 1306 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1307 | 1308 | # ipt doesn't do anything with values, 1309 | # just moves them around, so only bit width matters 1310 | # int, uint, float, bool who cares 1311 | if nbytes == 1: 1312 | _ipt3d[uint8_t]( 1313 | &arrview[0,0,0], 1314 | sx, sy, sz 1315 | ) 1316 | elif nbytes == 2: 1317 | _ipt3d[uint16_t]( 1318 | &arrview[0,0,0], 1319 | sx, sy, sz 1320 | ) 1321 | elif nbytes == 4: 1322 | _ipt3d[uint32_t]( 1323 | &arrview[0,0,0], 1324 | sx, sy, sz 1325 | ) 1326 | else: 1327 | _ipt3d[uint64_t]( 1328 | &arrview[0,0,0], 1329 | sx, sy, sz 1330 | ) 1331 | 1332 | return arr 1333 | 1334 | def ipt4d(cnp.ndarray[COMPLEX_NUMBER, cast=True, ndim=4] arr): 1335 | cdef COMPLEX_NUMBER[:,:,:,:] arrview = arr 1336 | 1337 | cdef size_t sx 1338 | cdef size_t sy 1339 | cdef size_t sz 1340 | cdef size_t sw 1341 | 1342 | if arr.flags['F_CONTIGUOUS']: 1343 | sx = arr.shape[0] 1344 | sy = arr.shape[1] 1345 | sz = arr.shape[2] 1346 | sw = arr.shape[3] 1347 | else: 1348 | sx = arr.shape[3] 1349 | sy = arr.shape[2] 1350 | sz = arr.shape[1] 1351 | sw = arr.shape[0] 1352 | 1353 | cdef int nbytes = np.dtype(arr.dtype).itemsize 1354 | 1355 | # ipt doesn't do anything with values, 1356 | # just moves them around, so only bit width matters 1357 | # int, uint, float, bool who cares 1358 | if nbytes == 1: 1359 | _ipt4d[uint8_t]( 1360 | &arrview[0,0,0,0], 1361 | sx, sy, sz, sw 1362 | ) 1363 | elif nbytes == 2: 1364 | _ipt4d[uint16_t]( 1365 | &arrview[0,0,0,0], 1366 | sx, sy, sz, sw 1367 | ) 1368 | elif nbytes == 4: 1369 | _ipt4d[uint32_t]( 1370 | &arrview[0,0,0,0], 1371 | sx, sy, sz, sw 1372 | ) 1373 | else: 1374 | _ipt4d[uint64_t]( 1375 | &arrview[0,0,0,0], 1376 | sx, sy, sz, sw 1377 | ) 1378 | 1379 | return arr 1380 | 1381 | def foreground(arr): 1382 | """Returns the number of non-zero voxels in an array.""" 1383 | arr = reshape(arr, (arr.size,)) 1384 | return _foreground(arr) 1385 | 1386 | @cython.boundscheck(False) 1387 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1388 | @cython.nonecheck(False) 1389 | def _foreground(cnp.ndarray[ALLINT, ndim=1] arr): 1390 | cdef size_t i = 0 1391 | cdef size_t sz = arr.size 1392 | cdef size_t n_foreground = 0 1393 | for i in range(sz): 1394 | n_foreground += (arr[i] != 0) 1395 | return n_foreground 1396 | 1397 | def point_cloud(arr): 1398 | """ 1399 | point_cloud(arr) 1400 | 1401 | Given a 2D or 3D integer image, return a mapping from 1402 | labels to their (x,y,z) position in the image. 1403 | 1404 | Zero is considered a background label. 1405 | 1406 | Returns: ndarray(N, 2 or 3, dtype=uint16) 1407 | """ 1408 | if arr.dtype == bool: 1409 | arr = arr.view(np.uint8) 1410 | 1411 | if arr.ndim == 2: 1412 | return _point_cloud_2d(arr) 1413 | else: 1414 | return _point_cloud_3d(arr) 1415 | 1416 | @cython.boundscheck(False) 1417 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1418 | @cython.nonecheck(False) 1419 | def _point_cloud_2d(cnp.ndarray[ALLINT, ndim=2] arr): 1420 | cdef size_t n_foreground = foreground(arr) 1421 | 1422 | cdef size_t sx = arr.shape[0] 1423 | cdef size_t sy = arr.shape[1] 1424 | 1425 | if n_foreground == 0: 1426 | return {} 1427 | 1428 | cdef cnp.ndarray[ALLINT, ndim=1] ptlabel = np.zeros((n_foreground,), dtype=arr.dtype) 1429 | cdef cnp.ndarray[uint16_t, ndim=2] ptcloud = np.zeros((n_foreground, 2), dtype=np.uint16) 1430 | 1431 | cdef size_t i = 0 1432 | cdef size_t j = 0 1433 | 1434 | cdef size_t idx = 0 1435 | for i in range(sx): 1436 | for j in range(sy): 1437 | if arr[i,j] != 0: 1438 | ptlabel[idx] = arr[i,j] 1439 | ptcloud[idx,0] = i 1440 | ptcloud[idx,1] = j 1441 | idx += 1 1442 | 1443 | sortidx = ptlabel.argsort() 1444 | ptlabel = ptlabel[sortidx] 1445 | ptcloud = ptcloud[sortidx] 1446 | del sortidx 1447 | 1448 | ptcloud_by_label = {} 1449 | if n_foreground == 1: 1450 | ptcloud_by_label[ptlabel[0]] = ptcloud 1451 | return ptcloud_by_label 1452 | 1453 | cdef size_t start = 0 1454 | cdef size_t end = 0 1455 | for end in range(1, n_foreground): 1456 | if ptlabel[end] != ptlabel[end - 1]: 1457 | ptcloud_by_label[ptlabel[end - 1]] = ptcloud[start:end,:] 1458 | start = end 1459 | 1460 | ptcloud_by_label[ptlabel[end]] = ptcloud[start:,:] 1461 | 1462 | return ptcloud_by_label 1463 | 1464 | @cython.boundscheck(False) 1465 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1466 | @cython.nonecheck(False) 1467 | def _point_cloud_3d(cnp.ndarray[ALLINT, ndim=3] arr): 1468 | cdef size_t n_foreground = foreground(arr) 1469 | 1470 | cdef size_t sx = arr.shape[0] 1471 | cdef size_t sy = arr.shape[1] 1472 | cdef size_t sz = arr.shape[2] 1473 | 1474 | if n_foreground == 0: 1475 | return {} 1476 | 1477 | cdef cnp.ndarray[ALLINT, ndim=1] ptlabel = np.zeros((n_foreground,), dtype=arr.dtype) 1478 | cdef cnp.ndarray[uint16_t, ndim=2] ptcloud = np.zeros((n_foreground, 3), dtype=np.uint16) 1479 | 1480 | cdef size_t i = 0 1481 | cdef size_t j = 0 1482 | cdef size_t k = 0 1483 | 1484 | cdef size_t idx = 0 1485 | for i in range(sx): 1486 | for j in range(sy): 1487 | for k in range(sz): 1488 | if arr[i,j,k] != 0: 1489 | ptlabel[idx] = arr[i,j,k] 1490 | ptcloud[idx,0] = i 1491 | ptcloud[idx,1] = j 1492 | ptcloud[idx,2] = k 1493 | idx += 1 1494 | 1495 | sortidx = ptlabel.argsort() 1496 | ptlabel = ptlabel[sortidx] 1497 | ptcloud = ptcloud[sortidx] 1498 | del sortidx 1499 | 1500 | ptcloud_by_label = {} 1501 | if n_foreground == 1: 1502 | ptcloud_by_label[ptlabel[0]] = ptcloud 1503 | return ptcloud_by_label 1504 | 1505 | cdef size_t start = 0 1506 | cdef size_t end = 0 1507 | for end in range(1, n_foreground): 1508 | if ptlabel[end] != ptlabel[end - 1]: 1509 | ptcloud_by_label[ptlabel[end - 1]] = ptcloud[start:end,:] 1510 | start = end 1511 | 1512 | ptcloud_by_label[ptlabel[end]] = ptcloud[start:,:] 1513 | 1514 | return ptcloud_by_label 1515 | 1516 | 1517 | @cython.binding(True) 1518 | @cython.boundscheck(False) 1519 | @cython.wraparound(False) # turn off negative index wrapping for entire function 1520 | @cython.nonecheck(False) 1521 | def tobytes( 1522 | cnp.ndarray[NUMBER, ndim=3] image, 1523 | chunk_size:Sequence[int,int,int], 1524 | order:str="C" 1525 | ) -> List[bytes]: 1526 | """ 1527 | Compute the cutout.tobytes(order) with the image divided into 1528 | a grid of cutouts. Return the resultant binaries indexed by 1529 | their cutout's gridpoint in fortran order. 1530 | 1531 | This is faster than calling tobytes on each cutout individually 1532 | if the input and output orders match. 1533 | """ 1534 | if order not in ["C", "F"]: 1535 | raise ValueError(f"order must be C or F. Got: {order}") 1536 | 1537 | chunk_size = np.array(chunk_size, dtype=float) 1538 | shape = np.array((image.shape[0], image.shape[1], image.shape[2]), dtype=float) 1539 | grid_size = np.ceil(shape / chunk_size).astype(int) 1540 | 1541 | if np.any(np.remainder(shape, chunk_size)): 1542 | raise ValueError(f"chunk_size ({chunk_size}) must evenly divide the image shape ({shape}).") 1543 | 1544 | chunk_array_size = int(reduce(operator.mul, chunk_size)) 1545 | chunk_size = chunk_size.astype(int) 1546 | shape = shape.astype(int) 1547 | 1548 | num_grid = int(reduce(operator.mul, grid_size)) 1549 | 1550 | cdef int64_t img_i = 0 1551 | 1552 | cdef int64_t sgx = grid_size[0] 1553 | cdef int64_t sgy = grid_size[1] 1554 | cdef int64_t sgz = grid_size[2] 1555 | 1556 | cdef int64_t sx = shape[0] 1557 | cdef int64_t sy = shape[1] 1558 | cdef int64_t sz = shape[2] 1559 | cdef int64_t sxy = sx * sy 1560 | 1561 | cdef int64_t cx = chunk_size[0] 1562 | cdef int64_t cy = chunk_size[1] 1563 | cdef int64_t cz = chunk_size[2] 1564 | 1565 | cdef int64_t gx = 0 1566 | cdef int64_t gy = 0 1567 | cdef int64_t gz = 0 1568 | cdef int64_t gi = 0 1569 | 1570 | cdef int64_t idx = 0 1571 | cdef int64_t x = 0 1572 | cdef int64_t y = 0 1573 | cdef int64_t z = 0 1574 | 1575 | # It's difficult to do better than numpy when f and c or c and f 1576 | # because at least one of the arrays must be transversed substantially 1577 | # out of order. However, when f and f or c and c you can do strips in 1578 | # order. 1579 | if ( 1580 | (not image.flags.f_contiguous and not image.flags.c_contiguous) 1581 | or (image.flags.f_contiguous and order == "C") 1582 | or (image.flags.c_contiguous and order == "F") 1583 | ): 1584 | res = [] 1585 | for gz in range(sgz): 1586 | for gy in range(sgy): 1587 | for gx in range(sgx): 1588 | cutout = image[gx*cx:(gx+1)*cx, gy*cy:(gy+1)*cy, gz*cz:(gz+1)*cz] 1589 | res.append(cutout.tobytes(order)) 1590 | return res 1591 | elif (cx == sx and cy == sy and cz == sz): 1592 | return [ image.tobytes(order) ] 1593 | 1594 | cdef cnp.ndarray[NUMBER] arr 1595 | 1596 | cdef list[cnp.ndarray[NUMBER]] array_grid = [ 1597 | np.zeros((chunk_array_size,), dtype=image.dtype) 1598 | for i in range(num_grid) 1599 | ] 1600 | 1601 | cdef cnp.ndarray[NUMBER, ndim=1] img = reshape(image, (image.size,)) 1602 | 1603 | if order == "F": # b/c of guard above, this is F to F order 1604 | for gz in range(sgz): 1605 | for z in range(cz): 1606 | for gy in range(sgy): 1607 | for gx in range(sgx): 1608 | gi = gx + sgx * (gy + sgy * gz) 1609 | arr = array_grid[gi] 1610 | for y in range(cy): 1611 | img_i = cx * gx + sx * ((cy * gy + y) + sy * (cz * gz + z)) 1612 | idx = cx * (y + cy * z) 1613 | for x in range(cx): 1614 | arr[idx + x] = img[img_i + x] 1615 | else: # b/c of guard above, this is C to C order 1616 | for gx in range(sgx): 1617 | for x in range(cx): 1618 | for gy in range(sgy): 1619 | for gz in range(sgz): 1620 | gi = gx + sgx * (gy + sgy * gz) 1621 | arr = array_grid[gi] 1622 | for y in range(cy): 1623 | img_i = cz * gz + sz * ((cy * gy + y) + sy * (cx * gx + x)) 1624 | idx = cz * (y + cy * x) 1625 | for z in range(cz): 1626 | arr[idx + z] = img[img_i + z] 1627 | 1628 | return [ bytes(memoryview(ar)) for ar in array_grid ] 1629 | -------------------------------------------------------------------------------- /src/ipt.hpp: -------------------------------------------------------------------------------- 1 | /* ipt.hpp - In-Place Transposition 2 | * 3 | * When transitioning between different media, 4 | * e.g. CPU to GPU, CPU to Network, CPU to disk, 5 | * it's often necessary to physically transpose 6 | * multi-dimensional arrays to reformat as C or 7 | * Fortran order. Tranposing matrices is also 8 | * a common action in linear algebra, but often 9 | * you can get away with just changing the strides. 10 | * 11 | * An out-of-place transposition is easy to write, 12 | * often faster, but will spike peak memory consumption. 13 | * 14 | * This library grants the user the option of performing 15 | * an in-place transposition which trades CPU time for 16 | * peak memory usage. 17 | * 18 | * Author: William Silversmith 19 | * Date: Feb. 2019 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef IN_PLACE_TRANSPOSE_H 27 | #define IN_PLACE_TRANSPOSE_H 28 | 29 | // ipt = in-place transpose 30 | // call as: 31 | // 2d: ipt::ipt(arr, sx, sy); 32 | // 3d: ipt::ipt(arr, sx, sy, sz); 33 | // 4d: ipt::ipt(arr, sx, sy, sz, sw); 34 | 35 | namespace ipt { 36 | 37 | template 38 | void square_ipt(T* arr, const size_t sx, const size_t sy) { 39 | T tmp = 0; 40 | 41 | size_t k = 0; 42 | size_t next_k = 0; 43 | 44 | size_t base_k = 0; // just for going faster 45 | 46 | for (size_t y = 0; y < sy; y++) { 47 | base_k = sx * y; 48 | for (size_t x = y; x < sx; x++) { 49 | k = x + base_k; 50 | next_k = y + sy * x; 51 | 52 | tmp = arr[next_k]; 53 | arr[next_k] = arr[k]; 54 | arr[k] = tmp; 55 | } 56 | } 57 | } 58 | 59 | /* A permutation, P(k), is a mapping of 60 | * one arrangement of numbers to another. 61 | * For an m x n array, the permuatation 62 | * mapping from C to Fortran order is: 63 | * 64 | * P(k) := mk mod mn - 1 65 | * iP(k) := nk mod mn - 1 (the inverse) 66 | * 67 | * Where does this come from? Assume we are 68 | * going from C to Fortran order (it doesn't 69 | * matter either way). The indicies are defined 70 | * as: 71 | * 72 | * k = C(x,y) = x + sx * y 73 | * F(x,y) = y + sy * x 74 | * 75 | * The permutation P(k) is the transformation: 76 | * 77 | * P(C(x,y)) = F(x,y) 78 | * 79 | * 1. P(x + sx * y) = y + sx * x 80 | * 2. sy (x + sx y) = sy x + sx sy y 81 | * 3. Let q = (sx sy - 1) 82 | * 4. sy x + sx sy y % q 83 | * 5. ((sy x % q) + (sx sy y % q)) % q by distributive identity 84 | * 6. sy x is identical b/c q is always bigger 85 | * 7. sx sy y reduces to y 86 | * 8 q is always bigger than sy x + y so it disappears 87 | * 88 | * ==> P(k) = y + sy * x = F(x,y) 89 | * ==> P(k) = sy * k % (sx sy - 1) 90 | * 91 | * Note that P(0) and P(q) are always 0 and q respectively. 92 | * 93 | * Now we need a way to implement this insight. 94 | * How can we move the data around without using too 95 | * much extra space? A simple algorithm is 96 | * "follow-the-cycles". Each time you try moving a 97 | * k to P(k), it displaces the resident tile. Eventually, 98 | * this forms a cycle. When you reach the end of a cycle, 99 | * you can stop processing and move to unvisited parts of 100 | * the array. This requires storing a packed bit representation 101 | * of where we've visited to make sure we get everything. 102 | * This means we need to store between 2.0x and 1.016x 103 | * memory in the size of the original array depending on its 104 | * data type (2.0x would be a transpose of another bit packed 105 | * array and 1.016x would be 64-bit data types). 106 | * 107 | * There are fancier algorithms that use divide-and-conquer, 108 | * and SIMD tricks, and near zero extra memory, but 109 | * this is a good place to start. Fwiw, the bit vector 110 | * has an O(nm) time complexity (really 2nm) while the 111 | * sans-bit vector algorithms are O(nm log nm). 112 | */ 113 | template 114 | void rect_ipt(T* arr, const size_t sx, const size_t sy) { 115 | const size_t sxy = sx * sy; 116 | 117 | std::vector visited; 118 | visited.resize(sxy); 119 | 120 | visited[0] = true; 121 | visited[sxy - 1] = true; 122 | 123 | const size_t q = sxy - 1; 124 | 125 | size_t k, next_k; 126 | T tmp1, tmp2; 127 | 128 | for (size_t i = 1; i < q; i++) { 129 | if (visited[i]) { 130 | continue; 131 | } 132 | 133 | k = i; 134 | tmp1 = arr[k]; 135 | next_k = sy * k - q * (k / sx); // P(k) 136 | 137 | while (!visited[next_k]) { 138 | tmp2 = arr[next_k]; 139 | arr[next_k] = tmp1; 140 | tmp1 = tmp2; 141 | visited[next_k] = true; 142 | k = next_k; 143 | next_k = sy * k - q * (k / sx); // P(k) 144 | } 145 | } 146 | } 147 | 148 | // note: sx == sy == sz... find better convention? 149 | // still good for mutliple-dispatch. 150 | template 151 | void square_ipt( 152 | T* arr, 153 | const size_t sx, const size_t sy, const size_t sz 154 | ) { 155 | 156 | T tmp = 0; 157 | 158 | const size_t sxy = sx * sy; 159 | const size_t syz = sy * sz; 160 | 161 | size_t k = 0; 162 | size_t next_k = 0; 163 | size_t base_k = 0; 164 | for (size_t z = 0; z < sz; z++) { 165 | for (size_t y = 0; y < sy; y++) { 166 | base_k = sx * y + sxy * z; 167 | for (size_t x = z; x < sx; x++) { 168 | k = x + base_k; 169 | next_k = z + sz * y + syz * x; 170 | 171 | tmp = arr[next_k]; 172 | arr[next_k] = arr[k]; 173 | arr[k] = tmp; 174 | } 175 | } 176 | } 177 | } 178 | 179 | inline size_t P_3d( 180 | const size_t k, 181 | const size_t sx, const size_t sy, const size_t sz 182 | ) { 183 | const size_t sxy = sx * sy; 184 | 185 | // k = x + sx y + sx sy z 186 | 187 | size_t z = k / sxy; 188 | size_t y = (k - (z * sxy)) / sx; 189 | size_t x = k - sx * (y + z * sy); 190 | return z + sz * (y + sy * x); 191 | } 192 | 193 | template 194 | void rect_ipt( 195 | T* arr, 196 | const size_t sx, const size_t sy, const size_t sz 197 | ) { 198 | const size_t sxy = sx * sy; 199 | const size_t N = sxy * sz; 200 | 201 | std::vector visited; 202 | visited.resize(N); 203 | 204 | visited[0] = true; 205 | visited[N - 1] = true; 206 | 207 | size_t k, next_k; 208 | T tmp1 = 0, tmp2 = 0; 209 | 210 | for (size_t i = 1; i < (N - 1); i++) { 211 | if (visited[i]) { 212 | continue; 213 | } 214 | 215 | k = i; 216 | tmp1 = arr[k]; 217 | next_k = P_3d(k, sx, sy, sz); 218 | while (!visited[next_k]) { 219 | tmp2 = arr[next_k]; 220 | arr[next_k] = tmp1; 221 | tmp1 = tmp2; 222 | visited[next_k] = true; 223 | k = next_k; 224 | next_k = P_3d(k, sx, sy, sz); 225 | } 226 | } 227 | } 228 | 229 | inline size_t P_4d( 230 | const size_t k, 231 | const size_t sx, const size_t sy, const size_t sz, const size_t sw 232 | ) { 233 | const size_t sxy = sx * sy; 234 | const size_t sxyz = sxy * sz; 235 | 236 | // k = x + sx y + sx sy z + sx sy sz w 237 | 238 | size_t w = k / sxyz; 239 | size_t z = (k - w * sxyz) / sxy; 240 | size_t y = (k - (w * sxyz) - (z * sxy)) / sx; 241 | size_t x = k - (w * sxyz) - (z * sxy) - y * sx; 242 | 243 | return w + sw * (z + sz * (y + sy * x)); 244 | } 245 | 246 | template 247 | void rect_ipt( 248 | T* arr, 249 | const size_t sx, const size_t sy, const size_t sz, const size_t sw 250 | ) { 251 | 252 | const size_t N = sx * sy * sz * sw; 253 | 254 | std::vector visited; 255 | visited.resize(N); 256 | 257 | visited[0] = true; 258 | visited[N - 1] = true; 259 | 260 | size_t k, next_k; 261 | T tmp1 = 0, tmp2 = 0; 262 | 263 | for (size_t i = 1; i < (N - 1); i++) { 264 | if (visited[i]) { 265 | continue; 266 | } 267 | 268 | k = i; 269 | tmp1 = arr[k]; 270 | next_k = P_4d(k, sx, sy, sz, sw); 271 | while (!visited[next_k]) { 272 | tmp2 = arr[next_k]; 273 | arr[next_k] = tmp1; 274 | tmp1 = tmp2; 275 | visited[next_k] = true; 276 | k = next_k; 277 | next_k = P_4d(k, sx, sy, sz, sw); 278 | } 279 | } 280 | } 281 | 282 | template 283 | void ipt(T* arr, const size_t sx) { 284 | return; 285 | } 286 | 287 | template 288 | void ipt(T* arr, const size_t sx, const size_t sy) { 289 | if (sx * sy <= 1) { 290 | return; 291 | } 292 | 293 | if (sx == sy) { 294 | square_ipt(arr, sx, sy); 295 | } 296 | else { 297 | rect_ipt(arr, sx, sy); 298 | } 299 | } 300 | 301 | template 302 | void ipt(T* arr, const size_t sx, const size_t sy, const size_t sz) { 303 | if (sx * sy * sz <= 1) { 304 | return; 305 | } 306 | 307 | if (sx == sy && sy == sz) { 308 | square_ipt(arr, sx, sy, sz); 309 | } 310 | else { 311 | rect_ipt(arr, sx, sy, sz); 312 | } 313 | } 314 | 315 | template 316 | void ipt( 317 | T* arr, 318 | const size_t sx, const size_t sy, 319 | const size_t sz, const size_t sw 320 | ) { 321 | if (sx * sy * sz * sw <= 1) { 322 | return; 323 | } 324 | 325 | rect_ipt(arr, sx, sy, sz, sw); 326 | } 327 | 328 | }; 329 | 330 | namespace pyipt { 331 | 332 | template 333 | void _ipt2d(T* arr, const size_t sx, const size_t sy) { 334 | ipt::ipt(arr, sx, sy); 335 | } 336 | 337 | template 338 | void _ipt3d(T* arr, const size_t sx, const size_t sy, const size_t sz) { 339 | ipt::ipt(arr, sx, sy, sz); 340 | } 341 | 342 | template 343 | void _ipt4d( 344 | T* arr, 345 | const size_t sx, const size_t sy, 346 | const size_t sz, const size_t sw 347 | ) { 348 | 349 | ipt::ipt(arr, sx, sy, sz, sw); 350 | } 351 | 352 | }; 353 | 354 | #endif 355 | -------------------------------------------------------------------------------- /src/ska_flat_hash_map.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Malte Skarupke 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | // Boost Software License - Version 1.0 - August 17th, 2003 6 | 7 | // Permission is hereby granted, free of charge, to any person or organization 8 | // obtaining a copy of the software and accompanying documentation covered by 9 | // this license (the "Software") to use, reproduce, display, distribute, 10 | // execute, and transmit the Software, and to prepare derivative works of the 11 | // Software, and to permit third-parties to whom the Software is furnished to 12 | // do so, all subject to the following: 13 | 14 | // The copyright notices in the Software and this entire statement, including 15 | // the above license grant, this restriction and the following disclaimer, 16 | // must be included in all copies of the Software, in whole or in part, and 17 | // all derivative works of the Software, unless such copies or derivative 18 | // works are solely in the form of machine-executable object code generated by 19 | // a source language processor. 20 | 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 24 | // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 25 | // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 26 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | // DEALINGS IN THE SOFTWARE. 28 | 29 | #pragma once 30 | 31 | // Check windows 32 | #if _WIN32 || _WIN64 33 | #if _WIN64 34 | #define ENV64BIT 35 | #else 36 | #define ENV32BIT 37 | #endif 38 | #endif 39 | 40 | // Check GCC 41 | #if __GNUC__ 42 | #if __x86_64__ || __ppc64__ || __aarch64__ || __LP64__ 43 | #define ENV64BIT 44 | #else 45 | #define ENV32BIT 46 | #endif 47 | #endif 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | 58 | #ifdef _MSC_VER 59 | #define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ 60 | #else 61 | #define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) 62 | #endif 63 | 64 | namespace ska 65 | { 66 | struct prime_number_hash_policy; 67 | struct power_of_two_hash_policy; 68 | struct fibonacci_hash_policy; 69 | 70 | namespace detailv3 71 | { 72 | template 73 | struct functor_storage : Functor 74 | { 75 | functor_storage() = default; 76 | functor_storage(const Functor & functor) 77 | : Functor(functor) 78 | { 79 | } 80 | template 81 | Result operator()(Args &&... args) 82 | { 83 | return static_cast(*this)(std::forward(args)...); 84 | } 85 | template 86 | Result operator()(Args &&... args) const 87 | { 88 | return static_cast(*this)(std::forward(args)...); 89 | } 90 | }; 91 | template 92 | struct functor_storage 93 | { 94 | typedef Result (*function_ptr)(Args...); 95 | function_ptr function; 96 | functor_storage(function_ptr function) 97 | : function(function) 98 | { 99 | } 100 | Result operator()(Args... args) const 101 | { 102 | return function(std::forward(args)...); 103 | } 104 | operator function_ptr &() 105 | { 106 | return function; 107 | } 108 | operator const function_ptr &() 109 | { 110 | return function; 111 | } 112 | }; 113 | template 114 | struct KeyOrValueHasher : functor_storage 115 | { 116 | typedef functor_storage hasher_storage; 117 | KeyOrValueHasher() = default; 118 | KeyOrValueHasher(const hasher & hash) 119 | : hasher_storage(hash) 120 | { 121 | } 122 | size_t operator()(const key_type & key) 123 | { 124 | return static_cast(*this)(key); 125 | } 126 | size_t operator()(const key_type & key) const 127 | { 128 | return static_cast(*this)(key); 129 | } 130 | size_t operator()(const value_type & value) 131 | { 132 | return static_cast(*this)(value.first); 133 | } 134 | size_t operator()(const value_type & value) const 135 | { 136 | return static_cast(*this)(value.first); 137 | } 138 | template 139 | size_t operator()(const std::pair & value) 140 | { 141 | return static_cast(*this)(value.first); 142 | } 143 | template 144 | size_t operator()(const std::pair & value) const 145 | { 146 | return static_cast(*this)(value.first); 147 | } 148 | }; 149 | template 150 | struct KeyOrValueEquality : functor_storage 151 | { 152 | typedef functor_storage equality_storage; 153 | KeyOrValueEquality() = default; 154 | KeyOrValueEquality(const key_equal & equality) 155 | : equality_storage(equality) 156 | { 157 | } 158 | bool operator()(const key_type & lhs, const key_type & rhs) 159 | { 160 | return static_cast(*this)(lhs, rhs); 161 | } 162 | bool operator()(const key_type & lhs, const value_type & rhs) 163 | { 164 | return static_cast(*this)(lhs, rhs.first); 165 | } 166 | bool operator()(const value_type & lhs, const key_type & rhs) 167 | { 168 | return static_cast(*this)(lhs.first, rhs); 169 | } 170 | bool operator()(const value_type & lhs, const value_type & rhs) 171 | { 172 | return static_cast(*this)(lhs.first, rhs.first); 173 | } 174 | template 175 | bool operator()(const key_type & lhs, const std::pair & rhs) 176 | { 177 | return static_cast(*this)(lhs, rhs.first); 178 | } 179 | template 180 | bool operator()(const std::pair & lhs, const key_type & rhs) 181 | { 182 | return static_cast(*this)(lhs.first, rhs); 183 | } 184 | template 185 | bool operator()(const value_type & lhs, const std::pair & rhs) 186 | { 187 | return static_cast(*this)(lhs.first, rhs.first); 188 | } 189 | template 190 | bool operator()(const std::pair & lhs, const value_type & rhs) 191 | { 192 | return static_cast(*this)(lhs.first, rhs.first); 193 | } 194 | template 195 | bool operator()(const std::pair & lhs, const std::pair & rhs) 196 | { 197 | return static_cast(*this)(lhs.first, rhs.first); 198 | } 199 | }; 200 | static constexpr int8_t min_lookups = 4; 201 | template 202 | struct sherwood_v3_entry 203 | { 204 | sherwood_v3_entry() 205 | { 206 | } 207 | sherwood_v3_entry(int8_t distance_from_desired) 208 | : distance_from_desired(distance_from_desired) 209 | { 210 | } 211 | ~sherwood_v3_entry() 212 | { 213 | } 214 | 215 | bool has_value() const 216 | { 217 | return distance_from_desired >= 0; 218 | } 219 | bool is_empty() const 220 | { 221 | return distance_from_desired < 0; 222 | } 223 | bool is_at_desired_position() const 224 | { 225 | return distance_from_desired <= 0; 226 | } 227 | template 228 | void emplace(int8_t distance, Args &&... args) 229 | { 230 | new (std::addressof(value)) T(std::forward(args)...); 231 | distance_from_desired = distance; 232 | } 233 | 234 | void destroy_value() 235 | { 236 | value.~T(); 237 | distance_from_desired = -1; 238 | } 239 | 240 | int8_t distance_from_desired = -1; 241 | static constexpr int8_t special_end_value = 0; 242 | union { T value; }; 243 | }; 244 | 245 | inline int8_t log2(size_t value) 246 | { 247 | #ifdef ENV64BIT 248 | static constexpr int8_t table[64] = 249 | { 250 | 63, 0, 58, 1, 59, 47, 53, 2, 251 | 60, 39, 48, 27, 54, 33, 42, 3, 252 | 61, 51, 37, 40, 49, 18, 28, 20, 253 | 55, 30, 34, 11, 43, 14, 22, 4, 254 | 62, 57, 46, 52, 38, 26, 32, 41, 255 | 50, 36, 17, 19, 29, 10, 13, 21, 256 | 56, 45, 25, 31, 35, 16, 9, 12, 257 | 44, 24, 15, 8, 23, 7, 6, 5 258 | }; 259 | value |= value >> 1; 260 | value |= value >> 2; 261 | value |= value >> 4; 262 | value |= value >> 8; 263 | value |= value >> 16; 264 | value |= value >> 32; 265 | return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; 266 | #endif 267 | #ifdef ENV32BIT 268 | static constexpr int8_t table[32] = 269 | { 270 | 0, 9, 1, 10, 13, 21, 2, 29, 271 | 11, 14, 16, 18, 22, 25, 3, 30, 272 | 8, 12, 20, 28, 15, 17, 24, 7, 273 | 19, 27, 23, 6, 26, 5, 4, 31 274 | }; 275 | 276 | value |= value >> 1; 277 | value |= value >> 2; 278 | value |= value >> 4; 279 | value |= value >> 8; 280 | value |= value >> 16; 281 | return table[(value*0x07C4ACDD) >> 27]; 282 | #endif 283 | } 284 | 285 | template 286 | struct AssignIfTrue 287 | { 288 | void operator()(T & lhs, const T & rhs) 289 | { 290 | lhs = rhs; 291 | } 292 | void operator()(T & lhs, T && rhs) 293 | { 294 | lhs = std::move(rhs); 295 | } 296 | }; 297 | template 298 | struct AssignIfTrue 299 | { 300 | void operator()(T &, const T &) 301 | { 302 | } 303 | void operator()(T &, T &&) 304 | { 305 | } 306 | }; 307 | 308 | inline size_t next_power_of_two(size_t i) 309 | { 310 | --i; 311 | i |= i >> 1; 312 | i |= i >> 2; 313 | i |= i >> 4; 314 | i |= i >> 8; 315 | i |= i >> 16; 316 | #ifdef ENV64BIT 317 | i |= i >> 32; 318 | #endif 319 | ++i; 320 | return i; 321 | } 322 | 323 | // Implementation taken from http://en.cppreference.com/w/cpp/types/void_t 324 | // (it takes CWG1558 into account and also works for older compilers) 325 | template struct make_void { typedef void type;}; 326 | template using void_t = typename make_void::type; 327 | 328 | template 329 | struct HashPolicySelector 330 | { 331 | typedef fibonacci_hash_policy type; 332 | }; 333 | template 334 | struct HashPolicySelector> 335 | { 336 | typedef typename T::hash_policy type; 337 | }; 338 | 339 | template 340 | class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal 341 | { 342 | using Entry = detailv3::sherwood_v3_entry; 343 | using AllocatorTraits = std::allocator_traits; 344 | using EntryPointer = typename AllocatorTraits::pointer; 345 | struct convertible_to_iterator; 346 | 347 | public: 348 | 349 | using value_type = T; 350 | using size_type = size_t; 351 | using difference_type = std::ptrdiff_t; 352 | using hasher = ArgumentHash; 353 | using key_equal = ArgumentEqual; 354 | using allocator_type = EntryAlloc; 355 | using reference = value_type &; 356 | using const_reference = const value_type &; 357 | using pointer = value_type *; 358 | using const_pointer = const value_type *; 359 | 360 | sherwood_v3_table() 361 | { 362 | } 363 | explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) 364 | : EntryAlloc(alloc), Hasher(hash), Equal(equal) 365 | { 366 | rehash(bucket_count); 367 | } 368 | sherwood_v3_table(size_type bucket_count, const ArgumentAlloc & alloc) 369 | : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) 370 | { 371 | } 372 | sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) 373 | : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) 374 | { 375 | } 376 | explicit sherwood_v3_table(const ArgumentAlloc & alloc) 377 | : EntryAlloc(alloc) 378 | { 379 | } 380 | template 381 | sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) 382 | : sherwood_v3_table(bucket_count, hash, equal, alloc) 383 | { 384 | insert(first, last); 385 | } 386 | template 387 | sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc) 388 | : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) 389 | { 390 | } 391 | template 392 | sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) 393 | : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) 394 | { 395 | } 396 | sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) 397 | : sherwood_v3_table(bucket_count, hash, equal, alloc) 398 | { 399 | if (bucket_count == 0) 400 | rehash(il.size()); 401 | insert(il.begin(), il.end()); 402 | } 403 | sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc & alloc) 404 | : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) 405 | { 406 | } 407 | sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) 408 | : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) 409 | { 410 | } 411 | sherwood_v3_table(const sherwood_v3_table & other) 412 | : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) 413 | { 414 | } 415 | sherwood_v3_table(const sherwood_v3_table & other, const ArgumentAlloc & alloc) 416 | : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) 417 | { 418 | rehash_for_other_container(other); 419 | try 420 | { 421 | insert(other.begin(), other.end()); 422 | } 423 | catch(...) 424 | { 425 | clear(); 426 | deallocate_data(entries, num_slots_minus_one, max_lookups); 427 | throw; 428 | } 429 | } 430 | sherwood_v3_table(sherwood_v3_table && other) noexcept 431 | : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) 432 | { 433 | swap_pointers(other); 434 | } 435 | sherwood_v3_table(sherwood_v3_table && other, const ArgumentAlloc & alloc) noexcept 436 | : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) 437 | { 438 | swap_pointers(other); 439 | } 440 | sherwood_v3_table & operator=(const sherwood_v3_table & other) 441 | { 442 | if (this == std::addressof(other)) 443 | return *this; 444 | 445 | clear(); 446 | if (AllocatorTraits::propagate_on_container_copy_assignment::value) 447 | { 448 | if (static_cast(*this) != static_cast(other)) 449 | { 450 | reset_to_empty_state(); 451 | } 452 | AssignIfTrue()(*this, other); 453 | } 454 | _max_load_factor = other._max_load_factor; 455 | static_cast(*this) = other; 456 | static_cast(*this) = other; 457 | rehash_for_other_container(other); 458 | insert(other.begin(), other.end()); 459 | return *this; 460 | } 461 | sherwood_v3_table & operator=(sherwood_v3_table && other) noexcept 462 | { 463 | if (this == std::addressof(other)) 464 | return *this; 465 | else if (AllocatorTraits::propagate_on_container_move_assignment::value) 466 | { 467 | clear(); 468 | reset_to_empty_state(); 469 | AssignIfTrue()(*this, std::move(other)); 470 | swap_pointers(other); 471 | } 472 | else if (static_cast(*this) == static_cast(other)) 473 | { 474 | swap_pointers(other); 475 | } 476 | else 477 | { 478 | clear(); 479 | _max_load_factor = other._max_load_factor; 480 | rehash_for_other_container(other); 481 | for (T & elem : other) 482 | emplace(std::move(elem)); 483 | other.clear(); 484 | } 485 | static_cast(*this) = std::move(other); 486 | static_cast(*this) = std::move(other); 487 | return *this; 488 | } 489 | ~sherwood_v3_table() 490 | { 491 | clear(); 492 | deallocate_data(entries, num_slots_minus_one, max_lookups); 493 | } 494 | 495 | const allocator_type & get_allocator() const 496 | { 497 | return static_cast(*this); 498 | } 499 | const ArgumentEqual & key_eq() const 500 | { 501 | return static_cast(*this); 502 | } 503 | const ArgumentHash & hash_function() const 504 | { 505 | return static_cast(*this); 506 | } 507 | 508 | template 509 | struct templated_iterator 510 | { 511 | templated_iterator() = default; 512 | templated_iterator(EntryPointer current) 513 | : current(current) 514 | { 515 | } 516 | EntryPointer current = EntryPointer(); 517 | 518 | using iterator_category = std::forward_iterator_tag; 519 | using value_type = ValueType; 520 | using difference_type = ptrdiff_t; 521 | using pointer = ValueType *; 522 | using reference = ValueType &; 523 | 524 | friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs) 525 | { 526 | return lhs.current == rhs.current; 527 | } 528 | friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs) 529 | { 530 | return !(lhs == rhs); 531 | } 532 | 533 | templated_iterator & operator++() 534 | { 535 | do 536 | { 537 | ++current; 538 | } 539 | while(current->is_empty()); 540 | return *this; 541 | } 542 | templated_iterator operator++(int) 543 | { 544 | templated_iterator copy(*this); 545 | ++*this; 546 | return copy; 547 | } 548 | 549 | ValueType & operator*() const 550 | { 551 | return current->value; 552 | } 553 | ValueType * operator->() const 554 | { 555 | return std::addressof(current->value); 556 | } 557 | 558 | operator templated_iterator() const 559 | { 560 | return { current }; 561 | } 562 | }; 563 | using iterator = templated_iterator; 564 | using const_iterator = templated_iterator; 565 | 566 | iterator begin() 567 | { 568 | for (EntryPointer it = entries;; ++it) 569 | { 570 | if (it->has_value()) 571 | return { it }; 572 | } 573 | } 574 | const_iterator begin() const 575 | { 576 | for (EntryPointer it = entries;; ++it) 577 | { 578 | if (it->has_value()) 579 | return { it }; 580 | } 581 | } 582 | const_iterator cbegin() const 583 | { 584 | return begin(); 585 | } 586 | iterator end() 587 | { 588 | return { entries + static_cast(num_slots_minus_one + max_lookups) }; 589 | } 590 | const_iterator end() const 591 | { 592 | return { entries + static_cast(num_slots_minus_one + max_lookups) }; 593 | } 594 | const_iterator cend() const 595 | { 596 | return end(); 597 | } 598 | 599 | iterator find(const FindKey & key) 600 | { 601 | size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); 602 | EntryPointer it = entries + ptrdiff_t(index); 603 | for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) 604 | { 605 | if (compares_equal(key, it->value)) 606 | return { it }; 607 | } 608 | return end(); 609 | } 610 | const_iterator find(const FindKey & key) const 611 | { 612 | return const_cast(this)->find(key); 613 | } 614 | size_t count(const FindKey & key) const 615 | { 616 | return find(key) == end() ? 0 : 1; 617 | } 618 | std::pair equal_range(const FindKey & key) 619 | { 620 | iterator found = find(key); 621 | if (found == end()) 622 | return { found, found }; 623 | else 624 | return { found, std::next(found) }; 625 | } 626 | std::pair equal_range(const FindKey & key) const 627 | { 628 | const_iterator found = find(key); 629 | if (found == end()) 630 | return { found, found }; 631 | else 632 | return { found, std::next(found) }; 633 | } 634 | 635 | template 636 | std::pair emplace(Key && key, Args &&... args) 637 | { 638 | size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); 639 | EntryPointer current_entry = entries + ptrdiff_t(index); 640 | int8_t distance_from_desired = 0; 641 | for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) 642 | { 643 | if (compares_equal(key, current_entry->value)) 644 | return { { current_entry }, false }; 645 | } 646 | return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); 647 | } 648 | 649 | std::pair insert(const value_type & value) 650 | { 651 | return emplace(value); 652 | } 653 | std::pair insert(value_type && value) 654 | { 655 | return emplace(std::move(value)); 656 | } 657 | template 658 | iterator emplace_hint(const_iterator, Args &&... args) 659 | { 660 | return emplace(std::forward(args)...).first; 661 | } 662 | iterator insert(const_iterator, const value_type & value) 663 | { 664 | return emplace(value).first; 665 | } 666 | iterator insert(const_iterator, value_type && value) 667 | { 668 | return emplace(std::move(value)).first; 669 | } 670 | 671 | template 672 | void insert(It begin, It end) 673 | { 674 | for (; begin != end; ++begin) 675 | { 676 | emplace(*begin); 677 | } 678 | } 679 | void insert(std::initializer_list il) 680 | { 681 | insert(il.begin(), il.end()); 682 | } 683 | 684 | void rehash(size_t num_buckets) 685 | { 686 | num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); 687 | if (num_buckets == 0) 688 | { 689 | reset_to_empty_state(); 690 | return; 691 | } 692 | auto new_prime_index = hash_policy.next_size_over(num_buckets); 693 | if (num_buckets == bucket_count()) 694 | return; 695 | int8_t new_max_lookups = compute_max_lookups(num_buckets); 696 | EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); 697 | EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); 698 | for (EntryPointer it = new_buckets; it != special_end_item; ++it) 699 | it->distance_from_desired = -1; 700 | special_end_item->distance_from_desired = Entry::special_end_value; 701 | std::swap(entries, new_buckets); 702 | std::swap(num_slots_minus_one, num_buckets); 703 | --num_slots_minus_one; 704 | hash_policy.commit(new_prime_index); 705 | int8_t old_max_lookups = max_lookups; 706 | max_lookups = new_max_lookups; 707 | num_elements = 0; 708 | for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) 709 | { 710 | if (it->has_value()) 711 | { 712 | emplace(std::move(it->value)); 713 | it->destroy_value(); 714 | } 715 | } 716 | deallocate_data(new_buckets, num_buckets, old_max_lookups); 717 | } 718 | 719 | void reserve(size_t num_elements) 720 | { 721 | size_t required_buckets = num_buckets_for_reserve(num_elements); 722 | if (required_buckets > bucket_count()) 723 | rehash(required_buckets); 724 | } 725 | 726 | // the return value is a type that can be converted to an iterator 727 | // the reason for doing this is that it's not free to find the 728 | // iterator pointing at the next element. if you care about the 729 | // next iterator, turn the return value into an iterator 730 | convertible_to_iterator erase(const_iterator to_erase) 731 | { 732 | EntryPointer current = to_erase.current; 733 | current->destroy_value(); 734 | --num_elements; 735 | for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) 736 | { 737 | current->emplace(next->distance_from_desired - 1, std::move(next->value)); 738 | next->destroy_value(); 739 | } 740 | return { to_erase.current }; 741 | } 742 | 743 | iterator erase(const_iterator begin_it, const_iterator end_it) 744 | { 745 | if (begin_it == end_it) 746 | return { begin_it.current }; 747 | for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) 748 | { 749 | if (it->has_value()) 750 | { 751 | it->destroy_value(); 752 | --num_elements; 753 | } 754 | } 755 | if (end_it == this->end()) 756 | return this->end(); 757 | ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); 758 | EntryPointer to_return = end_it.current - num_to_move; 759 | for (EntryPointer it = end_it.current; !it->is_at_desired_position();) 760 | { 761 | EntryPointer target = it - num_to_move; 762 | target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); 763 | it->destroy_value(); 764 | ++it; 765 | num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); 766 | } 767 | return { to_return }; 768 | } 769 | 770 | size_t erase(const FindKey & key) 771 | { 772 | auto found = find(key); 773 | if (found == end()) 774 | return 0; 775 | else 776 | { 777 | erase(found); 778 | return 1; 779 | } 780 | } 781 | 782 | void clear() 783 | { 784 | for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) 785 | { 786 | if (it->has_value()) 787 | it->destroy_value(); 788 | } 789 | num_elements = 0; 790 | } 791 | 792 | void shrink_to_fit() 793 | { 794 | rehash_for_other_container(*this); 795 | } 796 | 797 | void swap(sherwood_v3_table & other) 798 | { 799 | using std::swap; 800 | swap_pointers(other); 801 | swap(static_cast(*this), static_cast(other)); 802 | swap(static_cast(*this), static_cast(other)); 803 | if (AllocatorTraits::propagate_on_container_swap::value) 804 | swap(static_cast(*this), static_cast(other)); 805 | } 806 | 807 | size_t size() const 808 | { 809 | return num_elements; 810 | } 811 | size_t max_size() const 812 | { 813 | return (AllocatorTraits::max_size(*this)) / sizeof(Entry); 814 | } 815 | size_t bucket_count() const 816 | { 817 | return num_slots_minus_one ? num_slots_minus_one + 1 : 0; 818 | } 819 | size_type max_bucket_count() const 820 | { 821 | return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); 822 | } 823 | size_t bucket(const FindKey & key) const 824 | { 825 | return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); 826 | } 827 | float load_factor() const 828 | { 829 | size_t buckets = bucket_count(); 830 | if (buckets) 831 | return static_cast(num_elements) / bucket_count(); 832 | else 833 | return 0; 834 | } 835 | void max_load_factor(float value) 836 | { 837 | _max_load_factor = value; 838 | } 839 | float max_load_factor() const 840 | { 841 | return _max_load_factor; 842 | } 843 | 844 | bool empty() const 845 | { 846 | return num_elements == 0; 847 | } 848 | 849 | private: 850 | EntryPointer entries = empty_default_table(); 851 | size_t num_slots_minus_one = 0; 852 | typename HashPolicySelector::type hash_policy; 853 | int8_t max_lookups = detailv3::min_lookups - 1; 854 | float _max_load_factor = 0.5f; 855 | size_t num_elements = 0; 856 | 857 | EntryPointer empty_default_table() 858 | { 859 | EntryPointer result = AllocatorTraits::allocate(*this, detailv3::min_lookups); 860 | EntryPointer special_end_item = result + static_cast(detailv3::min_lookups - 1); 861 | for (EntryPointer it = result; it != special_end_item; ++it) 862 | it->distance_from_desired = -1; 863 | special_end_item->distance_from_desired = Entry::special_end_value; 864 | return result; 865 | } 866 | 867 | static int8_t compute_max_lookups(size_t num_buckets) 868 | { 869 | int8_t desired = detailv3::log2(num_buckets); 870 | return std::max(detailv3::min_lookups, desired); 871 | } 872 | 873 | size_t num_buckets_for_reserve(size_t num_elements) const 874 | { 875 | return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); 876 | } 877 | void rehash_for_other_container(const sherwood_v3_table & other) 878 | { 879 | rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); 880 | } 881 | 882 | void swap_pointers(sherwood_v3_table & other) 883 | { 884 | using std::swap; 885 | swap(hash_policy, other.hash_policy); 886 | swap(entries, other.entries); 887 | swap(num_slots_minus_one, other.num_slots_minus_one); 888 | swap(num_elements, other.num_elements); 889 | swap(max_lookups, other.max_lookups); 890 | swap(_max_load_factor, other._max_load_factor); 891 | } 892 | 893 | template 894 | SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key && key, Args &&... args) 895 | { 896 | using std::swap; 897 | if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) 898 | { 899 | grow(); 900 | return emplace(std::forward(key), std::forward(args)...); 901 | } 902 | else if (current_entry->is_empty()) 903 | { 904 | current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); 905 | ++num_elements; 906 | return { { current_entry }, true }; 907 | } 908 | value_type to_insert(std::forward(key), std::forward(args)...); 909 | swap(distance_from_desired, current_entry->distance_from_desired); 910 | swap(to_insert, current_entry->value); 911 | iterator result = { current_entry }; 912 | for (++distance_from_desired, ++current_entry;; ++current_entry) 913 | { 914 | if (current_entry->is_empty()) 915 | { 916 | current_entry->emplace(distance_from_desired, std::move(to_insert)); 917 | ++num_elements; 918 | return { result, true }; 919 | } 920 | else if (current_entry->distance_from_desired < distance_from_desired) 921 | { 922 | swap(distance_from_desired, current_entry->distance_from_desired); 923 | swap(to_insert, current_entry->value); 924 | ++distance_from_desired; 925 | } 926 | else 927 | { 928 | ++distance_from_desired; 929 | if (distance_from_desired == max_lookups) 930 | { 931 | swap(to_insert, result.current->value); 932 | grow(); 933 | return emplace(std::move(to_insert)); 934 | } 935 | } 936 | } 937 | } 938 | 939 | void grow() 940 | { 941 | rehash(std::max(size_t(4), 2 * bucket_count())); 942 | } 943 | 944 | void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) 945 | { 946 | AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); 947 | } 948 | 949 | void reset_to_empty_state() 950 | { 951 | deallocate_data(entries, num_slots_minus_one, max_lookups); 952 | entries = empty_default_table(); 953 | num_slots_minus_one = 0; 954 | hash_policy.reset(); 955 | max_lookups = detailv3::min_lookups - 1; 956 | } 957 | 958 | template 959 | size_t hash_object(const U & key) 960 | { 961 | return static_cast(*this)(key); 962 | } 963 | template 964 | size_t hash_object(const U & key) const 965 | { 966 | return static_cast(*this)(key); 967 | } 968 | template 969 | bool compares_equal(const L & lhs, const R & rhs) 970 | { 971 | return static_cast(*this)(lhs, rhs); 972 | } 973 | 974 | struct convertible_to_iterator 975 | { 976 | EntryPointer it; 977 | 978 | operator iterator() 979 | { 980 | if (it->has_value()) 981 | return { it }; 982 | else 983 | return ++iterator{it}; 984 | } 985 | operator const_iterator() 986 | { 987 | if (it->has_value()) 988 | return { it }; 989 | else 990 | return ++const_iterator{it}; 991 | } 992 | }; 993 | 994 | }; 995 | } 996 | 997 | struct prime_number_hash_policy 998 | { 999 | static size_t mod0(size_t) { return 0u; } 1000 | static size_t mod2(size_t hash) { return hash % 2u; } 1001 | static size_t mod3(size_t hash) { return hash % 3u; } 1002 | static size_t mod5(size_t hash) { return hash % 5u; } 1003 | static size_t mod7(size_t hash) { return hash % 7u; } 1004 | static size_t mod11(size_t hash) { return hash % 11u; } 1005 | static size_t mod13(size_t hash) { return hash % 13u; } 1006 | static size_t mod17(size_t hash) { return hash % 17u; } 1007 | static size_t mod23(size_t hash) { return hash % 23u; } 1008 | static size_t mod29(size_t hash) { return hash % 29u; } 1009 | static size_t mod37(size_t hash) { return hash % 37u; } 1010 | static size_t mod47(size_t hash) { return hash % 47u; } 1011 | static size_t mod59(size_t hash) { return hash % 59u; } 1012 | static size_t mod73(size_t hash) { return hash % 73u; } 1013 | static size_t mod97(size_t hash) { return hash % 97u; } 1014 | static size_t mod127(size_t hash) { return hash % 127u; } 1015 | static size_t mod151(size_t hash) { return hash % 151u; } 1016 | static size_t mod197(size_t hash) { return hash % 197u; } 1017 | static size_t mod251(size_t hash) { return hash % 251u; } 1018 | static size_t mod313(size_t hash) { return hash % 313u; } 1019 | static size_t mod397(size_t hash) { return hash % 397u; } 1020 | static size_t mod499(size_t hash) { return hash % 499u; } 1021 | static size_t mod631(size_t hash) { return hash % 631u; } 1022 | static size_t mod797(size_t hash) { return hash % 797u; } 1023 | static size_t mod1009(size_t hash) { return hash % 1009u; } 1024 | static size_t mod1259(size_t hash) { return hash % 1259u; } 1025 | static size_t mod1597(size_t hash) { return hash % 1597u; } 1026 | static size_t mod2011(size_t hash) { return hash % 2011u; } 1027 | static size_t mod2539(size_t hash) { return hash % 2539u; } 1028 | static size_t mod3203(size_t hash) { return hash % 3203u; } 1029 | static size_t mod4027(size_t hash) { return hash % 4027u; } 1030 | static size_t mod5087(size_t hash) { return hash % 5087u; } 1031 | static size_t mod6421(size_t hash) { return hash % 6421u; } 1032 | static size_t mod8089(size_t hash) { return hash % 8089u; } 1033 | static size_t mod10193(size_t hash) { return hash % 10193u; } 1034 | static size_t mod12853(size_t hash) { return hash % 12853u; } 1035 | static size_t mod16193(size_t hash) { return hash % 16193u; } 1036 | static size_t mod20399(size_t hash) { return hash % 20399u; } 1037 | static size_t mod25717(size_t hash) { return hash % 25717u; } 1038 | static size_t mod32401(size_t hash) { return hash % 32401u; } 1039 | static size_t mod40823(size_t hash) { return hash % 40823u; } 1040 | static size_t mod51437(size_t hash) { return hash % 51437u; } 1041 | static size_t mod64811(size_t hash) { return hash % 64811u; } 1042 | static size_t mod81649(size_t hash) { return hash % 81649u; } 1043 | static size_t mod102877(size_t hash) { return hash % 102877u; } 1044 | static size_t mod129607(size_t hash) { return hash % 129607u; } 1045 | static size_t mod163307(size_t hash) { return hash % 163307u; } 1046 | static size_t mod205759(size_t hash) { return hash % 205759u; } 1047 | static size_t mod259229(size_t hash) { return hash % 259229u; } 1048 | static size_t mod326617(size_t hash) { return hash % 326617u; } 1049 | static size_t mod411527(size_t hash) { return hash % 411527u; } 1050 | static size_t mod518509(size_t hash) { return hash % 518509u; } 1051 | static size_t mod653267(size_t hash) { return hash % 653267u; } 1052 | static size_t mod823117(size_t hash) { return hash % 823117u; } 1053 | static size_t mod1037059(size_t hash) { return hash % 1037059u; } 1054 | static size_t mod1306601(size_t hash) { return hash % 1306601u; } 1055 | static size_t mod1646237(size_t hash) { return hash % 1646237u; } 1056 | static size_t mod2074129(size_t hash) { return hash % 2074129u; } 1057 | static size_t mod2613229(size_t hash) { return hash % 2613229u; } 1058 | static size_t mod3292489(size_t hash) { return hash % 3292489u; } 1059 | static size_t mod4148279(size_t hash) { return hash % 4148279u; } 1060 | static size_t mod5226491(size_t hash) { return hash % 5226491u; } 1061 | static size_t mod6584983(size_t hash) { return hash % 6584983u; } 1062 | static size_t mod8296553(size_t hash) { return hash % 8296553u; } 1063 | static size_t mod10453007(size_t hash) { return hash % 10453007u; } 1064 | static size_t mod13169977(size_t hash) { return hash % 13169977u; } 1065 | static size_t mod16593127(size_t hash) { return hash % 16593127u; } 1066 | static size_t mod20906033(size_t hash) { return hash % 20906033u; } 1067 | static size_t mod26339969(size_t hash) { return hash % 26339969u; } 1068 | static size_t mod33186281(size_t hash) { return hash % 33186281u; } 1069 | static size_t mod41812097(size_t hash) { return hash % 41812097u; } 1070 | static size_t mod52679969(size_t hash) { return hash % 52679969u; } 1071 | static size_t mod66372617(size_t hash) { return hash % 66372617u; } 1072 | static size_t mod83624237(size_t hash) { return hash % 83624237u; } 1073 | static size_t mod105359939(size_t hash) { return hash % 105359939u; } 1074 | static size_t mod132745199(size_t hash) { return hash % 132745199u; } 1075 | static size_t mod167248483(size_t hash) { return hash % 167248483u; } 1076 | static size_t mod210719881(size_t hash) { return hash % 210719881u; } 1077 | static size_t mod265490441(size_t hash) { return hash % 265490441u; } 1078 | static size_t mod334496971(size_t hash) { return hash % 334496971u; } 1079 | static size_t mod421439783(size_t hash) { return hash % 421439783u; } 1080 | static size_t mod530980861(size_t hash) { return hash % 530980861u; } 1081 | static size_t mod668993977(size_t hash) { return hash % 668993977u; } 1082 | static size_t mod842879579(size_t hash) { return hash % 842879579u; } 1083 | static size_t mod1061961721(size_t hash) { return hash % 1061961721u; } 1084 | static size_t mod1337987929(size_t hash) { return hash % 1337987929u; } 1085 | static size_t mod1685759167(size_t hash) { return hash % 1685759167u; } 1086 | static size_t mod2123923447(size_t hash) { return hash % 2123923447u; } 1087 | static size_t mod2675975881(size_t hash) { return hash % 2675975881u; } 1088 | static size_t mod3371518343(size_t hash) { return hash % 3371518343u; } 1089 | static size_t mod4247846927(size_t hash) { return hash % 4247846927u; } 1090 | 1091 | #ifdef ENV64BIT 1092 | static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } 1093 | static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } 1094 | static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } 1095 | static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } 1096 | static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } 1097 | static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } 1098 | static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } 1099 | static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } 1100 | static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } 1101 | static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } 1102 | static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } 1103 | static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } 1104 | static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } 1105 | static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } 1106 | static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } 1107 | static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } 1108 | static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } 1109 | static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } 1110 | static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } 1111 | static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } 1112 | static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } 1113 | static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } 1114 | static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } 1115 | static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } 1116 | static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } 1117 | static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } 1118 | static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } 1119 | static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } 1120 | static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } 1121 | static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } 1122 | static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } 1123 | static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } 1124 | static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } 1125 | static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } 1126 | static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } 1127 | static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } 1128 | static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } 1129 | static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } 1130 | static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } 1131 | static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } 1132 | static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } 1133 | static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } 1134 | static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } 1135 | static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } 1136 | static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } 1137 | static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } 1138 | static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } 1139 | static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } 1140 | static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } 1141 | static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } 1142 | static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } 1143 | static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } 1144 | static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } 1145 | static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } 1146 | static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } 1147 | static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } 1148 | static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } 1149 | static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } 1150 | static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } 1151 | static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } 1152 | static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } 1153 | static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } 1154 | static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } 1155 | static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } 1156 | static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } 1157 | static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } 1158 | static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } 1159 | static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } 1160 | static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } 1161 | static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } 1162 | static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } 1163 | static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } 1164 | static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } 1165 | static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } 1166 | static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } 1167 | static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } 1168 | static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } 1169 | static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } 1170 | static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } 1171 | static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } 1172 | static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } 1173 | static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } 1174 | static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } 1175 | static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } 1176 | static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } 1177 | static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } 1178 | static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } 1179 | static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } 1180 | static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } 1181 | static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } 1182 | static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } 1183 | static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } 1184 | static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } 1185 | static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } 1186 | static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } 1187 | static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } 1188 | #endif 1189 | 1190 | using mod_function = size_t (*)(size_t); 1191 | 1192 | mod_function next_size_over(size_t & size) const 1193 | { 1194 | // prime numbers generated by the following method: 1195 | // 1. start with a prime p = 2 1196 | // 2. go to wolfram alpha and get p = NextPrime(2 * p) 1197 | // 3. repeat 2. until you overflow 64 bits 1198 | // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. 1199 | // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps 1200 | // 5. get PrevPrime(2^64) and put it at the end 1201 | static constexpr const size_t prime_list[] = 1202 | { 1203 | 2u, 3u, 5u, 7u, 11u, 13u, 17u, 23u, 29u, 37u, 47u, 1204 | 59u, 73u, 97u, 127u, 151u, 197u, 251u, 313u, 397u, 1205 | 499u, 631u, 797u, 1009u, 1259u, 1597u, 2011u, 2539u, 1206 | 3203u, 4027u, 5087u, 6421u, 8089u, 10193u, 12853u, 16193u, 1207 | 20399u, 25717u, 32401u, 40823u, 51437u, 64811u, 81649u, 1208 | 102877u, 129607u, 163307u, 205759u, 259229u, 326617u, 1209 | 411527u, 518509u, 653267u, 823117u, 1037059u, 1306601u, 1210 | 1646237u, 2074129u, 2613229u, 3292489u, 4148279u, 5226491u, 1211 | 6584983u, 8296553u, 10453007u, 13169977u, 16593127u, 20906033u, 1212 | 26339969u, 33186281u, 41812097u, 52679969u, 66372617u, 1213 | 83624237u, 105359939u, 132745199u, 167248483u, 210719881u, 1214 | 265490441u, 334496971u, 421439783u, 530980861u, 668993977u, 1215 | 842879579u, 1061961721u, 1337987929u, 1685759167u, 2123923447u, 1216 | 2675975881u, 3371518343u, 4247846927u 1217 | #ifdef ENV64BIT 1218 | , 5351951779llu, 6743036717llu, 1219 | 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, 1220 | 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, 1221 | 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, 1222 | 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, 1223 | 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, 1224 | 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, 1225 | 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, 1226 | 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, 1227 | 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, 1228 | 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, 1229 | 87686378464759llu, 110477914016779llu, 139193449418173llu, 1230 | 175372756929481llu, 220955828033581llu, 278386898836457llu, 1231 | 350745513859007llu, 441911656067171llu, 556773797672909llu, 1232 | 701491027718027llu, 883823312134381llu, 1113547595345903llu, 1233 | 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, 1234 | 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, 1235 | 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, 1236 | 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, 1237 | 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, 1238 | 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, 1239 | 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, 1240 | 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, 1241 | 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, 1242 | 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, 1243 | 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, 1244 | 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, 1245 | 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, 1246 | 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu 1247 | #endif 1248 | }; 1249 | static constexpr size_t (* const mod_functions[])(size_t) = 1250 | { 1251 | &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, 1252 | &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, 1253 | &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, 1254 | &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, 1255 | &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, 1256 | &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, 1257 | &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, 1258 | &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, 1259 | &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, 1260 | &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, 1261 | &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, 1262 | &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, 1263 | &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927 1264 | 1265 | #ifdef ENV64BIT 1266 | , &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, 1267 | &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, 1268 | &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, 1269 | &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, 1270 | &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, 1271 | &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, 1272 | &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, 1273 | &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, 1274 | &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, 1275 | &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, 1276 | &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, 1277 | &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, 1278 | &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, 1279 | &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, 1280 | &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, 1281 | &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, 1282 | &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, 1283 | &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, 1284 | &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, 1285 | &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, 1286 | &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, 1287 | &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, 1288 | &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, 1289 | &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, 1290 | &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, 1291 | &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 1292 | #endif 1293 | }; 1294 | const size_t * found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); 1295 | size = *found; 1296 | return mod_functions[1 + found - prime_list]; 1297 | } 1298 | void commit(mod_function new_mod_function) 1299 | { 1300 | current_mod_function = new_mod_function; 1301 | } 1302 | void reset() 1303 | { 1304 | current_mod_function = &mod0; 1305 | } 1306 | 1307 | size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const 1308 | { 1309 | return current_mod_function(hash); 1310 | } 1311 | size_t keep_in_range(size_t index, size_t num_slots_minus_one) const 1312 | { 1313 | return index > num_slots_minus_one ? current_mod_function(index) : index; 1314 | } 1315 | 1316 | private: 1317 | mod_function current_mod_function = &mod0; 1318 | }; 1319 | 1320 | struct power_of_two_hash_policy 1321 | { 1322 | size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const 1323 | { 1324 | return hash & num_slots_minus_one; 1325 | } 1326 | size_t keep_in_range(size_t index, size_t num_slots_minus_one) const 1327 | { 1328 | return index_for_hash(index, num_slots_minus_one); 1329 | } 1330 | int8_t next_size_over(size_t & size) const 1331 | { 1332 | size = detailv3::next_power_of_two(size); 1333 | return 0; 1334 | } 1335 | void commit(int8_t) 1336 | { 1337 | } 1338 | void reset() 1339 | { 1340 | } 1341 | 1342 | }; 1343 | 1344 | struct fibonacci_hash_policy 1345 | { 1346 | size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const 1347 | { 1348 | return (11400714819323198485ull * hash) >> shift; 1349 | } 1350 | size_t keep_in_range(size_t index, size_t num_slots_minus_one) const 1351 | { 1352 | return index & num_slots_minus_one; 1353 | } 1354 | 1355 | int8_t next_size_over(size_t & size) const 1356 | { 1357 | size = std::max(size_t(2), detailv3::next_power_of_two(size)); 1358 | return 64 - detailv3::log2(size); 1359 | } 1360 | void commit(int8_t shift) 1361 | { 1362 | this->shift = shift; 1363 | } 1364 | void reset() 1365 | { 1366 | shift = 63; 1367 | } 1368 | 1369 | private: 1370 | int8_t shift = 63; 1371 | }; 1372 | 1373 | template, typename E = std::equal_to, typename A = std::allocator > > 1374 | class flat_hash_map 1375 | : public detailv3::sherwood_v3_table 1376 | < 1377 | std::pair, 1378 | K, 1379 | H, 1380 | detailv3::KeyOrValueHasher, H>, 1381 | E, 1382 | detailv3::KeyOrValueEquality, E>, 1383 | A, 1384 | typename std::allocator_traits::template rebind_alloc>> 1385 | > 1386 | { 1387 | using Table = detailv3::sherwood_v3_table 1388 | < 1389 | std::pair, 1390 | K, 1391 | H, 1392 | detailv3::KeyOrValueHasher, H>, 1393 | E, 1394 | detailv3::KeyOrValueEquality, E>, 1395 | A, 1396 | typename std::allocator_traits::template rebind_alloc>> 1397 | >; 1398 | public: 1399 | 1400 | using key_type = K; 1401 | using mapped_type = V; 1402 | 1403 | using Table::Table; 1404 | flat_hash_map() 1405 | { 1406 | } 1407 | 1408 | inline V & operator[](const K & key) 1409 | { 1410 | return emplace(key, convertible_to_value()).first->second; 1411 | } 1412 | inline V & operator[](K && key) 1413 | { 1414 | return emplace(std::move(key), convertible_to_value()).first->second; 1415 | } 1416 | V & at(const K & key) 1417 | { 1418 | auto found = this->find(key); 1419 | if (found == this->end()) 1420 | throw std::out_of_range("Argument passed to at() was not in the map."); 1421 | return found->second; 1422 | } 1423 | const V & at(const K & key) const 1424 | { 1425 | auto found = this->find(key); 1426 | if (found == this->end()) 1427 | throw std::out_of_range("Argument passed to at() was not in the map."); 1428 | return found->second; 1429 | } 1430 | 1431 | using Table::emplace; 1432 | std::pair emplace() 1433 | { 1434 | return emplace(key_type(), convertible_to_value()); 1435 | } 1436 | template 1437 | std::pair insert_or_assign(const key_type & key, M && m) 1438 | { 1439 | auto emplace_result = emplace(key, std::forward(m)); 1440 | if (!emplace_result.second) 1441 | emplace_result.first->second = std::forward(m); 1442 | return emplace_result; 1443 | } 1444 | template 1445 | std::pair insert_or_assign(key_type && key, M && m) 1446 | { 1447 | auto emplace_result = emplace(std::move(key), std::forward(m)); 1448 | if (!emplace_result.second) 1449 | emplace_result.first->second = std::forward(m); 1450 | return emplace_result; 1451 | } 1452 | template 1453 | typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m) 1454 | { 1455 | return insert_or_assign(key, std::forward(m)).first; 1456 | } 1457 | template 1458 | typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m) 1459 | { 1460 | return insert_or_assign(std::move(key), std::forward(m)).first; 1461 | } 1462 | 1463 | friend bool operator==(const flat_hash_map & lhs, const flat_hash_map & rhs) 1464 | { 1465 | if (lhs.size() != rhs.size()) 1466 | return false; 1467 | for (const typename Table::value_type & value : lhs) 1468 | { 1469 | auto found = rhs.find(value.first); 1470 | if (found == rhs.end()) 1471 | return false; 1472 | else if (value.second != found->second) 1473 | return false; 1474 | } 1475 | return true; 1476 | } 1477 | friend bool operator!=(const flat_hash_map & lhs, const flat_hash_map & rhs) 1478 | { 1479 | return !(lhs == rhs); 1480 | } 1481 | 1482 | private: 1483 | struct convertible_to_value 1484 | { 1485 | operator V() const 1486 | { 1487 | return V(); 1488 | } 1489 | }; 1490 | }; 1491 | 1492 | template, typename E = std::equal_to, typename A = std::allocator > 1493 | class flat_hash_set 1494 | : public detailv3::sherwood_v3_table 1495 | < 1496 | T, 1497 | T, 1498 | H, 1499 | detailv3::functor_storage, 1500 | E, 1501 | detailv3::functor_storage, 1502 | A, 1503 | typename std::allocator_traits::template rebind_alloc> 1504 | > 1505 | { 1506 | using Table = detailv3::sherwood_v3_table 1507 | < 1508 | T, 1509 | T, 1510 | H, 1511 | detailv3::functor_storage, 1512 | E, 1513 | detailv3::functor_storage, 1514 | A, 1515 | typename std::allocator_traits::template rebind_alloc> 1516 | >; 1517 | public: 1518 | 1519 | using key_type = T; 1520 | 1521 | using Table::Table; 1522 | flat_hash_set() 1523 | { 1524 | } 1525 | 1526 | template 1527 | std::pair emplace(Args &&... args) 1528 | { 1529 | return Table::emplace(T(std::forward(args)...)); 1530 | } 1531 | std::pair emplace(const key_type & arg) 1532 | { 1533 | return Table::emplace(arg); 1534 | } 1535 | std::pair emplace(key_type & arg) 1536 | { 1537 | return Table::emplace(arg); 1538 | } 1539 | std::pair emplace(const key_type && arg) 1540 | { 1541 | return Table::emplace(std::move(arg)); 1542 | } 1543 | std::pair emplace(key_type && arg) 1544 | { 1545 | return Table::emplace(std::move(arg)); 1546 | } 1547 | 1548 | friend bool operator==(const flat_hash_set & lhs, const flat_hash_set & rhs) 1549 | { 1550 | if (lhs.size() != rhs.size()) 1551 | return false; 1552 | for (const T & value : lhs) 1553 | { 1554 | if (rhs.find(value) == rhs.end()) 1555 | return false; 1556 | } 1557 | return true; 1558 | } 1559 | friend bool operator!=(const flat_hash_set & lhs, const flat_hash_set & rhs) 1560 | { 1561 | return !(lhs == rhs); 1562 | } 1563 | }; 1564 | 1565 | 1566 | template 1567 | struct power_of_two_std_hash : std::hash 1568 | { 1569 | typedef ska::power_of_two_hash_policy hash_policy; 1570 | }; 1571 | 1572 | } // end namespace ska 1573 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import fastremap 2 | import numpy as np 3 | 4 | x = np.ones((512,512,512), dtype=np.float32) 5 | x = fastremap.asfortranarray(x) 6 | print(x) 7 | print(x.flags) 8 | print(x.strides) 9 | 10 | print(x.dtype) 11 | 12 | 13 | 14 | # @profile 15 | # def run(): 16 | # x = np.ones( (512,512,512), dtype=np.uint32, order='C') 17 | # x += 1 18 | # print(x.strides, x.flags) 19 | # y = np.asfortranarray(x) 20 | # print(x.strides, x.flags) 21 | 22 | # print("done.") 23 | 24 | # run() -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,py312 3 | 4 | [testenv] 5 | platform = darwin 6 | deps = 7 | oldest-supported-numpy 8 | setuptools 9 | wheel 10 | -rrequirements.txt 11 | -rrequirements_dev.txt 12 | 13 | commands = 14 | python setup.py develop 15 | pytest -v -x automated_test.py 16 | python setup.py bdist_wheel --------------------------------------------------------------------------------