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
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
#pragma once
|
|
25
|
+
|
|
26
|
+
#ifdef DELAUNATOR_HEADER_ONLY
|
|
27
|
+
#define INLINE inline
|
|
28
|
+
#else
|
|
29
|
+
#define INLINE
|
|
30
|
+
#endif
|
|
31
|
+
|
|
32
|
+
#include <limits>
|
|
33
|
+
#include <vector>
|
|
34
|
+
#include <ostream>
|
|
35
|
+
|
|
36
|
+
namespace delaunator {
|
|
37
|
+
|
|
38
|
+
constexpr std::size_t INVALID_INDEX =
|
|
39
|
+
(std::numeric_limits<std::size_t>::max)();
|
|
40
|
+
|
|
41
|
+
class Point
|
|
42
|
+
{
|
|
43
|
+
public:
|
|
44
|
+
Point(double x, double y) : m_x(x), m_y(y)
|
|
45
|
+
{}
|
|
46
|
+
Point() : m_x(0), m_y(0)
|
|
47
|
+
{}
|
|
48
|
+
|
|
49
|
+
double x() const
|
|
50
|
+
{ return m_x; }
|
|
51
|
+
|
|
52
|
+
double y() const
|
|
53
|
+
{ return m_y; }
|
|
54
|
+
|
|
55
|
+
double magnitude2() const
|
|
56
|
+
{ return m_x * m_x + m_y * m_y; }
|
|
57
|
+
|
|
58
|
+
static double determinant(const Point& p1, const Point& p2)
|
|
59
|
+
{
|
|
60
|
+
return p1.m_x * p2.m_y - p1.m_y * p2.m_x;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static Point vector(const Point& p1, const Point& p2)
|
|
64
|
+
{
|
|
65
|
+
return Point(p2.m_x - p1.m_x, p2.m_y - p1.m_y);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static double dist2(const Point& p1, const Point& p2)
|
|
69
|
+
{
|
|
70
|
+
Point vec = vector(p1, p2);
|
|
71
|
+
return vec.m_x * vec.m_x + vec.m_y * vec.m_y;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static bool equal(const Point& p1, const Point& p2, double span)
|
|
75
|
+
{
|
|
76
|
+
double dist = dist2(p1, p2) / span;
|
|
77
|
+
|
|
78
|
+
// ABELL - This number should be examined to figure how how
|
|
79
|
+
// it correlates with the breakdown of calculating determinants.
|
|
80
|
+
return dist < 1e-20;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
double m_x;
|
|
85
|
+
double m_y;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
inline std::ostream& operator<<(std::ostream& out, const Point& p)
|
|
89
|
+
{
|
|
90
|
+
out << p.x() << "/" << p.y();
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Points
|
|
96
|
+
{
|
|
97
|
+
public:
|
|
98
|
+
using const_iterator = Point const *;
|
|
99
|
+
|
|
100
|
+
Points(const std::vector<double>& coords) : m_coords(coords)
|
|
101
|
+
{}
|
|
102
|
+
|
|
103
|
+
const Point& operator[](size_t offset)
|
|
104
|
+
{
|
|
105
|
+
return reinterpret_cast<const Point&>(
|
|
106
|
+
*(m_coords.data() + (offset * 2)));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Points::const_iterator begin() const
|
|
110
|
+
{ return reinterpret_cast<const Point *>(m_coords.data()); }
|
|
111
|
+
Points::const_iterator end() const
|
|
112
|
+
{ return reinterpret_cast<const Point *>(
|
|
113
|
+
m_coords.data() + m_coords.size()); }
|
|
114
|
+
size_t size() const
|
|
115
|
+
{ return m_coords.size() / 2; }
|
|
116
|
+
|
|
117
|
+
private:
|
|
118
|
+
const std::vector<double>& m_coords;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
class Delaunator {
|
|
122
|
+
|
|
123
|
+
public:
|
|
124
|
+
std::vector<double> const& coords;
|
|
125
|
+
Points m_points;
|
|
126
|
+
|
|
127
|
+
// 'triangles' stores the indices to the 'X's of the input
|
|
128
|
+
// 'coords'.
|
|
129
|
+
std::vector<std::size_t> triangles;
|
|
130
|
+
|
|
131
|
+
// 'halfedges' store indices into 'triangles'. If halfedges[X] = Y,
|
|
132
|
+
// It says that there's an edge from X to Y where a) X and Y are
|
|
133
|
+
// both indices into triangles and b) X and Y are indices into different
|
|
134
|
+
// triangles in the array. This allows you to get from a triangle to
|
|
135
|
+
// its adjacent triangle. If the a triangle edge has no adjacent triangle,
|
|
136
|
+
// its half edge will be INVALID_INDEX.
|
|
137
|
+
std::vector<std::size_t> halfedges;
|
|
138
|
+
|
|
139
|
+
std::vector<std::size_t> hull_prev;
|
|
140
|
+
std::vector<std::size_t> hull_next;
|
|
141
|
+
|
|
142
|
+
// This contains indexes into the triangles array.
|
|
143
|
+
std::vector<std::size_t> hull_tri;
|
|
144
|
+
std::size_t hull_start;
|
|
145
|
+
|
|
146
|
+
INLINE Delaunator(std::vector<double> const& in_coords);
|
|
147
|
+
INLINE double get_hull_area();
|
|
148
|
+
INLINE double get_triangle_area();
|
|
149
|
+
|
|
150
|
+
private:
|
|
151
|
+
std::vector<std::size_t> m_hash;
|
|
152
|
+
Point m_center;
|
|
153
|
+
std::size_t m_hash_size;
|
|
154
|
+
std::vector<std::size_t> m_edge_stack;
|
|
155
|
+
|
|
156
|
+
INLINE std::size_t legalize(std::size_t a);
|
|
157
|
+
INLINE std::size_t hash_key(double x, double y) const;
|
|
158
|
+
INLINE std::size_t add_triangle(
|
|
159
|
+
std::size_t i0,
|
|
160
|
+
std::size_t i1,
|
|
161
|
+
std::size_t i2,
|
|
162
|
+
std::size_t a,
|
|
163
|
+
std::size_t b,
|
|
164
|
+
std::size_t c);
|
|
165
|
+
INLINE void link(std::size_t a, std::size_t b);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
} //namespace delaunator
|
|
169
|
+
|
|
170
|
+
#undef INLINE
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#include "triangulator.h"
|
|
2
|
+
|
|
3
|
+
class Interpolator {
|
|
4
|
+
|
|
5
|
+
public:
|
|
6
|
+
using pyarr_double = py::array_t<double, py::array::c_style | py::array::forcecast>;
|
|
7
|
+
const Triangulator triangulation;
|
|
8
|
+
Interpolator(const Triangulator::pyarr_size_t& pypoints, int n_jobs,
|
|
9
|
+
std::optional<Triangulator::pyarr_size_t> pytriangles)
|
|
10
|
+
: triangulation(pypoints, n_jobs, pytriangles) {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pyarr_double operator()(const Triangulator::pyarr_size_t& int_points,
|
|
14
|
+
const pyarr_double& values, const Triangulator::pyarr_size_t& neighbors,
|
|
15
|
+
double fill_value = 0.0) {
|
|
16
|
+
if (static_cast<long>(triangulation.points.size() / 2) != values.shape(0)) {
|
|
17
|
+
throw std::invalid_argument("Length mismatch between known points and their values");
|
|
18
|
+
}
|
|
19
|
+
if (neighbors.shape(0) != int_points.shape(0)) {
|
|
20
|
+
throw std::invalid_argument("Length mismatch between int_points and their neighbors");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
size_t n = int_points.shape(0);
|
|
24
|
+
std::vector<double> int_values(n);
|
|
25
|
+
|
|
26
|
+
omp_set_dynamic(0); // Explicitly disable dynamic teams
|
|
27
|
+
omp_set_num_threads(triangulation.n_jobs_);
|
|
28
|
+
|
|
29
|
+
#pragma parallel for
|
|
30
|
+
for (size_t i = 0; i < n; ++i) {
|
|
31
|
+
size_t x = int_points.at(i, 0);
|
|
32
|
+
size_t y = int_points.at(i, 1);
|
|
33
|
+
auto location_info = triangulation.locate_point(x, y, neighbors.at(i));
|
|
34
|
+
if (location_info.has_value()) {
|
|
35
|
+
size_t t = location_info->first;
|
|
36
|
+
auto& coords_info = location_info->second;
|
|
37
|
+
double one_over_2area = 1.0 / static_cast<double>(coords_info[3]);
|
|
38
|
+
double lambda_1 = static_cast<double>(coords_info[0]);
|
|
39
|
+
double lambda_2 = static_cast<double>(coords_info[1]);
|
|
40
|
+
double lambda_3 = static_cast<double>(coords_info[2]);
|
|
41
|
+
double f1 = values.at(triangulation.triangles.at(t));
|
|
42
|
+
double f2 = values.at(triangulation.triangles.at(t + 1));
|
|
43
|
+
double f3 = values.at(triangulation.triangles.at(t + 2));
|
|
44
|
+
int_values[i] = (f1 * lambda_1 + f2 * lambda_2 + f3 * lambda_3) * one_over_2area;
|
|
45
|
+
} else {
|
|
46
|
+
int_values[i] = fill_value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {{n}, {sizeof(double)}, int_values.data()};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#include <omp.h>
|
|
2
|
+
#include <memory>
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <pybind11/pybind11.h>
|
|
5
|
+
#include <pybind11/numpy.h>
|
|
6
|
+
#include <pybind11/embed.h>
|
|
7
|
+
#include <pybind11/stl.h>
|
|
8
|
+
#include "delaunator/delaunator-header-only.hpp"
|
|
9
|
+
#include "utils.h"
|
|
10
|
+
|
|
11
|
+
namespace py = pybind11;
|
|
12
|
+
|
|
13
|
+
class Triangulator {
|
|
14
|
+
private:
|
|
15
|
+
size_t n_jobs_;
|
|
16
|
+
friend class Interpolator;
|
|
17
|
+
|
|
18
|
+
public:
|
|
19
|
+
using pyarr_size_t = py::array_t<size_t, py::array::c_style | py::array::forcecast>;
|
|
20
|
+
std::vector<size_t> points;
|
|
21
|
+
std::vector<size_t> triangles;
|
|
22
|
+
std::vector<std::vector<size_t>> point2tri;
|
|
23
|
+
std::unordered_map<uint64_t, std::array<size_t, 2>> edge2tri;
|
|
24
|
+
|
|
25
|
+
Triangulator(const pyarr_size_t& pypoints, int n_jobs,
|
|
26
|
+
std::optional<pyarr_size_t> pytriangles) {
|
|
27
|
+
if (n_jobs <= 0 and n_jobs != -1) {
|
|
28
|
+
throw std::invalid_argument(
|
|
29
|
+
"Invalid number of workers, has to be -1 or positive integer");
|
|
30
|
+
}
|
|
31
|
+
if (pytriangles.has_value()) {
|
|
32
|
+
if (pytriangles->shape(1) != 3 or pytriangles->shape(0) == 0 or
|
|
33
|
+
pytriangles->size() / 3 != pytriangles->shape(0)) {
|
|
34
|
+
throw std::invalid_argument("Passed triangles argument has an incorrect shape");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
n_jobs_ = n_jobs == -1 ? omp_get_num_procs() : n_jobs;
|
|
39
|
+
size_t n = pypoints.shape(0);
|
|
40
|
+
|
|
41
|
+
points.resize(2 * n);
|
|
42
|
+
|
|
43
|
+
omp_set_dynamic(0); // Explicitly disable dynamic teams
|
|
44
|
+
omp_set_num_threads(n_jobs_);
|
|
45
|
+
|
|
46
|
+
if (pytriangles.has_value()) {
|
|
47
|
+
#pragma parallel for
|
|
48
|
+
for (size_t i = 0; i < n; ++i) {
|
|
49
|
+
size_t j = 2 * i;
|
|
50
|
+
points.at(j) = pypoints.at(i, 0);
|
|
51
|
+
points.at(j + 1) = pypoints.at(i, 1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
size_t m = pytriangles->shape(0);
|
|
55
|
+
triangles.resize(3 * m);
|
|
56
|
+
|
|
57
|
+
#pragma parallel for
|
|
58
|
+
for (size_t i = 0; i < m; ++i) {
|
|
59
|
+
size_t j = 3 * i;
|
|
60
|
+
triangles.at(j) = pytriangles->at(i, 0);
|
|
61
|
+
triangles.at(j + 1) = pytriangles->at(i, 1);
|
|
62
|
+
triangles.at(j + 2) = pytriangles->at(i, 2);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
else {
|
|
67
|
+
std::vector<double> double_points(2 * n);
|
|
68
|
+
#pragma parallel for
|
|
69
|
+
for (size_t i = 0; i < n; ++i) {
|
|
70
|
+
double_points.at(2 * i) = static_cast<double>(pypoints.at(i, 0));
|
|
71
|
+
double_points.at(2 * i + 1) = static_cast<double>(pypoints.at(i, 1));
|
|
72
|
+
}
|
|
73
|
+
delaunator::Delaunator delaunated(double_points);
|
|
74
|
+
triangles = std::move(delaunated.triangles);
|
|
75
|
+
|
|
76
|
+
#pragma parallel for
|
|
77
|
+
for (size_t i = 0; i < n; ++i) {
|
|
78
|
+
size_t j = 2 * i;
|
|
79
|
+
points.at(j) = static_cast<size_t>(delaunated.coords.at(j));
|
|
80
|
+
points.at(j + 1) = static_cast<size_t>(delaunated.coords.at(j + 1));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
point2tri.resize(n);
|
|
85
|
+
for (size_t i = 0; i < triangles.size() / 3; ++i) {
|
|
86
|
+
size_t t = 3 * i;
|
|
87
|
+
for (size_t j = 0; j < 3; ++j) {
|
|
88
|
+
size_t a = triangles.at(t + j);
|
|
89
|
+
size_t b = triangles.at(t + fast_mod(j + 1, 3));
|
|
90
|
+
point2tri.at(a).push_back(t);
|
|
91
|
+
uint64_t e = a < b ? elegant_pair(a, b) : elegant_pair(b, a);
|
|
92
|
+
auto got = edge2tri.find(e);
|
|
93
|
+
if (got != edge2tri.end()) {
|
|
94
|
+
got->second.at(1) = t;
|
|
95
|
+
} else {
|
|
96
|
+
edge2tri[e] = {t, t};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
inline std::optional<std::pair<size_t, std::array<int64_t, 4>>> locate_point(
|
|
103
|
+
size_t x, size_t y, size_t neighbor) const {
|
|
104
|
+
size_t nx = points.at(2 * neighbor);
|
|
105
|
+
size_t ny = points.at(2 * neighbor + 1);
|
|
106
|
+
int64_t curr_t = -1;
|
|
107
|
+
size_t curr_a, curr_b;
|
|
108
|
+
for (size_t t : point2tri.at(neighbor)) {
|
|
109
|
+
auto coords_info = barycentric_coords(x, y, t);
|
|
110
|
+
if (point_in_triangle(coords_info)) {
|
|
111
|
+
return {std::make_pair(t, coords_info)};
|
|
112
|
+
}
|
|
113
|
+
for (size_t j = 0; j < 3; ++j) {
|
|
114
|
+
size_t a = triangles.at(t + j);
|
|
115
|
+
size_t b = triangles.at(t + fast_mod(j + 1, 3));
|
|
116
|
+
if (a == neighbor or b == neighbor) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
size_t ax = points.at(2 * a);
|
|
120
|
+
size_t ay = points.at(2 * a + 1);
|
|
121
|
+
size_t bx = points.at(2 * b);
|
|
122
|
+
size_t by = points.at(2 * b + 1);
|
|
123
|
+
if (segments_intersection(nx, ny, x, y, ax, ay, bx, by)) {
|
|
124
|
+
curr_t = static_cast<int64_t>(t);
|
|
125
|
+
curr_a = a;
|
|
126
|
+
curr_b = b;
|
|
127
|
+
if (curr_a > curr_b) {
|
|
128
|
+
std::swap(curr_a, curr_b);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (curr_t != -1) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (curr_t == -1) {
|
|
139
|
+
return std::nullopt;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
while (true) {
|
|
143
|
+
uint64_t e = elegant_pair(curr_a, curr_b); // already curr_a < curr_b
|
|
144
|
+
auto& adj_t = edge2tri.at(e);
|
|
145
|
+
if (adj_t.at(0) == adj_t.at(1)) {
|
|
146
|
+
return std::nullopt;
|
|
147
|
+
}
|
|
148
|
+
size_t t = (adj_t.at(0) == static_cast<size_t>(curr_t)) ? adj_t.at(1) : adj_t.at(0);
|
|
149
|
+
auto coords_info = barycentric_coords(x, y, t);
|
|
150
|
+
if (point_in_triangle(coords_info)) {
|
|
151
|
+
return {std::make_pair(t, coords_info)};
|
|
152
|
+
}
|
|
153
|
+
for (size_t i = 0; i < 3; ++i) {
|
|
154
|
+
size_t a = triangles.at(t + i);
|
|
155
|
+
size_t b = triangles.at(t + fast_mod(i + 1, 3));
|
|
156
|
+
if (a > b) {
|
|
157
|
+
std::swap(a, b);
|
|
158
|
+
}
|
|
159
|
+
if (a == curr_a and b == curr_b) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
size_t ax = points.at(2 * a);
|
|
163
|
+
size_t ay = points.at(2 * a + 1);
|
|
164
|
+
size_t bx = points.at(2 * b);
|
|
165
|
+
size_t by = points.at(2 * b + 1);
|
|
166
|
+
if (segments_intersection(nx, ny, x, y, ax, ay, bx, by)) {
|
|
167
|
+
curr_t = static_cast<int64_t>(t);
|
|
168
|
+
curr_a = a;
|
|
169
|
+
curr_b = b;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
inline std::array<int64_t, 4> barycentric_coords(size_t x, size_t y, size_t t) const {
|
|
177
|
+
size_t r1 = 2 * triangles.at(t);
|
|
178
|
+
auto x1 = static_cast<int64_t>(points.at(r1));
|
|
179
|
+
auto y1 = static_cast<int64_t>(points.at(r1 + 1));
|
|
180
|
+
size_t r2 = 2 * triangles.at(t + 1);
|
|
181
|
+
auto x2 = static_cast<int64_t>(points.at(r2));
|
|
182
|
+
auto y2 = static_cast<int64_t>(points.at(r2 + 1));
|
|
183
|
+
size_t r3 = 2 * triangles.at(t + 2);
|
|
184
|
+
auto x3 = static_cast<int64_t>(points.at(r3));
|
|
185
|
+
auto y3 = static_cast<int64_t>(points.at(r3 + 1));
|
|
186
|
+
auto x1y2 = x1 * y2, x1y3 = x1 * y3;
|
|
187
|
+
auto x2y3 = x2 * y3, x2y1 = x2 * y1;
|
|
188
|
+
auto x3y1 = x3 * y1, x3y2 = x3 * y2;
|
|
189
|
+
int64_t xx = static_cast<int64_t>(x);
|
|
190
|
+
int64_t yy = static_cast<int64_t>(y);
|
|
191
|
+
std::array<int64_t, 4> coords_info;
|
|
192
|
+
coords_info[0] = x2y3 - x3y2 + (y2 - y3) * xx + (x3 - x2) * yy;
|
|
193
|
+
coords_info[1] = x3y1 - x1y3 + (y3 - y1) * xx + (x1 - x3) * yy;
|
|
194
|
+
coords_info[2] = x1y2 - x2y1 + (y1 - y2) * xx + (x2 - x1) * yy;
|
|
195
|
+
coords_info[3] = x1y2 - x1y3 + x2y3 - x2y1 + x3y1 - x3y2;
|
|
196
|
+
return coords_info;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Szudzik’s Pairing Function https://en.wikipedia.org/wiki/Pairing_function#Other_pairing_functions
|
|
2
|
+
inline uint64_t elegant_pair(size_t a, size_t b) {
|
|
3
|
+
return static_cast<uint64_t>(b) * b + a; // implicit cast
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
inline size_t fast_mod(const size_t i, const size_t c) {
|
|
7
|
+
return i >= c ? i % c : i;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
inline bool on_segment(size_t Ax, size_t Ay, size_t Bx, size_t By, size_t Cx, size_t Cy) {
|
|
11
|
+
return Bx <= std::max(Ax, Cx) and Bx >= std::min(Ax, Cx) and By <= std::max(Ay, Cy) and
|
|
12
|
+
By >= std::min(Ay, Cy);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
inline int32_t orientation(int64_t Ax, int64_t Ay, int64_t Bx, int64_t By, int64_t Cx, int64_t Cy) {
|
|
16
|
+
int64_t val = (By - Ay) * (Cx - Bx) - (Bx - Ax) * (Cy - By);
|
|
17
|
+
if (val > 0) {
|
|
18
|
+
return 1;
|
|
19
|
+
} else if (val < 0) {
|
|
20
|
+
return 2;
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
inline bool segments_intersection(size_t Ax, size_t Ay, size_t Bx, size_t By, size_t Cx, size_t Cy,
|
|
26
|
+
size_t Dx, size_t Dy) {
|
|
27
|
+
auto o1 = orientation(Ax, Ay, Bx, By, Cx, Cy);
|
|
28
|
+
auto o2 = orientation(Ax, Ay, Bx, By, Dx, Dy);
|
|
29
|
+
auto o3 = orientation(Cx, Cy, Dx, Dy, Ax, Ay);
|
|
30
|
+
auto o4 = orientation(Cx, Cy, Dx, Dy, Bx, By);
|
|
31
|
+
if ((o1 != o2) and (o3 != o4)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ((o1 == 0) and on_segment(Ax, Ay, Cx, Cy, Bx, By)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ((o2 == 0) and on_segment(Ax, Ay, Dx, Dy, Bx, By)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if ((o3 == 0) and on_segment(Cx, Cy, Ax, Ay, Dx, Dy)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ((o4 == 0) and on_segment(Cx, Cy, Bx, By, Dx, Dy)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
inline bool point_in_triangle(const std::array<int64_t, 4>& coords_info) {
|
|
55
|
+
if (coords_info[3] < 0) {
|
|
56
|
+
return coords_info[3] <= coords_info[0] and coords_info[0] <= 0 and
|
|
57
|
+
coords_info[3] <= coords_info[1] and coords_info[1] <= 0 and
|
|
58
|
+
coords_info[3] <= coords_info[2] and coords_info[2] <= 0;
|
|
59
|
+
}
|
|
60
|
+
return coords_info[3] >= coords_info[0] and coords_info[0] >= 0 and
|
|
61
|
+
coords_info[3] >= coords_info[1] and coords_info[1] >= 0 and
|
|
62
|
+
coords_info[3] >= coords_info[2] and coords_info[2] >= 0;
|
|
63
|
+
}
|
imops/cpp/main.cpp
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#include <cassert>
|
|
2
|
+
#include <iostream>
|
|
3
|
+
#include <stdexcept>
|
|
4
|
+
#include "interp2d/interpolator.h"
|
|
5
|
+
#include <Python.h>
|
|
6
|
+
|
|
7
|
+
PYBIND11_MODULE(cpp_modules, m) {
|
|
8
|
+
py::class_<Interpolator>(m, "Linear2DInterpolatorCpp", "Interpolator class")
|
|
9
|
+
.def(py::init<const Triangulator::pyarr_size_t&, int, std::optional<Triangulator::pyarr_size_t>>())
|
|
10
|
+
.def("__call__", &Interpolator::operator());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
int main() {}
|
imops/crop.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .backend import BackendLike
|
|
4
|
+
from .numeric import _NUMERIC_DEFAULT_NUM_THREADS
|
|
5
|
+
from .pad import pad
|
|
6
|
+
from .utils import AxesLike, AxesParams, assert_subdtype, broadcast_axis, fill_by_indices
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def crop_to_shape(x: np.ndarray, shape: AxesLike, axis: AxesLike = None, ratio: AxesParams = 0.5) -> np.ndarray:
|
|
10
|
+
"""
|
|
11
|
+
Crop `x` to match `shape` along `axis`.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
x: np.ndarray
|
|
16
|
+
n-dimensional array
|
|
17
|
+
shape: AxesLike
|
|
18
|
+
final shape
|
|
19
|
+
axis: AxesLike
|
|
20
|
+
axis along which `x` will be padded
|
|
21
|
+
ratio: AxesParams
|
|
22
|
+
float or sequence of floats describing what proportion of cropping to apply on the left sides of cropping axes.
|
|
23
|
+
Remaining ratio of cropping will be applied on the right sides
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
cropped: np.ndarray
|
|
28
|
+
cropped array
|
|
29
|
+
|
|
30
|
+
Examples
|
|
31
|
+
--------
|
|
32
|
+
```python
|
|
33
|
+
x # array of shape [2, 3, 4]
|
|
34
|
+
cropped = crop_to_shape(x, [1, 2, 3], ratio=0) # crop to shape [1, 2, 3] from the right
|
|
35
|
+
cropped = crop_to_shape(x, 2, axis=1, ratio=1) # crop to shape [2, 2, 4] from the left
|
|
36
|
+
cropped = crop_to_shape(x, [3, 4, 5]) # fail due to bigger resulting shape
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
x = np.asarray(x)
|
|
40
|
+
shape = np.asarray(shape)
|
|
41
|
+
assert_subdtype(shape.dtype, np.integer, 'shape')
|
|
42
|
+
|
|
43
|
+
axis, shape, ratio = broadcast_axis(axis, x.ndim, shape, ratio)
|
|
44
|
+
|
|
45
|
+
old_shape, new_shape = np.array(x.shape), np.array(fill_by_indices(x.shape, shape, axis))
|
|
46
|
+
if (old_shape < new_shape).any():
|
|
47
|
+
raise ValueError(f'The resulting shape cannot be greater than the original one: {old_shape} vs {new_shape}.')
|
|
48
|
+
|
|
49
|
+
ndim = len(x.shape)
|
|
50
|
+
ratio = fill_by_indices(np.zeros(ndim), ratio, axis)
|
|
51
|
+
start = ((old_shape - new_shape) * ratio).astype(int)
|
|
52
|
+
|
|
53
|
+
# TODO: Create contiguous array?
|
|
54
|
+
return x[tuple(map(slice, start, start + new_shape))]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def crop_to_box(
|
|
58
|
+
x: np.ndarray,
|
|
59
|
+
box: np.ndarray,
|
|
60
|
+
axis: AxesLike = None,
|
|
61
|
+
padding_values: AxesParams = None,
|
|
62
|
+
num_threads: int = _NUMERIC_DEFAULT_NUM_THREADS,
|
|
63
|
+
backend: BackendLike = None,
|
|
64
|
+
) -> np.ndarray:
|
|
65
|
+
"""
|
|
66
|
+
Crop `x` according to `box` along `axis`.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
x: np.ndarray
|
|
71
|
+
n-dimensional array
|
|
72
|
+
box: np.ndarray
|
|
73
|
+
array of shape (2, x.ndim or len(axis) if axis is passed) describing crop boundaries
|
|
74
|
+
axis: AxesLike
|
|
75
|
+
axis along which `x` will be cropped
|
|
76
|
+
padding_values: AxesParams
|
|
77
|
+
values to pad with if box exceeds the input's limits
|
|
78
|
+
num_threads: int
|
|
79
|
+
the number of threads to use for computation. Default = 4. If negative value passed
|
|
80
|
+
cpu count + num_threads + 1 threads will be used
|
|
81
|
+
backend: BackendLike
|
|
82
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
cropped: np.ndarray
|
|
87
|
+
cropped array
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
```python
|
|
92
|
+
x # array of shape [2, 3, 4]
|
|
93
|
+
cropped = crop_to_box(x, np.array([[0, 0, 0], [1, 1, 1]])) # crop to shape [1, 1, 1]
|
|
94
|
+
cropped = crop_to_box(x, np.array([[0, 0, 0], [5, 5, 5]])) # fail, box exceeds the input's limits
|
|
95
|
+
cropped = crop_to_box(x, np.array([[0], [5]]), axis=0, padding_values=0) # pad with 0-s to shape [5, 3, 4]
|
|
96
|
+
```
|
|
97
|
+
"""
|
|
98
|
+
x = np.asarray(x)
|
|
99
|
+
box = np.asarray(box)
|
|
100
|
+
assert_subdtype(box.dtype, np.integer, 'box')
|
|
101
|
+
|
|
102
|
+
start, stop = box
|
|
103
|
+
axis, start, stop = broadcast_axis(axis, x.ndim, start, stop)
|
|
104
|
+
|
|
105
|
+
slice_start = np.maximum(start, 0)
|
|
106
|
+
slice_stop = np.minimum(stop, np.array(x.shape)[list(axis)])
|
|
107
|
+
padding = np.array([slice_start - start, stop - slice_stop], dtype=int).T
|
|
108
|
+
|
|
109
|
+
if padding_values is None and padding.any():
|
|
110
|
+
raise ValueError(f"The box {box} exceeds the input's limits {x.shape}.")
|
|
111
|
+
|
|
112
|
+
slice_start = fill_by_indices(np.zeros(x.ndim, int), slice_start, axis)
|
|
113
|
+
slice_stop = fill_by_indices(x.shape, slice_stop, axis)
|
|
114
|
+
# TODO: Create contiguous array?
|
|
115
|
+
x = x[tuple(map(slice, slice_start, slice_stop))]
|
|
116
|
+
|
|
117
|
+
if padding_values is not None and padding.any():
|
|
118
|
+
x = pad(x, padding, axis, padding_values, num_threads=num_threads, backend=backend)
|
|
119
|
+
|
|
120
|
+
return x
|