Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
foliage
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Elphel
foliage
Commits
6eb3d420
Commit
6eb3d420
authored
May 25, 2026
by
Andrey Filippov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CLAUDE: Add v88_to_colmap_dense.sh — full pipeline from v88 to fused.ply
parent
978388ba
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
190 additions
and
0 deletions
+190
-0
v88_to_colmap_dense.sh
scripts/v88_to_colmap_dense.sh
+190
-0
No files found.
scripts/v88_to_colmap_dense.sh
0 → 100755
View file @
6eb3d420
#!/usr/bin/env bash
# =============================================================================
# v88_to_colmap_dense.sh — Elphel LWIR-16 v88 folder → COLMAP dense 3D model
#
# Copyright (C) 2026 Elphel, Inc.
# SPDX-License-Identifier: GPL-3.0-or-later
# =============================================================================
#
# Full pipeline from a v88 processing folder to a dense point cloud:
#
# Step 1 Python: inverse-shift TERRAIN-DISP-MERGED frames using ELEV_GND
# → COLMAP text sparse model (cameras.txt / images.txt / PNGs)
#
# Step 2 colmap model_converter: text → binary sparse model
#
# Step 3 colmap image_undistorter: prepare dense workspace
#
# Step 4 Python: write sequential patch-match.cfg (±N temporal neighbours)
# (required because we skip COLMAP feature matching)
#
# Step 5 colmap patch_match_stereo: GPU PatchMatch depth maps
# (with filter pass so stereo_fusion accepts the result)
#
# Step 6 colmap stereo_fusion: merge depth maps → fused.ply point cloud
#
# Usage:
# bash scripts/v88_to_colmap_dense.sh \
# --v88 /path/to/.../1763232146_700470/v88 \
# --out /tmp/colmap_run
#
# Optional:
# --neighbours N source frames each side for PatchMatch (default: 10)
# --depth_min M minimum depth in metres (default: auto)
# --depth_max M maximum depth in metres (default: auto)
# --hfov deg camera horizontal FoV in degrees (default: 32)
#
# Prerequisites:
# • conda-forge COLMAP with CUDA in PATH:
# export PATH="/home/elphel/miniforge3/bin:$PATH"
# • Python 3 with numpy, scipy, Pillow (system or conda env)
# • foliage repo checked out; script must be run from its root:
# bash scripts/v88_to_colmap_dense.sh ...
# =============================================================================
set
-euo
pipefail
SCRIPT_DIR
=
"
$(
cd
"
$(
dirname
"
$0
"
)
"
&&
pwd
)
"
REPO_ROOT
=
"
$(
cd
"
$SCRIPT_DIR
/.."
&&
pwd
)
"
PYTHON
=
"
${
PYTHON
:-
/usr/bin/python3
}
"
# ── Defaults ─────────────────────────────────────────────────────────────────
V88
=
""
OUT
=
""
NEIGHBOURS
=
10
DEPTH_MIN
=
""
DEPTH_MAX
=
""
HFOV
=
32.0
# ── Argument parsing ──────────────────────────────────────────────────────────
usage
()
{
echo
"Usage:
$0
--v88 <path> --out <dir> [--neighbours N] [--depth_min M] [--depth_max M] [--hfov deg]"
>
&2
exit
1
}
while
[[
$#
-gt
0
]]
;
do
case
"
$1
"
in
--v88
)
V88
=
"
$2
"
;
shift
2
;;
--out
)
OUT
=
"
$2
"
;
shift
2
;;
--neighbours
)
NEIGHBOURS
=
"
$2
"
;
shift
2
;;
--depth_min
)
DEPTH_MIN
=
"
$2
"
;
shift
2
;;
--depth_max
)
DEPTH_MAX
=
"
$2
"
;
shift
2
;;
--hfov
)
HFOV
=
"
$2
"
;
shift
2
;;
*
)
usage
;;
esac
done
[[
-n
"
$V88
"
&&
-n
"
$OUT
"
]]
||
usage
# ── Locate input files ────────────────────────────────────────────────────────
# Timestamp = name of the parent directory of v88
TS
=
"
$(
basename
"
$(
dirname
"
$V88
"
)
"
)
"
TIFF
=
"
$V88
/
${
TS
}
-TERRAIN-DISP-MERGED.tiff"
XML
=
"
$V88
/
${
TS
}
-INTERFRAME.corr-xml"
ELEV
=
"
$V88
/
${
TS
}
-ELEV_GND.tiff"
for
f
in
"
$TIFF
"
"
$XML
"
"
$ELEV
"
;
do
[[
-f
"
$f
"
]]
||
{
echo
"ERROR: missing
$f
"
>
&2
;
exit
1
;
}
done
echo
"=== Input files verified ==="
echo
" TIFF:
$TIFF
"
echo
" XML:
$XML
"
echo
" ELEV_GND:
$ELEV
"
# ── Auto-detect depth range from ELEV_GND if not specified ───────────────────
if
[[
-z
"
$DEPTH_MIN
"
||
-z
"
$DEPTH_MAX
"
]]
;
then
read
DEPTH_MIN DEPTH_MAX < <
(
$PYTHON
-
<<
PYEOF
import numpy as np
from PIL import Image
elev = np.array(Image.open("
$ELEV
"), dtype=np.float32)
z = -elev # positive AGL depth
# Add 40 % margin around the observed range
lo = z.min() * 0.6
hi = z.max() * 1.4
print(f"{lo:.0f} {hi:.0f}")
PYEOF
)
echo
" Auto depth range:
${
DEPTH_MIN
}
–
${
DEPTH_MAX
}
m AGL"
fi
mkdir
-p
"
$OUT
"
# ── Step 1: Perspective remap → COLMAP text sparse model ─────────────────────
COLMAP_TEXT
=
"
$OUT
/colmap_text"
echo
""
echo
"=== Step 1: Perspective remap ==="
$PYTHON
"
$REPO_ROOT
/scripts/elphel_to_colmap_perspective.py"
\
--tiff
"
$TIFF
"
\
--xml
"
$XML
"
\
--elev_gnd
"
$ELEV
"
\
--out
"
$COLMAP_TEXT
"
\
--hfov
"
$HFOV
"
# ── Step 2: Convert text sparse model → binary ───────────────────────────────
DENSE_ROOT
=
"
$OUT
/dense_workspace"
echo
""
echo
"=== Step 2: Convert sparse model text → binary ==="
mkdir
-p
"
$DENSE_ROOT
/sparse"
colmap model_converter
\
--input_path
"
$COLMAP_TEXT
/sparse/0"
\
--output_path
"
$DENSE_ROOT
/sparse"
\
--output_type
BIN
# ── Step 3: Undistort images (prepares dense/ workspace structure) ────────────
echo
""
echo
"=== Step 3: Undistort images ==="
colmap image_undistorter
\
--image_path
"
$COLMAP_TEXT
/images"
\
--input_path
"
$DENSE_ROOT
/sparse"
\
--output_path
"
$DENSE_ROOT
/dense"
\
--output_type
COLMAP
# ── Step 4: Write sequential patch-match.cfg ─────────────────────────────────
echo
""
echo
"=== Step 4: Write patch-match.cfg (neighbours = ±
${
NEIGHBOURS
}
) ==="
$PYTHON
-
<<
PYEOF
import os
from pathlib import Path
images_dir = Path("
$DENSE_ROOT
/dense/images")
images = sorted(os.listdir(images_dir))
N =
$NEIGHBOURS
cfg_lines = []
for i, img in enumerate(images):
sources = [images[j] for j in range(max(0, i-N), min(len(images), i+N+1)) if j != i]
cfg_lines.append(img)
cfg_lines.append(', '.join(sources))
cfg_path = Path("
$DENSE_ROOT
/dense/stereo/patch-match.cfg")
cfg_path.write_text('\n'.join(cfg_lines) + '\n')
print(f" Wrote {len(images)} entries to {cfg_path}")
PYEOF
# ── Step 5: PatchMatch stereo (GPU) ──────────────────────────────────────────
echo
""
echo
"=== Step 5: PatchMatch stereo depth=[
${
DEPTH_MIN
}
,
${
DEPTH_MAX
}
] m ==="
echo
" (this takes ~20-30 min on RTX 5060 Ti for 185 frames)"
colmap patch_match_stereo
\
--workspace_path
"
$DENSE_ROOT
/dense"
\
--workspace_format
COLMAP
\
--PatchMatchStereo
.depth_min
"
$DEPTH_MIN
"
\
--PatchMatchStereo
.depth_max
"
$DEPTH_MAX
"
\
--PatchMatchStereo
.geom_consistency
true
\
--PatchMatchStereo
.filter
true
# ── Step 6: Stereo fusion → dense point cloud ─────────────────────────────────
echo
""
echo
"=== Step 6: Stereo fusion ==="
colmap stereo_fusion
\
--workspace_path
"
$DENSE_ROOT
/dense"
\
--workspace_format
COLMAP
\
--input_type
geometric
\
--output_path
"
$OUT
/fused.ply"
\
--StereoFusion
.min_num_pixels 2
\
--StereoFusion
.max_depth_error 0.05
\
--StereoFusion
.max_reproj_error 4
echo
""
echo
"=== Done ==="
echo
"Dense point cloud:
$OUT
/fused.ply"
echo
"Size:
$(
du
-sh
"
$OUT
/fused.ply"
2>/dev/null |
cut
-f1
)
"
echo
""
echo
"Open in Blender:"
echo
" DISPLAY=:0 blender --python-expr
\"
import bpy; bpy.ops.wm.ply_import(filepath='
$OUT
/fused.ply')
\"
&"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment