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.
- {imops-0.8.1 → imops-0.8.3}/MANIFEST.in +1 -1
- {imops-0.8.1/imops.egg-info → imops-0.8.3}/PKG-INFO +36 -16
- {imops-0.8.1 → imops-0.8.3}/README.md +34 -14
- imops-0.8.3/_build_utils.py +87 -0
- {imops-0.8.1 → imops-0.8.3}/imops/__init__.py +1 -0
- imops-0.8.3/imops/__version__.py +1 -0
- {imops-0.8.1 → imops-0.8.3}/imops/backend.py +14 -10
- {imops-0.8.1 → imops-0.8.3}/imops/box.py +20 -29
- {imops-0.8.1 → imops-0.8.3}/imops/crop.py +18 -2
- {imops-0.8.1 → imops-0.8.3}/imops/interp1d.py +16 -13
- {imops-0.8.1 → imops-0.8.3}/imops/measure.py +12 -9
- imops-0.8.3/imops/morphology.py +347 -0
- imops-0.8.3/imops/numeric.py +376 -0
- {imops-0.8.1 → imops-0.8.3}/imops/pad.py +41 -5
- {imops-0.8.1 → imops-0.8.3}/imops/radon.py +9 -7
- imops-0.8.3/imops/src/_fast_morphology.pyx +329 -0
- imops-0.8.3/imops/src/_fast_numeric.pyx +242 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_zoom.pyx +1 -0
- imops-0.8.3/imops/src/_morphology.pyx +329 -0
- imops-0.8.3/imops/src/_numeric.pyx +242 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_zoom.pyx +1 -0
- imops-0.8.3/imops/utils.py +206 -0
- {imops-0.8.1 → imops-0.8.3}/imops/zoom.py +9 -9
- {imops-0.8.1 → imops-0.8.3/imops.egg-info}/PKG-INFO +36 -16
- {imops-0.8.1 → imops-0.8.3}/imops.egg-info/SOURCES.txt +2 -2
- {imops-0.8.1 → imops-0.8.3}/imops.egg-info/requires.txt +1 -0
- {imops-0.8.1 → imops-0.8.3}/pyproject.toml +2 -2
- {imops-0.8.1 → imops-0.8.3}/requirements.txt +1 -0
- {imops-0.8.1 → imops-0.8.3}/setup.py +4 -33
- imops-0.8.1/_pyproject_build.py +0 -49
- imops-0.8.1/imops/__version__.py +0 -1
- imops-0.8.1/imops/_numeric.py +0 -124
- imops-0.8.1/imops/morphology.py +0 -227
- imops-0.8.1/imops/src/_fast_morphology.pyx +0 -145
- imops-0.8.1/imops/src/_fast_numeric.pyx +0 -64
- imops-0.8.1/imops/src/_morphology.pyx +0 -145
- imops-0.8.1/imops/src/_numeric.pyx +0 -64
- imops-0.8.1/imops/utils.py +0 -106
- {imops-0.8.1 → imops-0.8.3}/LICENSE +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/_configs.py +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/__init__.py +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_backprojection.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_backprojection.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_measure.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_fast_radon.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_measure.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_numba_zoom.py +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/src/_radon.pyx +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops/testing.py +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops.egg-info/dependency_links.txt +0 -0
- {imops-0.8.1 → imops-0.8.3}/imops.egg-info/top_level.txt +0 -0
- {imops-0.8.1 → imops-0.8.3}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: imops
|
|
3
|
-
Version: 0.8.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
64
|
+
### Fast Radon transform
|
|
45
65
|
|
|
46
66
|
```python
|
|
47
67
|
from imops import radon, inverse_radon
|
|
48
68
|
```
|
|
49
69
|
|
|
50
|
-
|
|
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<=
|
|
62
|
-
|
|
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
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
|
131
|
-
|
|
150
|
+
| function / backend | Scipy | Cython | Numba |
|
|
151
|
+
|:-------------------:|:---------:|:---------:|:---------:|
|
|
132
152
|
| `zoom` | ✓ | ✓ | ✓ |
|
|
133
|
-
| `zoom_to_shape` | ✓ | ✓ | ✓ |
|
|
134
153
|
| `interp1d` | ✓ | ✓ | ✓ |
|
|
135
154
|
| `radon` | ✗ | ✓ | ✗ |
|
|
136
155
|
| `inverse_radon` | ✗ | ✓ | ✗ |
|
|
@@ -138,6 +157,7 @@ Available backends:
|
|
|
138
157
|
| `binary_erosion` | ✓ | ✓ | ✗ |
|
|
139
158
|
| `binary_closing` | ✓ | ✓ | ✗ |
|
|
140
159
|
| `binary_opening` | ✓ | ✓ | ✗ |
|
|
160
|
+
| `center_of_mass` | ✓ | ✓ | ✗ |
|
|
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.
|
|
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
|
-
|
|
39
|
+
### Fast Radon transform
|
|
20
40
|
|
|
21
41
|
```python
|
|
22
42
|
from imops import radon, inverse_radon
|
|
23
43
|
```
|
|
24
44
|
|
|
25
|
-
|
|
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<=
|
|
37
|
-
|
|
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
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
|
106
|
-
|
|
125
|
+
| function / backend | Scipy | Cython | Numba |
|
|
126
|
+
|:-------------------:|:---------:|:---------:|:---------:|
|
|
107
127
|
| `zoom` | ✓ | ✓ | ✓ |
|
|
108
|
-
| `zoom_to_shape` | ✓ | ✓ | ✓ |
|
|
109
128
|
| `interp1d` | ✓ | ✓ | ✓ |
|
|
110
129
|
| `radon` | ✗ | ✓ | ✗ |
|
|
111
130
|
| `inverse_radon` | ✗ | ✓ | ✗ |
|
|
@@ -113,6 +132,7 @@ Available backends:
|
|
|
113
132
|
| `binary_erosion` | ✓ | ✓ | ✗ |
|
|
114
133
|
| `binary_closing` | ✓ | ✓ | ✗ |
|
|
115
134
|
| `binary_opening` | ✓ | ✓ | ✗ |
|
|
135
|
+
| `center_of_mass` | ✓ | ✓ | ✗ |
|
|
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
|
|
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
|
|
10
|
+
if name in _AVAILABLE_BACKENDS:
|
|
10
11
|
raise ValueError(f'The name "{name}" is already in use.')
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
34
|
-
raise ValueError(f'"{value}" is not in the list of available backends: {tuple(
|
|
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
|
|
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
|
-
@
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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]
|