267 lines
10 KiB
C++
267 lines
10 KiB
C++
|
|
#include <lardon3d/calibration_workflow.h>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
#define CHECK(x) do { if(!(x)){ std::fprintf(stderr,"materialize failure %d: %s\n",__LINE__,#x); return false; } } while(0)
|
|
|
|
namespace {
|
|
|
|
std::string hex_repeat(char c) { return std::string(64, c); }
|
|
|
|
std::string hf(double value) {
|
|
std::ostringstream o;
|
|
o.imbue(std::locale::classic());
|
|
o << std::hexfloat << value;
|
|
return o.str();
|
|
}
|
|
|
|
bool write_text(const std::filesystem::path& path, const std::string& text) {
|
|
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
|
out << text;
|
|
return static_cast<bool>(out);
|
|
}
|
|
|
|
bool sha_file(const std::filesystem::path& path, unsigned char out[32]) {
|
|
std::ifstream in(path, std::ios::binary);
|
|
std::ostringstream bytes;
|
|
bytes << in.rdbuf();
|
|
const std::string data = bytes.str();
|
|
unsigned int n = 0;
|
|
return in && EVP_Digest(data.data(), data.size(), out, &n, EVP_sha256(), nullptr) == 1 &&
|
|
n == 32;
|
|
}
|
|
|
|
std::string sha_file_hex(const std::filesystem::path& path) {
|
|
unsigned char value[32]{};
|
|
if (!sha_file(path, value)) return {};
|
|
static const char digits[] = "0123456789abcdef";
|
|
std::string out(64, '0');
|
|
for (size_t i = 0; i < 32; ++i) {
|
|
out[2 * i] = digits[value[i] >> 4];
|
|
out[2 * i + 1] = digits[value[i] & 15];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
struct Fixture {
|
|
std::filesystem::path root, session, detection, solve, evidence, producer, campaign;
|
|
Lardon3DCalibrationWorkflowInputFiles files{};
|
|
};
|
|
|
|
std::string coordinate_lines(const std::string& sha, double distance, unsigned band) {
|
|
static const std::array<const char*, 9> labels = {
|
|
"center","top","right","bottom","left","top_left","top_right","bottom_left","bottom_right"};
|
|
std::ostringstream s;
|
|
s << "pre_solve " << sha << " 0.1\n"
|
|
<< "clipping " << sha << " 0\n"
|
|
<< "coordinate " << sha << " qualified_decoder 1 0 1000 800 20 0 0\n";
|
|
for (int i = 0; i < 20; ++i)
|
|
s << "coordinate_point " << sha << " " << labels[static_cast<size_t>(i)%labels.size()]
|
|
<< " " << i << " " << i+1 << " " << i << " " << i+1 << "\n";
|
|
s << "distance " << sha << " " << std::setprecision(17)
|
|
<< std::defaultfloat << distance << " " << band << "\n";
|
|
return s.str();
|
|
}
|
|
|
|
std::string detection_view(const std::string& sha, unsigned region, unsigned band,
|
|
double distance, bool last) {
|
|
std::ostringstream s;
|
|
s << "{\"source_sha256\":\"" << sha
|
|
<< "\",\"orientation\":0,\"oriented_width\":1000,\"oriented_height\":800"
|
|
<< ",\"decision\":\"accepted\",\"reason\":\"-\""
|
|
<< ",\"frame_region\":" << region
|
|
<< ",\"distance_band\":" << band
|
|
<< ",\"holdout\":false"
|
|
<< ",\"target_occupancy\":\"" << hf(.3)
|
|
<< "\",\"normal_angle_degrees\":\"" << hf(25.0)
|
|
<< "\",\"measured_distance_metres\":\"" << hf(distance)
|
|
<< "\",\"pre_solve_corner_rms_px\":\"" << hf(.1)
|
|
<< "\",\"clipping_fraction\":\"" << hf(0.0)
|
|
<< "\",\"physical_target_quadrants\":3"
|
|
<< ",\"target_corner_quadrant_mask\":7"
|
|
<< ",\"corner_count\":20,\"residual_count\":20,\"high_residual_count\":0"
|
|
<< ",\"reprojection_rmse_px\":\"" << hf(.1)
|
|
<< "\",\"maximum_residual_px\":\"" << hf(.2)
|
|
<< "\",\"coordinate_equivalence\":{\"comparison_points\":20"
|
|
<< ",\"max_abs_dx_px\":\"" << hf(0.0)
|
|
<< "\",\"max_abs_dy_px\":\"" << hf(0.0)
|
|
<< "\",\"pass\":true},\"corners\":[";
|
|
for (int i = 0; i < 20; ++i) {
|
|
if (i) s << ",";
|
|
s << "{\"id\":" << i << ",\"x\":\"" << hf(100.0+i)
|
|
<< "\",\"y\":\"" << hf(200.0+i) << "\"}";
|
|
}
|
|
s << "]}" << (last ? "\n" : " ,\n");
|
|
return s.str();
|
|
}
|
|
|
|
std::string params(double first = 1000.0) {
|
|
const double p[8] = {first,1001,500,400,.01,-.01,.001,-.001};
|
|
std::ostringstream s;
|
|
s << "[";
|
|
for (int i = 0; i < 8; ++i) {
|
|
if (i) s << ",";
|
|
s << "\"" << hf(p[i]) << "\"";
|
|
}
|
|
s << "]";
|
|
return s.str();
|
|
}
|
|
|
|
std::string pose() {
|
|
return "{\"rvec\":[\"" + hf(0) + "\",\"" + hf(0) + "\",\"" + hf(0) +
|
|
"\"],\"tvec_m\":[\"" + hf(0) + "\",\"" + hf(0) + "\",\"" + hf(1) + "\"]}";
|
|
}
|
|
|
|
std::string solve_text(bool mismatch) {
|
|
std::ostringstream s;
|
|
s << "{\n\"format\":\"L3DCAL_SOLVE_V1\",\n\"runs\":[\n";
|
|
for (int run = 0; run < 3; ++run) {
|
|
s << "{\"run\":" << run << ",\"params\":" << params(mismatch && run==1 ? 999.0 : 1000.0)
|
|
<< ",\"opencv_rms_px\":\"" << hf(.1) << "\",\"poses\":["
|
|
<< pose() << "," << pose() << "]}" << (run==2 ? "\n" : " ,\n");
|
|
}
|
|
s << "],\n\"fit_params\":" << params() << "\n}\n";
|
|
return s.str();
|
|
}
|
|
|
|
std::string evidence_text(const std::string& optical,
|
|
const std::string& generator,
|
|
const std::string& planarity,
|
|
const std::string& a,
|
|
const std::string& b) {
|
|
std::ostringstream s;
|
|
s << "{\n\"format\":\"L3DCAL_EVIDENCE_BUNDLE_V1\",\n"
|
|
<< "\"target\":{\"id\":\"board\",\"generator_sha256\":\"" << generator
|
|
<< "\",\"instrument\":\"caliper\",\"resolution_mm\":\"" << hf(.1)
|
|
<< "\",\"planarity_evidence_sha256\":\"" << planarity << "\"},\n"
|
|
<< "\"optical_sha256\":\"" << optical << "\",\n"
|
|
<< "\"optical_state\":\"body_objective_zoom_focus_stabilization_format_pipeline\",\n"
|
|
<< "\"validation_flags\":\"0xf\",\n"
|
|
<< "\"global_rmse_px\":\"" << hf(.1)
|
|
<< "\",\"maximum_residual_px\":\"" << hf(.2)
|
|
<< "\",\"high_residual_fraction\":\"" << hf(0.0) << "\",\n"
|
|
<< "\"holdout\":{\"rmse_px\":\"" << hf(.1)
|
|
<< "\",\"maximum_px\":\"" << hf(.2) << "\"},\n"
|
|
<< "\"maximum_parameter_delta_px\":\"" << hf(0.0) << "\",\n"
|
|
<< "\"deterministic_full_solve_equality\":true,\n\"residuals\":[";
|
|
bool first = true;
|
|
for (const std::string* sha : {&a, &b}) {
|
|
for (int i = 0; i < 20; ++i) {
|
|
if (!first) s << ",";
|
|
first = false;
|
|
s << "{\"source_sha256\":\"" << *sha << "\",\"corner_id\":" << i
|
|
<< ",\"dx_px\":\"" << hf(.1) << "\",\"dy_px\":\"" << hf(0.0)
|
|
<< "\",\"rmse_px\":\"" << hf(.1) << "\"}";
|
|
}
|
|
}
|
|
s << "]\n}\n";
|
|
return s.str();
|
|
}
|
|
|
|
bool make_fixture(Fixture *f, bool mismatch_solve = false) {
|
|
char temp[]="/tmp/lardon3d-materialize-XXXXXX";
|
|
char *r=mkdtemp(temp); if(!r) return false;
|
|
f->root=r; f->session=f->root/"session"; f->detection=f->root/"detection";
|
|
f->solve=f->root/"solve"; f->evidence=f->root/"evidence";
|
|
f->producer=f->root/"producer"; f->campaign=f->root/"campaign";
|
|
const std::string optical=hex_repeat('1'), generator=hex_repeat('2'),
|
|
planarity=hex_repeat('3'), a=hex_repeat('4'), b=hex_repeat('5');
|
|
std::ostringstream session;
|
|
session << "L3DCAL_SESSION_V1\n"
|
|
<< "target board " << generator << " DICT_5X5_100 9 7 30 21\n"
|
|
<< "measurement caliper 0.1 30 30 30 30 30 30 30 30 30 30\n"
|
|
<< "white_border 30\n"
|
|
<< "planarity PASS " << planarity << "\n"
|
|
<< "decoder qualified_decoder 1\n"
|
|
<< "optical_state " << optical << " body_objective_zoom_focus_stabilization_format_pipeline\n"
|
|
<< "image /a " << a << " 0\n"
|
|
<< "image /b " << b << " 0\n"
|
|
<< coordinate_lines(a,.3,0) << coordinate_lines(b,.5,1);
|
|
if(!write_text(f->session,session.str())) return false;
|
|
if(!write_text(f->detection,
|
|
"{\n\"format\":\"L3DCAL_DETECTION_V1\",\n\"decoder\":\"qualified_decoder\",\n"
|
|
"\"decoder_version\":\"1\",\n\"views\":[\n" +
|
|
detection_view(a,0,0,.3,false)+detection_view(b,1,1,.5,true)+"]\n}\n")) return false;
|
|
if(!write_text(f->solve,solve_text(mismatch_solve))) return false;
|
|
if(!write_text(f->evidence,evidence_text(optical,generator,planarity,a,b))) return false;
|
|
const std::string session_sha = sha_file_hex(f->session);
|
|
if (session_sha.empty()) return false;
|
|
if(!write_text(f->producer,
|
|
"{\n\"format\":\"L3DCAL_PRODUCER_V1\",\n"
|
|
"\"solver_executable_sha256\":\"" + hex_repeat('a') + "\",\n"
|
|
"\"solver_configuration_sha256\":\"" + hex_repeat('b') + "\",\n"
|
|
"\"session_sha256\":\"" + session_sha + "\",\n"
|
|
"\"opencv_version\":\"5.0.0\",\n"
|
|
"\"opencv_build_sha256\":\"" + hex_repeat('c') + "\",\n"
|
|
"\"threads\":1,\n\"rng_seed\":1278432342,\n"
|
|
"\"optical_sha256\":\"" + optical + "\"\n}\n")) return false;
|
|
if(!write_text(f->campaign,
|
|
"L3DCAL_CAMPAIGN_STATE_V1\n"
|
|
"execution 42\n"
|
|
"optical_configuration 7\n"
|
|
"optical_state " + optical +
|
|
" body_objective_zoom_focus_stabilization_format_pipeline\n"
|
|
"capture 0 101\ncapture 1 102\n")) return false;
|
|
|
|
f->files={f->session.c_str(),f->detection.c_str(),f->solve.c_str(),
|
|
f->evidence.c_str(),f->producer.c_str(),f->campaign.c_str()};
|
|
return true;
|
|
}
|
|
|
|
bool valid() {
|
|
Fixture f; CHECK(make_fixture(&f));
|
|
Lardon3DCalibrationToolingView views[2]{};
|
|
Lardon3DCalibrationToolingCoordinateCheck checks[40]{};
|
|
Lardon3DCalibrationWorkflowExternalEvidence out{};
|
|
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
|
|
&f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_OK);
|
|
CHECK(out.view_count==2 && out.coordinate_check_count==40);
|
|
CHECK(out.support_images==2 && out.support_observations==40);
|
|
CHECK(out.validation_flags==15 && out.target_white_border_mm==30.0);
|
|
CHECK(out.repeated_parameters[0][0]==1000.0 && out.fit_parameters[0]==1000.0);
|
|
CHECK(views[0].accepted==1 && views[0].corner_count==20 &&
|
|
views[1].distance_band==1);
|
|
CHECK(checks[0].dx_px==0.0 && checks[0].dy_px==0.0);
|
|
unsigned char session_sha[32]{};
|
|
CHECK(sha_file(f.session, session_sha));
|
|
CHECK(std::memcmp(out.initialization_evidence_sha256, session_sha, 32) == 0);
|
|
std::filesystem::remove_all(f.root);
|
|
return true;
|
|
}
|
|
|
|
bool capacity_and_mismatch() {
|
|
Fixture f; CHECK(make_fixture(&f));
|
|
Lardon3DCalibrationToolingView views[2]{};
|
|
Lardon3DCalibrationToolingCoordinateCheck checks[40]{};
|
|
Lardon3DCalibrationWorkflowExternalEvidence out{};
|
|
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
|
|
&f.files,views,1,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_CAPACITY);
|
|
std::filesystem::remove_all(f.root);
|
|
|
|
CHECK(make_fixture(&f,true));
|
|
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
|
|
&f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
|
|
std::filesystem::remove_all(f.root);
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(){
|
|
if(!valid()||!capacity_and_mismatch()) return 1;
|
|
std::puts("CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS");
|
|
return 0;
|
|
}
|