diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index 636d84a..edce667 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -525,6 +525,8 @@ The solver session contract now also requires explicit measured `white_border >= `CALIBRATION_SOLVER_PER_VIEW_EVIDENCE_V1=PASS/FROZEN`: `detection.json` now retains the exact per-view Science v1 classifications and residual-support evidence required by the future workflow coordinator; downstream code must consume rather than recompute them. +`CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN`: retained per-view fields are now present in the actual solver output, `producer.json` is valid canonical JSON, solver-configuration records contain real line feeds, and the deterministic self-test checks these contents rather than byte identity alone. + A bounded Tooling correction aligned planarity handling with Calibration Science v1: Science v1 defines a categorical physical planarity attestation, not a numeric flatness threshold. Tooling therefore rejects invented finite `target_flatness_mm` values. `L3DCALB1` v1 and Calibration diff --git a/prompt/02_CURRENT_FROZEN_STATE.md b/prompt/02_CURRENT_FROZEN_STATE.md index efa7ff8..04e33b7 100644 --- a/prompt/02_CURRENT_FROZEN_STATE.md +++ b/prompt/02_CURRENT_FROZEN_STATE.md @@ -30,6 +30,7 @@ Calibration Evidence Solver v1 IMPLEMENTED/VALIDATED Calibration solver white-border evidence PASS/FROZEN Calibration solver producer identity PASS/FROZEN Calibration solver per-view evidence PASS/FROZEN +Calibration solver bundle repair PASS/FROZEN Calibration Tooling planarity alignment PASS/FROZEN ``` diff --git a/prompt/12_CALIBRATION.md b/prompt/12_CALIBRATION.md index 40916ac..039abe3 100644 --- a/prompt/12_CALIBRATION.md +++ b/prompt/12_CALIBRATION.md @@ -11,6 +11,7 @@ CALIBRATION_EVIDENCE_SOLVER_V1=IMPLEMENTED/VALIDATED CALIBRATION_SOLVER_WHITE_BORDER_V1=PASS/FROZEN CALIBRATION_SOLVER_PRODUCER_IDENTITY_V1=PASS/FROZEN CALIBRATION_SOLVER_PER_VIEW_EVIDENCE_V1=PASS/FROZEN +CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN CALIBRATION_WORKFLOW=IN_PROGRESS CURRENT_CALIBRATION_NEXT=WORKFLOW_COORDINATOR ``` diff --git a/tools/calibration_evidence_solver/calibration_evidence_solver b/tools/calibration_evidence_solver/calibration_evidence_solver index 8dd3539..79ab57b 100755 Binary files a/tools/calibration_evidence_solver/calibration_evidence_solver and b/tools/calibration_evidence_solver/calibration_evidence_solver differ diff --git a/tools/calibration_evidence_solver/calibration_evidence_solver.cpp b/tools/calibration_evidence_solver/calibration_evidence_solver.cpp index 34ca562..c4b23f3 100644 --- a/tools/calibration_evidence_solver/calibration_evidence_solver.cpp +++ b/tools/calibration_evidence_solver/calibration_evidence_solver.cpp @@ -75,6 +75,7 @@ struct View { std::vector image; std::vector object; std::vector residuals; + size_t high_residual_count = 0; double occupancy = 0, angle = 0, distance = 0, rmse = 0, max_residual = 0; double image_conversion_max = 0, object_conversion_max = 0; double pre_solve_corner_rms = -1, clipping_fraction = -1; @@ -231,9 +232,9 @@ bool residuals(std::vector* views,const std::vector& indices, std::string* why) { long double sum=0; size_t n=0; *maximum=0; *high=0; if(s.rvec.size()!=indices.size()||s.tvec.size()!=indices.size()){*why="pose count";return false;} - for(size_t j=0;j1.0)++*high;sum+=static_cast(d)*d;++n;} + if(!finite(e.x)||!finite(e.y)){*why="nonfinite residual";return false;}const double d=cv::norm(e);v.residuals.push_back(e);v.rmse+=d*d;v.max_residual=std::max(v.max_residual,d);*maximum=std::max(*maximum,d);if(d>1.0){++v.high_residual_count;++*high;}sum+=static_cast(d)*d;++n;} if(v.object.empty()){*why="empty view";return false;}v.rmse=std::sqrt(v.rmse/static_cast(v.object.size()));} if(n==0){*why="no residuals";return false;}*count=n;*rms=std::sqrt(static_cast(sum/static_cast(n)));return true; } @@ -251,19 +252,20 @@ bool exact_solve(const Solve& a, const Solve& b) { } std::string number(double x) { std::ostringstream o; o.imbue(std::locale::classic()); o<=.135?1:0)+(p.y>=.105?2:0)]=1;d<<(q[0]+q[1]+q[2]+q[3])<<",\"coordinate_equivalence\":{\"comparison_points\":"<( + (point.x >= .135 ? 1 : 0) + (point.y >= .105 ? 2 : 0)); + target_mask |= 1u << quadrant; + } + unsigned target_quadrants = 0; + for (unsigned bit = 0; bit < 4; ++bit) { + target_quadrants += (target_mask >> bit) & 1u; + } + + d << "{\"source_sha256\":\"" << hex(v.hash) + << "\",\"orientation\":" << v.orientation + << ",\"oriented_width\":" << v.width + << ",\"oriented_height\":" << v.height + << ",\"decision\":\"" << (v.accepted ? "accepted" : "rejected") + << "\",\"reason\":\"" << (v.reject.empty() ? "-" : v.reject) + << "\",\"frame_region\":" << v.quadrant + << ",\"distance_band\":" << v.distance_band + << ",\"holdout\":" << (v.holdout ? "true" : "false") + << ",\"target_occupancy\":\"" << number(v.occupancy) + << "\",\"normal_angle_degrees\":\"" << number(v.angle) + << "\",\"measured_distance_metres\":\"" << number(v.distance) + << "\",\"pre_solve_corner_rms_px\":\"" << number(v.pre_solve_corner_rms) + << "\",\"clipping_fraction\":\"" << number(v.clipping_fraction) + << "\",\"physical_target_quadrants\":" << target_quadrants + << ",\"target_corner_quadrant_mask\":" << target_mask + << ",\"corner_count\":" << v.ids.size() + << ",\"residual_count\":" << v.residuals.size() + << ",\"high_residual_count\":" << v.high_residual_count + << ",\"reprojection_rmse_px\":\"" << number(v.rmse) + << "\",\"maximum_residual_px\":\"" << number(v.max_residual) + << "\",\"coordinate_equivalence\":{\"comparison_points\":" + << v.coordinate_comparison_count + << ",\"max_abs_dx_px\":\"" << number(v.coordinate_dx_max) + << "\",\"max_abs_dy_px\":\"" << number(v.coordinate_dy_max) + << "\",\"pass\":" << (v.coordinate_ok ? "true" : "false") + << "},\"corners\":["; + + for (size_t j = 0; j < v.ids.size(); ++j) { + if (j) d << ','; + d << "{\"id\":" << v.ids[j] + << ",\"x\":\"" << number(v.image[j].x) + << "\",\"y\":\"" << number(v.image[j].y) << "\"}"; + } + d << "]}" << (i + 1 == views.size() ? "\n" : " ,\n"); + } d<<"]\n}\n";d.close();if(!d)return fail("detection write"); std::ofstream s(temp/"solve.json",std::ios::binary);if(!s)return fail("solve output"); s<<"{\n\"format\":\"L3DCAL_SOLVE_V1\",\n\"runs\":[\n";for(int run=0;run<3;++run){const Solve&z=full[run];s<<"{\"run\":"<(high)/static_cast(count))<<"\",\n\"holdout\":{\"rmse_px\":\""<&v,const Solve&s,double independent_rms, @@ -322,7 +373,7 @@ void report(const std::vector&v,const Solve&s,double independent_rms, bool run_session(const std::string& session) { std::vector v;std::array optical{};SessionEvidence evidence;std::string why; std::array solver_executable_sha256{},session_sha256{}; - if(!file_hash("/proc/self/exe",&solver_executable_sha256)||!file_hash(session,&session_sha256)){std::cerr<<"FAIL producer/session hash\\n";return false;} + if(!file_hash("/proc/self/exe",&solver_executable_sha256)||!file_hash(session,&session_sha256)){std::cerr<<"FAIL producer/session hash\n";return false;} const std::array solver_configuration_sha256=text_hash(solver_configuration_record()); if(!read_session(session,&v,&optical,&evidence,&why)||!detect(&v,&why)){std::cerr<<"FAIL "< all;cv::Size size;for(size_t i=0;i=2 && + producer_text.compare(producer_text.size()-2,2,"}\n")==0 && + producer_text.find("\\n")==std::string::npos && + producer_text.find("\\\"")==std::string::npos; + const bool per_view_fields= + detection_text.find("\"frame_region\"")!=std::string::npos && + detection_text.find("\"distance_band\"")!=std::string::npos && + detection_text.find("\"holdout\"")!=std::string::npos && + detection_text.find("\"target_occupancy\"")!=std::string::npos && + detection_text.find("\"normal_angle_degrees\"")!=std::string::npos && + detection_text.find("\"measured_distance_metres\"")!=std::string::npos && + detection_text.find("\"target_corner_quadrant_mask\"")!=std::string::npos && + detection_text.find("\"corner_count\"")!=std::string::npos && + detection_text.find("\"residual_count\"")!=std::string::npos && + detection_text.find("\"high_residual_count\"")!=std::string::npos && + detection_text.find("\"reprojection_rmse_px\"")!=std::string::npos && + detection_text.find("\"maximum_residual_px\"")!=std::string::npos; + const bool bytes=written&&identical("detection.json")&&identical("solve.json")&& + identical("evidence.json")&&identical("producer.json")&& + producer_json_valid&&per_view_fields; std::cout<