-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathunproject_lut.cpp
More file actions
347 lines (305 loc) · 10.2 KB
/
Copy pathunproject_lut.cpp
File metadata and controls
347 lines (305 loc) · 10.2 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#include "unproject_lut.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include "json.hpp"
#include "npy.hpp"
namespace lensboy {
namespace {
bool is_finite(
PixelXY const& value
) {
return std::isfinite(value.xy[0]) and std::isfinite(value.xy[1]);
}
std::array<double, 4> catmull_rom_weights(
double t
) {
double const t2 = t * t;
double const t3 = t2 * t;
return {
-0.5 * t + t2 - 0.5 * t3,
1.0 - 2.5 * t2 + 1.5 * t3,
0.5 * t + 2.0 * t2 - 1.5 * t3,
-0.5 * t2 + 0.5 * t3,
};
}
PixelXY add_scaled(
PixelXY const& a,
PixelXY const& b,
double scale
) {
return {
a.xy[0] + b.xy[0] * scale,
a.xy[1] + b.xy[1] * scale,
};
}
void normalize_ray(
double ray[3]
) {
double const norm = std::sqrt(
ray[0] * ray[0] +
ray[1] * ray[1] +
ray[2] * ray[2]
);
if (norm == 0.0 or not std::isfinite(norm)) {
throw std::runtime_error("Cannot normalize a non-finite or zero-length ray.");
}
ray[0] /= norm;
ray[1] /= norm;
ray[2] /= norm;
}
} // namespace
UnprojectLUT::UnprojectLUT(
UnprojectLUTMetadata metadata,
std::vector<double> xy_grid
) : metadata_(std::move(metadata)),
xy_grid_(std::move(xy_grid)) {
if (metadata_.image_width < 2 or metadata_.image_height < 2) {
throw std::runtime_error("Image dimensions must be at least 2.");
}
if (metadata_.grid_width < 2 or metadata_.grid_height < 2) {
throw std::runtime_error("Grid dimensions must be at least 2 in each axis.");
}
if (xy_grid_.size() != metadata_.grid_width * metadata_.grid_height * 2) {
throw std::runtime_error("xy_grid size does not match metadata.");
}
grid_scale_x_ = static_cast<double>(metadata_.grid_width - 1) /
static_cast<double>(metadata_.image_width - 1);
grid_scale_y_ = static_cast<double>(metadata_.grid_height - 1) /
static_cast<double>(metadata_.image_height - 1);
}
UnprojectLUT UnprojectLUT::load(
std::string_view const dir_path
) {
std::string const dir(dir_path);
std::string const metadata_path = dir + "/metadata.json";
std::string const xy_grid_path = dir + "/xy_grid.npy";
std::ifstream metadata_file(metadata_path);
if (not metadata_file) {
throw std::runtime_error("Failed to open metadata file: " + metadata_path);
}
nlohmann::json metadata = nlohmann::json::parse(metadata_file);
std::string lensboy_version =
metadata.at("lensboy-version").get<std::string>();
auto const dot = lensboy_version.find('.');
std::string const major_str = lensboy_version.substr(0, dot);
int const major_version = std::stoi(major_str);
if (major_version < 3) {
throw std::runtime_error(
"This unproject LUT was created with an incompatible version of "
"lensboy (< 3.0.0). Please regenerate it with the current version."
);
}
std::vector<unsigned long> shape;
std::vector<float> raw;
bool fortran_order = false;
npy::LoadArrayFromNumpy<float>(xy_grid_path, shape, fortran_order, raw);
if (fortran_order) {
throw std::runtime_error("xy_grid.npy must be C-contiguous.");
}
if (shape.size() != 3 or shape[2] != 2) {
throw std::runtime_error("xy_grid.npy must have shape (H, W, 2).");
}
std::size_t const grid_height = static_cast<std::size_t>(shape[0]);
std::size_t const grid_width = static_cast<std::size_t>(shape[1]);
std::vector<double> xy_grid(raw.size());
for (std::size_t i = 0; i < raw.size(); ++i) {
if (not std::isfinite(raw[i])) {
throw std::runtime_error("xy_grid contains non-finite values.");
}
xy_grid[i] = static_cast<double>(raw[i]);
}
UnprojectLUTMetadata lut_metadata;
lut_metadata.image_width =
metadata.at("image_width").get<std::size_t>();
lut_metadata.image_height =
metadata.at("image_height").get<std::size_t>();
lut_metadata.grid_width = grid_width;
lut_metadata.grid_height = grid_height;
lut_metadata.lensboy_version = std::move(lensboy_version);
return UnprojectLUT(std::move(lut_metadata), std::move(xy_grid));
}
UnprojectLUTMetadata const& UnprojectLUT::metadata() const noexcept {
return metadata_;
}
std::size_t UnprojectLUT::flat_index(
std::size_t x,
std::size_t y
) const noexcept {
return (y * metadata_.grid_width + x) * 2;
}
PixelXY UnprojectLUT::sample_node(
std::size_t x,
std::size_t y
) const noexcept {
std::size_t const idx = flat_index(x, y);
return {{xy_grid_[idx], xy_grid_[idx + 1]}};
}
double UnprojectLUT::grid_coordinate_x(
double pixel_x
) const noexcept {
return pixel_x * grid_scale_x_;
}
double UnprojectLUT::grid_coordinate_y(
double pixel_y
) const noexcept {
return pixel_y * grid_scale_y_;
}
PixelXY UnprojectLUT::query_nearest(
double gx,
double gy
) const noexcept {
long long const ix = std::llround(gx);
long long const iy = std::llround(gy);
std::size_t const sample_ix = static_cast<std::size_t>(
std::clamp<long long>(ix, 0, static_cast<long long>(metadata_.grid_width) - 1)
);
std::size_t const sample_iy = static_cast<std::size_t>(
std::clamp<long long>(iy, 0, static_cast<long long>(metadata_.grid_height) - 1)
);
return sample_node(sample_ix, sample_iy);
}
PixelXY UnprojectLUT::query_bilinear(
double gx,
double gy
) const noexcept {
double const gx_work = std::clamp(
gx,
0.0,
static_cast<double>(metadata_.grid_width - 1)
);
double const gy_work = std::clamp(
gy,
0.0,
static_cast<double>(metadata_.grid_height - 1)
);
std::size_t const x0 = static_cast<std::size_t>(std::min(
static_cast<long long>(std::floor(gx_work)),
static_cast<long long>(metadata_.grid_width) - 2
));
std::size_t const y0 = static_cast<std::size_t>(std::min(
static_cast<long long>(std::floor(gy_work)),
static_cast<long long>(metadata_.grid_height) - 2
));
std::size_t const x1 = x0 + 1;
std::size_t const y1 = y0 + 1;
double const tx = gx_work - static_cast<double>(x0);
double const ty = gy_work - static_cast<double>(y0);
PixelXY const v00 = sample_node(x0, y0);
PixelXY const v10 = sample_node(x1, y0);
PixelXY const v01 = sample_node(x0, y1);
PixelXY const v11 = sample_node(x1, y1);
PixelXY const top = {{
v00.xy[0] * (1.0 - tx) + v10.xy[0] * tx,
v00.xy[1] * (1.0 - tx) + v10.xy[1] * tx,
}};
PixelXY const bottom = {{
v01.xy[0] * (1.0 - tx) + v11.xy[0] * tx,
v01.xy[1] * (1.0 - tx) + v11.xy[1] * tx,
}};
return {{
top.xy[0] * (1.0 - ty) + bottom.xy[0] * ty,
top.xy[1] * (1.0 - ty) + bottom.xy[1] * ty,
}};
}
PixelXY UnprojectLUT::query_bicubic(
double gx,
double gy
) const noexcept {
if (metadata_.grid_width < 4 or metadata_.grid_height < 4) {
return query_bilinear(gx, gy);
}
double const gx_work =
std::clamp(gx, 0.0, static_cast<double>(metadata_.grid_width - 1));
double const gy_work =
std::clamp(gy, 0.0, static_cast<double>(metadata_.grid_height - 1));
long long const anchor_x = static_cast<long long>(std::floor(gx_work));
long long const anchor_y = static_cast<long long>(std::floor(gy_work));
bool const has_full_support =
anchor_x >= 1 and
anchor_x <= static_cast<long long>(metadata_.grid_width) - 3 and
anchor_y >= 1 and
anchor_y <= static_cast<long long>(metadata_.grid_height) - 3;
if (not has_full_support) {
return query_bilinear(gx, gy);
}
double const tx = gx_work - static_cast<double>(anchor_x);
double const ty = gy_work - static_cast<double>(anchor_y);
std::array<double, 4> const wx = catmull_rom_weights(tx);
std::array<double, 4> const wy = catmull_rom_weights(ty);
PixelXY accum = {{0.0, 0.0}};
for (int j = 0; j < 4; ++j) {
std::size_t const sample_y_idx =
static_cast<std::size_t>(anchor_y + j - 1);
PixelXY row = {{0.0, 0.0}};
for (int i = 0; i < 4; ++i) {
std::size_t const sample_x_idx =
static_cast<std::size_t>(anchor_x + i - 1);
PixelXY const node = sample_node(sample_x_idx, sample_y_idx);
row = add_scaled(row, node, wx[i]);
}
accum = add_scaled(accum, row, wy[j]);
}
return accum;
}
UnprojectLUTQueryResult UnprojectLUT::query(
double pixel_x,
double pixel_y,
InterpolationMode interpolation,
bool const normalize
) const {
bool const inside =
pixel_x >= 0.0 and
pixel_x <= static_cast<double>(metadata_.image_width - 1) and
pixel_y >= 0.0 and
pixel_y <= static_cast<double>(metadata_.image_height - 1);
if (not inside) {
return UnprojectLUTQueryResult::invalid();
}
double const gx = grid_coordinate_x(pixel_x);
double const gy = grid_coordinate_y(pixel_y);
PixelXY xy = {{0.0, 0.0}};
switch (interpolation) {
case InterpolationMode::NEAREST:
xy = query_nearest(gx, gy);
break;
case InterpolationMode::BILINEAR:
xy = query_bilinear(gx, gy);
break;
case InterpolationMode::BICUBIC:
xy = query_bicubic(gx, gy);
break;
}
if (not is_finite(xy)) {
throw std::runtime_error("Query produced non-finite values.");
}
UnprojectLUTQueryResult result;
result.valid = true;
result.ray[0] = xy.xy[0];
result.ray[1] = xy.xy[1];
result.ray[2] = 1.0;
if (normalize) {
normalize_ray(result.ray);
}
return result;
}
std::vector<UnprojectLUTQueryResult> UnprojectLUT::query(
std::vector<PixelXY> const& pixels,
InterpolationMode interpolation,
bool const normalize
) const {
std::vector<UnprojectLUTQueryResult> results;
results.reserve(pixels.size());
for (PixelXY const& pixel : pixels) {
results.push_back(
query(pixel.xy[0], pixel.xy[1], interpolation, normalize)
);
}
return results;
}
} // namespace lensboy