Repository navigation
Expand file tree
/
Copy pathmonotone_norm.cpp
More file actions
151 lines (131 loc) · 3.5 KB
/
Copy pathmonotone_norm.cpp
File metadata and controls
151 lines (131 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <vector>
#include <limits>
namespace py = pybind11;
namespace {
void compute_monotone_normalized(const float *in, float *out, int H, int W)
{
const int N = H * W;
std::vector<int> next_up(N, -1);
std::vector<int> next_down(N, -1);
const int dx[8] = { -1, 1, 0, 0, -1, -1, 1, 1 };
const int dy[8] = { 0, 0, -1, 1, -1, 1, -1, 1 };
// Precompute steepest ascent and descent neighbor for each pixel.
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
const int idx = y * W + x;
const float v = in[idx];
float best_up_val = -std::numeric_limits<float>::infinity();
int best_up_idx = -1;
float best_down_val = std::numeric_limits<float>::infinity();
int best_down_idx = -1;
for (int k = 0; k < 8; ++k) {
const int nx = x + dx[k];
const int ny = y + dy[k];
if (nx < 0 || nx >= W || ny < 0 || ny >= H) {
continue;
}
const int nidx = ny * W + nx;
const float nv = in[nidx];
if (nv > v && nv > best_up_val) {
best_up_val = nv;
best_up_idx = nidx;
}
if (nv < v && nv < best_down_val) {
best_down_val = nv;
best_down_idx = nidx;
}
}
next_up[idx] = best_up_idx;
next_down[idx] = best_down_idx;
}
}
// Follow ascent/descent chains with path compression to find extrema.
std::vector<int> max_root(N, -1);
std::vector<int> min_root(N, -1);
for (int i = 0; i < N; ++i) {
int cur = i;
std::vector<int> path;
path.reserve(64);
while (true) {
if (max_root[cur] != -1) {
break;
}
path.push_back(cur);
int nxt = next_up[cur];
if (nxt == -1) {
break;
}
cur = nxt;
}
int root = (max_root[cur] != -1) ? max_root[cur] : cur;
for (int idx : path) {
max_root[idx] = root;
}
// Also ensure the final node has its root set.
if (max_root[cur] == -1) {
max_root[cur] = root;
}
}
for (int i = 0; i < N; ++i) {
int cur = i;
std::vector<int> path;
path.reserve(64);
while (true) {
if (min_root[cur] != -1) {
break;
}
path.push_back(cur);
int nxt = next_down[cur];
if (nxt == -1) {
break;
}
cur = nxt;
}
int root = (min_root[cur] != -1) ? min_root[cur] : cur;
for (int idx : path) {
min_root[idx] = root;
}
// Also ensure the final node has its root set.
if (min_root[cur] == -1) {
min_root[cur] = root;
}
}
// Compute normalized value in [0,1] for each pixel.
// Map reachable minimum -> 1 and reachable maximum -> 0:
// t = (d_max - d) / (d_max - d_min)
constexpr float eps = 1e-6f;
for (int i = 0; i < N; ++i) {
const float d = in[i];
const float dmax = in[max_root[i]];
const float dmin = in[min_root[i]];
const float denom = dmax - dmin;
if (denom > eps) {
out[i] = (dmax - d) / denom;
} else {
out[i] = 0.5f;
}
}
}
} // namespace
py::array_t<float> monotone_normalized(py::array_t<float, py::array::c_style | py::array::forcecast> input)
{
py::buffer_info buf = input.request();
if (buf.ndim != 2) {
throw std::runtime_error("Input array must be 2D");
}
const int H = static_cast<int>(buf.shape[0]);
const int W = static_cast<int>(buf.shape[1]);
const float *in = static_cast<const float *>(buf.ptr);
py::array_t<float> output({H, W});
py::buffer_info out_buf = output.request();
float *out = static_cast<float *>(out_buf.ptr);
compute_monotone_normalized(in, out, H, W);
return output;
}
PYBIND11_MODULE(monotone_norm, m)
{
m.doc() = "Monotone normalized distance transform";
m.def("compute", &monotone_normalized, "Compute monotone-normalized DT");
}