imops 0.8.8__cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl
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.
- _build_utils.py +113 -0
- imops/__init__.py +10 -0
- imops/__version__.py +1 -0
- imops/_configs.py +29 -0
- imops/backend.py +95 -0
- imops/box.py +74 -0
- imops/cpp/cpp_modules.cpython-38-i386-linux-gnu.so +0 -0
- imops/cpp/interp2d/delaunator/delaunator-header-only.hpp +33 -0
- imops/cpp/interp2d/delaunator/delaunator.cpp +645 -0
- imops/cpp/interp2d/delaunator/delaunator.hpp +170 -0
- imops/cpp/interp2d/interpolator.h +52 -0
- imops/cpp/interp2d/triangulator.h +198 -0
- imops/cpp/interp2d/utils.h +63 -0
- imops/cpp/main.cpp +13 -0
- imops/crop.py +120 -0
- imops/interp1d.py +207 -0
- imops/interp2d.py +120 -0
- imops/measure.py +228 -0
- imops/morphology.py +525 -0
- imops/numeric.py +384 -0
- imops/pad.py +253 -0
- imops/py.typed +0 -0
- imops/radon.py +247 -0
- imops/src/__init__.py +0 -0
- imops/src/_backprojection.c +27339 -0
- imops/src/_backprojection.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_backprojection.c +27339 -0
- imops/src/_fast_backprojection.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_measure.c +33810 -0
- imops/src/_fast_measure.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_morphology.c +26089 -0
- imops/src/_fast_morphology.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_numeric.c +48651 -0
- imops/src/_fast_numeric.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_radon.c +30714 -0
- imops/src/_fast_radon.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_zoom.c +57203 -0
- imops/src/_fast_zoom.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_measure.c +33810 -0
- imops/src/_measure.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_morphology.c +26089 -0
- imops/src/_morphology.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_numba_zoom.py +503 -0
- imops/src/_numeric.c +48651 -0
- imops/src/_numeric.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_radon.c +30714 -0
- imops/src/_radon.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_zoom.c +57203 -0
- imops/src/_zoom.cpython-38-i386-linux-gnu.so +0 -0
- imops/testing.py +57 -0
- imops/utils.py +205 -0
- imops/zoom.py +297 -0
- imops-0.8.8.dist-info/LICENSE +21 -0
- imops-0.8.8.dist-info/METADATA +218 -0
- imops-0.8.8.dist-info/RECORD +58 -0
- imops-0.8.8.dist-info/WHEEL +6 -0
- imops-0.8.8.dist-info/top_level.txt +2 -0
- imops.libs/libgomp-65f46eca.so.1.0.0 +0 -0
_build_utils.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
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 LazyImport(dict):
|
|
10
|
+
"""Hacky way to return any module'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, module_name):
|
|
15
|
+
self.module_name = module_name
|
|
16
|
+
super().__init__(self, description=self.__doc__)
|
|
17
|
+
|
|
18
|
+
# Must be hashable due to
|
|
19
|
+
# https://github.com/cython/cython/blob/6ad6ca0e9e7d030354b7fe7d7b56c3f6e6a4bc23/Cython/Compiler/Main.py#L307
|
|
20
|
+
def __hash__(self):
|
|
21
|
+
return id(self)
|
|
22
|
+
|
|
23
|
+
def __repr__(self):
|
|
24
|
+
scope = {}
|
|
25
|
+
exec(f'import {self.module_name} as module', scope)
|
|
26
|
+
|
|
27
|
+
return scope['module'].get_include()
|
|
28
|
+
|
|
29
|
+
__fspath__ = __repr__
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NumpyLibImport(str):
|
|
33
|
+
"""Hacky way to return Numpy's `lib` path with lazy import."""
|
|
34
|
+
|
|
35
|
+
# Exploit of https://github.com/pypa/setuptools/blob/1ef36f2d336e239bd8f83507cb9447e060b6ed60/setuptools/_distutils/
|
|
36
|
+
# unixccompiler.py#L276-L277
|
|
37
|
+
def __radd__(self, left):
|
|
38
|
+
import numpy as np
|
|
39
|
+
|
|
40
|
+
return left + str(Path(np.get_include()).parent / 'lib')
|
|
41
|
+
|
|
42
|
+
def __hash__(self):
|
|
43
|
+
return id(self)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PyprojectBuild(build_py):
|
|
47
|
+
def run(self):
|
|
48
|
+
self.run_command('build_ext')
|
|
49
|
+
return super().run()
|
|
50
|
+
|
|
51
|
+
def initialize_options(self):
|
|
52
|
+
super().initialize_options()
|
|
53
|
+
|
|
54
|
+
self.distribution.ext_modules = get_ext_modules()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_ext_modules():
|
|
58
|
+
name = 'imops'
|
|
59
|
+
on_windows = platform.system() == 'Windows'
|
|
60
|
+
args = ['/openmp' if on_windows else '-fopenmp']
|
|
61
|
+
cpp_args = [
|
|
62
|
+
'/std:c++20' if on_windows else '-std=c++2a',
|
|
63
|
+
'/O3' if on_windows else '-O3',
|
|
64
|
+
] # FIXME: account for higher gcc versions
|
|
65
|
+
|
|
66
|
+
modules = ['backprojection', 'measure', 'morphology', 'numeric', 'radon', 'zoom']
|
|
67
|
+
modules_to_link_against_numpy_core_math_lib = ['numeric']
|
|
68
|
+
|
|
69
|
+
src_dir = Path(__file__).parent / name / 'src'
|
|
70
|
+
for module in modules:
|
|
71
|
+
# Cython extension and .pyx source file names must be the same to compile
|
|
72
|
+
# https://stackoverflow.com/questions/8024805/cython-compiled-c-extension-importerror-dynamic-module-does-not-define-init-fu
|
|
73
|
+
shutil.copyfile(src_dir / f'_{module}.pyx', src_dir / f'_fast_{module}.pyx')
|
|
74
|
+
|
|
75
|
+
extensions = [
|
|
76
|
+
Extension(
|
|
77
|
+
f'{name}.cpp.cpp_modules',
|
|
78
|
+
[f'{name}/cpp/main.cpp'],
|
|
79
|
+
include_dirs=[LazyImport('pybind11')],
|
|
80
|
+
extra_compile_args=args + cpp_args,
|
|
81
|
+
extra_link_args=args + cpp_args,
|
|
82
|
+
language='c++',
|
|
83
|
+
)
|
|
84
|
+
]
|
|
85
|
+
for module in modules:
|
|
86
|
+
libraries = []
|
|
87
|
+
library_dirs = []
|
|
88
|
+
include_dirs = [LazyImport('numpy')]
|
|
89
|
+
|
|
90
|
+
if module in modules_to_link_against_numpy_core_math_lib:
|
|
91
|
+
library_dirs.append(NumpyLibImport())
|
|
92
|
+
libraries.append('npymath')
|
|
93
|
+
if not on_windows:
|
|
94
|
+
libraries.append('m')
|
|
95
|
+
|
|
96
|
+
# FIXME: import of `ffast-math` compiled modules changes global FPU state, so now `fast=True` will just
|
|
97
|
+
# fallback to standard `-O2` compiled versions until https://github.com/neuro-ml/imops/issues/37 is resolved
|
|
98
|
+
# for prefix, additional_args in zip(['', 'fast_'], [[], ['-ffast-math']])
|
|
99
|
+
for prefix, additional_args in zip(['', 'fast_'], [[], []]):
|
|
100
|
+
extensions.append(
|
|
101
|
+
Extension(
|
|
102
|
+
f'{name}.src._{prefix}{module}',
|
|
103
|
+
[f'{name}/src/_{prefix}{module}.pyx'],
|
|
104
|
+
include_dirs=include_dirs,
|
|
105
|
+
library_dirs=library_dirs,
|
|
106
|
+
libraries=libraries,
|
|
107
|
+
extra_compile_args=args + additional_args,
|
|
108
|
+
extra_link_args=args + additional_args,
|
|
109
|
+
define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')],
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return extensions
|
imops/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .__version__ import __version__
|
|
2
|
+
from .backend import Cython, Numba, Scipy, imops_backend, set_backend
|
|
3
|
+
from .crop import crop_to_box, crop_to_shape
|
|
4
|
+
from .interp1d import interp1d
|
|
5
|
+
from .measure import label
|
|
6
|
+
from .morphology import binary_closing, binary_dilation, binary_erosion, binary_opening
|
|
7
|
+
from .numeric import copy, fill_, full, pointwise_add
|
|
8
|
+
from .pad import pad, pad_to_divisible, pad_to_shape, restore_crop
|
|
9
|
+
from .radon import inverse_radon, radon
|
|
10
|
+
from .zoom import _zoom, zoom, zoom_to_shape
|
imops/__version__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.8.8'
|
imops/_configs.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
|
|
3
|
+
from .backend import Cython, Numba, Scipy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
scipy_configs = [Scipy()]
|
|
7
|
+
radon_configs = [Cython(fast) for fast in [False, True]]
|
|
8
|
+
numeric_configs = [
|
|
9
|
+
Scipy(),
|
|
10
|
+
*[Cython(fast) for fast in [False, True]],
|
|
11
|
+
]
|
|
12
|
+
measure_configs = [
|
|
13
|
+
Scipy(),
|
|
14
|
+
*[Cython(fast) for fast in [False, True]],
|
|
15
|
+
]
|
|
16
|
+
morphology_configs = [
|
|
17
|
+
Scipy(),
|
|
18
|
+
*[Cython(fast) for fast in [False, True]],
|
|
19
|
+
]
|
|
20
|
+
zoom_configs = [
|
|
21
|
+
Scipy(),
|
|
22
|
+
*[Cython(fast) for fast in [False, True]],
|
|
23
|
+
*[Numba(*flags) for flags in product([False, True], repeat=3)],
|
|
24
|
+
]
|
|
25
|
+
interp1d_configs = [
|
|
26
|
+
Scipy(),
|
|
27
|
+
*[Cython(fast) for fast in [False, True]],
|
|
28
|
+
*[Numba(*flags) for flags in product([False, True], repeat=3)],
|
|
29
|
+
]
|
imops/backend.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, Type, Union
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Backend:
|
|
8
|
+
def __init_subclass__(cls, **kwargs):
|
|
9
|
+
name = cls.__name__
|
|
10
|
+
if name in _AVAILABLE_BACKENDS:
|
|
11
|
+
raise ValueError(f'The name "{name}" is already in use.')
|
|
12
|
+
_AVAILABLE_BACKENDS[name] = cls
|
|
13
|
+
if not hasattr(Backend, name):
|
|
14
|
+
setattr(Backend, name, cls)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self):
|
|
18
|
+
return type(self).__name__
|
|
19
|
+
|
|
20
|
+
Cython: 'Cython'
|
|
21
|
+
Numba: 'Numba'
|
|
22
|
+
Scipy: 'Scipy'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
BackendLike = Union[str, Backend, Type[Backend], None]
|
|
26
|
+
_AVAILABLE_BACKENDS: Dict[str, Type[Backend]] = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve_backend(value: BackendLike, warn_stacklevel: int = 1) -> Backend:
|
|
30
|
+
if value is None:
|
|
31
|
+
return DEFAULT_BACKEND
|
|
32
|
+
|
|
33
|
+
if isinstance(value, str):
|
|
34
|
+
if value not in _AVAILABLE_BACKENDS:
|
|
35
|
+
raise ValueError(f'"{value}" is not in the list of available backends: {tuple(_AVAILABLE_BACKENDS)}.')
|
|
36
|
+
|
|
37
|
+
return _AVAILABLE_BACKENDS[value]()
|
|
38
|
+
|
|
39
|
+
if isinstance(value, type):
|
|
40
|
+
value = value()
|
|
41
|
+
|
|
42
|
+
if not isinstance(value, Backend):
|
|
43
|
+
raise ValueError(f'Expected a `Backend` instance, got {value}.')
|
|
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
|
+
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_backend(backend: BackendLike) -> Backend:
|
|
52
|
+
global DEFAULT_BACKEND
|
|
53
|
+
current = DEFAULT_BACKEND
|
|
54
|
+
DEFAULT_BACKEND = resolve_backend(backend)
|
|
55
|
+
return current
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@contextmanager
|
|
59
|
+
def imops_backend(backend: BackendLike):
|
|
60
|
+
previous = set_backend(backend)
|
|
61
|
+
try:
|
|
62
|
+
yield
|
|
63
|
+
finally:
|
|
64
|
+
set_backend(previous)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# implementations
|
|
68
|
+
# TODO: Investigate whether it is safe to use -ffast-math in numba
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class Numba(Backend):
|
|
71
|
+
parallel: bool = True
|
|
72
|
+
nogil: bool = True
|
|
73
|
+
cache: bool = True
|
|
74
|
+
|
|
75
|
+
def __post_init__(self):
|
|
76
|
+
try:
|
|
77
|
+
import numba # noqa: F401
|
|
78
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
79
|
+
raise ModuleNotFoundError('Install `numba` package (pip install numba) to use "numba" backend.')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class Cython(Backend):
|
|
84
|
+
fast: bool = False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class Scipy(Backend):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
DEFAULT_BACKEND = Cython()
|
|
93
|
+
|
|
94
|
+
BACKEND_NAME2ENV_NUM_THREADS_VAR_NAME = {Cython.__name__: 'OMP_NUM_THREADS', Numba.__name__: 'NUMBA_NUM_THREADS'}
|
|
95
|
+
SINGLE_THREADED_BACKENDS = (Scipy.__name__,)
|
imops/box.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from copy import copy
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
# for backward compatibility
|
|
9
|
+
from .utils import build_slices # noqa: F401
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Immutable numpy array
|
|
13
|
+
Box = np.ndarray
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_box(iterable) -> Box:
|
|
17
|
+
"""Returns a box, generated from copy of the `iterable`."""
|
|
18
|
+
box = np.asarray(copy(iterable))
|
|
19
|
+
box.setflags(write=False)
|
|
20
|
+
|
|
21
|
+
assert box.ndim == 2 and len(box) == 2, box.shape
|
|
22
|
+
assert np.all(box[0] <= box[1]), box
|
|
23
|
+
|
|
24
|
+
return box
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def returns_box(func: Callable) -> Callable:
|
|
28
|
+
"""Returns function, decorated so that it returns a box."""
|
|
29
|
+
|
|
30
|
+
@wraps(func)
|
|
31
|
+
def func_returning_box(*args, **kwargs):
|
|
32
|
+
return make_box(func(*args, **kwargs))
|
|
33
|
+
|
|
34
|
+
func_returning_box.__annotations__['return'] = Box
|
|
35
|
+
|
|
36
|
+
return func_returning_box
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@returns_box
|
|
40
|
+
def mask_to_box(mask: np.ndarray) -> Box:
|
|
41
|
+
"""Find the smallest box that contains all true values of the `mask`."""
|
|
42
|
+
if not mask.any():
|
|
43
|
+
raise ValueError('The mask is empty.')
|
|
44
|
+
|
|
45
|
+
start, stop = [], []
|
|
46
|
+
for ax in itertools.combinations(range(mask.ndim), mask.ndim - 1):
|
|
47
|
+
nonzero = np.any(mask, axis=ax)
|
|
48
|
+
if np.any(nonzero):
|
|
49
|
+
left, right = np.where(nonzero)[0][[0, -1]]
|
|
50
|
+
else:
|
|
51
|
+
left, right = 0, 0
|
|
52
|
+
start.insert(0, left)
|
|
53
|
+
stop.insert(0, right + 1)
|
|
54
|
+
|
|
55
|
+
return start, stop
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@returns_box
|
|
59
|
+
def shape_to_box(shape: Tuple) -> Box:
|
|
60
|
+
return make_box([(0,) * len(shape), shape]) # fmt: skip
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def box_to_shape(box: Box) -> Tuple:
|
|
64
|
+
return tuple(box[1] - box[0])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@returns_box
|
|
68
|
+
def add_margin(box: Box, margin) -> Box:
|
|
69
|
+
"""
|
|
70
|
+
Returns a box with size increased by the ``margin`` (need to be broadcastable to the box)
|
|
71
|
+
compared to the input ``box``.
|
|
72
|
+
"""
|
|
73
|
+
margin = np.broadcast_to(margin, box.shape)
|
|
74
|
+
return box[0] - margin[0], box[1] + margin[1]
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// MIT License
|
|
2
|
+
|
|
3
|
+
// Copyright (c) 2018 Volodymyr Bilonenko
|
|
4
|
+
|
|
5
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
// in the Software without restriction, including without limitation the rights
|
|
8
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
// furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
// copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
// SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#pragma once
|
|
26
|
+
|
|
27
|
+
#define DELAUNATOR_HEADER_ONLY
|
|
28
|
+
|
|
29
|
+
#include "delaunator.hpp"
|
|
30
|
+
|
|
31
|
+
#include "delaunator.cpp"
|
|
32
|
+
|
|
33
|
+
#undef DELAUNATOR_HEADER_ONLY
|