imops 0.8.1__tar.gz → 0.8.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of imops might be problematic. Click here for more details.

Files changed (52) hide show
  1. {imops-0.8.1 → imops-0.8.3}/MANIFEST.in +1 -1
  2. {imops-0.8.1/imops.egg-info → imops-0.8.3}/PKG-INFO +36 -16
  3. {imops-0.8.1 → imops-0.8.3}/README.md +34 -14
  4. imops-0.8.3/_build_utils.py +87 -0
  5. {imops-0.8.1 → imops-0.8.3}/imops/__init__.py +1 -0
  6. imops-0.8.3/imops/__version__.py +1 -0
  7. {imops-0.8.1 → imops-0.8.3}/imops/backend.py +14 -10
  8. {imops-0.8.1 → imops-0.8.3}/imops/box.py +20 -29
  9. {imops-0.8.1 → imops-0.8.3}/imops/crop.py +18 -2
  10. {imops-0.8.1 → imops-0.8.3}/imops/interp1d.py +16 -13
  11. {imops-0.8.1 → imops-0.8.3}/imops/measure.py +12 -9
  12. imops-0.8.3/imops/morphology.py +347 -0
  13. imops-0.8.3/imops/numeric.py +376 -0
  14. {imops-0.8.1 → imops-0.8.3}/imops/pad.py +41 -5
  15. {imops-0.8.1 → imops-0.8.3}/imops/radon.py +9 -7
  16. imops-0.8.3/imops/src/_fast_morphology.pyx +329 -0
  17. imops-0.8.3/imops/src/_fast_numeric.pyx +242 -0
  18. {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_zoom.pyx +1 -0
  19. imops-0.8.3/imops/src/_morphology.pyx +329 -0
  20. imops-0.8.3/imops/src/_numeric.pyx +242 -0
  21. {imops-0.8.1 → imops-0.8.3}/imops/src/_zoom.pyx +1 -0
  22. imops-0.8.3/imops/utils.py +206 -0
  23. {imops-0.8.1 → imops-0.8.3}/imops/zoom.py +9 -9
  24. {imops-0.8.1 → imops-0.8.3/imops.egg-info}/PKG-INFO +36 -16
  25. {imops-0.8.1 → imops-0.8.3}/imops.egg-info/SOURCES.txt +2 -2
  26. {imops-0.8.1 → imops-0.8.3}/imops.egg-info/requires.txt +1 -0
  27. {imops-0.8.1 → imops-0.8.3}/pyproject.toml +2 -2
  28. {imops-0.8.1 → imops-0.8.3}/requirements.txt +1 -0
  29. {imops-0.8.1 → imops-0.8.3}/setup.py +4 -33
  30. imops-0.8.1/_pyproject_build.py +0 -49
  31. imops-0.8.1/imops/__version__.py +0 -1
  32. imops-0.8.1/imops/_numeric.py +0 -124
  33. imops-0.8.1/imops/morphology.py +0 -227
  34. imops-0.8.1/imops/src/_fast_morphology.pyx +0 -145
  35. imops-0.8.1/imops/src/_fast_numeric.pyx +0 -64
  36. imops-0.8.1/imops/src/_morphology.pyx +0 -145
  37. imops-0.8.1/imops/src/_numeric.pyx +0 -64
  38. imops-0.8.1/imops/utils.py +0 -106
  39. {imops-0.8.1 → imops-0.8.3}/LICENSE +0 -0
  40. {imops-0.8.1 → imops-0.8.3}/imops/_configs.py +0 -0
  41. {imops-0.8.1 → imops-0.8.3}/imops/src/__init__.py +0 -0
  42. {imops-0.8.1 → imops-0.8.3}/imops/src/_backprojection.pyx +0 -0
  43. {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_backprojection.pyx +0 -0
  44. {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_measure.pyx +0 -0
  45. {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_radon.pyx +0 -0
  46. {imops-0.8.1 → imops-0.8.3}/imops/src/_measure.pyx +0 -0
  47. {imops-0.8.1 → imops-0.8.3}/imops/src/_numba_zoom.py +0 -0
  48. {imops-0.8.1 → imops-0.8.3}/imops/src/_radon.pyx +0 -0
  49. {imops-0.8.1 → imops-0.8.3}/imops/testing.py +0 -0
  50. {imops-0.8.1 → imops-0.8.3}/imops.egg-info/dependency_links.txt +0 -0
  51. {imops-0.8.1 → imops-0.8.3}/imops.egg-info/top_level.txt +0 -0
  52. {imops-0.8.1 → imops-0.8.3}/setup.cfg +0 -0
@@ -1,5 +1,5 @@
1
1
  include *.md
2
2
  include requirements.txt
3
3
  include pyproject.toml
4
- include _pyproject_build.py
4
+ include _build_utils.py
5
5
  recursive-include imops *.py
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imops
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary: Efficient parallelizable algorithms for multidimensional arrays to speed up your data pipelines
5
5
  Home-page: https://github.com/neuro-ml/imops
6
6
  Author: maxme1, vovaf709, talgat
7
7
  Author-email: maxs987@gmail.com, vovaf709@yandex.ru, saparov2130@gmail.com
8
8
  License: MIT
9
- Download-URL: https://github.com/neuro-ml/imops/archive/v0.8.1.tar.gz
9
+ Download-URL: https://github.com/neuro-ml/imops/archive/v0.8.3.tar.gz
10
10
  Keywords: image processing,fast,ndarray,data pipelines
11
11
  Platform: UNKNOWN
12
12
  Classifier: Development Status :: 5 - Production/Stable
@@ -30,7 +30,9 @@ License-File: LICENSE
30
30
 
31
31
  # Imops
32
32
 
33
- Efficient parallelizable algorithms for multidimensional arrays to speed up your data pipelines. Docs are [here](https://neuro-ml.github.io/imops/).
33
+ Efficient parallelizable algorithms for multidimensional arrays to speed up your data pipelines.
34
+ - [Documentation](https://neuro-ml.github.io/imops/)
35
+ - [Benchmarks](https://neuro-ml.github.io/imops/benchmarks/)
34
36
 
35
37
  # Install
36
38
 
@@ -39,15 +41,33 @@ pip install imops # default install with Cython backend
39
41
  pip install imops[numba] # additionally install Numba backend
40
42
  ```
41
43
 
44
+ # How fast is it?
45
+
46
+ Time comparisons (ms) for Intel(R) Xeon(R) Silver 4114 CPU @ 2.20GHz using 8 threads. All inputs are C-contiguous NumPy arrays. For morphology functions `bool` dtype is used and `float64` for all others.
47
+ | function / backend | Scipy() | Cython(fast=False) | Cython(fast=True) | Numba() |
48
+ |:----------------------:|:-----------:|:----------------------:|:---------------------:|:-----------:|
49
+ | `zoom(..., order=0)` | 2072 | 1114 | **867** | 3590 |
50
+ | `zoom(..., order=1)` | 6527 | 596 | **575** | 3757 |
51
+ | `interp1d` | 780 | 149 | **146** | 420 |
52
+ | `radon` | 59711 | 5982 | **4837** | - |
53
+ | `inverse_radon` | 52928 | 8254 | **6535** | - |
54
+ | `binary_dilation` | 2207 | 310 | **298** | - |
55
+ | `binary_erosion` | 2296 | 326 | **304** | - |
56
+ | `binary_closing` | 4158 | 544 | **469** | - |
57
+ | `binary_opening` | 4410 | 567 | **522** | - |
58
+ | `center_of_mass` | 2237 | **64** | **64** | - |
59
+
60
+ We use [`airspeed velocity`](https://asv.readthedocs.io/en/stable/) to benchmark our code. For detailed results visit [benchmark page](https://neuro-ml.github.io/imops/benchmarks/).
61
+
42
62
  # Features
43
63
 
44
- ## Fast Radon transform
64
+ ### Fast Radon transform
45
65
 
46
66
  ```python
47
67
  from imops import radon, inverse_radon
48
68
  ```
49
69
 
50
- ## Fast linear/bilinear/trilinear zoom
70
+ ### Fast 0/1-order zoom
51
71
 
52
72
  ```python
53
73
  from imops import zoom, zoom_to_shape
@@ -58,20 +78,20 @@ y = zoom(x, 2, axis=[0, 1])
58
78
  # without the need to compute the scale factor
59
79
  z = zoom_to_shape(x, (4, 120, 67))
60
80
  ```
61
- Works faster only for `ndim<=3, dtype=float32 or float64, output=None, order=1, mode='constant', grid_mode=False`
62
- ## Fast 1d linear interpolation
81
+ Works faster only for `ndim<=4, dtype=float32 or float64 (and bool-int16-32-64 if order == 0), output=None, order=0 or 1, mode='constant', grid_mode=False`
82
+ ### Fast 1d linear interpolation
63
83
 
64
84
  ```python
65
85
  from imops import interp1d # same as `scipy.interpolate.interp1d`
66
86
  ```
67
- Works faster only for `ndim<=3, dtype=float32 or float64, order=1 or 'linear'`
68
- ## Fast binary morphology
87
+ Works faster only for `ndim<=3, dtype=float32 or float64, order=1`
88
+ ### Fast binary morphology
69
89
 
70
90
  ```python
71
91
  from imops import binary_dilation, binary_erosion, binary_opening, binary_closing
72
92
  ```
73
93
  These functions mimic `scikit-image` counterparts
74
- ## Padding
94
+ ### Padding
75
95
 
76
96
  ```python
77
97
  from imops import pad, pad_to_shape
@@ -84,7 +104,7 @@ y = pad(x, 10, axis=[0, 1])
84
104
  z = pad_to_shape(x, (4, 120, 67), ratio=0.25)
85
105
  ```
86
106
 
87
- ## Cropping
107
+ ### Cropping
88
108
 
89
109
  ```python
90
110
  from imops import crop_to_shape
@@ -96,7 +116,7 @@ from imops import crop_to_shape
96
116
  z = crop_to_shape(x, (4, 120, 67), ratio=0.25)
97
117
  ```
98
118
 
99
- ## Labeling
119
+ ### Labeling
100
120
 
101
121
  ```python
102
122
  from imops import label
@@ -106,7 +126,7 @@ labeled, num_components = label(x, background=1, return_num=True)
106
126
  ```
107
127
 
108
128
  # Backends
109
- For `zoom`, `zoom_to_shape`, `interp1d`, `radon`, `inverse_radon` you can specify which backend to use. Backend can be specified by a string or by an instance of `Backend` class. The latter allows you to customize some backend options:
129
+ For all heavy image routines except `label` you can specify which backend to use. Backend can be specified by a string or by an instance of `Backend` class. The latter allows you to customize some backend options:
110
130
  ```python
111
131
  from imops import Cython, Numba, Scipy, zoom
112
132
 
@@ -127,10 +147,9 @@ with imops_backend('Cython'): # sets Cython backend via context manager
127
147
  ```
128
148
  Note that for `Numba` backend setting `num_threads` argument has no effect for now and you should use `NUMBA_NUM_THREADS` environment variable.
129
149
  Available backends:
130
- | | Scipy | Cython | Numba |
131
- |-------------------|---------|---------|---------|
150
+ | function / backend | Scipy | Cython | Numba |
151
+ |:-------------------:|:---------:|:---------:|:---------:|
132
152
  | `zoom` | &check; | &check; | &check; |
133
- | `zoom_to_shape` | &check; | &check; | &check; |
134
153
  | `interp1d` | &check; | &check; | &check; |
135
154
  | `radon` | &cross; | &check; | &cross; |
136
155
  | `inverse_radon` | &cross; | &check; | &cross; |
@@ -138,6 +157,7 @@ Available backends:
138
157
  | `binary_erosion` | &check; | &check; | &cross; |
139
158
  | `binary_closing` | &check; | &check; | &cross; |
140
159
  | `binary_opening` | &check; | &check; | &cross; |
160
+ | `center_of_mass` | &check; | &check; | &cross; |
141
161
 
142
162
  # Acknowledgements
143
163
 
@@ -5,7 +5,9 @@
5
5
 
6
6
  # Imops
7
7
 
8
- Efficient parallelizable algorithms for multidimensional arrays to speed up your data pipelines. Docs are [here](https://neuro-ml.github.io/imops/).
8
+ Efficient parallelizable algorithms for multidimensional arrays to speed up your data pipelines.
9
+ - [Documentation](https://neuro-ml.github.io/imops/)
10
+ - [Benchmarks](https://neuro-ml.github.io/imops/benchmarks/)
9
11
 
10
12
  # Install
11
13
 
@@ -14,15 +16,33 @@ pip install imops # default install with Cython backend
14
16
  pip install imops[numba] # additionally install Numba backend
15
17
  ```
16
18
 
19
+ # How fast is it?
20
+
21
+ Time comparisons (ms) for Intel(R) Xeon(R) Silver 4114 CPU @ 2.20GHz using 8 threads. All inputs are C-contiguous NumPy arrays. For morphology functions `bool` dtype is used and `float64` for all others.
22
+ | function / backend | Scipy() | Cython(fast=False) | Cython(fast=True) | Numba() |
23
+ |:----------------------:|:-----------:|:----------------------:|:---------------------:|:-----------:|
24
+ | `zoom(..., order=0)` | 2072 | 1114 | **867** | 3590 |
25
+ | `zoom(..., order=1)` | 6527 | 596 | **575** | 3757 |
26
+ | `interp1d` | 780 | 149 | **146** | 420 |
27
+ | `radon` | 59711 | 5982 | **4837** | - |
28
+ | `inverse_radon` | 52928 | 8254 | **6535** | - |
29
+ | `binary_dilation` | 2207 | 310 | **298** | - |
30
+ | `binary_erosion` | 2296 | 326 | **304** | - |
31
+ | `binary_closing` | 4158 | 544 | **469** | - |
32
+ | `binary_opening` | 4410 | 567 | **522** | - |
33
+ | `center_of_mass` | 2237 | **64** | **64** | - |
34
+
35
+ We use [`airspeed velocity`](https://asv.readthedocs.io/en/stable/) to benchmark our code. For detailed results visit [benchmark page](https://neuro-ml.github.io/imops/benchmarks/).
36
+
17
37
  # Features
18
38
 
19
- ## Fast Radon transform
39
+ ### Fast Radon transform
20
40
 
21
41
  ```python
22
42
  from imops import radon, inverse_radon
23
43
  ```
24
44
 
25
- ## Fast linear/bilinear/trilinear zoom
45
+ ### Fast 0/1-order zoom
26
46
 
27
47
  ```python
28
48
  from imops import zoom, zoom_to_shape
@@ -33,20 +53,20 @@ y = zoom(x, 2, axis=[0, 1])
33
53
  # without the need to compute the scale factor
34
54
  z = zoom_to_shape(x, (4, 120, 67))
35
55
  ```
36
- Works faster only for `ndim<=3, dtype=float32 or float64, output=None, order=1, mode='constant', grid_mode=False`
37
- ## Fast 1d linear interpolation
56
+ Works faster only for `ndim<=4, dtype=float32 or float64 (and bool-int16-32-64 if order == 0), output=None, order=0 or 1, mode='constant', grid_mode=False`
57
+ ### Fast 1d linear interpolation
38
58
 
39
59
  ```python
40
60
  from imops import interp1d # same as `scipy.interpolate.interp1d`
41
61
  ```
42
- Works faster only for `ndim<=3, dtype=float32 or float64, order=1 or 'linear'`
43
- ## Fast binary morphology
62
+ Works faster only for `ndim<=3, dtype=float32 or float64, order=1`
63
+ ### Fast binary morphology
44
64
 
45
65
  ```python
46
66
  from imops import binary_dilation, binary_erosion, binary_opening, binary_closing
47
67
  ```
48
68
  These functions mimic `scikit-image` counterparts
49
- ## Padding
69
+ ### Padding
50
70
 
51
71
  ```python
52
72
  from imops import pad, pad_to_shape
@@ -59,7 +79,7 @@ y = pad(x, 10, axis=[0, 1])
59
79
  z = pad_to_shape(x, (4, 120, 67), ratio=0.25)
60
80
  ```
61
81
 
62
- ## Cropping
82
+ ### Cropping
63
83
 
64
84
  ```python
65
85
  from imops import crop_to_shape
@@ -71,7 +91,7 @@ from imops import crop_to_shape
71
91
  z = crop_to_shape(x, (4, 120, 67), ratio=0.25)
72
92
  ```
73
93
 
74
- ## Labeling
94
+ ### Labeling
75
95
 
76
96
  ```python
77
97
  from imops import label
@@ -81,7 +101,7 @@ labeled, num_components = label(x, background=1, return_num=True)
81
101
  ```
82
102
 
83
103
  # Backends
84
- For `zoom`, `zoom_to_shape`, `interp1d`, `radon`, `inverse_radon` you can specify which backend to use. Backend can be specified by a string or by an instance of `Backend` class. The latter allows you to customize some backend options:
104
+ For all heavy image routines except `label` you can specify which backend to use. Backend can be specified by a string or by an instance of `Backend` class. The latter allows you to customize some backend options:
85
105
  ```python
86
106
  from imops import Cython, Numba, Scipy, zoom
87
107
 
@@ -102,10 +122,9 @@ with imops_backend('Cython'): # sets Cython backend via context manager
102
122
  ```
103
123
  Note that for `Numba` backend setting `num_threads` argument has no effect for now and you should use `NUMBA_NUM_THREADS` environment variable.
104
124
  Available backends:
105
- | | Scipy | Cython | Numba |
106
- |-------------------|---------|---------|---------|
125
+ | function / backend | Scipy | Cython | Numba |
126
+ |:-------------------:|:---------:|:---------:|:---------:|
107
127
  | `zoom` | &check; | &check; | &check; |
108
- | `zoom_to_shape` | &check; | &check; | &check; |
109
128
  | `interp1d` | &check; | &check; | &check; |
110
129
  | `radon` | &cross; | &check; | &cross; |
111
130
  | `inverse_radon` | &cross; | &check; | &cross; |
@@ -113,6 +132,7 @@ Available backends:
113
132
  | `binary_erosion` | &check; | &check; | &cross; |
114
133
  | `binary_closing` | &check; | &check; | &cross; |
115
134
  | `binary_opening` | &check; | &check; | &cross; |
135
+ | `center_of_mass` | &check; | &check; | &cross; |
116
136
 
117
137
  # Acknowledgements
118
138
 
@@ -0,0 +1,87 @@
1
+ import platform
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ from setuptools import Extension
6
+ from setuptools.command.build_py import build_py
7
+
8
+
9
+ class NumpyImport(dict):
10
+ """Hacky way to return Numpy's `include` path with lazy import."""
11
+
12
+ # Must be json-serializable due to
13
+ # https://github.com/cython/cython/blob/6ad6ca0e9e7d030354b7fe7d7b56c3f6e6a4bc23/Cython/Compiler/ModuleNode.py#L773
14
+ def __init__(self):
15
+ return super().__init__(self, description=self.__doc__)
16
+
17
+ # Must be hashable due to
18
+ # https://github.com/cython/cython/blob/6ad6ca0e9e7d030354b7fe7d7b56c3f6e6a4bc23/Cython/Compiler/Main.py#L307
19
+ def __hash__(self):
20
+ return id(self)
21
+
22
+ def __repr__(self):
23
+ import numpy as np
24
+
25
+ return np.get_include()
26
+
27
+ __fspath__ = __repr__
28
+
29
+
30
+ class NumpyLibImport(str):
31
+ """Hacky way to return Numpy's `lib` path with lazy import."""
32
+
33
+ # Exploit of https://github.com/pypa/setuptools/blob/1ef36f2d336e239bd8f83507cb9447e060b6ed60/setuptools/_distutils/
34
+ # unixccompiler.py#L276-L277
35
+ def __radd__(self, left):
36
+ import numpy as np
37
+
38
+ return left + str(Path(np.get_include()).parent / 'lib')
39
+
40
+ def __hash__(self):
41
+ return id(self)
42
+
43
+
44
+ class PyprojectBuild(build_py):
45
+ def run(self):
46
+ self.run_command('build_ext')
47
+ return super().run()
48
+
49
+ def initialize_options(self):
50
+ super().initialize_options()
51
+
52
+ self.distribution.ext_modules = get_ext_modules()
53
+
54
+
55
+ def get_ext_modules():
56
+ name = 'imops'
57
+ on_windows = platform.system() == 'Windows'
58
+ args = ['/openmp' if on_windows else '-fopenmp']
59
+
60
+ # Cython extension and .pyx source file names must be the same to compile
61
+ # https://stackoverflow.com/questions/8024805/cython-compiled-c-extension-importerror-dynamic-module-does-not-define-init-fu
62
+ modules = ['backprojection', 'measure', 'morphology', 'numeric', 'radon', 'zoom']
63
+ modules_to_link_against_numpy_core_math_lib = ['numeric']
64
+
65
+ for module in modules:
66
+ src_dir = Path(__file__).parent / name / 'src'
67
+ shutil.copyfile(src_dir / f'_{module}.pyx', src_dir / f'_fast_{module}.pyx')
68
+
69
+ return [
70
+ Extension(
71
+ f'{name}.src._{prefix}{module}',
72
+ [f'{name}/src/_{prefix}{module}.pyx'],
73
+ include_dirs=[NumpyImport()],
74
+ library_dirs=[NumpyLibImport()] if module in modules_to_link_against_numpy_core_math_lib else [],
75
+ libraries=['npymath'] + ['m'] * (not on_windows)
76
+ if module in modules_to_link_against_numpy_core_math_lib
77
+ else [],
78
+ extra_compile_args=args + additional_args,
79
+ extra_link_args=args + additional_args,
80
+ define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')],
81
+ )
82
+ for module in modules
83
+ # FIXME: import of `ffast-math` compiled modules changes global FPU state, so now `fast=True` will just
84
+ # fallback to standard `-O2` compiled versions until https://github.com/neuro-ml/imops/issues/37 is resolved
85
+ # for prefix, additional_args in zip(['', 'fast_'], [[], ['-ffast-math']])
86
+ for prefix, additional_args in zip(['', 'fast_'], [[], []])
87
+ ]
@@ -4,6 +4,7 @@ from .crop import crop_to_box, crop_to_shape
4
4
  from .interp1d import interp1d
5
5
  from .measure import label
6
6
  from .morphology import binary_closing, binary_dilation, binary_erosion, binary_opening
7
+ from .numeric import copy, fill_, full, pointwise_add
7
8
  from .pad import pad, pad_to_divisible, pad_to_shape, restore_crop
8
9
  from .radon import inverse_radon, radon
9
10
  from .zoom import _zoom, zoom, zoom_to_shape
@@ -0,0 +1 @@
1
+ __version__ = '0.8.3'
@@ -1,14 +1,15 @@
1
- import contextlib
1
+ from contextlib import contextmanager
2
2
  from dataclasses import dataclass
3
3
  from typing import Dict, Type, Union
4
+ from warnings import warn
4
5
 
5
6
 
6
7
  class Backend:
7
8
  def __init_subclass__(cls, **kwargs):
8
9
  name = cls.__name__
9
- if name in AVAILABLE_BACKENDS:
10
+ if name in _AVAILABLE_BACKENDS:
10
11
  raise ValueError(f'The name "{name}" is already in use.')
11
- AVAILABLE_BACKENDS[name] = cls
12
+ _AVAILABLE_BACKENDS[name] = cls
12
13
  if not hasattr(Backend, name):
13
14
  setattr(Backend, name, cls)
14
15
 
@@ -22,18 +23,18 @@ class Backend:
22
23
 
23
24
 
24
25
  BackendLike = Union[str, Backend, Type[Backend], None]
25
- AVAILABLE_BACKENDS: Dict[str, Type[Backend]] = {}
26
+ _AVAILABLE_BACKENDS: Dict[str, Type[Backend]] = {}
26
27
 
27
28
 
28
- def resolve_backend(value: BackendLike) -> Backend:
29
+ def resolve_backend(value: BackendLike, warn_stacklevel: int = 1) -> Backend:
29
30
  if value is None:
30
31
  return DEFAULT_BACKEND
31
32
 
32
33
  if isinstance(value, str):
33
- if value not in AVAILABLE_BACKENDS:
34
- raise ValueError(f'"{value}" is not in the list of available backends: {tuple(AVAILABLE_BACKENDS)}.')
34
+ if value not in _AVAILABLE_BACKENDS:
35
+ raise ValueError(f'"{value}" is not in the list of available backends: {tuple(_AVAILABLE_BACKENDS)}.')
35
36
 
36
- return AVAILABLE_BACKENDS[value]()
37
+ return _AVAILABLE_BACKENDS[value]()
37
38
 
38
39
  if isinstance(value, type):
39
40
  value = value()
@@ -41,6 +42,9 @@ def resolve_backend(value: BackendLike) -> Backend:
41
42
  if not isinstance(value, Backend):
42
43
  raise ValueError(f'Expected a `Backend` instance, got {value}.')
43
44
 
45
+ if isinstance(value, Cython) and value.fast:
46
+ warn('`fast=True` has no effect for `Cython` backend for now.', stacklevel=warn_stacklevel)
47
+
44
48
  return value
45
49
 
46
50
 
@@ -51,7 +55,7 @@ def set_backend(backend: BackendLike) -> Backend:
51
55
  return current
52
56
 
53
57
 
54
- @contextlib.contextmanager
58
+ @contextmanager
55
59
  def imops_backend(backend: BackendLike):
56
60
  previous = set_backend(backend)
57
61
  try:
@@ -87,5 +91,5 @@ class Scipy(Backend):
87
91
 
88
92
  DEFAULT_BACKEND = Cython()
89
93
 
90
- BACKEND2NUM_THREADS_VAR_NAME = {Cython.__name__: 'OMP_NUM_THREADS', Numba.__name__: 'NUMBA_NUM_THREADS'}
94
+ BACKEND_NAME2ENV_NUM_THREADS_VAR_NAME = {Cython.__name__: 'OMP_NUM_THREADS', Numba.__name__: 'NUMBA_NUM_THREADS'}
91
95
  SINGLE_THREADED_BACKENDS = (Scipy.__name__,)
@@ -1,7 +1,7 @@
1
1
  import itertools
2
2
  from copy import copy
3
3
  from functools import wraps
4
- from typing import Callable, Sequence, Tuple
4
+ from typing import Callable, Tuple
5
5
 
6
6
  import numpy as np
7
7
 
@@ -10,34 +10,6 @@ import numpy as np
10
10
  Box = np.ndarray
11
11
 
12
12
 
13
- def check_len(*args) -> None:
14
- lengths = list(map(len, args))
15
- if any(length != lengths[0] for length in lengths):
16
- raise ValueError(f'Arguments of equal length are required: {", ".join(map(str, lengths))}')
17
-
18
-
19
- def build_slices(start: Sequence[int], stop: Sequence[int] = None, step: Sequence[int] = None) -> Tuple[slice, ...]:
20
- """
21
- Returns a tuple of slices built from `start` and `stop` with `step`.
22
-
23
- Examples
24
- --------
25
- >>> build_slices([1, 2, 3], [4, 5, 6])
26
- (slice(1, 4), slice(2, 5), slice(3, 6))
27
- >>> build_slices([10, 11])
28
- (slice(10), slice(11))
29
- """
30
-
31
- check_len(*filter(lambda x: x is not None, [start, stop, step]))
32
- args = [
33
- start,
34
- stop if stop is not None else [None for _ in start],
35
- step if step is not None else [None for _ in start],
36
- ]
37
-
38
- return tuple(map(slice, *args))
39
-
40
-
41
13
  def make_box(iterable) -> Box:
42
14
  """Returns a box, generated from copy of the `iterable`."""
43
15
  box = np.asarray(copy(iterable))
@@ -78,3 +50,22 @@ def mask_to_box(mask: np.ndarray) -> Box:
78
50
  stop.insert(0, right + 1)
79
51
 
80
52
  return start, stop
53
+
54
+
55
+ @returns_box
56
+ def shape_to_box(shape: Tuple) -> Box:
57
+ return make_box([(0,) * len(shape), shape]) # fmt: skip
58
+
59
+
60
+ def box_to_shape(box: Box) -> Tuple:
61
+ return tuple(box[1] - box[0])
62
+
63
+
64
+ @returns_box
65
+ def add_margin(box: Box, margin) -> Box:
66
+ """
67
+ Returns a box with size increased by the ``margin`` (need to be broadcastable to the box)
68
+ compared to the input ``box``.
69
+ """
70
+ margin = np.broadcast_to(margin, box.shape)
71
+ return box[0] - margin[0], box[1] + margin[1]
@@ -1,5 +1,7 @@
1
1
  import numpy as np
2
2
 
3
+ from .backend import BackendLike
4
+ from .numeric import _NUMERIC_DEFAULT_NUM_THREADS
3
5
  from .pad import pad
4
6
  from .utils import AxesLike, AxesParams, broadcast_axis, fill_by_indices
5
7
 
@@ -43,10 +45,18 @@ def crop_to_shape(x: np.ndarray, shape: AxesLike, axis: AxesLike = None, ratio:
43
45
  ratio = fill_by_indices(np.zeros(ndim), ratio, axis)
44
46
  start = ((old_shape - new_shape) * ratio).astype(int)
45
47
 
48
+ # TODO: Create contiguous array?
46
49
  return x[tuple(map(slice, start, start + new_shape))]
47
50
 
48
51
 
49
- def crop_to_box(x: np.ndarray, box: np.ndarray, axis: AxesLike = None, padding_values: AxesParams = None) -> np.ndarray:
52
+ def crop_to_box(
53
+ x: np.ndarray,
54
+ box: np.ndarray,
55
+ axis: AxesLike = None,
56
+ padding_values: AxesParams = None,
57
+ num_threads: int = _NUMERIC_DEFAULT_NUM_THREADS,
58
+ backend: BackendLike = None,
59
+ ) -> np.ndarray:
50
60
  """
51
61
  Crop `x` according to `box` along `axis`.
52
62
 
@@ -60,6 +70,11 @@ def crop_to_box(x: np.ndarray, box: np.ndarray, axis: AxesLike = None, padding_v
60
70
  axis along which `x` will be cropped
61
71
  padding_values: AxesParams
62
72
  values to pad with if box exceeds the input's limits
73
+ num_threads: int
74
+ the number of threads to use for computation. Default = 4. If negative value passed
75
+ cpu count + num_threads + 1 threads will be used
76
+ backend: BackendLike
77
+ which backend to use. `cython` and `scipy` are available, `cython` is used by default
63
78
 
64
79
  Returns
65
80
  -------
@@ -86,9 +101,10 @@ def crop_to_box(x: np.ndarray, box: np.ndarray, axis: AxesLike = None, padding_v
86
101
 
87
102
  slice_start = fill_by_indices(np.zeros(x.ndim, int), slice_start, axis)
88
103
  slice_stop = fill_by_indices(x.shape, slice_stop, axis)
104
+ # TODO: Create contiguous array?
89
105
  x = x[tuple(map(slice, slice_start, slice_stop))]
90
106
 
91
107
  if padding_values is not None and padding.any():
92
- x = pad(x, padding, axis, padding_values)
108
+ x = pad(x, padding, axis, padding_values, num_threads=num_threads, backend=backend)
93
109
 
94
110
  return x
@@ -5,6 +5,7 @@ import numpy as np
5
5
  from scipy.interpolate import interp1d as scipy_interp1d
6
6
 
7
7
  from .backend import BackendLike, resolve_backend
8
+ from .numeric import copy as _copy
8
9
  from .src._fast_zoom import _interp1d as cython_fast_interp1d
9
10
  from .src._zoom import _interp1d as cython_interp1d
10
11
  from .utils import normalize_num_threads
@@ -74,7 +75,7 @@ class interp1d:
74
75
  num_threads: int = -1,
75
76
  backend: BackendLike = None,
76
77
  ) -> None:
77
- backend = resolve_backend(backend)
78
+ backend = resolve_backend(backend, warn_stacklevel=3)
78
79
  if backend.name not in ('Scipy', 'Numba', 'Cython'):
79
80
  raise ValueError(f'Unsupported backend "{backend.name}".')
80
81
 
@@ -88,13 +89,19 @@ class interp1d:
88
89
  warn(
89
90
  "Fast interpolation is only supported for ndim<=3, dtype=float32 or float64, order=1 or 'linear'. "
90
91
  "Falling back to scipy's implementation.",
92
+ stacklevel=2,
91
93
  )
92
94
  self.scipy_interp1d = scipy_interp1d(x, y, kind, axis, copy, bounds_error, fill_value, assume_sorted)
93
95
  else:
96
+ if len(x) != y.shape[axis]:
97
+ raise ValueError(
98
+ f'x and y arrays must be equal in length along interpolation axis: {len(x)} vs {y.shape[axis]}.'
99
+ )
100
+
94
101
  if bounds_error and fill_value == 'extrapolate':
95
102
  raise ValueError('Cannot extrapolate and raise at the same time.')
96
103
 
97
- if fill_value == 'extrapolate' and len(x) < 2 or len(y) < 2:
104
+ if fill_value == 'extrapolate' and len(x) < 2 or y.shape[axis] < 2:
98
105
  raise ValueError('x and y arrays must have at least 2 entries.')
99
106
 
100
107
  if fill_value == 'extrapolate':
@@ -102,11 +109,6 @@ class interp1d:
102
109
  else:
103
110
  self.bounds_error = True if bounds_error is None else bounds_error
104
111
 
105
- if len(x) != y.shape[axis]:
106
- raise ValueError(
107
- f'x and y arrays must be equal in length along interpolation axis: {len(x)} vs {y.shape[axis]}.'
108
- )
109
-
110
112
  self.axis = axis
111
113
 
112
114
  if axis not in (-1, y.ndim - 1):
@@ -114,12 +116,13 @@ class interp1d:
114
116
 
115
117
  self.fill_value = fill_value
116
118
  self.scipy_interp1d = None
117
- self.x = np.copy(x) if copy else x
119
+ # FIXME: how to accurately pass `num_threads` and `backend` arguments to `copy`?
120
+ self.x = _copy(x, order='C') if copy else x
118
121
  self.n_dummy = 3 - y.ndim
119
122
  self.y = y[(None,) * self.n_dummy] if self.n_dummy else y
120
123
 
121
124
  if copy:
122
- self.y = np.copy(self.y)
125
+ self.y = _copy(self.y, order='C')
123
126
 
124
127
  self.assume_sorted = assume_sorted
125
128
 
@@ -149,7 +152,7 @@ class interp1d:
149
152
  interpolated values. Shape is determined by replacing the interpolation axis in the original array with
150
153
  the shape of x
151
154
  """
152
- num_threads = normalize_num_threads(self.num_threads, self.backend)
155
+ num_threads = normalize_num_threads(self.num_threads, self.backend, warn_stacklevel=3)
153
156
 
154
157
  if self.scipy_interp1d is not None:
155
158
  return self.scipy_interp1d(x_new)
@@ -165,8 +168,8 @@ class interp1d:
165
168
  # TODO: Figure out how to properly handle multiple type signatures in Cython and remove `.astype`-s
166
169
  out = self.src_interp1d(
167
170
  self.y,
168
- self.x.astype(np.float64),
169
- x_new.astype(np.float64),
171
+ self.x.astype(np.float64, copy=False),
172
+ x_new.astype(np.float64, copy=False),
170
173
  self.bounds_error,
171
174
  0.0 if extrapolate else self.fill_value,
172
175
  extrapolate,
@@ -177,7 +180,7 @@ class interp1d:
177
180
  if self.backend.name == 'Numba':
178
181
  set_num_threads(old_num_threads)
179
182
 
180
- out = out.astype(max(self.y.dtype, self.x.dtype, x_new.dtype, key=lambda x: x.type(0).itemsize))
183
+ out = out.astype(max(self.y.dtype, self.x.dtype, x_new.dtype, key=lambda x: x.type(0).itemsize), copy=False)
181
184
 
182
185
  if self.n_dummy:
183
186
  out = out[(0,) * self.n_dummy]