lardon3d/src/dense_mvs.cpp

1288 lines
52 KiB
C++

#include <lardon3d/dense_mvs.h>
#include <algorithm>
#include <cerrno>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <iomanip>
#include <limits>
#include <locale>
#include <map>
#include <new>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <openssl/evp.h>
#include <poll.h>
#include <sstream>
#include <signal.h>
#include <set>
#include <string>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <vector>
namespace lardon3d::dense_mvs_detail {
bool source_image_set_identity(const Lardon3DDenseMvsSourceImage *images,
size_t count, unsigned char digest[32]);
bool calibration_binding_identity(const Lardon3DDenseMvsSourceImage *images,
size_t count, unsigned char digest[32]);
} // namespace lardon3d::dense_mvs_detail
namespace {
constexpr size_t kMaxVersion = 16384;
constexpr int kVersionTimeoutMs = 5000;
constexpr uint64_t kMaxSourceFileBytes = UINT64_C(1024) * 1024 * 1024;
constexpr uint64_t kMaxBackendFileBytes = UINT64_C(1024) * 1024 * 1024;
constexpr size_t kMaxBackendLogBytes = 1024 * 1024;
constexpr size_t kMaxPlyHeader = 1024 * 1024;
constexpr size_t kMaxPlyLine = 64 * 1024;
constexpr size_t kMaxPlyProperties = 256;
bool sha(const unsigned char *p, size_t n, unsigned char d[32]) {
unsigned int length = 0;
return p && d &&
EVP_Digest(p, n, d, &length, EVP_sha256(), nullptr) == 1 &&
length == 32;
}
bool executable(const char *path) {
struct stat status {};
return path && stat(path, &status) == 0 && S_ISREG(status.st_mode) &&
access(path, X_OK) == 0;
}
bool same_file_snapshot(const struct stat &before, const struct stat &after) {
return before.st_dev == after.st_dev && before.st_ino == after.st_ino &&
before.st_size == after.st_size &&
before.st_mtim.tv_sec == after.st_mtim.tv_sec &&
before.st_mtim.tv_nsec == after.st_mtim.tv_nsec &&
before.st_ctim.tv_sec == after.st_ctim.tv_sec &&
before.st_ctim.tv_nsec == after.st_ctim.tv_nsec;
}
bool sha_regular_file(const char *path, unsigned char digest[32],
uint64_t max_bytes = UINT64_MAX,
uint64_t *remaining_bytes = nullptr) {
if (!path || !digest) return false;
const int descriptor = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
if (descriptor < 0) return false;
struct stat status {};
const uint64_t effective_max = remaining_bytes
? std::min(max_bytes, *remaining_bytes)
: max_bytes;
bool ok = fstat(descriptor, &status) == 0 && S_ISREG(status.st_mode) &&
status.st_size >= 0 &&
static_cast<uint64_t>(status.st_size) <= effective_max;
if (!ok) {
(void)close(descriptor);
return false;
}
FILE *file = fdopen(descriptor, "rb");
if (!file) {
(void)close(descriptor);
return false;
}
const uint64_t expected_size = ok ? static_cast<uint64_t>(status.st_size) : 0;
EVP_MD_CTX *context = EVP_MD_CTX_new();
ok = ok && context && EVP_DigestInit_ex(context, EVP_sha256(), nullptr) == 1;
unsigned char buffer[8192];
uint64_t total = 0;
while (ok && total < expected_size) {
const uint64_t remaining = expected_size - total;
const size_t request = static_cast<size_t>(
std::min<uint64_t>(remaining, sizeof buffer));
const size_t size = std::fread(buffer, 1, request, file);
if (size != 0) {
total += size;
ok = EVP_DigestUpdate(context, buffer, size) == 1;
}
if (size < request) ok = false;
}
struct stat final_status {};
ok = ok && !std::ferror(file) && total == expected_size &&
fstat(fileno(file), &final_status) == 0 &&
same_file_snapshot(status, final_status);
unsigned int length = 0;
if (ok) ok = EVP_DigestFinal_ex(context, digest, &length) == 1 && length == 32;
EVP_MD_CTX_free(context);
const int close_result = std::fclose(file);
ok = ok && close_result == 0;
if (ok && remaining_bytes) *remaining_bytes -= expected_size;
return ok;
}
bool ms(uint64_t *value) {
timespec time {};
if (!value || clock_gettime(CLOCK_MONOTONIC, &time) || time.tv_sec < 0)
return false;
*value = static_cast<uint64_t>(time.tv_sec) * 1000 +
static_cast<uint64_t>(time.tv_nsec) / 1000000;
return true;
}
bool wait_nonblocking(pid_t child, int *status, bool *exited) {
for (;;) {
const pid_t result = waitpid(child, status, WNOHANG);
if (result == child) { *exited = true; return true; }
if (result == 0) return true;
if (errno == ECHILD) { *exited = true; return true; }
if (errno != EINTR) return false;
}
}
/* Probe children have a deadline; backend execution intentionally does not. */
bool signal_group(pid_t group, int signal) {
return kill(-group, signal) == 0 || errno == ESRCH;
}
bool owns_process_group(pid_t child) {
const pid_t group = getpgid(child);
return group == child || (group < 0 && errno == ESRCH);
}
bool terminate_and_reap(pid_t child, pid_t group) {
int status = 0;
bool exited = false;
if (!wait_nonblocking(child, &status, &exited)) return false;
if (!signal_group(group, SIGTERM)) return false;
uint64_t start = 0;
if (!ms(&start)) return false;
while (true) {
uint64_t now = 0;
if (!ms(&now) || now - start >= 250) break;
if (!wait_nonblocking(child, &status, &exited)) return false;
if (exited && kill(-group, 0) != 0 && errno == ESRCH) return true;
(void)poll(nullptr, 0, 10);
}
if (!signal_group(group, SIGKILL)) return false;
if (!ms(&start)) return false;
while (true) {
uint64_t now = 0;
if (!ms(&now) || now - start >= 250) return false;
if (!wait_nonblocking(child, &status, &exited)) return false;
if (exited) return true;
(void)poll(nullptr, 0, 10);
}
}
bool run(const std::vector<std::string>& a, const std::string &directory,
const char *log_name) {
if(a.empty())return false;
std::vector<char*> v;
for(const auto&x:a)v.push_back(const_cast<char*>(x.c_str()));
v.push_back(nullptr);
const std::string log_path = directory + "/" + log_name;
int log_fd = open(log_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (log_fd < 0) return false;
int output[2];
if (pipe(output) != 0) { close(log_fd); return false; }
int launch[2];
if (pipe(launch) != 0) {
close(output[0]); close(output[1]); close(log_fd); return false;
}
if (fcntl(launch[1], F_SETFD, FD_CLOEXEC) != 0) {
close(launch[0]); close(launch[1]); close(output[0]); close(output[1]);
close(log_fd); return false;
}
pid_t c=fork();
if(c<0){close(launch[0]);close(launch[1]);close(output[0]);close(output[1]);close(log_fd);return false;}
if(!c){
close(launch[0]);
close(output[0]);
const int input_fd = open("/dev/null", O_RDONLY);
if (setpgid(0, 0) != 0 || input_fd < 0 || dup2(input_fd, STDIN_FILENO) < 0 ||
dup2(output[1], STDOUT_FILENO) < 0 ||
dup2(output[1], STDERR_FILENO) < 0) {
const int error = errno;
(void)!write(launch[1], &error, sizeof error);
_exit(127);
}
close(input_fd);
close(output[1]);
close(log_fd);
execv(v[0],v.data());
const int error = errno;
(void)!write(launch[1], &error, sizeof error);
_exit(127);
}
close(launch[1]);
close(output[1]);
if (setpgid(c, c) != 0 && errno != EACCES && errno != ESRCH) {
close(launch[0]); close(output[0]); close(log_fd);
(void)terminate_and_reap(c, c);
return false;
}
if (!owns_process_group(c)) {
close(launch[0]); close(output[0]); close(log_fd);
(void)terminate_and_reap(c, c);
return false;
}
int launch_error = 0;
ssize_t launch_size;
do { launch_size = read(launch[0], &launch_error, sizeof launch_error); }
while (launch_size < 0 && errno == EINTR);
close(launch[0]);
if (launch_size != 0) {
close(output[0]); close(log_fd);
(void)terminate_and_reap(c, c);
return false;
}
int flags = fcntl(output[0], F_GETFL);
if (flags < 0 || fcntl(output[0], F_SETFL, flags | O_NONBLOCK) != 0) {
close(output[0]); close(log_fd);
(void)terminate_and_reap(c, c);
return false;
}
int s = 0;
bool exited = false, eof = false, io_ok = true;
size_t retained = 0;
while (!exited || !eof) {
if (!exited && !wait_nonblocking(c, &s, &exited)) { io_ok = false; break; }
for (;;) {
unsigned char buffer[8192];
const ssize_t size = read(output[0], buffer, sizeof buffer);
if (size > 0) {
const size_t keep = std::min<size_t>(static_cast<size_t>(size),
kMaxBackendLogBytes - retained);
if (keep && write(log_fd, buffer, keep) != static_cast<ssize_t>(keep))
io_ok = false;
retained += keep;
continue;
}
if (size == 0) eof = true;
else if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
io_ok = false;
break;
}
if (!io_ok || (exited && eof)) break;
pollfd descriptor{output[0], POLLIN | POLLHUP, 0};
if (poll(&descriptor, 1, 50) < 0 && errno != EINTR) { io_ok = false; break; }
if (exited && !eof && (kill(-c, 0) == 0 || errno != ESRCH)) {
if (!terminate_and_reap(c, c)) io_ok = false;
}
}
close(output[0]);
if (close(log_fd) != 0) io_ok = false;
if (!io_ok) {
(void)terminate_and_reap(c, c);
return false;
}
const bool ok = WIFEXITED(s)&&WEXITSTATUS(s)==0;
/* A successful direct child may still have left helpers in its owned group. */
if (kill(-c, 0) == 0 || errno != ESRCH) {
if (!terminate_and_reap(c, c)) return false;
}
return ok;
}
struct BackendCapabilities {
bool cuda_option;
unsigned char version_identity[32];
};
bool probe(const char *path, bool interface_colmap,
BackendCapabilities *capabilities) {
int fd[2];
if (!path || !capabilities || pipe(fd)) return false;
pid_t c=fork();
if(c<0){close(fd[0]);close(fd[1]);return false;}
if(!c){
if (setpgid(0, 0) != 0) _exit(127);
const char *arguments[] = {path, "--help", nullptr};
(void)dup2(fd[1],STDOUT_FILENO);
(void)dup2(fd[1],STDERR_FILENO);
close(fd[0]);
close(fd[1]);
execv(path, const_cast<char *const *>(arguments));
_exit(127);
}
if (setpgid(c, c) != 0 && errno != EACCES && errno != ESRCH) {
close(fd[0]);
close(fd[1]);
(void)terminate_and_reap(c, c);
return false;
}
if (!owns_process_group(c)) {
close(fd[0]);
close(fd[1]);
(void)terminate_and_reap(c, c);
return false;
}
close(fd[1]);
const int flags=fcntl(fd[0],F_GETFL);
if(flags<0||fcntl(fd[0],F_SETFL,flags|O_NONBLOCK)!=0){close(fd[0]);(void)terminate_and_reap(c,c);return false;}
std::vector<unsigned char> out;
uint64_t start=0;
bool ok=ms(&start), eof=false, exited=false;
const uint64_t deadline=start+kVersionTimeoutMs;
int s=0;
while (ok && (!exited || !eof)) {
if (!exited && !wait_nonblocking(c, &s, &exited)) {
ok = false;
break;
}
for (;;) {
unsigned char b[512];
const ssize_t n=read(fd[0],b,sizeof b);
if(n>0){if((size_t)n>kMaxVersion-out.size()){ok=false;break;}out.insert(out.end(),b,b+n);continue;}
if(n==0)eof=true;
else if(errno!=EAGAIN&&errno!=EWOULDBLOCK&&errno!=EINTR)ok=false;
break;
}
if (!ok || (exited && eof)) break;
uint64_t now=0;
if(!ms(&now)||now>=deadline){ok=false;break;}
pollfd x{fd[0],POLLIN|POLLHUP,0};
const int r=poll(&x,1,(int)std::min<uint64_t>(deadline-now,50));
if(r<0&&errno!=EINTR){ok=false;break;}
}
close(fd[0]);
if(!ok||!exited){(void)terminate_and_reap(c,c);return false;}
const bool process_ok = eof && WIFEXITED(s) && !out.empty();
if (kill(-c, 0) == 0 || errno != ESRCH) {
if (!terminate_and_reap(c, c)) return false;
}
if (!process_ok) return false;
const std::string output(out.begin(), out.end());
const bool pinned = output.find("OpenMVS") != std::string::npos &&
output.find("v2.4.0") != std::string::npos;
const bool common = output.find("--max-threads") != std::string::npos;
const bool specific = interface_colmap
? output.find("--image-folder") != std::string::npos
: output.find("--resolution-level") != std::string::npos &&
output.find("--min-resolution") != std::string::npos &&
output.find("--number-views") != std::string::npos &&
output.find("--fusion-mode") != std::string::npos;
static const unsigned char version_record[] = "OpenMVS v2.4.0";
capabilities->cuda_option = output.find("--cuda-device") != std::string::npos;
return pinned && common && specific &&
sha(version_record, sizeof version_record - 1,
capabilities->version_identity);
}
bool finite_cal(const Lardon3DSparseGeometryCalibration &calibration) {
return calibration.width && calibration.height &&
std::isfinite(calibration.fx) && std::isfinite(calibration.fy) &&
std::isfinite(calibration.cx) && std::isfinite(calibration.cy) &&
std::isfinite(calibration.k1) && std::isfinite(calibration.k2) &&
std::isfinite(calibration.p1) && std::isfinite(calibration.p2) &&
calibration.fx > 0 && calibration.fy > 0;
}
struct ObservationKey {
uint64_t feature_set_id;
uint32_t feature_index;
bool operator<(const ObservationKey &other) const {
return feature_set_id < other.feature_set_id ||
(feature_set_id == other.feature_set_id &&
feature_index < other.feature_index);
}
};
struct ExportObservation {
const Lardon3DSparseIncrementalLandmarkObservation *snapshot;
const Lardon3DSparseIncrementalObservation *source;
uint32_t point_id;
double x;
double y;
};
bool finalize(std::ofstream &file) {
file.flush();
const bool written = file.good();
file.close();
return written && !file.fail();
}
bool valid_rotation(const double *r) {
if (!r) return false;
for (int i = 0; i < 9; ++i) if (!std::isfinite(r[i])) return false;
double residual = 0;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
double dot = 0;
for (int k = 0; k < 3; ++k) dot += r[row * 3 + k] * r[col * 3 + k];
residual = std::max(residual, std::fabs(dot - (row == col ? 1.0 : 0.0)));
}
}
const double det = r[0] * (r[4] * r[8] - r[5] * r[7]) -
r[1] * (r[3] * r[8] - r[5] * r[6]) +
r[2] * (r[3] * r[7] - r[4] * r[6]);
return residual <= 1e-6 && std::fabs(det - 1.0) <= 1e-6;
}
bool quaternion(const double *rotation, double q[4]) {
if (!rotation || !q || !valid_rotation(rotation)) return false;
const double trace = rotation[0] + rotation[4] + rotation[8];
double scale = 0;
if (trace > 0) {
scale = std::sqrt(trace + 1) * 2;
q[0] = 0.25 * scale;
q[1] = (rotation[7] - rotation[5]) / scale;
q[2] = (rotation[2] - rotation[6]) / scale;
q[3] = (rotation[3] - rotation[1]) / scale;
} else if (rotation[0] > rotation[4] && rotation[0] > rotation[8]) {
scale = std::sqrt(std::max(0.0, 1 + rotation[0] - rotation[4] - rotation[8])) * 2;
q[0] = (rotation[7] - rotation[5]) / scale;
q[1] = 0.25 * scale;
q[2] = (rotation[1] + rotation[3]) / scale;
q[3] = (rotation[2] + rotation[6]) / scale;
} else if (rotation[4] > rotation[8]) {
scale = std::sqrt(std::max(0.0, 1 + rotation[4] - rotation[0] - rotation[8])) * 2;
q[0] = (rotation[2] - rotation[6]) / scale;
q[1] = (rotation[1] + rotation[3]) / scale;
q[2] = 0.25 * scale;
q[3] = (rotation[5] + rotation[7]) / scale;
} else {
scale = std::sqrt(std::max(0.0, 1 + rotation[8] - rotation[0] - rotation[4])) * 2;
q[0] = (rotation[3] - rotation[1]) / scale;
q[1] = (rotation[2] + rotation[6]) / scale;
q[2] = (rotation[5] + rotation[7]) / scale;
q[3] = 0.25 * scale;
}
const double norm = std::sqrt(q[0] * q[0] + q[1] * q[1] +
q[2] * q[2] + q[3] * q[3]);
if (!(norm > 0) || !std::isfinite(norm)) return false;
for (size_t index = 0; index < 4; ++index) q[index] /= norm;
const bool negative = q[0] < 0 ||
(q[0] == 0 &&
(q[1] < 0 ||
(q[1] == 0 && (q[2] < 0 || (q[2] == 0 && q[3] < 0)))));
if (negative)
for (size_t index = 0; index < 4; ++index) q[index] = -q[index];
return std::all_of(q, q + 4, [](double value) { return std::isfinite(value); });
}
bool finite_point(const Lardon3DSparseGeometryPoint3 &point) {
return std::isfinite(point.x) && std::isfinite(point.y) &&
std::isfinite(point.z);
}
/* Two fixed-size CV_8UC3 image buffers cover decode plus undistortion. */
constexpr uint64_t kMaxImageDimension = 16384;
constexpr uint64_t kMaxImagePixels = 40000000;
constexpr uint64_t kMaxImageWorkingSetBytes = 240000000;
bool bounded_image_allocation(uint32_t width, uint32_t height) {
if (!width || !height || width > kMaxImageDimension ||
height > kMaxImageDimension || width > UINT64_MAX / height)
return false;
const uint64_t pixels = static_cast<uint64_t>(width) * height;
constexpr uint64_t bytes_per_pixel_for_two_images = 2U * 3U;
return pixels <= kMaxImagePixels &&
pixels <= kMaxImageWorkingSetBytes / bytes_per_pixel_for_two_images;
}
uint16_t be16(const unsigned char *p) {
return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8) |
static_cast<uint16_t>(p[1]));
}
uint32_t be32(const unsigned char *p) {
return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 |
uint32_t(p[2]) << 8 | p[3];
}
uint32_t le32(const unsigned char *p) {
return uint32_t(p[0]) | uint32_t(p[1]) << 8 |
uint32_t(p[2]) << 16 | uint32_t(p[3]) << 24;
}
bool encoded_image_dimensions(const char *path, uint32_t *width,
uint32_t *height) {
if (!path || !width || !height) return false;
const int descriptor = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
if (descriptor < 0) return false;
struct stat status {};
if (fstat(descriptor, &status) != 0 || !S_ISREG(status.st_mode)) {
(void)close(descriptor);
return false;
}
unsigned char data[64 * 1024];
size_t size = 0;
bool read_ok = true;
while (size < sizeof data) {
ssize_t amount;
do {
amount = read(descriptor, data + size, sizeof data - size);
} while (amount < 0 && errno == EINTR);
if (amount < 0) {
read_ok = false;
break;
}
if (amount == 0) break;
size += static_cast<size_t>(amount);
}
read_ok = close(descriptor) == 0 && read_ok;
if (!read_ok) return false;
if (size >= 24 && !std::memcmp(data, "\x89PNG\r\n\x1a\n", 8) &&
!std::memcmp(data + 12, "IHDR", 4)) {
*width = be32(data + 16); *height = be32(data + 20); return true;
}
if (size >= 26 && data[0] == 'B' && data[1] == 'M') {
*width = le32(data + 18);
const int32_t signed_height = static_cast<int32_t>(le32(data + 22));
if (signed_height == INT32_MIN) return false;
*height = static_cast<uint32_t>(std::abs(signed_height));
return true;
}
if (size >= 4 && data[0] == 0xff && data[1] == 0xd8) {
size_t offset = 2;
while (offset + 4 <= size) {
if (data[offset++] != 0xff) return false;
while (offset < size && data[offset] == 0xff) ++offset;
if (offset >= size) return false;
const unsigned char marker = data[offset++];
if (marker == 0xd8 || marker == 0xd9) continue;
if (offset + 2 > size) return false;
const uint16_t length = be16(data + offset);
if (length < 2 || offset + length > size) return false;
if ((marker >= 0xc0 && marker <= 0xc3) ||
(marker >= 0xc5 && marker <= 0xc7) ||
(marker >= 0xc9 && marker <= 0xcb) ||
(marker >= 0xcd && marker <= 0xcf)) {
if (length < 7) return false;
*height = be16(data + offset + 3); *width = be16(data + offset + 5);
return true;
}
offset += length;
}
}
return false;
}
Lardon3DDenseMvsStatus validate_snapshot(const Lardon3DDenseMvsInput &input) {
const auto &snapshot = *input.snapshot;
if (!input.source_observations ||
input.source_observation_count != snapshot.observation_count)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
if ((snapshot.camera_count && !snapshot.cameras) ||
(snapshot.landmark_count && !snapshot.landmarks) ||
(snapshot.observation_count && !snapshot.observations) ||
snapshot.camera_count == 0 || snapshot.landmark_count == 0 ||
snapshot.observation_count == 0 ||
snapshot.camera_count > UINT32_MAX ||
snapshot.landmark_count > UINT32_MAX)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
std::map<uint64_t, uint64_t> camera_components;
std::map<uint64_t, const Lardon3DSparseIncrementalLandmark *> landmarks;
std::map<ObservationKey, const Lardon3DSparseIncrementalObservation *> sources;
std::map<uint64_t, uint64_t> observed;
std::set<std::pair<uint64_t, uint32_t>> positions;
for (size_t index = 0; index < snapshot.camera_count; ++index) {
const auto &camera = snapshot.cameras[index];
if (!camera.image_id || !camera.component_key ||
!camera_components.emplace(camera.image_id, camera.component_key).second)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
for (double value : camera.pose_cw.translation_cw)
if (!std::isfinite(value)) return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
if (!valid_rotation(camera.pose_cw.rotation_cw))
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
}
for (size_t index = 0; index < snapshot.landmark_count; ++index) {
const auto &landmark = snapshot.landmarks[index];
if (!landmark.landmark_id || !landmark.track_id ||
landmark.observation_count == 0 || !finite_point(landmark.point) ||
!std::isfinite(landmark.reprojection_rmse_px) ||
!landmarks.emplace(landmark.landmark_id, &landmark).second)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
}
for (size_t index = 0; index < input.source_observation_count; ++index) {
const auto &observation = input.source_observations[index];
if (!observation.image_id || !observation.track_id ||
!observation.feature_set_id || !std::isfinite(observation.x) ||
!std::isfinite(observation.y) ||
!sources.emplace(ObservationKey{observation.feature_set_id,
observation.feature_index},
&observation)
.second)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
for (size_t index = 0; index < snapshot.observation_count; ++index) {
const auto &observation = snapshot.observations[index];
const auto landmark = landmarks.find(observation.landmark_id);
const auto camera = camera_components.find(observation.image_id);
const auto source = sources.find(
{observation.feature_set_id, observation.feature_index});
if (landmark == landmarks.end() || camera == camera_components.end() ||
observation.track_id != landmark->second->track_id ||
camera->second != landmark->second->component_key ||
observation.position_in_track >= landmark->second->observation_count ||
!positions.emplace(observation.track_id,
observation.position_in_track)
.second)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
if (source == sources.end() ||
source->second->image_id != observation.image_id ||
source->second->track_id != observation.track_id)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
++observed[observation.landmark_id];
}
for (const auto &entry : landmarks)
if (observed[entry.first] != entry.second->observation_count)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
return LARDON3D_DENSE_MVS_OK;
}
Lardon3DDenseMvsStatus validate_sources(const Lardon3DDenseMvsInput &input) {
const auto &snapshot = *input.snapshot;
if (!input.source_observations ||
input.source_observation_count != snapshot.observation_count ||
input.source_image_count != snapshot.camera_count)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
std::set<uint64_t> camera_ids;
for (size_t index = 0; index < snapshot.camera_count; ++index)
camera_ids.insert(snapshot.cameras[index].image_id);
std::set<uint64_t> source_ids;
for (size_t index = 0; index < input.source_image_count; ++index) {
const auto &image = input.source_images[index];
if (!image.image_id || !image.source_path || !*image.source_path ||
!finite_cal(image.calibration) ||
!bounded_image_allocation(image.calibration.width,
image.calibration.height) ||
!source_ids.insert(image.image_id).second)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
uint32_t encoded_width = 0, encoded_height = 0;
if (!encoded_image_dimensions(image.source_path, &encoded_width,
&encoded_height) ||
!bounded_image_allocation(encoded_width, encoded_height) ||
encoded_width != image.calibration.width ||
encoded_height != image.calibration.height)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
if (source_ids != camera_ids) return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
return LARDON3D_DENSE_MVS_OK;
}
Lardon3DDenseMvsStatus export_colmap(const Lardon3DDenseMvsInput *in,
const std::string &directory) {
std::vector<const Lardon3DSparseIncrementalCamera *> cameras;
for (size_t i = 0; i < in->snapshot->camera_count; ++i)
cameras.push_back(in->snapshot->cameras + i);
std::sort(cameras.begin(), cameras.end(), [](const auto *a, const auto *b) {
return a->image_id < b->image_id;
});
if (cameras.size() != in->source_image_count)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
std::vector<const Lardon3DSparseIncrementalLandmark *> landmarks;
for (size_t index = 0; index < in->snapshot->landmark_count; ++index)
landmarks.push_back(in->snapshot->landmarks + index);
std::sort(landmarks.begin(), landmarks.end(), [](const auto *a, const auto *b) {
return a->landmark_id < b->landmark_id;
});
std::map<uint64_t, uint32_t> point_ids;
std::map<ObservationKey, const Lardon3DSparseIncrementalObservation *> source_coordinates;
for (size_t index = 0; index < in->source_observation_count; ++index) {
const auto &observation = in->source_observations[index];
source_coordinates.emplace(
ObservationKey{observation.feature_set_id, observation.feature_index},
&observation);
}
for (size_t index = 0; index < landmarks.size(); ++index)
point_ids.emplace(landmarks[index]->landmark_id, static_cast<uint32_t>(index + 1));
std::map<uint64_t, std::vector<ExportObservation>> image_observations;
for (size_t index = 0; index < in->snapshot->observation_count; ++index) {
const auto *observation = in->snapshot->observations + index;
const auto coordinate = source_coordinates.find(
{observation->feature_set_id, observation->feature_index});
image_observations[observation->image_id].push_back(
{observation, coordinate->second, point_ids.at(observation->landmark_id), 0, 0});
}
for (auto &entry : image_observations)
std::sort(entry.second.begin(), entry.second.end(), [](const auto &a, const auto &b) {
if (a.point_id != b.point_id) return a.point_id < b.point_id;
if (a.snapshot->feature_set_id != b.snapshot->feature_set_id)
return a.snapshot->feature_set_id < b.snapshot->feature_set_id;
return a.snapshot->feature_index < b.snapshot->feature_index;
});
const std::string sparse_directory = directory + "/sparse";
const std::string image_directory = directory + "/images";
if ((mkdir(sparse_directory.c_str(), 0700) != 0 && errno != EEXIST) ||
(mkdir(image_directory.c_str(), 0700) != 0 && errno != EEXIST))
return LARDON3D_DENSE_MVS_IO_ERROR;
std::ofstream camera_file(sparse_directory + "/cameras.txt",
std::ios::out | std::ios::trunc);
std::ofstream image_file(sparse_directory + "/images.txt",
std::ios::out | std::ios::trunc);
std::ofstream point_file(sparse_directory + "/points3D.txt",
std::ios::out | std::ios::trunc);
if (!camera_file || !image_file || !point_file)
return LARDON3D_DENSE_MVS_IO_ERROR;
for (std::ofstream *file : {&camera_file, &image_file, &point_file}) {
file->imbue(std::locale::classic());
*file << std::scientific
<< std::setprecision(std::numeric_limits<double>::max_digits10 - 1);
}
std::map<uint32_t, std::vector<std::pair<uint32_t, size_t>>> tracks;
uint64_t id = 1;
for (const auto *camera : cameras) {
if (id > 1 && cameras[id - 2]->image_id == camera->image_id)
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
const Lardon3DDenseMvsSourceImage *image = nullptr;
for (size_t index = 0; index < in->source_image_count; ++index)
if (in->source_images[index].image_id == camera->image_id)
image = in->source_images + index;
if (!image || !finite_cal(image->calibration))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
double q[4];
if (!quaternion(camera->pose_cw.rotation_cw, q))
return LARDON3D_DENSE_MVS_INVALID_SNAPSHOT;
const auto &k = image->calibration;
if (!bounded_image_allocation(k.width, k.height))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
uint32_t encoded_width = 0, encoded_height = 0;
if (!encoded_image_dimensions(image->source_path, &encoded_width,
&encoded_height) ||
!bounded_image_allocation(encoded_width, encoded_height) ||
encoded_width != k.width || encoded_height != k.height)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
cv::Mat source_image;
try {
source_image = cv::imread(image->source_path, cv::IMREAD_COLOR);
} catch (const cv::Exception &error) {
if (error.code == cv::Error::StsNoMem) throw std::bad_alloc();
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
if (source_image.empty() || source_image.cols != static_cast<int>(k.width) ||
source_image.rows != static_cast<int>(k.height))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
const cv::Mat camera_matrix = cv::Mat(cv::Matx33d(
k.fx, 0, k.cx,
0, k.fy, k.cy,
0, 0, 1));
const cv::Mat distortion = cv::Mat(cv::Matx<double, 1, 4>(
k.k1, k.k2, k.p1, k.p2));
cv::Mat undistorted;
try {
cv::undistort(source_image, undistorted, camera_matrix, distortion,
camera_matrix);
} catch (const cv::Exception &error) {
if (error.code == cv::Error::StsNoMem) throw std::bad_alloc();
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
const std::string image_name = "image_" + std::to_string(id) + ".png";
try {
if (!cv::imwrite(image_directory + "/" + image_name, undistorted))
return LARDON3D_DENSE_MVS_IO_ERROR;
} catch (const cv::Exception &error) {
if (error.code == cv::Error::StsNoMem) throw std::bad_alloc();
return LARDON3D_DENSE_MVS_IO_ERROR;
}
auto &observations = image_observations[camera->image_id];
std::vector<cv::Point2d> distorted_points;
distorted_points.reserve(observations.size());
for (const auto &observation : observations)
distorted_points.emplace_back(observation.source->x, observation.source->y);
std::vector<cv::Point2d> undistorted_points;
if (!distorted_points.empty()) {
try {
cv::undistortPoints(distorted_points, undistorted_points, camera_matrix,
distortion, cv::noArray(), camera_matrix);
} catch (const cv::Exception &error) {
if (error.code == cv::Error::StsNoMem) throw std::bad_alloc();
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
}
for (size_t index = 0; index < observations.size(); ++index) {
observations[index].x = undistorted_points[index].x;
observations[index].y = undistorted_points[index].y;
if (!std::isfinite(observations[index].x) ||
!std::isfinite(observations[index].y))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
/* InterfaceCOLMAP subtracts 0.5 from COLMAP's principal point. */
camera_file << id << " PINHOLE " << k.width << ' ' << k.height << ' '
<< k.fx << ' ' << k.fy << ' ' << k.cx + 0.5 << ' '
<< k.cy + 0.5 << '\n';
image_file << id << ' ' << q[0] << ' ' << q[1] << ' ' << q[2] << ' ' << q[3]
<< ' ' << camera->pose_cw.translation_cw[0] << ' '
<< camera->pose_cw.translation_cw[1] << ' '
<< camera->pose_cw.translation_cw[2] << ' ' << id << ' '
<< image_name << '\n';
for (size_t index = 0; index < observations.size(); ++index) {
const auto &observation = observations[index];
image_file << observation.x << ' ' << observation.y << ' '
<< observation.point_id << ' ';
tracks[observation.point_id].emplace_back(static_cast<uint32_t>(id), index);
}
image_file << '\n';
if (!camera_file || !image_file) return LARDON3D_DENSE_MVS_IO_ERROR;
++id;
}
for (const auto *landmark : landmarks) {
const uint32_t point_id = point_ids.at(landmark->landmark_id);
point_file << point_id << ' ' << landmark->point.x << ' ' << landmark->point.y
<< ' ' << landmark->point.z << " 0 0 0 "
<< landmark->reprojection_rmse_px;
const auto track = tracks.find(point_id);
if (track != tracks.end())
for (const auto &member : track->second)
point_file << ' ' << member.first << ' ' << member.second;
point_file << '\n';
if (!point_file) return LARDON3D_DENSE_MVS_IO_ERROR;
}
if (!finalize(camera_file) || !finalize(image_file) || !finalize(point_file))
return LARDON3D_DENSE_MVS_IO_ERROR;
return LARDON3D_DENSE_MVS_OK;
}
bool bounded_line(std::ifstream &file, std::string &line, bool *had_newline,
size_t *raw_size) {
line.clear();
*raw_size = 0;
char value = 0;
while (file.get(value)) {
if (*raw_size == SIZE_MAX) return false;
++*raw_size;
if (value == '\n') {
*had_newline = true;
if (!line.empty() && line.back() == '\r') line.pop_back();
return line.find('\r') == std::string::npos;
}
if (line.size() == kMaxPlyLine) return false;
line.push_back(value);
}
*had_newline = false;
return file.eof() && !line.empty() && line.find('\r') == std::string::npos;
}
bool bounded_header_line(std::ifstream &file, std::string &line, size_t &total) {
bool had_newline = false;
size_t raw_size = 0;
if (!bounded_line(file, line, &had_newline, &raw_size)) return false;
if (total > kMaxPlyHeader || raw_size > kMaxPlyHeader - total) return false;
total += raw_size;
return true;
}
bool ply_data_line(std::ifstream &file, std::string &line) {
bool had_newline = false;
size_t raw_size = 0;
return bounded_line(file, line, &had_newline, &raw_size);
}
enum class PlyScalar {
I8, U8, I16, U16, I32, U32, F32, F64
};
struct PlyProperty {
std::string name;
PlyScalar scalar{};
PlyScalar count_scalar{};
size_t scalar_size = 0;
size_t count_size = 0;
bool list = false;
};
bool ply_integer(PlyScalar scalar) {
return scalar != PlyScalar::F32 && scalar != PlyScalar::F64;
}
bool ply_scalar(const std::string &name, PlyScalar *scalar, size_t *size) {
if (name == "char" || name == "int8") {
*scalar = PlyScalar::I8;
*size = 1;
} else if (name == "uchar" || name == "uint8") {
*scalar = PlyScalar::U8;
*size = 1;
} else if (name == "short" || name == "int16") {
*scalar = PlyScalar::I16;
*size = 2;
} else if (name == "ushort" || name == "uint16") {
*scalar = PlyScalar::U16;
*size = 2;
} else if (name == "int" || name == "int32") {
*scalar = PlyScalar::I32;
*size = 4;
} else if (name == "uint" || name == "uint32") {
*scalar = PlyScalar::U32;
*size = 4;
} else if (name == "float" || name == "float32") {
*scalar = PlyScalar::F32;
*size = 4;
} else if (name == "double" || name == "float64") {
*scalar = PlyScalar::F64;
*size = 8;
} else {
return false;
}
return true;
}
bool read_little_endian(std::ifstream &file, PlyScalar scalar, double *value) {
size_t size = 0;
PlyScalar ignored{};
const char *name = scalar == PlyScalar::I8 ? "int8" :
scalar == PlyScalar::U8 ? "uint8" :
scalar == PlyScalar::I16 ? "int16" :
scalar == PlyScalar::U16 ? "uint16" :
scalar == PlyScalar::I32 ? "int32" :
scalar == PlyScalar::U32 ? "uint32" :
scalar == PlyScalar::F32 ? "float32" : "float64";
if (!ply_scalar(name, &ignored, &size)) return false;
unsigned char bytes[8]{};
if (!file.read(reinterpret_cast<char *>(bytes), static_cast<std::streamsize>(size)))
return false;
uint64_t bits = 0;
for (size_t index = 0; index < size; ++index)
bits |= static_cast<uint64_t>(bytes[index]) << (index * 8U);
switch (scalar) {
case PlyScalar::I8:
*value = static_cast<int8_t>(bytes[0]);
break;
case PlyScalar::U8:
*value = bytes[0];
break;
case PlyScalar::I16:
*value = static_cast<int16_t>(static_cast<uint16_t>(bits));
break;
case PlyScalar::U16:
*value = static_cast<uint16_t>(bits);
break;
case PlyScalar::I32:
*value = static_cast<int32_t>(static_cast<uint32_t>(bits));
break;
case PlyScalar::U32:
*value = static_cast<uint32_t>(bits);
break;
case PlyScalar::F32: {
const uint32_t raw = static_cast<uint32_t>(bits);
float decoded = 0;
std::memcpy(&decoded, &raw, sizeof decoded);
*value = decoded;
break;
}
case PlyScalar::F64: {
double decoded = 0;
std::memcpy(&decoded, &bits, sizeof decoded);
*value = decoded;
break;
}
}
return true;
}
bool ply(const std::string &path, uint64_t *point_count) {
struct stat status {};
if (!point_count || stat(path.c_str(), &status) || !S_ISREG(status.st_mode) ||
status.st_size <= 0) return false;
std::ifstream file(path, std::ios::in | std::ios::binary);
std::string line;
size_t header_size = 0;
if (!bounded_header_line(file, line, header_size) || line != "ply" ||
!bounded_header_line(file, line, header_size)) return false;
const bool ascii = line == "format ascii 1.0";
const bool binary_little_endian = line == "format binary_little_endian 1.0";
if (!ascii && !binary_little_endian) return false;
bool vertex_element = false;
bool header_complete = false;
uint64_t vertices = 0;
std::vector<std::vector<std::string>> properties;
while (bounded_header_line(file, line, header_size)) {
if (line == "end_header") { header_complete = true; break; }
std::istringstream fields(line);
fields.imbue(std::locale::classic());
std::string kind, type, name;
if (!(fields >> kind)) return false;
if (kind == "comment" || kind == "obj_info") continue;
if (kind == "element") {
if (vertex_element || !(fields >> type >> name) || type != "vertex") return false;
char *end = nullptr;
errno = 0;
const unsigned long long parsed = strtoull(name.c_str(), &end, 10);
if (errno || !end || *end || parsed == 0) return false;
vertices = parsed;
vertex_element = true;
continue;
}
if (kind == "property") {
if (!vertex_element || !(fields >> type) || properties.size() >= kMaxPlyProperties)
return false;
if (type == "list") {
std::string count_type, item_type;
if (ascii || !(fields >> count_type >> item_type >> name)) return false;
properties.push_back({type, count_type, item_type, name});
} else {
if (!(fields >> name)) return false;
properties.push_back({type, name});
}
continue;
}
return false;
}
int xi = -1, yi = -1, zi = -1;
std::vector<PlyProperty> decoded_properties;
for (size_t i = 0; i < properties.size(); ++i) {
PlyProperty property;
property.list = properties[i][0] == "list";
property.name = property.list ? properties[i][3] : properties[i][1];
const std::string &item_type = property.list ? properties[i][2] : properties[i][0];
if (!ply_scalar(item_type, &property.scalar, &property.scalar_size)) return false;
if (property.list &&
(!ply_scalar(properties[i][1], &property.count_scalar, &property.count_size) ||
!ply_integer(property.count_scalar)))
return false;
decoded_properties.push_back(property);
int *index = property.name == "x" ? &xi : property.name == "y" ? &yi :
property.name == "z" ? &zi : nullptr;
if (index) {
if (*index >= 0 || property.list ||
(item_type != "float" && item_type != "float32" && item_type != "double" &&
item_type != "float64"))
return false;
*index = static_cast<int>(i);
}
}
if (!header_complete || !vertex_element || xi < 0 || yi < 0 || zi < 0 ||
properties.empty()) return false;
uint64_t remaining = 0;
if (binary_little_endian) {
const std::streampos body_offset = file.tellg();
if (body_offset < 0 || static_cast<uint64_t>(body_offset) >
static_cast<uint64_t>(status.st_size))
return false;
remaining = static_cast<uint64_t>(status.st_size) - static_cast<uint64_t>(body_offset);
uint64_t minimum_vertex_size = 0;
for (const PlyProperty &property : decoded_properties) {
const size_t property_minimum = property.list ? property.count_size : property.scalar_size;
if (property_minimum > UINT64_MAX - minimum_vertex_size) return false;
minimum_vertex_size += property_minimum;
}
if (minimum_vertex_size == 0 || vertices > remaining / minimum_vertex_size) return false;
}
for (uint64_t vertex = 0; vertex < vertices; ++vertex) {
if (ascii) {
if (!ply_data_line(file, line)) return false;
std::istringstream fields(line);
fields.imbue(std::locale::classic());
for (size_t index = 0; index < decoded_properties.size(); ++index) {
double value = 0;
if (!(fields >> value) || !std::isfinite(value))
return false;
}
std::string extra;
if (fields >> extra) return false;
} else {
for (size_t index = 0; index < decoded_properties.size(); ++index) {
const PlyProperty &property = decoded_properties[index];
if (property.list) {
double count_value = 0;
if (property.count_size > remaining ||
!read_little_endian(file, property.count_scalar, &count_value) ||
count_value < 0)
return false;
remaining -= property.count_size;
const uint64_t count = static_cast<uint64_t>(count_value);
if (count > remaining / property.scalar_size) return false;
const uint64_t bytes = count * property.scalar_size;
if (bytes > static_cast<uint64_t>(std::numeric_limits<std::streamoff>::max()) ||
!file.seekg(static_cast<std::streamoff>(bytes), std::ios::cur))
return false;
remaining -= bytes;
continue;
}
double value = 0;
if (property.scalar_size > remaining ||
!read_little_endian(file, property.scalar, &value) ||
((static_cast<int>(index) == xi || static_cast<int>(index) == yi ||
static_cast<int>(index) == zi) && !std::isfinite(value)))
return false;
remaining -= property.scalar_size;
}
}
}
if (binary_little_endian)
return remaining == 0 && file.peek() == std::char_traits<char>::eof() &&
(*point_count = vertices, true);
char trailing;
bool pending_cr = false;
while (file.get(trailing)) {
if (pending_cr) {
if (trailing != '\n') return false;
pending_cr = false;
} else if (trailing == '\r') {
pending_cr = true;
} else if (!std::isspace(static_cast<unsigned char>(trailing))) {
return false;
}
}
if (pending_cr) return false;
*point_count = vertices;
return true;
}
Lardon3DDenseMvsStatus run_impl(const Lardon3DDenseMvsInput *input,
Lardon3DDenseMvsResult *result) {
if (!input || !result || !input->snapshot || !input->source_images ||
!input->source_image_count || !input->staging_directory ||
!input->execution_thread_count || !input->interface_colmap_executable ||
!input->densify_point_cloud_executable)
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
if (input->parameters.fusion_mode != 0)
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
unsigned char source_identity[32], calibration_binding_identity[32];
unsigned char parameter_fingerprint[32], backend_identity[32];
if (!lardon3d_dense_mvs_parameter_fingerprint(
&input->parameters, parameter_fingerprint))
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
if (!lardon3d::dense_mvs_detail::source_image_set_identity(
input->source_images, input->source_image_count, source_identity))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
if (!lardon3d::dense_mvs_detail::calibration_binding_identity(
input->source_images, input->source_image_count,
calibration_binding_identity))
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
const Lardon3DDenseMvsStatus snapshot_status = validate_snapshot(*input);
if (snapshot_status != LARDON3D_DENSE_MVS_OK) return snapshot_status;
const Lardon3DDenseMvsStatus source_status = validate_sources(*input);
if (source_status != LARDON3D_DENSE_MVS_OK) return source_status;
for (size_t index = 0; index < input->source_image_count; ++index) {
unsigned char actual[32];
const auto &image = input->source_images[index];
if (!sha_regular_file(image.source_path, actual, kMaxSourceFileBytes) ||
std::memcmp(actual, image.immutable_sha256, sizeof actual) != 0)
return LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
}
if (!executable(input->interface_colmap_executable) ||
!executable(input->densify_point_cloud_executable))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
BackendCapabilities interface_capabilities{};
BackendCapabilities densify_capabilities{};
if (!probe(input->interface_colmap_executable, true, &interface_capabilities) ||
!probe(input->densify_point_cloud_executable, false, &densify_capabilities))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
Lardon3DDenseMvsBackendManifest manifest{};
uint64_t remaining_backend_hash_bytes = kMaxBackendFileBytes;
if (!sha_regular_file(input->interface_colmap_executable,
manifest.interface_colmap_binary_sha256,
kMaxBackendFileBytes, &remaining_backend_hash_bytes) ||
!sha_regular_file(input->densify_point_cloud_executable,
manifest.densify_point_cloud_binary_sha256,
kMaxBackendFileBytes, &remaining_backend_hash_bytes))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
std::memcpy(manifest.interface_colmap_version_identity,
interface_capabilities.version_identity, 32);
std::memcpy(manifest.densify_point_cloud_version_identity,
densify_capabilities.version_identity, 32);
if (!lardon3d_dense_mvs_backend_manifest_digest(&manifest, backend_identity))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
Lardon3DDenseMvsIdentity identity{};
std::memcpy(identity.base_reconstruction_identity,
input->base_reconstruction_identity, 32);
std::memcpy(identity.source_image_set_identity, source_identity, 32);
std::memcpy(identity.calibration_scope_identity,
input->calibration_scope_identity, 32);
std::memcpy(identity.calibration_binding_identity,
calibration_binding_identity, 32);
identity.dense_kind = identity.backend_kind = LARDON3D_DENSE_MVS_KIND_OPENMVS;
identity.dense_version = identity.backend_version = LARDON3D_DENSE_MVS_VERSION;
std::memcpy(identity.backend_binary_sha256, backend_identity, 32);
std::memcpy(identity.parameter_fingerprint, parameter_fingerprint, 32);
unsigned char dense_identity[32];
if (!lardon3d_dense_mvs_identity_digest(&identity, dense_identity))
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
const std::string staging(input->staging_directory);
constexpr char workspace_suffix[] = "/lardon3d-mvs-XXXXXX";
constexpr char cloud_suffix[] = "/dense.ply";
if (staging.size() > SIZE_MAX - sizeof workspace_suffix ||
staging.size() + sizeof workspace_suffix - 1 >
SIZE_MAX - sizeof cloud_suffix ||
staging.size() + sizeof workspace_suffix - 1 + sizeof cloud_suffix - 1 >=
sizeof result->point_cloud_path)
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
if (mkdir(staging.c_str(), 0700) != 0 && errno != EEXIST)
return LARDON3D_DENSE_MVS_IO_ERROR;
std::vector<char> workspace_template(staging.begin(), staging.end());
workspace_template.insert(workspace_template.end(), workspace_suffix,
workspace_suffix + sizeof workspace_suffix);
char *created = mkdtemp(workspace_template.data());
if (!created) return LARDON3D_DENSE_MVS_IO_ERROR;
const std::string root(created);
const std::string workspace = root + "/colmap";
const std::string scene = root + "/scene.mvs";
const std::string cloud_stem = root + "/dense";
const std::string cloud = cloud_stem + ".ply";
if (mkdir(workspace.c_str(), 0700) != 0)
return LARDON3D_DENSE_MVS_IO_ERROR;
const Lardon3DDenseMvsStatus export_status = export_colmap(input, workspace);
if (export_status != LARDON3D_DENSE_MVS_OK) return export_status;
if (!run({input->interface_colmap_executable, "-i", workspace, "-o", scene,
"--max-threads", std::to_string(input->execution_thread_count)},
root, "interface-colmap.log"))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
std::vector<std::string> densify_arguments = {
input->densify_point_cloud_executable,
scene,
"-o",
cloud_stem,
"--resolution-level",
std::to_string(input->parameters.resolution_level),
"--min-resolution",
std::to_string(input->parameters.minimum_resolution),
"--number-views",
std::to_string(input->parameters.number_views),
"--fusion-mode",
std::to_string(input->parameters.fusion_mode),
"--max-threads",
std::to_string(input->execution_thread_count),
};
if (densify_capabilities.cuda_option) {
densify_arguments.emplace_back("--cuda-device");
densify_arguments.emplace_back("-2");
}
if (!run(densify_arguments, root, "densify-point-cloud.log"))
return LARDON3D_DENSE_MVS_BACKEND_ERROR;
if (!ply(cloud, &result->point_count)) return LARDON3D_DENSE_MVS_INVALID_OUTPUT;
std::memcpy(result->dense_identity, dense_identity, 32);
std::memcpy(result->parameter_fingerprint, parameter_fingerprint, 32);
std::memcpy(result->base_reconstruction_identity,
input->base_reconstruction_identity, 32);
std::memcpy(result->source_image_set_identity, source_identity, 32);
std::memcpy(result->backend_implementation_sha256, backend_identity, 32);
const int copied = std::snprintf(result->point_cloud_path,
sizeof result->point_cloud_path, "%s", cloud.c_str());
if (copied < 0 || static_cast<size_t>(copied) >= sizeof result->point_cloud_path)
return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
return LARDON3D_DENSE_MVS_OK;
}
}
extern "C" Lardon3DDenseMvsStatus lardon3d_dense_mvs_run(
const Lardon3DDenseMvsInput *input, Lardon3DDenseMvsResult *result) {
if (!result) return LARDON3D_DENSE_MVS_INVALID_ARGUMENT;
Lardon3DDenseMvsResult candidate{};
Lardon3DDenseMvsStatus status;
try {
status = run_impl(input, &candidate);
} catch (const std::bad_alloc &) {
status = LARDON3D_DENSE_MVS_OUT_OF_MEMORY;
} catch (const cv::Exception &error) {
status = error.code == cv::Error::StsNoMem
? LARDON3D_DENSE_MVS_OUT_OF_MEMORY
: LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE;
} catch (...) {
status = LARDON3D_DENSE_MVS_IO_ERROR;
}
if (status == LARDON3D_DENSE_MVS_OK) {
candidate.status = status;
*result = candidate;
} else {
std::memset(result, 0, sizeof *result);
result->status = status;
}
return status;
}
extern "C" void lardon3d_dense_mvs_result_destroy(Lardon3DDenseMvsResult *result) {
if (result) std::memset(result, 0, sizeof *result);
}