1"use strict";(globalThis.webpackChunkvrgs_docs_v_3_1=globalThis.webpackChunkvrgs_docs_v_3_1||[]).push([[17752],{35780(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>c,frontMatter:()=>o,metadata:()=>t,toc:()=>h});const t=JSON.parse('{"id":"general/meshes-point-clouds/sfm-user-guide","title":"Structure-from-Motion (SfM) & Dense MVS","description":"Reconstruct camera poses and 3D point clouds from photographs, build a surface mesh, and bake a photographic texture \u2014 tuning every parameter of the sparse SfM, dense MVS, meshing, and texturing pipeline.","source":"@site/versioned_docs/version-3.4/general/meshes-point-clouds/sfm-user-guide.md","sourceDirName":"general/meshes-point-clouds","slug":"/general/meshes-point-clouds/sfm-user-guide","permalink":"/docs/general/meshes-point-clouds/sfm-user-guide","draft":false,"unlisted":false,"editUrl":"https://github.com/vrgeoscience/vrgs_docs_v3/tree/master/versioned_docs/version-3.4/general/meshes-point-clouds/sfm-user-guide.md","tags":[],"version":"3.4","sidebarPosition":1,"frontMatter":{"id":"sfm-user-guide","title":"Structure-from-Motion (SfM) & Dense MVS","sidebar_label":"SfM / Photogrammetry","sidebar_position":1,"description":"Reconstruct camera poses and 3D point clouds from photographs, build a surface mesh, and bake a photographic texture \u2014 tuning every parameter of the sparse SfM, dense MVS, meshing, and texturing pipeline."},"sidebar":"docs","previous":{"title":"GNSS in the Field","permalink":"/docs/general/setup-system/gnss-field-navigation"},"next":{"title":"Preparing a Model","permalink":"/docs/general/meshes-point-clouds/model-preparation"}}');var r=s(74848),i=s(28453);const o={id:"sfm-user-guide",title:"Structure-from-Motion (SfM) & Dense MVS",sidebar_label:"SfM / Photogrammetry",sidebar_position:1,description:"Reconstruct camera poses and 3D point clouds from photographs, build a surface mesh, and bake a photographic texture \u2014 tuning every parameter of the sparse SfM, dense MVS, meshing, and texturing pipeline."},a="Structure-from-Motion (SfM) & Dense MVS",l={},h=[{value:"Running a reconstruction",id:"running-a-reconstruction",level:2},{value:"Parameter reference",id:"parameter-reference",level:2},{value:"Quality preset",id:"quality-preset",level:3},{value:"Feature extraction & matching",id:"feature-extraction--matching",level:3},{value:"Detector",id:"detector",level:4},{value:"Alignment image scale",id:"alignment-image-scale",level:4},{value:"Features per megapixel",id:"features-per-megapixel",level:4},{value:"Max features per image",id:"max-features-per-image",level:4},{value:"Lowe ratio test",id:"lowe-ratio-test",level:4},{value:"Min geometric inliers",id:"min-geometric-inliers",level:4},{value:"Match strategy",id:"match-strategy",level:4},{value:"Auto retrieval threshold (images)",id:"auto-retrieval-threshold-images",level:4},{value:"Retrieval neighbours",id:"retrieval-neighbours",level:4},{value:"Retrieval sequential overlap",id:"retrieval-sequential-overlap",level:4},{value:"Epipolar RANSAC (px)",id:"epipolar-ransac-px",level:4},{value:"Registration tolerance (px)",id:"registration-tolerance-px",level:4},{value:"Min triangulation angle (deg)",id:"min-triangulation-angle-deg",level:4},{value:"Seed pair min median angle (deg)",id:"seed-pair-min-median-angle-deg",level:4},{value:"Cameras & calibration",id:"cameras--calibration",level:3},{value:"Share intrinsics per camera",id:"share-intrinsics-per-camera",level:4},{value:"Where the starting focal length comes from",id:"where-the-starting-focal-length-comes-from",level:4},{value:"Cameras that correct their own lens",id:"cameras-that-correct-their-own-lens",level:4},{value:"Auto lens calibration",id:"auto-lens-calibration",level:4},{value:"Distortion policy",id:"distortion-policy",level:4},{value:"GPS & georeferencing",id:"gps--georeferencing",level:3},{value:"Use GPS prior",id:"use-gps-prior",level:4},{value:"GPS altitude is MSL",id:"gps-altitude-is-msl",level:4},{value:"GPS sigma XY (m)",id:"gps-sigma-xy-m",level:4},{value:"GPS sigma Z (m)",id:"gps-sigma-z-m",level:4},{value:"GPS pair max distance (m)",id:"gps-pair-max-distance-m",level:4},{value:"Bundle adjustment",id:"bundle-adjustment",level:3},{value:"BA max iterations (global)",id:"ba-max-iterations-global",level:4},{value:"Huber threshold (px)",id:"huber-threshold-px",level:4},{value:"Dense MVS",id:"dense-mvs",level:3},{value:"Dense MVS (learned)",id:"dense-mvs-learned",level:4},{value:"Dense MVS backend",id:"dense-mvs-backend",level:4},{value:"Depth map quality",id:"depth-map-quality",level:4},{value:"Dense MVS neighbor views",id:"dense-mvs-neighbor-views",level:4},{value:"Dense MVS depth hypotheses",id:"dense-mvs-depth-hypotheses",level:4},{value:"Dense MVS confidence cutoff",id:"dense-mvs-confidence-cutoff",level:4},{value:"Dense MVS CasMVSNet confidence cutoff",id:"dense-mvs-casmvsnet-confidence-cutoff",level:4},{value:"Dense MVS consistent views",id:"dense-mvs-consistent-views",level:4},{value:"Dense MVS depth tolerance",id:"dense-mvs-depth-tolerance",level:4},{value:"Dense MVS sky filter",id:"dense-mvs-sky-filter",level:4},{value:"Dense MVS fusion voxel",id:"dense-mvs-fusion-voxel",level:4},{value:"Dense MVS min component size",id:"dense-mvs-min-component-size",level:4},{value:"Texturing",id:"texturing",level:3},{value:"Texture multi-photo blend",id:"texture-multi-photo-blend",level:4},{value:"Texture blend sharpness",id:"texture-blend-sharpness",level:4},{value:"Texture lens distortion",id:"texture-lens-distortion",level:4},{value:"Texture max UV stretch",id:"texture-max-uv-stretch",level:4},{value:"Texture max view angle (deg)",id:"texture-max-view-angle-deg",level:4},{value:"Texture rescue occluded triangles",id:"texture-rescue-occluded-triangles",level:4},{value:"Diagnostics & output",id:"diagnostics--output",level:3},{value:"Write undistorted previews",id:"write-undistorted-previews",level:4},{value:"Verbose logging",id:"verbose-logging",level:4},{value:"Ground control points",id:"ground-control-points",level:2},{value:"1. Import the survey",id:"1-import-the-survey",level:3},{value:"2. Mark them in the photographs",id:"2-mark-them-in-the-photographs",level:3},{value:"3. Re-run",id:"3-re-run",level:3},{value:"Control points vs check points",id:"control-points-vs-check-points",level:3},{value:"Using control elsewhere in the project",id:"using-control-elsewhere-in-the-project",level:3},{value:"Scale bars",id:"scale-bars",level:2},{value:"Quality report",id:"quality-report",level:2},{value:"Inspecting the sparse cloud",id:"inspecting-the-sparse-cloud",level:2},{value:"Building a mesh",id:"building-a-mesh",level:2},{value:"Create Mesh (Poisson)",id:"create-mesh-poisson",level:3},{value:"Octree depth",id:"octree-depth",level:4},{value:"Samples per node",id:"samples-per-node",level:4}
1,{value:"Trim factor (\xd7 point spacing)",id:"trim-factor--point-spacing",level:4},{value:"Create Mesh (Greedy Projection)",id:"create-mesh-greedy-projection",level:3},{value:"Texturing the mesh",id:"texturing-the-mesh",level:2},{value:"Let VRGS choose the budget",id:"let-vrgs-choose-the-budget",level:3},{value:"Resolution mode <em>(advanced)</em>",id:"resolution-mode-advanced",level:3},{value:"Page size <em>(advanced)</em>",id:"page-size-advanced",level:3},{value:"Target pixel size <em>(pixel-size mode)</em>",id:"target-pixel-size-pixel-size-mode",level:3},{value:"Number of pages <em>(page-budget mode)</em>",id:"number-of-pages-page-budget-mode",level:3},{value:"How a triangle is textured",id:"how-a-triangle-is-textured",level:3},{value:"Lens distortion",id:"lens-distortion",level:3},{value:"Texture resolution attributes",id:"texture-resolution-attributes",level:3},{value:"How long the bake takes, and how to stop it",id:"how-long-the-bake-takes-and-how-to-stop-it",level:3},{value:"Reading the run log",id:"reading-the-run-log",level:2},{value:"Sparse summary",id:"sparse-summary",level:3},{value:"Dense fusion funnel",id:"dense-fusion-funnel",level:3},{value:"Confidence profile",id:"confidence-profile",level:3},{value:"Per-reference yield",id:"per-reference-yield",level:3},{value:"Per-view depth diagnostic",id:"per-view-depth-diagnostic",level:3},{value:"Why the dense cloud has gaps where the sparse cloud has points",id:"why-the-dense-cloud-has-gaps-where-the-sparse-cloud-has-points",level:3},{value:"Worked examples",id:"worked-examples",level:2},{value:"Drone survey with GPS (nadir)",id:"drone-survey-with-gps-nadir",level:3},{value:"Convergent outcrop (oblique, high overlap)",id:"convergent-outcrop-oblique-high-overlap",level:3},{value:"Densest possible dense cloud",id:"densest-possible-dense-cloud",level:3},{value:"Cleanest dense cloud",id:"cleanest-dense-cloud",level:3},{value:"Fast preview",id:"fast-preview",level:3},{value:"Textured outcrop mesh",id:"textured-outcrop-mesh",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"structure-from-motion-sfm--dense-mvs",children:"Structure-from-Motion (SfM) & Dense MVS"})}),"\n",(0,r.jsx)(n.admonition,{title:"Requires the SfM licence feature",type:"info",children:(0,r.jsxs)(n.p,{children:["The photogrammetry workflow and its branch of the project tree appear only when your\nlicence includes the ",(0,r.jsx)(n.strong,{children:"SFM"})," feature. See ",(0,r.jsx)(n.a,{href:"/docs/general/setup-system/feature-availability",children:"Why Is This Greyed Out?"}),"\nif you cannot find it."]})}),"\n",(0,r.jsx)(n.p,{children:"VRGS can rebuild a 3D scene from a set of overlapping photographs. The pipeline\nruns as a chain of stages \u2014 the first always runs, the rest are optional\nfollow-ons you trigger when you want them:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Sparse SfM"})," \u2014 detects features in each photo, matches them across overlapping\npairs, and solves for every camera's position/orientation and a ",(0,r.jsx)(n.strong,{children:"sparse"}),"\ncoloured point cloud (the matched tie points). This stage always runs."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Dense MVS"})," (optional) \u2014 takes the solved cameras and estimates a depth map\nper view, then fuses them into a ",(0,r.jsx)(n.strong,{children:"dense"})," coloured point cloud of the surface."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Create Mesh"})," (optional) \u2014 turns the dense cloud into a triangulated surface\n(",(0,r.jsx)(n.strong,{children:"Poisson"})," or ",(0,r.jsx)(n.strong,{children:"Greedy Projection"}),"), carrying the cloud's per-vertex colour."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Texture Mesh from Photos"})," (optional) \u2014 projects the solved photographs back\nonto the mesh and bakes a high-detail ",(0,r.jsx)(n.strong,{children:"photographic texture"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Typical inputs are drone/UAV imagery or hand-held photos of an outcrop. Typical\noutputs are a georeferenced camera set (with image thumbnails in the 3D view), a\nsparse point cloud, and\u2014if enabled\u2014a dense point cloud, a surface mesh, and a\nphoto-textured mesh you can interpret."}),"\n",(0,r.jsx)(n.admonition,{title:"Workflow in one line",type:"tip",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"New"})," an SfM workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Add Photos\u2026"})," \u2192 set parameters \u2192 ",(0,r.jsx)(n.strong,{children:"Run\nReconstruction"})," \u2192 (optional) ",(0,r.jsx)(n.strong,{children:"Create Mesh"})," \u2192 (optional) ",(0,r.jsx)(n.strong,{children:"Texture Mesh from\nPhotos"}),"."]})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-a-reconstruction",children:"Running a reconstruction"}),"\n",(0,r.jsxs)(n.p,{children:["All actions live on the ",(0,r.jsx)(n.strong,{children:"SFM"})," branch of the ",(0,r.jsx)(n.strong,{children:"Data Tree"}),"."]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Step"}),(0,r.jsx)(n.th,{children:"Action"}),(0,r.jsx)(n.th,{children:"Where"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"1. Create a workflow"}),(0,r.jsxs)(n.td,{children:["Right-click the ",(0,r.jsx)(n.strong,{children:"SFM Group"})," \u2192 ",(0,r.jsx)(n.strong,{children:"New"})]}),(0,r.jsxs)(n.td,{children:["Creates ",(0,r.jsx)(n.code,{children:"Model 1"}),", ",(0,r.jsx)(n.code,{children:"Model 2"}),", \u2026"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"2. Add images"}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Add Photos\u2026"})]}),(0,r.jsx)(n.td,{children:"Browse to your JPEGs"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"3. Configure"}),(0,r.jsxs)(n.td,{children:["Select the workflow; pick a ",(0,r.jsx)(n.strong,{children:"Quality preset"}),", or edit the ",(0,r.jsx)(n.strong,{children:"Parameters"})]}),(0,r.jsxs)(n.td,{children:["See ",(0,r.jsx)(n.a,{href:"#parameter-reference",children:"Parameters"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["4. Control ",(0,r.jsx)(n.em,{children:"(optional)"})]}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Import Ground Control Points (CSV)\u2026"})]}),(0,r.jsxs)(n.td,{children:["See ",(0,r.jsx)(n.a,{href:"#ground-control-points",children:"Ground control points"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"5. Reconstruct"}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Run Reconstruction"})]}),(0,r.jsx)(n.td,{children:"Runs on a background thread, with a progress bar"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"6. Check quality"}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Show Quality Report"})]}),(0,r.jsxs)(n.td,{children:["See ",(0,r.jsx)(n.a,{href:"#quality-report",children:"Quality report"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"7. Review images"}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Open Photo Browser"})]}),(0,r.jsx)(n.td,{children:"Per-photo keypoint/track stats; also where you mark control points"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["8. Mesh ",(0,r.jsx)(n.em,{children:"(optional)"})]}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Create Mesh (Poisson)\u2026"})," or ",(0,r.jsx)(n.strong,{children:"Create Mesh (Greedy Projection)\u2026"})]}),(0,r.jsxs)(n.td,{children:["Needs a dense cloud; see ",(0,r.jsx)(n.a,{href:"#building-a-mesh",children:"Building a mesh"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["9. Texture ",(0,r.jsx)(n.em,{children:"(optional)"})]}),(0,r.jsxs)(n.td,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Texture Mesh from Photos\u2026"})]}),(0,r.jsxs)(n.td,{children:["Needs a mesh; see ",(0,r.jsx)(n.a,{href:"#texturing-the-mesh",children:"Texturing the mesh"})]})]})]})]}
1),"\n",(0,r.jsx)(n.p,{children:"Other commands on the workflow's right-click menu:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Command"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Use Project Ground Control Points"})}),(0,r.jsx)(n.td,{children:"Adopts ground control already in the project into this workflow"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Scale Bars (Known Distances)\u2026"})}),(0,r.jsxs)(n.td,{children:["Gives a model real size without GPS or control \u2014 see ",(0,r.jsx)(n.a,{href:"#scale-bars",children:"Scale bars"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Seed Mesh Tie Points from Ground Control"})}),(0,r.jsxs)(n.td,{children:["Turns measured control into the mesh's own tie points, so the mesh ribbon's ",(0,r.jsx)(n.strong,{children:"Corrections \u2192 GCP"})," can pull the mesh onto the survey"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Open Photos in Annotation View"})}),(0,r.jsx)(n.td,{children:"Opens the mesh's photos in the annotation view for SAM2/UNet interpretation"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Show Camera GPS as Point Cloud"})}),(0,r.jsx)(n.td,{children:"A QC cloud of the raw EXIF GPS positions, to compare against the solved cameras"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Add Satellite Imagery Basemap"})}),(0,r.jsx)(n.td,{children:"Drapes Bing aerial imagery under the model"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["As soon as you ",(0,r.jsx)(n.strong,{children:"Add Photos"}),", any photo with EXIF GPS gets a ",(0,r.jsx)(n.strong,{children:"camera marker"}),"\nin the 3D view at its GPS position \u2014 before you run anything. These are drawn\nwithout a thumbnail to mark them as ",(0,r.jsx)(n.em,{children:"provisional"}),": they are where the receiver\nsaid the camera was, not a solved pose. After a reconstruction the registered\ncameras are redrawn with thumbnails, in their solved positions."]}),"\n",(0,r.jsxs)(n.p,{children:["The reconstruction runs in the background; progress appears on the main progress\nbar and a summary in the log (and the workflow's ",(0,r.jsx)(n.strong,{children:"Status"})," property). When it\nfinishes, the results are added to the project automatically:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"<name> sparse"})})," \u2014 the sparse tie-point cloud (e.g. ",(0,r.jsx)(n.code,{children:"Model 1 sparse"}),"),\ncarrying ",(0,r.jsx)(n.strong,{children:"Observations"})," and ",(0,r.jsx)(n.strong,{children:"Reprojection error"})," attributes you can colour\nby \u2014 see ",(0,r.jsx)(n.a,{href:"#inspecting-the-sparse-cloud",children:"Inspecting the sparse cloud"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"<name> dense"})})," \u2014 the dense MVS cloud, ",(0,r.jsx)(n.em,{children:"only"})," if Dense MVS was enabled."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"<name> mesh"})})," \u2014 the surface mesh, once you run ",(0,r.jsx)(n.strong,{children:"Create Mesh"})," (it later\ncarries the baked texture and the photographs too)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Camera poses"})," \u2014 each registered photo, with its image thumbnail."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ground control points"})," \u2014 any control you imported, as project objects under\n",(0,r.jsx)(n.strong,{children:"Ground Control Points"})," in the Data Tree, showing how far the model landed\nfrom each one."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The mesh and texturing stages also run on background threads; the mesh links back\nto its workflow, so ",(0,r.jsx)(n.strong,{children:"Texture Mesh from Photos"})," always finds the mesh that\nworkflow created."]}),"\n",(0,r.jsxs)(n.admonition,{title:"Re-running is cheap \u2014 the solve is reused",type:"tip",children:[(0,r.jsxs)(n.p,{children:["A completed solve is kept in the workflow's workspace, and ",(0,r.jsx)(n.strong,{children:"Run Reconstruction\nreuses it whenever nothing that shapes the solve has changed"})," \u2014 same photos,\nsame reconstruction parameters, same control, tie points and scale bars. The\nre-run then skips feature extraction, matching and registration entirely and\ngoes straight to whatever is new."]}),(0,r.jsxs)(n.p,{children:["The everyday case: you run a reconstruction, only then remember ",(0,r.jsx)(n.strong,{children:"Dense MVS"}),"\nwas left off. Enable it and run again \u2014 the second run starts at the dense\nstage within seconds
1instead of re-registering every photo for half an hour.\nThe same applies to tuning any ",(0,r.jsx)(n.strong,{children:"Dense MVS"})," parameter: the dense settings are\ndeliberately ",(0,r.jsx)(n.em,{children:"not"})," part of the reuse check, so dense experiments never pay the\nsparse cost. The log states plainly when a run reused the solve and when it\ndid a full one."]}),(0,r.jsxs)(n.p,{children:["Change ",(0,r.jsx)(n.strong,{children:"any"})," reconstruction parameter \u2014 or the photo list, ground control,\ntie points or scale bars \u2014 and the next run does an honest full solve. To force\none without changing anything, delete ",(0,r.jsx)(n.code,{children:"sparse_solution.bin"})," from the workflow's\nworkspace folder."]})]}),"\n",(0,r.jsxs)(n.admonition,{title:"Coordinate frame",type:"note",children:[(0,r.jsx)(n.p,{children:"The model is placed by whichever of these you give it, in this order of priority:"}),(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ground control points"})," \u2014 3 or more marked control points fit the model\ndirectly to your survey (centimetre accuracy). See\n",(0,r.jsx)(n.a,{href:"#ground-control-points",children:"Ground control points"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Camera GPS"})," \u2014 with ",(0,r.jsx)(n.strong,{children:"Use GPS prior"})," on and valid EXIF GPS, the model is\nfitted to the camera positions ",(0,r.jsx)(n.strong,{children:"in the project's coordinate system"}),", so the\noutput lands alongside the rest of your project data (metre accuracy)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Nothing"})," \u2014 the model is reconstructed in an arbitrary local frame with no\nscale and no orientation. A ",(0,r.jsx)(n.a,{href:"#scale-bars",children:"scale bar"})," can still give it real\n",(0,r.jsx)(n.strong,{children:"size"}),", but it will not be georeferenced."]}),"\n"]}),(0,r.jsx)(n.p,{children:"If the photos carry GPS and the project has no coordinate system yet, VRGS opens\nthe Coordinate System dialog with the UTM zone covering your photos already\nselected \u2014 accept it or search for a different CRS."}),(0,r.jsxs)(n.p,{children:["Whatever placed the model, the ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#quality-report",children:"quality report"})})," always\nstates which data did it and how well it fitted."]})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"parameter-reference",children:"Parameter reference"}),"\n",(0,r.jsx)(n.p,{children:"Parameters are grouped below the way they appear in the Properties panel. Every\nvalue has a sensible default\u2014you can run a first reconstruction without changing\nanything and tune afterwards."}),"\n",(0,r.jsxs)(n.admonition,{type:"info",children:[(0,r.jsxs)(n.mdxAdmonitionTitle,{children:["Most of these live under ",(0,r.jsx)(n.strong,{children:"Advanced"})]}),(0,r.jsxs)(n.p,{children:["Only four settings sit at the top of the property sheet \u2014 the ",(0,r.jsx)(n.strong,{children:"quality\npreset"}),", the ",(0,r.jsx)(n.strong,{children:"dense"})," toggle, ",(0,r.jsx)(n.strong,{children:"depth quality"}),", and the ",(0,r.jsx)(n.strong,{children:"altitude datum"}),".\nEverything else described below is inside a collapsed ",(0,r.jsx)(n.strong,{children:"Advanced"})," group."]}),(0,r.jsxs)(n.p,{children:["That is deliberate: the majority path should be one click. Nothing has been\nremoved, everything still defaults to ",(0,r.jsx)(n.code,{children:"Auto"}),", and the presets are unchanged \u2014\nthe dials are simply out of the way until you need one."]})]}),"\n",(0,r.jsx)(n.h3,{id:"quality-preset",children:"Quality preset"}),"\n",(0,r.jsx)(n.p,{children:"Start here. The preset moves the handful of settings that genuinely trade speed\nagainst accuracy, so you rarely need to touch the rest."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options:"})," ",(0,r.jsx)(n.code,{children:"Fast"}),", ",(0,r.jsx)(n.code,{children:"Balanced"})," (the defaults), ",(0,r.jsx)(n.code,{children:"Accurate"}),", ",(0,r.jsx)(n.code,{children:"Custom"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Fast"})," \u2014 half resolution, fewer features, retrieval matching. Loses fine\ndetail and some weak-overlap pairs. Use it same-day, in the field, to find\nout whether the survey actually worked."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Balanced"})," \u2014 the shipped defaults. What you want almost always."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Accurate"})," \u2014 full resolution, dense features, exhaustive matching, tighter\ngates. Markedly slower (on a large set the exhaustive matcher is the cost),\nbut leaves nothing on the table."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Custom"})," \u2014 a ",(0,r.jsx)(n.em,{children:"readout"}),", not a choice: it means the settings below no longer\nmatch any preset. Selecting it does nothing."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it will not touch:"})," your GPS, altitude-datum and Dense MVS settings. A\npreset never silently undoes a decision you made for a different reason."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"feature-extraction--matching",children:"Feature extraction & matching"}),"\n",(0,r.jsxs)(n.p,{children:["These control how features are found in each image and matched across images. They\nhave the biggest effect on ",(0,r.jsx)(n.strong,{children:"how many photos register"})," and how complete the\nsparse cloud is."]}),"\n",(0,r.jsx)(n.h4,{id:"detector",children:"Detector"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it is:"})," the feature detector/descriptor algorithm."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"SIFT"})," (default), ",(0,r.jsx)(n.code,{children:"ORB"}),", ",(0,r.jsx)(n.code,{children:"AKAZE"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," ",(0,r.jsx)(n.code,{children:"SIFT"})," gives the most robust, repeatable matches on natural\nrock texture and is the best default for quality. ",(0,r.jsx)(n.code,{children:"ORB"})," is a fast binary\ndetector\u2014much quicker and lighter on memory, but produces fewer reliable\nmatches. ",(0,r.jsx)(n.code,{children:"AKAZE"})," sits in between."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," keep ",(0,r.jsx)(n.code,{children:"SIFT"})," for final outcrop reconstructions. Switch to ",(0,r.jsx)(n.code,{children:"ORB"})," for\na fast sanity check on a very large image set, or when you only need a rough\ncamera layout quickly."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"alignment-image-scale",children:"Alignment image scale"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"align_image_scale"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"Full"}),", ",(0,r.jsx)(n.code,{children:"Half (1/2)"})," (default), ",(0,r.jsx)(n.code,{children:"Quarter (1/4)"}),",\n",(0,r.jsx)(n.code,{children:"Eighth (1/8)"}),", ",(0,r.jsx)(n.code,{children:"Sixteenth (1/16)"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the working resolution for feature detection, ",(0,r.jsx)(n.strong,{children:"relative to\neach photo"})," rather than an absolute pixel cap. Smaller is faster and uses less\nmemory but finds fewer fine features."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Why relative:"})," an absolute cap makes a preset mean different things on\ndifferent cameras. At a 3200 px cap, a 45 MP frame and a 12 MP frame both land\nat 3200 px, so the larger sensor buys nothing. A relative ladder preserves each\ncamera's angular resolution, so a preset means the same thing everywhere."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"Half"})," for normal work, ",(0,r.jsx)(n.code,{children:"Quarter"})," for quick tests, ",(0,r.jsx)(n.code,{children:"Full"})," to\nsqueeze maximum detail from a small, high-resolution set."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["This matches Metashape's alignment accuracy ladder: their High = ",(0,r.jsx)(n.code,{children:"Full"}),",\nMedium = ",(0,r.jsx)(n.code,{children:"Half"}),", Low = ",(0,r.jsx)(n.code,{children:"Quarter"}),". The presets set it for you \u2014 Fast =\n",(0,r.jsx)(n.code,{children:"Quarter"}),", Balanced = ",(0,r.jsx)(n.code,{children:"Half"}),", Accurate = ",(0,r.jsx)(n.code,{children:"Full"}),"."]}),"\n",(0,r.jsx)(n.admonition,{title:'Replaces "Max image dimension"',type:"note",children:(0,r.jsxs)(n.p,{children:["Older projects stored an absolute ",(0,r.jsx)(n.code,{children:"max_image_dim"})," in pixels. It is read and\nconverted to the nearest rung when such a project is opened; the setting itself\nis gone."]})}),"\n",(0,r.jsx)(n.h4,{id:"features-per-megapixel",children:"Features per megapixel"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"features_per_mpx"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"2000"}),". ",(0,r.jsx)(n.code,{children:"0"})," ignores density and uses the cap as a flat count."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," keypoint density on the working (downsampled) image. The\nbudget for a photo is this times its working megapixels, then capped by ",(0,r.jsx)(n.strong,{children:"Max\nfeatures per image"}),". Expressing it as a density rather than a count keeps\ndetection even across mixed-resolution sets."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave at ",(0,r.jsx)(n.code,{children:"2000"}),". Raise it on low-texture scenes where pairs are\nhard to verify. Ignored by the AKAZE detector."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"max-features-per-image",children:"Max features per image"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"8000"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the upper bound on keypoints kept per image. More features\ngive more matches and a denser sparse cloud at the cost of speed and memory."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"12000"}),"\u2013",(0,r.jsx)(n.code,{children:"20000"})," on low-texture scenes (smooth, uniform\nrock) where pairs are hard to verify; drop to ",(0,r.jsx)(n.code,{children:"4000"})," for speed."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"lowe-ratio-test",children:"Lowe ratio test"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0.8"})," (typical range ",(0,r.jsx)(n.code,{children:"0.7"}),"\u2013",(0,r.jsx)(n.code,{children:"0.8"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," a match is accepted only if the best descriptor match is\nclearly better than the second-best by this ratio. ",(0,r.jsx)(n.strong,{children:"Lower is stricter"})," (fewer\nbut cleaner matches); higher keeps more matches but admits more noise."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," lower to ",(0,r.jsx)(n.code,{children:"0.75"})," on repetitive texture (e.g. bedded sandstone,\nbrickwork) to
1suppress ambiguous matches."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"min-geometric-inliers",children:"Min geometric inliers"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"20"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the minimum number of geometrically-verified inlier matches\nfor an image pair to be trusted. It is also the floor used when choosing the\ninitial pair and registering later views. ",(0,r.jsx)(n.strong,{children:"Higher = stricter"})," (cleaner pose\ngraph, but a thin-overlap dataset may fragment); lower admits weaker pairs."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"30"}),"\u2013",(0,r.jsx)(n.code,{children:"40"})," on clean, high-overlap datasets for robustness;\nonly lower below ",(0,r.jsx)(n.code,{children:"20"})," (cautiously) when overlap is genuinely sparse."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"match-strategy",children:"Match strategy"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"Auto"})," (default), ",(0,r.jsx)(n.code,{children:"Exhaustive"}),", ",(0,r.jsx)(n.code,{children:"Vocabulary tree"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," decides ",(0,r.jsx)(n.strong,{children:"which image pairs"})," are matched at all.\n",(0,r.jsx)(n.code,{children:"Exhaustive"})," tries every pair \u2014 the most thorough, but the work grows with the\nsquare of the image count and becomes impractical past a couple of thousand\nphotos. ",(0,r.jsx)(n.code,{children:"Vocabulary tree"})," matches each image only against its most visually\nsimilar neighbours, which collapses the cost and tends to ",(0,r.jsx)(n.em,{children:"improve"})," robustness\nby never attempting pairs that cannot overlap. ",(0,r.jsx)(n.code,{children:"Auto"})," is exhaustive up to the\nthreshold below, then switches."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave on ",(0,r.jsx)(n.code,{children:"Auto"}),". Force ",(0,r.jsx)(n.code,{children:"Exhaustive"})," on a small, awkward set where\nevery possible pair is worth trying."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"auto-retrieval-threshold-images",children:"Auto retrieval threshold (images)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"600"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the image count above which ",(0,r.jsx)(n.code,{children:"Auto"})," switches to\nvocabulary-tree retrieval. Ignored unless ",(0,r.jsx)(n.strong,{children:"Match strategy"})," is ",(0,r.jsx)(n.code,{children:"Auto"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"retrieval-neighbours",children:"Retrieval neighbours"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"40"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," how many similar images each image is matched against under\nretrieval. Higher finds more overlap at proportionally more matching time."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"60"}),"+ if a large set is registering poorly; drop to ",(0,r.jsx)(n.code,{children:"20"}),"\nfor speed."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"retrieval-sequential-overlap",children:"Retrieval sequential overlap"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0"})," (retrieved candidates only)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," additionally matches each photo against this many neighbours\neither side of it in capture order, whatever the similarity search made of\nthem."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," set to ",(0,r.jsx)(n.code,{children:"2"}),"\u2013",(0,r.jsx)(n.code,{children:"5"})," on ordered flight lines. Retrieval ranks images by\nvisual similarity, so a frame over featureless sand, water or snow can score\npoorly against its true neighbours and never be tried \u2014 this pairs it with them\nanyway, on the strength of capture order alone."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"epipolar-ransac-px",children:"Epipolar RANSAC (px)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"2.0"}),", in ",(0,r.jsx)(n.em,{children:"detection-scale"})," pixels."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the inlier threshold used to verify a pair's geometry."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise it if pairs that clearly overlap are being rejected on noisy\nor motion-blurred imagery."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"registration-tolerance-px",children:"Registration tolerance (px)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"4.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the base reprojection tolerance for the gates that decide\nwhether a photo registers at all."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"6"}),"\u2013",(0,r.jsx)(n.code,{children:"8"})," when photos that obviously overlap still fail to\nregister; lower to ",(0,r.jsx)(n.code,{children:"3"})," for a stricter, cleaner model."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"min-triangulation-angle-deg",children:"Min triangulation angle (deg)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"2.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," points whose viewing rays converge at a shallower angle than\nthis are rejected \u2014 their depth is too uncertain to trust."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"3"}),"\u2013",(0,r.jsx)(n.code,{children:"5"})," for a cleaner cloud; lower only for narrow-baseline\nsets where you are losing too many points."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"seed-pair-min-median-angle-deg",children:"Seed pair min median angle (deg)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"8.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the geometry demanded of the ",(0,r.jsx)(n.strong,{children:"first"})," pair. A weak seed\nsettles the solve into a poor basin and everything registered afterwards\ninherits it. If no pair qualifies, VRGS relaxes to ",(0,r.jsx)(n.em,{children:"Min triangulation angle"}),"\nand retries, logging a warning."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"cameras--calibration",children:"Cameras & calibration"}),"\n",(0,r.jsx)(n.h4,{id:"share-intrinsics-per-camera",children:"Share intrinsics per camera"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," groups photos by camera make/model/focal length so they share\none set of intrinsics (focal length, principal point, distortion). Fewer\nunknowns means a more stable, faster bundle adjustment."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Turn off when:"})," every image may have different intrinsics\u2014mixed cameras, or a\nzoom lens used at varying focal lengths. Per-image intrinsics need more overlap\nto solve reliably."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave ",(0,r.jsx)(n.strong,{children:"on"})," for a single drone camera at a fixed focal length;\nconsider ",(0,r.jsx)(n.strong,{children:"off"})," for an ad-hoc mix of phone and drone photos."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"where-the-starting-focal-length-comes-from",children:"Where the starting focal length comes from"}),"\n",(0,r.jsx)(n.p,{children:"Bundle adjustment needs a focal length to start from, and it is taken from one of\nthree sources, in this order:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"A calibration already attached to the photograph"})," \u2014 including one written by\nan earlier reconstruction of the same photos. This is the most accurate source,\nand it is why re-running a workflow often starts from better intrinsics than\nthe first run did."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["EXIF ",(0,r.jsx)(n.code,{children:"FocalLengthIn35mmFilm"})]})," (tag 41989). It is normalised to a 36 mm\nframe, so ",(0,r.jsx)(n.code,{children:"fx = equivalent \xd7 width / 36"})," without needing the sensor size. The\ntag holds a rounded whole number of millimetres, which is close enough to start\nfrom."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"A 50 mm assumption"}),", if neither of the above is available."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The third is a guess, and a poor one for the imagery this tool usually sees:\ndrone and ph
1one cameras sit nearer 20 mm equivalent, so 50 mm begins the solve\nwith a focal length roughly 2.5\xd7 too long. That is enough to stop photographs\nregistering at all \u2014 and the symptom, a reconstruction that fails to register or\ncomes out badly scaled, looks like a matching problem rather than a missing EXIF\ntag. The run now states it plainly instead of proceeding in silence:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[WARNING] SfM intrinsics: 12 of 255 photo(s) carry no usable focal length in\nEXIF (FocalLengthIn35mmFilm, tag 41989) and were seeded from a 50 mm\nASSUMPTION. Drone and phone imagery is nearer 20 mm equivalent, so this seeds\nfx roughly 2.5x too long.\n"})}),"\n",(0,r.jsx)(n.p,{children:"If you see this line and registration is poor, attach a camera calibration to\nthose photographs."}),"\n",(0,r.jsx)(n.admonition,{title:"EXIF is read at import, not at solve time",type:"note",children:(0,r.jsxs)(n.p,{children:["Focal length, GPS and camera identity are read when photographs are ",(0,r.jsx)(n.strong,{children:"imported"}),"\nand stored with the workflow. Photographs imported by an earlier version keep\nwhatever was read then, so re-import them if you want a later version's reading."]})}),"\n",(0,r.jsx)(n.h4,{id:"cameras-that-correct-their-own-lens",children:"Cameras that correct their own lens"}),"\n",(0,r.jsxs)(n.p,{children:["Some cameras remove lens distortion ",(0,r.jsx)(n.strong,{children:"in firmware"}),", before the JPEG is written \u2014\nevery DJI drone does this, and so do most phones. The file you get is already\nrectilinear even though it is the original, straight off the card."]}),"\n",(0,r.jsxs)(n.p,{children:["VRGS reads the camera make and model from EXIF and recognises these bodies\nautomatically. When it does, it holds the distortion coefficients at ",(0,r.jsx)(n.strong,{children:"zero"}),"\nthrough bundle adjustment instead of solving for them, and says so in the log:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] SfM intrinsics: camera 0 'DJI FC330' 4000x3000 seed fx=2222.22\n -- de-warped in firmware: distortion FIXED at zero\n"})}),"\n",(0,r.jsx)(n.p,{children:"A camera with a genuinely raw lens is reported the other way and solved normally:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] SfM intrinsics: camera 0 'SONY NEX-5R' 3568x2368 -- distortion will be\n refined by BA\n"})}),"\n",(0,r.jsx)(n.p,{children:"This matters more than it looks. If distortion is left free on an already-corrected\ncamera, bundle adjustment will still find coefficients for it \u2014 a near-nadir survey\nmakes distortion, focal length and depth strongly correlated, so the solver can\nlower reprojection error by absorbing feature-matching noise into the radial terms\nwhile the poses and points quietly compensate. The result reports an excellent\nreprojection error and is geometrically wrong, and the damage only becomes visible\nlater, when the model is used to project photographs onto real geometry: each photo\nlands slightly off, by an amount that grows toward its frame corners, and the\ntexture bake blends the disagreement into smeared bands across the model."}),"\n",(0,r.jsx)(n.admonition,{title:"A low reprojection error does not prove the camera model is right",type:"note",children:(0,r.jsxs)(n.p,{children:["Bundle adjustment fits image features, and those features carry whatever bias the\nimages have. The honest test is photometric \u2014 do overlapping photographs agree\nwhen sampled at real geometry? That is exactly what the texture bake's\n",(0,r.jsx)(n.a,{href:"#lens-distortion",children:"distortion self-check"})," measures."]})}),"\n",(0,r.jsx)(n.h4,{id:"auto-lens-calibration",children:"Auto lens calibration"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on. Under ",(0,r.jsx)(n.strong,{children:"Advanced"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," when a camera has no held calibration, VRGS looks for a\n",(0,r.jsx)(n.strong,{children:"convergent view cluster"})," in the survey \u2014 an orbit of a structure, views that\nlook at one spot from many directions \u2014 solves the lens freely on ",(0,r.jsx)(n.strong,{children:"just that\nsub
1set"}),", validates the result, and then ",(0,r.jsx)(n.strong,{children:"holds it fixed"})," for the whole\nreconstruction."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Why it exists:"})," it is the answer to the corridor survey. A strip of\nnear-parallel views cannot separate lens from structure: pinning distortion\nbends the residual into focal length and principal point instead, a bounded box\nsaturates against its limits, and free refinement rubber-sheets the model. A\nconvergent subset ",(0,r.jsx)(n.em,{children:"can"})," separate them, and a lens solved there transfers to the\nrest of the survey."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Cost:"})," seconds, where convergent geometry exists. Where it does not \u2014 or\nwhere the subset solve fails validation \u2014 the run falls back to the normal\nautomatic behaviour and ",(0,r.jsx)(n.strong,{children:"says so in plain language"})," rather than proceeding in\nsilence."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Re-runs:"})," the phase reuses the cached poses from a previous solve, so a\nre-run builds once with the lens already held rather than solving twice."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"distortion-policy",children:"Distortion policy"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"Auto"}),". Under ",(0,r.jsx)(n.strong,{children:"Advanced"}),"."]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.strong,{children:"Options:"})}),"\n"]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Option"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Auto"})}),(0,r.jsx)(n.td,{children:"The decision chain below. Almost always what you want."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Fix at zero"})}),(0,r.jsx)(n.td,{children:"Hold the distortion coefficients at zero. For a camera you know de-warps in firmware and VRGS has not recognised."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Refine freely"})}),(0,r.jsx)(n.td,{children:"Solve distortion without constraint. The explicit escape hatch \u2014 see the caution below."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Fixed (attached calibration)"})}),(0,r.jsxs)(n.td,{children:["A calibration-first workflow: seed everything from the photos' attached calibration, hold distortion constant, and hold focal length and principal point close to it. Cameras without a usable attached calibration fall back to ",(0,r.jsx)(n.code,{children:"Auto"}),"."]})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Under ",(0,r.jsx)(n.strong,{children:"Auto"}),", VRGS works down this chain and logs one sentence saying which\nbranch it took:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"A valid calibration attached to the photographs"})," \u2014 held, not refined."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"An auto-calibrated lens"})," from the convergent subset above \u2014 held."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Bounded refinement"}),", when GPS or ground control can keep it honest."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Pinned at zero"}),", when nothing else applies."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:'"Refine freely" is not the safe option it sounds like',type:"caution",children:(0,r.jsxs)(n.p,{children:["Freely refining distortion ",(0,r.jsx)(n.strong,{children:"from attached values"})," is how a single bad writeback\npoisons a project: the solver drifts away from a good calibration and writes the\ndrift back, and the next run starts from that. ",(0,r.jsx)(n.code,{children:"Auto"})," holds an attached\ncalibration rather than refining it, and ",(0,r.jsx)(n.code,{children:"Refine freely"})," is the deliberate\noverride."]})}),"\n",(0,r.jsx)(n.h3,{id:"gps--georeferencing",children:"GPS & georeferencing"}),"\n",(0,r.jsx)(n.p,{children:"These only matter when your photos carry EXIF GPS (most drone imagery does)."}),"\n",(0,r.jsx)(n.h4,{id:"use-gps-prior",children:"Use GPS prior"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," uses EXIF GPS to (1) optionally pre-filter image pairs that\nare too far apart to overlap, and (2) after a purely visual reconstruction,\nalign the whole model to a metric East-North-Up frame and refine it with GPS as\na soft constraint. The result has real-world ",(0,r.jsx)(n.strong,{children:"scale and georeferencing"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Turn off when:"})," photos have no/poor GPS, or you deliberately want a\nscale-free visual reconstruction."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.strong,{children:"on"})," for drone surveys; ",(0,r.jsx)(n.strong,{children:"off"}
1)," for ground-based photos with no\nGPS."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"gps-altitude-is-msl",children:"GPS altitude is MSL"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," off (heights are treated as ",(0,r.jsx)(n.strong,{children:"ellipsoidal"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," tells VRGS how to read the EXIF altitude. Off means\nellipsoidal height \u2014 what most drones actually write, despite the EXIF\nspecification saying otherwise \u2014 and VRGS converts it into the project's\nvertical frame if a ",(0,r.jsx)(n.a,{href:"/docs/general/setup-system/project-properties#vertical-datum-and-orthometric-height",children:"vertical datum is\nconfigured"}),".\nOn means the altitude is already MSL/orthometric and is passed straight\nthrough without a second geoid correction."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Why it matters:"})," getting this wrong shifts the whole model vertically by the\nlocal geoid separation, which can be ",(0,r.jsx)(n.strong,{children:"tens of metres"}),". It is nothing to do\nwith horizontal accuracy, and it will not look obviously wrong \u2014 it just sits\nat the wrong height."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave ",(0,r.jsx)(n.strong,{children:"off"})," for DJI and most consumer drones. Turn ",(0,r.jsx)(n.strong,{children:"on"})," only\nif you know your camera or flight software wrote true MSL altitudes."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"gps-sigma-xy-m",children:"GPS sigma XY (m)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"5.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the assumed ",(0,r.jsx)(n.strong,{children:"horizontal"})," uncertainty of the GPS positions\u2014\nhow much to trust GPS versus the visual geometry. Smaller pulls cameras harder\nonto their GPS coordinates; larger lets the photo geometry dominate."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"1"}),"\u2013",(0,r.jsx)(n.code,{children:"2"})," m for RTK/PPK drones; ",(0,r.jsx)(n.code,{children:"5"}),"\u2013",(0,r.jsx)(n.code,{children:"10"})," m for consumer/phone EXIF."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"gps-sigma-z-m",children:"GPS sigma Z (m)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"10.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the assumed ",(0,r.jsx)(n.strong,{children:"vertical"})," uncertainty. GPS altitude is usually\nmuch less accurate than horizontal position, so this is normally larger than\n",(0,r.jsx)(n.em,{children:"GPS sigma XY"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"10"}),"\u2013",(0,r.jsx)(n.code,{children:"20"})," m for consumer EXIF altitude; smaller for survey-grade\nvertical control."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"gps-pair-max-distance-m",children:"GPS pair max distance (m)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"100.0"}),". ",(0,r.jsx)(n.code,{children:"0"})," disables it."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," when greater than ",(0,r.jsx)(n.code,{children:"0"})," and both photos in a pair have GPS,\nmatching is skipped if the cameras are farther apart than this. On large flights\nmost pairs cannot overlap, so this is a big speed-up. Pairs where ",(0,r.jsx)(n.strong,{children:"either"}),"\nphoto lacks GPS are always tried, so projects without GPS are unaffected."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," set to roughly 2\u20133\xd7 your photo spacing/footprint (e.g. ",(0,r.jsx)(n.code,{children:"30"}),"\u2013",(0,r.jsx)(n.code,{children:"50"})," m)\non a big survey. Raise it (or set ",(0,r.jsx)(n.code,{children:"0"}),") if photos that genuinely overlap are\nfailing to register \u2014 for example a convergent set shot from far apart, or\nimagery whose EXIF GPS is poor enough to push a real pair over the limit."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{type:"caution",children:(0,r.jsxs)(n.p,{children:["This is ",(0,r.jsx)(n.strong,{children:"on by default at 100 m"}),". If your survey has overlapping photos taken\nmore than 100 m apart \u2014 long-range oblique or telephoto shots, for instance \u2014\nthose pairs are dropped before matching is even attempted. Raise the value or set\nit to ",(0,r.jsx)(n.code,{children:"0"}),"."]})}),"\n",(0,r.jsx)(n.h3,{id:"bundle-adjustment",children:"Bundle adjustment"}),"\n",(0,r.jsx)(n.h4,{id:"ba-max-iterations-global",children:"BA max iterations (global)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"100"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the maximum optimiser iterations per global bundle-adjustment\npass. The solver usually converges well before the cap; more iterations can\nrefine a difficult solve at the cost of time."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave at ",(0,r.jsx)(n.code,{children:"100"}),"; drop to ",(0,r.jsx)(n.code,{children:"30"}),"\u2013",(0,r.jsx)(n.code,{children:"50"})," for fast previews."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"huber-threshold-px",children:"Huber threshold (px)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"2.0"}),", in ",(0,r.jsx)(n.strong,{children:"original-image"})," pixels."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," sets how much reprojection error bundle adjustment will\nbelieve before it decides an observation is probably wrong."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Every tie point contributes a ",(0,r.jsx)(n.em,{children:"residual"}),": the gap, in pixels, between where the\nsolved model says the point should appear in a photo and where the feature\nmatcher actually found it. Bundle adjustment moves the cameras and points to make\nthose gaps small. The question this setting answers is what to do with the gaps\nthat ",(0,r.jsx)(n.strong,{children:"stay"})," large."]}),"\n",(0,r.jsx)(n.p,{children:"Below the threshold, a residual is treated as ordinary measurement noise and\ncounts at full strength. Above it, the observation still counts \u2014 but its\ninfluence is capped rather than growing with the square of the error. So a tie\npoint that lands 40 px out is heard, not obeyed."}),"\n",(0,r.jsx)(n.p,{children:"That cap is what stops one bad match from wrecking a model. Without it, error\ngrows quadratically without limit, so a single gross mismatch \u2014 a repeated\ntexture, a moving object, a reflection matched across two photos \u2014 can outweigh\nhundreds of good observations and visibly bend the cameras around it. The wreckage\nusually shows up far from the actual mistake, which makes it painful to diagnose."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Lower"})," (",(0,r.jsx)(n.code,{children:"1.0"}),"\u2013",(0,r.jsx)(n.code,{children:"1.5"}),"): stricter. Reasonable for sharp, well-textured imagery\nwhere a large residual really does mean a bad match."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Higher"})," (",(0,r.jsx)(n.code,{children:"3.0"}),"\u2013",(0,r.jsx)(n.code,{children:"4.0"}),"): more forgiving. Reasonable for soft, noisy, or\nmotion-blurred imagery, where honest observations routinely land a few pixels\nout and a tight threshold would quietly discount most of your data."]}),"\n"]}),"\n",(0,r.jsxs)(n.admonition,{title:"These are not the same pixels as Epip
1olar RANSAC",type:"caution",children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Epipolar RANSAC (px)"})," is in ",(0,r.jsx)(n.em,{children:"detection-scale"})," pixels \u2014 measured on the\ndownsampled working image and scaled up internally. ",(0,r.jsx)(n.strong,{children:"Huber threshold (px)"})," is in\n",(0,r.jsx)(n.em,{children:"original-image"})," pixels. The same number means different things in the two boxes,\nand the gap is large: on a 45 MP frame at ",(0,r.jsx)(n.strong,{children:"Alignment image scale"})," ",(0,r.jsx)(n.code,{children:"Quarter"}),", the\nworking image is about 2.9\xd7 smaller, so ",(0,r.jsx)(n.code,{children:"2.0"})," detection px is roughly ",(0,r.jsx)(n.code,{children:"5.8"}),"\noriginal px."]}),(0,r.jsxs)(n.p,{children:["The practical consequence is that this threshold has to ",(0,r.jsx)(n.strong,{children:"track Max image\ndimension"}),". Downsample harder and feature positions get scaled up further, so\ntheir noise in original pixels grows; a threshold left where it was would sit\nbelow the noise floor and start treating routine noise as suspect. The fit would\nnot break \u2014 it would lean toward a median rather than a mean \u2014 but you would lose\nprecision you had paid for."]}),(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"quality presets do this for you"}),": ",(0,r.jsx)(n.code,{children:"4.0"})," for Fast (working resolution\n",(0,r.jsx)(n.code,{children:"1600"}),"), ",(0,r.jsx)(n.code,{children:"2.0"})," for Balanced (",(0,r.jsx)(n.code,{children:"3200"}),"), ",(0,r.jsx)(n.code,{children:"1.5"})," for Accurate (no downsampling). You\nonly need to move it yourself if you set ",(0,r.jsx)(n.strong,{children:"Alignment image scale"})," by hand, in which\ncase scale it inversely \u2014 halve the resolution, roughly double this."]})]}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:["This applies to ",(0,r.jsx)(n.strong,{children:"image observations only"}),", across every bundle-adjustment pass\n(local, global, and the ground-control refinement). GPS and ground-control priors\nare robustified separately at a fixed 2 sigma, in units of their own quoted\naccuracy, and are unaffected by this setting \u2014 tune those with ",(0,r.jsx)(n.strong,{children:"GPS sigma XY /\nZ"})," and the per-point accuracies in your control CSV instead."]})}),"\n",(0,r.jsx)(n.h3,{id:"dense-mvs",children:"Dense MVS"}),"\n",(0,r.jsxs)(n.p,{children:["Dense MVS runs ",(0,r.jsx)(n.strong,{children:"after"})," sparse SfM and produces the dense surface cloud. It is\noff by default."]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-learned",children:"Dense MVS (learned)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," off."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the master on/off switch for the dense stage. When on, a dense\ncoloured point cloud (",(0,r.jsx)(n.code,{children:"<name> dense"}),") is produced in addition to the sparse one."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," turn ",(0,r.jsx)(n.strong,{children:"on"})," whenever you want a dense surface to mesh or interpret."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-backend",children:"Dense MVS backend"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"dense_mvs_backend"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," (default), ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP_GPU"}),", ",(0,r.jsx)(n.code,{children:"CASMVSNET"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," chooses the depth estimator.","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," is the built-in CPU plane-sweep (normalised cross-correlation).\nIt needs no model file and runs anywhere. Roughly ",(0,r.jsx)(n.strong,{children:"22 seconds per view"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PLANE_SWEEP_GPU"})," is the ",(0,r.jsx)(n.em,{children:"same algorithm"})," as Vulkan compute shaders, on any\nVulkan 1.1 GPU. Roughly ",(0,r.jsx)(n.strong,{children:"0.2 seconds per view"})," \u2014 a 370-view survey sweeps in\nabout a minute instead of two and a quarter hours. It needs no model file and\nno 3D view open, and it falls back to ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," for the whole run if no\nsuitable device is available, reporting why in the log."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"CASMVSNET"})," is a learned neural-network estimator that requires a\n",(0,r.jsx)(n.code,{children:"casmvsnet.onnx"})," model in the project ",(0,r.jsx)(n.code,{children:"MODELS"})," folder and a CUDA-capable GPU."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Do the two plane sweeps agree?"})," They are held to the same result by\ndifferential tests: on a synthetic scene the GPU chain reproduces the CPU\nsweep's winning depth bin on 196,606 of 196,608 pixels, with identical\nvalidity. They share one code path for the depth bracket and for every\npost-filter, so they cannot drift on those at all."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," use ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP_GPU"})," if your machine has a Vulkan GPU \u2014 it is the\nsame result, far faster, and it is what makes a raised ",(0,r.jsx)(n.strong,{children:"depth hypotheses"}),"\ncount affordable. Choose ",(0,r.jsx)(n.code,{children:"CASMVSNET"})," only if you have the model file and a GPU\nand want to compare learned depth."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"depth-map-quality",children:"Depth map quality"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"depth_map_quality"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"Full"}),", ",(0,r.jsx)(n.code,{children:"Half (1/2)"}),", ",(0,r.jsx)(n.code,{children:"Quarter (1/4)"})," (default),\n",(0,r.jsx)(n.code,{children:"Eighth (1/8)"}),", ",(0,r.jsx)(n.code,{children:"Sixteenth (1/16)"})," \u2014 the same relative ladder as\n",(0,r.jsx)(n.a,{href:"#alignment-image-scale",children:"Alignment image scale"}),", applied to dense matching."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the working resolution for dense depth estimation, relative\nto each photo. ",(0,r.jsx)(n.strong,{children:"Cost and memory grow with the square of this"}),", so one rung up\nis roughly four times the work."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Memory:"})," the plane-sweep grid is trimmed if it will not fit its memory\nbudget, and the log reports both the grid actually used and the budgets it was\nmeasured against. On the ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP_GPU"}
1)," backend the cost volumes live in\ngraphics memory, so only fusion is priced against system RAM."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CasMVSNet:"})," its ONNX input is locked at 1152\xd7864, so for that backend\n",(0,r.jsx)(n.code,{children:"Half"})," and ",(0,r.jsx)(n.code,{children:"Full"})," buy detail by ",(0,r.jsx)(n.strong,{children:"tiling"})," instead, at roughly (T+1)\xb2 inferences\nper view."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"Quarter"})," is the default and is a good balance. Matches\nMetashape's depth map quality ladder: their Ultra High = ",(0,r.jsx)(n.code,{children:"Full"}),", High = ",(0,r.jsx)(n.code,{children:"Half"}),",\nMedium = ",(0,r.jsx)(n.code,{children:"Quarter"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-neighbor-views",children:"Dense MVS neighbor views"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"dense_mvs_num_views"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"5"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," how many neighbouring source views are used to estimate depth\nfor each reference view. More views give more robust depth (slower); fewer are\nfaster but noisier."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"5"})," is a good default; try ",(0,r.jsx)(n.code,{children:"7"})," for wide-baseline captures, ",(0,r.jsx)(n.code,{children:"3"})," for\nspeed. (The ",(0,r.jsx)(n.code,{children:"CASMVSNET"})," backend uses the view count baked into its model.)"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-depth-hypotheses",children:"Dense MVS depth hypotheses"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"dense_mvs_num_depths"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"128"}),". Allowed: a multiple of 32 from ",(0,r.jsx)(n.code,{children:"32"})," to ",(0,r.jsx)(n.code,{children:"1024"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," how many candidate depths the sweep tests per pixel. The\nsweep spaces them evenly in ",(0,r.jsx)(n.em,{children:"inverse"})," depth, which is the right choice for\nperspective imagery but means the hypotheses are ",(0,r.jsx)(n.strong,{children:"fine near the camera and\ncoarse far away"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave at ",(0,r.jsx)(n.code,{children:"128"})," for close-range or flat-on captures. Raise to ",(0,r.jsx)(n.code,{children:"256"}),"\nor ",(0,r.jsx)(n.code,{children:"512"})," ",(0,r.jsx)(n.strong,{children:"on the GPU backend"})," when the log shows wide depth brackets, or when\nyou see a plume of points projecting away from the camera below the outcrop. If\na value is too large for your GPU the run reports the device limit and falls\nback to the CPU sweep rather than producing a wrong cloud. The ",(0,r.jsx)(n.code,{children:"CASMVSNET"}),"\nbackend ignores this setting; its hypotheses are baked into the exported model."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The depth resolution at the ",(0,r.jsx)(n.strong,{children:"far"})," end of a view's range is\n",(0,r.jsx)(n.code,{children:"(far / near - 1) / (hypotheses - 1)"}),", expressed as a fraction of the depth\nitself. A view whose visible scene spans 2 m to 42 m \u2014 a 20:1 range, ordinary\nwhen looking along a cliff \u2014 therefore resolves its far end in steps of about\n15%, or ",(0,r.jsx)(n.strong,{children:"5.5 metres"})," at 42 m, when 128 hypotheses are used."]}),"\n",(0,r.jsxs)(n.p,{children:["That shows up in the cloud as points landing on discrete shells metres apart: a\nplume projecting ",(0,r.jsx)(n.strong,{children:"away from the camera"}),", typically below and behind the\noutcrop. It is a quantisation artefact rather than a matching failure, so no\nconfidence, consistency or outlier filter can remove it \u2014 a measurement whose\nquantum is 5 m cannot be repaired downstream. The cure is more hypotheses, or a\nnarrower depth range."]}),"\n",(0,r.jsx)(n.p,{children:"To keep far-end resolution under about 4%:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Depth range (far / near)"}),(0,r.jsx)(n.th,{children:"Hypotheses needed"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"5:1"}),(0,r.jsx)(n.td,{children:"128 \u2014 the default is fine"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"10:1"}),(0,r.jsx)(n.td,{children:"256"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"20:1"}),(0,r.jsx)(n.td,{children:"512"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Your own ranges are in the log. Each view reports a line like\n",(0,r.jsx)(n.code,{children:"MVS depth diag view 370: bracket 2.10643-42.4069 m over 128 bins (bin width 0.0158811 m near / 5.55205 m far)"}),". If that ",(0,r.jsx)(n.strong,{children:"far"})," bin width is a significant\nfraction of the detail you care about, raise the setting."]}),"\n",(0,r.jsxs)(n.p,{children:["Time and memory both scale linearly with the count. On ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP_GPU"})," that is\nabout 0.08 s per view at 128 and 0.33 s at 512, which is what makes the setting\npractical. On ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," the same change takes a view from roughly 22 s to\n88 s ",(0,r.jsx)(n.strong,{children:"and shrinks the working image"}),": the CPU backend holds its cost volumes in\nsystem RAM, so the memory budget trades image area for hypotheses, and 128 \u2192 512\ncosts about three quarters of the grid. The GPU backend keeps those volumes in\ngraphics memory and checks the graphics heap instead, so raising the count there\ncosts time only."]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-confidence-cutoff",children:"Dense MVS confidence cutoff"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0.75"})," (used by the ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," backend)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," per-pixel confidence threshold on the plane-sweep result,\nmapped as ",(0,r.jsx)(n.code,{children:"(NCC + 1) / 2"}),", so ",(0,r.jsx)(n.code,{children:"0.75"})," corresponds to ",(0,r.jsx)(n.code,{children:"NCC >= 0.5"})," (a\nwell-matched, textured pixel). Pixels below the cutoff are discarded before\nfusion. ",(0,r.jsx)(n.strong,{children:"Raise for sparser/cleaner output; lower for denser/noisier."})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"0.75"})," default; ",(0,r.jsx)(n.code,{children:"0.6"})," (",(0,r.jsx)(n.code,{children:"NCC >= 0.2"}),") to densify a sparse cloud once\ndepth is trustworthy; ",(0,r.jsx)(n.code,{children:"0.85"})," for a cleaner cloud."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-casmvsnet-confidence-cutoff",children:"Dense MVS CasMVSNet confidence cutoff"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0.3"})," (used only when backend is ",(0,r.jsx)(n.code,{children:"CASMVSNET"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the equivalent cutoff for the learned backend. Its confidence\nis a network probability on a different scale (it peaks much lower than the\nplane-sweep value), so this default is far below ",(0,r.jsx)(n.code,{children:"0.75"}),". Applying the\nplane-sweep cutoff to it would reject almost every point."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"0.3"})," default; raise to ",(0,r.jsx)(n.code,{children:"0.4"}),"\u2013",(0,r.jsx)(n.code,{children:"0.5"})," for a cleaner learned cloud."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-consistent-views",children:"Dense MVS consistent views"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"2"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," a fused point is kept only if at least this many ",(0,r.jsx)(n.strong,{children:"other"}),"\nreference views agree on its depth (so ",(0,r.jsx)(n.code,{children:"2"})," means a 3-view consensus including\nthe source view). ",(0,r.jsx)(n.strong,{children:"Higher = cleaner/sparser; lower = denser/noisier."})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"2"})," default; ",(0,r.jsx)(n.code,{children:"1"})," for maximum density (noisier); ",(0,r.jsx)(n.code,{children:"3"})," for a clean\ncloud on high-overlap data."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-depth-tolerance",children:"Dense MVS depth tolerance"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0.025"})," (= 2.5%)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the relative depth-agreement tolerance used by the\ncross-view consistency check above. Loosen it to keep more points; tighten it\nfor a cleaner cloud."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," raise to ",(0,r.jsx)(n.code,{children:"0.04"})," if the cloud is too sparse once per-view depth is\ngood; drop to ",(0,r.jsx)(n.code,{children:"0.015"})," for a tighter, cleaner surface."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-sky-filter",children:"Dense MVS sky filter"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," runs the bundled ",(0,r.jsx)(n.strong,{children:"SkyWater SegFormer"})," model over each\nundistorted dense-MVS image and removes the predicted ",(0,r.jsx)(n.strong,{children:"sky"})," before depth\nmatching and fusion. Water and person predictions are not removed. VRGS keeps\nsky predictions with probability at least ",(0,r.jsx)(n.code,{children:"0.5"})," only when they belong to a\ncomponent connected to an image border; this avoids cutting isolated pale or\nblue areas out of the rock. It then expands the mask by 3 pixels at dense-MVS\nresolution to cover the mixed sky/foreground pixels that create white or blue\nsilhouette halos."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Effect on reconstruction:"})," masked pixels are excluded from plane-sweep and\nCasMVSNet matching. Any remaining masked depth and confidence values are also\ncleared before cross-view fusion, so sky cannot re-enter the dense cloud from\na later stage."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave this ",(0,r.jsx)(n.strong,{children:"on"})," for outcrops, cliffs and landscapes photographed\nagainst the sky. Turn it off only when the sky itself must remain eligible for\nreconstruction or while comparing diagnostic runs. The fusion log reports a\n",(0,r.jsx)(n.code,{children:"sky-masked"})," count showing how many depth pixels the active sky filter removed."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["VRGS ships FP16 and FP32 ONNX versions of the model. It tries FP16 on CUDA first,\nthen FP32 on CUDA, and finally FP32 on the CPU, so the filter still works on a\nmachine without a compatible NVIDIA GPU. A project-specific model with the same\nfilename in the project's ",(0,r.jsx)(n.code,{children:"MODELS"})," folder takes precedence over the installed\ncopy. If no model can be loaded, or inference fails for one photograph, VRGS logs\nthe reason and uses the older colour-and-confidence sky test for the affected\nview instead of failing the dense reconstruction."]}),"\n",(0,r.jsxs)(n.p,{children:["Semantic probability maps are cached as PNG files in\n",(0,r.jsx)(n.code,{children:"<workspace>/sky_masks/"}),". Re-running Dense MVS normally reuses them; changing the\nsource image, image geometry, model file or preprocessing version invali
1dates the\ncorresponding cache entry automatically."]}),"\n",(0,r.jsxs)(n.p,{children:["To check the segmentation visually, open the ",(0,r.jsx)(n.strong,{children:"Photo Browser"}),", right-click the\nimage and choose ",(0,r.jsx)(n.strong,{children:"Show Sky Mask (magenta)"}),". VRGS overlays the exact sky mask\nused by the most recent successful Dense MVS run on its undistorted MVS image.\nThe command is disabled until a successful run has produced a preview for that\nphoto. Mask-preview mode is read-only and temporarily hides tie-point, keypoint\nand ground-control-point markers because those markers use the original photo's\ndistorted pixel coordinates. Choose the command again to return to the original\nphoto."]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-fusion-voxel",children:"Dense MVS fusion voxel"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"dense_mvs_fuse_voxel_size"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0"})," \u2014 size it from the imagery. Otherwise a voxel edge in ",(0,r.jsx)(n.strong,{children:"metres"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," fusion bins every surviving depth observation into a regular\n3D grid and emits one point per occupied cell, at the confidence-weighted\ncentroid of the observations in it. That is what stops two views of the same\nrock face producing a pair of slightly offset parallel surfaces. It also means\n",(0,r.jsx)(n.strong,{children:"the voxel size is the output resolution"}),", and it is by far the largest\ninfluence on final point count, which scales as ",(0,r.jsx)(n.code,{children:"1 / voxel\xb2"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave at ",(0,r.jsx)(n.code,{children:"0"}),". Set a larger value (say ",(0,r.jsx)(n.code,{children:"0.02"}),") when you want a\nlighter cloud for interpretation or export; there is little point setting it\nsmaller than auto, because that subdivides a single pixel's footprint and only\nsplits noise."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Auto uses one ",(0,r.jsx)(n.strong,{children:"ground sample distance"})," \u2014 ",(0,r.jsx)(n.code,{children:"depth / focal length in MVS pixels"}),",\nthe spacing of two adjacent depth-map pixels where they land on the rock. That\naverages across ",(0,r.jsx)(n.strong,{children:"views"}),", which is the noise reduction the voxel grid exists\nfor, without averaging across ",(0,r.jsx)(n.strong,{children:"surface"}),", which is detail the sweep measured.\nIt therefore follows the thing that actually determines resolution: how far away\nthe subject is and what resolution you swept it at."]}),"\n",(0,r.jsx)(n.p,{children:"The fusion log names the choice and the reason:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Fusion voxel 0.00372 m (auto: one ground sample, i.e. depth / focal in mvs pixels) over 446948470 observations\n"})}),"\n",(0,r.jsxs)(n.admonition,{title:"A gridded-looking cloud means the voxel is too coarse",type:"tip",children:[(0,r.jsxs)(n.p,{children:["If the points look laid out on a ",(0,r.jsx)(n.strong,{children:"regular lattice"})," rather than scattered over\nthe surface, the voxel is much larger than a ground sample. Each emitted point\nis then the mean of many observations spread through its cell, and the mean of\nmany samples in a cube converges on the cube's centre \u2014 so every point sits near\na grid node."]}),(0,r.jsxs)(n.p,{children:["Divide ",(0,r.jsx)(n.code,{children:"voxels"})," by the ",(0,r.jsx)(n.code,{children:"consistency>=2"})," count in the fusion funnel to see how\nmany observations are being averaged into each point. A handful is healthy;\nhundreds means the voxel is far coarser than the imagery and detail is being\naveraged away. Setting ",(0,r.jsx)(n.code,{children:"0"})," restores the automatic size."]})]}),"\n",(0,r.jsx)(n.h4,{id:"dense-mvs-min-component-size",children:"Dense MVS min component size"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"200"})," points (",(0,r.jsx)(n.code,{children:"0"})," disables)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," after fusion and outlier removal, the cloud is split into\nspatially connected components and any isolated clump with fewer than this\nmany points is deleted (the largest component is always kept, so the filter\ncan never remove the model itself). This is the filter that kills the small\nfloating patches of noise that can appear ",(0,r.jsx)(n.strong,{children:"below"})," the model over\nlow-texture or repetitive ground: those patches are locally dense, so the\nstatistical outlier stage cannot tell them apart from real surface \u2014 only\ntheir isolation gives them away."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," if a few larger islands survive, raise it (",(0,r.jsx)(n.code,{children:"500"}),"\u2013",(0,r.jsx)(n.code,{children:"1000"}),"); if a\ngenuinely separate part of the scene (a detached block, a second outcrop\nface) is being removed, lower it or set ",(0,r.jsx)(n.code,{children:"0"}),". The fusion log reports how many\nislands and points were dropped."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"texturing",children:"Texturing"}),"\n",(0,r.jsx)(n.p,{children:"These control the photo-texture bake, not the reconstruction. All of them are\ndiagnosable from the mesh attributes the bake writes."}),"\n",(0,r.jsx)(n.h4,{id:"texture-multi-photo-blend",children:"Texture multi-photo blend"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_blend_enabled"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," blends every photograph that can see a texel, weighted by how\nwell each sees ",(0,r.jsx)(n.em,{children:"that point"}),", instead of taking one photo per triangle."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave on. Turn it ",(0,r.jsx)(n.strong,{children:"off"})," to see where the photo boundaries\nactually are \u2014 they become hard-edged, which is useful for diagnosing str
1ipes."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"texture-blend-sharpness",children:"Texture blend sharpness"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_blend_sharpness"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"4.0"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the seam-versus-sharpness dial for the blend. ",(0,r.jsx)(n.strong,{children:"High"}),"\nconfines mixing to a narrow band \u2014 and a narrow band between two photos that\ndisagree shows as a stripe. ",(0,r.jsx)(n.strong,{children:"Low"})," blends broadly, which dissolves stripes but\nsoftens detail."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," try ",(0,r.jsx)(n.code,{children:"1.0"})," if you see stripes."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"texture-lens-distortion",children:"Texture lens distortion"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_distortion"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Options / default:"})," ",(0,r.jsx)(n.code,{children:"Auto"})," (default), ",(0,r.jsx)(n.code,{children:"On"}),", ",(0,r.jsx)(n.code,{children:"Off"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," whether to apply the solved lens distortion when sampling\nphotographs. ",(0,r.jsx)(n.code,{children:"Auto"})," ",(0,r.jsx)(n.strong,{children:"measures"})," it: each bake compares, over points two photos\nboth see, how well those photos agree with the model applied and without, and\nuses the winner when the margin is decisive (>5%)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"When to override:"})," ",(0,r.jsx)(n.code,{children:"Off"})," is right for any camera that de-warps in firmware \u2014\n",(0,r.jsx)(n.strong,{children:"all DJI drones and most phones"})," \u2014 where the JPEG is already corrected even\nthough it is the original file. ",(0,r.jsx)(n.code,{children:"On"})," is right for a genuinely raw lens with a\nwell-conditioned solve. Both are obeyed exactly: the measurement still runs and\nis reported, but will not override you."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Getting it wrong"})," displaces samples radially from each photo's centre, and\nthe blend turns that into smeared bands."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"texture-max-uv-stretch",children:"Texture max UV stretch"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_max_uv_anisotropy"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"8.0"}),". ",(0,r.jsx)(n.code,{children:"0"})," turns the check off."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," refuses to photo-texture a triangle whose UV mapping is\nstretched more than this ratio (1 = undistorted, over 4 is visibly smeared,\nover 16 is a streak). Rejected triangles take vertex colour instead."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it fixes:"}),' ribbons of repeating "ladder" texture that survive every\ncalibration change. The photograph is innocent \u2014 the ',(0,r.jsx)(n.strong,{children:"unwrap"})," is at fault: a\nsliver triangle squeezed to near-zero width in UV space replicates one row of\nsource pixels along its whole length. Because the bake writes through the same\nUVs the renderer reads, the smear is baked in and model-locked, so it does not\nmove with the camera. Usually a fraction of a percent of the mesh, but each one\nis long, so they dominate visually."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"texture-max-view-angle-deg",children:"Texture max view angle (deg)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_max_view_angle_deg"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"75"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"}),' rejects a photograph whose view of a surface is more grazing\nthan this, in degrees from the surface normal. A source pixel seen at angle \u03b8 is\nsmeared across 1/cos \u03b8 of surface, so 75\xb0 is a 4\xd7 smear and 84\xb0 is 10\xd7 \u2014 which\nis what the thin banded "comb" patches on
1steep faces and in recesses are.']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," lower it if you see banding; raise it if you would rather have a\nsmeared texture than a flat facet. Square-on views already win on score, so this\nonly decides what happens where ",(0,r.jsx)(n.strong,{children:"no"})," good view exists. Colour the mesh by the\n",(0,r.jsx)(n.code,{children:"Texture view angle (deg)"})," attribute the bake writes to see which areas are\naffected."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"texture-rescue-occluded-triangles",children:"Texture rescue occluded triangles"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Setting key:"})," ",(0,r.jsx)(n.code,{children:"texture_rescue_occluded"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," off."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," textures a triangle from its best-scoring photograph even when\nevery photograph considers it occluded."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," turn it on when genuinely visible surface is being left untextured\nby an over-eager occlusion test; leave it off otherwise, since the rescued\ncolour comes from a photo that may be looking at something in front."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"diagnostics--output",children:"Diagnostics & output"}),"\n",(0,r.jsx)(n.h4,{id:"write-undistorted-previews",children:"Write undistorted previews"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," saves an undistorted JPEG per registered view to\n",(0,r.jsx)(n.code,{children:"<workspace>/undistorted/"}),". Useful for quality control and for downstream tools\nthat expect undistorted images."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," turn ",(0,r.jsx)(n.strong,{children:"off"})," to save disk space and a little time once you no\nlonger need the previews."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"verbose-logging",children:"Verbose logging"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," off."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," emits extra per-photo, per-pair, and per-cache diagnostic\nmessages to the log."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," turn ",(0,r.jsx)(n.strong,{children:"on"})," while tuning parameters or troubleshooting a run; turn\noff for clean logs."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"ground-control-points",children:"Ground control points"}),"\n",(0,r.jsxs)(n.p,{children:["Ground control points (GCPs) are physical targets whose coordinates you surveyed.\nMarking them in the photographs lets VRGS fit the model to ",(0,r.jsx)(n.strong,{children:"your survey"})," instead\nof to consumer GPS \u2014 centimetres instead of metres \u2014 and, just as importantly,\nlets you ",(0,r.jsx)(n.strong,{children:"measure"})," how accurate the result actually is."]}),"\n",(0,r.jsx)(n.h3,{id:"1-import-the-survey",children:"1. Import the survey"}),"\n",(0,r.jsxs)(n.p,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Import Ground Control Points (CSV)\u2026"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The importer is deliberately tolerant of what survey instruments actually export:\nbyte-order marks, Windows or Unix line endings, quoted fields, comment lines,\ncomma ",(0,r.jsx)(n.strong,{children:"or"})," semicolon ",(0,r.jsx)(n.strong,{children:"or"})," tab ",(0,r.jsx)(n.strong,{children:"or"})," space delimiters (auto-detected), and\ndecimal commas (",(0,r.jsx)(n.code,{children:"547871,5"}),"). Pick the file and it will guess your column mapping\nfrom the header; confirm or correct it, and check the preview grid before\nimporting."]}),"\n",(0,r.jsx)(n.p,{children:"Two things it will not do quietly:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ambiguous numbers are refused, not guessed."})," ",(0,r.jsx)(n.code,{children:"1.234,5"})," could be 1234.5 or\n1.234, so it fails loudly rather than silently importing a wrong coordinate."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Every dropped row is reported"}),", with the line number as it appears in your\nfile, in the messages panel."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Coordinates are in"})," \u2014 either the project's own coordinate system (the usual\ncase: your surveyor delivered grid coordinates) or WGS84 longitude/latitude, which\nVRGS reprojects on import. Everything is stored in the project CRS, so mixed\nframes never reach the solver."]}),"\n",(0,r.jsxs)(n.p,{children:["Imported points appear immediately in the 3D view and under ",(0,r.jsx)(n.strong,{children:"Ground Control\nPoints"})," in the Data Tree \u2014 ",(0,r.jsx)(n.em,{children:"before"})," you spend an hour marking them. This is the\nfastest way to catch a wrong CRS or a swapped easting/northing: if the control\nlands in the wrong county, stop now."]}),"\n",(0,r.jsx)(n.h3,{id:"2-mark-them-in-the-photographs",children:"2. Mark them in the photographs"}),"\n",(0,r.jsxs)(n.p,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Photo Browser"}),", right-click \u2192 ",(0,r.jsx)(n.strong,{children:"Mark Ground Control Point"})," and choose\na point from the list (it shows how many photos each one is marked in). Click the\ntarget in the image. Click again to correct the position; ",(0,r.jsx)(n.strong,{children:"Shift+click"})," removes\na mark; ",(0,r.jsx)(n.strong,{children:"Escape"})," or ",(0,r.jsx)(n.strong,{children:"Stop Marking"})," leaves the mode."]}),"\n",(0,r.jsxs)(n.p,{children:['GCP marks are drawn as magenta boxed crosses, deliberately unlike the tie-point\ncircles \u2014 a tie point says "these clicks are the same thing", a GCP says "this\nclick is ',(0,r.jsx)(n.em,{children:"that surveyed coordinate"}),'".']}),"\n",(0,r.jsx)(n.admonition,{title:"Each point needs two photos",type:"note",children:(0,r.jsxs)(n.p,{children:["A control point cannot be located in 3D until it is marked in ",(0,r.jsx)(n.strong,{children:"at least two"}),"\nregistered photographs. Three or more marked points are needed before they can\ndrive the georeferencing."]})}),"\n",(0,r.jsx)(n.h3,{id:"3-re-run",children:"3. Re-run"}),"\n",(0,r.jsxs)(n.p,{children:["With 3+ usable control points, GCPs ",(0,r.jsx)(n.strong,{children:"take over"})," the georeferencing from GPS,\nwhich drops back to a soft constraint on cameras far from any control. The log\
1nsays which drove the fit \u2014 it never happens silently."]}),"\n",(0,r.jsx)(n.h3,{id:"control-points-vs-check-points",children:"Control points vs check points"}),"\n",(0,r.jsxs)(n.p,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Use as control"})," off on a point (in its properties) to make it a ",(0,r.jsx)(n.strong,{children:"check\npoint"}),": it is measured but never fitted."]}),"\n",(0,r.jsxs)(n.p,{children:["This matters more than it sounds. A control point's residual only tells you how\nwell the fit ",(0,r.jsx)(n.em,{children:"absorbed"})," it \u2014 fit enough points and you can drive that number to\nalmost zero while the model is still wrong elsewhere. A check point is held back\nfrom the fit, so its residual is the ",(0,r.jsx)(n.strong,{children:"only honest estimate"})," of the model's real\naccuracy. The quality report RMSs the two separately and tells you when you have\nno check points at all."]}),"\n",(0,r.jsx)(n.admonition,{title:"Spread your control",type:"caution",children:(0,r.jsx)(n.p,{children:"Control pegged out along a single line \u2014 down one road or one bench \u2014 cannot fix\nrotation about that line. VRGS refuses such a fit rather than emit a model rolled\nby whatever the noise favoured, and says so in the log. Spread the points across\nthe site, and in height where you can."})}),"\n",(0,r.jsx)(n.h3,{id:"using-control-elsewhere-in-the-project",children:"Using control elsewhere in the project"}),"\n",(0,r.jsx)(n.p,{children:"Ground control points are ordinary project objects, not an SfM-only feature:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Use Project Ground Control Points"})," adopts control that already exists in the\nproject (surveyed for something else, or imported before this workflow existed)\ninto the workflow."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Seed Mesh Tie Points from Ground Control"})," turns ",(0,r.jsx)(n.em,{children:"measured"})," control into the\nmesh's own tie points, so the mesh ribbon's ",(0,r.jsx)(n.strong,{children:"Corrections \u2192 GCP"})," can pull an\nexisting mesh onto the survey. Most useful when a model was GPS-aligned and the\nGCPs were check points: the residuals are real, and this is the one-click way\nto consume them."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"scale-bars",children:"Scale bars"}),"\n",(0,r.jsxs)(n.p,{children:["A photograph set can never recover its own ",(0,r.jsx)(n.strong,{children:"scale"}),": a model of a quarry and a\nmodel of a sandpit look identical to the solver. GPS and ground control both give\nscale as a side effect of giving position. When you have neither \u2014 hand-held\nphotos of an outcrop with no GPS and no survey \u2014 a single measured distance is\nenough."]}),"\n",(0,r.jsxs)(n.p,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Scale Bars (Known Distances)\u2026"}),". Pick the two tie\npoints at either end of something you measured (a ranging rod, a tape, a known\nfeature), type the distance in metres, and ",(0,r.jsx)(n.strong,{children:"Add"}),". Re-run the reconstruction."]}),"\n",(0,r.jsxs)(n.p,{children:["Both ends must be ",(0,r.jsx)(n.strong,{children:"tie points linked across at least two photographs"}),", so mark\nthem first in the Photo Browser (Ctrl+click a point to start a link, then click\nthe same feature in another photo). Only links that qualify are offered."]}),"\n",(0,r.jsx)(n.admonition,{title:"Scale is not georeferencing",type:"note",children:(0,r.jsxs)(n.p,{children:["A scale bar gives the model real ",(0,r.jsx)(n.strong,{children:"size"}),". It does not give it a position or an\norientation on the Earth. A scale-bar-only model is metric \u2014 you can measure\ndistances on it \u2014 but it is ",(0,r.jsx)(n.strong,{children:"not"})," georeferenced and must not be measured against\nother project data. The quality report says this explicitly."]})}),"\n",(0,r.jsxs)(n.p,{children:["If GPS or ground control already scaled the model, scale bars are ",(0,r.jsx)(n.strong,{children:"not"})," applied\n\u2014 re-scaling would pull the model off the coordinates that placed it. They are\nstill measured, which makes them a free independent check of the GPS/GCP scale."]}),"\n",(0,r.jsxs)(n.p,{children:["Several bars are combined by the ",(0,r.jsx)(n.strong,{children:"median"}),", so one mistyped distance (a 1 m rod\nentered as ",(0,r.jsx)(n.code,{children:"10"}),") cannot drag the whole model's scale."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"quality-report",children:"Quality report"}),"\n",(0,r.jsxs)(n.p,{children:["Right-click the workflow \u2192 ",(0,r.jsx)(n.strong,{children:"Show Quality Report"}),". It goes to the messages panel\nand to ",(0,r.jsx)(n.code,{children:"quality_report.txt"})," in the workflow's workspace folder, so it can be\nhanded over with a deliverable."]}),"\n",(0,r.jsx)(n.p,{children:"It answers, in one place:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Registration"})," \u2014 how many photos registered, and which ones did not."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Reprojection error"})," \u2014 RMS plus median, 90th and 99th percentiles. If the 99th\nexceeds 5\xd7 the median the report says so: a handful of outliers is dominating\nthe RMS, the bulk of the model is better than that number suggests, and the fix\nis to find the outliers rather than to distrust the whole thing."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Georeferencing"})," \u2014 what placed the model (GCPs, GPS, or nothing) and how well\
1nit fitted."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ground control accuracy"})," \u2014 per-point dX/dY/dZ and 3D error, with control and\ncheck points RMS'd ",(0,r.jsx)(n.strong,{children:"separately"}),". A point that was never measured is reported as\nsuch, not as a zero error."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Scale bars"})," \u2014 expected vs. actual, and whether they were applied or are acting\nas a check."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Worst photographs"})," \u2014 the ten with the highest reprojection error, which is\nusually where a bad solve is hiding."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"inspecting-the-sparse-cloud",children:"Inspecting the sparse cloud"}),"\n",(0,r.jsx)(n.p,{children:"The sparse cloud carries two attributes you can colour by, filter on, and\nhistogram like any other VRGS attribute:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Observations"})," \u2014 how many cameras actually see each point. Thin coverage is\nwhere a reconstruction is weakest. Colour by this ",(0,r.jsx)(n.strong,{children:"before"})," launching a dense\nMVS run that will spend hours on geometry the sparse solve never really pinned\ndown."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Reprojection error"})," \u2014 the per-point residual in pixels. A healthy RMS can\nhide a bad tail; filtering to the worst points shows you exactly ",(0,r.jsx)(n.strong,{children:"where"})," the\nmodel is unhappy, rather than just telling you that it is."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"building-a-mesh",children:"Building a mesh"}),"\n",(0,r.jsxs)(n.p,{children:["Once you have a ",(0,r.jsx)(n.strong,{children:"dense"})," cloud (",(0,r.jsx)(n.code,{children:"<name> dense"}),"), turn it into a triangulated\nsurface. Both methods run on a background thread, copy the cloud's per-vertex\ncolour onto the mesh, and add ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"<name> mesh"})})," to the project. Right-click the\nworkflow and pick one:"]}),"\n",(0,r.jsx)(n.admonition,{title:"Needs a dense cloud",type:"note",children:(0,r.jsxs)(n.p,{children:["Create Mesh works on the dense MVS cloud, so enable ",(0,r.jsx)(n.strong,{children:"Dense MVS (learned)"})," and\nrun a reconstruction first. If the dense cloud is missing or still loading, the\naction tells you so."]})}),"\n",(0,r.jsx)(n.h3,{id:"create-mesh-poisson",children:"Create Mesh (Poisson)"}),"\n",(0,r.jsxs)(n.p,{children:["Poisson reconstruction fits a single ",(0,r.jsx)(n.strong,{children:"watertight"})," surface through the points \u2014\nit fills small gaps and gives clean, closed geometry. Best default for outcrops."]}),"\n",(0,r.jsx)(n.h4,{id:"octree-depth",children:"Octree depth"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0"})," = ",(0,r.jsx)(n.strong,{children:"auto"})," \u2014 matched to the measured point spacing."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the resolution budget of the whole reconstruction. Poisson\nbuilds the surface on a grid of 2^depth cells across the model, and ",(0,r.jsx)(n.strong,{children:"nothing\nsmaller than one cell can appear in the mesh, however dense the cloud is"}),". At\ndepth ",(0,r.jsx)(n.code,{children:"8"}),' a 300 m outcrop gets ~1.3 m cells \u2014 a 5-million-point cloud with\ncentimetre spacing comes out looking melted, which is the classic "dense cloud\nlooks great, mesh looks poor" complaint. Auto sizes the cell to ~2\xd7 the cloud\'s\npoint spacing (capped at depth 13), and the log prints the chosen depth, cell\nsize and spacing so the budget is visible. Each +1 of depth halves the cell and\nroughly quadruples time and memory.']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," leave at ",(0,r.jsx)(n.code,{children:"0"}),". Set an explicit ",(0,r.jsx)(n.code,{children:"9"}),"\u2013",(0,r.jsx)(n.code,{children:"10"})," only when you ",(0,r.jsx)(n.em,{children:"want"})," a\nlighter, generalised mesh \u2014 for draping or as a fast preview."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"samples-per-node",children:"Samples per node"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"4"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," how much noise averaging Poisson applies: how many input\npoints fall in a leaf cell before it is fully trusted. ",(0,r.jsx)(n.code,{children:"1"})," interpolates every\npoint exactly \u2014 right for clean laser scans, wrong for photogrammetry, whose\ndense clouds carry centimetre-scale noise that would be reconstructed as\nsurface bumps at high depth. ",(0,r.jsx)(n.code,{children:"4"}),"\u2013",(0,r.jsx)(n.code,{children:"8"})," averages that noise away while the octree\ndepth carries the real detail."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"4"})," for drone photogrammetry; ",(0,r.jsx)(n.code,{children:"1"})," for TLS/LiDAR input; raise\ntoward ",(0,r.jsx)(n.code,{children:"8"})," if a noisy cloud still meshes bumpy."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"trim-factor--point-spacing",children:"Trim factor (\xd7 point spacing)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"6"}),". ",(0,r.jsx)(n.code,{children:"0"})," keeps the full watertight surface."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"}),' Poisson extrapolates a "bubble" beyond the real data; this\ntrims away triangles whose vertices sit farther than ',(0,r.jsx)(n.em,{children:"factor \xd7 the local point\nspacing"})," from any input point. ",(0,r.jsx)(n.strong,{children:"Lower trims more aggressively"})," (tighter to the\ndata, but can punch holes); higher keeps more surface; ",(0,r.jsx)(n.code,{children:"0"})," disables trimming."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"6"})," removes the balloon-like overhang around the edges while keeping\nthe surface intact. Lower to ",(0,r.jsx)(n.code,{children:"3"}),"\u2013",(0,r.jsx)(n.code,{children:"4"})," if the bubble is still obvious; set ",(0,r.jsx)(n.code,{children:"0"})," if\nyou specifically want a closed watertight mesh."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Cleaning up after Poisson",type:"tip",children:(0,r.jsxs)(n.p,{children:["Poisson leaves disconnected fragments \u2014 vegetation, scaffolding, patches of sky \u2014\nfloating near the outcrop. ",(0,r.jsx)(n.strong,{children:"Progressive Component Select"})," is the quickest way to\nget rid of them: it sorts the mesh's disconnected pieces smallest-first and lets\nyou delete them with a slider, and it cannot select the main surface however far\nyou drag. See\n",(0,r.jsx)(n.a,{href:"/docs/general/meshes-point-clouds/model-preparation#progressive-component-select",children:"Preparing a Model for Interpretation"}),"."]})}),"\n",(0,r.jsx)(n.h3,{id:"create-mesh-greedy-projection",children:"Create Mesh (Greedy Projection)"}),"\n",(0,r.jsxs)(n.p,{children:["Greedy projection triangulates the points ",(0,r.jsx)(n.strong,{children:"directly"})," \u2014 it follows the cloud\nclosely and is fast, but it does ",(0,r.jsx)(n.strong,{children:"not"})," fill gaps (sparse areas stay holed). Use\nit when you want the raw measured surface rather than an interpolated one."]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Field"}),(0,r.jsx)(n.th,{children:"Default"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Search radius"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"0.5"})}),(0,r.jsx)(n.td,{children:"Maximum edge length / connection distance, in model units. The single most important value \u2014 set it to a few times the point spacing."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Max edge multiplier (\u03bc)"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"2.5"})}),(0,r.jsx)(n.td,{children:"Caps an edge at \u03bc \xd7 the local point distance, so dense areas use short edges and sparse areas longer ones."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Max nearest neighbours"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"100"})}),(0,r.jsx)(n.td,{children:"How many neighbours each point may connect to. Higher closes more triangles (slower)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Max surface angle"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"45\xb0"})}),(0,r.jsx)(n.td,{children:"Don't connect points across a normal change larger than this \u2014 preserves sharp edges."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Min triangle angle"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"10\xb0"})}),(0,r.jsx)(n.td,{children:"Lower bound on triangle angles (avoids slivers)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Max triangle angle"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"120\xb0"})}),(0,r.jsx)(n.td,{children:"Upper bound on triangle angles."})]})]})]}),"\n",(0,r.jsx)(n.admonition,{title:"Poisson vs Greedy",type:"tip",children:(0,r.jsxs)(n.p,{children:["Use ",(0,r.jsx)(n.strong,{children:"Poisson"})," for a clean, closed surface you'll texture or interpret. Use\n",(0,r.jsx)(n.strong,{children:"Greedy"})," for a fast, faithful triangulation of exactly the measured points when\nyou don't want gaps filled in."]})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"texturing-the-mesh",children:"Texturing the mesh"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Texture Mesh from Photos\u2026"})," projects the solved photographs back onto the mesh\nand bakes a photographic texture \u2014 far more detail than the per-vertex colour the\nmesh inherits from the cloud. It needs a mesh (run ",(0,r.jsx)(n.strong,{children:"Create Mesh"})," first) and the\nworkflow's photos. The bake runs on a background thread and the texture is saved\nwith the project."]}),"\n",(0,r.jsx)(n.h3,{id:"let-vrgs-choose-the-budget",children:"Let VRGS choose the budget"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"You do not have to answer any of these questions."})," The first prompt shows a\nbudget worked out from your own data and you can simply accept it:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Recommended: 2 page(s) of 8192 x 8192 (536 MB).\n\nThe photographs resolve about 0.043 units per pixel at this range, so the survey\nholds roughly that much real detail \u2014 more texture than this stores blur, not\ninformation.\nThat gives ~336 texels per triangle over 1639133 triangles.\n\nYES - use the recommendation\nNO - set the resolution manually (advanced)\nCANCEL - abort\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Answer ",(0,r.jsx)(n.strong,{children:"Yes"})," and the bake starts. Everything below this point is the ",(0,r.jsx)(n.strong,{children:"advanced"}),"\npath, reached with ",(0,r.jsx)(n.strong,{children:"No"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The recommendation is arithmetic, not a house default. A photograph resolves the\nground at ",(0,r.jsx)(n.code,{children:"range \xf7 focal length in pixels"}),", so the whole survey contains about\n",(0,r.jsx)(n.code,{children:"mesh surface area \xf7 g
1sd\xb2"})," genuinely distinct texels \u2014 and asking for more texture\nthan that stores interpolation, at full cost in memory and bake time. VRGS sums the\nreal triangle areas, takes the ",(0,r.jsx)(n.strong,{children:"median"})," ground sample distance across the posed\nphotographs (a median so one distant or badly-posed camera cannot skew it), then\npicks the smallest page size that covers the requirement in four pages or fewer."]}),"\n",(0,r.jsx)(n.p,{children:"If the mesh is far denser than the photographs can resolve, the prompt says so and\nsuggests decimating first rather than silently baking a starved texture."}),"\n",(0,r.jsx)(n.admonition,{title:"This is the setting people get most wrong",type:"tip",children:(0,r.jsx)(n.p,{children:"Texture resolution is easy to over-buy. A survey whose imagery supports four pages\nof real detail will happily accept a request for 128 \u2014 and spend gigabytes of disk\nand hours of bake time storing blur. The recommendation exists so that the number\nof pages is tied to what the photographs actually contain rather than to a guess."})}),"\n",(0,r.jsxs)(n.h3,{id:"resolution-mode-advanced",children:["Resolution mode ",(0,r.jsx)(n.em,{children:"(advanced)"})]}),"\n",(0,r.jsxs)(n.p,{children:["If you answered ",(0,r.jsx)(n.strong,{children:"No"}),", the next prompt picks the mode:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Target pixel size"})," ",(0,r.jsxs)(n.em,{children:["(answer ",(0,r.jsx)(n.strong,{children:"Yes"}),")"]})," \u2014 you specify the real-world size each\ntexel should cover (the ground sample distance). VRGS packs the atlas at exactly\nthat density: a target of ",(0,r.jsx)(n.strong,{children:"0.005 m"})," means each texel is 5 mm on the outcrop\n(200 texels per metre). This is the most intuitive control \u2014 ask for the detail\nyou need and let the page count follow."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Page budget"})," ",(0,r.jsxs)(n.em,{children:["(answer ",(0,r.jsx)(n.strong,{children:"No"}),")"]})," \u2014 you instead fix the ",(0,r.jsx)(n.strong,{children:"number"})," of texture\npages and VRGS spreads the available texels evenly over the surface. Use this\nwhen you want a hard limit on texture memory rather than a guaranteed detail."]}),"\n"]}),"\n",(0,r.jsxs)(n.h3,{id:"page-size-advanced",children:["Page size ",(0,r.jsx)(n.em,{children:"(advanced)"})]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"4096"})," texels per side."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Choices:"})," ",(0,r.jsx)(n.code,{children:"4096"}),", ",(0,r.jsx)(n.code,{children:"8192"})," or ",(0,r.jsx)(n.code,{children:"16384"}),". Anything you type is rounded to a power\nof two and ",(0,r.jsx)(n.strong,{children:"clamped to 16384"}),", with a note in the messages panel if it was."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the dimensions of each atlas page. Larger pages hold more\ndetail per page (so fewer pages) but cost ",(0,r.jsx)(n.code,{children:"size \xd7 size \xd7 4"})," bytes each:"]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Page size"}),(0,r.jsx)(n.th,{children:"Memory per page"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"4096"}),(0,r.jsx)(n.td,{children:"64 MB"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"8192"}),(0,r.jsx)(n.td,{children:"256 MB"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"16384"}),(0,r.jsx)(n.td,{children:"1 GB"})]})]})]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Why 16384 is the ceiling:"})," graphics hardware will not create a 2D texture\nlarger than this \u2014 16384 is the limit on AMD and Intel GPUs, and the Vulkan\nstandard only ",(0,r.jsx)(n.em,{children:"guarantees"})," 4096. Some high-end NVIDIA cards manage 32768, but a\npage that size is 4 GB and sits on the single-allocation limit most drivers\nreport, so it would work on one machine and fail on the next."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"4096"})," suits most meshes; ",(0,r.jsx)(n.code,{children:"8192"})," where the recommendation asks for\nmore than about four pages at 4096."]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Bigger pages are not automatically cheaper",type:"note",children:(0,r.jsx)(n.p,{children:"Nine pages at 4096 is 576 MB; one page at 16384 is 1 GB for the same texel count.\nFewer, larger pages pack slightly better and cost fewer draw calls, but they are\nnot the smaller option. The recommendation weighs this for you."})}),"\n",(0,r.jsxs)(n.h3,{id:"target-pixel-size-pixel-size-mode",children:["Target pixel size ",(0,r.jsx)(n.em,{children:"(pixel-size mode)"})]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," ",(0,r.jsx)(n.code,{children:"0.01"})," m/texel."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," the real-world size of one texel. ",(0,r.jsx)(n.strong,{children:"Smaller = sharper texture"}),"\nbut more pages and more memory. There is a safety cap of ",(0,r.jsx)(n.strong,{children:"8 pages"})," \u2014 if your\npixel size would need more, VRGS coarsens the density automatically and notes it\nin the log rather than exhausting memory."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," ",(0,r.jsx)(n.code,{children:"0.005"})," for close-range outcrop detail; ",(0,r.jsx)(n.code,{children:"0.02"})," for a quick, light\ntexture of a large area."]}),"\n"]}),"\n",(0,r.jsxs)(n.h3,{id:"number-of-pages-page-budget-mode",children:["Number of pages ",(0,r.jsx)(n.em,{children:"(page-budget mode)"})]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Default:"})," calculated from the mesh and pre-filled, not a fixed number. VRGS\naims for about 64 texels per triangle, allowing for the ~30% of each page that\ngoes on the padding every atlas chart needs:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"pages = triangles \xd7 64 \xf7 (page size\xb2 \xd7 0.7)\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"What it does:"})," how many pages to fill. More pages = finer texture over the\nsame surface. The prompt shows the suggestion, your triangle count and the cost\nper page."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Example:"})," accept the suggestion unless you have a reason; drop to ",(0,r.jsx)(n.code,{children:"1"})," for a\nlight preview texture."]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"how-a-triangle-is-textured",children:"How a triangle is textured"}),"\n",(0,r.jsxs)(n.p,{children:["VRGS ranks the photographs that can see each triangle \u2014 most head-on and\nhighest-resolution wins \u2014 rejecting back-facing and very oblique views and\nchecking ",(0,r.jsx)(n.strong,{children:"occlusion"}),", so a photo can never texture a surface hidden behind the\nmesh. Triangles ",(0,r.jsx)(n.strong,{children:"no"})," photo can see fall back to the mesh's per-vertex colour, so\nthere are ",(0,r.jsx)(n.strong,{children:"no black holes"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"The occlusion test is a ray cast, and on a rough surface it is deliberately\nforgiving: its tolerance scales with the size of the triangle being tested, so a\nray that clips a neighbouring bump a few centimetres early is not mistaken for a\nreal obstruction. Without that, whether a triangle textured at all came down to\nwhether its ray happened to graze a nearby lump, which showed up as flat facets\nscattered through otherwise good texture."}),"\n",(0,r.jsxs)(n.p,{children:["If a triangle loses ",(0,r.jsx)(n.strong,{children:"every"})," candidate to the occlusion test, VRGS textures it\nfrom its best-scoring photograph anyway rather than leaving it flat. Photo content\nthat may be slightly bled from an occluder reads as texture; a flat facet reads as\na hole. Only triangles that no photograph framed at all fall back to vertex colour.\nThe log separates the two, because they need opposite fixes:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] Texture bake: occlusion outcome: 21313 triangle(s) lost every candidate to\n the occlusion test, of which 21313 were textured anyway from their\n best-scoring photo instead of going flat; 3017 were framed by NO photo at\n all and are the only genuine vertex-colour fills\n"})}),"\n",(0,r.jsx)(n.p,{children:"A large second number means a coverage problem \u2014 those parts of the outcrop were\nnever photographed from a usable angle."}),"\n",(0,r.jsxs)(n.p,{children:["Every texel is then ",(0,r.jsx)(n.strong,{children:"blended"}
1)," from the photos that see it, weighted by how well\neach one sees ",(0,r.jsx)(n.em,{children:"that texel"})," \u2014 how square-on, how close, and how far from the edge\nof its frame. Because those weights depend on the point in space rather than on\nwhich triangle it belongs to, the mix changes smoothly across the whole surface,\nand the exposure/white-balance ",(0,r.jsx)(n.strong,{children:"seams"})," you would otherwise get between\nneighbouring triangles that chose different photos are dissolved."]}),"\n",(0,r.jsx)(n.p,{children:"Patches showing flat colour instead of photographic detail are triangles no photo\ncould see (outside every image, or framed only at an impossibly grazing angle)."}),"\n",(0,r.jsx)(n.h3,{id:"lens-distortion",children:"Lens distortion"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Texture apply lens distortion"})," ",(0,r.jsx)(n.em,{children:"(SfM workflow properties, default on)"})," controls\nwhether the solved lens model is applied when sampling a photograph."]}),"\n",(0,r.jsxs)(n.p,{children:["Leave it ",(0,r.jsx)(n.strong,{children:"off"})," for any camera that de-warps in firmware \u2014 all DJI drones, most\nphones \u2014 because the image is already rectilinear even though it is the original\nfile. Turn it ",(0,r.jsx)(n.strong,{children:"on"})," only for a genuinely raw lens with a solved model."]}),"\n",(0,r.jsxs)(n.p,{children:["You do not have to work this out. ",(0,r.jsx)(n.strong,{children:"Every bake measures it."})," Over roughly 4,000\ntriangles that two photographs both see, VRGS compares the colour each photo\nreports for the same point in space, with the model applied and without. The\ncorrect setting is the one where overlapping photographs ",(0,r.jsx)(n.em,{children:"agree"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] Texture bake: distortion self-check over 3866 twice-seen triangles --\n cross-photo colour disagreement with distortion ON 0.0779, OFF 0.0779\n => OFF agrees better by 0%\n"})}),"\n",(0,r.jsx)(n.p,{children:"If the setting you used is more than 5% worse than the alternative, VRGS says so in\nthe messages panel and tells you which way to change it."}),"\n",(0,r.jsxs)(n.p,{children:["Once the camera model is right the two readings become ",(0,r.jsx)(n.strong,{children:"identical"}),", as above, and\nthe setting stops mattering at all \u2014 which is the real sign that\n",(0,r.jsx)(n.a,{href:"#cameras-that-correct-their-own-lens",children:"the intrinsics are honest"})," rather than that\nthe toggle happens to be in the lucky position."]}),"\n",(0,r.jsx)(n.admonition,{title:"Getting this wrong looks like banding, not blur",type:"warning",children:(0,r.jsxs)(n.p,{children:["Applying a distortion model that does not belong to the image displaces every\nsample radially from the centre of that photograph \u2014 nothing at the middle, a few\ntexels at the corners. Each texel is blended from several photographs that see it\nat ",(0,r.jsx)(n.em,{children:"different"})," radii, so they disagree by different amounts, and the blend turns\nthat into smeared, evenly spaced bands across the model. It looks like a texturing\nbug and is actually a camera-model one."]})}),"\n",(0,r.jsx)(n.h3,{id:"texture-resolution-attributes",children:"Texture resolution attributes"}),"\n",(0,r.jsx)(n.p,{children:"Each bake writes two per-triangle attribute layers, so you can see how well\nresolved any part of the model is instead of guessing:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Texture GSD (units/texel)"})," \u2014 the real ground sample distance, in project\nunits. It is the ",(0,r.jsx)(n.strong,{children:"worse"})," of two independent limits: what the atlas can store\n(set by page size and count) and what the imagery carries (",(0,r.jsx)(n.code,{children:"range \xf7 focal length"}),", set by flying height and lens)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Texture resolution 0-1"})," \u2014 the same information normalised, ",(0,r.jsx)(n.strong,{children:"1 = finest"}),".\nUse this one on an ",(0,r.jsx)(n.strong,{children:"unscaled"})," mesh, where an absolute distance is meaningless.\nIt is mapped logarithmically between the 5th and 95th percentiles: logarithmic\nbecause GSD spans orders of magnitude, and percentile-clipped so a handful of\
1nsliver triangles cannot flatten everything else onto one colour."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Colour the mesh by either layer from the ",(0,r.jsx)(n.strong,{children:"Attributes"})," node. The log also reports\nwhich limit dominates:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] Texture bake: GSD per triangle (project units/texel) p5 0.021, median\n 0.043, p95 0.11 -- 62% of triangles are SOURCE-limited (photo resolution),\n the rest TEXTURE-limited (add pages or decimate)\n"})}),"\n",(0,r.jsxs)(n.p,{children:["That split is the actionable part. ",(0,r.jsx)(n.strong,{children:"Source-limited"})," means the photographs are the\nconstraint \u2014 more pages will not help, you would need to fly lower or use a longer\nlens. ",(0,r.jsx)(n.strong,{children:"Texture-limited"})," means the atlas is the constraint, and more pages (or a\ncoarser mesh) will sharpen it."]}),"\n",(0,r.jsx)(n.admonition,{title:"If the texture looks soft",type:"tip",children:(0,r.jsxs)(n.p,{children:["Blending is weighted heavily toward the single best view, so it should stay\nsharp. If a bake looks blurred or ghosted, the usual cause is a small pose error\nletting two views disagree. Textures are baked with ",(0,r.jsx)(n.strong,{children:"blending on"}),", up to ",(0,r.jsx)(n.strong,{children:"3"}),"\nphotos per texel, at a ",(0,r.jsx)(n.strong,{children:"sharpness"})," of 4 \u2014 higher biases harder toward the one\nbest photo, lower spreads the blend wider."]})}),"\n",(0,r.jsx)(n.h3,{id:"how-long-the-bake-takes-and-how-to-stop-it",children:"How long the bake takes, and how to stop it"}),"\n",(0,r.jsxs)(n.p,{children:["A single progress bar tracks the whole bake and names the stage it is in: ",(0,r.jsx)(n.em,{children:"UV\nunwrap"}),", ",(0,r.jsx)(n.em,{children:"building occlusion tree"}),", ",(0,r.jsx)(n.em,{children:"decoding photos"}),", ",(0,r.jsx)(n.em,{children:"scoring photos per\ntriangle"}),", ",(0,r.jsx)(n.em,{children:"rasterizing"}),", ",(0,r.jsx)(n.em,{children:"finalizing"}),". The ",(0,r.jsx)(n.strong,{children:"\u2715 on the progress bar cancels the\nbake"}),", including part-way through the unwrap; a cancelled bake leaves the\nmesh's existing texture untouched."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"UV unwrap is the slow stage."})," VRGS splits the mesh into chunks of at most\n100,000 triangles and unwraps them in parallel across your CPU cores \u2014 the log\nrecords the split:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] Texture bake: 6279507 triangles in 63 unwrap chunk(s)\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Without that split a large mesh is unwrapped as a single unit on ",(0,r.jsx)(n.strong,{children:"one"})," core,\nand the cost grows far faster than the triangle count \u2014 a multi-million\ntriangle mesh would appear to hang for hours. Photo decoding, per-triangle\nphoto scoring and rasterisation all run in parallel too."]}),"\n",(0,r.jsxs)(n.admonition,{title:"Decimate before texturing a very dense mesh",type:"tip",children:[(0,r.jsx)(n.p,{children:"The texture, not the geometry, carries the visible detail \u2014 so a mesh far denser\nthan the atlas can resolve costs time and memory for nothing. At the end of a\nbake the log reports the budget:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"[INFO] Texture bake: 21.4 texels per atlased triangle\n"})}),(0,r.jsxs)(n.p,{children:["Below about ",(0,r.jsx)(n.strong,{children:"16 texels per triangle"})," VRGS also warns in the messages panel.\nA mesh of a few hundred thousand triangles textured at the same page budget\ngives a ",(0,r.jsx)(n.strong,{children:"sharper"})," result than a multi-million triangle one, and bakes far\nfaster. Decimate the mesh first, then texture."]})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"reading-the-run-log",children:"Reading the run log"}),"\n",(0,r.jsx)(n.admonition,{title:"Ask Athos to read it for you",type:"tip",children:(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"/docs/general/viewing-collaboration/athos-ai-assistant#working-with-photogrammetry-sfm-models",children:"Athos AI assistant"})," can interpret all of this for you. ",(0,r.jsxs)(n.strong,{children:["Right-click the SfM workflow in the project tree and choose ",(0,r.jsx)(n.em,{children:"Ask Athos about this Model"})]}),", or ask something like ",(0,r.jsx)(n.em,{children:'"why does my dense cloud have holes?"'})," in the chat. Athos reads the same settings, run statistics, and per-view diagnostics described below and answers with your actual numbers \u2014 including which photos failed, whether gaps are a coverage or a pose problem, and which parameters to change. It can also apply a change or start a re-run for you, always after showing you what it will do."]})}),"\n",(0,r.jsx)(n.p,{children:"Two summary lines tell you most of what you need to tune a run."}),"\n",(0,r.jsx)(n.h3,{id:"sparse-summary",children:"Sparse summary"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"SFM done: 20/21 views, 22196 points (reproj px: med=0.34, p90=0.81, p99=2.10, rms=0.55)\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"20/21 views"})})," \u2014 registered vs. total photos. If many photos fail to\nregister, the cause is usually too little overlap, too few features, or too\nstrict matching thresholds."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"22196 points"})})," \u2014 size of the sparse cloud."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"reproj px"})})," \u2014 per-point reprojection error percentiles. A ",(0,r.jsx)(n.strong,{children:"sub-pixel\nmedian"}
1)," is healthy. A small median with a large ",(0,r.jsx)(n.code,{children:"p99"})," means a few outliers;\na large median everywhere means a weak solve."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"dense-fusion-funnel",children:"Dense fusion funnel"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Fusion: 5836800 -> 4443855 depth>0 -> 3841509 conf>=0.75 -> 2241200 consistency>=2 -> 12844 conflict-cut -> 31840 sky-masked -> 706202 voxels -> 698061 after SOR -> 697620 after component filter(min=200 pts: dropped 28 islands / 441 pts)\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Each arrow shows how many pixels/points survive a stage, so you can see ",(0,r.jsx)(n.strong,{children:"where"}),"\npoints are lost and which knob to turn:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["big drop at ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"conf>="})})," \u2192 lower ",(0,r.jsx)(n.em,{children:"Dense MVS confidence cutoff"}),";"]}),"\n",(0,r.jsxs)(n.li,{children:["big drop at ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"consistency>="})})," \u2192 loosen ",(0,r.jsx)(n.em,{children:"Dense MVS depth tolerance"})," or lower\n",(0,r.jsx)(n.em,{children:"Dense MVS consistent views"}),";"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"conflict-cut"})})," counts consistency survivors that were vetoed because\n",(0,r.jsx)(n.em,{children:"more"})," neighbouring views measured a clearly different surface along the same\nray than agreed with the point \u2014 the signature of a correlated mismatch\nburied below (or floating in front of) the real surface. A very large number\nhere means the per-view depth maps disagree badly: check overlap and texture\nbefore loosening anything;"]}),"\n",(0,r.jsxs)(n.li,{children:["with the sky filter on, an extra ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sky-masked"})})," term shows how many semantic\nsky depth pixels were removed (or colour/confidence fallback pixels if the\nmodel was unavailable);"]}),"\n",(0,r.jsxs)(n.li,{children:["the final ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"component filter"})})," term reports the isolated islands removed \u2014\nsee ",(0,r.jsx)(n.em,{children:"Dense MVS min component size"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Tune from the funnel, not by guesswork",type:"tip",children:(0,r.jsx)(n.p,{children:"Run once with defaults, read the funnel, then change the one stage that is\ncutting too much (or too little). Re-run and compare."})}),"\n",(0,r.jsx)(n.h3,{id:"confidence-profile",children:"Confidence profile"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Fusion confidence profile over 67332814 px with depth (cutoff 0.75): ... | 48.3%\nbelow cutoff, of which 12.7% of all depth px would be admitted by relaxing the\ncutoff to 0.6\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The funnel tells you the confidence gate cut half your pixels; this tells you\nwhether that was worth doing. The histogram is collected ",(0,r.jsx)(n.strong,{children:"before"})," the gate, so\nthe buckets below the cutoff are populated, and the closing figure answers the\nonly question that matters in practice: how much would come back if you lowered\nthe cutoff."]}),"\n",(0,r.jsxs)(n.p,{children:["Mass piled just under the cutoff means real surface is being rejected on close\ncalls, and lowering ",(0,r.jsx)(n.em,{children:"Dense MVS confidence cutoff"})," will recover geometry. Mass\npiled far below it is noise, and lowering the cutoff will only admit rubbish."]}),"\n",(0,r.jsx)(n.h3,{id:"per-reference-yield",children:"Per-reference yield"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Fusion per-ref yield: median 119255 pts/ref, range 0-280048; 44 of 252 ref(s)\nyielded <=10% of median (partners p=nominal/e=effective) (weakest views:\n115=0(p8/e0) 144=0(p6/e1) 146=0(p7/e0) ...)\n"})}),"\n",(0,r.jsxs)(n.p,{children:["This separates a ",(0,r.jsx)(n.strong,{children:"hole"})," from ",(0,r.jsx)(n.strong,{children:"thinning"}),". If every view sits near the median,\nthe cloud is uniformly sparse and the funnel tells you which gate to relax. If a\nhandful of views yielded almost nothing, the loss is concentrated in whatever\nthose cameras were looking at \u2014 and since the numbers are view IDs, you can find\nthat stretch of the flight path and check its overlap, lighting and angle."]}),"\n",(0,r.jsxs)(n.p,{children:["Each weak view carries ",(0,r.jsx)(n.code,{children:"(p<nominal>/e<effective>)"}),", which splits a near-zero\nyield into its two possible causes:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"p"})," (nominal)"]})," \u2014 how many overlapping views the matcher ",(0,r.jsx)(n.em,{children:"assigned"})," to this\nreference."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"e"})," (effective)"]})," \u2014 how many of those actually ",(0,r.jsx)(n.em,{children:"confirmed"})," its depth at one\nor more pixels."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"p1/e0"})," or ",(0,r.jsx)(n.code,{children:"p0/e0"})," is a ",(0,r.jsx)(n.strong,{children:"coverage gap"})," \u2014 nothing overlaps that viewpoint, and\nthe only fix is more photographs from that angle. ",(0,r.jsx)(n.code,{children:"p8/e0"})," is a ",(0,r.jsx)(n.strong,{children:"pose fault"})," \u2014\neight views should see that surface and none agree, so the camera (or its\nneighbours) was placed wrong in the reconstruction, and no amount of extra\nimagery or dense-setting tuning will fill it until the pose is corrected. This\nmatters because a mis-placed camera can still have a low reprojection error, so\nthe pose looks fine by the usual measure; the effective-partner count is the\ncheck that catches it."]}),"\n",(0,r.jsx)(n.h3,{id:"per-view-depth-diagnostic",children:"Per-view depth diagnostic"}),"\n",(0,r.jsx)(n.p,{children:"One line per reference view describes how the plane sweep behaved:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"MVS depth diag view 37: bracket 34.2-106.8 m over 64 bins (bin width 0.371 m\nnear / 3.52 m far); winners 41% edge-far, 2.1% edge-near, 3.4% flat-cost, 0.6%\nnon-parabolic, 52.9% refined (18.9% by <0.05 bin) => 66.0% on a bin centre\n<-- RANGE-LIMITED: surface outside the depth bracket; more bins will NOT help\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"bracket"})," is the depth range searched for that view, derived from the\nsparse points it can see. Depth hypotheses are spaced evenly in ",(0,r.jsx)(n.em,{children:"inverse"})," depth,\nso the metric step grows with the square of distance \u2014 hence a bin 0.37 m thick\nnear the camera and 3.5 m thick at the far end of the same view. When the far\nfigure exceeds the ",(0,r.jsx)(n.em,{children:"Dense MVS depth tolerance"}
1)," applied at that range, distant\nsurface in that view cannot pass the cross-view consistency check however well it\nwas photographed."]}),"\n",(0,r.jsx)(n.p,{children:"The percentages say where each best match landed, and the trailing verdict names\nthe dominant cause:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"RANGE-LIMITED"})})," \u2014 most matches sat against a wall of the bracket, so the\ntrue surface lies outside the range searched. Usually the view is dominated by\nsky or distant background, or the sparse cloud within it is too thin to bracket\nit properly. Better sparse coverage helps; the dense settings cannot."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"TEXTURE-LIMITED"})})," \u2014 the correlation curve is flat, so no depth is\ndistinguishable from any other. The surface carries no usable texture at the\nscale the matcher works at. Nothing in the settings panel fixes this; it needs\ncloser or better-lit photographs."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"OVER-QUANTISED"})})," \u2014 the matches landed cleanly inside the volume, but the\nfar bin is coarser than the depth tolerance that has to confirm it, so distant\nsurface in this view cannot be verified by any other view. This is the one a\nfiner depth search improves, and it is invisible to the other counters \u2014 a\nview can refine every winner cleanly and still emit nothing."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"TERRACED"})})," \u2014 depths are quantised onto discrete shells. Also answered by a\nfiner depth search."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Two of these are not settings problems",type:"warning",children:(0,r.jsxs)(n.p,{children:["Read this line before spending a run turning knobs. If your views report\n",(0,r.jsx)(n.code,{children:"RANGE-LIMITED"})," or ",(0,r.jsx)(n.code,{children:"TEXTURE-LIMITED"}),", lowering the confidence cutoff or loosening\nthe depth tolerance will add noise without filling the gaps, because the depth\nwas never measured in the first place. ",(0,r.jsx)(n.code,{children:"OVER-QUANTISED"})," and ",(0,r.jsx)(n.code,{children:"TERRACED"})," are the\nopposite \u2014 the depth ",(0,r.jsx)(n.em,{children:"was"})," measured but sampled too coarsely, and a finer sweep\n(handled internally) recovers it."]})}),"\n",(0,r.jsx)(n.h3,{id:"why-the-dense-cloud-has-gaps-where-the-sparse-cloud-has-points",children:"Why the dense cloud has gaps where the sparse cloud has points"}),"\n",(0,r.jsx)(n.p,{children:"This is expected, not a fault \u2014 the two clouds are not built to comparable\nstandards:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["a ",(0,r.jsx)(n.strong,{children:"sparse"})," point needs one distinctive feature matched in ",(0,r.jsx)(n.strong,{children:"two"}),"\nphotographs, at full image resolution;"]}),"\n",(0,r.jsxs)(n.li,{children:["a ",(0,r.jsx)(n.strong,{children:"dense"})," point needs an entire correlation patch to match in ",(0,r.jsx)(n.strong,{children:"three or\nmore"})," views, all agreeing on depth within the tolerance, at the reduced\nresolution the dense stage works at."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"So the sparse cloud thins gracefully as conditions worsen while the dense cloud\ntends toward all-or-nothing: full density where matching succeeds, empty where it\nfails. Shadowed recesses, smooth or uniform rock, and surfaces seen only at a\ngrazing angle are the usual empty areas. The last one catches people out \u2014 a\nwell-textured face photographed obliquely correlates just as badly as a\nfeatureless one photographed straight on, because the correlation patch is\nforeshortened differently in each view."}),"\n",(0,r.jsx)(n.p,{children:"The dense stage also runs at a reduced image resolution, which puts a floor under\npoint spacing no matter how good the photographs are. If the dense cloud is\nuniformly coarser than you expected rather than patchy, that floor is what you\nare seeing, and no setting will move it."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"worked-examples",children:"Worked examples"}),"\n",(0,r.jsx)(n.h3,{id:"drone-survey-with-gps-nadir",children:"Drone survey with GPS (nadir)"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"SIFT"}),", ",(0,r.jsx)(n.em,{children:"Alignment image scale"})," ",(0,r.jsx)(n.code,{children:"Half"}),", ",(0,r.jsx)(n.em,{children:"Share intrinsics per camera"})," ",(0,r.jsx)(n.strong,{children:"on"}),",\n",(0,r.jsx)(n.em,{children:"Use GPS prior"})," ",(0,r.jsx)(n.strong,{children:"on"}),", ",(0,r.jsx)(n.em,{children:"GPS sigma XY"})," ",(0,r.jsx)(n.code,{children:"2"}),"\u2013",(0,r.jsx)(n.code,{children:"5"}),", ",(0,r.jsx)(n.em,{children:"GPS pair max distance"})," set to a\nfew times the photo spacing. Enable ",(0,r.jsx)(n.em,{children:"Dense MVS"})," with the ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"})," backend."]}),"\n",(0,r.jsx)(n.h3,{id:"convergent-outcrop-oblique-high-overlap",children:"Convergent outcrop (oblique, high overlap)"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"SIFT"}),", ",(0,r.jsx)(n.em,{children:"Max features"})," ",(0,r.jsx)(n.code,{children:"8000"}),"+, ",(0,r.jsx)(n.em,{children:"Use GPS prior"})," on if the photos are tagged. Enable\n",(0,r.jsx)(n.em,{children:"Dense MVS"})," and leave the default ",(0,r.jsx)(n.em,{children:"Sky filter"})," on when the outcrop is shot against\nthe sky."]}),"\n",(0,r.jsx)(n.h3,{id:"densest-possible-dense-cloud",children:"Densest possible dense cloud"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.em,{children:"Dense MVS confidence cutoff"})," ",(0,r.jsx)(n.code,{children:"0.6"}),", ",(0,r.jsx)(n.em,{children:"Dense MVS consistent views"})," ",(0,r.jsx)(n.code,{children:"1"}),", ",(0,r.jsx)(n.em,{children:"Dense MVS\ndepth tolerance"})," ",(0,r.jsx)(n.code,{children:"0.04"}),". (Expect more noise\u2014clean up afterwards with point-cloud\nfilters.)"]}),"\n",(0,r.jsx)(n.h3,{id:"cleanest-dense-cloud",children:"Cleanest dense cloud"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.em,{children:"Dense MVS confidence cutoff"})," ",(0,r.jsx)(n.code,{children:"0.85"}),", ",(0,r.jsx)(n.em,{children:"Dense MVS consistent views"})," ",(0,r.jsx)(n.code,{children:"3"}),", ",(0,r.jsx)(n.em,{children:"Dense MVS\ndepth tolerance"})," ",(0,r.jsx)(n.code,{children:"0.015"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"fast-preview",children:"Fast preview"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"ORB"}),", ",(0,r.jsx)(n.em,{children:"Alignment image scale"})," ",(0,r.jsx)(n.code,{children:"Quarter"}),", ",(0,r.jsx)(n.em,{children:"Max features"})," ",(0,r.jsx)(n.code,{children:"4000"}),", ",(0,r.jsx)(n.em,{children:"BA max iterations"}),"\n",(0,r.jsx)(n.code,{children:"40"}),", ",(0,r.jsx)(n.em,{children:"Dense MVS"})," off (or on with ",(0,r.jsx)(n.em,{children:"neighbor views"})," ",(0,r.jsx)(n.code,{children:"3"}),")."]}),"\n",(0,r.jsx)(n.h3,{id:"textured-outcrop-mesh",children:"Textured outcrop mesh"}),"\n",(0,r.jsxs)(n.p,{children:["Run the reconstruction with ",(0,r.jsx)(n.em,{children:"Dense MVS"})," ",(0,r.jsx)(n.strong,{children:"on"}),", then ",(0,r.jsx)(n.strong,{children:"Create Mesh (Poisson)"}),"\nwith ",(0,r.jsx)(n.em,{children:"Octree depth"})," ",(0,r.jsx)(n.code,{children:"9"}),", ",(0,r.jsx)(n.em,{children:"Trim factor"})," ",(0,r.jsx)(n.code,{children:"6"}),". Finally ",(0,r.jsx)(n.strong,{children:"Texture Mesh from Photos"}),"\nand ",(0,r.jsx)(n.strong,{children:"accept the recommended budget"})," \u2014 it is derived from what your photographs\ncan actually resolve, which is the ceiling on useful detail whatever you ask for."]}),"\n",(0,r.jsxs)(n.p,{children:["Only override it if you have a specific reason: ",(0,r.jsx)(n.em,{children:"Target pixel size"})," mode at\n",(0,r.jsx)(n.code,{children:"0.005"})," m with ",(0,r.jsx)(n.em,{children:"Page size"})," ",(0,r.jsx)(n.code,{children:"4096"})," forces a fixed 5 mm texel, which is worth doing\nwhen you need a guaranteed density for measurement rather than the best texture\nthe imagery supports."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Symptom"}),(0,r.jsx)(n.th,{children:"Likely cause / fix"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["Few photos register (",(0,r.jsx)(n.code,{children:"5/20 views"}),")"]}),(0,r.jsxs)(n.td,{children:["Too little overlap, too few features, or matching too strict. Raise ",(0,r.jsx)(n.em,{children:"Max features"})," and/or ",(0,r.jsx)(n.em,{children:"Alignment image scale"}),"; lower ",(0,r.jsx)(n.em,{children:"Lowe ratio test"})," slightly; only lower ",(0,r.jsx)(n.em,{children:"Min geometric inliers"})," as a last resort. Check the photos aren't blurry."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Dense cloud nearly empty / very sparse"}),(0,r.jsxs)(n.td,{children:["Check the ",(0,r.jsx)(n.a,{href:"#per-view-depth-diagnostic",children:"per-view depth diagnostic"})," first \u2014 if it reports ",(0,r.jsx)(n.code,{children:"RANGE-LIMITED"})," or ",(0,r.jsx)(n.code,{children:"TEXTURE-LIMITED"}),", the depth was never measured and relaxing the gates only adds noise. Otherwise read the fusion funnel and relax the stage that cuts most: lower ",(0,r.jsx)(n.em,{children:"confidence cutoff"}),", loosen ",(0,r.jsx)(n.em,{children:"depth tolerance"}),", or lower ",(0,r.jsx)(n.em,{children:"consistent views"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Dense cloud has gaps where the sparse cloud has points"}),(0,r.jsxs)(n.td,{children:["Normal, and ",(0,r.jsx)(n.a,{href:"#why-the-dense-cloud-has-gaps-where-the-sparse-cloud-has-points",children:"explained here"})," \u2014 dense matching needs three views and a whole patch to correlate, sparse needs two views and one feature. Use the ",(0,r.jsx)(n.a,{href:"#per-reference-yield",children:"per-reference yield"})," line to tell a localised hole from uniform thinning, and the ",(0,r.jsx)(n.a,{href:"#confidence-profile",children:"confidence profile"})," to see whether lowering the cutoff would actually recover anything."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"A few specific views contribute no dense points"}),(0,r.jsxs)(n.td,{children:["Read their ",(0,r.jsx)(n.code,{children:"(p/e)"})," partner counts in the ",(0,r.jsx)(n.a,{href:"#per-reference-yield",children:"per-reference yield"})," line. ",(0,r.jsx)(n.code,{children:"p0/e0"})," or ",(0,r.jsx)(n.code,{children:"p1/e0"})," is a coverage gap (add photos from that angle); ",(0,r.jsx)(n.code,{children:"p8/e0"})," means overlapping views were found but disagree on the depth, which points at a bad camera pose rather than the imagery \u2014 re-check that stretch of the flight path in the sparse solution."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"seeded from a 50 mm ASSUMPTION"})," warning"]}),(0,r.jsxs)(n.td,{children:["Those photographs carry no EXIF ",(0,r.jsx)(n.code,{children:"FocalLengthIn35mmFilm"}),", so the solve started from a guess about 2.5\xd7 too long for typical drone or phone imagery. See ",(0,r.jsx)(n.a,{href:"#where-the-starting-focal-length-comes-from",children:"where the starting focal length comes from"}),"; attach a camera calibration if registration is poor."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Dense cloud is noisy"}),(0,r.jsxs)(n.td,{children:["Tighten the same three: raise ",(0,r.jsx)(n.em,{children:"confidence cutoff"}),", raise ",(0,r.jsx)(n.em,{children:"consistent views"}),", tighten ",(0,r.jsx)(n.em,{children:"depth tolerance"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Points look laid out on a regular grid"}),(0,r.jsxs)(n.td,{children:["The ",(0,r.jsx)(n.em,{children:"Dense MVS fusion voxel"})," is much coarser than a ground sample, so each point is the mean of many observations and that mean lands near its cell centre. Set the voxel to ",(0,r.jsx)(n.code,{children:"0"})," for the automatic size. Divide ",(0,r.jsx)(n.code,{children:"voxels"})," by ",(0,r.jsx)(n.code,{children:"consistency>=2"})," in the fusion funnel: a handful of observations per point is healthy, hundreds is not."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Points project away from the camera below the outcrop"}),(0,r.jsxs)(n.td,{children:["Depth quantisation on a wide depth range, not a matching failure. Check the ",(0,r.jsx)(n.code,{children:"bin width ... near / ... far"})," figures in the ",(0,r.jsx)(n.a,{href:"#per-view-depth-diagnostic",children:"per-view depth diagnostic"})," and raise ",(0,r.jsx)(n.em,{children:"Dense MVS depth hypotheses"})," \u2014 see that setting for the resolution table."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Small floating patches of noise below the model"}),(0,r.jsxs)(n.td,{children:["Removed by default by the component filter; raise ",(0,r.jsx)(n.em,{children:"Dense MVS min component size"})," if some survive. If a genuinely separate structure disappears instead, lower it or set ",(0,r.jsx)(n.code,{children:"0"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Blue/white halo around the outcrop"}),(0,r.jsxs)(n.td,{children:["Leave the ",(0,r.jsx)(n.em,{children:"Dense MVS sky filter"}
1)," on and re-run Dense MVS. Check the log for ",(0,r.jsx)(n.code,{children:"SkyWater semantic masks enabled"})," and a non-zero ",(0,r.jsx)(n.code,{children:"sky-masked"})," count. If VRGS reports that the model is unavailable, verify that the installed ",(0,r.jsx)(n.code,{children:"VRGS_ml"})," folder contains ",(0,r.jsx)(n.code,{children:"skywater_segformer_b2_fp16.onnx"})," and ",(0,r.jsx)(n.code,{children:"skywater_segformer_b2_fp32.onnx"}),"; the colour/confidence fallback is less reliable around white cloud and pale sky."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Sky filter is slow on the first run"}),(0,r.jsxs)(n.td,{children:["Without compatible CUDA, VRGS deliberately falls back to the FP32 CPU model. Let the first run finish: masks are cached under ",(0,r.jsx)(n.code,{children:"<workspace>/sky_masks/"}),", so unchanged photographs are substantially cheaper on later dense runs."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Sky filter removes part of the subject"}),(0,r.jsx)(n.td,{children:"The semantic filter removes only border-connected sky, but an object touching the image edge can still be misclassified. Turn the filter off for that workflow and compare the result; avoid lowering dense confidence or consistency thresholds, because those do not correct a segmentation error."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"GPS-tagged run looks worse than expected"}),(0,r.jsxs)(n.td,{children:["Check the EXIF GPS is valid; raise ",(0,r.jsx)(n.em,{children:"GPS sigma XY/Z"})," (trust GPS less), or turn ",(0,r.jsx)(n.em,{children:"Use GPS prior"})," off to reconstruct in a purely visual frame."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"CASMVSNET"})," backend does nothing"]}),(0,r.jsxs)(n.td,{children:["It needs ",(0,r.jsx)(n.code,{children:"casmvsnet.onnx"})," in the project ",(0,r.jsx)(n.code,{children:"MODELS"})," folder and a CUDA GPU; otherwise use ",(0,r.jsx)(n.code,{children:"PLANE_SWEEP"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:'"No mesh to texture"'}),(0,r.jsxs)(n.td,{children:["Run ",(0,r.jsx)(n.strong,{children:"Create Mesh"})," on the workflow first \u2014 texturing needs a ",(0,r.jsx)(n.code,{children:"<name> mesh"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Texture looks coarse / blurry"}),(0,r.jsxs)(n.td,{children:["Colour the mesh by ",(0,r.jsx)(n.strong,{children:"Texture resolution 0-1"})," and check the log's GSD line first. If most triangles are ",(0,r.jsx)(n.strong,{children:"source-limited"})," the photographs are the ceiling and more pages will not help. If ",(0,r.jsx)(n.strong,{children:"texture-limited"}),", use more ",(0,r.jsx)(n.em,{children:"pages"})," or a larger ",(0,r.jsx)(n.em,{children:"Page size"}),' \u2014 or accept the recommended budget, which sizes this for you. Also check for a "density coarsened" note; you may have hit the 8-page safety cap.']})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Mesh has holes"}),(0,r.jsxs)(n.td,{children:["Poisson ",(0,r.jsx)(n.em,{children:"Trim factor"})," too aggressive \u2014 raise it or set ",(0,r.jsx)(n.code,{children:"0"})," for a watertight surface; or you used ",(0,r.jsx)(n.strong,{children:"Greedy"}),", which leaves gaps in sparse areas (switch to ",(0,r.jsx)(n.strong,{children:"Poisson"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Patches show flat colour, not photo detail"}),(0,r.jsx)(n.td,{children:"Those triangles were seen by no photograph (occluded or outside every image), so they fall back to per-vertex colour. Add photos covering that area or re-shoot it."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Faint seams across the texture"}),(0,r.jsxs)(n.td,{children:["Every texel is blended from up to three photographs weighted by how well each sees ",(0,r.jsx)(n.em,{children:"that point"}),", so exposure seams should dissolve. If they persist, lower ",(0,r.jsx)(n.em,{children:"Texture blend sharpness"})," (4 \u2192 1) to spread the blend wider."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["Smeared or ghosted ",(0,r.jsx)(n.strong,{children:"bands"})," across the texture"]}),(0,r.jsxs)(n.td,{children:["A lens-distortion mismatch, not a texturing fault. Check the bake's ",(0,r.jsx)(n.a,{href:"#lens-distortion",children:"distortion self-check"})," line \u2014 if it says the other setting agrees better, change ",(0,r.jsx)(n.strong,{children:"Texture apply lens distortion"})," and re-bake. The lasting fix is to re-run the reconstruction so the camera model is solved correctly for a ",(0,r.jsx)(n.a,{href:"#cameras-that-correct-their-own-lens",children:"firmware-corrected camera"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Flat single-colour triangles among good texture"}),(0,r.jsx)(n.td,{children:'Check the occlusion outcome line. Triangles "framed by NO photo at all" are a coverage gap \u2014 that part of the outcrop was never shot from a usable angle.'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["Texture bake sits a long time in ",(0,r.jsx)(n.em,{children:"UV unwrap"})]}),(0,r.jsxs)(n.td,{children:["Normal on a large mesh \u2014 it is the slow stage. Check the log for the chunk split (",(0,r.jsx)(n.code,{children:"N unwrap chunk(s)"}),"); the bar advances as chunks finish. Cancel with the \u2715 on the progress bar if you would rather decimate the mesh first."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Log warns about texels per triangle"}),(0,r.jsxs)(n.td,{children:["The mesh is much denser than the atlas can resolve. Decimate the mesh before texturing, or raise the ",(0,r.jsx)(n.em,{children:"Page size"})," / page count."]})]})]})]})]})}function c(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>o,x:()=>a});var t=s(96540);const r={},i=t.createContext(r);function o(e){const n=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.