Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. ย See raw diff
- configs/jobs/demo_camera_modes.json +18 -0
- configs/jobs/ground_ablation.json +11 -0
- configs/jobs/veh_60k_v2.json +14 -0
- src/nusim/nusim/__init__.py +33 -0
- src/nusim/nusim/aug/__init__.py +1 -0
- src/nusim/nusim/aug/artifixer_prep.py +128 -0
- src/nusim/nusim/aug/artifixer_run.py +257 -0
- src/nusim/nusim/aug/lane_check.py +145 -0
- src/nusim/nusim/aug/lane_fix.py +219 -0
- src/nusim/nusim/aug/map_aug.py +115 -0
- src/nusim/nusim/aug/rig_aug.py +170 -0
- src/nusim/nusim/aug/translate.py +117 -0
- src/nusim/nusim/cli/__init__.py +1 -0
- src/nusim/nusim/cli/doctor.py +255 -0
- src/nusim/nusim/cli/main.py +1487 -0
- src/nusim/nusim/config.py +57 -0
- src/nusim/nusim/data/__init__.py +5 -0
- src/nusim/nusim/data/index.py +459 -0
- src/nusim/nusim/data/rig.py +204 -0
- src/nusim/nusim/io/__init__.py +5 -0
- src/nusim/nusim/io/jsonstream.py +103 -0
- src/nusim/nusim/io/ktx2.py +139 -0
- src/nusim/nusim/io/ply.py +170 -0
- src/nusim/nusim/jobs.py +116 -0
- src/nusim/nusim/log.py +67 -0
- src/nusim/nusim/paths.py +154 -0
- src/nusim/nusim/render/__init__.py +9 -0
- src/nusim/pyproject.toml +25 -0
- src/vkgs/CMakeLists.txt +168 -0
- src/vkgs/shaders/bindless.glsl +20 -0
- src/vkgs/shaders/depth_common.glsl +19 -0
- src/vkgs/shaders/env.frag +21 -0
- src/vkgs/shaders/env.vert +14 -0
- src/vkgs/shaders/fxaa.comp +80 -0
- src/vkgs/shaders/gs_common.glsl +32 -0
- src/vkgs/shaders/gs_preprocess.comp +145 -0
- src/vkgs/shaders/gs_radix_common.glsl +19 -0
- src/vkgs/shaders/gs_radix_hist.comp +19 -0
- src/vkgs/shaders/gs_radix_scan.comp +30 -0
- src/vkgs/shaders/gs_radix_scatter.comp +53 -0
- src/vkgs/shaders/gs_sort.comp +32 -0
- src/vkgs/shaders/gs_splat.frag +21 -0
- src/vkgs/shaders/gs_splat.vert +33 -0
- src/vkgs/shaders/ibl_common.glsl +36 -0
- src/vkgs/shaders/line.frag +7 -0
- src/vkgs/shaders/line.vert +17 -0
- src/vkgs/shaders/nodes/isp.glsl +82 -0
- src/vkgs/shaders/pbr.frag +45 -0
- src/vkgs/shaders/pbr.vert +25 -0
- src/vkgs/shaders/pbr_gltf.frag +128 -0
configs/jobs/demo_camera_modes.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_doc": "๋ฐํ์ฉ ์นด๋ฉ๋ผ ๋ชจ๋ ์ฃผํ ์์ (2026-10-12). demo_videos ์ ๊ฐ์ ์๋ ์ฃผํ 20์ด ์ฐฝ ํ๋ฉด ๋
นํ๋ฅผ ์นด๋ฉ๋ผ ๋ชจ๋(--camera-mode)๋ง ๋ฐ๊ฟ ๊น์ด ยท ์๋งจํฑ ยท ํ๋ฐฑ์ผ๋ก โ ๋ชจ๋๋ ์์ชฝ ์ค ์นด๋ฉ๋ผ ์ผ์ ์์์๋ง ๊ฑธ๋ฆฌ๊ณ ๋ฉ์ธ ์ฃผํ ํ๋ฉด ยท BEV ๋ RGB ๊ทธ๋๋ก. ์๋งจํฑ์ ๋ชจ๋ธ ์ semantic.rgba ๊ฐ ์์ด์ผ ํ๋ค (nusim recon semantic --location singapore-onenorth). ์ฐฝ์ด ํ๋ฉด์ ๋ ์ ํผ์ ๋ฌ๋ฆฐ๋ค โ ์ฌ์ฉ์์๊ฒ ๋ฏธ๋ฆฌ ์๋ฆด ๊ฒ. ๊ฒฐ๊ณผ: outputs/videos/camera_<๋ชจ๋>.mp4 (+ _poster.jpg).",
|
| 3 |
+
"name": "demo_camera_modes",
|
| 4 |
+
"vars": {
|
| 5 |
+
"map": "render map singapore-onenorth --no-sensors --",
|
| 6 |
+
"common": "--autopilot --front-cams --bev-pip --hide-panels --autoshot 400",
|
| 7 |
+
"dir": "outputs/videos",
|
| 8 |
+
"enc": "render encode --bitrate 2M"
|
| 9 |
+
},
|
| 10 |
+
"steps": [
|
| 11 |
+
{"name": "rec_depth", "run": "${map} ${common} --camera-mode depth --record-screen ${dir}/camera_depth/frames"},
|
| 12 |
+
{"name": "enc_depth", "run": "${enc} ${dir}/camera_depth/frames ${dir}/camera_depth.mp4 --poster ${dir}/camera_depth_poster.jpg --caption '๊น์ด ์นด๋ฉ๋ผ โ ๋ก๊ทธ ๊ฑฐ๋ฆฌ turbo ์ (0~80 m)'"},
|
| 13 |
+
{"name": "rec_semantic", "run": "${map} ${common} --camera-mode semantic --record-screen ${dir}/camera_semantic/frames"},
|
| 14 |
+
{"name": "enc_semantic", "run": "${enc} ${dir}/camera_semantic/frames ${dir}/camera_semantic.mp4 --poster ${dir}/camera_semantic_poster.jpg --caption '์๋งจํฑ ์ธ๊ทธ๋ฉํ
์ด์
์นด๋ฉ๋ผ โ nuScenes-lidarseg ๋ผ๋ฒจ (Cityscapes ์)'"},
|
| 15 |
+
{"name": "rec_gray", "run": "${map} ${common} --camera-mode gray --record-screen ${dir}/camera_gray/frames"},
|
| 16 |
+
{"name": "enc_gray", "run": "${enc} ${dir}/camera_gray/frames ${dir}/camera_gray.mp4 --poster ${dir}/camera_gray_poster.jpg --caption 'ํ๋ฐฑ ์นด๋ฉ๋ผ โ Rec.601 ๋ฃจ๋ง'"}
|
| 17 |
+
]
|
| 18 |
+
}
|
configs/jobs/ground_ablation.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_doc": "์ง๋ฉด ๋ชจ๋ธ (HUGSIM ๋ค์ค ํ๋ฉด ์ง๋ฉด, nusim/recon/ground.py) ํจ๊ณผ ์ ๊ฒ (2026-10-10). scene-0003 ์ ๋งต ๋ฐฐ์น์ ๊ฐ์ ํ๋ฆฌ์
(day_map, 15k)์ผ๋ก ์ง๋ฉด ๋ชจ๋ธ ์์ด / ฮป 10 / ฮป 100 ํ์ตํ๊ณ , ์๋ ๊ฒฝ๋ก ํ๊ฐ(eval)์ ์ค์ ์ฐจ์ ๋ณ๊ฒฝ ์์ (onenorth_0003 ํ์ผ์์ ๋บ ์ฃผํ 0127ยท0969, ์ ์ฝ 4 m โ tile-eval)์ ๋น๊ตํ๋ค. L_ground ๋ mยฒ ๋จ์ ๋ถ์ฐ์ด๋ผ ๋
ธ๋ฉด ์ก์ 0.05 m ์ด๋ฉด 0.0025 โ ์ธก๊ด ์์ค(~0.1)๊ณผ ๋ง์ถ๋ ค๋ฉด ฮป 10~100. ๋งต ๋ฐฐ์น(day_map)๊ฐ ๋๋ ๋ค์ ๋๋ฆฐ๋ค (์ฌ์ฉ์ ๊ฒฐ์ 2026-10-10: GPU ์คํ์ ๋ฐฐ์น ๋ค).",
|
| 3 |
+
"name": "ground_ablation",
|
| 4 |
+
"vars": {"base": "recon train scene-0003 --preset day_map", "out": "outputs/gs_exp/ground"},
|
| 5 |
+
"steps": [
|
| 6 |
+
{"name": "base", "run": "${base} --out ${out}/base"},
|
| 7 |
+
{"name": "g10", "run": "${base} --ground-lambda 10 --out ${out}/g10"},
|
| 8 |
+
{"name": "g100", "run": "${base} --ground-lambda 100 --out ${out}/g100"},
|
| 9 |
+
{"name": "lane", "run": "recon tile-eval onenorth_0003 --dir ${out}/base --dir ${out}/g10 --dir ${out}/g100 --out ${out}/tile_eval"}
|
| 10 |
+
]
|
| 11 |
+
}
|
configs/jobs/veh_60k_v2.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_doc": "์ฐจ๋ ๋์ ํ์ต 60k ์ฌํ์ต (2026-10-06). veh_60k ๋ MCMC ์ฌ๋ฐฐ์น๊ฐ ๊ฐ์ฒด ๊ฐ์ฐ์์์ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ฎ๊ฒจ ์ฐจ๋ 35๋ ์ค 17๋๋ง ๋จ์๋ค โ ์ฌ๋ฐฐ์น ํ์ ์ง์ ์ ๊ฐ์ฒด ๊ฐ์ฐ์์ ๋ถํฌ๋ช
๋ ํํ(train.py). 3k ์ ๊ฒ์์ 35๋ ์ ๋ถ ๋จ์.",
|
| 3 |
+
"name": "veh_60k_v2",
|
| 4 |
+
"steps": [
|
| 5 |
+
{
|
| 6 |
+
"name": "train",
|
| 7 |
+
"run": "recon train scene-0003 --strategy mcmc --depth-lambda 0.1 --objects vehicles --eval-every 5000 --out outputs/gs/scene-0003_veh60p"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"name": "eval",
|
| 11 |
+
"run": "recon eval scene-0003 --dir outputs/gs/scene-0003_veh60p --geom"
|
| 12 |
+
}
|
| 13 |
+
]
|
| 14 |
+
}
|
src/nusim/nusim/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""nuScenes ์ฑ๊ฐํฌ๋ฅด ๋งต ๊ธฐ๋ฐ Vulkan 3DGS closed-loop ์๋ฎฌ๋ ์ดํฐ.
|
| 2 |
+
|
| 3 |
+
๋ ์ด์ด ๊ตฌ์กฐ (docs/architecture.md ์ฐธ๊ณ ):
|
| 4 |
+
|
| 5 |
+
data/ nuScenes ์ฝ๊ธฐ โ scene ์ธ๋ฑ์ค, ์นด๋ฉ๋ผ ๋ฆฌ๊ทธ, ego ๊ถค์
|
| 6 |
+
maps/ ๋งต ํ์ฅ(map expansion) โ ์ฐจ์ ๊ทธ๋ํ, ์ฃผํ๊ฐ๋ฅ์์ญ
|
| 7 |
+
recon/ 3DGS ์ฌ๊ตฌ์ฑ(ํ์ต) โ gsplat ๊ธฐ๋ฐ
|
| 8 |
+
io/ .ply / .splat ์ง๋ ฌํ โ ํ์ต๊ธฐ์ Vulkan ๋ ๋๋ฌ ์ฌ์ด์ ๊ตํ ํฌ๋งท
|
| 9 |
+
sim/ closed-loop ํ๊ฒฝ โ ego ์ํ ์ ๋ถ, 6์นด๋ฉ๋ผ ๊ด์ธก ์์ฑ
|
| 10 |
+
cli/ ์ปค๋งจ๋๋ผ์ธ ์ง์
์
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
__version__ = "0.1.0"
|
| 14 |
+
|
| 15 |
+
# 6๊ฐ ์นด๋ฉ๋ผ. nuScenes ์ ํ์ค ์ด๋ฆ์ด์ ์ด ํ๋ก์ ํธ ์ ์ฒด์์ ์ฐ๋ ์ ์ค ์์๋ค.
|
| 16 |
+
# ์์๊ฐ ๋ฐ๋๋ฉด ๋ฉํฐ๋ทฐ ํ
์์ ์ถ ์๋ฏธ๊ฐ ๋ฌ๋ผ์ง๋ฏ๋ก ์ฌ๊ธฐ์๋ง ์ ์ํ๋ค.
|
| 17 |
+
CAMERAS = (
|
| 18 |
+
"CAM_FRONT_LEFT",
|
| 19 |
+
"CAM_FRONT",
|
| 20 |
+
"CAM_FRONT_RIGHT",
|
| 21 |
+
"CAM_BACK_RIGHT",
|
| 22 |
+
"CAM_BACK",
|
| 23 |
+
"CAM_BACK_LEFT",
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# ์ฑ๊ฐํฌ๋ฅด ๋งต 3์ข
. boston-seaport ๋ ์ด ํ๋ก์ ํธ์ ๋ฒ์ ๋ฐ์ด๋ค.
|
| 27 |
+
SINGAPORE_MAPS = (
|
| 28 |
+
"singapore-onenorth",
|
| 29 |
+
"singapore-queenstown",
|
| 30 |
+
"singapore-hollandvillage",
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
__all__ = ["CAMERAS", "SINGAPORE_MAPS", "__version__"]
|
src/nusim/nusim/aug/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""๋ฐ์ดํฐ์
๋ค์์ฑ ์ฆ๊ฐ โ ์ฌ๊ตฌ์ฑ ์ฅ๋ฉด์ผ๋ก ์ ์นด๋ฉ๋ผ ์์ (rig_aug), ์์ฑ ๋ชจ๋ธ๋ก ๋ฐคยท๋น โ ๋ฎ(translate)."""
|
src/nusim/nusim/aug/artifixer_prep.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ArtiFixer(nv-tlabs/artifixer, SIGGRAPH 2026) ์
๋ ฅ ๋ง๋ค๊ธฐ โ ์์ ๋ณํ ๋ ๋๋ฅผ ํ์ฐ ๋ชจ๋ธ๋ก ๊ณ ์น๋ ์คํ (nusim aug artifixer-prep).
|
| 2 |
+
|
| 3 |
+
ArtiFixer ๋ 3D ์ฌ๊ตฌ์ฑ ๋ ๋(+๋ถํฌ๋ช
๋)์ ์ค์ ์ฐธ์กฐ ์ฌ์ง ๋ช ์ฅ, ์นด๋ฉ๋ผ ์์ธ๋ฅผ ๋ฐ์ ๋ ๋์ ํ๋ฆผ ยท ๊ตฌ๋ฉ ยท ๋ ๋ค๋๋ ์กฐ๊ฐ์
|
| 4 |
+
์๊ธฐํ๊ท ๋น๋์ค ํ์ฐ(Wan2.1)์ผ๋ก ๊ณ ์น๋ค. ์๋ ํ์ดํ๋ผ์ธ์ COLMAP โ 3DGRUT ๋ฅผ ์๋ก ํ์ตํ์ง๋ง, ์ฌ๊ธฐ์๋ ์ฐ๋ฆฌ ๊ตฌ๊ฐ ๋ชจ๋ธ
|
| 5 |
+
(outputs/gs_day/<์ฌ>/seg<k>)์ gsplat ๋ ๋๋ฅผ ๊ทธ๋๋ก ๋ฃ๋๋ค โ model_eval/datasets/reconstructed_colmap_eval.py ํ์:
|
| 6 |
+
|
| 7 |
+
<out>/split.json {"test": {<id>: {transforms_path, image_root, render_dir, opacity_dir,
|
| 8 |
+
selected_indices_path, target_indices_path, prompt_path, camera_scale, has_gt}}}
|
| 9 |
+
<out>/transforms.json OPENCV ๋ด๋ถ ํ๋ผ๋ฏธํฐ + frames[i].transform_matrix (NeRFStudio/OpenGL c2w, ๋ฏธํฐ)
|
| 10 |
+
<out>/images/ ์ฐธ์กฐ ์ฌ์ง (์๋ ์์ธ์ ์ค์ nuScenes ์ฌ์ง, ๋ ๋ ํด์๋๋ก ์ค์)
|
| 11 |
+
<out>/renders/{i:05d}.png ๋ ๋ (์ ๋ณด์ ยท ํ๋ ํฉ์ฑ = nusim recon eval ๊ณผ ๊ฐ์ ์์)
|
| 12 |
+
<out>/opacity/{i:05d}.png ๋ ๋ ๋ถํฌ๋ช
๋ ฮฑ (ํ๋ ํฉ์ฑ ์ )
|
| 13 |
+
<out>/selected_indices.json ์ฐธ์กฐ(์ค์ ์ฌ์ง) ํ๋ ์ = 0..N-1
|
| 14 |
+
<out>/target_indices.json ๊ณ ์น ํ๋ ์ = ๋ณํ ์์ธ N..2N-1
|
| 15 |
+
<out>/caption.txt ์บก์
โ ์๋ฒ ๋ฉ(caption.h5)์ artifixer ํ๊ฒฝ์์ UMT5 ๋ก (scripts/artifixer_caption.py)
|
| 16 |
+
|
| 17 |
+
์์ธ๋ ํ์ต์ด ๋ณด์ ํ ์ธ๋ถ ํ๋ผ๋ฏธํฐ(camera_adjust)๋ฅผ ์ ์ฉํ ๋ ๋ ์์ธ ๊ทธ๋๋ก๋ค. ๋ฏธํฐ ๋จ์๋ผ metric_scale = 1,
|
| 18 |
+
camera_scale = 0.01 (ArtiFixer prepare_colmap_artifixer_inputs ์ DEFAULT_CAMERA_CONDITIONING_SCALE).
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import json
|
| 24 |
+
import logging
|
| 25 |
+
from dataclasses import replace
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
import numpy as np
|
| 29 |
+
|
| 30 |
+
LOG = logging.getLogger(__name__)
|
| 31 |
+
CAMERA_CONDITIONING_SCALE = 0.01
|
| 32 |
+
OPENCV_TO_OPENGL = np.diag([1.0, -1.0, -1.0, 1.0])
|
| 33 |
+
DEFAULT_CAPTION = ("A daytime dashcam video driving along a tree-lined street in Singapore, asphalt road with white "
|
| 34 |
+
"lane markings, curbs, sidewalks, trees and buildings, clear sky, realistic photo.")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def prepare(scene_name: str, model_dir: Path, variant: dict, out: Path, cam: str = "CAM_FRONT",
|
| 38 |
+
width: int | None = None, max_frames: int | None = None, timestamps: set | None = None, start: int = 0, caption: str = DEFAULT_CAPTION,
|
| 39 |
+
tag: str = "", log=LOG.info) -> dict:
|
| 40 |
+
import torch
|
| 41 |
+
from PIL import Image
|
| 42 |
+
|
| 43 |
+
from nusim.aug.rig_aug import transform_calib
|
| 44 |
+
from nusim.cli.main import _model_data, _model_objects, _model_views
|
| 45 |
+
from nusim.io.ply import read_ply
|
| 46 |
+
from nusim.recon.dataset import build_views
|
| 47 |
+
from nusim.recon.train import CameraAdjust, ColorCorrection, SkyCube, _to_params, composite_sky, render
|
| 48 |
+
|
| 49 |
+
model_dir = Path(model_dir)
|
| 50 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 51 |
+
for d in ("images", "renders", "opacity"):
|
| 52 |
+
(out / d).mkdir(exist_ok=True)
|
| 53 |
+
g = read_ply(model_dir / "point_cloud.ply")
|
| 54 |
+
vk = _model_views(model_dir)
|
| 55 |
+
sub, ds = _model_data(scene_name, model_dir)
|
| 56 |
+
kw = dict(downscale=vk["downscale"], frame_stride=vk["frame_stride"], keyframes_only=vk["keyframes_only"],
|
| 57 |
+
objects=_model_objects(model_dir), load_images=False, mask_dynamic=False, log=LOG.debug)
|
| 58 |
+
# ํ์ต๊ณผ ๊ฐ์ ๋ทฐ ๋ฒํธ (์ ๋ณด์ ์ ํ๋ ์๋ณ ๊ฒ์ธ์ด ์ด ๋ฒํธ) โ ๊ทธ์ค ํ ์นด๋ฉ๋ผ๋ง ์ด๋ค
|
| 59 |
+
real = build_views(sub, ds, g.origin, **kw)
|
| 60 |
+
vsub = replace(sub, calib={c: (transform_calib(k, variant.get("shift_m", (0, 0, 0)), variant.get("rot_deg", (0, 0, 0)))
|
| 61 |
+
if c == cam else k) for c, k in sub.calib.items()})
|
| 62 |
+
var = build_views(vsub, ds, g.origin, **kw)
|
| 63 |
+
from nusim import CAMERAS
|
| 64 |
+
|
| 65 |
+
cid = CAMERAS.index(cam)
|
| 66 |
+
rv = [v for v in real.all if v.cam_id == cid][start:]
|
| 67 |
+
tv = [v for v in var.all if v.cam_id == cid][start:]
|
| 68 |
+
if timestamps is not None: # ๊ตฌ๊ฐ ๋ชจ๋ธ์ด '๋งก๋' ํ๋ ์๋ง (nusim.aug.map_aug.assign_front_frames)
|
| 69 |
+
keep = [i for i, v in enumerate(rv) if v.pose.timestamp in timestamps]
|
| 70 |
+
rv, tv = [rv[i] for i in keep], [tv[i] for i in keep]
|
| 71 |
+
if max_frames:
|
| 72 |
+
rv, tv = rv[:max_frames], tv[:max_frames]
|
| 73 |
+
if width and width != rv[0].width: # 8 GB VRAM: ArtiFixer ํ ํฐ ์๊ฐ ํด์๋์ ๋น๋ก โ ๋ ๋ ํด์๋ ์์ฒด๋ฅผ ์ค์ธ๋ค
|
| 74 |
+
s = width / rv[0].width
|
| 75 |
+
|
| 76 |
+
def small(v):
|
| 77 |
+
K = v.K.copy()
|
| 78 |
+
K[:2] *= s
|
| 79 |
+
return replace(v, K=K, width=width, height=round(v.height * s))
|
| 80 |
+
rv, tv = [small(v) for v in rv], [small(v) for v in tv]
|
| 81 |
+
assert [v.pose.timestamp for v in rv] == [v.pose.timestamp for v in tv]
|
| 82 |
+
n = len(rv)
|
| 83 |
+
dev = torch.device("cuda")
|
| 84 |
+
params = _to_params(g, dev)
|
| 85 |
+
color = ColorCorrection.load(model_dir / "color_correction.pt", 6, len(real.all), dev)
|
| 86 |
+
adj = None
|
| 87 |
+
if (model_dir / "camera_adjust.pt").exists():
|
| 88 |
+
adj = CameraAdjust(6).to(dev)
|
| 89 |
+
adj.load_state_dict(torch.load(model_dir / "camera_adjust.pt", weights_only=True))
|
| 90 |
+
sky = SkyCube.from_state(torch.load(model_dir / "sky.pt", weights_only=True)) if (model_dir / "sky.pt").exists() else None
|
| 91 |
+
|
| 92 |
+
frames = []
|
| 93 |
+
w, h = rv[0].width, rv[0].height
|
| 94 |
+
K = rv[0].K
|
| 95 |
+
for i, v in enumerate(rv + tv):
|
| 96 |
+
with torch.no_grad():
|
| 97 |
+
img, alpha, info = render(params, v, g.sh_degree, cam_adjust=adj, cam_grad=False)
|
| 98 |
+
img = composite_sky(img, alpha, sky, v, info["viewmat"])
|
| 99 |
+
img = color(img, v.cam_id, rv[i % n].idx).clamp(0, 1) # ๋ณํ ํ๋ ์๋ ๊ฐ์ ์๊ฐ ์ค์ ํ๋ ์์ ๊ฒ์ธ
|
| 100 |
+
Image.fromarray((img[0].cpu().numpy() * 255 + 0.5).astype(np.uint8)).save(out / "renders" / f"{i:05d}.png")
|
| 101 |
+
Image.fromarray((alpha[0, ..., 0].clamp(0, 1).cpu().numpy() * 255 + 0.5).astype(np.uint8)).save(
|
| 102 |
+
out / "opacity" / f"{i:05d}.png")
|
| 103 |
+
w2c = info["viewmat"][0].double().cpu().numpy() if info["viewmat"].dim() == 3 else info["viewmat"].double().cpu().numpy()
|
| 104 |
+
c2w = np.linalg.inv(w2c) @ OPENCV_TO_OPENGL
|
| 105 |
+
fr = rv[i % n].pose
|
| 106 |
+
name = f"images/{i % n:05d}.jpg"
|
| 107 |
+
if i < n: # ์ฐธ์กฐ = ์๋ ์์ธ์ ์ค์ ์ฌ์ง
|
| 108 |
+
Image.open(ds.root / fr.filename).convert("RGB").resize((w, h), Image.BICUBIC).save(out / name, quality=95)
|
| 109 |
+
frames.append({"file_path": name, "transform_matrix": c2w.tolist(), "timestamp": int(fr.timestamp),
|
| 110 |
+
"original_filename": fr.filename, "role": "reference" if i < n else "target"})
|
| 111 |
+
tr = {"camera_model": "OPENCV", "w": int(w), "h": int(h), "fl_x": float(K[0, 0]), "fl_y": float(K[1, 1]),
|
| 112 |
+
"cx": float(K[0, 2]), "cy": float(K[1, 2]), "k1": 0.0, "k2": 0.0, "p1": 0.0, "p2": 0.0, "frames": frames}
|
| 113 |
+
(out / "transforms.json").write_text(json.dumps(tr, indent=1))
|
| 114 |
+
(out / "selected_indices.json").write_text(json.dumps(list(range(n))))
|
| 115 |
+
(out / "target_indices.json").write_text(json.dumps(list(range(n, 2 * n))))
|
| 116 |
+
(out / "caption.txt").write_text(caption)
|
| 117 |
+
sid = f"{scene_name}_{model_dir.name}_{cam}_{variant['name']}{tag}" # tag: ํ ๊ตฌ๊ฐ์ ๋๋ ์กฐ๊ฐ (artifixer_run.prep_scene)
|
| 118 |
+
split = {"test": {sid: {"scene_id": sid, "transforms_path": "transforms.json", "image_root": ".",
|
| 119 |
+
"render_dir": "renders", "opacity_dir": "opacity",
|
| 120 |
+
"selected_indices_path": "selected_indices.json", "target_indices_path": "target_indices.json",
|
| 121 |
+
"prompt_path": "caption.h5", "metric_scale": 1.0,
|
| 122 |
+
"camera_scale": CAMERA_CONDITIONING_SCALE, "has_gt": False}}}
|
| 123 |
+
(out / "split.json").write_text(json.dumps(split, indent=1))
|
| 124 |
+
meta = {"scene": scene_name, "model": str(model_dir), "cam": cam, "variant": variant, "frames": n,
|
| 125 |
+
"size": [int(w), int(h)], "scene_id": sid}
|
| 126 |
+
(out / "prep.json").write_text(json.dumps(meta, indent=1))
|
| 127 |
+
log(f" {sid}: ์ฐธ์กฐ {n}์ฅ + ๊ณ ์น ํ๋ ์ {n}์ฅ ({w}ร{h}) โ {out}")
|
| 128 |
+
return meta
|
src/nusim/nusim/aug/artifixer_run.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ArtiFixer + ์ฐจ์ ํ์ฒ๋ฆฌ ์ฆ๊ฐ์ ์ฌ ๋จ์๋ก ๋๊น์ง (nusim aug artifixer) โ ์ค์ configs/aug/artifixer.json.
|
| 2 |
+
|
| 3 |
+
์ฌ ํ๋, ์นด๋ฉ๋ผ ํ๋๋ง๋ค:
|
| 4 |
+
1. prep ๊ตฌ๊ฐ ๋ชจ๋ธ(outputs/gs_day/<์ฌ>/seg<k>) ร ๋ฆฌ๊ทธ ๋ณํ(๋ ์จ ์๋ 9์ข
)๋ง๋ค ArtiFixer ์
๋ ฅ
|
| 5 |
+
(nusim.aug.artifixer_prep.prepare โ ๊ตฌ๊ฐ์ด '๋งก๋' ํ๋ ์๋ง, nusim.aug.map_aug.assign_front_frames)
|
| 6 |
+
โ outputs/artifixer/<์ฌ>_<CAM>/<๊ตฌ๊ฐ>_<๋ณํ>/ + ํฉ์น split.json
|
| 7 |
+
2. infer artifixer ํ๊ฒฝ์์ model_eval.run_inference ํ ๋ฒ (๋ชจ๋ธ ๋ก๋ ํ ๋ฒ์ ์
๋ ฅ ์ ๋ถ) โ <๊ฐ์ ๊ณณ>/run
|
| 8 |
+
3. collect ๊ณ ์น ํ๋ ์ ์ค ํคํ๋ ์ โ outputs/aug/aug_artfixer/<์ฌ>/<๋ณํ>/<CAM>/<์๋ณธ ํ์ผ๋ช
>.jpg + meta.json
|
| 9 |
+
4. lanefix ๋
ธ๋ฉด์ ์ค์ ์ฌ์ง์ ํ๋ฉด ํธ๋ชจ๊ทธ๋ํผ๋ก (nusim.aug.lane_fix, road ๋ชจ๋)
|
| 10 |
+
โ outputs/aug/aug_artfixer/<์ฌ>_lanefix/<๋ณํ>/<CAM>/ + meta.json
|
| 11 |
+
5. ์ค๊ฐ ํ์ผ ์ ๋ฆฌ (๋ ๋ ยท ๋ถํฌ๋ช
๋ ยท ์ถ๋ก ํ๋ ์, ์ฌ๋น ์ฝ 2.8 GB) โ keep_intermediate ๋ก ๋จ๊ธธ ์ ์๋ค
|
| 12 |
+
๋๋ ํ์๋ <์ฌ>_lanefix/done.json (์นด๋ฉ๋ผ๋ณ). ์์ผ๋ฉด ๊ฑด๋๋ฐ๋ฏ๋ก ๋์๋ค ๋ค์ ๋๋ ค๋ ๋๋ค.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import shutil
|
| 20 |
+
import subprocess
|
| 21 |
+
import time
|
| 22 |
+
from dataclasses import asdict
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
from nusim.config import configs_dir
|
| 26 |
+
from nusim.paths import output_dir, workspace_root
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def artifixer_config() -> dict:
|
| 30 |
+
return json.loads((configs_dir() / "aug" / "artifixer.json").read_text())
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _variants() -> list[dict]:
|
| 34 |
+
from nusim.aug.rig_aug import load_variants
|
| 35 |
+
|
| 36 |
+
return [v for v in load_variants(configs_dir() / "aug" / "rig_variants.json") if not v.get("weather")]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def done_cams(scene: str) -> set[str]:
|
| 40 |
+
p = output_dir() / "aug" / "aug_artfixer" / f"{scene}_lanefix" / "done.json"
|
| 41 |
+
return set(json.loads(p.read_text()).get("cams", {})) if p.exists() else set()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def finished_scenes(root: Path | None = None) -> list[str]:
|
| 45 |
+
"""ํ์ต ยท ๊ธฐ์กด ์ฆ๊ฐ(aug map)๊น์ง ๋๋ ์ฌ โ ๊ธฐ์กด ์ฆ๊ฐ์ด ๋๋ ์์(= ๋๋ก ์ปค๋ฒ๋ฆฌ์ง ์ฐ์ ํ์ต ์์)๋๋ก."""
|
| 46 |
+
n_var = len(_variants())
|
| 47 |
+
base = output_dir() / "aug" / "map"
|
| 48 |
+
out = []
|
| 49 |
+
for d in base.glob("scene-*"):
|
| 50 |
+
metas = list(d.glob("*/meta.json"))
|
| 51 |
+
if len(metas) >= n_var:
|
| 52 |
+
out.append((max(m.stat().st_mtime for m in metas), d.name))
|
| 53 |
+
return [s for _, s in sorted(out)]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def prep_scene(scene: str, cam: str, cfg: dict, root: Path, log=print) -> Path:
|
| 57 |
+
from nusim.aug.artifixer_prep import prepare
|
| 58 |
+
from nusim.aug.map_aug import assign_front_frames
|
| 59 |
+
from nusim.data.index import load_scene
|
| 60 |
+
from nusim.recon.batch import segment_dirs
|
| 61 |
+
|
| 62 |
+
base = output_dir() / "artifixer" / f"{scene}_{cam}"
|
| 63 |
+
segs = segment_dirs(root, scene)
|
| 64 |
+
if not segs:
|
| 65 |
+
raise RuntimeError(f"{scene}: ํ์ต๋ ๊ตฌ๊ฐ ๋ชจ๋ธ์ด ์์ ({root})")
|
| 66 |
+
sc = load_scene(scene)
|
| 67 |
+
owned = assign_front_frames(sc, [s for s, _ in segs])
|
| 68 |
+
cap = workspace_root() / cfg["caption_h5"]
|
| 69 |
+
split = {"test": {}}
|
| 70 |
+
for (info, d), fr in zip(segs, owned):
|
| 71 |
+
# ๊ตฌ๊ฐ์ด ๋งก๋ ์๊ฐ โ ์ ๋ฉด ์นด๋ฉ๋ผ ์๊ฐ ๊ธฐ์ค์ด๋ผ ๋ค๋ฅธ ์นด๋ฉ๋ผ๋ ๊ตฌ๊ฐ ๋ชจ๋ธ์ ํ์ต ๋ทฐ ์ ๋ถ
|
| 72 |
+
ts = {f.timestamp for f in fr} if cam == "CAM_FRONT" else None
|
| 73 |
+
# 8 GB: ์
๋ ฅ์ด ๊ธธ๋ฉด ArtiFixer ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ โ 60 m ๋ณด๋ค ์งง์ ํ ๊ตฌ๊ฐ์ผ๋ก ํ์ต๋ ์ฌ(์ฝ 230ํ๋ ์)์ด ๊ทธ๋ฌ๋ค.
|
| 74 |
+
# ์ฐ์ํ ์กฐ๊ฐ์ผ๋ก ๋๋ ๋ฐ๋ก ๋ฃ๋๋ค (์กฐ๊ฐ ๊ฒฝ๊ณ์์ ์๊ฐ ์ผ๊ด์ฑ์ ๋๊ธด๋ค)
|
| 75 |
+
chunks = [ts]
|
| 76 |
+
mx = cfg.get("max_frames")
|
| 77 |
+
if ts is not None and mx and len(ts) > mx:
|
| 78 |
+
order, n = sorted(ts), -(-len(ts) // mx)
|
| 79 |
+
chunks = [set(order[j * len(order) // n:(j + 1) * len(order) // n]) for j in range(n)]
|
| 80 |
+
for c, cts in enumerate(chunks):
|
| 81 |
+
tag = f"_c{c}" if len(chunks) > 1 else ""
|
| 82 |
+
for v in _variants():
|
| 83 |
+
out = base / f"{d.name}_{v['name']}{tag}"
|
| 84 |
+
meta = prepare(scene, d, v, out, cam=cam, width=cfg["width"], timestamps=cts, tag=tag, log=log)
|
| 85 |
+
shutil.copy(cap, out / "caption.h5")
|
| 86 |
+
ent = json.loads((out / "split.json").read_text())["test"][meta["scene_id"]]
|
| 87 |
+
for k in ("transforms_path", "image_root", "render_dir", "opacity_dir", "selected_indices_path",
|
| 88 |
+
"target_indices_path", "prompt_path"):
|
| 89 |
+
ent[k] = str((out / ent[k]).resolve())
|
| 90 |
+
split["test"][meta["scene_id"]] = {**ent, "prep_dir": str(out.resolve())}
|
| 91 |
+
(base / "split.json").write_text(json.dumps(split, indent=1))
|
| 92 |
+
log(f" prep: ์
๋ ฅ {len(split['test'])}๊ฐ โ {base / 'split.json'}")
|
| 93 |
+
return base
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def _collected(scene: str, cam: str, base: Path) -> dict[str, list] | None:
|
| 97 |
+
"""split.json ์ ๋ณํ๋ง๋ค ์์ง๋ ArtiFixer ๊ฒฐ๊ณผ(meta.json ์ ์ด ์นด๋ฉ๋ผ ํ๋ณธ) โ ํ๋๋ผ๋ ์์ผ๋ฉด None."""
|
| 98 |
+
sp = base / "split.json"
|
| 99 |
+
if not sp.exists():
|
| 100 |
+
return None
|
| 101 |
+
want = {json.loads((Path(e["prep_dir"]) / "prep.json").read_text())["variant"]["name"]
|
| 102 |
+
for e in json.loads(sp.read_text())["test"].values()}
|
| 103 |
+
root = output_dir() / "aug" / "aug_artfixer" / scene
|
| 104 |
+
out = {}
|
| 105 |
+
for var in want:
|
| 106 |
+
m = root / var / "meta.json"
|
| 107 |
+
smp = [x for x in json.loads(m.read_text())["samples"] if x["filename"].startswith(cam + "/")] if m.exists() else []
|
| 108 |
+
if not smp:
|
| 109 |
+
return None
|
| 110 |
+
out[var] = smp
|
| 111 |
+
return out
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _inferred(base: Path) -> bool:
|
| 115 |
+
"""split.json ์ ์
๋ ฅ๋ง๋ค ArtiFixer ๊ฒฐ๊ณผ(pred)๊ฐ ์๋ โ ์ง๊ธ ์ค์ (max_frames)์ผ๋ก ๋ง๋ split ์ด์ด์ผ ํ๋ค."""
|
| 116 |
+
sp = base / "split.json"
|
| 117 |
+
if not sp.exists():
|
| 118 |
+
return False
|
| 119 |
+
sids = json.loads(sp.read_text())["test"]
|
| 120 |
+
return bool(sids) and all(next((base / "run").glob(f"*/*/{sid}/frames/batch_0000/pred"), None) for sid in sids)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def infer(base: Path, cfg: dict, log=print) -> Path:
|
| 124 |
+
"""artifixer ํ๊ฒฝ ํ์ ํ๋ก์ธ์ค โ vkgs ํ๊ฒฝ๊ณผ torch ๋ฒ์ ์ด ๋ฌ๋ผ ๊ฐ์ ํ๋ก์ธ์ค์์ ๋ชป ๋๋ฆฐ๋ค."""
|
| 125 |
+
repo = workspace_root() / cfg["repo"]
|
| 126 |
+
run = base / "run"
|
| 127 |
+
env = {**os.environ, "PYTHONPATH": str(repo / "thirdparty" / "3DGRUT-ArtiFixer"),
|
| 128 |
+
"PYTORCH_CUDA_ALLOC_CONF": "expandable_segments:True"}
|
| 129 |
+
cmd = [os.path.expanduser(cfg["python"]), "-m", "model_eval.run_inference", "--evalset", "reconstructed_colmap",
|
| 130 |
+
"--checkpoint_pt", str(workspace_root() / cfg["checkpoint"]), "--model_id", cfg["model_id"],
|
| 131 |
+
"--save_dir", str(run), "--split_path", str(base / "split.json"), *cfg["infer_args"]]
|
| 132 |
+
t = time.time()
|
| 133 |
+
with open(base / "run.log", "w") as f:
|
| 134 |
+
r = subprocess.run(cmd, cwd=repo, env=env, stdout=f, stderr=subprocess.STDOUT)
|
| 135 |
+
if r.returncode:
|
| 136 |
+
raise RuntimeError(f"ArtiFixer ์ถ๋ก ์คํจ (์ฝ๋ {r.returncode}) โ {base / 'run.log'}")
|
| 137 |
+
log(f" infer: {(time.time() - t) / 60:.1f}๋ถ โ {run}")
|
| 138 |
+
return run
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def collect(scene: str, cam: str, base: Path, log=print) -> dict[str, list]:
|
| 142 |
+
from PIL import Image
|
| 143 |
+
|
| 144 |
+
split = json.loads((base / "split.json").read_text())["test"]
|
| 145 |
+
dst_root = output_dir() / "aug" / "aug_artfixer" / scene
|
| 146 |
+
per_var: dict[str, list] = {}
|
| 147 |
+
size = None
|
| 148 |
+
for sid, ent in split.items():
|
| 149 |
+
prep_dir = Path(ent["prep_dir"])
|
| 150 |
+
pm = json.loads((prep_dir / "prep.json").read_text())
|
| 151 |
+
tr = json.loads((prep_dir / "transforms.json").read_text())
|
| 152 |
+
pred = next((base / "run").glob(f"*/*/{sid}/frames/batch_0000/pred"), None)
|
| 153 |
+
if pred is None:
|
| 154 |
+
raise RuntimeError(f"{sid}: ArtiFixer ๊ฒฐ๊ณผ ์์ โ ์ถ๋ก ์ด ์ค๊ฐ์ ๋๊ฒผ๋ค")
|
| 155 |
+
var = pm["variant"]["name"]
|
| 156 |
+
out = dst_root / var / cam
|
| 157 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 158 |
+
size = Image.open(prep_dir / "renders/00000.png").size # ArtiFixer ๋ 16 ๋ฐฐ์๋ก ๋ฐ๊ฟ ๋ด๋ฏ๋ก ๋ ๋ ํฌ๊ธฐ๋ก ๋๋๋ฆฐ๋ค
|
| 159 |
+
for i, f in enumerate(tr["frames"]):
|
| 160 |
+
if f["role"] != "target" or not f["original_filename"].startswith("samples/"): # ํคํ๋ ์๋ง
|
| 161 |
+
continue
|
| 162 |
+
src = pred / f"{i:05d}.png"
|
| 163 |
+
if not src.exists():
|
| 164 |
+
continue
|
| 165 |
+
name = Path(f["original_filename"]).name
|
| 166 |
+
Image.open(src).convert("RGB").resize(size, Image.BICUBIC).save(out / name, quality=95)
|
| 167 |
+
per_var.setdefault(var, []).append({"segment": Path(pm["model"]).name, "original_filename": f["original_filename"],
|
| 168 |
+
"filename": f"{cam}/{name}", "timestamp": f["timestamp"]})
|
| 169 |
+
for var, samples in per_var.items():
|
| 170 |
+
p = dst_root / var / "meta.json"
|
| 171 |
+
old = json.loads(p.read_text()) if p.exists() else {}
|
| 172 |
+
keep = [s for s in old.get("samples", []) if not s["filename"].startswith(cam + "/")]
|
| 173 |
+
(dst_root / var / "meta.json").write_text(json.dumps({
|
| 174 |
+
"scene": scene, "variant": var, "cams": sorted(set(old.get("cams", [])) | {cam}),
|
| 175 |
+
"method": "ArtiFixer 1.3B (nv-tlabs/artifixer) on VKGS-Sim 3DGS renders",
|
| 176 |
+
"size": list(size), "keyframes_only": True,
|
| 177 |
+
"samples": sorted(keep + samples, key=lambda s: (s["filename"].split("/")[0], s["timestamp"]))}, indent=1))
|
| 178 |
+
log(f" collect: {sum(map(len, per_var.values()))}์ฅ โ {dst_root}")
|
| 179 |
+
return per_var
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def lanefix(scene: str, cam: str, base: Path, log=print) -> int:
|
| 183 |
+
from scipy.spatial import cKDTree
|
| 184 |
+
|
| 185 |
+
from nusim.aug.lane_fix import LaneFixConfig, Segmenter, fix_prep_dir, map_points
|
| 186 |
+
from nusim.data.index import load_scene
|
| 187 |
+
|
| 188 |
+
lcfg = LaneFixConfig()
|
| 189 |
+
sc = load_scene(scene)
|
| 190 |
+
tree = cKDTree(map_points(sc, lcfg))
|
| 191 |
+
seg = Segmenter()
|
| 192 |
+
src_root = output_dir() / "aug" / "aug_artfixer" / scene
|
| 193 |
+
dst_root = output_dir() / "aug" / "aug_artfixer" / f"{scene}_lanefix"
|
| 194 |
+
n = 0
|
| 195 |
+
preps = sorted({Path(e["prep_dir"]) for e in json.loads((base / "split.json").read_text())["test"].values()})
|
| 196 |
+
for prep in preps: # ๊ตฌ๊ฐ ์๋ ์ฌ์ scene-XXXX_<๋ณํ> โ seg* ๋ง ๋ณด๋ฉด 0์ฅ์ด ๋๋ค
|
| 197 |
+
var = json.loads((prep / "prep.json").read_text())["variant"]["name"]
|
| 198 |
+
n += fix_prep_dir(prep, src_root / var, dst_root / var, seg, sc, tree, lcfg)
|
| 199 |
+
for var_dir in src_root.iterdir():
|
| 200 |
+
if not (var_dir / "meta.json").exists() or not (dst_root / var_dir.name).exists():
|
| 201 |
+
continue
|
| 202 |
+
m = json.loads((var_dir / "meta.json").read_text())
|
| 203 |
+
m["method"] += " + ๋
ธ๋ฉด ํ๋ฉด ํธ๋ชจ๊ทธ๋ํผ ์ฐจ์ ํ์ฒ๋ฆฌ (nusim.aug.lane_fix)"
|
| 204 |
+
m["lanefix"] = asdict(lcfg)
|
| 205 |
+
(dst_root / var_dir.name / "meta.json").write_text(json.dumps(m, indent=1, ensure_ascii=False))
|
| 206 |
+
del seg
|
| 207 |
+
import torch
|
| 208 |
+
|
| 209 |
+
torch.cuda.empty_cache()
|
| 210 |
+
log(f" lanefix: {n}์ฅ โ {dst_root}")
|
| 211 |
+
return n
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def cleanup(base: Path) -> None:
|
| 215 |
+
"""๋ฌด๊ฑฐ์ด ์ค๊ฐ ํ์ผ๋ง ์ง์ด๋ค โ prep.json ยท transforms.json ยท split.json ์ ๋จ๊ฒจ ๋ค์ ํ์ฒ๋ฆฌํ ์ ์๊ฒ."""
|
| 216 |
+
shutil.rmtree(base / "run", ignore_errors=True)
|
| 217 |
+
for prep in (d for d in base.iterdir() if d.is_dir() and d.name != "run"): # ๊ตฌ๊ฐ ์๋ ์ฌ์ scene-XXXX_<๋ณํ>
|
| 218 |
+
for d in ("images", "renders", "opacity"):
|
| 219 |
+
shutil.rmtree(prep / d, ignore_errors=True)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def aug_scene(scene: str, cfg: dict | None = None, root: Path | None = None, cams=None, force: bool = False,
|
| 223 |
+
log=print) -> dict:
|
| 224 |
+
cfg = cfg or artifixer_config()
|
| 225 |
+
root = root or output_dir() / "gs_day"
|
| 226 |
+
res = {}
|
| 227 |
+
for cam in cams or cfg["cams"]:
|
| 228 |
+
if cam in done_cams(scene) and not force:
|
| 229 |
+
log(f" {cam}: ์ด๋ฏธ ๋๋จ โ ๊ฑด๋๋")
|
| 230 |
+
continue
|
| 231 |
+
t = time.time()
|
| 232 |
+
base = output_dir() / "artifixer" / f"{scene}_{cam}"
|
| 233 |
+
per_var = None if force else _collected(scene, cam, base)
|
| 234 |
+
if per_var: # ์ถ๋ก ยท ์์ง๊น์ง ๋๋๊ณ ์ฐจ์ ํ์ฒ๋ฆฌ์์ ์คํจํ ์ฌ โ 20๋ถ์ง๋ฆฌ ์ถ๋ก ์ ๋ค์ ๋๋ฆฌ์ง ์๋๋ค
|
| 235 |
+
log(f" {cam}: ArtiFixer ๊ฒฐ๊ณผ {sum(map(len, per_var.values()))}์ฅ ์์ โ ์ฐจ์ ํ์ฒ๋ฆฌ๋ถํฐ")
|
| 236 |
+
elif not force and _inferred(base):
|
| 237 |
+
log(f" {cam}: ์ถ๋ก ๊ฒฐ๊ณผ ์์ โ ์์ง๋ถํฐ")
|
| 238 |
+
else:
|
| 239 |
+
base = prep_scene(scene, cam, cfg, root, log=log)
|
| 240 |
+
import torch
|
| 241 |
+
|
| 242 |
+
torch.cuda.empty_cache() # ArtiFixer ๊ฐ 8 GB ๊ฑฐ์ ์ ๋ถ๋ฅผ ์ด๋ค
|
| 243 |
+
infer(base, cfg, log=log)
|
| 244 |
+
if not per_var:
|
| 245 |
+
per_var = collect(scene, cam, base, log=log)
|
| 246 |
+
n = lanefix(scene, cam, base, log=log)
|
| 247 |
+
if not cfg.get("keep_intermediate"):
|
| 248 |
+
cleanup(base)
|
| 249 |
+
p = output_dir() / "aug" / "aug_artfixer" / f"{scene}_lanefix" / "done.json"
|
| 250 |
+
p.parent.mkdir(parents=True, exist_ok=True)
|
| 251 |
+
d = json.loads(p.read_text()) if p.exists() else {"scene": scene, "cams": {}}
|
| 252 |
+
d["cams"][cam] = {"images": n, "variants": sorted(per_var), "minutes": round((time.time() - t) / 60, 1),
|
| 253 |
+
"finished": time.strftime("%Y-%m-%d %H:%M")}
|
| 254 |
+
p.write_text(json.dumps(d, indent=1))
|
| 255 |
+
res[cam] = d["cams"][cam]
|
| 256 |
+
log(f" {cam}: ์๋ฃ {n}์ฅ, {d['cams'][cam]['minutes']}๋ถ")
|
| 257 |
+
return res
|
src/nusim/nusim/aug/lane_check.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์ฆ๊ฐ ์์์ ์ฐจ์ ๋ณด์ ๊ฒ์ฌ (nusim aug lanecheck) โ ์ฐจ์ ๊ฒ์ถ๊ธฐ ์์ด, nuScenes ๋งต์ ์ฐจ์ ์ผ๋ก ์ฐ๋ค.
|
| 2 |
+
|
| 3 |
+
๋งต์ lane_divider ยท road_divider ํด๋ฆฌ๋ผ์ธ์ ์ง๋ฉด(์์ฐจ ๋์ด)์ ๋๊ณ ์นด๋ฉ๋ผ์ ํฌ์ํ ๋ค, ํ๋ณธ์ ๋ง๋ค ์ ์ ์์ง์ธ
|
| 4 |
+
๋ฐ๊ธฐ ๋จ๋ฉด์ ๋ณธ๋ค. ๋จ๋ฉด ๊ฐ์ด๋ฐ ์ฐฝ(ยฑsearch_m, ๋งต ยท ํฌ์ฆ ์ค์ฐจ ๋ชซ) ์์ ์ฃผ๋ณ(์ค์๊ฐ)๋ณด๋ค contrast ์ด์ ๋ฐ์ ๋ฅ์ ์ด ์์ผ๋ฉด
|
| 5 |
+
'๋ณด์'. ์ค์ ์ฌ์ง ยท ์๋ ์์น ๋ ๋ ยท ๋ณํ ๋ ๋๋ฅผ ๊ฐ์ ํ๋ณธ์ ์ผ๋ก ์ฌ๋ฏ๋ก, ์ฐจ์ด๊ฐ ๊ณง ์ฌ๊ตฌ์ฑ ยท ์์ ์ด๋์ ์์ค์ด๋ค.
|
| 6 |
+
๋ฅ์ ์ด ๋ ์ด์(์ฌ์ด์ ๊ณจ)์ด๋ฉด '์ด์ค์ ' โ ์์ ์ด๋์์ ๋
ธ๋ฉด ๊ธฐํ๊ฐ ํ๋ ค ์ฐจ์ ์ด ๊ฐ๋ผ์ง ๊ฒ.
|
| 7 |
+
|
| 8 |
+
ํ๋ณธ์ : ์นด๋ฉ๋ผ ์ near_m~far_m, 0.5 m ๊ฐ๊ฒฉ, ์์ ์. ์ฌ์ง์์ ์ฐจ๋์ด ๊ฐ๋ฆฐ ์ ์ ์ฌ์ง์์๋ ์ ๋ณด์ด๋ฏ๋ก
|
| 9 |
+
๋ณด์ ๋น์จ์ ์ฌ์ง ๋๋น๋ก ์ฝ๋๋ค (recall_vs_photo = ๋ ๋๋ ๋ณด์ด๊ณ ์ฌ์ง๋ ๋ณด์ธ ์ / ์ฌ์ง์์ ๋ณด์ธ ์ ).
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import json
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class LaneCheckConfig:
|
| 23 |
+
near_m: float = 5.0
|
| 24 |
+
far_m: float = 25.0
|
| 25 |
+
step_m: float = 0.5
|
| 26 |
+
profile_m: float = 1.2 # ๋จ๋ฉด ๋ฐํญ (์ง๋ฉด m)
|
| 27 |
+
search_m: float = 0.35 # ๋ฅ์ ์ ์ฐพ๋ ๊ฐ์ด๋ฐ ์ฐฝ ๋ฐํญ
|
| 28 |
+
contrast: float = 0.06 # ๋ฅ์ โ ์ฃผ๋ณ ์ค์๊ฐ (0~1 ๋ฐ๊ธฐ)
|
| 29 |
+
cams: tuple = ("CAM_FRONT",)
|
| 30 |
+
layers: tuple = ("lane_divider", "road_divider")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _gray(path: Path) -> np.ndarray:
|
| 34 |
+
from PIL import Image
|
| 35 |
+
|
| 36 |
+
return np.asarray(Image.open(path).convert("L"), np.float32) / 255
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def map_lines(scene, margin: float = 40.0) -> list[np.ndarray]:
|
| 40 |
+
from nusim.maps.expansion import BBox, MapExpansion
|
| 41 |
+
from nusim.paths import dataset
|
| 42 |
+
|
| 43 |
+
ego = np.array([f.ego_translation for f in scene.frames["CAM_FRONT"]])
|
| 44 |
+
box = BBox(ego[:, 0].min() - margin, ego[:, 1].min() - margin, ego[:, 0].max() + margin, ego[:, 1].max() + margin)
|
| 45 |
+
feats = MapExpansion(scene.location, dataset()).features(box)
|
| 46 |
+
out = []
|
| 47 |
+
for layer in LaneCheckConfig.layers:
|
| 48 |
+
for pl in feats.get(layer, []):
|
| 49 |
+
pl = np.asarray(pl, np.float64)[:, :2]
|
| 50 |
+
d = np.r_[0, np.cumsum(np.linalg.norm(np.diff(pl, axis=0), axis=1))]
|
| 51 |
+
t = np.arange(0, d[-1], 0.5)
|
| 52 |
+
if len(t) >= 2:
|
| 53 |
+
out.append(np.c_[np.interp(t, d, pl[:, 0]), np.interp(t, d, pl[:, 1])])
|
| 54 |
+
return out
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def sample_points(pose, lines: list[np.ndarray], ground_z: float, cfg: LaneCheckConfig):
|
| 58 |
+
"""(u, v, ๋จ๋ฉด ๋ฐฉํฅ (du, dv) ํฝ์
/m) โ ์ง๋ฉด ์ ์ฐจ์ ํ๋ณธ์ ์ค ๋ณด์ด๋ ๊ฒ."""
|
| 59 |
+
w2c, K = pose.w2c, pose.K
|
| 60 |
+
out = []
|
| 61 |
+
for pl in lines:
|
| 62 |
+
tang = np.gradient(pl, axis=0)
|
| 63 |
+
tang /= np.linalg.norm(tang, axis=1, keepdims=True) + 1e-9
|
| 64 |
+
nrm = np.c_[-tang[:, 1], tang[:, 0]]
|
| 65 |
+
P = np.c_[pl, np.full(len(pl), ground_z), np.ones(len(pl))]
|
| 66 |
+
Pn = np.c_[pl + 0.1 * nrm, np.full(len(pl), ground_z), np.ones(len(pl))]
|
| 67 |
+
c, cn = (w2c @ P.T).T, (w2c @ Pn.T).T
|
| 68 |
+
ok = (c[:, 2] > cfg.near_m) & (c[:, 2] < cfg.far_m)
|
| 69 |
+
for i in np.nonzero(ok)[0]:
|
| 70 |
+
uv = K @ c[i, :3]
|
| 71 |
+
uvn = K @ cn[i, :3]
|
| 72 |
+
u, v = uv[:2] / uv[2]
|
| 73 |
+
un, vn = uvn[:2] / uvn[2]
|
| 74 |
+
if 2 <= u < pose.width - 2 and 2 <= v < pose.height - 2:
|
| 75 |
+
out.append((u, v, (un - u) / 0.1, (vn - v) / 0.1))
|
| 76 |
+
return out
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def ridge(img: np.ndarray, u, v, du, dv, cfg: LaneCheckConfig) -> tuple[bool, int]:
|
| 80 |
+
"""(๊ฐ์ด๋ฐ ์ฐฝ์ ๋ฅ์ ์ด ์๋, ์ฐฝ ์ ๋ฅ์ ์)."""
|
| 81 |
+
s = np.linspace(-cfg.profile_m, cfg.profile_m, 49)
|
| 82 |
+
x, y = u + s * du, v + s * dv
|
| 83 |
+
h, w = img.shape
|
| 84 |
+
keep = (x >= 0) & (x < w - 1) & (y >= 0) & (y < h - 1)
|
| 85 |
+
if keep.sum() < 30:
|
| 86 |
+
return False, 0
|
| 87 |
+
xi, yi = np.clip(x, 0, w - 1).astype(int), np.clip(y, 0, h - 1).astype(int)
|
| 88 |
+
prof = img[yi, xi]
|
| 89 |
+
base = np.median(prof[keep])
|
| 90 |
+
mid = keep & (np.abs(s) <= cfg.search_m)
|
| 91 |
+
lift = prof - base
|
| 92 |
+
hit = mid & (lift > cfg.contrast)
|
| 93 |
+
if not hit.any():
|
| 94 |
+
return False, 0
|
| 95 |
+
runs = np.diff(np.r_[0, hit.astype(int), 0])
|
| 96 |
+
return True, int((runs == 1).sum())
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def check_scene(scene_name: str, sources: dict[str, Path], variants: dict[str, dict], cfg: LaneCheckConfig | None = None,
|
| 100 |
+
keyframes: bool = True) -> dict:
|
| 101 |
+
"""sources: ์ด๋ฆ โ ์์ ๋๋ ํฐ๋ฆฌ (<dir>/<CAM>/<์๋ณธ ํ์ผ๋ช
>), 'photo' ๋ ๋ฐ์ดํฐ์
์๋ณธ. variants: ์ด๋ฆ โ ๋ฆฌ๊ทธ ๋ณํ
|
| 102 |
+
(๊ทธ ์์ค์ ์นด๋ฉ๋ผ ํฌ์ฆ). ๊ฒฐ๊ณผ: ์์ค๋ง๋ค ํ๋ณธ์ ์ ยท ๋ณด์ ๋น์จ ยท ์ฌ์ง ๋๋น ์ฌํ์จ ยท ์ด์ค์ ๋น์จ."""
|
| 103 |
+
from nusim.aug.rig_aug import transform_calib
|
| 104 |
+
from nusim.data.index import load_scene
|
| 105 |
+
from nusim.data.rig import camera_pose
|
| 106 |
+
from nusim.paths import dataset
|
| 107 |
+
|
| 108 |
+
cfg = cfg or LaneCheckConfig()
|
| 109 |
+
scene = load_scene(scene_name)
|
| 110 |
+
ds = dataset()
|
| 111 |
+
lines = map_lines(scene)
|
| 112 |
+
res = {}
|
| 113 |
+
for name, d in sources.items():
|
| 114 |
+
var = variants.get(name, {})
|
| 115 |
+
st = {"points": 0, "visible": 0, "double": 0, "photo_visible": 0, "both": 0}
|
| 116 |
+
for cam in cfg.cams:
|
| 117 |
+
calib = transform_calib(scene.calib[cam], var.get("shift_m", (0, 0, 0)), var.get("rot_deg", (0, 0, 0)))
|
| 118 |
+
calib0 = scene.calib[cam]
|
| 119 |
+
for fr in scene.frames[cam]:
|
| 120 |
+
if keyframes and not fr.is_keyframe:
|
| 121 |
+
continue
|
| 122 |
+
p = (ds.root / fr.filename) if d is None else d / cam / Path(fr.filename).name
|
| 123 |
+
if not p.exists():
|
| 124 |
+
continue
|
| 125 |
+
img = _gray(p)
|
| 126 |
+
pose = camera_pose(fr, calib, cam)
|
| 127 |
+
gz = fr.ego_translation[2]
|
| 128 |
+
pts = sample_points(pose, lines, gz, cfg)
|
| 129 |
+
photo = None
|
| 130 |
+
if d is not None and not var: # ๊ฐ์ ํฌ์ฆ์ ์ฌ์ง๊ณผ ์ ๋ง๋ค ์ง์ง์ด ์ฌํ์จ
|
| 131 |
+
photo = _gray(ds.root / fr.filename)
|
| 132 |
+
for u, v, du, dv in pts:
|
| 133 |
+
vis, n = ridge(img, u, v, du, dv, cfg)
|
| 134 |
+
st["points"] += 1
|
| 135 |
+
st["visible"] += vis
|
| 136 |
+
st["double"] += n >= 2
|
| 137 |
+
if photo is not None:
|
| 138 |
+
pv, _ = ridge(photo, u, v, du, dv, cfg)
|
| 139 |
+
st["photo_visible"] += pv
|
| 140 |
+
st["both"] += pv and vis
|
| 141 |
+
n = max(st["points"], 1)
|
| 142 |
+
res[name] = {**st, "visible_rate": st["visible"] / n, "double_rate": st["double"] / max(st["visible"], 1)}
|
| 143 |
+
if st["photo_visible"]:
|
| 144 |
+
res[name]["recall_vs_photo"] = st["both"] / st["photo_visible"]
|
| 145 |
+
return res
|
src/nusim/nusim/aug/lane_fix.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์ฐจ์ ํ์ฒ๋ฆฌ โ ArtiFixer ์ฆ๊ฐ ์์์ ๋๊ธฐ๊ณ ํ๋ฆฐ ์ฐจ์ ์ ์ค์ ์ฌ์ง์ ์ฐจ์ ์ผ๋ก ๋ฐ๊พผ๋ค (scripts/artifixer_lanefix.py).
|
| 2 |
+
|
| 3 |
+
์์ ๋ณํ์ ๊ฐ์ ์๊ฐ ยท ๊ฐ์ ์์ฐจ์์ ์นด๋ฉ๋ผ๋ง ์ฎ๊ธด ๊ฒ์ด๋ค. ๋
ธ๋ฉด์ ํ๋ฉด(์์ฐจ ์ขํ z = 0)์ผ๋ก ๋ณด๋ฉด, ๋ณํ ์นด๋ฉ๋ผ์ ํฝ์
๋ง๋ค
|
| 4 |
+
๊ด์ โ ๋
ธ๋ฉด ๊ต์ โ ์๋ ์นด๋ฉ๋ผ๋ก ํฌ์ํด **์ค์ ์ฌ์ง**(1600ร900)์ ๊ทธ ์๋ฆฌ์ ๊ฐ์ ธ์ฌ ์ ์๋ค (ํ๋ฉด ํธ๋ชจ๊ทธ๋ํผ โ ํํํ ๋
ธ๋ฉด์ด๋ฉด
|
| 5 |
+
๊ธฐํ๊ฐ ์ ํํ๋ค). ๊ทธ์ค nuScenes HD ๋งต์ lane_divider ยท road_divider ยท ped_crossing ยท stop_line ์ฃผ๋ณ(ยฑband_m)๋ง ๋ฐ๊พผ๋ค:
|
| 6 |
+
๋งต์ '์ฐจ์ ์ด ์ด๋ ์๋' ๋ง ์ฃผ๊ณ , ์ฐจ์ ์ ๋ชจ์(์ ์ ๊ฐ๊ฒฉ ยท ํญ ยท ์)์ ์ค์ ์ฌ์ง์์ ์จ๋ค (๋งต์๋ ์ ์ ๊ฐ๊ฒฉ ยท ํญ์ด ์๊ณ
|
| 7 |
+
์์๊ณผ 10~30 cm ์ด๊ธ๋์, ๋งต ์ ์ ์ง์ ๊ทธ๋ฆฌ๋ฉด ์ค์ ์ ์ ๋ง๋๋ค).
|
| 8 |
+
|
| 9 |
+
๋นผ๋ ๊ณณ: ๋
ธ๋ฉด ๊ต์ ์ด max_dist_m ๋ณด๋ค ๋จผ ๊ณณ(์์ ๊ฐ๋ ์ค์ฐจ๊ฐ ํฌ๊ฒ ๋ฒ์ง๋ค), ์๋ ์นด๋ฉ๋ผ ์์ ๋ฐ, ์ค์ ์ฌ์ง์์ ์ฐจ๋ ยท ์ฌ๋์ด
|
| 10 |
+
๊ฐ๋ฆฐ ๊ณณ (SegFormer-B5 Cityscapes โ nusim.recon.sky ์ ๊ฐ์ ๋ชจ๋ธ), ์ค์ ์ฌ์ง์์ ๋
ธ๋ฉด ยท ๋ณด๋๊ฐ ์๋ ๊ณณ.
|
| 11 |
+
ํฉ์ฑ: ๋ ์ ๋
ธ๋ฉด(์ฐจ์ ์ ์ธ)์ ํ๊ท ยท ํ์คํธ์ฐจ๋ก ์ค์ ์ฌ์ง ์์ ArtiFixer ์์ ์ชฝ์ผ๋ก ๋ง์ถ๊ณ , ๊ฐ์ฅ์๋ฆฌ๋ฅผ ํ๋ ค ์๋๋ค.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
|
| 22 |
+
OPENGL_TO_OPENCV = np.diag([1.0, -1.0, -1.0, 1.0])
|
| 23 |
+
MOVERS = ("car", "truck", "bus", "train", "motorcycle", "bicycle", "person", "rider")
|
| 24 |
+
GROUND = ("road", "sidewalk")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class LaneFixConfig:
|
| 29 |
+
band_m: float = 0.6 # ๋งต ์ ๋๋ ๋ฐ๊ฟ ๋ ๋ฐํญ
|
| 30 |
+
max_dist_m: float = 35.0 # ์ด๋ณด๋ค ๋จผ ๋
ธ๋ฉด์ ๊ทธ๋๋ก (ํธ๋ชจ๊ทธ๋ํผ ์ค์ฐจ๊ฐ ์ปค์ง๋ค)
|
| 31 |
+
feather_px: float = 12.0 # ํฉ์ฑ ๊ฐ์ฅ์๋ฆฌ ์๋ ํญ (์์ญ ์์ชฝ์ผ๋ก, ์ถ๋ ฅ ํด์๋ ํฝ์
)
|
| 32 |
+
contrast: float = 0.75 # ์ค์ ์ฌ์ง ๋
ธ๋ฉด ๋๋น๋ฅผ ArtiFixer ์ชฝ์ผ๋ก ๋ง์ถ๋ ์ ๋ (1 = ์์ ํ)
|
| 33 |
+
mover_dilate_px: int = 9 # ์ค์ ์ฌ์ง ์ฐจ๋ ๋ง์คํฌ ํฝ์ฐฝ (์๋ณธ ํด์๋)
|
| 34 |
+
layers: tuple = ("lane_divider", "road_divider", "ped_crossing", "stop_line")
|
| 35 |
+
mode: str = "road" # road = ๋
ธ๋ฉด ์ ์ฒด๋ฅผ ์ค์ ์ฌ์ง์ผ๋ก (๊ถ์ฅ) ยท band = ๋งต ์ฐจ์ ๋ ๋ง
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def map_points(scene, cfg: LaneFixConfig, step: float = 0.1, margin: float = 60.0) -> np.ndarray:
|
| 39 |
+
"""๋งต ์ฐจ์ ์์๋ฅผ ์ ์ญ xy ์ ๋ค๋ก (์ ์ 0.1 m ๊ฐ๊ฒฉ, ๋ค๊ฐํ์ ํ
๋๋ฆฌ + ๋ด๋ถ ๊ฒฉ์)."""
|
| 40 |
+
from matplotlib.path import Path as MPath
|
| 41 |
+
|
| 42 |
+
from nusim.maps.expansion import BBox, MapExpansion
|
| 43 |
+
from nusim.paths import dataset
|
| 44 |
+
|
| 45 |
+
ego = np.array([f.ego_translation for f in scene.frames["CAM_FRONT"]])
|
| 46 |
+
box = BBox(ego[:, 0].min() - margin, ego[:, 1].min() - margin, ego[:, 0].max() + margin, ego[:, 1].max() + margin)
|
| 47 |
+
feats = MapExpansion(scene.location, dataset()).features(box)
|
| 48 |
+
pts = []
|
| 49 |
+
for layer in cfg.layers:
|
| 50 |
+
for pl in feats.get(layer, []):
|
| 51 |
+
pl = np.asarray(pl, np.float64)[:, :2]
|
| 52 |
+
d = np.r_[0, np.cumsum(np.linalg.norm(np.diff(pl, axis=0), axis=1))]
|
| 53 |
+
if d[-1] <= 0:
|
| 54 |
+
continue
|
| 55 |
+
t = np.arange(0, d[-1], step)
|
| 56 |
+
pts.append(np.c_[np.interp(t, d, pl[:, 0]), np.interp(t, d, pl[:, 1])])
|
| 57 |
+
if layer in ("ped_crossing", "stop_line") and len(pl) >= 3: # ๋ค๊ฐํ ๋ด๋ถ๋ (ํก๋จ๋ณด๋ ์ค๋ฌด๋ฌ)
|
| 58 |
+
lo, hi = pl.min(0), pl.max(0)
|
| 59 |
+
gx, gy = np.meshgrid(np.arange(lo[0], hi[0], 0.3), np.arange(lo[1], hi[1], 0.3))
|
| 60 |
+
g = np.c_[gx.ravel(), gy.ravel()]
|
| 61 |
+
pts.append(g[MPath(pl).contains_points(g)])
|
| 62 |
+
return np.concatenate(pts) if pts else np.zeros((0, 2))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class Segmenter:
|
| 66 |
+
def __init__(self):
|
| 67 |
+
import torch
|
| 68 |
+
from transformers import SegformerForSemanticSegmentation
|
| 69 |
+
|
| 70 |
+
from nusim.recon.sky import MODEL
|
| 71 |
+
|
| 72 |
+
self.torch = torch
|
| 73 |
+
self.model = SegformerForSemanticSegmentation.from_pretrained(MODEL).half().cuda().eval()
|
| 74 |
+
ids = {v.lower(): int(k) for k, v in self.model.config.id2label.items()}
|
| 75 |
+
self.movers = [ids[k] for k in MOVERS if k in ids]
|
| 76 |
+
self.ground = [ids[k] for k in GROUND if k in ids]
|
| 77 |
+
self.mean = torch.tensor([0.485, 0.456, 0.406], device="cuda")[:, None, None]
|
| 78 |
+
self.std = torch.tensor([0.229, 0.224, 0.225], device="cuda")[:, None, None]
|
| 79 |
+
|
| 80 |
+
def __call__(self, im: np.ndarray) -> np.ndarray:
|
| 81 |
+
"""(H,W,3) uint8 โ ํด๋์ค ๋ฒํธ (H,W)."""
|
| 82 |
+
import torch.nn.functional as F
|
| 83 |
+
|
| 84 |
+
t = self.torch.as_tensor(im, device="cuda").permute(2, 0, 1).float() / 255
|
| 85 |
+
h, w = im.shape[:2]
|
| 86 |
+
t = F.interpolate(t[None], size=(576, 1024), mode="bilinear", align_corners=False)
|
| 87 |
+
with self.torch.no_grad():
|
| 88 |
+
lg = self.model(pixel_values=((t - self.mean) / self.std).half()).logits
|
| 89 |
+
lg = F.interpolate(lg.float(), size=(h, w), mode="bilinear", align_corners=False)[0]
|
| 90 |
+
return lg.argmax(0).cpu().numpy()
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def ground_lookup(c2w_v: np.ndarray, Kv: np.ndarray, size_v, plane_n: np.ndarray, plane_p: np.ndarray):
|
| 94 |
+
"""๋ณํ ์นด๋ฉ๋ผ ํฝ์
๋ง๋ค ๋
ธ๋ฉด ๊ต์ (H,W,3) ๊ณผ ๊ฑฐ๋ฆฌ (H,W). ๋
ธ๋ฉด์ ์ ๋ฟ์ผ๋ฉด nan."""
|
| 95 |
+
w, h = size_v
|
| 96 |
+
u, v = np.meshgrid(np.arange(w) + 0.5, np.arange(h) + 0.5)
|
| 97 |
+
d_cam = np.stack([(u - Kv[0, 2]) / Kv[0, 0], (v - Kv[1, 2]) / Kv[1, 1], np.ones_like(u)], -1)
|
| 98 |
+
R, o = c2w_v[:3, :3], c2w_v[:3, 3]
|
| 99 |
+
d = d_cam @ R.T
|
| 100 |
+
denom = d @ plane_n
|
| 101 |
+
t = ((plane_p - o) @ plane_n) / np.where(np.abs(denom) < 1e-9, np.nan, denom)
|
| 102 |
+
t = np.where(t > 0, t, np.nan)
|
| 103 |
+
X = o + d * t[..., None]
|
| 104 |
+
dist = t * np.linalg.norm(d_cam, axis=-1)
|
| 105 |
+
return X, dist
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def project(X: np.ndarray, c2w: np.ndarray, K: np.ndarray):
|
| 109 |
+
w2c = np.linalg.inv(c2w)
|
| 110 |
+
Xc = X @ w2c[:3, :3].T + w2c[:3, 3]
|
| 111 |
+
z = Xc[..., 2]
|
| 112 |
+
uv = (Xc[..., :2] / z[..., None]) * np.array([K[0, 0], K[1, 1]]) + np.array([K[0, 2], K[1, 2]])
|
| 113 |
+
return uv, z
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def bilinear(img: np.ndarray, uv: np.ndarray) -> np.ndarray:
|
| 117 |
+
import cv2
|
| 118 |
+
|
| 119 |
+
m = uv.astype(np.float32)
|
| 120 |
+
return cv2.remap(img, m[..., 0] - 0.5, m[..., 1] - 0.5, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def fix_image(art: np.ndarray, photo: np.ndarray, seg: np.ndarray, seg_ids: tuple, c2w_v, c2w_o, Kv, Ko,
|
| 124 |
+
plane_n, plane_p, origin_xy, tree, cfg: LaneFixConfig) -> tuple[np.ndarray, np.ndarray]:
|
| 125 |
+
"""(ํ์ฒ๋ฆฌ ์์, ๋ฐ๊พผ ๊ฐ์ค์น ฮฑ) โ art (h,w,3) ์ถ๋ ฅ ํด์๋, photo (H,W,3) ์๋ณธ ํด์๋."""
|
| 126 |
+
import cv2
|
| 127 |
+
|
| 128 |
+
h, w = art.shape[:2]
|
| 129 |
+
X, dist = ground_lookup(c2w_v, Kv, (w, h), plane_n, plane_p)
|
| 130 |
+
ok = np.isfinite(dist) & (dist < cfg.max_dist_m)
|
| 131 |
+
Xs = np.where(ok[..., None], X, 0)
|
| 132 |
+
if cfg.mode == "band":
|
| 133 |
+
dmap, _ = tree.query((Xs[..., :2] + origin_xy).reshape(-1, 2), distance_upper_bound=cfg.band_m)
|
| 134 |
+
ok &= np.isfinite(dmap.reshape(h, w))
|
| 135 |
+
uv, z = project(Xs, c2w_o, Ko)
|
| 136 |
+
H, W = photo.shape[:2]
|
| 137 |
+
ok &= (z > 0.5) & (uv[..., 0] >= 0) & (uv[..., 0] < W - 1) & (uv[..., 1] >= 0) & (uv[..., 1] < H - 1)
|
| 138 |
+
movers, ground = seg_ids
|
| 139 |
+
bad = np.isin(seg, movers).astype(np.uint8)
|
| 140 |
+
bad = cv2.dilate(bad, np.ones((cfg.mover_dilate_px, cfg.mover_dilate_px), np.uint8)) > 0
|
| 141 |
+
good = np.isin(seg, ground if cfg.mode == "band" else ground[:1]) & ~bad # road ๋ชจ๋: ์ฐจ๋๋ง (๋ณด๋๋ 15 cm ๋์ ํ๋ฉด์ด ์๋๋ค)
|
| 142 |
+
uvc = np.clip(np.nan_to_num(uv), 0, [W - 1, H - 1])
|
| 143 |
+
ok &= bilinear(good.astype(np.float32), uvc) > 0.99
|
| 144 |
+
if ok.sum() < 50:
|
| 145 |
+
return art, np.zeros((h, w), np.float32)
|
| 146 |
+
warped = bilinear(photo.astype(np.float32), uvc)
|
| 147 |
+
# ์ ๋ง์ถค: ๋ ์ ๋
ธ๋ฉด(์ฐจ์ ํ์ธํธ = ๋ฐ์ ์์ 20% ์ ์ธ)์์ ์ฑ๋๋ณ ํ๊ท ยท ํ์คํธ์ฐจ
|
| 148 |
+
lum_w, lum_a = warped.mean(-1), art.astype(np.float32).mean(-1)
|
| 149 |
+
road = ok & (lum_w < np.percentile(lum_w[ok], 80))
|
| 150 |
+
out = art.astype(np.float32).copy()
|
| 151 |
+
fixed = warped.copy()
|
| 152 |
+
for c in range(3):
|
| 153 |
+
mw, sw = warped[..., c][road].mean(), warped[..., c][road].std() + 1e-3
|
| 154 |
+
ma, sa = out[..., c][road].mean(), out[..., c][road].std() + 1e-3
|
| 155 |
+
k = cfg.contrast * (sa / sw) + (1 - cfg.contrast) # ๋๋น๋ฅผ ๋ค ๋ง์ถ๋ฉด ์ฐจ์ ์ด ํ๋ ค์ง๋ค โ ์ผ๋ถ๋ง
|
| 156 |
+
fixed[..., c] = (warped[..., c] - mw) * k + ma
|
| 157 |
+
# ์์ญ ๊ฒฝ๊ณ(์๋ ์นด๋ฉ๋ผ ์์ผ ๋ ยท ์ฐ์ ยท ์ฐจ๋)์์ ์์ชฝ์ผ๋ก feather_px ์ ๊ฑธ์ณ ์๋๋ค
|
| 158 |
+
inside = cv2.distanceTransform(ok.astype(np.uint8), cv2.DIST_L2, 5)
|
| 159 |
+
a = cv2.GaussianBlur(np.clip(inside / cfg.feather_px, 0, 1).astype(np.float32), (0, 0), 1.5) * ok
|
| 160 |
+
out = out * (1 - a[..., None]) + fixed * a[..., None]
|
| 161 |
+
_ = lum_a
|
| 162 |
+
return np.clip(out, 0, 255).astype(np.uint8), a
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def plane_of(frame, origin: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
| 166 |
+
"""์์ฐจ ์ขํ z = 0 ํ๋ฉด (์ฌ ์์ ๊ธฐ์ค ์ขํ์ ๋ฒ์ ยท ํ ์ )."""
|
| 167 |
+
from nusim.data.rig import quat_to_matrix
|
| 168 |
+
|
| 169 |
+
R = quat_to_matrix(frame.ego_rotation)
|
| 170 |
+
return R[:, 2], np.asarray(frame.ego_translation, np.float64) - origin
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def fix_prep_dir(prep_dir: Path, art_dir: Path, out_dir: Path, seg: Segmenter, scene, tree, cfg: LaneFixConfig,
|
| 174 |
+
keyframes_only: bool = True, debug_dir: Path | None = None) -> int:
|
| 175 |
+
"""์ค๋น ๋๋ ํฐ๋ฆฌ ํ๋(๊ตฌ๊ฐ ร ๋ณํ)์ ๊ณ ์น ํ๋ ์๋ง๋ค. art_dir = ๊ทธ ๋ณํ์ ArtiFixer ๊ฒฐ๊ณผ (<CAM>/<์๋ณธ ํ์ผ๋ช
>.jpg)."""
|
| 176 |
+
from PIL import Image
|
| 177 |
+
|
| 178 |
+
from nusim.io.ply import read_origin
|
| 179 |
+
from nusim.paths import dataset
|
| 180 |
+
|
| 181 |
+
pm = json.loads((prep_dir / "prep.json").read_text())
|
| 182 |
+
tr = json.loads((prep_dir / "transforms.json").read_text())
|
| 183 |
+
origin = read_origin(Path(pm["model"]) / "point_cloud.ply")
|
| 184 |
+
frames = tr["frames"]
|
| 185 |
+
n = len(frames) // 2
|
| 186 |
+
Kv = np.array([[tr["fl_x"], 0, tr["cx"]], [0, tr["fl_y"], tr["cy"]], [0, 0, 1]], np.float64)
|
| 187 |
+
by_fn = {f.filename: f for f in scene.frames[pm["cam"]]}
|
| 188 |
+
calib = scene.calib[pm["cam"]]
|
| 189 |
+
Ko = np.asarray(calib.intrinsic, np.float64)
|
| 190 |
+
ds = dataset()
|
| 191 |
+
cnt = 0
|
| 192 |
+
for i in range(n):
|
| 193 |
+
ref, tgt = frames[i], frames[i + n]
|
| 194 |
+
fn = ref["original_filename"]
|
| 195 |
+
if keyframes_only and not fn.startswith("samples/"):
|
| 196 |
+
continue
|
| 197 |
+
src = art_dir / pm["cam"] / Path(fn).name
|
| 198 |
+
if not src.exists():
|
| 199 |
+
continue
|
| 200 |
+
art = np.asarray(Image.open(src).convert("RGB"))
|
| 201 |
+
photo = np.asarray(Image.open(ds.root / fn).convert("RGB"))
|
| 202 |
+
s = photo.shape[1] / tr["w"] # ์๋ ์นด๋ฉ๋ผ๋ ์๋ณธ ํด์๋๋ก โ ์์ธ๋ ๊ฐ๊ณ K ๋ง ํค์ด๋ค
|
| 203 |
+
Ko_s = Kv.copy()
|
| 204 |
+
Ko_s[:2] *= s
|
| 205 |
+
c2w_v = np.asarray(tgt["transform_matrix"]) @ OPENGL_TO_OPENCV
|
| 206 |
+
c2w_o = np.asarray(ref["transform_matrix"]) @ OPENGL_TO_OPENCV
|
| 207 |
+
pn, pp = plane_of(by_fn[fn], origin)
|
| 208 |
+
lab = seg(photo)
|
| 209 |
+
out, a = fix_image(art, photo, lab, (seg.movers, seg.ground), c2w_v, c2w_o, Kv, Ko_s, pn, pp,
|
| 210 |
+
origin[:2], tree, cfg)
|
| 211 |
+
dst = out_dir / pm["cam"] / Path(fn).name
|
| 212 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 213 |
+
Image.fromarray(out).save(dst, quality=95)
|
| 214 |
+
if debug_dir is not None:
|
| 215 |
+
debug_dir.mkdir(parents=True, exist_ok=True)
|
| 216 |
+
Image.fromarray((a * 255).astype(np.uint8)).save(debug_dir / (Path(fn).stem + "_alpha.png"))
|
| 217 |
+
cnt += 1
|
| 218 |
+
_ = Ko
|
| 219 |
+
return cnt
|
src/nusim/nusim/aug/map_aug.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""๋งต ์ ์ฒด ์์ ๋ณํ ์ฆ๊ฐ โ ์ผ๊ด ์ฌ๊ตฌ์ฑ(nusim recon batch)ํ ๋ฎ ์ฌ๋ค์ ๋ฆฌ๊ทธ ๋ณํ์ผ๋ก ๋ค์ ์ฐ๋๋ค.
|
| 2 |
+
|
| 3 |
+
nusim aug rig ์ ์ฌ ํ๋๋ฅผ ๋ชจ๋ธ ํ๋๋ก ๊ทธ๋ฆฐ๋ค. ๋งต ์ผ๊ด ์ฌ๊ตฌ์ฑ์์๋ ๊ธด ์ฌ์ด ๊ตฌ๊ฐ(seg<k>)์ผ๋ก ๋๋์ด ์์ผ๋ฏ๋ก
|
| 4 |
+
(nusim.recon.batch.plan_segments), CAM_FRONT ํ๋ ์๋ง๋ค ๊ทธ ์๊ฐ์ '๋งก๋' ๊ตฌ๊ฐ์ ๋ชจ๋ธ๋ก ๊ทธ๋ฆฐ๋ค. ๋งก๋ ๊ตฌ๊ฐ์ ์ด์๊ณผ
|
| 5 |
+
๊ฒน์น์ง ์๋๋ค (๊ฒน์นจ 10 m ์ ๊ฐ์ด๋ฐ์์ ์๋ฆ) โ ํ๋ ์๋ง๋ค ์ ํํ ํ ๋ชจ๋ธ์ด๊ณ , ๊ฐ ๋ชจ๋ธ์ ์๊ธฐ ํ์ต ๊ตฌ๊ฐ ์์ชฝ๋ง ๊ทธ๋ฆฐ๋ค.
|
| 6 |
+
|
| 7 |
+
๊ฒฐ๊ณผ: <out>/<scene>/<variant>/<CAM>/<์๋ณธ ํ์ผ๋ช
>.jpg + meta.json (nusim aug rig ๊ณผ ๊ฐ์ ํ์ + segmentsยทkeyframes_only).
|
| 8 |
+
๊ฐ์ ๋ชจ๋ธยท์ค์ ์ผ๋ก ๋๋ ๋ณํ์ ๊ฑด๋๋ด๋ค (meta.json ์ model ๋ชฉ๋ก ๋น๊ต) โ ์ผ๊ด ํ์ต์ ๋ฐ๋ผ๊ฐ๋ฉฐ ์ฌ๋ฌ ๋ฒ ๋ถ๋ฌ๋ ๋๋ค.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import logging
|
| 15 |
+
import shutil
|
| 16 |
+
from dataclasses import replace
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
LOG = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def assign_front_frames(scene, segs: list[dict]) -> list[list]:
|
| 23 |
+
"""๊ตฌ๊ฐ๋ง๋ค ๋งก๋ CAM_FRONT ํ๋ ์ โ ๋ฐ์ด๋ฆฐ ๊ตฌ๊ฐ [own0, own1) (๋ง์ง๋ง์ ๋๊น์ง), ์ฌ ์์ ๊ธฐ์ค ๋ง์ดํฌ๋ก์ด๋ก ๋น๊ต."""
|
| 24 |
+
start = min(f.timestamp for fs in scene.frames.values() for f in fs[:1])
|
| 25 |
+
front = scene.frames["CAM_FRONT"]
|
| 26 |
+
out = []
|
| 27 |
+
for i, s in enumerate(segs):
|
| 28 |
+
lo = -1 if s.get("own0") is None or i == 0 else start + round(s["own0"] * 1e6)
|
| 29 |
+
hi = None if s.get("own1") is None or i == len(segs) - 1 else start + round(s["own1"] * 1e6)
|
| 30 |
+
out.append([f for f in front if f.timestamp >= lo and (hi is None or f.timestamp < hi)])
|
| 31 |
+
return out
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _stamp(d: Path) -> str:
|
| 35 |
+
p = d / "point_cloud.ply"
|
| 36 |
+
return f"{d}@{int(p.stat().st_mtime)}"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def ensure_ibl(scene_name: str, segs: list[tuple[dict, Path]], log=LOG.info) -> None:
|
| 40 |
+
"""glTF ์ฐจ๋์ ๋ค์ ๋ฃ์ผ๋ ค๋ฉด ์ฌ์ IBL(ํ๊ฒฝ ์กฐ๋ช
)์ด ์์ด์ผ ํ๋ค (_ibl_gltf_args). ์์ผ๋ฉด ๊ฐ์ด๋ฐ ํ๋ ์์ ๋งก๋ ๊ตฌ๊ฐ ๋ชจ๋ธ๋ก ๊ตฝ๋๋ค."""
|
| 41 |
+
from nusim.data.index import load_scene
|
| 42 |
+
from nusim.paths import output_dir
|
| 43 |
+
from nusim.render.ibl import bake_ibl
|
| 44 |
+
|
| 45 |
+
out = output_dir() / "ibl" / scene_name
|
| 46 |
+
if (out / "env_specular.ktx2").exists():
|
| 47 |
+
return
|
| 48 |
+
scene = load_scene(scene_name)
|
| 49 |
+
mid = len(scene.frames["CAM_FRONT"]) // 2
|
| 50 |
+
owned = assign_front_frames(scene, [s for s, _ in segs])
|
| 51 |
+
tmid = scene.frames["CAM_FRONT"][mid].timestamp
|
| 52 |
+
d = next((d for (s, d), fr in zip(segs, owned) if any(f.timestamp == tmid for f in fr)), segs[0][1])
|
| 53 |
+
# ๊ตฌ๊ฐ ๋ชจ๋ธ์ ์ ๋ณด์ ์ ๊ทธ ํ์ต ๊ตฌ๊ฐ์ ๋ทฐ ์๋งํผ์ด๋ค โ ๊ฐ์ ๊ตฌ๊ฐ์ผ๋ก ์๋ฅธ ์ฌ์ ๋๊ธด๋ค
|
| 54 |
+
from nusim.cli.main import _model_data
|
| 55 |
+
|
| 56 |
+
sub, _ = _model_data(scene_name, d)
|
| 57 |
+
front = sub.frames["CAM_FRONT"]
|
| 58 |
+
k = min(range(len(front)), key=lambda i: abs(front[i].timestamp - tmid))
|
| 59 |
+
log(f" IBL ๊ตฝ๊ธฐ (๋ชจ๋ธ {d})")
|
| 60 |
+
bake_ibl(sub, d, out, frame=k)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def aug_scene(scene_name: str, root: Path, variants: list[dict], out_root: Path, viewer: Path,
|
| 64 |
+
keyframes: bool = True, agents: bool = True, log=LOG.info) -> dict:
|
| 65 |
+
from nusim.aug.rig_aug import keyframes_only, render_frames, variant_meta
|
| 66 |
+
from nusim.cli.main import _ibl_gltf_args
|
| 67 |
+
from nusim.data.index import load_scene
|
| 68 |
+
from nusim.recon.batch import segment_dirs
|
| 69 |
+
|
| 70 |
+
segs = segment_dirs(root, scene_name)
|
| 71 |
+
if not segs:
|
| 72 |
+
raise FileNotFoundError(f"{root / scene_name} ์ ํ์ต๋ ๋ชจ๋ธ์ด ์๋ค")
|
| 73 |
+
if len(segs) != segs[0][0]["n"]:
|
| 74 |
+
raise RuntimeError(f"{scene_name}: ๊ตฌ๊ฐ {segs[0][0]['n']}๊ฐ ์ค {len(segs)}๊ฐ๋ง ํ์ต๋จ")
|
| 75 |
+
if agents:
|
| 76 |
+
ensure_ibl(scene_name, segs, log)
|
| 77 |
+
extra = _ibl_gltf_args(scene_name, agents=True) if agents else []
|
| 78 |
+
scene = load_scene(scene_name)
|
| 79 |
+
owned = assign_front_frames(scene, [s for s, _ in segs])
|
| 80 |
+
base = keyframes_only(scene) if keyframes else scene
|
| 81 |
+
models = [_stamp(d) for _, d in segs]
|
| 82 |
+
summary = {}
|
| 83 |
+
for v in variants:
|
| 84 |
+
out = out_root / scene_name / v["name"]
|
| 85 |
+
meta_path = out / "meta.json"
|
| 86 |
+
if meta_path.exists():
|
| 87 |
+
old = json.loads(meta_path.read_text())
|
| 88 |
+
if old.get("model") == models and old.get("keyframes_only") == keyframes and old.get("variant") == v \
|
| 89 |
+
and old.get("agents") == agents:
|
| 90 |
+
summary[v["name"]] = len(old["samples"])
|
| 91 |
+
continue
|
| 92 |
+
if out.exists():
|
| 93 |
+
shutil.rmtree(out)
|
| 94 |
+
samples, seg_meta, offset = [], [], 0
|
| 95 |
+
for (s, d), fr in zip(segs, owned):
|
| 96 |
+
keep = {f.timestamp for f in fr}
|
| 97 |
+
front = [f for f in base.frames["CAM_FRONT"] if f.timestamp in keep]
|
| 98 |
+
if not front:
|
| 99 |
+
continue
|
| 100 |
+
sub = replace(base, frames={**base.frames, "CAM_FRONT": front})
|
| 101 |
+
got = render_frames(sub, d, v, out, viewer, extra)
|
| 102 |
+
(out / "rig.txt").rename(out / f"rig_seg{s['k']}.txt")
|
| 103 |
+
for x in got:
|
| 104 |
+
x["frame"] += offset
|
| 105 |
+
x["segment"] = s["k"]
|
| 106 |
+
offset += len(front)
|
| 107 |
+
samples += got
|
| 108 |
+
seg_meta.append({"k": s["k"], "own0": s.get("own0"), "own1": s.get("own1"), "model": str(d),
|
| 109 |
+
"front_frames": len(front), "images": len(got)})
|
| 110 |
+
meta = variant_meta(scene, v, models, samples)
|
| 111 |
+
meta.update(keyframes_only=keyframes, agents=agents, segments=seg_meta)
|
| 112 |
+
meta_path.write_text(json.dumps(meta, indent=1, ensure_ascii=False))
|
| 113 |
+
summary[v["name"]] = len(samples)
|
| 114 |
+
log(f" {v['name']:<14} {len(samples):,}์ฅ (๊ตฌ๊ฐ {len(seg_meta)}๊ฐ) โ {out}")
|
| 115 |
+
return summary
|
src/nusim/nusim/aug/rig_aug.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์นด๋ฉ๋ผ ๋ฆฌ๊ทธ ๋ณํ ์ฆ๊ฐ โ ์ฌ๊ตฌ์ฑํ ๋ฎ ์ฅ๋ฉด์ 6์บ ์์ ์ ๋ฐ๊ฟ ๋ค์ ์ฐ๋๋ค.
|
| 2 |
+
|
| 3 |
+
๊ฐ์ ์ฃผํ(๊ฐ์ ์๊ฐยท๊ฐ์ ์์ฐจ ๊ถค์ )์ ์นด๋ฉ๋ผ ์ฅ์ฐฉ ์์นยท์์ธ๋ง ๋ฐ๊ฟ ๋ ๋ํ๋ค: ์ ์ฐจ์ ์ชฝ์ผ๋ก ์ด๋, ๋ ๋์/๋ฎ์
|
| 4 |
+
์ฐจ, ์๋๋ก ์์, ์ข์ฐ๋ก ๋๋ฆผ (configs/aug/rig_variants.json). 6์บ ์ ์ฒด์ ๊ฐ์ ๋ณํ์ ์ฃผ๋ฏ๋ก ๋ฆฌ๊ทธ ๊ธฐํ๊ฐ ์ผ๊ด๋๋ค.
|
| 5 |
+
|
| 6 |
+
๋ฐฉ๋ฒ: ์ฌ ์ธ๋ฑ์ค์ ์นด๋ฉ๋ผ ์บ๋ฆฌ๋ธ๋ ์ด์
(cam โ ego)๋ง ๋ฐ๊พผ ์ฌ๋ณธ์ผ๋ก ๋ทฐ์ด ๋ฆฌ๊ทธ(rig.txt)๋ฅผ ๋ง๋ค๊ณ
|
| 7 |
+
(nusim.render.export.write_rig โ ํ์ต์ด ๋ณด์ ํ ์ธ๋ถ ํ๋ผ๋ฏธํฐ๋ ๊ทธ๋๋ก ์ ์ฉ), vkgs_viewer --offscreen ์ผ๋ก ๋ ๋ํ๋ค.
|
| 8 |
+
๊ฒฐ๊ณผ: outputs/aug/rig/<scene>/<variant>/
|
| 9 |
+
<CAM>/<์๋ณธ ํ์ผ๋ช
>.jpg ์๋ณธ๊ณผ ๊ฐ์ ์ด๋ฆ โ CAM_FRONT ํ๋ ์๋ง๋ค ๊ฐ ์นด๋ฉ๋ผ์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ฅ (rig.txt ์ ๊ฐ์ ๋์).
|
| 10 |
+
์๋ณธ sweep ์ ๋ถ๋ ์๋๋ค (์นด๋ฉ๋ผ๋ง๋ค ์ฝ 12 Hz ์ค CAM_FRONT ์๊ฐ์ ๊ฐ๊น์ด ๊ฒ๋ง)
|
| 11 |
+
meta.json ๋ณํ, ๋ฐ๋ calibrated_sensor (cam โ ego, nuScenes ํ์), ์ฅ๋ง๋ค ์๋ณธ ํ์ผยท์๊ฐยท์์ฐจ ํฌ์ฆ
|
| 12 |
+
rig.txt ๋ ๋์ ์ด ๋ฆฌ๊ทธ
|
| 13 |
+
์๋ณธ ๋ฐ์ดํฐ์
์๋ ์ฐ์ง ์๋๋ค.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import json
|
| 19 |
+
import logging
|
| 20 |
+
import shutil
|
| 21 |
+
import subprocess
|
| 22 |
+
from dataclasses import replace
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
from nusim.data.rig import matrix_to_quat, quat_to_matrix
|
| 28 |
+
|
| 29 |
+
LOG = logging.getLogger(__name__)
|
| 30 |
+
RENDER_ATTEMPTS = 3 # ๊นจ์ง PNG ๊ฐ ๋์ค๋ฉด ๊ตฌ๊ฐ์ ๋ค์ ๋ ๋ํ๋ ํ์ (render_frames)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _broken_pngs(d: Path) -> list[Path]:
|
| 34 |
+
"""d ์ PNG ์ค ๋๊น์ง ๋์ฝ๋ฉ๋์ง ์๋ ๊ฒ (zlib ๊ฒ์ฌํฉ ํฌํจ)."""
|
| 35 |
+
from PIL import Image
|
| 36 |
+
|
| 37 |
+
bad = []
|
| 38 |
+
for p in sorted(d.glob("*.png")):
|
| 39 |
+
try:
|
| 40 |
+
with Image.open(p) as im:
|
| 41 |
+
im.load()
|
| 42 |
+
except OSError:
|
| 43 |
+
bad.append(p)
|
| 44 |
+
return bad
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _rx(a: float) -> np.ndarray:
|
| 48 |
+
c, s = np.cos(a), np.sin(a)
|
| 49 |
+
return np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _rz(a: float) -> np.ndarray:
|
| 53 |
+
c, s = np.cos(a), np.sin(a)
|
| 54 |
+
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def transform_calib(calib, shift_m=(0, 0, 0), rot_deg=(0, 0, 0)):
|
| 58 |
+
"""CameraCalib (cam โ ego) ์ ๋ฆฌ๊ทธ ๋ณํ์ ์ค๋ค.
|
| 59 |
+
shift_m: ego ์ขํ [x ์, y ์ผ์ชฝ, z ์] ์ด๋. rot_deg: [roll, pitch, yaw] โ
|
| 60 |
+
pitch + = ์นด๋ฉ๋ผ๊ฐ ์๋๋ก ์์ธ๋ค (์นด๋ฉ๋ผ ์์ ์ ๊ฐ๋ก์ถ), yaw + = ์ผ์ชฝ์ผ๋ก (ego z ์ถ, ๋ฆฌ๊ทธ ์ ์ฒด๊ฐ ๊ฐ์ด),
|
| 61 |
+
roll = ์นด๋ฉ๋ผ ๊ด์ถ ๋๋ . ์นด๋ฉ๋ผ ์ขํ๋ OpenCV (x ์ค๋ฅธ์ชฝ, y ์๋, z ์) โ rig.py ์ ๊ฐ๋ค."""
|
| 62 |
+
roll, pitch, yaw = np.radians(np.asarray(rot_deg, dtype=np.float64))
|
| 63 |
+
R = quat_to_matrix(calib.rotation)
|
| 64 |
+
# ์นด๋ฉ๋ผ x ์ถ ๋๋ +ฮธ ๋ ๊ด์ถ(z)์ ์(-y)๋ก ๋ ๋ค โ ์์ด๋ ค๋ฉด -pitch
|
| 65 |
+
R2 = _rz(yaw) @ R @ _rx(-pitch) @ _rz(roll)
|
| 66 |
+
t2 = np.asarray(calib.translation, dtype=np.float64) + np.asarray(shift_m, dtype=np.float64)
|
| 67 |
+
return replace(calib, rotation=matrix_to_quat(R2), translation=tuple(float(x) for x in t2))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def load_variants(path: Path, names: list[str] | None = None) -> list[dict]:
|
| 71 |
+
vs = [v for v in json.loads(path.read_text())["variants"]]
|
| 72 |
+
if names:
|
| 73 |
+
missing = set(names) - {v["name"] for v in vs}
|
| 74 |
+
if missing:
|
| 75 |
+
raise ValueError(f"๋ณํ์ด ์์: {sorted(missing)} (์๋ ๊ฒ: {[v['name'] for v in vs]})")
|
| 76 |
+
vs = [v for v in vs if v["name"] in names]
|
| 77 |
+
return vs
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def keyframes_only(scene):
|
| 81 |
+
"""ํคํ๋ ์(2 Hz sample)๋ง ๋จ๊ธด ์ฌ๋ณธ. rig_frames ๋ CAM_FRONT ์๊ฐ์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ฅ์ ๊ณ ๋ฅด๋ฏ๋ก, ๋ชจ๋ ์นด๋ฉ๋ผ๋ฅผ
|
| 82 |
+
ํคํ๋ ์์ผ๋ก ์ค์ด๋ฉด ๊ฐ์ sample ์ 6์ฅ์ด ์ง์ง์ด์ง๋ค."""
|
| 83 |
+
return replace(scene, frames={c: [f for f in fs if f.is_keyframe] for c, fs in scene.frames.items()})
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def render_frames(scene, model_dir: Path, variant: dict, out: Path, viewer: Path,
|
| 87 |
+
extra_args: list[str] | None = None, jpeg_quality: int = 95) -> list[dict]:
|
| 88 |
+
"""scene(๊ตฌ๊ฐยทํคํ๋ ์์ผ๋ก ์ค์ธ ์ฌ๋ณธ์ด์ด๋ ๋จ)์ CAM_FRONT ํ๋ ์๋ง๋ค 6์บ ์ ๋ฆฌ๊ทธ ๋ณํ์ผ๋ก ๋ ๋ํด
|
| 89 |
+
out/<CAM>/<์๋ณธ ํ์ผ๋ช
>.jpg ๋ก ์ด๋ค. ์ฅ๋ง๋ค ๋ฉํ ๋ชฉ๋ก์ ๋๋ ค์ค๋ค (frame ์ ์ด ํธ์ถ ์์ ๋ฒํธ)."""
|
| 90 |
+
from PIL import Image
|
| 91 |
+
|
| 92 |
+
from nusim.cli.main import _model_objects
|
| 93 |
+
from nusim.io.ply import read_origin
|
| 94 |
+
from nusim.render.export import load_camera_adjust, rig_frames, write_rig
|
| 95 |
+
|
| 96 |
+
vscene = replace(scene, calib={cam: transform_calib(c, variant.get("shift_m", (0, 0, 0)), variant.get("rot_deg", (0, 0, 0)))
|
| 97 |
+
for cam, c in scene.calib.items()})
|
| 98 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 99 |
+
origin = read_origin(model_dir / "point_cloud.ply")
|
| 100 |
+
write_rig(out / "rig.txt", vscene, origin, load_camera_adjust(model_dir), objects=_model_objects(model_dir))
|
| 101 |
+
tmp = out / "_png"
|
| 102 |
+
# ๋ฐ์ดํฐ์
์ฉ: ํ๋ฉด ๊ณต๊ฐ AA ์์ โ ์ค์ ์นด๋ฉ๋ผ ์์์ฒ๋ผ. ํ๋: ๋ ์จ๊ฐ ์์ผ๋ฉด ํ์ต๊ธฐ์ ๊ฐ์ ํ๋(ํ๋ ๊ฐ์ฐ์์ ์ ์ง,
|
| 103 |
+
# ํ๋ ํ๋ธ๋งต์ ํ์ตํ ๋ชจ๋ธ์ด๋ฉด ๊ทธ ํ๋ธ๋งต), ๋ ์จ ๋ณํ์ด๋ฉด ๋ฎ ํ๋ ๊ฐ์ฐ์์์ ๋นผ๊ณ ์๋ฎฌ๋ ์ด์
ํ๋
|
| 104 |
+
cmd = [str(viewer), str(model_dir / "point_cloud.ply"), "--cams", str(out / "rig.txt"), "--offscreen",
|
| 105 |
+
"--out", str(tmp), "--pass", "fxaa=off"]
|
| 106 |
+
if (model_dir / "objects" / "objects.json").exists(): # ์ฐจ๋์ ํจ๊ป ํ์ตํ ๋ชจ๋ธ โ ์ฐจ๋ ์์
๋ ๊ฐ์ ์์ ์์
|
| 107 |
+
cmd += ["--objects", str(model_dir / "objects")]
|
| 108 |
+
if variant.get("weather"):
|
| 109 |
+
cmd += ["--weather", variant["weather"]]
|
| 110 |
+
else:
|
| 111 |
+
cmd += ["--keep-sky-gaussians"]
|
| 112 |
+
if (model_dir / "sky.ktx2").exists():
|
| 113 |
+
cmd += ["--sky", str(model_dir / "sky.ktx2")]
|
| 114 |
+
cmd += extra_args or []
|
| 115 |
+
# ๋ ๋๋ ๊ฒฐ์ ์ ์ธ๋ฐ PNG ๊ฐ ๊ฐ๋ ๊นจ์ง๋ค โ ๋ค์ ๋ ๋ํ ๊ฒ๊ณผ 1๋นํธ ์ฐจ์ด (2026-10-12, scene-0017ยท0036: ECC ์๋ ๋ฉ๋ชจ๋ฆฌ์
|
| 116 |
+
# ๋นํธ ๋ค์งํ์ผ๋ก ๋ณธ๋ค). zlib ๊ฒ์ฌํฉ์ด ์ก์ผ๋ฏ๋ก PNG ๋ฅผ ๋ค ์ด์ด ๋ณด๊ณ , ๊นจ์ก์ผ๋ฉด ๊ทธ ๊ตฌ๊ฐ์ ํ ๋ฒ ๋ ๋ ๋ํ๋ค
|
| 117 |
+
for attempt in range(RENDER_ATTEMPTS):
|
| 118 |
+
if tmp.exists():
|
| 119 |
+
shutil.rmtree(tmp)
|
| 120 |
+
r = subprocess.run(cmd, capture_output=True, text=True)
|
| 121 |
+
if r.returncode != 0:
|
| 122 |
+
raise RuntimeError(f"vkgs_viewer ์คํจ:\n{r.stdout[-2000:]}\n{r.stderr[-2000:]}")
|
| 123 |
+
bad = _broken_pngs(tmp)
|
| 124 |
+
if not bad:
|
| 125 |
+
break
|
| 126 |
+
LOG.warning(f" ๊นจ์ง PNG {len(bad)}์ฅ ({bad[0].name}) โ ๋ค์ ๋ ๋ ({attempt + 2}/{RENDER_ATTEMPTS})")
|
| 127 |
+
else:
|
| 128 |
+
raise RuntimeError(f"PNG ๊ฐ {RENDER_ATTEMPTS}๋ฒ ๋ค ๊นจ์ง: {bad[:3]}")
|
| 129 |
+
samples, done = [], set()
|
| 130 |
+
for k, cam, fr in rig_frames(scene):
|
| 131 |
+
src = tmp / f"f{k:04d}_{cam}.png"
|
| 132 |
+
# rig ๋ CAM_FRONT ํ๋ ์๋ง๋ค ๊ฐ ์นด๋ฉ๋ผ์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ฅ์ ๊ณ ๋ฅด๋ฏ๋ก ๊ฐ์ ์ฅ์ด ๋ ๋ฒ ๋ฝํ ์ ์๋ค โ ํ ๋ฒ๋ง
|
| 133 |
+
if not src.exists() or fr.filename in done:
|
| 134 |
+
continue
|
| 135 |
+
done.add(fr.filename)
|
| 136 |
+
dst = out / cam / Path(fr.filename).name
|
| 137 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 138 |
+
Image.open(src).convert("RGB").save(dst, quality=jpeg_quality)
|
| 139 |
+
samples.append({"frame": k, "cam": cam, "timestamp": fr.timestamp, "is_keyframe": fr.is_keyframe,
|
| 140 |
+
"original_filename": fr.filename, "filename": str(dst.relative_to(out)),
|
| 141 |
+
"ego_translation": list(fr.ego_translation), "ego_rotation": list(fr.ego_rotation)})
|
| 142 |
+
shutil.rmtree(tmp)
|
| 143 |
+
return samples
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def variant_meta(scene, variant: dict, models, samples: list[dict]) -> dict:
|
| 147 |
+
vscene_calib = {cam: transform_calib(c, variant.get("shift_m", (0, 0, 0)), variant.get("rot_deg", (0, 0, 0)))
|
| 148 |
+
for cam, c in scene.calib.items()}
|
| 149 |
+
return {
|
| 150 |
+
"scene": scene.name, "variant": variant, "model": models,
|
| 151 |
+
"calibrated_sensor": {cam: {"translation": list(c.translation), "rotation": list(c.rotation),
|
| 152 |
+
"camera_intrinsic": [list(r) for r in c.intrinsic]}
|
| 153 |
+
for cam, c in vscene_calib.items()},
|
| 154 |
+
"note": "calibrated_sensor = ๋ฐ๋ cam โ ego (nuScenes ํ์, ํ์ต ๋ณด์ ์ ). ์์ฐจ ํฌ์ฆ๋ ์๋ณธ ๊ทธ๋๋ก.",
|
| 155 |
+
"samples": samples,
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def render_variant(scene_name: str, model_dir: Path, variant: dict, out_root: Path, viewer: Path,
|
| 160 |
+
extra_args: list[str] | None = None, jpeg_quality: int = 95, log=LOG.info) -> dict:
|
| 161 |
+
"""์ฌ ์ ์ฒด๋ฅผ ๋ชจ๋ธ ํ๋๋ก (nusim aug rig). ๊ตฌ๊ฐ ๋ชจ๋ธ๋ก ๋๋ ์ฌ์ nusim.aug.map_aug."""
|
| 162 |
+
from nusim.data.index import load_scene
|
| 163 |
+
|
| 164 |
+
scene = load_scene(scene_name)
|
| 165 |
+
out = out_root / scene_name / variant["name"]
|
| 166 |
+
samples = render_frames(scene, model_dir, variant, out, viewer, extra_args, jpeg_quality)
|
| 167 |
+
meta = variant_meta(scene, variant, str(model_dir), samples)
|
| 168 |
+
(out / "meta.json").write_text(json.dumps(meta, indent=1, ensure_ascii=False))
|
| 169 |
+
log(f" {variant['name']:<20} {len(samples):,}์ฅ โ {out}")
|
| 170 |
+
return meta
|
src/nusim/nusim/aug/translate.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""๋ฐคยท๋น โ ๋ฎ ์์ ๋ณํ โ ๋ฎ ์ฌ๋ง์ผ๋ก๋ ๋ชป ๋ฎ๋ ์ง์ญ(์ฑ๊ฐํฌ๋ฅด ์ผ๊ฐยท์ฐ์ฒ ์ฌ)์ ์ฌ๊ตฌ์ฑํ ์ ์๊ฒ.
|
| 2 |
+
|
| 3 |
+
๋ชจ๋ธ: CycleGAN-Turbo (img2img-turbo, Parmar et al. 2024, MIT ์ฝ๋ / SD-Turbo ๊ฐ์ค์น ์ฐ๊ตฌ์ฉ) ์ BDD100K ์ฃผํ ์์
|
| 4 |
+
์ฌ์ ํ์ต ์ฒดํฌํฌ์ธํธ night_to_day, rainy_to_clear. third_party/img2img-turbo, ์ฒดํฌํฌ์ธํธ๋ cache/models/img2img-turbo
|
| 5 |
+
(cache/models/img2img-turbo/fetch.sh ๋ก ๋ณ๋ ฌ ๋ค์ด๋ก๋ โ ๊ฐ๋น 1.7 GB).
|
| 6 |
+
|
| 7 |
+
๊ฒฐ๊ณผ๋ ์๋ณธ๊ณผ ๊ฐ์ ์๋ ๊ฒฝ๋ก๋ก outputs/aug/translate/<model>/samples|sweeps/... ์ ์ด๋ค. ์ฌ๊ตฌ์ฑ์
|
| 8 |
+
`nusim recon train <scene> --image-root outputs/aug/translate/<model>` โ ์ด๋ฏธ์ง๋ง ๋ฐ๋๊ณ ๋ผ์ด๋คยทํฌ์ฆ๋ ์๋ณธ์ด๋ค.
|
| 9 |
+
์๋ณธ ๋ฐ์ดํฐ์
์๋ ์ฐ์ง ์๋๋ค.
|
| 10 |
+
|
| 11 |
+
์๊ณ ์ฐ์: ํ๋ ์๋ง๋ค ๋ฐ๋ก ๋ณํํ๋ฏ๋ก ์๊ฐ ๊น๋นก์ยท์นด๋ฉ๋ผ ์ฌ์ด ๋ถ์ผ์น๊ฐ ์๋ค. 3DGS ๋ ์ฌ๋ฌ ๋ทฐ์ ํ๊ท ์ ๋ฐฐ์ฐ๋ฏ๋ก
|
| 12 |
+
์กฐ๊ธ์ ๊ฒฌ๋์ง๋ง, ๊ฒฐ๊ณผ๋ ์ฌ๊ตฌ์ฑ PSNRยท์ก์์ผ๋ก ํ์ธํ๋ค.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import logging
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
|
| 24 |
+
LOG = logging.getLogger(__name__)
|
| 25 |
+
ROOT = Path(__file__).resolve().parents[4]
|
| 26 |
+
CKPT_DIR = ROOT / "cache" / "models" / "img2img-turbo"
|
| 27 |
+
MODELS = {"night_to_day": "night2day.pkl", "rainy_to_clear": "rainy2clear.pkl"}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_model(name: str):
|
| 31 |
+
import torch
|
| 32 |
+
|
| 33 |
+
if name not in MODELS:
|
| 34 |
+
raise ValueError(f"๋ชจ๋ฅด๋ ๋ณํ '{name}' (์๋ ๊ฒ: {list(MODELS)})")
|
| 35 |
+
if not (CKPT_DIR / MODELS[name]).exists():
|
| 36 |
+
raise FileNotFoundError(f"{CKPT_DIR / MODELS[name]} ๊ฐ ์์ต๋๋ค: bash {CKPT_DIR / 'fetch.sh'} {MODELS[name][:-4]}")
|
| 37 |
+
src = str(ROOT / "third_party" / "img2img-turbo" / "src")
|
| 38 |
+
if src not in sys.path:
|
| 39 |
+
sys.path.append(src) # nusim ๋ณด๋ค ๋ค โ ๊ทธ์ชฝ ๋ชจ๋ ์ด๋ฆ๊ณผ ๊ฒน์น์ง ์๊ฒ
|
| 40 |
+
from cyclegan_turbo import CycleGAN_Turbo
|
| 41 |
+
|
| 42 |
+
# ์๋ ์ฝ๋๋ fp32 ๋ก GPU ์ ์ฌ๋ฆฐ ๋ค ์ค์ธ๋ค (4.4 GB) โ 8 GB GPU ์์ ํ์ต๊ณผ ๊ฐ์ด ๋ชป ๋๋ค.
|
| 43 |
+
# ์์ฑ ์ค Module.cuda ๋ฅผ ์ ์ ๋ง์ CPU ์์ ๋ง๋ค๊ณ , fp16 ์ผ๋ก ๋ฐ๊พผ ๋ค ์ฌ๋ฆฐ๋ค (์ฝ 2 GB).
|
| 44 |
+
# ์ฒดํฌํฌ์ธํธ(1.7 GB fp32)๋ GPU ๋ก ์ ์ฅ๋ผ ์์ด torch.load ๊ฐ ๋ฐ๋ก ์ฌ๋ฆฐ๋ค โ CPU ๋ก ์ฝ๊ฒ.
|
| 45 |
+
real_cuda, real_load = torch.nn.Module.cuda, torch.load
|
| 46 |
+
torch.nn.Module.cuda = lambda self, *a, **k: self
|
| 47 |
+
torch.load = lambda f, *a, **k: real_load(f, *a, **{**k, "map_location": "cpu", "weights_only": False})
|
| 48 |
+
try:
|
| 49 |
+
m = CycleGAN_Turbo(pretrained_name=name, ckpt_folder=str(CKPT_DIR))
|
| 50 |
+
finally:
|
| 51 |
+
torch.nn.Module.cuda, torch.load = real_cuda, real_load
|
| 52 |
+
m = m.eval().half().cuda()
|
| 53 |
+
m.requires_grad_(False)
|
| 54 |
+
return m
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def translate_image(model, img, size: tuple[int, int]):
|
| 58 |
+
"""PIL ์ด๋ฏธ์ง โ ๋ณํํ PIL ์ด๋ฏธ์ง (์๋ ํฌ๊ธฐ๋ก). size = ๋ชจ๋ธ ์
๋ ฅ (8์ ๋ฐฐ์)."""
|
| 59 |
+
import torch
|
| 60 |
+
from PIL import Image
|
| 61 |
+
from torchvision import transforms
|
| 62 |
+
|
| 63 |
+
x = transforms.ToTensor()(img.resize(size, Image.LANCZOS))
|
| 64 |
+
x = (x * 2 - 1).unsqueeze(0).cuda().half()
|
| 65 |
+
with torch.no_grad(): # ์ ์ญ grad ์ค์ ์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค (๊ฐ์ ํ๋ก์ธ์ค์ ํ์ต์ ๋ง์น์ง ์๊ฒ)
|
| 66 |
+
y = model(x)
|
| 67 |
+
out = ((y[0].float().clamp(-1, 1) + 1) * 127.5).round().byte().permute(1, 2, 0).cpu().numpy()
|
| 68 |
+
return Image.fromarray(out).resize(img.size, Image.LANCZOS)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def translate_scene(scene_name: str, models: list[str], out_root: Path, size=(912, 512), cameras=None,
|
| 72 |
+
stride: int = 1, keyframes_only: bool = False, quality: int = 95, log=LOG.info) -> dict:
|
| 73 |
+
"""์ฌ์ ์นด๋ฉ๋ผ ์์์ models ์์๋๋ก ๋ณํ (์: rainy_to_clear ๋ค์ night_to_day). ์ด๋ฏธ ์๋ ํ์ผ์ ๊ฑด๋๋ด๋ค."""
|
| 74 |
+
from PIL import Image
|
| 75 |
+
|
| 76 |
+
from nusim import CAMERAS
|
| 77 |
+
from nusim.data.index import load_scene
|
| 78 |
+
from nusim.paths import dataset
|
| 79 |
+
|
| 80 |
+
scene, ds = load_scene(scene_name), dataset()
|
| 81 |
+
cams = cameras or CAMERAS
|
| 82 |
+
frames = []
|
| 83 |
+
for cam in cams:
|
| 84 |
+
fr = scene.keyframes(cam) if keyframes_only else scene.frames[cam]
|
| 85 |
+
frames += fr[::stride]
|
| 86 |
+
tag = "+".join(models)
|
| 87 |
+
out = out_root / tag
|
| 88 |
+
todo = [f for f in frames if not (out / f.filename).exists()]
|
| 89 |
+
log(f" {scene_name}: {len(frames):,}์ฅ ์ค {len(todo):,}์ฅ ๋ณํ ({tag}, ์
๋ ฅ {size[0]}x{size[1]}) โ {out}")
|
| 90 |
+
if not todo:
|
| 91 |
+
return {"n": len(frames), "done": 0, "out": str(out)}
|
| 92 |
+
# ๋ชจ๋ธ์ ํ๋์ฉ๋ง ์ฌ๋ฆฐ๋ค (fp16 ํ๋์ ์ฝ 2.2 GB โ 8 GB GPU ์์ ํ์ต๊ณผ ๊ฐ์ด ๋๊ฒ). ์ฐ์ ๋ณํ์ ๋จ๊ณ๋ณ๋ก:
|
| 93 |
+
# ์ ๋จ๊ณ ๊ฒฐ๊ณผ๋ฅผ out/_stage<i>/ ์ ๋๊ณ ๋ค์ ๋จ๊ณ๊ฐ ์ฝ๋๋ค.
|
| 94 |
+
import shutil
|
| 95 |
+
|
| 96 |
+
import torch
|
| 97 |
+
|
| 98 |
+
t0 = time.time()
|
| 99 |
+
src_root = ds.root
|
| 100 |
+
for si, name in enumerate(models):
|
| 101 |
+
last = si == len(models) - 1
|
| 102 |
+
dst_root = out if last else out / f"_stage{si}_{scene_name}" # ์ฌ๋ง๋ค ๋ฐ๋ก (๋์์ ๋๋ ค๋ ์๋ก ์ ์ง์ฐ๊ฒ)
|
| 103 |
+
net = load_model(name)
|
| 104 |
+
for i, f in enumerate(todo):
|
| 105 |
+
img = Image.open(src_root / f.filename).convert("RGB") # ์๋ณธ์ ์ฝ๊ธฐ๋ง
|
| 106 |
+
img = translate_image(net, img, size)
|
| 107 |
+
dst = dst_root / f.filename
|
| 108 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 109 |
+
img.save(dst, quality=quality if last else 98)
|
| 110 |
+
if i % 200 == 0:
|
| 111 |
+
log(f" [{name}] {i:,}/{len(todo):,} {(time.time() - t0) / max(i + si * len(todo), 1):.2f}s/์ฅ")
|
| 112 |
+
del net
|
| 113 |
+
torch.cuda.empty_cache()
|
| 114 |
+
if si > 0:
|
| 115 |
+
shutil.rmtree(src_root) # ์ ๋จ๊ณ ์์ ๊ฒฐ๊ณผ
|
| 116 |
+
src_root = dst_root
|
| 117 |
+
return {"n": len(frames), "done": len(todo), "out": str(out), "s_per_image": (time.time() - t0) / len(todo)}
|
src/nusim/nusim/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""์ปค๋งจ๋๋ผ์ธ ์ง์
์ ."""
|
src/nusim/nusim/cli/doctor.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""vkgs-doctor โ ์ปจํ
์ด๋ ํ๊ฒฝ์ด ์ค์ ๋ก ์ธ ์ ์๋ ์ํ์ธ์ง ํ ๋ฒ์ ์ ๊ฒํ๋ค.
|
| 2 |
+
|
| 3 |
+
์ด๊ฒ ๋ฐ๋ก ์๋ ์ด์ : ์ด ์คํ์ ์กฐ์ฉํ ๋ฐ์ฏค ๋ง๊ฐ์ง๋ ๋ฐฉ์์ด ์ฌ๋ฟ์ด๋ค.
|
| 4 |
+
- nvidia-container-toolkit ์ ์๋๋ฐ DRIVER_CAPABILITIES ์ graphics ๊ฐ ๋น ์ง๋ฉด
|
| 5 |
+
CUDA ๋ ๋๊ณ Vulkan ๋ง GPU ๋ฅผ 0๊ฐ๋ก ๋ณธ๋ค. ์๋ฌ ์์ด ๊ฒ์ ํ๋ฉด๋ง ๋์จ๋ค.
|
| 6 |
+
- NFS ๊ฐ ์ ๊น ๋๊ธฐ๋ฉด ๋ฐ์ดํฐ์
๊ฒฝ๋ก๊ฐ '์๋ ๊ฒ์ฒ๋ผ' ๋ณด์ธ๋ค.
|
| 7 |
+
- gsplat ์ CUDA ํ์ฅ์ด ๋น๋๋ ๋๋๋ฐ ์ํคํ
์ฒ๊ฐ ์ ๋ง์ผ๋ฉด ์ฒซ backward ์์์ผ ํฐ์ง๋ค.
|
| 8 |
+
ํ์ต์ 40๋ถ ๋๋ฆฐ ๋ค์ ์๋ ๊ฒ๋ณด๋ค ์ฌ๊ธฐ์ ์๋ ๊ฒ ๋ซ๋ค.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import os
|
| 14 |
+
import platform
|
| 15 |
+
import shutil
|
| 16 |
+
import subprocess
|
| 17 |
+
import sys
|
| 18 |
+
|
| 19 |
+
_OK, _WARN, _FAIL = "โ", "!", "โ"
|
| 20 |
+
_C = {"โ": "\033[1;32m", "!": "\033[1;33m", "โ": "\033[1;31m"}
|
| 21 |
+
_RESET = "\033[0m"
|
| 22 |
+
|
| 23 |
+
_failures = 0
|
| 24 |
+
_warnings = 0
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _line(status: str, label: str, detail: str = "") -> None:
|
| 28 |
+
global _failures, _warnings
|
| 29 |
+
if status == _FAIL:
|
| 30 |
+
_failures += 1
|
| 31 |
+
elif status == _WARN:
|
| 32 |
+
_warnings += 1
|
| 33 |
+
color = _C.get(status, "")
|
| 34 |
+
print(f" {color}{status}{_RESET} {label:<22} {detail}")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _head(title: str) -> None:
|
| 38 |
+
print(f"\n\033[1;36m{title}\033[0m")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _run(cmd: list[str], timeout: int = 20) -> tuple[int, str]:
|
| 42 |
+
try:
|
| 43 |
+
p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
| 44 |
+
return p.returncode, (p.stdout or "") + (p.stderr or "")
|
| 45 |
+
except FileNotFoundError:
|
| 46 |
+
return 127, "์คํํ์ผ ์์"
|
| 47 |
+
except subprocess.TimeoutExpired:
|
| 48 |
+
return 124, "์๊ฐ ์ด๊ณผ"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# --------------------------------------------------------------------------- ๊ฒ์ฌ๋ค
|
| 52 |
+
def check_runtime() -> None:
|
| 53 |
+
_head("๋ฐํ์")
|
| 54 |
+
_line(_OK, "python", f"{platform.python_version()} ({sys.executable})")
|
| 55 |
+
_line(_OK, "platform", f"{platform.system()} {platform.release()}")
|
| 56 |
+
in_docker = os.path.exists("/.dockerenv")
|
| 57 |
+
in_local = os.environ.get("CONDA_DEFAULT_ENV") == "vkgs"
|
| 58 |
+
if in_docker:
|
| 59 |
+
_line(_OK, "์คํ ํ๊ฒฝ", "Docker ์ปจํ
์ด๋")
|
| 60 |
+
elif in_local:
|
| 61 |
+
_line(_OK, "์คํ ํ๊ฒฝ", f"๋ก์ปฌ micromamba 'vkgs' ({os.environ.get('CONDA_PREFIX', '')})")
|
| 62 |
+
else:
|
| 63 |
+
_line(_WARN, "์คํ ํ๊ฒฝ", "์ปจํ
์ด๋๋ vkgs ํ๊ฒฝ๋ ์๋ โ source scripts/local_env.sh ๋ฅผ ํ๋์ง ํ์ธํ์ธ์")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def check_cuda() -> None:
|
| 67 |
+
_head("CUDA / PyTorch")
|
| 68 |
+
try:
|
| 69 |
+
import torch
|
| 70 |
+
except ImportError as e:
|
| 71 |
+
_line(_FAIL, "torch", f"์ํฌํธ ์คํจ: {e}")
|
| 72 |
+
return
|
| 73 |
+
_line(_OK, "torch", torch.__version__)
|
| 74 |
+
|
| 75 |
+
if not torch.cuda.is_available():
|
| 76 |
+
_line(_FAIL, "cuda", "torch.cuda.is_available() == False "
|
| 77 |
+
"โ --gpus all ์์ด ์ปจํ
์ด๋๋ฅผ ๋์ ๊ฑฐ๋ ๋๋ผ์ด๋ฒ ๋ถ์ผ์น")
|
| 78 |
+
return
|
| 79 |
+
n = torch.cuda.device_count()
|
| 80 |
+
_line(_OK, "cuda", f"{torch.version.cuda}, ์ฅ์น {n}๊ฐ")
|
| 81 |
+
for i in range(n):
|
| 82 |
+
p = torch.cuda.get_device_properties(i)
|
| 83 |
+
cap = f"sm_{p.major}{p.minor}"
|
| 84 |
+
vram = p.total_memory / 1024**3
|
| 85 |
+
note = ""
|
| 86 |
+
if vram < 10:
|
| 87 |
+
note = " โ 8 GB ๊ธ: 3DGS ํ์ต ์ ๋ค์ด์ค์ผ์ผ/๊ฐ์ฐ์์ ์ ์ ํ ํ์"
|
| 88 |
+
_line(_OK, f"gpu[{i}]", f"{p.name} {vram:.1f} GB {cap}{note}")
|
| 89 |
+
|
| 90 |
+
# ์ค์ ๋ก ์ปค๋์ด ๋๋์ง. is_available() ๋ง์ผ๋ก๋ ๋ถ์กฑํ๋ค.
|
| 91 |
+
try:
|
| 92 |
+
a = torch.randn(256, 256, device="cuda")
|
| 93 |
+
torch.cuda.synchronize()
|
| 94 |
+
_line(_OK, "cuda ์ฐ์ฐ", f"๊ฒ์ฆ ํต๊ณผ ({float((a @ a.T).sum()):.3e})")
|
| 95 |
+
except Exception as e:
|
| 96 |
+
_line(_FAIL, "cuda ์ฐ์ฐ", f"{type(e).__name__}: {e}")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def check_gsplat() -> None:
|
| 100 |
+
_head("3DGS ํ์ต ๋ฐฑ์๋")
|
| 101 |
+
try:
|
| 102 |
+
import gsplat
|
| 103 |
+
except ImportError as e:
|
| 104 |
+
_line(_FAIL, "gsplat", f"์ํฌํธ ์คํจ: {e}")
|
| 105 |
+
return
|
| 106 |
+
_line(_OK, "gsplat", getattr(gsplat, "__version__", "?"))
|
| 107 |
+
try:
|
| 108 |
+
import torch
|
| 109 |
+
from gsplat import rasterization
|
| 110 |
+
if not torch.cuda.is_available():
|
| 111 |
+
_line(_WARN, "gsplat ๋์คํฐํ", "CUDA ๊ฐ ์์ด ๊ฑด๋๋")
|
| 112 |
+
return
|
| 113 |
+
# ๊ฐ์ฐ์์ 1๊ฐ์ง๋ฆฌ ์ต์ ๋์คํฐํ. CUDA ํ์ฅ์ด ์ด GPU ์ํคํ
์ฒ๋ก
|
| 114 |
+
# ์ค์ ๋น๋๋๋์ง ์ฌ๊ธฐ์ ๋๋ฌ๋๋ค.
|
| 115 |
+
dev = "cuda"
|
| 116 |
+
means = torch.zeros(1, 3, device=dev); means[0, 2] = 5.0
|
| 117 |
+
quats = torch.tensor([[1.0, 0, 0, 0]], device=dev)
|
| 118 |
+
scales = torch.full((1, 3), 0.3, device=dev)
|
| 119 |
+
opac = torch.ones(1, device=dev)
|
| 120 |
+
colors = torch.ones(1, 3, device=dev)
|
| 121 |
+
viewmat = torch.eye(4, device=dev)[None]
|
| 122 |
+
K = torch.tensor([[[100.0, 0, 32.0], [0, 100.0, 32.0], [0, 0, 1.0]]], device=dev)
|
| 123 |
+
img, _, _ = rasterization(means, quats, scales, opac, colors, viewmat, K, 64, 64)
|
| 124 |
+
torch.cuda.synchronize()
|
| 125 |
+
_line(_OK, "gsplat ๋์คํฐํ", f"์ถ๋ ฅ {tuple(img.shape)}, ์ต๋๊ฐ {float(img.max()):.3f}")
|
| 126 |
+
except Exception as e:
|
| 127 |
+
_line(_FAIL, "gsplat ๋์คํฐํ", f"{type(e).__name__}: {e}")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def check_vulkan() -> None:
|
| 131 |
+
_head("Vulkan")
|
| 132 |
+
# ํธ์คํธ๋ /usr/share (๋๋ผ์ด๋ฒ ํจํค์ง), ์ปจํ
์ด๋ ์ด๋ฏธ์ง๋ /etc ์ ๋๋ค (docker/Dockerfile). VK_DRIVER_FILES ๊ฐ ์ฐ์ .
|
| 133 |
+
cands = [f for f in os.environ.get("VK_DRIVER_FILES", "").split(":") if f]
|
| 134 |
+
for icd_dir in ("/usr/share/vulkan/icd.d", "/etc/vulkan/icd.d"):
|
| 135 |
+
if os.path.isdir(icd_dir):
|
| 136 |
+
cands += [f"{icd_dir}/{f}" for f in sorted(os.listdir(icd_dir))]
|
| 137 |
+
nvidia_icd = [f for f in cands if "nvidia" in os.path.basename(f).lower() and os.path.isfile(f)]
|
| 138 |
+
if nvidia_icd:
|
| 139 |
+
_line(_OK, "NVIDIA ICD", nvidia_icd[0])
|
| 140 |
+
else:
|
| 141 |
+
_line(_FAIL, "NVIDIA ICD", "nvidia ICD ๊ฐ ์์ต๋๋ค (/usr/share ยท /etc vulkan/icd.d) โ "
|
| 142 |
+
"NVIDIA_DRIVER_CAPABILITIES ์ graphics ๊ฐ ๋น ์ก์ ๊ฐ๋ฅ์ฑ์ด ํฝ๋๋ค")
|
| 143 |
+
|
| 144 |
+
if not shutil.which("vulkaninfo"):
|
| 145 |
+
_line(_WARN, "vulkaninfo", "์ค์น๋์ง ์์ (vulkan-tools)")
|
| 146 |
+
return
|
| 147 |
+
rc, out = _run(["vulkaninfo", "--summary"])
|
| 148 |
+
if rc != 0:
|
| 149 |
+
_line(_FAIL, "vulkaninfo", f"์ข
๋ฃ์ฝ๋ {rc}: {out.strip().splitlines()[-1][:120] if out.strip() else ''}")
|
| 150 |
+
return
|
| 151 |
+
|
| 152 |
+
gpus = [l.strip() for l in out.splitlines() if "deviceName" in l]
|
| 153 |
+
api = next((l.strip() for l in out.splitlines() if "apiVersion" in l), "")
|
| 154 |
+
if gpus:
|
| 155 |
+
_line(_OK, "Vulkan ์ฅ์น", f"{len(gpus)}๊ฐ")
|
| 156 |
+
for g in gpus:
|
| 157 |
+
_line(_OK, "", g)
|
| 158 |
+
if api:
|
| 159 |
+
_line(_OK, "apiVersion", api.split("=")[-1].strip())
|
| 160 |
+
else:
|
| 161 |
+
_line(_FAIL, "Vulkan ์ฅ์น", "0๊ฐ โ ICD ๋ ์๋๋ฐ ๋ฌผ๋ฆฌ์ฅ์น๊ฐ ์ ์กํ๋๋ค")
|
| 162 |
+
|
| 163 |
+
for tool, pkg in (("glslangValidator", "glslang-tools"), ("glslc", "glslc")):
|
| 164 |
+
if shutil.which(tool):
|
| 165 |
+
_line(_OK, f"์
ฐ์ด๋ ์ปดํ์ผ๋ฌ", tool)
|
| 166 |
+
break
|
| 167 |
+
else:
|
| 168 |
+
_line(_FAIL, "์
ฐ์ด๋ ์ปดํ์ผ๋ฌ", "glslangValidator / glslc ๋ ๋ค ์์")
|
| 169 |
+
|
| 170 |
+
if os.environ.get("DISPLAY"):
|
| 171 |
+
_line(_OK, "DISPLAY", os.environ["DISPLAY"] + " (์ธํฐ๋ํฐ๋ธ ๋ทฐ์ด ๊ฐ๋ฅ)")
|
| 172 |
+
else:
|
| 173 |
+
_line(_WARN, "DISPLAY", "์์ โ ํค๋๋ฆฌ์ค. ์คํ๋ผ์ธ ๋ฐฐ์น ๋ ๋๋ง์ ์ ์ ๋์ํฉ๋๋ค")
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def check_dataset() -> None:
|
| 177 |
+
_head("nuScenes ๋ฐ์ดํฐ์
")
|
| 178 |
+
try:
|
| 179 |
+
from nusim.paths import dataset
|
| 180 |
+
ds = dataset()
|
| 181 |
+
except Exception as e:
|
| 182 |
+
_line(_FAIL, "๋ฐ์ดํฐ์
๋ฃจํธ", str(e).splitlines()[0])
|
| 183 |
+
return
|
| 184 |
+
_line(_OK, "๋ฃจํธ", str(ds.root))
|
| 185 |
+
for name, (exists, path) in ds.check().items():
|
| 186 |
+
_line(_OK if exists else _FAIL, name, str(path) if not exists else "")
|
| 187 |
+
|
| 188 |
+
# NFS ๋ ๋ง์ดํธ๋ผ ๋ณด์ฌ๋ ์๋ต์ด ์ฃฝ์ด ์์ ์ ์๋ค. ์ค์ ๋ก ํ ํ์ผ์ ์ฝ์ด ๋ณธ๋ค.
|
| 189 |
+
try:
|
| 190 |
+
import time
|
| 191 |
+
t = time.time()
|
| 192 |
+
(ds.meta / "scene.json").read_bytes()
|
| 193 |
+
_line(_OK, "์ฝ๊ธฐ ์ํ", f"scene.json {(time.time()-t)*1000:.0f} ms")
|
| 194 |
+
except Exception as e:
|
| 195 |
+
_line(_FAIL, "์ฝ๊ธฐ ์ํ", f"{type(e).__name__}: {e}")
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def check_index() -> None:
|
| 199 |
+
_head("์ฌ ์ธ๋ฑ์ค")
|
| 200 |
+
try:
|
| 201 |
+
from nusim.data.index import load_catalog
|
| 202 |
+
cat = load_catalog()
|
| 203 |
+
except FileNotFoundError:
|
| 204 |
+
_line(_WARN, "์นดํ๋ก๊ทธ", "์์ง ์์ โ nusim index build ๋ฅผ ์คํํ์ธ์")
|
| 205 |
+
return
|
| 206 |
+
except Exception as e:
|
| 207 |
+
_line(_FAIL, "์นดํ๋ก๊ทธ", f"{type(e).__name__}: {e}")
|
| 208 |
+
return
|
| 209 |
+
import collections
|
| 210 |
+
by = collections.Counter(s["location"] for s in cat["scenes"])
|
| 211 |
+
total = sum(s["n_images"] for s in cat["scenes"])
|
| 212 |
+
_line(_OK, "์นดํ๋ก๊ทธ", f"์ฌ {len(cat['scenes'])}๊ฐ / ์ด๋ฏธ์ง {total:,}์ฅ (์์ฑ {cat['built_at']})")
|
| 213 |
+
for loc, n in sorted(by.items()):
|
| 214 |
+
_line(_OK, "", f"{loc:26s} {n:4d} ์ฌ")
|
| 215 |
+
if cat.get("dataset_root"):
|
| 216 |
+
from nusim.paths import nuscenes_root
|
| 217 |
+
cur = str(nuscenes_root())
|
| 218 |
+
if cat["dataset_root"] != cur:
|
| 219 |
+
_line(_WARN, "๋ฃจํธ ๋ถ์ผ์น",
|
| 220 |
+
f"์ธ๋ฑ์ค๋ {cat['dataset_root']} ๊ธฐ์ค์ผ๋ก ๋ง๋ค์ด์ก๋๋ฐ ํ์ฌ๋ {cur} ์
๋๋ค. "
|
| 221 |
+
"ํ์ผ ์๋๊ฒฝ๋ก๋ ๊ฐ์ผ๋ฏ๋ก ๋ณดํต ๋ฌดํดํ์ง๋ง, ์ด์ํ๋ฉด --force ๋ก ์ฌ์์ฑํ์ธ์.")
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def check_workspace() -> None:
|
| 225 |
+
_head("์์
๊ณต๊ฐ")
|
| 226 |
+
from nusim.paths import cache_dir, output_dir, workspace_root
|
| 227 |
+
_line(_OK, "workspace", str(workspace_root()))
|
| 228 |
+
for label, d in (("cache", cache_dir()), ("outputs", output_dir())):
|
| 229 |
+
writable = os.access(d, os.W_OK)
|
| 230 |
+
_line(_OK if writable else _FAIL, label,
|
| 231 |
+
str(d) if writable else f"{d} โ ์ฐ๊ธฐ ๋ถ๊ฐ. uid ๋ถ์ผ์น์ผ ์ ์์ต๋๋ค (HOST_UID/.env ํ์ธ)")
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def main() -> int:
|
| 235 |
+
print("\033[1mvkgs-sim ํ๊ฒฝ ์ง๋จ\033[0m")
|
| 236 |
+
for fn in (check_runtime, check_cuda, check_gsplat, check_vulkan,
|
| 237 |
+
check_dataset, check_index, check_workspace):
|
| 238 |
+
try:
|
| 239 |
+
fn()
|
| 240 |
+
except Exception as e: # ํ ํญ๋ชฉ์ด ํฐ์ ธ๋ ๋๋จธ์ง๋ ๋ด์ผ ํ๋ค
|
| 241 |
+
_line(_FAIL, fn.__name__, f"๊ฒ์ฌ ์์ฒด๊ฐ ์คํจ: {type(e).__name__}: {e}")
|
| 242 |
+
|
| 243 |
+
print()
|
| 244 |
+
if _failures:
|
| 245 |
+
print(f"\033[1;31m{_failures}๊ฐ ์คํจ\033[0m, {_warnings}๊ฐ ๊ฒฝ๊ณ ")
|
| 246 |
+
return 1
|
| 247 |
+
if _warnings:
|
| 248 |
+
print(f"\033[1;33m๋ฌธ์ ์์ ({_warnings}๊ฐ ๊ฒฝ๊ณ )\033[0m")
|
| 249 |
+
return 0
|
| 250 |
+
print("\033[1;32m๋ชจ๋ ์ ์\033[0m")
|
| 251 |
+
return 0
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
if __name__ == "__main__":
|
| 255 |
+
raise SystemExit(main())
|
src/nusim/nusim/cli/main.py
ADDED
|
@@ -0,0 +1,1487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""nusim โ ํ๋ก์ ํธ ์ปค๋งจ๋๋ผ์ธ.
|
| 2 |
+
|
| 3 |
+
typer/rich ๊ฐ ์๋๋ผ argparse ๋ก ์ด ์ด์ : ์ด CLI ๋ ์ปจํ
์ด๋ ๋ฐ ํธ์คํธ์์๋
|
| 4 |
+
(์์กด์ฑ ์ค์น ์์ด) ๋์์ผ ์ง๋จ์ ์ธ๋ชจ๊ฐ ์๋ค. ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ด๋ค.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import logging
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
LOG = logging.getLogger("nusim.cli")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _cmd_index_build(a: argparse.Namespace) -> int:
|
| 19 |
+
from nusim import SINGAPORE_MAPS
|
| 20 |
+
from nusim.data.index import build_index
|
| 21 |
+
|
| 22 |
+
locations = a.locations or list(SINGAPORE_MAPS)
|
| 23 |
+
build_index(
|
| 24 |
+
locations=locations,
|
| 25 |
+
include_sweeps=not a.no_sweeps,
|
| 26 |
+
force=a.force,
|
| 27 |
+
)
|
| 28 |
+
return 0
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _cmd_index_list(a: argparse.Namespace) -> int:
|
| 32 |
+
from nusim.data.index import load_catalog
|
| 33 |
+
|
| 34 |
+
cat = load_catalog()
|
| 35 |
+
rows = cat["scenes"]
|
| 36 |
+
if a.location:
|
| 37 |
+
rows = [r for r in rows if r["location"] == a.location]
|
| 38 |
+
if a.grep:
|
| 39 |
+
g = a.grep.lower()
|
| 40 |
+
rows = [r for r in rows if g in r["description"].lower() or g in r["name"]]
|
| 41 |
+
if a.limit:
|
| 42 |
+
rows = rows[: a.limit]
|
| 43 |
+
|
| 44 |
+
print(f"{'scene':<12} {'location':<26} {'imgs':>6} {'sec':>6} description")
|
| 45 |
+
print("-" * 100)
|
| 46 |
+
for r in rows:
|
| 47 |
+
print(f"{r['name']:<12} {r['location']:<26} {r['n_images']:>6} "
|
| 48 |
+
f"{r['duration_s']:>6.1f} {r['description'][:44]}")
|
| 49 |
+
print(f"\n{len(rows)}๊ฐ ์ฌ / ์ด๋ฏธ์ง {sum(r['n_images'] for r in rows):,}์ฅ")
|
| 50 |
+
return 0
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _cmd_scene_show(a: argparse.Namespace) -> int:
|
| 54 |
+
from nusim.data.index import load_scene
|
| 55 |
+
from nusim.paths import dataset
|
| 56 |
+
|
| 57 |
+
si = load_scene(a.name)
|
| 58 |
+
ds = dataset()
|
| 59 |
+
print(f"\n\033[1m{si.name}\033[0m {si.location}")
|
| 60 |
+
print(f" {si.description}")
|
| 61 |
+
print(f" ์ฐจ๋ {si.vehicle} / ์ดฌ์์ผ {si.date_captured} / {si.duration_s():.1f}์ด")
|
| 62 |
+
print(f" ํคํ๋ ์ {si.nbr_samples}๊ฐ / ์ ์ฒด ์ด๋ฏธ์ง {si.n_images:,}์ฅ\n")
|
| 63 |
+
print(f" {'camera':<17} {'frames':>7} {'keyfr':>6} {'Hz':>6} {'fx':>8} {'HFOV':>6}")
|
| 64 |
+
for cam, frames in si.frames.items():
|
| 65 |
+
if not frames:
|
| 66 |
+
continue
|
| 67 |
+
span = (frames[-1].timestamp - frames[0].timestamp) / 1e6
|
| 68 |
+
hz = (len(frames) - 1) / span if span > 0 else 0.0
|
| 69 |
+
c = si.calib[cam]
|
| 70 |
+
import math
|
| 71 |
+
fx = c.intrinsic[0][0]
|
| 72 |
+
hfov = math.degrees(2 * math.atan(c.width / (2 * fx)))
|
| 73 |
+
print(f" {cam:<17} {len(frames):>7} {len(si.keyframes(cam)):>6} {hz:>6.1f} {fx:>8.1f} {hfov:>5.1f}ยฐ")
|
| 74 |
+
|
| 75 |
+
f0 = si.frames["CAM_FRONT"][0]
|
| 76 |
+
print(f"\n ์ฒซ ํ๋ ์: {f0.filename}")
|
| 77 |
+
print(f" ์กด์ฌ ์ฌ๋ถ: {f0.path(ds).exists()}")
|
| 78 |
+
return 0
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _cmd_recon_init(a: argparse.Namespace) -> int:
|
| 82 |
+
from nusim.data.index import load_scene
|
| 83 |
+
from nusim.io.ply import write_ply
|
| 84 |
+
from nusim.paths import dataset, output_dir
|
| 85 |
+
from nusim.recon.init import InitConfig, init_gaussians
|
| 86 |
+
|
| 87 |
+
import json
|
| 88 |
+
|
| 89 |
+
from nusim.recon.init import init_meta
|
| 90 |
+
|
| 91 |
+
cfg = InitConfig(voxel=a.voxel, max_points=a.max_points)
|
| 92 |
+
print(f"\033[1m{a.name}\033[0m ์ด๊ธฐํ")
|
| 93 |
+
g = init_gaussians(load_scene(a.name), dataset(), cfg)
|
| 94 |
+
path = write_ply(output_dir() / "gs" / a.name / "init.ply", g)
|
| 95 |
+
path.with_suffix(".json").write_text(json.dumps(init_meta(cfg), indent=2))
|
| 96 |
+
print(f" โ {path}")
|
| 97 |
+
return 0
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _train_config(a: argparse.Namespace, **extra):
|
| 101 |
+
"""recon train ์ต์
โ TrainConfig (๋จ์ผ ์ฌยทํ์ผ ๊ณต์ฉ)."""
|
| 102 |
+
from nusim.recon.train import TrainConfig
|
| 103 |
+
|
| 104 |
+
mcmc = a.strategy == "mcmc"
|
| 105 |
+
return TrainConfig(iters=a.iters, max_gaussians=a.max_gaussians, depth_lambda=a.depth_lambda,
|
| 106 |
+
depth_scale=a.depth_scale, strategy=a.strategy, sky=a.sky, objects=a.objects,
|
| 107 |
+
downscale=a.downscale, frame_stride=a.stride, keyframes_only=a.keyframes_only,
|
| 108 |
+
aniso_reg=a.aniso_reg, aniso_max=a.aniso_max,
|
| 109 |
+
ground_lambda=getattr(a, "ground_lambda", 0.0),
|
| 110 |
+
opacity_reg=a.opacity_reg if a.opacity_reg is not None else (0.01 if mcmc else 0.0),
|
| 111 |
+
scale_reg=a.scale_reg if a.scale_reg is not None else (0.01 if mcmc else 0.0),
|
| 112 |
+
# MCMC ๋ cap ์์์ ์ฌ๋ฐฐ์น๋ง ํ๋ฏ๋ก ์ค๋ ์ผ ๋๋ค (gsplat ๊ธฐ๋ณธ 25k/30k โ 0.8)
|
| 113 |
+
refine_stop=a.refine_stop or (int(0.8 * a.iters) if mcmc else min(15_000, a.iters // 2)),
|
| 114 |
+
eval_every=a.eval_every, absgrad=a.absgrad,
|
| 115 |
+
grow_grad2d=a.grow_grad2d or (0.0008 if a.absgrad else 0.0002),
|
| 116 |
+
lr_camera=0.0 if a.no_camera_adjust else TrainConfig.lr_camera,
|
| 117 |
+
time_window=a.time_window, **extra, image_root=str(Path(a.image_root).resolve()) if a.image_root else None)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _train_tile(a: argparse.Namespace, out: Path) -> int:
|
| 121 |
+
"""์ฌ๋ฌ ์ฃผํ์ ํ ๋ชจ๋ธ๋ก (recon.tile). a.name ์ ๋ฌด์ํ๊ณ ํ์ผ์ anchor ๋ฅผ ์ด๋ค."""
|
| 122 |
+
import json
|
| 123 |
+
|
| 124 |
+
from nusim.io.ply import read_ply, write_ply
|
| 125 |
+
from nusim.log import run_log
|
| 126 |
+
from nusim.paths import dataset
|
| 127 |
+
from nusim.recon.init import InitConfig, init_meta
|
| 128 |
+
from nusim.recon.tile import ALIGN_VERSION, Tile, tile_init, tile_lidar, tile_scenes, tile_views
|
| 129 |
+
from nusim.recon.train import train
|
| 130 |
+
|
| 131 |
+
if a.objects == "vehicles":
|
| 132 |
+
raise SystemExit("ํ์ผ ํ์ต์ ์์ง --objects vehicles ๋ฅผ ์ง์ํ์ง ์๋๋ค (์ฃผํ๋ง๋ค ์ฐจ๋์ด ๋ค๋ฅด๋ค) โ agents ๋ก")
|
| 133 |
+
tile, ds = Tile.load(a.tile), dataset()
|
| 134 |
+
if a.name != tile.anchor:
|
| 135 |
+
print(f" ํ์ผ {tile.name} ์ ๊ธฐ์ค ์ฌ์ {tile.anchor} โ {a.name} ๋์ ๊ทธ๊ฑธ ์ด๋ค")
|
| 136 |
+
scenes = tile_scenes(tile)
|
| 137 |
+
icfg = InitConfig(sky_points=0 if a.sky else 30_000, objects=a.objects)
|
| 138 |
+
init_ply = out / "init.ply"
|
| 139 |
+
want = {**init_meta(icfg), "tile": json.loads(json.dumps(
|
| 140 |
+
{"train": tile.train, "center": tile.center.tolist(), "half_m": tile.half_m,
|
| 141 |
+
"align": ALIGN_VERSION if tile.align else 0}))}
|
| 142 |
+
meta_path = init_ply.with_suffix(".json")
|
| 143 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 144 |
+
if not init_ply.exists() or not meta_path.exists() or json.loads(meta_path.read_text()) != want:
|
| 145 |
+
write_ply(init_ply, tile_init(tile, scenes, ds, icfg, max_total=icfg.max_points))
|
| 146 |
+
meta_path.write_text(json.dumps(want, indent=2))
|
| 147 |
+
init = read_ply(init_ply)
|
| 148 |
+
views = tile_views(scenes, ds, init.origin, objects=a.objects, downscale=a.downscale,
|
| 149 |
+
frame_stride=a.stride, keyframes_only=a.keyframes_only)
|
| 150 |
+
pts = None
|
| 151 |
+
if a.depth_lambda > 0:
|
| 152 |
+
from nusim.recon.depth import lidar_depth_maps
|
| 153 |
+
|
| 154 |
+
pts = tile_lidar(scenes, ds, InitConfig(objects=a.objects), init.origin)
|
| 155 |
+
lidar_depth_maps(pts, views.train, scale=a.depth_scale)
|
| 156 |
+
if a.sky:
|
| 157 |
+
# ํ๋ = ํ๋ธ๋งต + ํ๋ ํฝ์
์์ ์คํ๋ซ ฮฑโ0. ๋ ๋ง๋ค ํ๋ ๋ฐ๊ธฐ๊ฐ ๋ฌ๋ผ, ํ๋์ ๋ฐ๋ก ๋์ง ์์ผ๋ฉด ๊ฐ์ฐ์์์ด ๋ถํ์ด
|
| 158 |
+
# ํ๋์ ๋ฎ๋ค ์นด๋ฉ๋ผ๊น์ง ๊ฐ์๋ค (6์ฃผํ 15k: 10 m ๋๋ ๊ฐ์ฐ์์ 685๊ฐ, ์๊ธฐ ์ฃผํ ํ๊ฐ 12.7 dB)
|
| 159 |
+
from nusim.recon.sky import sky_masks
|
| 160 |
+
|
| 161 |
+
for k, s in enumerate(scenes):
|
| 162 |
+
sky_masks([v for v in views.train if v.cam_id // 6 == k], ds, sky_mask_cache(s.name, ds))
|
| 163 |
+
cfg = _train_config(a, tile=tile.name, reg_visible=True)
|
| 164 |
+
with run_log(out / "run.log"):
|
| 165 |
+
LOG.info("ํ์ผ ํ์ต %s (%d์ฃผํ: %s, ๋บ ์ฃผํ %s) โ %s", tile.name, len(scenes), ", ".join(tile.train),
|
| 166 |
+
", ".join(tile.heldout) or "์์", out)
|
| 167 |
+
train(views, init, out, cfg, depth_points=pts)
|
| 168 |
+
return 0
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _cmd_recon_tile_eval(a: argparse.Namespace) -> int:
|
| 172 |
+
import json
|
| 173 |
+
|
| 174 |
+
from nusim.log import run_log
|
| 175 |
+
from nusim.paths import dataset, output_dir
|
| 176 |
+
from nusim.recon.tile import Tile, tile_eval
|
| 177 |
+
|
| 178 |
+
tile = Tile.load(a.tile)
|
| 179 |
+
out = Path(a.out) if a.out else output_dir() / "tiles" / tile.name
|
| 180 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 181 |
+
with run_log(out / "eval.log"):
|
| 182 |
+
res = tile_eval(tile, [Path(d) for d in a.dir], dataset(), stride=a.stride, save_dir=out / "renders")
|
| 183 |
+
(out / "eval.json").write_text(json.dumps(res, indent=2, ensure_ascii=False))
|
| 184 |
+
print(f"\n ๋บ ์ฃผํ {', '.join(tile.heldout)} ์ ์ค์ ์ฌ์ง (์ ์ํ ๋ง์ถค ํ, ์ฌ๋ยท์ฐจ๋ ๊ฐ๋ฆผ)")
|
| 185 |
+
print(f" ์ฐจ์ ๋ณ๊ฒฝ ์์ = {tile.anchor} ์ ๊ฐ์ ๋ฐฉํฅ ์นด๋ฉ๋ผ๊ฐ ์ 6 m ์์ ์๋ ๋ทฐ")
|
| 186 |
+
for d, r in res["models"].items():
|
| 187 |
+
dist = " ".join(f"{k} {v['psnr']:.2f}/{v['ssim']:.3f}({v['n']})" for k, v in r["by_dist"].items() if v["n"])
|
| 188 |
+
print(f" {Path(d).parent.name + '/' + Path(d).name:<36} PSNR {r['psnr']:.2f} SSIM {r['ssim']:.4f} "
|
| 189 |
+
f"| ์ ๊ฑฐ๋ฆฌ๋ณ PSNR/SSIM: {dist} | ์ ์ฒด {r['all']['psnr']:.2f}")
|
| 190 |
+
print(f" โ {out / 'eval.json'}")
|
| 191 |
+
return 0
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def _cmd_recon_train(a: argparse.Namespace) -> int:
|
| 195 |
+
from nusim.data.index import load_scene
|
| 196 |
+
from nusim.io.ply import read_ply, write_ply
|
| 197 |
+
from nusim.paths import dataset, output_dir
|
| 198 |
+
from nusim.recon.dataset import build_views
|
| 199 |
+
from nusim.recon.init import init_gaussians
|
| 200 |
+
from nusim.recon.train import TrainConfig, train
|
| 201 |
+
|
| 202 |
+
out = Path(a.out) if a.out else output_dir() / "gs" / a.name
|
| 203 |
+
if a.tile:
|
| 204 |
+
return _train_tile(a, out)
|
| 205 |
+
scene, ds = load_scene(a.name), dataset()
|
| 206 |
+
if a.image_root: # ์นด๋ฉ๋ผ ์ด๋ฏธ์ง๋ง ๋ค๋ฅธ ๊ณณ์์ (๋ฐคยท๋น โ ๋ฎ ๋ณํ ๋ฑ, ๊ฐ์ ์๋ ๊ฒฝ๋ก)
|
| 207 |
+
from dataclasses import replace as _replace
|
| 208 |
+
|
| 209 |
+
ds = _replace(ds, image_root=Path(a.image_root))
|
| 210 |
+
print(f" ์นด๋ฉ๋ผ ์ด๋ฏธ์ง ๋ฃจํธ {ds.images}")
|
| 211 |
+
if a.time_window:
|
| 212 |
+
t0, t1 = (float(x) for x in a.time_window.split(":"))
|
| 213 |
+
scene = scene.window(t0, t1)
|
| 214 |
+
print(f" ์๊ฐ ๊ตฌ๊ฐ {t0:g}~{t1:g} ์ด โ ์นด๋ฉ๋ผ {scene.n_images}์ฅ, ๋ผ์ด๋ค {len(scene.lidar)}์ฅ")
|
| 215 |
+
# ์ด๊ธฐํ๋ ๊ฒฐ๊ณผ ๋๋ ํฐ๋ฆฌ๋ง๋ค ๋ฐ๋ก ๋๋ค โ ์ค์ (sweep ์ฌ๋ถ ๋ฑ)์ด ๋ค๋ฅธ ์คํ์ด ์๋ก์ init ์ ์ฐ์ง ์๊ฒ
|
| 216 |
+
init_ply = out / "init.ply"
|
| 217 |
+
print(f"\033[1m{a.name}\033[0m {scene.description}")
|
| 218 |
+
import json
|
| 219 |
+
|
| 220 |
+
from nusim.recon.init import InitConfig, init_meta
|
| 221 |
+
|
| 222 |
+
# ํ๋์ ํ๋ธ๋งต์ผ๋ก ๋ถ๋ฆฌํ๋ฉด ๋จผ ๊ตฌ๋ฉด ๊ฐ์ฐ์์(ํ๋ ๊ตฌ)์ ํ์ ์๋ค โ ์คํ๋ ค floater ์์ธ
|
| 223 |
+
# vehicles: ๋ฐฐ๊ฒฝ ์ด๊ธฐํยท๊น์ด ์ง๋๋ ์ฐจ๋๊น์ง ๋บ ๋ผ์ด๋ค๋ก (์ฐจ๋์ ๊ฐ์ฒด ๊ฐ์ฐ์์์ด ๋ฐ๋ก ๋งก๋๋ค โ recon.objects)
|
| 224 |
+
bg_objects = "agents" if a.objects == "vehicles" else a.objects
|
| 225 |
+
icfg = InitConfig(sky_points=0 if a.sky else 30_000, objects=bg_objects)
|
| 226 |
+
meta_path = init_ply.with_suffix(".json")
|
| 227 |
+
old_meta = json.loads(meta_path.read_text()) if meta_path.exists() else None
|
| 228 |
+
want_meta = {**init_meta(icfg), **({"time_window": a.time_window} if a.time_window else {}),
|
| 229 |
+
**({"image_root": str(ds.images)} if a.image_root else {})} # ์์ ์ด ์ด๋ฏธ์ง์์
|
| 230 |
+
if not init_ply.exists() or old_meta != want_meta:
|
| 231 |
+
if init_ply.exists():
|
| 232 |
+
print(" init.ply ๋ฅผ ๋ค์ ๋ง๋ญ๋๋ค (์ค์ ๋๋ ์ด๊ธฐํ ์๊ณ ๋ฆฌ์ฆ ํ์ด ๋ค๋ฆ)")
|
| 233 |
+
write_ply(init_ply, init_gaussians(scene, ds, icfg))
|
| 234 |
+
meta_path.write_text(json.dumps(want_meta, indent=2))
|
| 235 |
+
init = read_ply(init_ply)
|
| 236 |
+
views = build_views(scene, ds, init.origin, downscale=a.downscale,
|
| 237 |
+
frame_stride=a.stride, keyframes_only=a.keyframes_only, objects=a.objects)
|
| 238 |
+
if a.depth_lambda > 0:
|
| 239 |
+
from nusim.recon.depth import lidar_depth_maps
|
| 240 |
+
from nusim.recon.init import InitConfig, accumulate_lidar
|
| 241 |
+
|
| 242 |
+
pts = accumulate_lidar(scene, ds, InitConfig(objects=bg_objects)) - init.origin # ๊ฐ๋ฆฐ ๊ฐ์ฒด์ ์ ์ ๋บ๋ค
|
| 243 |
+
lidar_depth_maps(pts, views.train, scale=a.depth_scale)
|
| 244 |
+
else:
|
| 245 |
+
pts = None
|
| 246 |
+
if a.sky:
|
| 247 |
+
from nusim.paths import cache_dir
|
| 248 |
+
from nusim.recon.sky import sky_masks
|
| 249 |
+
|
| 250 |
+
sky_masks(views.train, ds, sky_mask_cache(a.name, ds))
|
| 251 |
+
cfg = _train_config(a)
|
| 252 |
+
from nusim.log import run_log
|
| 253 |
+
|
| 254 |
+
with run_log(out / "run.log"):
|
| 255 |
+
LOG.info("ํ์ต ์์ %s โ %s (strategy=%s, sky=%s, depth_lambda=%s, objects=%s)", a.name, out, a.strategy,
|
| 256 |
+
a.sky, a.depth_lambda, a.objects)
|
| 257 |
+
objs = obj_init = None
|
| 258 |
+
if a.objects == "vehicles":
|
| 259 |
+
from nusim.recon.objects import ObjectSet, init_objects
|
| 260 |
+
|
| 261 |
+
objs = ObjectSet.from_scene(scene, init.origin)
|
| 262 |
+
obj_init = init_objects(objs, scene, ds, icfg, log=print)
|
| 263 |
+
gmask = None
|
| 264 |
+
if cfg.ground_lambda > 0:
|
| 265 |
+
from nusim.recon.ground import ground_mask
|
| 266 |
+
|
| 267 |
+
gmask = ground_mask(init.means, scene, init.origin, ds)
|
| 268 |
+
train(views, init, out, cfg, depth_points=pts, objects=objs, obj_init=obj_init, ground=gmask)
|
| 269 |
+
return 0
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def _model_views(d: Path) -> dict:
|
| 273 |
+
"""๋ชจ๋ธ์ ํ์ตํ ๋ทฐ ์งํฉ ์ค์ (report.json ์ config). ์ ๋ชจ๋ธ์ ๊ธฐ๋ณธ๊ฐ."""
|
| 274 |
+
import json
|
| 275 |
+
|
| 276 |
+
out = {"downscale": 2, "frame_stride": 1, "keyframes_only": False}
|
| 277 |
+
try:
|
| 278 |
+
cfg = json.loads((d / "report.json").read_text())["config"]
|
| 279 |
+
out.update({k: cfg[k] for k in out if k in cfg})
|
| 280 |
+
except (OSError, KeyError, ValueError):
|
| 281 |
+
pass
|
| 282 |
+
return out
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _model_data(name: str, d: Path):
|
| 286 |
+
"""(์ฌ, ๋ฐ์ดํฐ ๊ฒฝ๋ก) โ ๋ชจ๋ธ์ ํ์ตํ ๋ฐ์ดํฐ ๋ฒ์ ๊ทธ๋๋ก (report.json config ์ time_windowยทimage_root).
|
| 287 |
+
ํ๊ฐยท์ฆ๋ฅยท์ ๋ฆฌยทํ๋ ํ์ยท์ฌ์ดํด์ด ๋ชจ๋ ์ด๊ฑธ๋ก โ ๊ตฌ๊ฐ ๋ชจ๋ธ์ ์ฌ ์ ์ฒด ๋ทฐ๋ฅผ ์ฃผ๋ฉด ์ ๋ณด์ ํฌ๊ธฐ๊ฐ ์ ๋ง๊ณ ,
|
| 288 |
+
๋ณํ ์์์ผ๋ก ํ์ตํ ๋ชจ๋ธ์ ์๋ณธ(๋ฐค) ์ฌ์ง์ผ๋ก ํ๊ฐํ๊ฒ ๋๋ค (2026-10-05 ๊ฒํ )."""
|
| 289 |
+
import json
|
| 290 |
+
from dataclasses import replace
|
| 291 |
+
|
| 292 |
+
from nusim.data.index import load_scene
|
| 293 |
+
from nusim.paths import dataset
|
| 294 |
+
|
| 295 |
+
try:
|
| 296 |
+
cfg = json.loads((d / "report.json").read_text())["config"]
|
| 297 |
+
except (OSError, KeyError, ValueError):
|
| 298 |
+
cfg = {}
|
| 299 |
+
scene, ds = load_scene(name), dataset()
|
| 300 |
+
if cfg.get("time_window"):
|
| 301 |
+
t0, t1 = (float(x) for x in cfg["time_window"].split(":"))
|
| 302 |
+
scene = scene.window(t0, t1)
|
| 303 |
+
if cfg.get("image_root"):
|
| 304 |
+
ds = replace(ds, image_root=Path(cfg["image_root"]))
|
| 305 |
+
return scene, ds
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def sky_mask_cache(name: str, ds) -> Path:
|
| 309 |
+
"""ํ๋ ๋ง์คํฌ ์บ์ โ ์ด๋ฏธ์ง ๋ฃจํธ๋ง๋ค ๋ฐ๋ก (์๋ณธ ๋ฐค ์ฌ์ง๊ณผ ๋ณํํ ๋ฎ ์์์ ๋ง์คํฌ๋ฅผ ์์ง ์๊ฒ)."""
|
| 310 |
+
from nusim.paths import cache_dir
|
| 311 |
+
|
| 312 |
+
tag = name if ds.image_root is None else f"{name}@{Path(ds.image_root).name}"
|
| 313 |
+
return cache_dir() / "sky_masks" / tag
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def _model_objects(d: Path) -> str:
|
| 317 |
+
"""๋ชจ๋ธ์ด ํ์ต ๋ ๊ฐ๋ฆฐ ๊ฐ์ฒด ์ข
๋ฅ (report.json ์ config.objects). ์ ๋ชจ๋ธ์ dynamic."""
|
| 318 |
+
import json
|
| 319 |
+
|
| 320 |
+
try:
|
| 321 |
+
return json.loads((d / "report.json").read_text())["config"].get("objects", "dynamic")
|
| 322 |
+
except (OSError, KeyError, ValueError):
|
| 323 |
+
return "dynamic"
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def _cmd_recon_eval(a: argparse.Namespace) -> int:
|
| 327 |
+
import json
|
| 328 |
+
|
| 329 |
+
import torch
|
| 330 |
+
|
| 331 |
+
from nusim.data.index import load_scene
|
| 332 |
+
from nusim.io.ply import read_ply
|
| 333 |
+
from nusim.paths import dataset, output_dir
|
| 334 |
+
from nusim.recon.dataset import build_views
|
| 335 |
+
from nusim.recon.train import CameraAdjust, ColorCorrection, _to_params, evaluate
|
| 336 |
+
|
| 337 |
+
d = Path(a.dir) if a.dir else output_dir() / "gs" / a.name
|
| 338 |
+
g = read_ply(d / "point_cloud.ply")
|
| 339 |
+
objects = _model_objects(d)
|
| 340 |
+
vk = _model_views(d)
|
| 341 |
+
scene, ds = _model_data(a.name, d)
|
| 342 |
+
views = build_views(scene, ds, g.origin, downscale=a.downscale or vk["downscale"],
|
| 343 |
+
frame_stride=vk["frame_stride"], keyframes_only=vk["keyframes_only"], objects=objects)
|
| 344 |
+
color = ColorCorrection.load(d / "color_correction.pt", 6, len(views.train) + len(views.test), "cuda")
|
| 345 |
+
adj = None
|
| 346 |
+
if (d / "camera_adjust.pt").exists():
|
| 347 |
+
adj = CameraAdjust(6).cuda()
|
| 348 |
+
adj.load_state_dict(torch.load(d / "camera_adjust.pt", weights_only=True))
|
| 349 |
+
sky = None
|
| 350 |
+
if (d / "sky.pt").exists():
|
| 351 |
+
from nusim.recon.train import SkyCube
|
| 352 |
+
|
| 353 |
+
try:
|
| 354 |
+
sky = SkyCube.from_state(torch.load(d / "sky.pt", weights_only=True))
|
| 355 |
+
except ValueError as e:
|
| 356 |
+
LOG.warning("ํ๋ ํ๋ธ๋งต์ ๋ชป ์ฝ์: %s", e)
|
| 357 |
+
params = _to_params(g, "cuda")
|
| 358 |
+
objposes = None
|
| 359 |
+
if objects == "vehicles" and (d / "objects" / "objects.json").exists():
|
| 360 |
+
# ์ฐจ๋ ๋์ ํ์ต ๋ชจ๋ธ: ๋ฐฐ๊ฒฝ ply ์ ์ฐจ๋ ์์
์ ๋ค์ ๋ถ์ฌ ํ์ต ๋์ฒ๋ผ ๊ทธ๋ฆฐ๋ค
|
| 361 |
+
from nusim.recon.objects import load_objects
|
| 362 |
+
|
| 363 |
+
arrays, ids, objs = load_objects(d, scene, g.origin)
|
| 364 |
+
if len(ids):
|
| 365 |
+
for key, val in arrays.items():
|
| 366 |
+
params[key] = torch.nn.Parameter(torch.cat([params[key].data, val], 0))
|
| 367 |
+
params["obj"] = torch.nn.Parameter(torch.cat([torch.full((len(g),), -1.0, device="cuda"),
|
| 368 |
+
torch.as_tensor(ids, device="cuda")]))
|
| 369 |
+
cache: dict[int, tuple] = {}
|
| 370 |
+
|
| 371 |
+
def objposes(v):
|
| 372 |
+
t = int(v.pose.timestamp)
|
| 373 |
+
if t not in cache:
|
| 374 |
+
cache[t] = tuple(torch.as_tensor(x, device="cuda", dtype=torch.float32) for x in objs.poses_at(t))
|
| 375 |
+
return cache[t]
|
| 376 |
+
print(f" ์ฐจ๋ ์์
{len(set(ids.tolist()))}๋, ๊ฐ์ฐ์์ {len(ids):,}๊ฐ๋ฅผ ๋ถ์ฌ ํ๊ฐ")
|
| 377 |
+
ev = evaluate(params, color, views, g.sh_degree, cam_adjust=adj, sky=sky, objposes=objposes, with_lpips=a.lpips)
|
| 378 |
+
if a.geom:
|
| 379 |
+
from nusim.paths import cache_dir
|
| 380 |
+
from nusim.recon.depth import lidar_depth_maps
|
| 381 |
+
from nusim.recon.init import InitConfig, accumulate_lidar
|
| 382 |
+
from nusim.recon.metrics import geometry_metrics
|
| 383 |
+
from nusim.recon.sky import sky_masks
|
| 384 |
+
|
| 385 |
+
pts = accumulate_lidar(scene, ds, InitConfig(objects=objects)) - g.origin
|
| 386 |
+
if adj is not None: # ๋ ๋์ ๊ฐ์ (๋ณด์ ๋) ์นด๋ฉ๋ผ๋ก ํฌ์ํ๋ค
|
| 387 |
+
from nusim.recon.train import _refresh_depth
|
| 388 |
+
|
| 389 |
+
_refresh_depth(views.test, adj, pts, 4)
|
| 390 |
+
else:
|
| 391 |
+
lidar_depth_maps(pts, views.test, scale=4)
|
| 392 |
+
sky_masks(views.test, ds, sky_mask_cache(a.name, ds))
|
| 393 |
+
ev.update(geometry_metrics(params, views.test, g.sh_degree, cam_adjust=adj))
|
| 394 |
+
prev = json.loads((d / "eval.json").read_text()) if (d / "eval.json").exists() else {}
|
| 395 |
+
(d / "eval.json").write_text(json.dumps({**prev, **ev}, indent=2)) # ์ด์ --geom ๋ฑ ๋ค๋ฅธ ํค๋ ๋จ๊ธด๋ค
|
| 396 |
+
print(json.dumps(ev, indent=2))
|
| 397 |
+
return 0
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _cmd_run(a: argparse.Namespace) -> int:
|
| 401 |
+
from nusim.config import resolve
|
| 402 |
+
from nusim.jobs import run_job
|
| 403 |
+
from nusim.log import run_log
|
| 404 |
+
from nusim.paths import output_dir
|
| 405 |
+
|
| 406 |
+
path = resolve("jobs", a.job)
|
| 407 |
+
with run_log(output_dir() / "jobs" / f"{path.stem}.log"):
|
| 408 |
+
return run_job(path, dry=a.dry_run)
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def _cmd_recon_batch(a: argparse.Namespace) -> int:
|
| 412 |
+
from nusim.paths import output_dir
|
| 413 |
+
from nusim.recon.batch import day_scenes, run_batch
|
| 414 |
+
|
| 415 |
+
if a.scenes:
|
| 416 |
+
scenes = [s.strip() for s in a.scenes.split(",") if s.strip()]
|
| 417 |
+
else:
|
| 418 |
+
scenes = day_scenes(a.location)
|
| 419 |
+
if a.order == "coverage":
|
| 420 |
+
from nusim.recon.batch import coverage_order
|
| 421 |
+
|
| 422 |
+
scenes = coverage_order(scenes)
|
| 423 |
+
if a.limit:
|
| 424 |
+
scenes = scenes[: a.limit]
|
| 425 |
+
root = Path(a.root) if a.root else output_dir() / "gs_day"
|
| 426 |
+
print(f"์ผ๊ด ์ฌ๊ตฌ์ฑ {len(scenes)}์ฌ (ํ๋ฆฌ์
{a.preset}) โ {root}")
|
| 427 |
+
from nusim.log import run_log
|
| 428 |
+
|
| 429 |
+
with run_log(root / "batch.log"):
|
| 430 |
+
status = run_batch(scenes, root, a.preset, iters=a.iters, sky=False if a.no_sky else None,
|
| 431 |
+
segment_m=a.segment_m, overlap_m=a.overlap_m, after=a.after, keep_init=a.keep_init)
|
| 432 |
+
units = [v for k, v in status.items() if not k.startswith("_")]
|
| 433 |
+
return 0 if all(v.get("state") == "done" for v in units) else 1
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def _cmd_recon_cycle(a: argparse.Namespace) -> int:
|
| 437 |
+
import json
|
| 438 |
+
|
| 439 |
+
from nusim.log import run_log
|
| 440 |
+
from nusim.paths import output_dir
|
| 441 |
+
from nusim.recon.cycle import bench_metrics, build_cycle
|
| 442 |
+
|
| 443 |
+
base = Path(a.base) if a.base else output_dir() / "gs_day" / a.name
|
| 444 |
+
with run_log(base / "cycle" / "run.log"):
|
| 445 |
+
cdir = build_cycle(a.name, base, a.iters, a.shift)
|
| 446 |
+
res = {k: bench_metrics(cdir / "bench", k) for k in ("render", "base")}
|
| 447 |
+
(cdir / "bench_metrics.json").write_text(json.dumps(res, indent=2))
|
| 448 |
+
print(json.dumps(res, indent=2))
|
| 449 |
+
return 0
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
def _cmd_recon_prune(a: argparse.Namespace) -> int:
|
| 453 |
+
from nusim.log import run_log
|
| 454 |
+
from nusim.recon.prune import prune_model
|
| 455 |
+
|
| 456 |
+
src = Path(a.dir) if a.dir else _render_paths(a.name)
|
| 457 |
+
out = Path(a.out) if a.out else src.parent / f"{src.name}_pruned"
|
| 458 |
+
with run_log(out / "prune.log"):
|
| 459 |
+
prune_model(a.name, src, out, margin=a.margin, ratio=a.ratio, min_obs=a.min_obs)
|
| 460 |
+
return 0
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def _cmd_recon_bench(a: argparse.Namespace) -> int:
|
| 464 |
+
import json
|
| 465 |
+
|
| 466 |
+
from nusim.recon.cycle import bench_metrics, run_fixer_bench
|
| 467 |
+
|
| 468 |
+
bench = Path(a.bench)
|
| 469 |
+
if a.run_difix:
|
| 470 |
+
run_fixer_bench(bench)
|
| 471 |
+
res = {k: bench_metrics(bench, k) for k in a.pred.split(",")}
|
| 472 |
+
print(json.dumps(res, indent=2))
|
| 473 |
+
return 0
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
def _cmd_recon_skylabel(a: argparse.Namespace) -> int:
|
| 477 |
+
from nusim.log import run_log
|
| 478 |
+
from nusim.recon.sky_label import label_sky
|
| 479 |
+
|
| 480 |
+
d = Path(a.dir) if a.dir else _render_paths(a.name)
|
| 481 |
+
with run_log(d / "sky_label.log"):
|
| 482 |
+
label_sky(a.name, d, ratio=a.ratio, min_obs=a.min_obs)
|
| 483 |
+
return 0
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def _cmd_recon_semantic(a: argparse.Namespace) -> int:
|
| 487 |
+
from nusim.data.index import load_scene
|
| 488 |
+
from nusim.paths import dataset, output_dir
|
| 489 |
+
from nusim.recon.batch import segment_dirs
|
| 490 |
+
from nusim.recon.semantic import is_current, semantic_config, write_semantic
|
| 491 |
+
|
| 492 |
+
root = Path(a.root) if a.root else output_dir() / "gs_day"
|
| 493 |
+
names = list(a.names)
|
| 494 |
+
if a.location:
|
| 495 |
+
from nusim.render.regionmap import trained_scenes
|
| 496 |
+
|
| 497 |
+
names += [s for s, _ in trained_scenes(root, a.location)]
|
| 498 |
+
if not names:
|
| 499 |
+
raise SystemExit("์ฌ ์ด๋ฆ์ด๋ --location ์ ์ค๋ค")
|
| 500 |
+
cfg, paths, n = semantic_config(), dataset(), 0
|
| 501 |
+
for i, name in enumerate(names):
|
| 502 |
+
dirs = [m for _, m in segment_dirs(root, name)] or ([Path(a.dir)] if a.dir else [_render_paths(name)])
|
| 503 |
+
todo = [m for m in dirs if a.force or not is_current(m)]
|
| 504 |
+
if not todo:
|
| 505 |
+
continue
|
| 506 |
+
print(f"[{i + 1}/{len(names)}] {name}: ๋ชจ๋ธ {len(todo)}๊ฐ")
|
| 507 |
+
sc, cache = load_scene(name), {}
|
| 508 |
+
for m in todo:
|
| 509 |
+
write_semantic(m, sc, paths, cfg, log=print, cache=cache)
|
| 510 |
+
n += 1
|
| 511 |
+
print(f"์๋งจํฑ ๋ผ๋ฒจ {n}๊ฐ ๋ชจ๋ธ ์๋ก ๋ง๋ฆ")
|
| 512 |
+
return 0
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def _ensure_sky_label(name: str, d: Path) -> None:
|
| 516 |
+
"""ํ๋์ ๋ถ๋ฆฌํ์ง ์๊ณ ํ์ตํ ๋ชจ๋ธ์ด๋ฉด ํ๋ ๊ฐ์ฐ์์์ ํ์ํด ๋๋ค (๋ทฐ์ด๊ฐ ์๋ฎฌ๋ ์ด์
ํ๋ ๋ชจ๋์์ ๋บ๋ค).
|
| 517 |
+
ํ๋ ํ๋ธ๋งต(sky.ktx2)์ด ์๋ ๋ชจ๋ธ์ ํ๋์ด ์ด๋ฏธ ๋ฐ๋ก๋ผ ํ์ ์๋ค."""
|
| 518 |
+
from nusim.recon.sky_label import is_fresh, label_sky
|
| 519 |
+
|
| 520 |
+
if (d / "sky.ktx2").exists() or is_fresh(d):
|
| 521 |
+
return
|
| 522 |
+
print(" ํ๋ ๊ฐ์ฐ์์ ํ์ (ํ ๋ฒ๋ง, ํ๋ ๋ง์คํฌ ์บ์ ์ฌ์ฉ)โฆ")
|
| 523 |
+
label_sky(name, d, log=print)
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
def _cmd_aug_rig(a: argparse.Namespace) -> int:
|
| 527 |
+
from nusim.aug.rig_aug import load_variants, render_variant
|
| 528 |
+
from nusim.config import configs_dir
|
| 529 |
+
from nusim.log import run_log
|
| 530 |
+
from nusim.paths import output_dir
|
| 531 |
+
from nusim.render.compare import viewer_binary
|
| 532 |
+
|
| 533 |
+
d = _render_paths(a.name, a.model)
|
| 534 |
+
_ensure_export(a.name, d, 1)
|
| 535 |
+
variants = load_variants(Path(a.variants) if a.variants else configs_dir() / "aug" / "rig_variants.json",
|
| 536 |
+
a.only.split(",") if a.only else None)
|
| 537 |
+
out_root = Path(a.out) if a.out else output_dir() / "aug" / "rig"
|
| 538 |
+
extra = _ibl_gltf_args(a.name, agents=True) if a.agents else []
|
| 539 |
+
with run_log(out_root / a.name / "aug.log"):
|
| 540 |
+
print(f"\033[1m{a.name}\033[0m ๋ฆฌ๊ทธ ๋ณํ {len(variants)}๊ฐ (๋ชจ๋ธ {d})")
|
| 541 |
+
for v in variants:
|
| 542 |
+
render_variant(a.name, d, v, out_root, viewer_binary(), extra, log=print)
|
| 543 |
+
return 0
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def _cmd_aug_map(a: argparse.Namespace) -> int:
|
| 547 |
+
from nusim.aug.map_aug import aug_scene
|
| 548 |
+
from nusim.aug.rig_aug import load_variants
|
| 549 |
+
from nusim.config import configs_dir
|
| 550 |
+
from nusim.log import run_log
|
| 551 |
+
from nusim.paths import output_dir
|
| 552 |
+
from nusim.render.compare import viewer_binary
|
| 553 |
+
|
| 554 |
+
variants = load_variants(Path(a.variants) if a.variants else configs_dir() / "aug" / "rig_variants.json",
|
| 555 |
+
a.only.split(",") if a.only else None)
|
| 556 |
+
if not a.only: # ๊ธฐ๋ณธ์ ๋ ์จ ์๋ ๋ณํ๋ง โ ๋ ์จ ํฉ์ฑ์ ๋
ผ๋ฌธ ๋ฒ์ ๋ฐ (2026-10-06)
|
| 557 |
+
variants = [v for v in variants if not v.get("weather")]
|
| 558 |
+
root = Path(a.root) if a.root else output_dir() / "gs_day"
|
| 559 |
+
out_root = Path(a.out) if a.out else output_dir() / "aug" / "map"
|
| 560 |
+
with run_log(out_root / a.name / "aug.log"):
|
| 561 |
+
print(f"\033[1m{a.name}\033[0m ๋ฆฌ๊ทธ ๋ณํ {len(variants)}๊ฐ (๋ชจ๋ธ {root / a.name}, "
|
| 562 |
+
f"{'ํคํ๋ ์' if a.keyframes_only else '์ ์ฒด ํ๋ ์'}, ์ฐจ๋ {'glTF' if a.agents else '์์'})")
|
| 563 |
+
aug_scene(a.name, root, variants, out_root, viewer_binary(), keyframes=a.keyframes_only, agents=a.agents,
|
| 564 |
+
log=print)
|
| 565 |
+
return 0
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def _cmd_aug_artifixer(a: argparse.Namespace) -> int:
|
| 569 |
+
from nusim.aug.artifixer_run import aug_scene, artifixer_config, done_cams, finished_scenes
|
| 570 |
+
from nusim.log import run_log
|
| 571 |
+
from nusim.paths import output_dir
|
| 572 |
+
|
| 573 |
+
cfg = artifixer_config()
|
| 574 |
+
cams = a.cams.split(",") if a.cams else cfg["cams"]
|
| 575 |
+
names = list(a.names) or finished_scenes()
|
| 576 |
+
if a.limit:
|
| 577 |
+
names = [n for n in names if a.force or not set(cams) <= done_cams(n)][:a.limit]
|
| 578 |
+
log_dir = output_dir() / "aug" / "aug_artfixer"
|
| 579 |
+
log_dir.mkdir(parents=True, exist_ok=True)
|
| 580 |
+
failed = []
|
| 581 |
+
with run_log(log_dir / "artifixer.log"):
|
| 582 |
+
for i, name in enumerate(names):
|
| 583 |
+
if not a.force and set(cams) <= done_cams(name):
|
| 584 |
+
continue
|
| 585 |
+
print(f"\033[1m[{i + 1}/{len(names)}] {name}\033[0m ArtiFixer + ์ฐจ์ ํ์ฒ๋ฆฌ ({','.join(cams)})", flush=True)
|
| 586 |
+
try:
|
| 587 |
+
aug_scene(name, cfg, Path(a.root) if a.root else None, cams, force=a.force)
|
| 588 |
+
except Exception as e: # ์ฌ ํ๋๊ฐ ๊นจ์ ธ๋ (PNG ์์ ยท NFS ๋๊น) ๋๋จธ์ง๋ ๊ณ์
|
| 589 |
+
print(f" ์คํจ: {e}")
|
| 590 |
+
failed.append(name)
|
| 591 |
+
if failed:
|
| 592 |
+
print(f"์คํจ {len(failed)}์ฌ: {' '.join(failed)}")
|
| 593 |
+
return 1 if failed else 0
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def _cmd_aug_translate(a: argparse.Namespace) -> int:
|
| 597 |
+
from nusim.aug.translate import translate_scene
|
| 598 |
+
from nusim.log import run_log
|
| 599 |
+
from nusim.paths import output_dir
|
| 600 |
+
|
| 601 |
+
out_root = Path(a.out) if a.out else output_dir() / "aug" / "translate"
|
| 602 |
+
w, h = (int(x) for x in a.size.split("x"))
|
| 603 |
+
with run_log(out_root / "translate.log"):
|
| 604 |
+
for name in a.names:
|
| 605 |
+
translate_scene(name, a.models.split(","), out_root, size=(w, h), stride=a.stride,
|
| 606 |
+
keyframes_only=a.keyframes_only, log=print)
|
| 607 |
+
return 0
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
def _is_seg(d: Path) -> bool:
|
| 611 |
+
from nusim.render.segmap import is_segmented
|
| 612 |
+
|
| 613 |
+
return is_segmented(d)
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def _render_paths(name: str, model: str | None = None, segmented: bool = False):
|
| 617 |
+
"""๋ ๋์ ์ธ ๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ. ๊ธฐ๋ณธ์ outputs/gs/<scene>_env (ํ๊ฒฝ ์ ์ฉ), ๋ค์ outputs/gs/<scene>_depth (๋ผ์ด๋ค ๊น์ด ๊ฐ๋
),
|
| 618 |
+
์์ผ๋ฉด outputs/gs/<scene>. ์ฐจ์ ๋ณ๊ฒฝ ๊ฐ์ ์ ์์ ์์ ๊น์ด ๊ฐ๋
๋ชจ๋ธ์ด ํ์ฐํ ๋ซ๋ค
|
| 619 |
+
(docs/roadmap.md 1๋จ๊ณ ๋ชจ๋ธ ๋น๊ต). segmented: ๊ตฌ๊ฐ์ผ๋ก ๋๋ ์ผ๊ด ์ฌ๊ตฌ์ฑ ์ฌ ๋๋ ํฐ๋ฆฌ(seg<k>/)๋ ๋ฐ๋๋ค (render view)."""
|
| 620 |
+
from nusim.paths import output_dir
|
| 621 |
+
|
| 622 |
+
if model:
|
| 623 |
+
d = Path(model)
|
| 624 |
+
else:
|
| 625 |
+
# ํ๊ฒฝ ์ ์ฉ(_env: ์ฐจ๋ยท์ฌ๋ ์ ๋ถ ์ง์) > ์ผ๊ด ์ฌ๊ตฌ์ฑ(gs_day, ํ๊ฒฝ ์ ์ฉ) > ๊น์ด ๊ฐ๋
(_depth) > ๊ธฐ๋ณธ ์
|
| 626 |
+
cands = [output_dir() / "gs" / f"{name}_env", output_dir() / "gs_day" / name,
|
| 627 |
+
output_dir() / "gs" / f"{name}_depth", output_dir() / "gs" / name]
|
| 628 |
+
for d in cands:
|
| 629 |
+
if (d / "point_cloud.ply").exists() or (segmented and _is_seg(d)):
|
| 630 |
+
break
|
| 631 |
+
if not (d / "point_cloud.ply").exists() and not (segmented and _is_seg(d)):
|
| 632 |
+
raise FileNotFoundError(f"{d / 'point_cloud.ply'} ๊ฐ ์์ต๋๋ค. ๋จผ์ : nusim recon train {name}")
|
| 633 |
+
return d
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
def _ibl_gltf_args(name: str, agents: bool = False) -> list[str]:
|
| 637 |
+
"""IBL ์ด ๊ตฌ์์ ธ ์์ผ๋ฉด ํ๊ฒฝ ์กฐ๋ช
์ ์ผ ๋ค.
|
| 638 |
+
๋ค๋ฅธ ์ฐจ๋(glTF)์ ๊ธฐ๋ณธ์ผ๋ก ๋ฃ์ง ์๋๋ค โ ๋ ๋๋ง์ ์ฃผํ ํ๊ฒฝ(๋งต)๋ง ๊ตฌ์ฑํ๊ณ , ์ฐจ๋ยท์ฌ๋ ๊ฐ์ ๊ฐ์ฒด๋
|
| 639 |
+
์ดํ ์๋ฎฌ๋ ์ดํฐ๊ฐ ๋ฐ๋ก ๋ฃ๋ ๋ฐฉํฅ์ด๋ค. agents=True ๋ฉด nuScenes ๋ก๊ทธ์ ์์ง์ด๋ ์ฐจ ๊ถค์ ์ glTF ์ฐจ๋์ ๋๋๋ค."""
|
| 640 |
+
from nusim.paths import output_dir, workspace_root
|
| 641 |
+
|
| 642 |
+
args = []
|
| 643 |
+
ibl = output_dir() / "ibl" / name
|
| 644 |
+
if (ibl / "env_specular.ktx2").exists():
|
| 645 |
+
args += ["--ibl", str(ibl)]
|
| 646 |
+
car = workspace_root() / "assets" / "gltf" / "CarConcept.glb"
|
| 647 |
+
if agents and car.exists():
|
| 648 |
+
args += ["--gltf", str(car)]
|
| 649 |
+
return args
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
def _write_obstacles(name: str, d: Path, origin) -> None:
|
| 653 |
+
"""์ถฉ๋์ฉ ์ฅ์ ๋ฌผ ์ง๋ (์ ์ ๋ผ์ด๋ค, ๋ชจ๋ธ๊ณผ ๊ฐ์ objects ์ค์ ์ผ๋ก ์ฐจ๋ยท์ฌ๋ ์ ๊ฑฐ) โ d/obstacles.pgm"""
|
| 654 |
+
from nusim.data.index import load_scene
|
| 655 |
+
from nusim.paths import dataset
|
| 656 |
+
from nusim.sim.obstacles import write_obstacles
|
| 657 |
+
|
| 658 |
+
info = write_obstacles(d / "obstacles.pgm", load_scene(name), dataset(), origin, objects=_model_objects(d))
|
| 659 |
+
print(f" ์ฅ์ ๋ฌผ ์ง๋ {info['cells']:,}์นธ ({info['res']} m; ๋
ธ๋ฉด ์์ชฝ์์ ๋น์ด ์นธ {info['cleared_on_road']}, "
|
| 660 |
+
f"์๋ ๊ฒฝ๋ก์์ ๋ ๋น์ด ์นธ {info['carved_by_log_path']}) โ {d / 'obstacles.pgm'}")
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
EXPORT_VERSION = 2 # ๋ด๋ณด๋ด๊ธฐ ํ (๋ฐ๋๋ฉด ๋ค์ ๋ง๋ ๋ค). 2: ํ๊ฒฝ ์ ์ฉ ๋ชจ๋ธ์ A ์ค์ ์ฃผ์ฐจ ์ฐจ๋
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
def _model_stamp(d: Path) -> str:
|
| 667 |
+
"""๋ชจ๋ธ์ด ๋ฐ๋์๋์ง โ ply ์ ์ธ๋ถ ํ๋ผ๋ฏธํฐ ๋ณด์ ์ ์์ ์๊ฐ."""
|
| 668 |
+
parts = [str(int((d / "point_cloud.ply").stat().st_mtime))]
|
| 669 |
+
if (d / "camera_adjust.json").exists():
|
| 670 |
+
parts.append(str(int((d / "camera_adjust.json").stat().st_mtime)))
|
| 671 |
+
return ":".join(parts)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
def _ensure_export(name: str, d: Path, downscale: int, margin: float = 60.0, force: bool = False) -> None:
|
| 675 |
+
"""๋ทฐ์ด๊ฐ ์ฝ๋ ํ์ผ(rig.txt, map.txt, drivable.txt, obstacles.pgm)์ ํ์ํ ๊ฒ๋ง ๋ค์ ๋ง๋ ๋ค.
|
| 676 |
+
์์ ์๋ ํ์ผ์ด ์๊ธฐ๋ง ํ๋ฉด ๊ทธ๋๋ก ์จ์, ๋ชจ๋ธ์ ๋ค์ ํ์ตํ๊ฑฐ๋ --downscaleยทobjectsยท์๊ณ ๋ฆฌ์ฆ์ ๋ฐ๊ฟ๋
|
| 677 |
+
๋ก์ ๋ฆฌ๊ทธยท์ง๋๋ก ๊ทธ๋ ธ๋ค (2026-10-02 ๋ฆฌ๋ทฐ). ๋ฌด์์ผ๋ก ๋ง๋ค์๋์ง export.json ์ ๋จ๊ธฐ๊ณ ๋น๊ตํ๋ค."""
|
| 678 |
+
import json
|
| 679 |
+
|
| 680 |
+
from nusim.data.index import load_scene
|
| 681 |
+
from nusim.io.ply import read_origin
|
| 682 |
+
from nusim.render.export import load_camera_adjust, write_drivable, write_map_lines, write_rig
|
| 683 |
+
from nusim.sim.obstacles import OBSTACLES_VERSION
|
| 684 |
+
from nusim.sim.voxels import VOXELS_VERSION
|
| 685 |
+
|
| 686 |
+
state_path = d / "export.json"
|
| 687 |
+
state = json.loads(state_path.read_text()) if state_path.exists() and not force else {}
|
| 688 |
+
stamp = _model_stamp(d)
|
| 689 |
+
want = {
|
| 690 |
+
"rig": {"model": stamp, "downscale": downscale, "version": EXPORT_VERSION},
|
| 691 |
+
"map": {"model": stamp, "version": EXPORT_VERSION, "margin": margin},
|
| 692 |
+
"obstacles": {"model": stamp, "version": OBSTACLES_VERSION, "objects": _model_objects(d)},
|
| 693 |
+
"voxels": {"model": stamp, "version": VOXELS_VERSION, "objects": _model_objects(d)},
|
| 694 |
+
}
|
| 695 |
+
files = {"rig": ["rig.txt"], "map": ["map.txt", "drivable.txt"], "obstacles": ["obstacles.pgm"],
|
| 696 |
+
"voxels": ["world.vox"]}
|
| 697 |
+
todo = [k for k in want if state.get(k) != want[k] or not all((d / f).exists() for f in files[k])]
|
| 698 |
+
if not todo:
|
| 699 |
+
return
|
| 700 |
+
scene, _ = _model_data(name, d) # ๊ตฌ๊ฐ ๋ชจ๋ธ(--time-window)์ด๋ฉด ๊ทธ ํ์ต ๊ตฌ๊ฐ๋ง ์ฌ์ยทํ์
|
| 701 |
+
origin = read_origin(d / "point_cloud.ply")
|
| 702 |
+
if "rig" in todo:
|
| 703 |
+
adjust = load_camera_adjust(d)
|
| 704 |
+
n = write_rig(d / "rig.txt", scene, origin, adjust, downscale=downscale, objects=_model_objects(d))
|
| 705 |
+
print(f" ์นด๋ฉ๋ผ ๋ฆฌ๊ทธ {n}ํ๋ ์ ร 6์บ โ {d / 'rig.txt'}" + (" (์ธ๋ถ ํ๋ผ๋ฏธํฐ ๋ณด์ ๋ฐ์)" if adjust else ""))
|
| 706 |
+
if "map" in todo:
|
| 707 |
+
counts = write_map_lines(d / "map.txt", scene, origin, margin=margin)
|
| 708 |
+
n_poly = write_drivable(d / "drivable.txt", scene, origin)
|
| 709 |
+
print(f" ์ฃผํ๊ฐ๋ฅ์์ญ {n_poly}๊ฐ ํด๋ฆฌ๊ณค โ {d / 'drivable.txt'}")
|
| 710 |
+
print(f" ๋งต ์ โ {d / 'map.txt'}")
|
| 711 |
+
for k, v in counts.items():
|
| 712 |
+
print(f" {k:<16} {v:>5}")
|
| 713 |
+
if "obstacles" in todo:
|
| 714 |
+
_write_obstacles(name, d, origin)
|
| 715 |
+
if "voxels" in todo: # ๋ผ์ด๋คยท๋ ์ด๋ ๋ ์ด์บ์คํธ์ฉ (nusim/sim/voxels.py)
|
| 716 |
+
from nusim.paths import dataset
|
| 717 |
+
from nusim.sim.voxels import write_voxels
|
| 718 |
+
|
| 719 |
+
info = write_voxels(d / "world.vox", scene, dataset(), origin, objects=_model_objects(d))
|
| 720 |
+
print(f" ๋ณต์
์๋ {info['occupied']:,}์นธ ({info['res']} m, {info['mb']} MB) โ {d / 'world.vox'}")
|
| 721 |
+
state.update({k: want[k] for k in todo})
|
| 722 |
+
state_path.write_text(json.dumps(state, indent=2))
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
def _cmd_render_export(a: argparse.Namespace) -> int:
|
| 726 |
+
d = _render_paths(a.name, getattr(a, "model", None))
|
| 727 |
+
_ensure_export(a.name, d, a.downscale, margin=a.margin, force=True)
|
| 728 |
+
return 0
|
| 729 |
+
|
| 730 |
+
|
| 731 |
+
def _cmd_render_compare(a: argparse.Namespace) -> int:
|
| 732 |
+
import json
|
| 733 |
+
|
| 734 |
+
from nusim.data.index import load_scene
|
| 735 |
+
from nusim.io.ply import read_origin
|
| 736 |
+
from nusim.paths import dataset
|
| 737 |
+
from nusim.recon.dataset import build_views
|
| 738 |
+
from nusim.render.compare import compare, run_offscreen
|
| 739 |
+
from nusim.render.export import load_camera_adjust, write_views
|
| 740 |
+
|
| 741 |
+
d = _render_paths(a.name, getattr(a, "model", None))
|
| 742 |
+
origin = read_origin(d / "point_cloud.ply")
|
| 743 |
+
vk = _model_views(d)
|
| 744 |
+
scene, ds = _model_data(a.name, d)
|
| 745 |
+
views = build_views(scene, ds, origin, load_images=False, mask_dynamic=False,
|
| 746 |
+
frame_stride=vk["frame_stride"], keyframes_only=vk["keyframes_only"]).test
|
| 747 |
+
views = views[:: max(1, len(views) // a.n)][: a.n]
|
| 748 |
+
adjust = load_camera_adjust(d)
|
| 749 |
+
write_views(d / "test_views.txt", views, adjust)
|
| 750 |
+
vk_dir = d / "vk_test"
|
| 751 |
+
for old in vk_dir.glob("*.png"):
|
| 752 |
+
old.unlink()
|
| 753 |
+
print(run_offscreen(d / "point_cloud.ply", d / "test_views.txt", vk_dir).rstrip())
|
| 754 |
+
res = compare(d, views, vk_dir, adjust)
|
| 755 |
+
(d / "vk_compare.json").write_text(json.dumps(res, indent=2))
|
| 756 |
+
ok = res["mean_abs_diff_pct"] < 2.0
|
| 757 |
+
print(f"\n Vulkan vs gsplat ({res['n_views']}๋ทฐ): PSNR {res['psnr_vk_vs_gsplat']:.2f} dB "
|
| 758 |
+
f"ํ๊ท ์ฐจ์ด {res['mean_abs_diff_pct']:.3f}% 2% ๋๋ ํฝ์
{res['pixels_over_2pct']:.2f}% "
|
| 759 |
+
f"์ต์
๋ทฐ {res['worst_view_psnr']:.2f} dB โ {'ํฉ๊ฒฉ' if ok else '๋ถํฉ๊ฒฉ'} (๊ธฐ์ค: ํ๊ท ์ฐจ์ด < 2%)")
|
| 760 |
+
return 0 if ok else 1
|
| 761 |
+
|
| 762 |
+
|
| 763 |
+
def _cmd_render_view(a: argparse.Namespace) -> int:
|
| 764 |
+
import os
|
| 765 |
+
|
| 766 |
+
from nusim.render.compare import viewer_binary
|
| 767 |
+
|
| 768 |
+
d = _render_paths(a.name, getattr(a, "model", None), segmented=True)
|
| 769 |
+
sky_dir = d
|
| 770 |
+
if _is_seg(d): # ์ผ๊ด ์ฌ๊ตฌ์ฑ์ด ๊ตฌ๊ฐ์ผ๋ก ๋๋ ์ฌ โ ๊ตฌ๊ฐ์ ์ด์ด์ (์์ฐจ ์์น์ ๊ตฌ๊ฐ์ ๊ทธ๋ฆฐ๋ค)
|
| 771 |
+
from nusim.render.segmap import write_segment_map
|
| 772 |
+
|
| 773 |
+
segs = write_segment_map(a.name, d, log=print)
|
| 774 |
+
ply, sky_dir = segs[0][1] / "point_cloud.ply", segs[0][1]
|
| 775 |
+
from nusim.aug.map_aug import ensure_ibl
|
| 776 |
+
|
| 777 |
+
ensure_ibl(a.name, segs, log=print) # glTF ์์ฐจ์ ์กฐ๋ช
โ ์์ผ๋ฉด 3DGS ์์ฐจ ์์
์ผ๋ก ๋์ ํด ๊ฒ์ ๋ฉ์ด๋ฆฌ๊ฐ ๋์๋ค
|
| 778 |
+
seg_args = ["--segments", str(d / "segments.txt")]
|
| 779 |
+
model_dirs = [m for _, m in segs]
|
| 780 |
+
else:
|
| 781 |
+
_ensure_export(a.name, d, a.downscale)
|
| 782 |
+
_ensure_sky_label(a.name, d)
|
| 783 |
+
ply, seg_args, model_dirs = d / "point_cloud.ply", [], [d]
|
| 784 |
+
from nusim.recon.semantic import ensure_semantic
|
| 785 |
+
|
| 786 |
+
ensure_semantic(a.name, model_dirs, log=print) # ์์ผ๋ฉด ์๋งจํฑ ์นด๋ฉ๋ผ(F3)๊ฐ ํ๋๋ง ํ๋๊ณ ๋๋จธ์ง๋ ๊ฒ๋ค
|
| 787 |
+
cmd = [str(viewer_binary()), str(ply.resolve()), "--cams", str(d / "rig.txt"),
|
| 788 |
+
"--lines", str(d / "map.txt"), "--shot-dir", str(d / "shots"), *seg_args]
|
| 789 |
+
if (d / "drivable.txt").exists():
|
| 790 |
+
cmd += ["--drivable", str(d / "drivable.txt")] # ์ง์ ์ด์ (G) ๋ ๋๋ก ์ดํ ํ์ + ์ฐ์ ์ถฉ๋
|
| 791 |
+
cmd += ["--obstacles", str(d / "obstacles.pgm")] # ๊ฑด๋ฌผยท๊ธฐ๋ฅยท๋ฒฝ ์ถฉ๋
|
| 792 |
+
if (d / "objects" / "objects.json").exists(): # ์ฐจ๋์ ํจ๊ป ํ์ตํ ๋ชจ๋ธ โ ์ฐจ๋ ์์
์ ๊ฐ์์ ๊ถค์ ์ผ๋ก (K: ํธ์ง)
|
| 793 |
+
cmd += ["--objects", str(d / "objects")]
|
| 794 |
+
from nusim.paths import output_dir, workspace_root
|
| 795 |
+
|
| 796 |
+
cmd += _ibl_gltf_args(a.name, agents=a.agents)
|
| 797 |
+
if (sky_dir / "sky.ktx2").exists():
|
| 798 |
+
cmd += ["--sky", str(sky_dir / "sky.ktx2")]
|
| 799 |
+
if (d / "world.vox").exists():
|
| 800 |
+
cmd += ["--voxels", str(d / "world.vox")] # ๋ผ์ด๋คยท๋ ์ด๋ (configs/sim.json sensors, ์ฐฝ์ '์ผ์' ํจ๋)
|
| 801 |
+
from nusim.config import sim_config, sim_config_path
|
| 802 |
+
|
| 803 |
+
if sim_config_path().exists():
|
| 804 |
+
cmd += ["--config", str(sim_config_path())] # ๋ ์จ ํ๋ฆฌ์
ยทํ๋ยท3์ธ์นญ ์นด๋ฉ๋ผยท์ถฉ๋ (configs/sim.json)
|
| 805 |
+
ego_cfg = sim_config().get("ego", {})
|
| 806 |
+
ego_gltf = workspace_root() / ego_cfg.get("gltf", "assets/gltf/ego_car.glb")
|
| 807 |
+
ego = workspace_root() / ego_cfg.get("fallback_3dgs_asset", "outputs/assets/ego_car.ply")
|
| 808 |
+
if ego_gltf.exists() and "--ibl" in cmd:
|
| 809 |
+
cmd += ["--ego-gltf", str(ego_gltf)] # ์์ฐจ = ๋จธํฐ๋ฆฌ์ผ ์๋ ์ฐจ๋ ๋ฉ์ (PBR + IBL)
|
| 810 |
+
if "length_m" in ego_cfg:
|
| 811 |
+
cmd += ["--ego-length", str(float(ego_cfg["length_m"]))]
|
| 812 |
+
elif ego.exists():
|
| 813 |
+
cmd += ["--ego-asset", str(ego)] # ์์ผ๋ฉด nuScenes ์ฃผ์ฐจ ์ฐจ๋ 3DGS ์์
|
| 814 |
+
if a.autoshot:
|
| 815 |
+
cmd += ["--autoshot", str(a.autoshot)]
|
| 816 |
+
elif not a.replay:
|
| 817 |
+
cmd += ["--drive"] # ์ด์ ๋ชจ๋(์ฐจ ๋ค ์์ )๋ก ์์. G ๋ฅผ ๋๋ฅด๋ฉด ๋ก๊ทธ ์ฌ์(์ฐจ์ ๋ฌ๋ฆฐ ์นด๋ฉ๋ผ ์์ )
|
| 818 |
+
if a.weather:
|
| 819 |
+
cmd += ["--weather", a.weather] # ํฉ์ฑ ๋ ์จ ์์๊ฐ โ ์ฐฝ ์ ์ฌ๋ผ์ด๋(F1)๋ก ๋ฐ๊ฟ ์ ์๋ค
|
| 820 |
+
os.execv(cmd[0], cmd)
|
| 821 |
+
|
| 822 |
+
|
| 823 |
+
def _cmd_render_map(a: argparse.Namespace) -> int:
|
| 824 |
+
import os
|
| 825 |
+
|
| 826 |
+
from nusim.config import sim_config, sim_config_path
|
| 827 |
+
from nusim.paths import output_dir, workspace_root
|
| 828 |
+
from nusim.render.compare import viewer_binary
|
| 829 |
+
from nusim.render.regionmap import write_region_map
|
| 830 |
+
|
| 831 |
+
root = Path(a.root) if a.root else output_dir() / "gs_day"
|
| 832 |
+
out = root / "_map" / a.location
|
| 833 |
+
if not a.no_sensors: # ์๋ ์ฌ ๊ฒ๋ง (--sensors: ํ์ต๋ ๊ตฌ๊ฐ์ด ๋ฐ๋ ์ฌ๋ ๋ค์)
|
| 834 |
+
from nusim.render.regionmap import build_sensor_worlds
|
| 835 |
+
|
| 836 |
+
n = build_sensor_worlds(a.location, root, log=print, stale=a.sensors)
|
| 837 |
+
if n:
|
| 838 |
+
print(f" ์ผ์ ๋ณต์
์๋ {n}๊ฐ ์ฌ ์๋ก ๋ง๋ฆ")
|
| 839 |
+
info = write_region_map(a.location, root, out, start=a.start, log=print)
|
| 840 |
+
if a.no_view:
|
| 841 |
+
return 0
|
| 842 |
+
from nusim.recon.semantic import ensure_semantic
|
| 843 |
+
from nusim.render.regionmap import trained_scenes
|
| 844 |
+
|
| 845 |
+
for s, segs in ([] if a.no_semantic else trained_scenes(root, a.location)): # ์๋งจํฑ ์นด๋ฉ๋ผ(F3) ๋ผ๋ฒจ โ ์๋ก ํ์ต๋ ๊ตฌ๊ฐ๋ง (๋ชจ๋ธ๋น ์ ์ด)
|
| 846 |
+
ensure_semantic(s, [m for _, m in segs], log=print)
|
| 847 |
+
from nusim.aug.map_aug import ensure_ibl
|
| 848 |
+
from nusim.recon.batch import segment_dirs
|
| 849 |
+
|
| 850 |
+
ensure_ibl(info["anchor"], segment_dirs(root, info["anchor"]), log=print) # ์์ผ๋ฉด glTF ์์ฐจ ๋์ 3DGS ์์ฐจ(๊ฒ์ ๋ฉ์ด๋ฆฌ)๊ฐ ๋์จ๋ค
|
| 851 |
+
cmd = [str(viewer_binary()), info["first_ply"], "--cams", str(out / "rig.txt"), "--segments", str(out / "segments.txt"),
|
| 852 |
+
"--lines", str(out / "map.txt"), "--drivable", str(out / "drivable.txt"), "--shot-dir", str(out / "shots")]
|
| 853 |
+
cmd += _ibl_gltf_args(info["anchor"])
|
| 854 |
+
if Path(info["sky"]).exists():
|
| 855 |
+
cmd += ["--sky", info["sky"]]
|
| 856 |
+
if sim_config_path().exists():
|
| 857 |
+
cmd += ["--config", str(sim_config_path())]
|
| 858 |
+
ego_cfg = sim_config().get("ego", {})
|
| 859 |
+
ego_gltf = workspace_root() / ego_cfg.get("gltf", "assets/gltf/ego_car.glb")
|
| 860 |
+
ego = workspace_root() / ego_cfg.get("fallback_3dgs_asset", "outputs/assets/ego_car.ply")
|
| 861 |
+
if ego_gltf.exists() and "--ibl" in cmd:
|
| 862 |
+
cmd += ["--ego-gltf", str(ego_gltf)]
|
| 863 |
+
if "length_m" in ego_cfg:
|
| 864 |
+
cmd += ["--ego-length", str(float(ego_cfg["length_m"]))]
|
| 865 |
+
elif ego.exists():
|
| 866 |
+
cmd += ["--ego-asset", str(ego)]
|
| 867 |
+
if not a.replay:
|
| 868 |
+
cmd += ["--drive"]
|
| 869 |
+
cmd += a.viewer_args # nusim render map <์ง์ญ> -- --autopilot ... (๋ทฐ์ด ์ต์
๊ทธ๋๋ก)
|
| 870 |
+
print(f" ์ง์ญ ๋งต {info['location']}: ์ฌ {info['scenes']}๊ฐ ยท ๊ตฌ๊ฐ {info['segments']}๊ฐ โ ์ผ์ ๋ณต์
์๋ "
|
| 871 |
+
f"{info['sensor_worlds']}/{info['scenes']}์ฌ"
|
| 872 |
+
+ ("" if info["sensor_worlds"] == info["scenes"] else " (๋๋จธ์ง๋ ์ผ์ ๊บผ์ง โ --no-sensors ๋ฅผ ๋นผ๊ณ ๋ค์)")
|
| 873 |
+
+ " ยท ์ฅ์ ๋ฌผ ์ง๋๋ ์์ง ์ง์ญ ๋จ์๋ก ์์ (์ฐ์ ์ถฉ๋๋ง)")
|
| 874 |
+
os.execv(cmd[0], cmd)
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
def _cmd_render_encode(a: argparse.Namespace) -> int:
|
| 878 |
+
"""PNG ํ๋ ์ ๋๋ ํฐ๋ฆฌ (vkgs_viewer --record-screen / --record) โ mp4. ์ผ์ชฝ ์๋์ ์ค๋ช
๊ธ์๋ฅผ ์น๋๋ค."""
|
| 879 |
+
import imageio.v2 as imageio
|
| 880 |
+
import numpy as np
|
| 881 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 882 |
+
|
| 883 |
+
frames = sorted(Path(a.frames).glob("frame_*.png"))
|
| 884 |
+
if not frames:
|
| 885 |
+
raise SystemExit(f"{a.frames} ์ frame_*.png ๊ฐ ์๋ค")
|
| 886 |
+
font = None
|
| 887 |
+
for f in ("/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf", "/usr/share/fonts/truetype/nanum/NanumSquareRoundB.ttf"):
|
| 888 |
+
if Path(f).exists():
|
| 889 |
+
font = ImageFont.truetype(f, a.font_size)
|
| 890 |
+
break
|
| 891 |
+
out = Path(a.out)
|
| 892 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 893 |
+
with imageio.get_writer(out, fps=a.fps, codec="libx264", quality=None, bitrate=a.bitrate, macro_block_size=1,
|
| 894 |
+
pixelformat="yuv420p") as w:
|
| 895 |
+
for fp in frames:
|
| 896 |
+
im = Image.open(fp).convert("RGB")
|
| 897 |
+
if a.width and im.width != a.width:
|
| 898 |
+
im = im.resize((a.width, round(im.height * a.width / im.width / 2) * 2), Image.LANCZOS)
|
| 899 |
+
if a.caption:
|
| 900 |
+
d = ImageDraw.Draw(im, "RGBA")
|
| 901 |
+
x0, y0, x1, y1 = d.textbbox((0, 0), a.caption, font=font)
|
| 902 |
+
m = 10
|
| 903 |
+
d.rectangle((m, im.height - m - (y1 - y0) - 14, m + x1 - x0 + 16, im.height - m), fill=(0, 0, 0, 160))
|
| 904 |
+
d.text((m + 8 - x0, im.height - m - (y1 - y0) - 7 - y0), a.caption, font=font, fill=(255, 255, 255, 255))
|
| 905 |
+
w.append_data(np.asarray(im))
|
| 906 |
+
if a.poster:
|
| 907 |
+
Image.open(frames[len(frames) // 2]).convert("RGB").save(a.poster, quality=90)
|
| 908 |
+
print(f" {len(frames)}ํ๋ ์ โ {out} ({out.stat().st_size / 2**20:.1f} MB)")
|
| 909 |
+
return 0
|
| 910 |
+
|
| 911 |
+
|
| 912 |
+
def _cmd_render_video(a: argparse.Namespace) -> int:
|
| 913 |
+
from nusim.render.video import compose, render_frames, write_video
|
| 914 |
+
|
| 915 |
+
d = _render_paths(a.name, getattr(a, "model", None))
|
| 916 |
+
_ensure_export(a.name, d, 1)
|
| 917 |
+
_ensure_sky_label(a.name, d)
|
| 918 |
+
fdir = d / "sim_frames"
|
| 919 |
+
print(render_frames(d, fdir, a.frames, bev_height=a.bev_height).rstrip())
|
| 920 |
+
out = d / f"{a.name}_sim.mp4"
|
| 921 |
+
n = write_video(fdir, out, a.name, fps=a.fps)
|
| 922 |
+
# ์ฒซ ํ๋ ์ ์ ์ง ์ด๋ฏธ์ง๋ ๋จ๊ธด๋ค (์์์ ๋ชป ์ฌ๋ ๊ณณ์์ ๋ณด๋ ค๊ณ )
|
| 923 |
+
from PIL import Image
|
| 924 |
+
|
| 925 |
+
first = sorted(fdir.glob("f*_BEV.png"))[len(sorted(fdir.glob("f*_BEV.png"))) // 2]
|
| 926 |
+
Image.fromarray(compose(fdir, int(first.name[1:5]), a.name)).save(d / f"{a.name}_sim.jpg", quality=92)
|
| 927 |
+
print(f" ์์ {n}ํ๋ ์ โ {out}\n ์ ์ง ์ด๋ฏธ์ง โ {d / (a.name + '_sim.jpg')}")
|
| 928 |
+
return 0
|
| 929 |
+
|
| 930 |
+
|
| 931 |
+
def _cmd_recon_distill(a: argparse.Namespace) -> int:
|
| 932 |
+
from nusim.data.index import load_scene
|
| 933 |
+
from nusim.io.ply import read_ply
|
| 934 |
+
from nusim.paths import dataset, output_dir
|
| 935 |
+
from nusim.recon.dataset import build_views
|
| 936 |
+
from nusim.recon.distill import make_pseudo_views
|
| 937 |
+
from nusim.recon.train import TrainConfig, train
|
| 938 |
+
|
| 939 |
+
base = Path(a.base) if a.base else output_dir() / "gs" / a.name
|
| 940 |
+
out = Path(a.out) if a.out else base.parent / f"{base.name}_difix"
|
| 941 |
+
scene, ds = _model_data(a.name, base)
|
| 942 |
+
if (base / "sky.pt").exists():
|
| 943 |
+
# ํ๋ ํ๋ธ๋งต์ ์ด์ด๋ฐ๋ ๋ฏธ์ธ์กฐ์ ์ ์์ง ์๋ค. ๊ทธ๋ฅ ๋๋ฆฌ๋ฉด ํ๋์ด ๋น ์ง ๋ชจ๋ธ(ํ๋ ๊ตฌ ๊ฐ์ฐ์์๋ ์์)์ด
|
| 944 |
+
# ์๊ธฐ๊ณ , ์์ฌ ๋ทฐ์ ๊ฒ์ ํ๋์ Difix ๊ฐ ๊ณ ์น ๊ฑธ ๊ฐ์ฐ์์์ด ๋ฐฐ์ ํ๋ floater ๊ฐ ๋๋ค.
|
| 945 |
+
print(f"์ค๋ฅ: {base} ๋ ํ๋์ ํ๋ธ๋งต์ผ๋ก ๋ถ๋ฆฌํ ๋ชจ๋ธ(sky.pt) โ distill ์ ์์ง ํ๋ ๋ถ๋ฆฌ ๋ชจ๋ธ์ ์ง์ํ์ง ์์ต๋๋ค")
|
| 946 |
+
return 1
|
| 947 |
+
g = read_ply(base / "point_cloud.ply")
|
| 948 |
+
print(f"\033[1m{a.name}\033[0m Difix ์์ฌ ์ ๋ต โ 3DGS ๋ฏธ์ธ์กฐ์ ({base} โ {out})")
|
| 949 |
+
objects = _model_objects(base)
|
| 950 |
+
vk = _model_views(base)
|
| 951 |
+
views = build_views(scene, ds, g.origin, downscale=vk["downscale"], frame_stride=vk["frame_stride"],
|
| 952 |
+
keyframes_only=vk["keyframes_only"], objects=objects)
|
| 953 |
+
if a.depth_lambda > 0: # ๋ฏธ์ธ์กฐ์ ์ค์๋ ๊ธฐํ๋ฅผ ๋ถ์ก๋๋ค (์์ฌ ๋ทฐ์๋ ๊น์ด๊ฐ ์๋ค)
|
| 954 |
+
from nusim.recon.depth import lidar_depth_maps
|
| 955 |
+
from nusim.recon.init import InitConfig, accumulate_lidar
|
| 956 |
+
|
| 957 |
+
import torch
|
| 958 |
+
|
| 959 |
+
from nusim.recon.train import CameraAdjust, _refresh_depth
|
| 960 |
+
|
| 961 |
+
pts = accumulate_lidar(scene, ds, InitConfig(objects=objects)) - g.origin
|
| 962 |
+
adj = CameraAdjust(6).cuda() # ๋ ๋์ ๊ฐ์ (๋ณด์ ๋) ์นด๋ฉ๋ผ๋ก ๊น์ด ์ง๋๋ฅผ ๋ง๋ ๋ค
|
| 963 |
+
if (base / "camera_adjust.pt").exists():
|
| 964 |
+
adj.load_state_dict(torch.load(base / "camera_adjust.pt", weights_only=True))
|
| 965 |
+
_refresh_depth(views.train, adj, pts, 4)
|
| 966 |
+
del adj
|
| 967 |
+
|
| 968 |
+
def cfg_for(iters: int):
|
| 969 |
+
return TrainConfig(depth_lambda=a.depth_lambda,
|
| 970 |
+
iters=iters, sh_degree_every=0, lr_camera=0.0,
|
| 971 |
+
lr_means=1.6e-5, lr_means_final_ratio=0.1, # ์ด๋ฏธ ์๋ ดํ ์์น๋ฅผ ํฌ๊ฒ ํ๋ค์ง ์๋๋ค
|
| 972 |
+
refine_start=500, refine_stop=iters // 2, reset_every=10**9, # ๋ถํฌ๋ช
๋ ๋ฆฌ์
์์ (step 0 ์ train ์ด ๊ฑด๋๋)
|
| 973 |
+
max_gaussians=a.max_gaussians, eval_every=a.eval_every, objects=objects,
|
| 974 |
+
downscale=vk["downscale"], frame_stride=vk["frame_stride"], keyframes_only=vk["keyframes_only"],
|
| 975 |
+
pseudo_prob=a.pseudo_prob, pseudo_weight=a.pseudo_weight)
|
| 976 |
+
|
| 977 |
+
def pseudo_from(model: Path, shifts):
|
| 978 |
+
# ์์ฌ ๋ทฐ ์บ์๋ ๊ธฐ๋ฐ ๋ชจ๋ธยท๊ฐ๋ฆผ ์ค์ ยท๊ฐ๊ฒฉ๋ง๋ค ๋ฐ๋ก โ ์ฌํ์ตํ ๋ชจ๋ธ์ ๋ก์ Difix ๊ฒฐ๊ณผ๋ฅผ ์ฐ์ง ์๊ฒ
|
| 979 |
+
stamp = f"{objects}_every{a.every}_{int((model / 'point_cloud.ply').stat().st_mtime)}"
|
| 980 |
+
return make_pseudo_views(model, scene, views, model / "pseudo" / stamp, shifts, a.every, objects=objects,
|
| 981 |
+
downscale=vk["downscale"])
|
| 982 |
+
|
| 983 |
+
if not a.progressive:
|
| 984 |
+
pseudo = pseudo_from(base, tuple(float(x) for x in a.shifts.split(",")))
|
| 985 |
+
train(views, g, out, cfg_for(a.iters), resume_dir=base, pseudo=pseudo)
|
| 986 |
+
return 0
|
| 987 |
+
|
| 988 |
+
# ์ ์ง ์ฆ๋ฅ (Difix3D+): ๊ฐ๊น์ด ์ด๋๋ถํฐ. ๋จ๊ณ๋ง๋ค ์ง์ ๋จ๊ณ ๋ชจ๋ธ๋ก ยฑs ์์ ์ ๋ ๋ โ Difix โ ์์ฌ ๋ทฐ๋ฅผ ์๊ณ ์งง๊ฒ
|
| 989 |
+
# ๋ฏธ์ธ์กฐ์ . ์ถ๋ฐ์ ์ด ์ด๋ฏธ ์ข์์ ธ์ Difix ๊ฒฐ๊ณผ๊ฐ ์์ ๋ง๋ค ์ผ๊ด๋๊ณ , ํ ๋ฒ์ 3 m ๋ฅผ ๊ณ ์น ๋์ ํ๋ ค์ง์ ์ค์ธ๋ค.
|
| 990 |
+
steps = [float(x) for x in a.progressive.split(",")]
|
| 991 |
+
pseudo, cur = [], base
|
| 992 |
+
for k, s in enumerate(steps):
|
| 993 |
+
print(f"\n โโ ์ ์ง {k + 1}/{len(steps)}: ยฑ{s:g} m (์ถ๋ฐ {cur})")
|
| 994 |
+
pseudo += pseudo_from(cur, (-s, s))
|
| 995 |
+
stage = out / f"stage{k + 1}_{s:g}m" if k < len(steps) - 1 else out
|
| 996 |
+
g_cur = read_ply(cur / "point_cloud.ply")
|
| 997 |
+
train(views, g_cur, stage, cfg_for(a.iters_per_stage), resume_dir=cur, pseudo=pseudo)
|
| 998 |
+
cur = stage
|
| 999 |
+
return 0
|
| 1000 |
+
|
| 1001 |
+
|
| 1002 |
+
def _cmd_asset_scan(a: argparse.Namespace) -> int:
|
| 1003 |
+
from nusim.data.index import iter_scenes
|
| 1004 |
+
from nusim.recon.asset import scan_parked_cars
|
| 1005 |
+
|
| 1006 |
+
rows = scan_parked_cars(iter_scenes(), max_dist=a.max_dist)
|
| 1007 |
+
print(f"{'๋ฐฉ์(/8)':>8} {'์ต์๊ฑฐ๋ฆฌ':>8} {'scene':<12} {'inst':>4} ํฌ๊ธฐ (w,l,h)")
|
| 1008 |
+
for r in rows[: a.limit]:
|
| 1009 |
+
print(f"{r[0]:>8} {r[1]:>7.1f}m {r[2]:<12} {r[3]:>4} {r[4]}")
|
| 1010 |
+
return 0
|
| 1011 |
+
|
| 1012 |
+
|
| 1013 |
+
def _cmd_asset_extract(a: argparse.Namespace) -> int:
|
| 1014 |
+
from nusim.data.index import load_scene
|
| 1015 |
+
from nusim.io.ply import read_ply, write_ply
|
| 1016 |
+
from nusim.paths import output_dir
|
| 1017 |
+
from nusim.recon.asset import extract_vehicle
|
| 1018 |
+
|
| 1019 |
+
model = Path(a.model) if a.model else output_dir() / "gs" / f"{a.name}_depth"
|
| 1020 |
+
g = extract_vehicle(read_ply(model / "point_cloud.ply"), load_scene(a.name), a.instance, mirror=not a.no_mirror)
|
| 1021 |
+
out = Path(a.out) if a.out else output_dir() / "assets" / f"car_{a.name}_{a.instance}.ply"
|
| 1022 |
+
write_ply(out, g)
|
| 1023 |
+
print(f" ์์
๊ฐ์ฐ์์ {len(g):,}๊ฐ โ {out}")
|
| 1024 |
+
if a.as_ego:
|
| 1025 |
+
ego = output_dir() / "assets" / "ego_car.ply"
|
| 1026 |
+
ego.unlink(missing_ok=True)
|
| 1027 |
+
ego.symlink_to(out.name)
|
| 1028 |
+
print(f" ์์ฐจ ์์
์ผ๋ก ์ง์ โ {ego} (nusim render view ๊ฐ ์๋์ผ๋ก ์ด๋ค)")
|
| 1029 |
+
return 0
|
| 1030 |
+
|
| 1031 |
+
|
| 1032 |
+
def _cmd_render_ibl(a: argparse.Namespace) -> int:
|
| 1033 |
+
from nusim.data.index import load_scene
|
| 1034 |
+
from nusim.paths import output_dir
|
| 1035 |
+
from nusim.render.ibl import bake_ibl
|
| 1036 |
+
|
| 1037 |
+
d = _render_paths(a.name, a.model)
|
| 1038 |
+
out = output_dir() / "ibl" / a.name
|
| 1039 |
+
print(f"\033[1m{a.name}\033[0m IBL ๊ตฝ๊ธฐ (๋ชจ๋ธ {d})")
|
| 1040 |
+
bake_ibl(load_scene(a.name), d, out, frame=a.frame, size=a.size, samples=a.samples, fill=a.fill)
|
| 1041 |
+
return 0
|
| 1042 |
+
|
| 1043 |
+
|
| 1044 |
+
def _cmd_doctor(a: argparse.Namespace) -> int:
|
| 1045 |
+
from nusim.cli.doctor import main as doctor_main
|
| 1046 |
+
return doctor_main()
|
| 1047 |
+
|
| 1048 |
+
|
| 1049 |
+
|
| 1050 |
+
# ------------------------------------------------------------------ closed-loop ํ๊ฐ (nusim.sim.closed_loop, HUGSIM ์)
|
| 1051 |
+
def _cmd_sim_run(a: argparse.Namespace) -> int:
|
| 1052 |
+
from nusim.paths import output_dir
|
| 1053 |
+
from nusim.sim.closed_loop import run_episode
|
| 1054 |
+
from nusim.sim.policies import make_policy
|
| 1055 |
+
from nusim.sim.scenario import Scenario, closed_loop_config
|
| 1056 |
+
|
| 1057 |
+
scn = Scenario.load(a.scenario)
|
| 1058 |
+
over = dict(scn.overrides)
|
| 1059 |
+
if a.size:
|
| 1060 |
+
w, h = a.size.lower().split("x")
|
| 1061 |
+
over.setdefault("render", {}).update(width=int(w), height=int(h))
|
| 1062 |
+
if a.max_duration:
|
| 1063 |
+
scn.max_duration_s = a.max_duration
|
| 1064 |
+
cfg = closed_loop_config(over)
|
| 1065 |
+
out = Path(a.out) if a.out else output_dir() / "closed_loop" / scn.name / a.policy
|
| 1066 |
+
print(f" ์๋๋ฆฌ์ค {scn.name} ({scn.difficulty}, {scn.scene}) ยท ์ ์ฑ
{a.policy} โ {out}")
|
| 1067 |
+
res = run_episode(scn, make_policy(a.policy), out, cfg, save_front=not a.no_front, video=not a.no_video)
|
| 1068 |
+
return 0 if res["hd_score"] >= 0 else 1
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
def _cmd_sim_score(a: argparse.Namespace) -> int:
|
| 1072 |
+
import json
|
| 1073 |
+
|
| 1074 |
+
from nusim.sim.hdscore import format_score, score_episode
|
| 1075 |
+
|
| 1076 |
+
for ep in a.episodes:
|
| 1077 |
+
res = score_episode(Path(ep))
|
| 1078 |
+
print(f" {ep} [{res.get('scenario')} ยท {res.get('policy')} ยท {res.get('termination')}]")
|
| 1079 |
+
print(f" {format_score(res)}")
|
| 1080 |
+
if a.verbose:
|
| 1081 |
+
for p in res["per_step"]:
|
| 1082 |
+
print(f" t={p['t']:5.1f} NC {p['nc']:.1f} DAC {p['dac']:.0f} TTC {p['ttc']:.0f} COM {p['com']:.0f}"
|
| 1083 |
+
f" โ {p['hd']:.3f}" + (f" (์น์ฐจ๊ฐ: {','.join(p['com_fail'])})" if p["com_fail"] else ""))
|
| 1084 |
+
if a.json:
|
| 1085 |
+
print(json.dumps({k: v for k, v in res.items() if k != "per_step"}, indent=1))
|
| 1086 |
+
return 0
|
| 1087 |
+
|
| 1088 |
+
|
| 1089 |
+
def _cmd_sim_make_scenarios(a: argparse.Namespace) -> int:
|
| 1090 |
+
import numpy as np
|
| 1091 |
+
|
| 1092 |
+
from nusim.data.index import load_scene
|
| 1093 |
+
from nusim.paths import output_dir, workspace_root
|
| 1094 |
+
from nusim.recon.batch import segment_dirs
|
| 1095 |
+
from nusim.sim.scenario import closed_loop_config, scenarios_dir, write_scenario
|
| 1096 |
+
|
| 1097 |
+
cfg = closed_loop_config()["scenarios"]
|
| 1098 |
+
root = Path(a.root) if a.root else output_dir() / "gs_day"
|
| 1099 |
+
names = a.scenes or sorted(p.name for p in root.iterdir() if p.is_dir() and p.name.startswith("scene-"))
|
| 1100 |
+
out_dir = Path(a.out) if a.out else scenarios_dir()
|
| 1101 |
+
n_ok = 0
|
| 1102 |
+
for name in names:
|
| 1103 |
+
segs = segment_dirs(root, name)
|
| 1104 |
+
if not segs or len(segs) < segs[0][0].get("n", 1):
|
| 1105 |
+
print(f" {name}: ๊ฑด๋๋ โ ๊ตฌ๊ฐ {len(segs)}/{segs[0][0].get('n', 1) if segs else '?'}๊ฐ๋ง ํ์ต๋จ")
|
| 1106 |
+
continue
|
| 1107 |
+
fr = load_scene(name).frames["CAM_FRONT"]
|
| 1108 |
+
xy = np.array([f.ego_translation[:2] for f in fr])
|
| 1109 |
+
ts = np.array([f.timestamp for f in fr]) * 1e-6
|
| 1110 |
+
dist = float(np.linalg.norm(np.diff(xy, axis=0), axis=1).sum())
|
| 1111 |
+
route_m = float(a.route_m if a.route_m is not None else cfg["route_m"])
|
| 1112 |
+
use_m = min(dist, route_m) if route_m > 0 else dist
|
| 1113 |
+
if use_m < cfg["min_route_m"]:
|
| 1114 |
+
print(f" {name}: ๊ฑด๋๋ โ ๊ฒฝ๋ก {dist:.1f} m < {cfg['min_route_m']} m (์ ์ฐจ ์ฌ)")
|
| 1115 |
+
continue
|
| 1116 |
+
dur = float(ts[-1] - ts[0]) * (use_m / max(dist, 1e-6))
|
| 1117 |
+
model = root / name
|
| 1118 |
+
try:
|
| 1119 |
+
model = model.relative_to(workspace_root())
|
| 1120 |
+
except ValueError:
|
| 1121 |
+
pass
|
| 1122 |
+
d = {"name": f"{name}_easy", "difficulty": "easy", "scene": name, "model": str(model),
|
| 1123 |
+
"start": {"frame": 0, "speed": cfg["start_speed"]},
|
| 1124 |
+
"route": {"to_frame": None, "max_m": route_m},
|
| 1125 |
+
"max_duration_s": round(dur * cfg["duration_factor"] + cfg["duration_pad_s"], 1),
|
| 1126 |
+
"actors": [], "overrides": {},
|
| 1127 |
+
"info": {"route_m": round(use_m, 1), "log_duration_s": round(dur, 1), "segments": len(segs)}}
|
| 1128 |
+
write_scenario(out_dir / f"{name}_easy.json", d)
|
| 1129 |
+
n_ok += 1
|
| 1130 |
+
print(f" {name}_easy: ๊ฒฝ๋ก {use_m:.0f} m, ๊ธฐ๋ก {dur:.1f}์ด, ๊ตฌ๊ฐ {len(segs)}๊ฐ")
|
| 1131 |
+
print(f" ์๋๋ฆฌ์ค {n_ok}๊ฐ โ {out_dir}")
|
| 1132 |
+
return 0
|
| 1133 |
+
|
| 1134 |
+
|
| 1135 |
+
def build_parser() -> argparse.ArgumentParser:
|
| 1136 |
+
p = argparse.ArgumentParser(prog="nusim", description=__doc__.splitlines()[0])
|
| 1137 |
+
sub = p.add_subparsers(dest="cmd", required=True)
|
| 1138 |
+
|
| 1139 |
+
idx = sub.add_parser("index", help="์ฌ ์ธ๋ฑ์ค ์์ฑ/์กฐํ").add_subparsers(dest="sub", required=True)
|
| 1140 |
+
|
| 1141 |
+
b = idx.add_parser("build", help="์๋ณธ ํ
์ด๋ธ์ ํ์ด ์ฌ ์ธ๋ฑ์ค๋ฅผ ์บ์์ ๋ง๋ ๋ค (์ฝ 40์ด)")
|
| 1142 |
+
b.add_argument("--force", action="store_true", help="์ด๋ฏธ ์์ด๋ ๋ค์ ๋ง๋ ๋ค")
|
| 1143 |
+
b.add_argument("--no-sweeps", action="store_true",
|
| 1144 |
+
help="ํคํ๋ ์(2 Hz)๋ง ๋ด๋๋ค. 12๋ฐฐ ์์ง๋ง ์ฌ๊ตฌ์ฑ ํ์ง์ด ๋จ์ด์ง๋ค")
|
| 1145 |
+
b.add_argument("--locations", nargs="+", metavar="LOC",
|
| 1146 |
+
help="๊ธฐ๋ณธ๊ฐ์ ์ฑ๊ฐํฌ๋ฅด 3์ข
. ์: --locations boston-seaport")
|
| 1147 |
+
b.set_defaults(func=_cmd_index_build)
|
| 1148 |
+
|
| 1149 |
+
l = idx.add_parser("list", help="์ธ๋ฑ์ฑ๋ ์ฌ ๋ชฉ๋ก")
|
| 1150 |
+
l.add_argument("--location", help="singapore-onenorth ๋ฑ์ผ๋ก ํํฐ")
|
| 1151 |
+
l.add_argument("--grep", help="์ด๋ฆ/์ค๋ช
์ ์ด ๋ฌธ์์ด์ด ๋ ์ฌ๋ง")
|
| 1152 |
+
l.add_argument("--limit", type=int, help="์์ N๊ฐ๋ง")
|
| 1153 |
+
l.set_defaults(func=_cmd_index_list)
|
| 1154 |
+
|
| 1155 |
+
s = sub.add_parser("scene", help="์ฌ ์์ธ").add_subparsers(dest="sub", required=True)
|
| 1156 |
+
sh = s.add_parser("show", help="์ฌ ํ๋์ ์นด๋ฉ๋ผ/ํ๋ ์ ์์ฝ")
|
| 1157 |
+
sh.add_argument("name", help="์: scene-0003")
|
| 1158 |
+
sh.set_defaults(func=_cmd_scene_show)
|
| 1159 |
+
|
| 1160 |
+
rn = sub.add_parser("run", help="์์
์ ์(configs/jobs/<์ด๋ฆ>.json) ์คํ โ ์ฌ๋ฌ ๋จ๊ณ๋ฅผ ์์๋๋ก, ๋๋ ๋จ๊ณ๋ ๊ฑด๋๋ด๋ค")
|
| 1161 |
+
rn.add_argument("job", help="์์
์ด๋ฆ ๋๋ JSON ๊ฒฝ๋ก")
|
| 1162 |
+
rn.add_argument("--dry-run", action="store_true", help="์คํํ์ง ์๊ณ ํผ์น ๋จ๊ณ๋ง ๋ณด์ฌ ์ค๋ค")
|
| 1163 |
+
rn.set_defaults(func=_cmd_run)
|
| 1164 |
+
|
| 1165 |
+
r = sub.add_parser("recon", help="3DGS ์ฌ๊ตฌ์ฑ").add_subparsers(dest="sub", required=True)
|
| 1166 |
+
ri = r.add_parser("init", help="LIDAR_TOP โ ์ด๊ธฐ ๊ฐ์ฐ์์ (outputs/gs/<scene>/init.ply)")
|
| 1167 |
+
ri.add_argument("name")
|
| 1168 |
+
ri.add_argument("--voxel", type=float, default=0.15, help="๋ณต์
ํฌ๊ธฐ m (๊ธฐ๋ณธ 0.15)")
|
| 1169 |
+
ri.add_argument("--max-points", type=int, default=1_500_000)
|
| 1170 |
+
ri.set_defaults(func=_cmd_recon_init)
|
| 1171 |
+
|
| 1172 |
+
rt = r.add_parser("train", help="gsplat ํ์ต โ point_cloud.ply + report.json")
|
| 1173 |
+
p._nusim_train = rt
|
| 1174 |
+
rt.add_argument("name")
|
| 1175 |
+
rt.add_argument("--iters", type=int, default=60_000,
|
| 1176 |
+
help="๊ธฐ๋ณธ 6๋ง. ์ฌ๋น ๋ทฐ๊ฐ 1,200์ฅ ๋จ์ง์ด๋ผ 3๋ง์ด๋ฉด ๋ทฐ๋น 25ํ๋ฟ โ ๋ ํ์ต๋๋ค")
|
| 1177 |
+
rt.add_argument("--downscale", type=int, default=2, help="์ด๋ฏธ์ง ์ถ์ ๋น์จ (๊ธฐ๋ณธ 2 โ 800x450)")
|
| 1178 |
+
rt.add_argument("--stride", type=int, default=1, help="ํ๋ ์ ๊ฐ๊ฒฉ (2 ๋ฉด ์ฝ 6 Hz)")
|
| 1179 |
+
rt.add_argument("--keyframes-only", action="store_true", help="2 Hz ํคํ๋ ์๋ง")
|
| 1180 |
+
rt.add_argument("--image-root", default=None,
|
| 1181 |
+
help="์นด๋ฉ๋ผ ์ด๋ฏธ์ง๋ฅผ ์ด ๋ฃจํธ์์ (์๋ณธ๊ณผ ๊ฐ์ ์๋ ๊ฒฝ๋ก) โ nusim aug translate ๊ฒฐ๊ณผ ๋ฑ")
|
| 1182 |
+
rt.add_argument("--time-window", default=None, metavar="T0:T1",
|
| 1183 |
+
help="์ฌ ์์๋ถํฐ T0~T1 ์ด๋ง ํ์ต (๋น ๋ฅธ ์ฌ์ ๊ตฌ๊ฐ๋ณ๋ก ๋๋ ๋)")
|
| 1184 |
+
rt.add_argument("--max-gaussians", type=int, default=2_000_000)
|
| 1185 |
+
rt.add_argument("--eval-every", type=int, default=5000)
|
| 1186 |
+
rt.add_argument("--refine-stop", type=int, help="densify ์ข
๋ฃ ์คํ
(๊ธฐ๋ณธ min(15000, iters/2))")
|
| 1187 |
+
rt.add_argument("--absgrad", action="store_true", help="์ ๋๊ฐ gradient ๋ก densify (์ยท์ธ๋ถ์ ์ ๋ฆฌ)")
|
| 1188 |
+
rt.add_argument("--grow-grad2d", type=float, help="densify ์๊ณ๊ฐ (๊ธฐ๋ณธ 0.0002, absgrad ๋ฉด 0.0008)")
|
| 1189 |
+
rt.add_argument("--depth-lambda", type=float, default=0.0, help="๋ผ์ด๋ค ๊น์ด ๊ฐ๋
๊ฐ์ค์น (0 ์ด๋ฉด ๋)")
|
| 1190 |
+
rt.add_argument("--strategy", choices=["default", "mcmc"], default="default",
|
| 1191 |
+
help="densify ์ ๋ต. mcmc = 3DGS-MCMC (cap = --max-gaussians, ๋ถํฌ๋ช
๋ยท์ค์ผ์ผ ์ ๊ทํ 0.01)")
|
| 1192 |
+
rt.add_argument("--sky", action="store_true", help="ํ๋์ ํ์ต ๊ฐ๋ฅํ ํ๋ธ๋งต์ผ๋ก ๋ถ๋ฆฌ (ํ๋ ๋ง์คํฌ BCE)")
|
| 1193 |
+
rt.add_argument("--no-sky", dest="sky", action="store_false", help="ํ๋ ๋ถ๋ฆฌ ๋๊ธฐ (ํ๋ฆฌ์
์ด ์ผ ๊ฒ์ ๋ ๋)")
|
| 1194 |
+
rt.add_argument("--preset", help="ํ์ต ํ๋ฆฌ์
(configs/train/<์ด๋ฆ>.json ๋๋ ๊ฒฝ๋ก) โ ๋ช
๋ น์ค ๊ฐ์ด ์ฐ์ ")
|
| 1195 |
+
rt.add_argument("--objects", choices=("dynamic", "agents", "vehicles"), default="dynamic",
|
| 1196 |
+
help="ํ์ต์์ ์ง์ธ ๊ฐ์ฒด. dynamic: ์์ง์ด๋ ๊ฒ๋ง (์ฃผ์ฐจ ์ฐจ๋์ ๋ฐฐ๊ฒฝ), "
|
| 1197 |
+
"agents: ์ฐจ๋ยท์ฌ๋ยท์์ ๊ฑฐ ์ ๋ถ โ ์ฃผํ ๋งต๋ง ๋จ๊ธฐ๋ ํ๊ฒฝ ์ ์ฉ ์ฌ๊ตฌ์ฑ, "
|
| 1198 |
+
"vehicles: ์ฐจ๋์ ๊ฐ์ฒด ๊ฐ์ฐ์์์ผ๋ก ํจ๊ป ํ์ต(์์
์ผ๋ก ๋ด๋ณด๋), ์ฌ๋ยท์์ ๊ฑฐ๋ง ์ง์")
|
| 1199 |
+
rt.add_argument("--opacity-reg", type=float, help="๋ถํฌ๋ช
๋ ์ ๊ทํ (mcmc ๊ธฐ๋ณธ 0.01)")
|
| 1200 |
+
rt.add_argument("--aniso-reg", type=float, default=0.0, help="๋ฐ๋ ์ต์ ๊ฐ์ค์น (๊ฐ์ฅ ๊ธด ์ถ/๋ ๋ฒ์งธ ์ถ ๋น ๋ฒ์ , 0 = ๋)")
|
| 1201 |
+
rt.add_argument("--aniso-max", type=float, default=6.0, help="๋ฐ๋ ์ต์ ๊ฐ ์์๋๋ ์ถ ๋น")
|
| 1202 |
+
rt.add_argument("--ground-lambda", type=float, default=0.0,
|
| 1203 |
+
help="์ง๋ฉด ๋ชจ๋ธ (HUGSIM ๋ค์ค ํ๋ฉด): ๋
ธ๋ฉด ๊ฐ์ฐ์์์ ์ํ์ผ๋ก ๊ณ ์ + ์นธ๋ณ ๋์ด ๋ถ์ฐ ๊ฐ์ค์น (0 ์ด๋ฉด ๋)")
|
| 1204 |
+
rt.add_argument("--scale-reg", type=float, help="์ค์ผ์ผ ์ ๊ทํ (mcmc ๊ธฐ๋ณธ 0.01)")
|
| 1205 |
+
rt.add_argument("--depth-scale", type=int, default=2,
|
| 1206 |
+
help="๊น์ด ์ง๋ ์ถ์ ๋น์จ (sweep ๋์ ์ด๋ฉด 1/2 ์์๋ 79%% ์ฑ์์ง๋ค)")
|
| 1207 |
+
rt.add_argument("--no-camera-adjust", action="store_true", help="์นด๋ฉ๋ผ ์ธ๋ถ ํ๋ผ๋ฏธํฐ ๋ณด์ ๋๊ธฐ")
|
| 1208 |
+
rt.add_argument("--out", help="๊ฒฐ๊ณผ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>). ์คํ์ ๋๋ํ ๋๋ฆด ๏ฟฝ๏ฟฝ")
|
| 1209 |
+
rt.add_argument("--tile", help="์ฌ๋ฌ ์ฃผํ์ ํ ๋ชจ๋ธ๋ก (configs/tiles/<์ด๋ฆ>.json, recon.tile). name ๋์ ํ์ผ์ anchor")
|
| 1210 |
+
rt.set_defaults(func=_cmd_recon_train)
|
| 1211 |
+
|
| 1212 |
+
rte = r.add_parser("tile-eval", help="ํ์ผ์์ ๋บ ์ฃผํ(์ ์ฐจ์ )์ ์ค์ ์ฌ์ง์ผ๋ก ๋ชจ๋ธ๋ค์ ๋น๊ต")
|
| 1213 |
+
rte.add_argument("tile")
|
| 1214 |
+
rte.add_argument("--dir", action="append", required=True, help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (์ฌ๋ฌ ๋ฒ)")
|
| 1215 |
+
rte.add_argument("--stride", type=int, default=4, help="๋บ ์ฃผํ์ ํ๋ ์ ๊ฐ๊ฒฉ")
|
| 1216 |
+
rte.add_argument("--out", help="๊ธฐ๋ณธ outputs/tiles/<ํ์ผ>")
|
| 1217 |
+
rte.set_defaults(func=_cmd_recon_tile_eval)
|
| 1218 |
+
|
| 1219 |
+
rds = r.add_parser("distill", help="Difix ๋ก ๊ณ ์น ์ ์์ (์ฐจ์ ์ด๋)์ ์์ฌ ์ ๋ต์ผ๋ก 3DGS ๋ฏธ์ธ์กฐ์ ")
|
| 1220 |
+
rds.add_argument("name")
|
| 1221 |
+
rds.add_argument("--shifts", default="-3,-1.5,1.5,3", help="์์ฐจ ์ข์ฐ ์ด๋๋ m (์ผํ)")
|
| 1222 |
+
rds.add_argument("--every", type=int, default=6, help="์นด๋ฉ๋ผ๋ณ ํ์ต ํ๋ ์ ๊ฐ๊ฒฉ")
|
| 1223 |
+
rds.add_argument("--iters", type=int, default=15000)
|
| 1224 |
+
rds.add_argument("--pseudo-prob", type=float, default=0.3)
|
| 1225 |
+
rds.add_argument("--pseudo-weight", type=float, default=0.5)
|
| 1226 |
+
rds.add_argument("--max-gaussians", type=int, default=2_600_000)
|
| 1227 |
+
rds.add_argument("--eval-every", type=int, default=5000)
|
| 1228 |
+
rds.add_argument("--base", help="์ถ๋ฐ ๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>)")
|
| 1229 |
+
rds.add_argument("--depth-lambda", type=float, default=0.0, help="๋ผ์ด๋ค ๊น์ด ๊ฐ๋
์ ์ง ๊ฐ์ค์น")
|
| 1230 |
+
rds.add_argument("--progressive", help="์ ์ง ์ฆ๋ฅ: ์ด๋๋ ๋จ๊ณ (์ผํ, m) โ ์ 0.5,1,2,3. ์ฃผ๋ฉด --shiftsยท--iters ๋์ ")
|
| 1231 |
+
rds.add_argument("--iters-per-stage", type=int, default=3000, help="์ ์ง ์ฆ๋ฅ ๋จ๊ณ๋น ๋ฏธ์ธ์กฐ์ ์คํ
")
|
| 1232 |
+
rds.add_argument("--out", help="๊ธฐ๋ณธ <base>_difix")
|
| 1233 |
+
rds.set_defaults(func=_cmd_recon_distill)
|
| 1234 |
+
|
| 1235 |
+
rb = r.add_parser("batch", help="์ฌ๋ฌ ์ฌ ์ผ๊ด ์ฌ๊ตฌ์ฑ (๊ธฐ๋ณธ: ์ฃผ๊ฐ ์ฌ ์ ์ฒด โ outputs/gs_day/<scene>, ์ด์ด ํ๊ธฐ ๊ฐ๋ฅ)")
|
| 1236 |
+
rb.add_argument("--scenes", help="์ผํ๋ก ๊ตฌ๋ถํ ์ฌ ์ด๋ฆ (๊ธฐ๋ณธ: ์ค๋ช
์ night/rain ์ด ์๋ ์ฌ ์ ๋ถ)")
|
| 1237 |
+
rb.add_argument("--location", help="singapore-onenorth ๋ฑ์ผ๋ก ์ ํ")
|
| 1238 |
+
rb.add_argument("--limit", type=int, help="์์์ N ์ฌ๋ง")
|
| 1239 |
+
rb.add_argument("--order", choices=["location", "coverage"], default="location",
|
| 1240 |
+
help="location: ์ง์ญยท์ฌ ๋ฒํธ ์ (๊ธฐ๋ณธ). coverage: ์ ๋๋ก๋ฅผ ๋ง์ด ๋ฎ๋ ์ฌ๋ถํฐ โ ๋งต์ด ๋นจ๋ฆฌ ์ฑ์์ง๋ค")
|
| 1241 |
+
rb.add_argument("--preset", default="day_map", help="ํ์ต ํ๋ฆฌ์
(configs/train/<์ด๋ฆ>.json)")
|
| 1242 |
+
rb.add_argument("--iters", type=int, help="ํ๋ฆฌ์
์ iters ๋ฅผ ๋ฎ์ด์ด๋ค")
|
| 1243 |
+
rb.add_argument("--root", help="๊ฒฐ๊ณผ ๋ฃจํธ (๊ธฐ๋ณธ outputs/gs_day)")
|
| 1244 |
+
rb.add_argument("--no-sky", action="store_true", help="ํ๋ฆฌ์
๊ณผ ์๊ด์์ด ํ๋ ๋ถ๋ฆฌ ์์ด")
|
| 1245 |
+
rb.add_argument("--segment-m", type=float, default=60.0,
|
| 1246 |
+
help="๊ตฌ๊ฐ ๊ธธ์ด ์ํ (์์ฐจ ์ด๋ m). ๋ ๊ธด ์ฌ์ ๊ฐ์ ๊ธธ์ด ๊ตฌ๊ฐ์ผ๋ก ๋๋ ๋ฐ๋ก ํ์ต (๊ธฐ๋ณธ 60)")
|
| 1247 |
+
rb.add_argument("--overlap-m", type=float, default=10.0, help="์ด์ ๊ตฌ๊ฐ ๊ฒน์นจ (m, ๊ธฐ๋ณธ 10)")
|
| 1248 |
+
rb.add_argument("--after", action="append", metavar="CMD",
|
| 1249 |
+
help="์ฌ์ ๊ตฌ๊ฐ์ด ๋ค ๋๋๋ฉด ๋๋ฆด nusim ๋ช
๋ น ({scene}ยท{root} ์นํ, ์ฌ๋ฌ ๋ฒ). ์: 'aug map {scene} --root {root}'")
|
| 1250 |
+
rb.add_argument("--keep-init", action="store_true", help="init.ply ๋ฅผ ์ง์ฐ์ง ์๋๋ค (๊ธฐ๋ณธ: ํ์ต ๋ค ์ง์, ๋์คํฌ ์ ์ฝ)")
|
| 1251 |
+
rb.set_defaults(func=_cmd_recon_batch)
|
| 1252 |
+
|
| 1253 |
+
rc = r.add_parser("cycle", help="์ฌ์ดํด ๋ ๋ ๋ฒค์น๋งํฌ: ์์ผ๋ก ์ฎ๊ธด ๋ ๋๋ก ๋ชจ๋ธ์ ๋ค์ ํ์ตํด ์๋ ์์ (์ ๋ต ์ฌ์ง)์์ ์ฐ๋ค")
|
| 1254 |
+
rc.add_argument("name")
|
| 1255 |
+
rc.add_argument("--base", help="์ค์ ์ฌ์ง์ผ๋ก ํ์ตํ ๋ชจ๋ธ (๊ธฐ๋ณธ outputs/gs_day/<scene>)")
|
| 1256 |
+
rc.add_argument("--iters", type=int, default=15_000)
|
| 1257 |
+
rc.add_argument("--shift", type=float, default=1.0, help="์์ผ๋ก ์ฎ๊ธฐ๋ ๊ฑฐ๋ฆฌ m (๊ธฐ๋ณธ 1, ๋
ผ๋ฌธ๊ณผ ๊ฐ์)")
|
| 1258 |
+
rc.set_defaults(func=_cmd_recon_cycle)
|
| 1259 |
+
rp = r.add_parser("prune", help="floater ์ ๋ฆฌ: ๋ผ์ด๋ค๊ฐ ๋น์ด ์๋ค๊ณ ๋ณธ ๊ณต๊ฐ์ ๋ ์๋ ๊ฐ์ฐ์์์ ์ง์ด๋ค")
|
| 1260 |
+
rp.add_argument("name")
|
| 1261 |
+
rp.add_argument("--dir", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ: render ๊ฐ ์ฐ๋ ๋ชจ๋ธ)")
|
| 1262 |
+
rp.add_argument("--out", help="๊ฒฐ๊ณผ (๊ธฐ๋ณธ <dir>_pruned)")
|
| 1263 |
+
rp.add_argument("--margin", type=float, default=0.15, help="๋ผ์ด๋ค ํ๋ฉด๋ณด๋ค ์ด ๋น์จ ์ด์ ์์ด๋ฉด ์๋ฐ")
|
| 1264 |
+
rp.add_argument("--ratio", type=float, default=0.5, help="์๋ฐ ๋น์จ์ด ์ด๋ณด๋ค ํฌ๋ฉด ์ง์")
|
| 1265 |
+
rp.add_argument("--min-obs", type=int, default=3, help="ํ์ ์ ํ์ํ ์ต์ ๊ด์ธก ํ์")
|
| 1266 |
+
rp.set_defaults(func=_cmd_recon_prune)
|
| 1267 |
+
rsl = r.add_parser("skylabel", help="ํ๋ ๊ฐ์ฐ์์ ํ์: ํ๋ ๋ง์คํฌ์ ๋จ์ด์ง๋ ๊ฐ์ฐ์์ (๋ฐค์ ํ๋์ด ํ์๊ฒ ๋จ๋ ๋ฌธ์ )")
|
| 1268 |
+
rsl.add_argument("name")
|
| 1269 |
+
rsl.add_argument("--dir", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ: render ๊ฐ ์ฐ๋ ๋ชจ๋ธ)")
|
| 1270 |
+
rsl.add_argument("--ratio", type=float, default=0.4, help="ํ๋ ํฝ์
๋น์จ์ด ์ด๋ณด๋ค ํฌ๋ฉด ํ๋")
|
| 1271 |
+
rsl.add_argument("--min-obs", type=int, default=3, help="ํ์ ์ ํ์ํ ์ต์ ๊ด์ธก ํ์")
|
| 1272 |
+
rsl.set_defaults(func=_cmd_recon_skylabel)
|
| 1273 |
+
rse = r.add_parser("semantic", help="๊ฐ์ฐ์์ ์๋งจํฑ ๋ผ๋ฒจ (lidarseg ๋ค์๊ฒฐ) โ ๋ชจ๋ธ ์ semantic.rgba โ ๋ทฐ์ด์ ์๋งจํฑ ์นด๋ฉ๋ผ")
|
| 1274 |
+
rse.add_argument("names", nargs="*", help="์ฌ (๊ตฌ๊ฐ ๋ชจ๋ธ์ด๋ฉด ๊ตฌ๊ฐ ์ ๋ถ)")
|
| 1275 |
+
rse.add_argument("--location", help="์ด ์ง์ญ์ ํ์ต๋ ์ฌ ์ ๋ถ (singapore-onenorth ๋ฑ)")
|
| 1276 |
+
rse.add_argument("--root", help="์ผ๊ด ์ฌ๊ตฌ์ฑ ๋ฃจํธ (๊ธฐ๋ณธ outputs/gs_day)")
|
| 1277 |
+
rse.add_argument("--dir", help="๊ตฌ๊ฐ ๋ชจ๋ธ์ด ์๋ ์ฌ์ ๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ: render ๊ฐ ์ฐ๋ ๋ชจ๋ธ)")
|
| 1278 |
+
rse.add_argument("--force", action="store_true", help="์์ด๋ ๋ค์")
|
| 1279 |
+
rse.set_defaults(func=_cmd_recon_semantic)
|
| 1280 |
+
rbn = r.add_parser("bench", help="๋ฒค์น ์ธํธ(<cycle>/bench)์ ์์ธก์ ์ ๋ต๊ณผ ๋น๊ต (PSNRยทSSIMยทLPIPSยทDISTS)")
|
| 1281 |
+
rbn.add_argument("bench")
|
| 1282 |
+
rbn.add_argument("--pred", default="render,base", help="๋น๊ตํ ์ ๋ฏธ์ฌ๋ค (render = ์ฌ์ดํด ๋ ๋, base = ์ ๋ชจ๋ธ, "
|
| 1283 |
+
"difix = Difix ์ ์ฒด ๋ณด์ , difixm = ์ ๋ขฐ๋ ๋ง์คํฌ ๋ธ๋ ๋ฉ)")
|
| 1284 |
+
rbn.add_argument("--run-difix", action="store_true", help="๋จผ์ Difix ๋ฅผ ๋๋ ค *_difix / *_difixm ์ ๋ง๋ ๋ค")
|
| 1285 |
+
rbn.set_defaults(func=_cmd_recon_bench)
|
| 1286 |
+
|
| 1287 |
+
re_ = r.add_parser("eval", help="ํ์ต๋ ply ๋ฅผ hold-out ๋ทฐ๋ก ๋ค์ ํ๊ฐ (์ ์ /์ ์ฒด PSNR)")
|
| 1288 |
+
re_.add_argument("name")
|
| 1289 |
+
re_.add_argument("--dir", help="point_cloud.ply ์ color_correction.pt ๊ฐ ์๋ ๊ณณ (๊ธฐ๋ณธ outputs/gs/<scene>)")
|
| 1290 |
+
re_.add_argument("--downscale", type=int, default=None, help="๊ธฐ๋ณธ: ํ์ต ๋ ๊ฐ (report.json)")
|
| 1291 |
+
re_.add_argument("--geom", action="store_true",
|
| 1292 |
+
help="๊ธฐํยทfloater ์งํ๋: ์ ์ /๊ตฌ๋ฉ(๋์ ๋ง์คํฌ) ๊น์ด ์ค์ฐจ, ํ๋ ฮฑ (nusim.recon.metrics)")
|
| 1293 |
+
re_.add_argument("--lpips", action="store_true", help="LPIPS (AlexNet) ๋ โ ์ ์ฒด ์์ ยท ์ ์ ์์ญ")
|
| 1294 |
+
re_.set_defaults(func=_cmd_recon_eval)
|
| 1295 |
+
|
| 1296 |
+
rd = sub.add_parser("render", help="Vulkan ๋ ๋๋ฌ (src/vkgs) ์ฐ๋").add_subparsers(dest="sub", required=True)
|
| 1297 |
+
ex = rd.add_parser("export", help="์นด๋ฉ๋ผ ๋ฆฌ๊ทธ + ๋งต ์ ํ์ผ โ outputs/gs/<scene>/{rig,map}.txt")
|
| 1298 |
+
ex.add_argument("name")
|
| 1299 |
+
ex.add_argument("--model", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>_depth, ์์ผ๋ฉด <scene>)")
|
| 1300 |
+
ex.add_argument("--downscale", type=int, default=1, help="๋ฆฌ๊ทธ ํด์๋ ์ถ์ (๊ธฐ๋ณธ 1 = 1600x900)")
|
| 1301 |
+
ex.add_argument("--margin", type=float, default=60.0, help="๊ถค์ ์ฃผ๋ณ ๋งต ๋ฒ์ m")
|
| 1302 |
+
ex.set_defaults(func=_cmd_render_export)
|
| 1303 |
+
cp = rd.add_parser("compare", help="ํ๊ฐ ๋ทฐ์์ Vulkan ๋ ๋ vs gsplat ๋ ๋ ํฝ์
๋น๊ต")
|
| 1304 |
+
cp.add_argument("name")
|
| 1305 |
+
cp.add_argument("--model", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>_depth, ์์ผ๋ฉด <scene>)")
|
| 1306 |
+
cp.add_argument("-n", type=int, default=24, help="๋น๊ตํ ๋ทฐ ์")
|
| 1307 |
+
cp.set_defaults(func=_cmd_render_compare)
|
| 1308 |
+
vw = rd.add_parser("view", help="์ฐฝ ๋ชจ๋ ๋ทฐ์ด (์์ ์นด๋ฉ๋ผ / 6์บ ์ถ์ข
/ ๊ถค์ ์ฌ์ / BEV / ๋งต)")
|
| 1309 |
+
vw.add_argument("name")
|
| 1310 |
+
vw.add_argument("--model", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>_depth, ์์ผ๋ฉด <scene>)")
|
| 1311 |
+
vw.add_argument("--downscale", type=int, default=1)
|
| 1312 |
+
vw.add_argument("--autoshot", type=int, default=0, help="N ํ๋ ์ ๋ค ์คํฌ๋ฆฐ์ท ์ฐ๊ณ ์ข
๋ฃ (์ ๊ฒ์ฉ)")
|
| 1313 |
+
vw.add_argument("--weather", help="ํฉ์ฑ ๋ ์จ ์์๊ฐ: clear|overcast|dusk|night|rain|heavy_rain|fog|night_rain "
|
| 1314 |
+
"๋๋ daylight=0.3,rain=0.5,... (์ฐฝ ์ ์ฌ๋ผ์ด๋๋ก๋ ์กฐ์ , F1 ํจ๋)")
|
| 1315 |
+
vw.add_argument("--replay", action="store_true", help="๋ก๊ทธ ์ฌ์ ๋ชจ๋(์ฐจ์ ๋ฌ๋ฆฐ CAM_FRONT ์์ )๋ก ์์ (๊ธฐ๋ณธ: ์ด์ ๋ชจ๋, ์ฐจ ๋ค ์์ )")
|
| 1316 |
+
vw.add_argument("--agents", action="store_true",
|
| 1317 |
+
help="nuScenes ๋ก๊ทธ์ ์์ง์ด๋ ์ฐจ ๊ถค์ ์ glTF ์ฐจ๋์ ๋ฃ๋๋ค (๊ธฐ๋ณธ: ํ๊ฒฝ๋ง)")
|
| 1318 |
+
vw.set_defaults(func=_cmd_render_view)
|
| 1319 |
+
rm = rd.add_parser("map", help="์ง์ญ ๋งต: ํ์ต๋ ์ฌ ๊ตฌ๊ฐ๋ค์ ์ด์ด ํ ๊ณต๊ฐ์์ ์ฃผํ (singapore-onenorth ๋ฑ)")
|
| 1320 |
+
rm.add_argument("location", help="singapore-onenorth | singapore-queenstown | singapore-hollandvillage")
|
| 1321 |
+
rm.add_argument("--start", help="์ถ๋ฐ ์ฌ (๊ธฐ๋ณธ: ๊ตฌ๊ฐ์ด ๊ฐ์ฅ ๋ง์ ์ฌ)")
|
| 1322 |
+
rm.add_argument("--root", help="์ผ๊ด ์ฌ๊ตฌ์ฑ ๋ฃจํธ (๊ธฐ๋ณธ outputs/gs_day)")
|
| 1323 |
+
rm.add_argument("--replay", action="store_true", help="์ถ๋ฐ ์ฌ์ ๋ก๊ทธ ์ฌ์ ๋ชจ๋๋ก ์์")
|
| 1324 |
+
rm.add_argument("--sensors", action="store_true",
|
| 1325 |
+
help="ํ์ต๋ ๊ตฌ๊ฐ์ด ๋ฐ๋ ์ฌ์ ๋ผ์ด๋คยท๋ ์ด๋ ๋ณต์
์๋๋ ๋ค์ ๋ง๋ ๋ค (์๋ ์ฌ ๊ฒ๏ฟฝ๏ฟฝ ๊ธฐ๋ณธ์ผ๋ก ๋ง๋ ๋ค, ์ฌ๋น 1~2๋ถ)")
|
| 1326 |
+
rm.add_argument("--no-sensors", action="store_true", help="์๋ ๋ณต์
์๋๋ฅผ ๋ง๋ค์ง ์๋๋ค (๊ทธ ์ฌ์ ์ผ์ ๊บผ์ง)")
|
| 1327 |
+
rm.add_argument("--no-view", action="store_true", help="ํ์ผ๋ง ๋ง๋ค๊ณ ์ฐฝ์ ๋์ฐ์ง ์๋๋ค")
|
| 1328 |
+
rm.add_argument("--no-semantic", action="store_true",
|
| 1329 |
+
help="์๋ ์๋งจํฑ ๋ผ๋ฒจ์ ๋ง๋ค์ง ์๋๋ค (๊ทธ ๊ตฌ๊ฐ์ F3 ์๋งจํฑ ์นด๋ฉ๋ผ๊ฐ ๋น์ด ๋ณด์ธ๋ค, ๋์ค์ nusim recon semantic)")
|
| 1330 |
+
rm.set_defaults(viewer_args=[]) # '--' ๋ค๋ vkgs_viewer ์ต์
๊ทธ๋๋ก (main ์ด ๋ผ์ด ๋๋ค): -- --autopilot --show-sensors
|
| 1331 |
+
rm.set_defaults(func=_cmd_render_map)
|
| 1332 |
+
|
| 1333 |
+
ve = rd.add_parser("encode", help="PNG ํ๋ ์ (vkgs_viewer --record-screen) โ mp4, ์ค๋ช
๊ธ์ ์น๊ธฐ")
|
| 1334 |
+
ve.add_argument("frames", help="frame_*.png ๋๋ ํฐ๋ฆฌ")
|
| 1335 |
+
ve.add_argument("out", help="์ถ๋ ฅ .mp4")
|
| 1336 |
+
ve.add_argument("--fps", type=float, default=20.0, help="๋
นํ๋ ๊ณ ์ 50 ms ์คํ
= 20 fps")
|
| 1337 |
+
ve.add_argument("--width", type=int, default=1280, help="๊ฐ๋ก ํฝ์
๋ก ์ค์ธ๋ค (0 = ๊ทธ๋๋ก)")
|
| 1338 |
+
ve.add_argument("--bitrate", default="3M", help="libx264 ๋นํธ๋ ์ดํธ (๋ฐํ์๋ฃ์ ๋ฃ์ ํฌ๊ธฐ: 3M ร 20์ด โ 7.5 MB)")
|
| 1339 |
+
ve.add_argument("--caption", default="", help="์ผ์ชฝ ์๋ ๊ธ์")
|
| 1340 |
+
ve.add_argument("--font-size", type=int, default=24)
|
| 1341 |
+
ve.add_argument("--poster", help="๊ฐ์ด๋ฐ ํ๋ ์์ ์ด jpg ๋ก (๋ฐํ์๋ฃ ํ์ง ํ๋ฉด)")
|
| 1342 |
+
ve.set_defaults(func=_cmd_render_encode)
|
| 1343 |
+
vd = rd.add_parser("video", help="6์บ + BEV(๋งต ์ค๋ฒ๋ ์ด) ์ฌ์ ์์ โ outputs/gs/<scene>/<scene>_sim.mp4")
|
| 1344 |
+
vd.add_argument("name")
|
| 1345 |
+
vd.add_argument("--model", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>_depth, ์์ผ๋ฉด <scene>)")
|
| 1346 |
+
vd.add_argument("--frames", help="ํ๋ ์ ๋ฒ์ A:B (๊ธฐ๋ณธ ์ ์ฒด)")
|
| 1347 |
+
vd.add_argument("--fps", type=float, default=12.0, help="๊ธฐ๋ณธ 12 (nuScenes ์นด๋ฉ๋ผ ์ฝ 12 Hz)")
|
| 1348 |
+
vd.add_argument("--bev-height", type=float, default=70.0)
|
| 1349 |
+
vd.set_defaults(func=_cmd_render_video)
|
| 1350 |
+
|
| 1351 |
+
asp = sub.add_parser("asset", help="nuScenes ์ค์ ์ฐจ๋ โ 3DGS ์์
").add_subparsers(dest="sub", required=True)
|
| 1352 |
+
sc_ = asp.add_parser("scan", help="์์ฐจ๊ฐ ๊ฐ๊น์ด ์ง๋๊ฐ๋ฉฐ ์ฌ๋ฌ ๋ฐฉ์์์ ๋ณธ ์ฃผ์ฐจ ์ฐจ๋ ์ฐพ๊ธฐ (์ฃผ๊ฐ ์ฌ)")
|
| 1353 |
+
sc_.add_argument("--max-dist", type=float, default=5.0)
|
| 1354 |
+
sc_.add_argument("--limit", type=int, default=15)
|
| 1355 |
+
sc_.set_defaults(func=_cmd_asset_scan)
|
| 1356 |
+
xt = asp.add_parser("extract", help="ํ์ต๋ ์ฌ์์ ์ฃผ์ฐจ ์ฐจ๋์ ์๋ผ ๋ฐ์ค ๋ก์ปฌ 3DGS ์์
์ผ๋ก")
|
| 1357 |
+
xt.add_argument("name", help="์ฌ (์: scene-0916)")
|
| 1358 |
+
xt.add_argument("--instance", type=int, required=True)
|
| 1359 |
+
xt.add_argument("--model", help="์ฌ ๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ outputs/gs/<scene>_depth)")
|
| 1360 |
+
xt.add_argument("--no-mirror", action="store_true", help="์ข์ฐ ๋์นญ ๋ณต์ฌ ๋๊ธฐ")
|
| 1361 |
+
xt.add_argument("--out")
|
| 1362 |
+
xt.add_argument("--as-ego", action="store_true", help="outputs/assets/ego_car.ply ๋ก ์ง์ ")
|
| 1363 |
+
xt.set_defaults(func=_cmd_asset_extract)
|
| 1364 |
+
|
| 1365 |
+
ib = rd.add_parser("ibl", help="6์บ ํ๋
ธ๋ผ๋ง โ HDR ํ๋ธ๋งต โ prefiltered diffuse/specular + BRDF LUT โ BC6H KTX2")
|
| 1366 |
+
ib.add_argument("name")
|
| 1367 |
+
ib.add_argument("--model")
|
| 1368 |
+
ib.add_argument("--frame", type=int, help="CAM_FRONT ํ๋ ์ (๊ธฐ๋ณธ ๊ฐ์ด๋ฐ)")
|
| 1369 |
+
ib.add_argument("--size", type=int, default=512)
|
| 1370 |
+
ib.add_argument("--samples", type=int, default=512, help="GGX ์ํ ์")
|
| 1371 |
+
ib.add_argument("--fill", choices=["extrap", "gs"], default="extrap",
|
| 1372 |
+
help="์ฌ์ง์ด ์๋ ์ยท์๋ ์ฑ์: extrap = ๋ ๊ฐ์ฅ์๋ฆฌ ์ ์ธ์ฝ (๊ธฐ๋ณธ), gs = 3DGS ๋ ๋")
|
| 1373 |
+
ib.set_defaults(func=_cmd_render_ibl)
|
| 1374 |
+
|
| 1375 |
+
ag = sub.add_parser("aug", help="๋ฐ์ดํฐ์
๋ค์์ฑ ์ฆ๊ฐ").add_subparsers(dest="sub", required=True)
|
| 1376 |
+
agr = ag.add_parser("rig", help="์ฌ๊ตฌ์ฑ ์ฅ๋ฉด์ 6์บ ์์ (์ฅ์ฐฉ ์์นยท์์ธ)์ ๋ฐ๊ฟ ๋ค์ ๋ ๋ โ outputs/aug/rig")
|
| 1377 |
+
agr.add_argument("name")
|
| 1378 |
+
agr.add_argument("--only", help="๋ณํ ์ด๋ฆ๋ค (์ผํ). ๊ธฐ๋ณธ: ์ ๋ถ")
|
| 1379 |
+
agr.add_argument("--variants", help="๋ณํ ์ ์ JSON (๊ธฐ๋ณธ configs/aug/rig_variants.json)")
|
| 1380 |
+
agr.add_argument("--model", help="๋ชจ๋ธ ๋๋ ํฐ๋ฆฌ (๊ธฐ๋ณธ: render ๊ฐ ์ฐ๋ ๋ชจ๋ธ)")
|
| 1381 |
+
agr.add_argument("--agents", action="store_true", help="์ง์ด ์ฐจ๋์ glTF ๋ก ๋ค์ ๋ฃ๋๋ค (ํ๊ฒฝ ์ ์ฉ ๋ชจ๋ธ์ผ ๋)")
|
| 1382 |
+
agr.add_argument("--out", help="๊ธฐ๋ณธ outputs/aug/rig")
|
| 1383 |
+
agr.set_defaults(func=_cmd_aug_rig)
|
| 1384 |
+
agm = ag.add_parser("map", help="์ผ๊ด ์ฌ๊ตฌ์ฑ(๊ตฌ๊ฐ ๋ชจ๋ธ) ์ฌ์ ๋ฆฌ๊ทธ ๋ณํ์ผ๋ก ๋ค์ ๋ ๋ โ outputs/aug/map")
|
| 1385 |
+
agm.add_argument("name")
|
| 1386 |
+
agm.add_argument("--root", help="์ผ๊ด ์ฌ๊ตฌ์ฑ ๋ฃจํธ (๊ธฐ๋ณธ outputs/gs_day)")
|
| 1387 |
+
agm.add_argument("--only", help="๋ณํ ์ด๋ฆ๋ค (์ผํ). ๊ธฐ๋ณธ: ๋ ์จ ์๋ ๋ณํ ์ ๋ถ")
|
| 1388 |
+
agm.add_argument("--variants", help="๋ณํ ์ ์ JSON (๊ธฐ๋ณธ configs/aug/rig_variants.json)")
|
| 1389 |
+
agm.add_argument("--keyframes-only", action="store_true", help="ํคํ๋ ์(2 Hz sample)๋ง")
|
| 1390 |
+
agm.add_argument("--agents", action="store_true", help="์ง์ด ์ฐจ๋์ glTF ๋ก ๋ค์ ๋ฃ๋๋ค (IBL ์ด ์์ผ๋ฉด ๊ตฝ๋๋ค)")
|
| 1391 |
+
agm.add_argument("--out", help="๊ธฐ๋ณธ outputs/aug/map")
|
| 1392 |
+
agm.set_defaults(func=_cmd_aug_map)
|
| 1393 |
+
agx = ag.add_parser("artifixer", help="ArtiFixer ํ์ฐ ๋ณด์ + ์ฐจ์ ํ์ฒ๋ฆฌ ์ฆ๊ฐ (configs/aug/artifixer.json) โ outputs/aug/aug_artfixer/<์ฌ>_lanefix")
|
| 1394 |
+
agx.add_argument("names", nargs="*", help="์ฌ (๊ธฐ๋ณธ: ํ์ต ยท aug map ์ด ๋๋ ์ฌ ์ ๋ถ, ๋๋ ์์๋๋ก)")
|
| 1395 |
+
agx.add_argument("--cams", help="์นด๋ฉ๋ผ๋ค (์ผํ, ๊ธฐ๋ณธ ์ค์ cams = CAM_FRONT)")
|
| 1396 |
+
agx.add_argument("--root", help="์ผ๊ด ์ฌ๊ตฌ์ฑ ๋ฃจํธ (๊ธฐ๋ณธ outputs/gs_day)")
|
| 1397 |
+
agx.add_argument("--limit", type=int, help="์ ๋๋ ์ฌ ์ค ์์์ N๊ฐ๋ง")
|
| 1398 |
+
agx.add_argument("--force", action="store_true", help="๋๋ฌ์ด๋ ๋ค์")
|
| 1399 |
+
agx.set_defaults(func=_cmd_aug_artifixer)
|
| 1400 |
+
agt = ag.add_parser("translate", help="๋ฐคยท๋น ์์ โ ๋ฎ (CycleGAN-Turbo) โ outputs/aug/translate/<๋ชจ๋ธ>/ (์๋ณธ๊ณผ ๊ฐ์ ์๋ ๊ฒฝ๋ก)")
|
| 1401 |
+
agt.add_argument("names", nargs="+")
|
| 1402 |
+
agt.add_argument("--models", default="night_to_day",
|
| 1403 |
+
help="์ฐจ๋ก๋ก ์ ์ฉํ ๋ณํ (์ผํ): night_to_day, rainy_to_clear โ ์: rainy_to_clear,night_to_day")
|
| 1404 |
+
agt.add_argument("--size", default="912x512", help="๋ชจ๋ธ ์
๋ ฅ ํฌ๊ธฐ (8์ ๋ฐฐ์). ์๋ณธ ๋น์จ 16:9")
|
| 1405 |
+
agt.add_argument("--stride", type=int, default=1)
|
| 1406 |
+
agt.add_argument("--keyframes-only", action="store_true")
|
| 1407 |
+
agt.add_argument("--out", help="๊ธฐ๋ณธ outputs/aug/translate")
|
| 1408 |
+
agt.set_defaults(func=_cmd_aug_translate)
|
| 1409 |
+
sm = sub.add_parser("sim", help="closed-loop ํ๊ฐ (HUGSIM ์: ์๋๋ฆฌ์ค ยท ์ ์ฑ
ยท HD-Score)").add_subparsers(dest="sub", required=True)
|
| 1410 |
+
smr = sm.add_parser("run", help="์๋๋ฆฌ์ค ํ๋๋ฅผ ์ ์ฑ
์ผ๋ก ๋๊น์ง (์ฐฝ ์์, vkgs_viewer --serve) โ ์ํผ์๋ ๋ก๊ทธ + HD-Score")
|
| 1411 |
+
smr.add_argument("scenario", help="configs/scenarios/<์ด๋ฆ>.json ์ ์ด๋ฆ ๋๋ ๊ฒฝ๋ก")
|
| 1412 |
+
smr.add_argument("--policy", default="replay", help="replay (๊ธฐ๋ก ๊ถค์ ์ ๋ฌธ๊ฐ) | constant (์ผ์ ์๋ ์ง์ง)")
|
| 1413 |
+
smr.add_argument("--out", help="๊ธฐ๋ณธ outputs/closed_loop/<์๋๋ฆฌ์ค>/<์ ์ฑ
>")
|
| 1414 |
+
smr.add_argument("--size", help="๊ด์ธก ํด์๋ WxH (๊ธฐ๋ณธ configs/closed_loop.json render)")
|
| 1415 |
+
smr.add_argument("--max-duration", type=float, help="์ ํ ์๊ฐ ๋ฎ์ด์ฐ๊ธฐ (์ด)")
|
| 1416 |
+
smr.add_argument("--no-front", action="store_true", help="์ ๋ฐฉ ์นด๋ฉ๋ผ jpg ๋ฅผ ๋จ๊ธฐ์ง ์๋๋ค")
|
| 1417 |
+
smr.add_argument("--no-video", action="store_true", help="front.mp4 ๋ฅผ ๋ง๋ค์ง ์๋๋ค")
|
| 1418 |
+
smr.set_defaults(func=_cmd_sim_run)
|
| 1419 |
+
sms = sm.add_parser("score", help="์ํผ์๋ ๋ก๊ทธ โ NC/DAC/TTC/COM/Rc/HD-Score")
|
| 1420 |
+
sms.add_argument("episodes", nargs="+", help="์ํผ์๋ ๋๋ ํฐ๋ฆฌ (episode.json + steps.csv)")
|
| 1421 |
+
sms.add_argument("-v", "--verbose", action="store_true", help="๊ฒฐ์ ์์ ๋ง๋ค")
|
| 1422 |
+
sms.add_argument("--json", action="store_true")
|
| 1423 |
+
sms.set_defaults(func=_cmd_sim_score)
|
| 1424 |
+
smm = sm.add_parser("make-scenarios", help="์ผ๊ด ์ฌ๊ตฌ์ฑ ์ฌ(๊ตฌ๊ฐ์ด ๋ค ๋๋ ๊ฒ)๋ง๋ค Easy ์๋๋ฆฌ์ค โ configs/scenarios/")
|
| 1425 |
+
smm.add_argument("scenes", nargs="*", help="๊ธฐ๋ณธ: --root ์ ๋ชจ๋ ์ฌ")
|
| 1426 |
+
smm.add_argument("--root", help="๊ธฐ๋ณธ outputs/gs_day")
|
| 1427 |
+
smm.add_argument("--route-m", type=float, help="๊ฒฝ๋ก ๊ธธ์ด ์ํ (๊ธฐ๋ณธ configs/closed_loop.json scenarios.route_m, 0 = ๋๊น์ง)")
|
| 1428 |
+
smm.add_argument("--out", help="๊ธฐ๋ณธ configs/scenarios")
|
| 1429 |
+
smm.set_defaults(func=_cmd_sim_make_scenarios)
|
| 1430 |
+
d = sub.add_parser("doctor", help="ํ๊ฒฝ ์ง๋จ (vkgs-doctor ์ ๋์ผ)")
|
| 1431 |
+
d.set_defaults(func=_cmd_doctor)
|
| 1432 |
+
|
| 1433 |
+
return p
|
| 1434 |
+
|
| 1435 |
+
|
| 1436 |
+
def _preset_defaults(argv: list[str] | None) -> dict:
|
| 1437 |
+
"""`recon train --preset X` ์ ํ๋ฆฌ์
๊ฐ (configs/train/X.json). ๋ช
๋ น์ค ๊ฐ์ด ์ฐ์ ํ๋๋ก ๊ธฐ๋ณธ๊ฐ์ผ๋ก๋ง ์ด๋ค."""
|
| 1438 |
+
av = sys.argv[1:] if argv is None else argv
|
| 1439 |
+
if "--preset" not in av or av[:2] != ["recon", "train"]:
|
| 1440 |
+
return {}
|
| 1441 |
+
from nusim.config import train_preset
|
| 1442 |
+
|
| 1443 |
+
return train_preset(av[av.index("--preset") + 1])
|
| 1444 |
+
|
| 1445 |
+
|
| 1446 |
+
def main(argv: list[str] | None = None) -> int:
|
| 1447 |
+
parser = build_parser()
|
| 1448 |
+
preset = _preset_defaults(argv)
|
| 1449 |
+
if preset:
|
| 1450 |
+
rt = parser._nusim_train # recon train ํ์ ํ์
|
| 1451 |
+
known = {a.dest for a in rt._actions}
|
| 1452 |
+
bad = sorted(set(preset) - known)
|
| 1453 |
+
if bad:
|
| 1454 |
+
print(f"\033[1;31m์ค๋ฅ:\033[0m ํ๋ฆฌ์
์ ๋ชจ๋ฅด๋ ํค: {bad} (nusim recon train --help)", file=sys.stderr)
|
| 1455 |
+
return 1
|
| 1456 |
+
rt.set_defaults(**preset)
|
| 1457 |
+
argv = list(sys.argv[1:] if argv is None else argv)
|
| 1458 |
+
tail = []
|
| 1459 |
+
if "--" in argv and argv[:2] == ["render", "map"]: # render map ์ '--' ๋ค = ๋ทฐ์ด ์ต์
|
| 1460 |
+
k = argv.index("--")
|
| 1461 |
+
argv, tail = argv[:k], argv[k + 1:]
|
| 1462 |
+
args = parser.parse_args(argv)
|
| 1463 |
+
if tail:
|
| 1464 |
+
args.viewer_args = tail
|
| 1465 |
+
try:
|
| 1466 |
+
from nusim.log import setup
|
| 1467 |
+
from nusim.paths import output_dir
|
| 1468 |
+
|
| 1469 |
+
tag = "_".join(x for x in (args.cmd, getattr(args, "sub", None)) if x)
|
| 1470 |
+
path = setup(output_dir() / "logs", tag=tag)
|
| 1471 |
+
LOG.debug("๋ช
๋ น %s (๋ก๊ทธ %s)", " ".join(sys.argv), path)
|
| 1472 |
+
except Exception: # ๋ก๊ทธ ์ค์ ์คํจ๊ฐ ๋ช
๋ น์ ๋ง์ผ๋ฉด ์ ๋๋ค (์: outputs ์ฐ๊ธฐ ๋ถ๊ฐ)
|
| 1473 |
+
pass
|
| 1474 |
+
try:
|
| 1475 |
+
return args.func(args)
|
| 1476 |
+
except (FileNotFoundError, RuntimeError, ValueError) as e:
|
| 1477 |
+
print(f"\033[1;31m์ค๋ฅ:\033[0m {e}", file=sys.stderr)
|
| 1478 |
+
return 1
|
| 1479 |
+
except KeyboardInterrupt:
|
| 1480 |
+
return 130
|
| 1481 |
+
except BrokenPipeError: # `nusim index list | head` ์ฒ๋ผ ํ์ดํ๊ฐ ๋จผ์ ๋ซํ ๊ฒฝ์ฐ
|
| 1482 |
+
sys.stderr.close()
|
| 1483 |
+
return 0
|
| 1484 |
+
|
| 1485 |
+
|
| 1486 |
+
if __name__ == "__main__":
|
| 1487 |
+
raise SystemExit(main())
|
src/nusim/nusim/config.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์ค์ ํ์ผ (configs/*.json) โ ์ฝ๋์ ๋ฐ์ ๋๋ ์กฐ์ ๊ฐ์ ํ๊ณณ์ ๋ชจ์๋ค.
|
| 2 |
+
|
| 3 |
+
configs/sim.json ์๋ฎฌ๋ ์ดํฐ(๋ทฐ์ด): ๋ ์จ ํ๋ฆฌ์
, ํ๋, 3์ธ์นญ ์นด๋ฉ๋ผ, ์ถฉ๋, ์์ฐจ (viewer.cpp ๋ ์ฝ๋๋ค)
|
| 4 |
+
configs/train/<์ด๋ฆ>.json ํ์ต ํ๋ฆฌ์
โ `nusim recon train --preset <์ด๋ฆ|๊ฒฝ๋ก>` (๋ช
๋ น์ค ๊ฐ์ด ์ฐ์ )
|
| 5 |
+
configs/jobs/<์ด๋ฆ>.json ์์
์ ์ โ `nusim run <์ด๋ฆ|๊ฒฝ๋ก>` (nusim.jobs)
|
| 6 |
+
|
| 7 |
+
JSON ์ "_doc" ํค๋ ์ค๋ช
์ฉ์ด๋ผ ๋ฌด์ํ๋ค.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
from functools import lru_cache
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
from nusim.paths import workspace_root
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def configs_dir() -> Path:
|
| 20 |
+
return workspace_root() / "configs"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def resolve(kind: str, name_or_path: str) -> Path:
|
| 24 |
+
"""'day_map' โ configs/<kind>/day_map.json, ๊ฒฝ๋ก๋ฉด ๊ทธ๋๋ก."""
|
| 25 |
+
p = Path(name_or_path)
|
| 26 |
+
if p.suffix == ".json" or p.exists():
|
| 27 |
+
return p if p.is_absolute() or p.exists() else workspace_root() / p
|
| 28 |
+
return configs_dir() / kind / f"{name_or_path}.json"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load(path: Path) -> dict:
|
| 32 |
+
if not path.exists():
|
| 33 |
+
raise FileNotFoundError(f"์ค์ ํ์ผ์ด ์์ต๋๋ค: {path}")
|
| 34 |
+
return _strip_doc(json.loads(path.read_text()))
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _strip_doc(x):
|
| 38 |
+
if isinstance(x, dict):
|
| 39 |
+
return {k: _strip_doc(v) for k, v in x.items() if not k.startswith("_doc")}
|
| 40 |
+
if isinstance(x, list):
|
| 41 |
+
return [_strip_doc(v) for v in x]
|
| 42 |
+
return x
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def sim_config_path() -> Path:
|
| 46 |
+
return configs_dir() / "sim.json"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@lru_cache(maxsize=1)
|
| 50 |
+
def sim_config() -> dict:
|
| 51 |
+
p = sim_config_path()
|
| 52 |
+
return load(p) if p.exists() else {}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def train_preset(name_or_path: str) -> dict:
|
| 56 |
+
"""ํ์ต ํ๋ฆฌ์
โ argparse dest ์ด๋ฆ์ ๊ธฐ๋ณธ๊ฐ dict (์: {"iters": 15000, "strategy": "mcmc", ...})."""
|
| 57 |
+
return load(resolve("train", name_or_path))
|
src/nusim/nusim/data/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""nuScenes ์ฝ๊ธฐ ๊ณ์ธต โ ์ฌ ์ธ๋ฑ์ค, ์นด๋ฉ๋ผ ๋ฆฌ๊ทธ, ego ๊ถค์ ."""
|
| 2 |
+
|
| 3 |
+
from nusim.data.index import SceneIndex, build_index, load_catalog, load_scene
|
| 4 |
+
|
| 5 |
+
__all__ = ["SceneIndex", "build_index", "load_catalog", "load_scene"]
|
src/nusim/nusim/data/index.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์ฑ๊ฐํฌ๋ฅด ์ฌ ์ธ๋ฑ์ค โ ์๋ณธ ํ
์ด๋ธ์ ํ ๋ฒ๋ง ํ์ด ์บ์๋ก ๊ตณํ๋ค.
|
| 2 |
+
|
| 3 |
+
์ ์ด ๊ณ์ธต์ด ํ์ํ๊ฐ:
|
| 4 |
+
nuscenes-devkit ์ NuScenes() ๋ ๋ชจ๋ ํ
์ด๋ธ์ ํต์งธ๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฐ๋ค
|
| 5 |
+
(trainval ๊ธฐ์ค 2~3๋ถ, 10 GB ์ด์). ํ์ผ ๋ชฉ๋ก ํ๋ ๋ณด์๊ณ ๋งค๋ฒ ๋ผ ๋น์ฉ์ด ์๋๋ค.
|
| 6 |
+
๊ฒ๋ค๊ฐ ์๋ณธ์ NFS ์์ ์์ด ๋ฐ๋ณต ์ ๊ทผ์ด ํนํ ๋น์ธ๋ค.
|
| 7 |
+
|
| 8 |
+
์ฌ๊ธฐ์๋ ํ
์ด๋ธ์ ์คํธ๋ฆฌ๋ฐ์ผ๋ก ๋ฑ ํ ๋ฒ ํ์ด ์ฐ๋ฆฌ๊ฐ ์ฐ๋ ๊ฒ๋ง ์ฌ๋ณ๋ก
|
| 9 |
+
์๋ผ ๋ก์ปฌ ์บ์์ ์ ์ฅํ๋ค. ์ดํ ์กฐํ๋ ์ฌ ํ๋๋น ์ MB ๋ฅผ ์ฝ๋ ์ผ์ด ๋๋ค.
|
| 10 |
+
|
| 11 |
+
devkit ์ ๋์ฒดํ๋ ค๋ ๊ฒ ์๋๋ค โ ์ด๋
ธํ
์ด์
/๋งต API ๊ฐ ํ์ํ๋ฉด devkit ์ ์ด๋ค.
|
| 12 |
+
์ด๊ฑด ์ฌ๊ตฌ์ฑยท์๋ฎฌ๋ ์ด์
๋ฃจํ๊ฐ ๋งค๋ฒ ๋๋๋ฆฌ๋ '๋จ๊ฑฐ์ด ๊ฒฝ๋ก' ๋ง ๋์ด๋ด๋ ๊ฒ์ด๋ค.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import logging
|
| 18 |
+
import gzip
|
| 19 |
+
import json
|
| 20 |
+
import time
|
| 21 |
+
from dataclasses import dataclass, field
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from typing import Iterable, Iterator, Sequence
|
| 24 |
+
|
| 25 |
+
from nusim import CAMERAS, SINGAPORE_MAPS
|
| 26 |
+
from nusim.io import iter_json_array
|
| 27 |
+
from nusim.paths import DatasetPaths, cache_dir, dataset
|
| 28 |
+
|
| 29 |
+
INDEX_VERSION = 4 # 2: LIDAR_TOP ํคํ๋ ์, 3: 3D ๋ฐ์ค ์ด๋
ธํ
์ด์
, 4: LIDAR_TOP sweep(20 Hz)
|
| 30 |
+
_INDEX_DIRNAME = "scene_index"
|
| 31 |
+
LIDAR = "LIDAR_TOP"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# --------------------------------------------------------------------------- ์๋ฃ๊ตฌ์กฐ
|
| 35 |
+
_log = logging.getLogger(__name__).info # ์งํ ๋ฉ์์ง ๊ธฐ๋ณธ ์ถ๋ ฅ โ nusim.log (์ฝ์ + ๋ก๊ทธ ํ์ผ)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass(frozen=True)
|
| 39 |
+
class CameraCalib:
|
| 40 |
+
"""์ฐจ์ฒด(ego) ์ขํ๊ณ ๊ธฐ์ค ์นด๋ฉ๋ผ ์ธ๋ถ/๋ด๋ถ ํ๋ผ๋ฏธํฐ.
|
| 41 |
+
|
| 42 |
+
translation/rotation ์ nuScenes ์ calibrated_sensor ๊ทธ๋๋ก๋ค:
|
| 43 |
+
์นด๋ฉ๋ผ โ ego ๋ณํ์ด๋ฉฐ rotation ์ wxyz ์ฟผํฐ๋์ธ.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
translation: tuple[float, float, float]
|
| 47 |
+
rotation: tuple[float, float, float, float] # w, x, y, z
|
| 48 |
+
intrinsic: tuple[tuple[float, float, float], ...] # 3x3
|
| 49 |
+
width: int
|
| 50 |
+
height: int
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@dataclass(frozen=True)
|
| 54 |
+
class Frame:
|
| 55 |
+
"""์นด๋ฉ๋ผ ํ ๋์ ํ ์ฅ."""
|
| 56 |
+
|
| 57 |
+
timestamp: int # ๋ง์ดํฌ๋ก์ด
|
| 58 |
+
filename: str # ๋ฐ์ดํฐ์
๋ฃจํธ ๊ธฐ์ค ์๋๊ฒฝ๋ก
|
| 59 |
+
ego_translation: tuple[float, float, float]
|
| 60 |
+
ego_rotation: tuple[float, float, float, float] # w, x, y, z, ์ ์ญ(๋งต) โ ego
|
| 61 |
+
is_keyframe: bool
|
| 62 |
+
|
| 63 |
+
def path(self, paths: DatasetPaths) -> Path:
|
| 64 |
+
return paths.root / self.filename
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@dataclass(frozen=True)
|
| 68 |
+
class Box:
|
| 69 |
+
"""ํคํ๋ ์ ํ๋์์ ๊ฐ์ฒด ํ๋์ 3D ๋ฐ์ค. ์ ์ญ(๋งต) ์ขํ.
|
| 70 |
+
|
| 71 |
+
nuScenes sample_annotation ๊ทธ๋๋ก๋ค: size ๋ (w, l, h) โ ๋ฐ์ค ๋ก์ปฌ x ๊ฐ ๊ธธ์ด(l),
|
| 72 |
+
y ๊ฐ ํญ(w). ๊ผญ์ง์ ๊ณ์ฐ์ nusim.data.rig.box_corners ๋ฅผ ์ด๋ค.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
timestamp: int # ๋ง์ดํฌ๋ก์ด (ํคํ๋ ์ sample ์ ์๊ฐ)
|
| 76 |
+
instance: int # SceneIndex.instances ์ ๋ฒํธ
|
| 77 |
+
translation: tuple[float, float, float]
|
| 78 |
+
size: tuple[float, float, float] # w, l, h
|
| 79 |
+
rotation: tuple[float, float, float, float] # w, x, y, z
|
| 80 |
+
attribute: str # 'vehicle.moving' ๋ฑ. ์์ผ๋ฉด ''
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@dataclass
|
| 84 |
+
class SceneIndex:
|
| 85 |
+
"""ํ ์ฌ์ ์ ์ฒด ๋ฉํฐ๋ทฐ ์ธ๋ฑ์ค."""
|
| 86 |
+
|
| 87 |
+
token: str
|
| 88 |
+
name: str # 'scene-0001'
|
| 89 |
+
location: str # 'singapore-onenorth'
|
| 90 |
+
description: str
|
| 91 |
+
log_token: str
|
| 92 |
+
date_captured: str
|
| 93 |
+
vehicle: str
|
| 94 |
+
nbr_samples: int
|
| 95 |
+
calib: dict[str, CameraCalib] = field(default_factory=dict)
|
| 96 |
+
frames: dict[str, list[Frame]] = field(default_factory=dict)
|
| 97 |
+
# LIDAR_TOP ์ ๋ถ (sweep 20 Hz, ์ฌ๋น ์ฝ 400์ฅ). ํคํ๋ ์(2 Hz)์ is_keyframe ์ผ๋ก ๊ตฌ๋ถํ๋ค.
|
| 98 |
+
# 3DGS ์ด๊ธฐํ์ ๊น์ด ๊ฐ๋
(์นด๋ฉ๋ผ-๋ผ์ด๋ค ์ตํฉ)์ ์ด๋ค.
|
| 99 |
+
# lidar_calib ์ intrinsic ์ ๋น์ด ์๋ค (nuScenes ๊ฐ ๋ผ์ด๋ค์ ์ฃผ์ง ์์).
|
| 100 |
+
lidar_calib: CameraCalib | None = None
|
| 101 |
+
lidar: list[Frame] = field(default_factory=list)
|
| 102 |
+
|
| 103 |
+
def lidar_keyframes(self) -> list[Frame]:
|
| 104 |
+
return [f for f in self.lidar if f.is_keyframe]
|
| 105 |
+
# 3D ๋ฐ์ค (ํคํ๋ ์์๋ง ์๋ค). instances[i] = (instance_token, category_name)
|
| 106 |
+
instances: list[tuple[str, str]] = field(default_factory=list)
|
| 107 |
+
boxes: list[Box] = field(default_factory=list)
|
| 108 |
+
|
| 109 |
+
@property
|
| 110 |
+
def n_images(self) -> int:
|
| 111 |
+
return sum(len(v) for v in self.frames.values())
|
| 112 |
+
|
| 113 |
+
def window(self, t0: float, t1: float) -> "SceneIndex":
|
| 114 |
+
"""์ฌ ์์๋ถํฐ [t0, t1] ์ด ๊ตฌ๊ฐ๋ง ๋จ๊ธด ์ฌ๋ณธ (์นด๋ฉ๋ผยท๋ผ์ด๋ค ํ๋ ์). ๋ฐ์ค๋ ๊ทธ๋๋ก โ
|
| 115 |
+
๋น ๋ฅธ ์ฌ(์๋ฐฑ m)์ ๊ตฌ๊ฐ๋ณ๋ก ๋๋ ์ฌ๊ตฌ์ฑํ๊ฑฐ๋ ์ง๋จํ ๋ ์ด๋ค."""
|
| 116 |
+
from dataclasses import replace
|
| 117 |
+
|
| 118 |
+
start = min(f.timestamp for fr in self.frames.values() for f in fr[:1])
|
| 119 |
+
lo, hi = start + int(t0 * 1e6), start + int(t1 * 1e6)
|
| 120 |
+
keep = lambda fr: [f for f in fr if lo <= f.timestamp <= hi]
|
| 121 |
+
return replace(self, frames={c: keep(fr) for c, fr in self.frames.items()}, lidar=keep(self.lidar))
|
| 122 |
+
|
| 123 |
+
def duration_s(self) -> float:
|
| 124 |
+
ts = [f.timestamp for v in self.frames.values() for f in v]
|
| 125 |
+
return (max(ts) - min(ts)) / 1e6 if ts else 0.0
|
| 126 |
+
|
| 127 |
+
def keyframes(self, camera: str) -> list[Frame]:
|
| 128 |
+
return [f for f in self.frames.get(camera, ()) if f.is_keyframe]
|
| 129 |
+
|
| 130 |
+
# ---- ์ง๋ ฌํ (์บ์๋ ์ฌ๋์ด ์ฝ์ ์ ์๋ gzip JSON) ----
|
| 131 |
+
def to_json(self) -> dict:
|
| 132 |
+
return {
|
| 133 |
+
"index_version": INDEX_VERSION,
|
| 134 |
+
"scene": {
|
| 135 |
+
"token": self.token, "name": self.name, "location": self.location,
|
| 136 |
+
"description": self.description, "log_token": self.log_token,
|
| 137 |
+
"date_captured": self.date_captured, "vehicle": self.vehicle,
|
| 138 |
+
"nbr_samples": self.nbr_samples,
|
| 139 |
+
},
|
| 140 |
+
"calib": {
|
| 141 |
+
cam: {
|
| 142 |
+
"translation": list(c.translation), "rotation": list(c.rotation),
|
| 143 |
+
"intrinsic": [list(r) for r in c.intrinsic],
|
| 144 |
+
"width": c.width, "height": c.height,
|
| 145 |
+
}
|
| 146 |
+
for cam, c in self.calib.items()
|
| 147 |
+
},
|
| 148 |
+
# ํ๋ ์์ ๋ฆฌ์คํธ-of-๋ฆฌ์คํธ๋ก ์ ์ฅํ๋ค. ํค ์ด๋ฆ์ 60๋ง ๋ฒ ๋ฐ๋ณตํ๋ ๊ฒ๋ณด๋ค
|
| 149 |
+
# ํ์ผ์ด 3๋ฐฐ ์๊ณ ํ์ฑ๋ ๊ทธ๋งํผ ๋น ๋ฅด๋ค.
|
| 150 |
+
"frame_schema": ["timestamp", "filename", "ex", "ey", "ez", "qw", "qx", "qy", "qz", "is_keyframe"],
|
| 151 |
+
"frames": {
|
| 152 |
+
cam: [
|
| 153 |
+
[f.timestamp, f.filename, *f.ego_translation, *f.ego_rotation, int(f.is_keyframe)]
|
| 154 |
+
for f in fs
|
| 155 |
+
]
|
| 156 |
+
for cam, fs in self.frames.items()
|
| 157 |
+
},
|
| 158 |
+
"lidar_calib": None if self.lidar_calib is None else {
|
| 159 |
+
"translation": list(self.lidar_calib.translation),
|
| 160 |
+
"rotation": list(self.lidar_calib.rotation),
|
| 161 |
+
},
|
| 162 |
+
"lidar": [
|
| 163 |
+
[f.timestamp, f.filename, *f.ego_translation, *f.ego_rotation, int(f.is_keyframe)]
|
| 164 |
+
for f in self.lidar
|
| 165 |
+
],
|
| 166 |
+
"instances": [list(i) for i in self.instances],
|
| 167 |
+
"box_schema": ["timestamp", "instance", "tx", "ty", "tz", "w", "l", "h",
|
| 168 |
+
"qw", "qx", "qy", "qz", "attribute"],
|
| 169 |
+
"boxes": [
|
| 170 |
+
[b.timestamp, b.instance, *b.translation, *b.size, *b.rotation, b.attribute]
|
| 171 |
+
for b in self.boxes
|
| 172 |
+
],
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
@classmethod
|
| 176 |
+
def from_json(cls, d: dict) -> "SceneIndex":
|
| 177 |
+
if d.get("index_version") != INDEX_VERSION:
|
| 178 |
+
raise ValueError(
|
| 179 |
+
f"์ธ๋ฑ์ค ๋ฒ์ ๋ถ์ผ์น ({d.get('index_version')} != {INDEX_VERSION}). "
|
| 180 |
+
"'nusim index build --force' ๋ก ๋ค์ ๋ง๋์ธ์."
|
| 181 |
+
)
|
| 182 |
+
s = d["scene"]
|
| 183 |
+
obj = cls(**s)
|
| 184 |
+
obj.calib = {
|
| 185 |
+
cam: CameraCalib(
|
| 186 |
+
translation=tuple(c["translation"]), rotation=tuple(c["rotation"]),
|
| 187 |
+
intrinsic=tuple(tuple(r) for r in c["intrinsic"]),
|
| 188 |
+
width=c["width"], height=c["height"],
|
| 189 |
+
)
|
| 190 |
+
for cam, c in d["calib"].items()
|
| 191 |
+
}
|
| 192 |
+
obj.frames = {
|
| 193 |
+
cam: [
|
| 194 |
+
Frame(timestamp=r[0], filename=r[1],
|
| 195 |
+
ego_translation=(r[2], r[3], r[4]),
|
| 196 |
+
ego_rotation=(r[5], r[6], r[7], r[8]),
|
| 197 |
+
is_keyframe=bool(r[9]))
|
| 198 |
+
for r in rows
|
| 199 |
+
]
|
| 200 |
+
for cam, rows in d["frames"].items()
|
| 201 |
+
}
|
| 202 |
+
if lc := d.get("lidar_calib"):
|
| 203 |
+
obj.lidar_calib = CameraCalib(
|
| 204 |
+
translation=tuple(lc["translation"]), rotation=tuple(lc["rotation"]),
|
| 205 |
+
intrinsic=(), width=0, height=0,
|
| 206 |
+
)
|
| 207 |
+
obj.lidar = [
|
| 208 |
+
Frame(timestamp=r[0], filename=r[1],
|
| 209 |
+
ego_translation=(r[2], r[3], r[4]),
|
| 210 |
+
ego_rotation=(r[5], r[6], r[7], r[8]),
|
| 211 |
+
is_keyframe=bool(r[9]))
|
| 212 |
+
for r in d.get("lidar", ())
|
| 213 |
+
]
|
| 214 |
+
obj.instances = [tuple(i) for i in d.get("instances", ())]
|
| 215 |
+
obj.boxes = [
|
| 216 |
+
Box(timestamp=r[0], instance=r[1], translation=(r[2], r[3], r[4]),
|
| 217 |
+
size=(r[5], r[6], r[7]), rotation=(r[8], r[9], r[10], r[11]), attribute=r[12])
|
| 218 |
+
for r in d.get("boxes", ())
|
| 219 |
+
]
|
| 220 |
+
return obj
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# --------------------------------------------------------------------------- ๋น๋
|
| 224 |
+
def _index_dir() -> Path:
|
| 225 |
+
d = cache_dir() / _INDEX_DIRNAME
|
| 226 |
+
d.mkdir(parents=True, exist_ok=True)
|
| 227 |
+
return d
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def build_index(
|
| 231 |
+
paths: DatasetPaths | None = None,
|
| 232 |
+
locations: Sequence[str] = SINGAPORE_MAPS,
|
| 233 |
+
cameras: Sequence[str] = CAMERAS,
|
| 234 |
+
include_sweeps: bool = True,
|
| 235 |
+
force: bool = False,
|
| 236 |
+
log=_log,
|
| 237 |
+
) -> dict:
|
| 238 |
+
"""ํ
์ด๋ธ์ ํ์ด ์ฌ๋ณ ์ธ๋ฑ์ค๋ฅผ ์บ์์ ์ด๋ค. ์นดํ๋ก๊ทธ(dict)๋ฅผ ๋๋ ค์ค๋ค.
|
| 239 |
+
|
| 240 |
+
include_sweeps=False ๋ฉด ํคํ๋ ์(2 Hz)๋ง ๋ด๋๋ค. ์ฌ๊ตฌ์ฑ ํ์ง์ ๋จ์ด์ง์ง๋ง
|
| 241 |
+
์ธ๋ฑ์ค๊ฐ 12๋ฐฐ ์์์ ธ ๋น ๋ฅธ ์คํ์ ์ธ ๋งํ๋ค.
|
| 242 |
+
"""
|
| 243 |
+
paths = paths or dataset()
|
| 244 |
+
out = _index_dir()
|
| 245 |
+
catalog_path = out / "catalog.json"
|
| 246 |
+
|
| 247 |
+
if catalog_path.exists() and not force:
|
| 248 |
+
log(f"์นดํ๋ก๊ทธ๊ฐ ์ด๋ฏธ ์์ต๋๋ค: {catalog_path} (--force ๋ก ์ฌ์์ฑ)")
|
| 249 |
+
return json.loads(catalog_path.read_text())
|
| 250 |
+
|
| 251 |
+
t0 = time.time()
|
| 252 |
+
|
| 253 |
+
# --- 1. ์์ ํ
์ด๋ธ๋ค (ํฉ์ณ์ 8 MB ๋จ์ง) --------------------------------
|
| 254 |
+
log("1/6 log / scene / sensor / calibrated_sensor ์ฝ๋ ์ค")
|
| 255 |
+
logs = {r["token"]: r for r in iter_json_array(paths.meta / "log.json")}
|
| 256 |
+
sensors = {r["token"]: r for r in iter_json_array(paths.meta / "sensor.json")}
|
| 257 |
+
calibs = {r["token"]: r for r in iter_json_array(paths.meta / "calibrated_sensor.json")}
|
| 258 |
+
|
| 259 |
+
# calibrated_sensor โ ์นด๋ฉ๋ผ ์ฑ๋ ์ด๋ฆ. sample_data ํํฐ๋ง์ ์ด๊ฑธ ์ด๋ค.
|
| 260 |
+
cam_set = set(cameras)
|
| 261 |
+
calib_channel = {
|
| 262 |
+
tok: sensors[c["sensor_token"]]["channel"]
|
| 263 |
+
for tok, c in calibs.items()
|
| 264 |
+
if sensors[c["sensor_token"]]["channel"] in cam_set
|
| 265 |
+
}
|
| 266 |
+
lidar_calibs = {
|
| 267 |
+
tok for tok, c in calibs.items() if sensors[c["sensor_token"]]["channel"] == LIDAR
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
wanted_loc = set(locations)
|
| 271 |
+
scenes: dict[str, dict] = {}
|
| 272 |
+
for r in iter_json_array(paths.meta / "scene.json"):
|
| 273 |
+
lg = logs[r["log_token"]]
|
| 274 |
+
if lg["location"] in wanted_loc:
|
| 275 |
+
scenes[r["token"]] = {**r, "_log": lg}
|
| 276 |
+
log(f" ๋์ ์ฌ {len(scenes)}๊ฐ ({', '.join(sorted(wanted_loc))})")
|
| 277 |
+
if not scenes:
|
| 278 |
+
raise RuntimeError(f"location {sorted(wanted_loc)} ์ ํด๋นํ๋ ์ฌ์ด ์์ต๋๋ค.")
|
| 279 |
+
|
| 280 |
+
# --- 2. sample โ scene (7 MB) -------------------------------------------
|
| 281 |
+
log("2/6 sample.json ์ฝ๋ ์ค")
|
| 282 |
+
sample_scene: dict[str, str] = {}
|
| 283 |
+
sample_ts: dict[str, int] = {}
|
| 284 |
+
for r in iter_json_array(paths.meta / "sample.json"):
|
| 285 |
+
if r["scene_token"] in scenes:
|
| 286 |
+
sample_scene[r["token"]] = r["scene_token"]
|
| 287 |
+
sample_ts[r["token"]] = r["timestamp"]
|
| 288 |
+
log(f" ํคํ๋ ์ ์ํ {len(sample_scene):,}๊ฐ")
|
| 289 |
+
|
| 290 |
+
# --- 3. sample_data ์คํธ๋ฆฌ๋ฐ (1.35 GB) ----------------------------------
|
| 291 |
+
# ๊ฐ์ฅ ๋น์ผ ๋จ๊ณ. ํ ๋ฒ๋ง ํ์ผ๋ฉด์ ํ์ํ ํ๋ง ๊ฑด์ง๋ค.
|
| 292 |
+
log("3/6 sample_data.json ์คํธ๋ฆฌ๋ฐ (1.35 GB โ 30์ด ๋ด์ธ)")
|
| 293 |
+
per_scene: dict[str, dict[str, list]] = {t: {c: [] for c in cameras} for t in scenes}
|
| 294 |
+
need_pose: set[str] = set()
|
| 295 |
+
scene_calib: dict[str, dict[str, str]] = {t: {} for t in scenes}
|
| 296 |
+
per_scene_lidar: dict[str, list] = {t: [] for t in scenes}
|
| 297 |
+
scene_lidar_calib: dict[str, str] = {}
|
| 298 |
+
seen = kept = 0
|
| 299 |
+
|
| 300 |
+
for r in iter_json_array(paths.meta / "sample_data.json"):
|
| 301 |
+
seen += 1
|
| 302 |
+
cs = r["calibrated_sensor_token"]
|
| 303 |
+
chan = calib_channel.get(cs)
|
| 304 |
+
if chan is None:
|
| 305 |
+
# ๋ผ์ด๋ค๋ sweep ๊น์ง ์ ๋ถ ๊ฑด์ง๋ค (RADAR ๋ ๋ฒ๋ฆฐ๋ค).
|
| 306 |
+
if cs in lidar_calibs:
|
| 307 |
+
st = sample_scene.get(r["sample_token"])
|
| 308 |
+
if st is not None:
|
| 309 |
+
per_scene_lidar[st].append(
|
| 310 |
+
(r["timestamp"], r["filename"], r["ego_pose_token"], bool(r["is_key_frame"])))
|
| 311 |
+
need_pose.add(r["ego_pose_token"])
|
| 312 |
+
scene_lidar_calib.setdefault(st, cs)
|
| 313 |
+
continue
|
| 314 |
+
st = sample_scene.get(r["sample_token"])
|
| 315 |
+
if st is None: # ์ฑ๊ฐํฌ๋ฅด ์ฌ์ด ์๋
|
| 316 |
+
continue
|
| 317 |
+
if not include_sweeps and not r["is_key_frame"]:
|
| 318 |
+
continue
|
| 319 |
+
per_scene[st][chan].append(
|
| 320 |
+
(r["timestamp"], r["filename"], r["ego_pose_token"], r["is_key_frame"])
|
| 321 |
+
)
|
| 322 |
+
need_pose.add(r["ego_pose_token"])
|
| 323 |
+
scene_calib[st].setdefault(chan, r["calibrated_sensor_token"])
|
| 324 |
+
kept += 1
|
| 325 |
+
log(f" {seen:,}ํ ์ค {kept:,}ํ ์ฑํ, ego_pose {len(need_pose):,}๊ฐ ํ์")
|
| 326 |
+
|
| 327 |
+
# --- 4. ego_pose ์คํธ๋ฆฌ๋ฐ (645 MB) --------------------------------------
|
| 328 |
+
log("4/6 ego_pose.json ์คํธ๋ฆฌ๋ฐ (645 MB)")
|
| 329 |
+
poses: dict[str, tuple] = {}
|
| 330 |
+
for r in iter_json_array(paths.meta / "ego_pose.json"):
|
| 331 |
+
if r["token"] in need_pose:
|
| 332 |
+
t = r["translation"]
|
| 333 |
+
q = r["rotation"]
|
| 334 |
+
poses[r["token"]] = (t[0], t[1], t[2], q[0], q[1], q[2], q[3])
|
| 335 |
+
missing = len(need_pose) - len(poses)
|
| 336 |
+
if missing:
|
| 337 |
+
raise RuntimeError(f"ego_pose {missing}๊ฐ๋ฅผ ์ฐพ์ง ๋ชปํ์ต๋๋ค โ ๋ฐ์ดํฐ์
์ด ๋ถ์์ ํฉ๋๋ค.")
|
| 338 |
+
|
| 339 |
+
# --- 5. 3D ๋ฐ์ค (sample_annotation 583 MB) ------------------------------
|
| 340 |
+
log("5/6 sample_annotation.json ์คํธ๋ฆฌ๋ฐ (583 MB)")
|
| 341 |
+
categories = {r["token"]: r["name"] for r in iter_json_array(paths.meta / "category.json")}
|
| 342 |
+
attributes = {r["token"]: r["name"] for r in iter_json_array(paths.meta / "attribute.json")}
|
| 343 |
+
inst_cat = {r["token"]: categories[r["category_token"]]
|
| 344 |
+
for r in iter_json_array(paths.meta / "instance.json")}
|
| 345 |
+
per_scene_inst: dict[str, dict[str, int]] = {t: {} for t in scenes}
|
| 346 |
+
per_scene_boxes: dict[str, list] = {t: [] for t in scenes}
|
| 347 |
+
n_box = 0
|
| 348 |
+
for r in iter_json_array(paths.meta / "sample_annotation.json"):
|
| 349 |
+
st = sample_scene.get(r["sample_token"])
|
| 350 |
+
if st is None:
|
| 351 |
+
continue
|
| 352 |
+
ids = per_scene_inst[st]
|
| 353 |
+
inst = ids.setdefault(r["instance_token"], len(ids))
|
| 354 |
+
attr = attributes[r["attribute_tokens"][0]] if r["attribute_tokens"] else ""
|
| 355 |
+
per_scene_boxes[st].append(Box(
|
| 356 |
+
timestamp=sample_ts[r["sample_token"]], instance=inst,
|
| 357 |
+
translation=tuple(r["translation"]), size=tuple(r["size"]),
|
| 358 |
+
rotation=tuple(r["rotation"]), attribute=attr,
|
| 359 |
+
))
|
| 360 |
+
n_box += 1
|
| 361 |
+
log(f" ๋ฐ์ค {n_box:,}๊ฐ")
|
| 362 |
+
|
| 363 |
+
# --- 6. ์ฌ๋ณ๋ก ์ฐ๊ธฐ ------------------------------------------------------
|
| 364 |
+
log("6/6 ์ฌ๋ณ ์ธ๋ฑ์ค ๊ธฐ๋ก")
|
| 365 |
+
catalog = {
|
| 366 |
+
"index_version": INDEX_VERSION,
|
| 367 |
+
"built_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
| 368 |
+
"dataset_root": str(paths.root),
|
| 369 |
+
"version": paths.version,
|
| 370 |
+
"locations": sorted(wanted_loc),
|
| 371 |
+
"cameras": list(cameras),
|
| 372 |
+
"include_sweeps": include_sweeps,
|
| 373 |
+
"scenes": [],
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
for tok, sc in sorted(scenes.items(), key=lambda kv: kv[1]["name"]):
|
| 377 |
+
si = SceneIndex(
|
| 378 |
+
token=tok, name=sc["name"], location=sc["_log"]["location"],
|
| 379 |
+
description=sc["description"], log_token=sc["log_token"],
|
| 380 |
+
date_captured=sc["_log"]["date_captured"], vehicle=sc["_log"]["vehicle"],
|
| 381 |
+
nbr_samples=sc["nbr_samples"],
|
| 382 |
+
)
|
| 383 |
+
for cam, cs_tok in scene_calib[tok].items():
|
| 384 |
+
c = calibs[cs_tok]
|
| 385 |
+
# ์ด ์ฑ๋์ ์ฒซ ํ๋ ์์์ ํด์๋๋ฅผ ๊ฐ์ ธ์จ๋ค (์ฌ ์์์๋ ๋ถ๋ณ).
|
| 386 |
+
si.calib[cam] = CameraCalib(
|
| 387 |
+
translation=tuple(c["translation"]),
|
| 388 |
+
rotation=tuple(c["rotation"]),
|
| 389 |
+
intrinsic=tuple(tuple(row) for row in c["camera_intrinsic"]),
|
| 390 |
+
width=1600, height=900, # nuScenes ์นด๋ฉ๋ผ๋ ์ ๋ถ 1600x900
|
| 391 |
+
)
|
| 392 |
+
for cam in cameras:
|
| 393 |
+
rows = sorted(per_scene[tok][cam])
|
| 394 |
+
si.frames[cam] = [
|
| 395 |
+
Frame(timestamp=ts, filename=fn,
|
| 396 |
+
ego_translation=poses[ep][:3], ego_rotation=poses[ep][3:],
|
| 397 |
+
is_keyframe=bool(kf))
|
| 398 |
+
for ts, fn, ep, kf in rows
|
| 399 |
+
]
|
| 400 |
+
if cs_tok := scene_lidar_calib.get(tok):
|
| 401 |
+
c = calibs[cs_tok]
|
| 402 |
+
si.lidar_calib = CameraCalib(
|
| 403 |
+
translation=tuple(c["translation"]), rotation=tuple(c["rotation"]),
|
| 404 |
+
intrinsic=(), width=0, height=0,
|
| 405 |
+
)
|
| 406 |
+
si.lidar = [
|
| 407 |
+
Frame(timestamp=ts, filename=fn,
|
| 408 |
+
ego_translation=poses[ep][:3], ego_rotation=poses[ep][3:],
|
| 409 |
+
is_keyframe=kf)
|
| 410 |
+
for ts, fn, ep, kf in sorted(per_scene_lidar[tok])
|
| 411 |
+
]
|
| 412 |
+
si.instances = [(it, inst_cat[it]) for it, _ in
|
| 413 |
+
sorted(per_scene_inst[tok].items(), key=lambda kv: kv[1])]
|
| 414 |
+
si.boxes = sorted(per_scene_boxes[tok], key=lambda b: (b.timestamp, b.instance))
|
| 415 |
+
_write_scene(si)
|
| 416 |
+
catalog["scenes"].append({
|
| 417 |
+
"name": si.name, "token": si.token, "location": si.location,
|
| 418 |
+
"n_images": si.n_images, "nbr_samples": si.nbr_samples,
|
| 419 |
+
"duration_s": round(si.duration_s(), 2), "description": si.description,
|
| 420 |
+
})
|
| 421 |
+
|
| 422 |
+
catalog_path.write_text(json.dumps(catalog, indent=2, ensure_ascii=False))
|
| 423 |
+
total_img = sum(s["n_images"] for s in catalog["scenes"])
|
| 424 |
+
log(f"์๋ฃ: ์ฌ {len(catalog['scenes'])}๊ฐ / ์ด๋ฏธ์ง {total_img:,}์ฅ / {time.time()-t0:.1f}์ด")
|
| 425 |
+
log(f" โ {out}")
|
| 426 |
+
return catalog
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
def _write_scene(si: SceneIndex) -> Path:
|
| 430 |
+
p = _index_dir() / f"{si.name}.json.gz"
|
| 431 |
+
with gzip.open(p, "wt", encoding="utf-8") as f:
|
| 432 |
+
json.dump(si.to_json(), f, ensure_ascii=False)
|
| 433 |
+
return p
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
# --------------------------------------------------------------------------- ์กฐํ
|
| 437 |
+
def load_catalog() -> dict:
|
| 438 |
+
p = _index_dir() / "catalog.json"
|
| 439 |
+
if not p.exists():
|
| 440 |
+
raise FileNotFoundError(
|
| 441 |
+
f"์ฌ ์ธ๋ฑ์ค๊ฐ ์์ต๋๋ค ({p}).\n๋จผ์ ๋ง๋์ธ์: nusim index build"
|
| 442 |
+
)
|
| 443 |
+
return json.loads(p.read_text())
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def load_scene(name: str) -> SceneIndex:
|
| 447 |
+
"""'scene-0001' ๊ฐ์ ์ด๋ฆ์ผ๋ก ์ฌ ์ธ๋ฑ์ค๋ฅผ ์ฝ๋๋ค."""
|
| 448 |
+
p = _index_dir() / f"{name}.json.gz"
|
| 449 |
+
if not p.exists():
|
| 450 |
+
raise FileNotFoundError(f"{name} ์ธ๋ฑ์ค๊ฐ ์์ต๋๋ค ({p}). 'nusim index build' ๋ฅผ ๋จผ์ ์คํํ์ธ์.")
|
| 451 |
+
with gzip.open(p, "rt", encoding="utf-8") as f:
|
| 452 |
+
return SceneIndex.from_json(json.load(f))
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def iter_scenes(location: str | None = None) -> Iterator[SceneIndex]:
|
| 456 |
+
for entry in load_catalog()["scenes"]:
|
| 457 |
+
if location and entry["location"] != location:
|
| 458 |
+
continue
|
| 459 |
+
yield load_scene(entry["name"])
|
src/nusim/nusim/data/rig.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์นด๋ฉ๋ผ ๋ฆฌ๊ทธ ์ํ โ nuScenes ํฌ์ฆ๋ฅผ ๋ ๋๋ฌ๊ฐ ์ฐ๋ ํ๋ ฌ๋ก ๋ฐ๊พผ๋ค.
|
| 2 |
+
|
| 3 |
+
์ขํ๊ณ ์ฝ์ (์ด ํ๋ก์ ํธ ์ ์ฒด์์ ๊ณ ์ ):
|
| 4 |
+
|
| 5 |
+
global / map nuScenes ๋งต ์ขํ. x=๋, y=๋ถ, z=์. ๋ฏธํฐ.
|
| 6 |
+
ego ์ฐจ์ฒด. x=์ ๋ฐฉ, y=์ข, z=์.
|
| 7 |
+
camera OpenCV ๊ด๋ก. x=์ฐ, y=ํ, z=์ ๋ฐฉ. โ nuScenes intrinsic ์ด ์ด๊ฑธ ๊ฐ์ ํ๋ค
|
| 8 |
+
clip Vulkan ๊ด๋ก. NDC x,y โ [-1,1], y ์๋๊ฐ +, z โ [0,1].
|
| 9 |
+
|
| 10 |
+
nuScenes ์๋ณธ์ ์๋ฏธ:
|
| 11 |
+
ego_pose .translation/.rotation = ego โ global
|
| 12 |
+
calibrated_sensor .translation/.rotation = camera โ ego
|
| 13 |
+
sample_annotation .translation/.rotation = box โ global, ๋ฐ์ค ์ค์ฌ ๊ธฐ์ค
|
| 14 |
+
.size = (w, l, h). ๋ฐ์ค ๋ก์ปฌ x ๊ฐ ๊ธธ์ด(l), y ๊ฐ ํญ(w), z ๊ฐ ๋์ด
|
| 15 |
+
์ฟผํฐ๋์ธ์ ์ ๋ถ (w, x, y, z) ์์๋ค.
|
| 16 |
+
|
| 17 |
+
๋ฐ๋ผ์ camera โ global = ego_pose โ calib ์ด๊ณ ,
|
| 18 |
+
๋ ๋๋ฌ๊ฐ ํ์๋ก ํ๋ view ํ๋ ฌ์ ๊ทธ ์ญ์ด๋ค.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
from dataclasses import dataclass
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
from nusim.data.index import CameraCalib, Frame
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def quat_to_matrix(q: tuple[float, float, float, float]) -> np.ndarray:
|
| 31 |
+
"""(w,x,y,z) ์ฟผํฐ๋์ธ โ 3x3 ํ์ ํ๋ ฌ.
|
| 32 |
+
|
| 33 |
+
pyquaternion ์ ์ฐ์ง ์๋ ์ด์ : ์ด ํจ์๋ ํ๋ ์๋ง๋ค 6๋ฒ์ฉ ์์ญ๋ง ๋ฒ ๋ถ๋ฆฐ๋ค.
|
| 34 |
+
์์กด์ฑ ์๋ ์์ numpy ์ชฝ์ด 10๋ฐฐ ์ด์ ๋น ๋ฅด๊ณ , ์๋ ์งง๋ค.
|
| 35 |
+
"""
|
| 36 |
+
w, x, y, z = q
|
| 37 |
+
n = w * w + x * x + y * y + z * z
|
| 38 |
+
if n < 1e-12:
|
| 39 |
+
return np.eye(3)
|
| 40 |
+
s = 2.0 / n
|
| 41 |
+
wx, wy, wz = s * w * x, s * w * y, s * w * z
|
| 42 |
+
xx, xy, xz = s * x * x, s * x * y, s * x * z
|
| 43 |
+
yy, yz, zz = s * y * y, s * y * z, s * z * z
|
| 44 |
+
return np.array([
|
| 45 |
+
[1.0 - (yy + zz), xy - wz, xz + wy],
|
| 46 |
+
[xy + wz, 1.0 - (xx + zz), yz - wx],
|
| 47 |
+
[xz - wy, yz + wx, 1.0 - (xx + yy)],
|
| 48 |
+
])
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def matrix_to_quat(r: np.ndarray) -> tuple[float, float, float, float]:
|
| 52 |
+
"""3x3 ํ์ ํ๋ ฌ โ (w,x,y,z) ์ฟผํฐ๋์ธ (quat_to_matrix ์ ์ญ, w โฅ 0). Shepperd ๋ฐฉ๋ฒ."""
|
| 53 |
+
m = np.asarray(r, dtype=np.float64)
|
| 54 |
+
tr = m[0, 0] + m[1, 1] + m[2, 2]
|
| 55 |
+
if tr > 0:
|
| 56 |
+
s = 2.0 * np.sqrt(tr + 1.0)
|
| 57 |
+
q = (0.25 * s, (m[2, 1] - m[1, 2]) / s, (m[0, 2] - m[2, 0]) / s, (m[1, 0] - m[0, 1]) / s)
|
| 58 |
+
elif m[0, 0] > m[1, 1] and m[0, 0] > m[2, 2]:
|
| 59 |
+
s = 2.0 * np.sqrt(1.0 + m[0, 0] - m[1, 1] - m[2, 2])
|
| 60 |
+
q = ((m[2, 1] - m[1, 2]) / s, 0.25 * s, (m[0, 1] + m[1, 0]) / s, (m[0, 2] + m[2, 0]) / s)
|
| 61 |
+
elif m[1, 1] > m[2, 2]:
|
| 62 |
+
s = 2.0 * np.sqrt(1.0 + m[1, 1] - m[0, 0] - m[2, 2])
|
| 63 |
+
q = ((m[0, 2] - m[2, 0]) / s, (m[0, 1] + m[1, 0]) / s, 0.25 * s, (m[1, 2] + m[2, 1]) / s)
|
| 64 |
+
else:
|
| 65 |
+
s = 2.0 * np.sqrt(1.0 + m[2, 2] - m[0, 0] - m[1, 1])
|
| 66 |
+
q = ((m[1, 0] - m[0, 1]) / s, (m[0, 2] + m[2, 0]) / s, (m[1, 2] + m[2, 1]) / s, 0.25 * s)
|
| 67 |
+
q = np.asarray(q)
|
| 68 |
+
q = q / np.linalg.norm(q)
|
| 69 |
+
if q[0] < 0:
|
| 70 |
+
q = -q
|
| 71 |
+
return tuple(float(x) for x in q)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def rt_to_matrix(r: np.ndarray, t) -> np.ndarray:
|
| 75 |
+
"""3x3 ํ์ + 3๋ฒกํฐ โ 4x4 ๋์ฐจ๋ณํ."""
|
| 76 |
+
m = np.eye(4)
|
| 77 |
+
m[:3, :3] = r
|
| 78 |
+
m[:3, 3] = t
|
| 79 |
+
return m
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def box_corners(translation, size, rotation, pad: float = 0.0) -> np.ndarray:
|
| 83 |
+
"""3D ๋ฐ์ค โ ์ ์ญ ์ขํ ๊ผญ์ง์ (8,3). devkit Box.corners() ์ ๊ฐ์ ์์.
|
| 84 |
+
|
| 85 |
+
pad ๋ ๊ฐ ๋ฐฉํฅ์ผ๋ก ๋ํ๋ ์ฌ์ (m). ์ด๋
ธํ
์ด์
๋ฐ์ค๋ ๊ฐ์ฒด์ ๋ฑ ๋ถ์ด ์์ด์
|
| 86 |
+
๊ทธ๋ฆผ์ยท๋ฐฑ๋ฏธ๋ฌยท๋ณดํ์ ํ๋ค๋ฆฌ๊ฐ ์์ ธ๋์จ๋ค.
|
| 87 |
+
"""
|
| 88 |
+
w, l, h = (np.asarray(size, dtype=np.float64) + 2 * pad)
|
| 89 |
+
x = l / 2 * np.array([1, 1, 1, 1, -1, -1, -1, -1])
|
| 90 |
+
y = w / 2 * np.array([1, -1, -1, 1, 1, -1, -1, 1])
|
| 91 |
+
z = h / 2 * np.array([1, 1, -1, -1, 1, 1, -1, -1])
|
| 92 |
+
return np.stack([x, y, z], axis=1) @ quat_to_matrix(rotation).T + np.asarray(translation)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def points_in_box(pts: np.ndarray, translation, size, rotation, pad: float = 0.0) -> np.ndarray:
|
| 96 |
+
"""(N,3) ์ ์ญ ์ขํ ์ ์ค ๋ฐ์ค ์์ ์๋ ๊ฒ์ bool ๋ง์คํฌ."""
|
| 97 |
+
local = (pts - np.asarray(translation)) @ quat_to_matrix(rotation) # R^T ๋ฅผ ๊ณฑํ๋ ๊ฒ๊ณผ ๊ฐ๋ค
|
| 98 |
+
w, l, h = (np.asarray(size, dtype=np.float64) + 2 * pad) / 2
|
| 99 |
+
return (np.abs(local[:, 0]) <= l) & (np.abs(local[:, 1]) <= w) & (np.abs(local[:, 2]) <= h)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@dataclass(frozen=True)
|
| 103 |
+
class CameraPose:
|
| 104 |
+
"""ํ ์์ ์ ์นด๋ฉ๋ผ ํ๋. ๋ ๋๋ฌ์ ๋๊ธธ ๊ฒ์ด ์ ๋ถ ๋ค์ด์๋ค."""
|
| 105 |
+
|
| 106 |
+
channel: str
|
| 107 |
+
timestamp: int
|
| 108 |
+
c2w: np.ndarray # 4x4, camera โ global
|
| 109 |
+
K: np.ndarray # 3x3 intrinsic
|
| 110 |
+
width: int
|
| 111 |
+
height: int
|
| 112 |
+
filename: str = ""
|
| 113 |
+
|
| 114 |
+
@property
|
| 115 |
+
def w2c(self) -> np.ndarray:
|
| 116 |
+
"""view ํ๋ ฌ. ํ์ ์ ์ ์น, ํํ์ด๋์ ๊ทธ์ ๋ง์ถฐ ๋ค์ ๊ณ์ฐํ๋ค
|
| 117 |
+
(์ผ๋ฐ ์ญํ๋ ฌ์ ์ฐ๋ฉด ์์น์ค์ฐจ๊ฐ ๋์ ๋๋ค)."""
|
| 118 |
+
r = self.c2w[:3, :3]
|
| 119 |
+
m = np.eye(4)
|
| 120 |
+
m[:3, :3] = r.T
|
| 121 |
+
m[:3, 3] = -r.T @ self.c2w[:3, 3]
|
| 122 |
+
return m
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def position(self) -> np.ndarray:
|
| 126 |
+
return self.c2w[:3, 3].copy()
|
| 127 |
+
|
| 128 |
+
def projection(self, znear: float = 0.1, zfar: float = 200.0) -> np.ndarray:
|
| 129 |
+
"""intrinsic โ Vulkan clip ๊ณต๊ฐ 4x4 ํฌ์ํ๋ ฌ.
|
| 130 |
+
|
| 131 |
+
OpenCV ์นด๋ฉ๋ผ(y ์๋, z ์ ๋ฐฉ)์ Vulkan NDC(y ์๋, z โ [0,1]) ๋ ์ถ ๋ฐฉํฅ์ด ๊ฐ๋ค.
|
| 132 |
+
๊ทธ๋์ OpenGL ์๊ณผ ๋ฌ๋ฆฌ y ๋ค์ง๊ธฐ๋, z ๋ถํธ ๋ฐ์ ๋ ์๋ค. ์ ํํ
|
| 133 |
+
ndc_x = 2u/w - 1, ndc_y = 2v/h - 1 (u,v = K ๋ก ํฌ์ํ ํฝ์
์ขํ)
|
| 134 |
+
๊ฐ ์ฑ๋ฆฝํ๊ณ , tests/test_rig.py ๊ฐ ์ด๊ฑธ ํฝ์
๋จ์๋ก ๊ฒ์ฆํ๋ค.
|
| 135 |
+
"""
|
| 136 |
+
fx, fy = self.K[0, 0], self.K[1, 1]
|
| 137 |
+
cx, cy = self.K[0, 2], self.K[1, 2]
|
| 138 |
+
w, h = self.width, self.height
|
| 139 |
+
|
| 140 |
+
p = np.zeros((4, 4))
|
| 141 |
+
p[0, 0] = 2.0 * fx / w
|
| 142 |
+
p[1, 1] = 2.0 * fy / h
|
| 143 |
+
# ์ฃผ์ ์คํ์
. u = fxยทx/z + cx ์ด๋ฏ๋ก 2u/w-1 = (2fx/w)ยทx/z + (2cx/w - 1).
|
| 144 |
+
p[0, 2] = 2.0 * cx / w - 1.0
|
| 145 |
+
p[1, 2] = 2.0 * cy / h - 1.0
|
| 146 |
+
# ๊น์ด [znear, zfar] โ [0, 1]
|
| 147 |
+
p[2, 2] = zfar / (zfar - znear)
|
| 148 |
+
p[2, 3] = -zfar * znear / (zfar - znear)
|
| 149 |
+
p[3, 2] = 1.0
|
| 150 |
+
return p
|
| 151 |
+
|
| 152 |
+
def fov_deg(self) -> tuple[float, float]:
|
| 153 |
+
fx, fy = self.K[0, 0], self.K[1, 1]
|
| 154 |
+
return (
|
| 155 |
+
float(np.degrees(2 * np.arctan(self.width / (2 * fx)))),
|
| 156 |
+
float(np.degrees(2 * np.arctan(self.height / (2 * fy)))),
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def camera_pose(frame: Frame, calib: CameraCalib, channel: str) -> CameraPose:
|
| 161 |
+
"""ํ ํ๋ ์ + ๊ทธ ์ฑ๋์ ์บ๋ฆฌ๋ธ โ CameraPose."""
|
| 162 |
+
ego2global = rt_to_matrix(quat_to_matrix(frame.ego_rotation), frame.ego_translation)
|
| 163 |
+
cam2ego = rt_to_matrix(quat_to_matrix(calib.rotation), calib.translation)
|
| 164 |
+
return CameraPose(
|
| 165 |
+
channel=channel,
|
| 166 |
+
timestamp=frame.timestamp,
|
| 167 |
+
c2w=ego2global @ cam2ego,
|
| 168 |
+
K=np.asarray(calib.intrinsic, dtype=np.float64),
|
| 169 |
+
width=calib.width,
|
| 170 |
+
height=calib.height,
|
| 171 |
+
filename=frame.filename,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def scene_poses(scene, cameras=None, keyframes_only: bool = False) -> list[CameraPose]:
|
| 176 |
+
"""์ฌ ์ ์ฒด์ ์นด๋ฉ๋ผ ํฌ์ฆ๋ฅผ ์๊ฐ์์ผ๋ก. 3DGS ํ์ต๊ธฐ์ ์
๋ ฅ์ด ๋๋ ํํ๋ค."""
|
| 177 |
+
from nusim import CAMERAS
|
| 178 |
+
|
| 179 |
+
out: list[CameraPose] = []
|
| 180 |
+
for cam in cameras or CAMERAS:
|
| 181 |
+
calib = scene.calib.get(cam)
|
| 182 |
+
if calib is None:
|
| 183 |
+
continue
|
| 184 |
+
frames = scene.keyframes(cam) if keyframes_only else scene.frames.get(cam, ())
|
| 185 |
+
out.extend(camera_pose(f, calib, cam) for f in frames)
|
| 186 |
+
out.sort(key=lambda p: (p.timestamp, p.channel))
|
| 187 |
+
return out
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def ego_trajectory(scene, channel: str = "CAM_FRONT") -> np.ndarray:
|
| 191 |
+
"""(N, 8) ๋ฐฐ์ด: t[s], x, y, z, qw, qx, qy, qz.
|
| 192 |
+
|
| 193 |
+
closed-loop ์์ ์ํ์ ์๋ณธ ๊ถค์ ๋น๊ต(์คํ๋ฃจํ ๊ธฐ์ค์ )์ ์ด๋ค.
|
| 194 |
+
"""
|
| 195 |
+
frames = scene.frames.get(channel, [])
|
| 196 |
+
if not frames:
|
| 197 |
+
raise ValueError(f"{scene.name} ์ {channel} ํ๋ ์์ด ์์ต๋๋ค")
|
| 198 |
+
t0 = frames[0].timestamp
|
| 199 |
+
return np.array(
|
| 200 |
+
[
|
| 201 |
+
[(f.timestamp - t0) / 1e6, *f.ego_translation, *f.ego_rotation]
|
| 202 |
+
for f in frames
|
| 203 |
+
]
|
| 204 |
+
)
|
src/nusim/nusim/io/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์ง๋ ฌํ ๊ณ์ธต โ ๋์ฉ๋ JSON ์คํธ๋ฆฌ๋ฐ, 3DGS .ply ์
์ถ๋ ฅ."""
|
| 2 |
+
|
| 3 |
+
from nusim.io.jsonstream import iter_json_array, load_table
|
| 4 |
+
|
| 5 |
+
__all__ = ["iter_json_array", "load_table"]
|
src/nusim/nusim/io/jsonstream.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""nuScenes ํ
์ด๋ธ์ ์ํ ์คํธ๋ฆฌ๋ฐ JSON ๋ฆฌ๋.
|
| 2 |
+
|
| 3 |
+
์ ํ์ํ๊ฐ:
|
| 4 |
+
v1.0-trainval ์ sample_data.json ์ 1.3 GB, ego_pose.json ์ 645 MB ๋ค.
|
| 5 |
+
json.load() ๋ก ์ฝ์ผ๋ฉด ํ์ด์ฌ ๊ฐ์ฒด ๊ทธ๋ํ๊ฐ ์๋ณธ์ 4~5๋ฐฐ๋ก ๋ถํ์ด
|
| 6 |
+
sample_data ํ๋์ 6 GB ์ํ์ ๋จน๋๋ค. ๊ฒ๋ค๊ฐ ์ด ํ์ผ๋ค์ NFS ์์ ์์ด
|
| 7 |
+
์ ์ฒด๋ฅผ ์ฝ๋ ๊ฒ ์์ฒด๊ฐ ๋น์ธ๋ค.
|
| 8 |
+
|
| 9 |
+
์ฌ๊ธฐ์๋ ์ต์์ ๋ฐฐ์ด์ ์์๋ฅผ ํ๋์ฉ ํ๋ ค๋ณด๋ธ๋ค. ํธ์ถ์๊ฐ ํ์ํ ๊ฒ๋ง
|
| 10 |
+
๊ณจ๋ผ ๋ด์ผ๋ฉด ํผํฌ ๋ฉ๋ชจ๋ฆฌ๊ฐ '๋ฒํผ ํฌ๊ธฐ + ๊ณ ๋ฅธ ๊ฒ' ์ผ๋ก ๋จ์ด์ง๋ค.
|
| 11 |
+
์ค์ธก: sample_data.json ์ ์ฒด ์ํ ์ ํผํฌ ~60 MB.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import re
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
from typing import Any, Callable, Iterator
|
| 20 |
+
|
| 21 |
+
# ์์ ์ฌ์ด์ ๊ณต๋ฐฑ๊ณผ ์ผํ. raw_decode ๋ idx ๊ฐ ๊ฐ์ ์ฒซ ๊ธ์๋ฅผ ๊ฐ๋ฆฌ์ผ์ผ ํ๋ค.
|
| 22 |
+
_GAP = re.compile(r"[\s,]*")
|
| 23 |
+
|
| 24 |
+
_CHUNK = 1 << 22 # 4 MiB
|
| 25 |
+
# ๋ฒํผ ์์ชฝ์ ์๋น๋ ๋ถ๋ถ์ ์๋ผ๋ด๋ ์๊ณ์น. ๋งค ์์๋ง๋ค ์๋ฅด๋ฉด ์ฌ๋ผ์ด์ค ๋ณต์ฌ๊ฐ
|
| 26 |
+
# O(nยฒ) ๊ฐ ๋๊ณ , ์์ ์ ์๋ฅด๋ฉด ๋ฒํผ๊ฐ ํ์ผ ํฌ๊ธฐ๋งํผ ์๋๋ค.
|
| 27 |
+
_COMPACT_AT = 1 << 21 # 2 MiB
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def iter_json_array(path: str | Path, chunk_size: int = _CHUNK) -> Iterator[Any]:
|
| 31 |
+
"""์ต์์๊ฐ JSON ๋ฐฐ์ด์ธ ํ์ผ์ ์์๋ฅผ ํ๋์ฉ ๋ด๋๋๋ค.
|
| 32 |
+
|
| 33 |
+
nuScenes ์ ๋ชจ๋ ํ
์ด๋ธ์ด ์ด ๋ชจ์์ด๋ค. ๋ค๋ฅธ ๋ชจ์์ด๋ฉด ValueError ๋ฅผ ๋ธ๋ค.
|
| 34 |
+
"""
|
| 35 |
+
path = Path(path)
|
| 36 |
+
dec = json.JSONDecoder()
|
| 37 |
+
|
| 38 |
+
with path.open("r", encoding="utf-8") as f:
|
| 39 |
+
buf = f.read(chunk_size)
|
| 40 |
+
if not buf:
|
| 41 |
+
return
|
| 42 |
+
|
| 43 |
+
start = buf.find("[")
|
| 44 |
+
if start < 0:
|
| 45 |
+
raise ValueError(f"{path}: ์ต์์ JSON ๋ฐฐ์ด์ด ์๋๋๋ค")
|
| 46 |
+
pos = start + 1
|
| 47 |
+
|
| 48 |
+
while True:
|
| 49 |
+
pos = _GAP.match(buf, pos).end()
|
| 50 |
+
|
| 51 |
+
# ๋ฒํผ๊ฐ ๋ฐ๋ฅ๋ฌ์ผ๋ฉด ๋ ์ฝ๋๋ค.
|
| 52 |
+
if pos >= len(buf):
|
| 53 |
+
more = f.read(chunk_size)
|
| 54 |
+
if not more:
|
| 55 |
+
return # ๋ซ๋ ']' ์์ด ๋๋จ โ ์๋ฆฐ ํ์ผ์ด์ง๋ง ์ป์ ๊ฑด ์ด๋ฏธ ๋๋ค
|
| 56 |
+
buf = buf[pos:] + more
|
| 57 |
+
pos = 0
|
| 58 |
+
continue
|
| 59 |
+
|
| 60 |
+
if buf[pos] == "]":
|
| 61 |
+
return
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
obj, pos = dec.raw_decode(buf, pos)
|
| 65 |
+
except ValueError:
|
| 66 |
+
# ์์๊ฐ ๋ฒํผ ๊ฒฝ๊ณ์ ๊ฑธ์ณค๋ค. ๋ ์ฝ์ด์ ๋ค์ ์๋ํ๋ค.
|
| 67 |
+
more = f.read(chunk_size)
|
| 68 |
+
if not more:
|
| 69 |
+
raise ValueError(f"{path}: ํ์ผ ๋์์ JSON ์ด ์๋ ธ์ต๋๋ค") from None
|
| 70 |
+
buf += more
|
| 71 |
+
continue
|
| 72 |
+
|
| 73 |
+
yield obj
|
| 74 |
+
|
| 75 |
+
if pos > _COMPACT_AT:
|
| 76 |
+
buf = buf[pos:]
|
| 77 |
+
pos = 0
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def load_table(
|
| 81 |
+
path: str | Path,
|
| 82 |
+
keep: Callable[[dict], bool] | None = None,
|
| 83 |
+
fields: tuple[str, ...] | None = None,
|
| 84 |
+
) -> list[dict]:
|
| 85 |
+
"""ํ
์ด๋ธ์ ์ฝ๋ ์ํ๋ ํ๊ณผ ์ด๋ง ๋จ๊ธด๋ค.
|
| 86 |
+
|
| 87 |
+
keep: ํ ํํฐ. None ์ด๋ฉด ์ ๋ถ.
|
| 88 |
+
fields: ๋จ๊ธธ ํค. None ์ด๋ฉด ์ ๋ถ. ํฐ ํ
์ด๋ธ์์๋ ์ด๊ฑธ ์ฃผ๋ ๊ฒ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํฌ๊ฒ ์ค์ธ๋ค.
|
| 89 |
+
"""
|
| 90 |
+
out: list[dict] = []
|
| 91 |
+
for row in iter_json_array(path):
|
| 92 |
+
if keep is not None and not keep(row):
|
| 93 |
+
continue
|
| 94 |
+
out.append({k: row[k] for k in fields} if fields else row)
|
| 95 |
+
return out
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def index_table(path: str | Path, key: str = "token", fields: tuple[str, ...] | None = None) -> dict[str, dict]:
|
| 99 |
+
"""ํ
์ด๋ธ์ key(๊ธฐ๋ณธ 'token') ๋ก ์ฐพ์ ์ ์๋ dict ๋ก ๋ง๋ ๋ค."""
|
| 100 |
+
out: dict[str, dict] = {}
|
| 101 |
+
for row in iter_json_array(path):
|
| 102 |
+
out[row[key]] = {k: row[k] for k in fields} if fields else row
|
| 103 |
+
return out
|
src/nusim/nusim/io/ktx2.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""KTX2 (Khronos Texture 2.0) ์ฐ๊ธฐ/์ฝ๊ธฐ โ Vulkan ๋ ๋๋ฌ์ ํ๋ธ๋งตยทLUT ๋ฅผ ๋๊ธฐ๋ ํฌ๋งท.
|
| 2 |
+
|
| 3 |
+
https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html
|
| 4 |
+
ํ์ํ ๊ฒ๋ง: 2D / ํ๋ธ๋งต, mip ์ฒด์ธ, ์ด์์ถ ์์(supercompression 0).
|
| 5 |
+
๋ธ๋ก ์์ถ(BC6H) ๋ฐ์ดํฐ๋ ์ด๋ฏธ ์์ถ๋ ๋ฐ์ดํธ๋ฅผ ๊ทธ๋๋ก ๋ด๋๋ค.
|
| 6 |
+
|
| 7 |
+
ํ์ผ ์ ๋ฐ์ดํฐ ์์๋ ์คํ๋๋ก **์์ mip ๋ถํฐ** ํฐ mip ์์ด๊ณ , ๊ฐ mip ์์์๋ ๋ฉด 0~5 ์์๋ค.
|
| 8 |
+
๋ ๋ฒจ ์ธ๋ฑ์ค๋ mip 0(๊ฐ์ฅ ํฐ ๊ฒ)๋ถํฐ ์ ๋๋ค.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import math
|
| 14 |
+
import struct
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
IDENTIFIER = b"\xabKTX 20\xbb\r\n\x1a\n"
|
| 19 |
+
|
| 20 |
+
# ์ฐ๋ VkFormat
|
| 21 |
+
VK_FORMAT_R16G16_SFLOAT = 83
|
| 22 |
+
VK_FORMAT_R16G16B16A16_SFLOAT = 97
|
| 23 |
+
VK_FORMAT_BC6H_UFLOAT_BLOCK = 143
|
| 24 |
+
|
| 25 |
+
_BLOCK = {VK_FORMAT_BC6H_UFLOAT_BLOCK: (4, 4, 16)} # ๋ธ๋ก ๊ฐ๋ก, ์ธ๋ก, ๋ฐ์ดํธ
|
| 26 |
+
_TEXEL = {VK_FORMAT_R16G16_SFLOAT: 4, VK_FORMAT_R16G16B16A16_SFLOAT: 8}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def level_size(vk_format: int, w: int, h: int) -> int:
|
| 30 |
+
if vk_format in _BLOCK:
|
| 31 |
+
bw, bh, nb = _BLOCK[vk_format]
|
| 32 |
+
return ((w + bw - 1) // bw) * ((h + bh - 1) // bh) * nb
|
| 33 |
+
return w * h * _TEXEL[vk_format]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _dfd(vk_format: int) -> bytes:
|
| 37 |
+
"""๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์ ์์ ์(DFD). ๋ทฐ์ด๋ vkFormat ๋ง ๋ณด์ง๋ง ์ธ๋ถ ๋๊ตฌ ํธํ์ ์ํด ๋ฃ๋๋ค."""
|
| 38 |
+
# KHR_DF_MODEL: RGBSDA=1, BC6H=131 / ์ ์ก ํจ์ LINEAR=1 / ์์ BT709=1
|
| 39 |
+
if vk_format == VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
| 40 |
+
model, dims, plane0 = 133, (3, 3, 0, 0), 16 # KHR_DF_MODEL_BC6H (131 ์ BC4)
|
| 41 |
+
# ์ํ 1๊ฐ: 128 ๋นํธ ๋ธ๋ก ์ ์ฒด, ์ฑ๋ COLOR(0), FLOAT ํ๋๊ทธ(0x80)
|
| 42 |
+
samples = [(0, 127, 0x80, 0, 0x3F800000)] # sampleUpper = 1.0f (ktx validate ๊ธฐ์ค)
|
| 43 |
+
elif vk_format in (VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT):
|
| 44 |
+
n = 2 if vk_format == VK_FORMAT_R16G16_SFLOAT else 4
|
| 45 |
+
model, dims, plane0 = 1, (0, 0, 0, 0), 2 * n
|
| 46 |
+
chan = [0, 1, 2, 15][:n] # R, G, B, A
|
| 47 |
+
# FLOAT(0x80) | SIGNED(0x40). ๋ฒ์ -1..1 ์ float ๋นํธ
|
| 48 |
+
samples = [(16 * i, 15, 0xC0, chan[i], (0xBF800000, 0x3F800000)) for i in range(n)]
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError(f"DFD ๋ฏธ์ง์ vkFormat {vk_format}")
|
| 51 |
+
body = b""
|
| 52 |
+
for s in samples:
|
| 53 |
+
off, length, flags, ch, rng = s if len(s) == 5 else (*s, 0)
|
| 54 |
+
lower, upper = rng if isinstance(rng, tuple) else (0, rng)
|
| 55 |
+
body += struct.pack("<HBBBBBBII", off, length, (flags | ch) & 0xFF, 0, 0, 0, 0, lower, upper)
|
| 56 |
+
block_size = 24 + len(body)
|
| 57 |
+
header = struct.pack("<IHH", 0, 2, block_size) # vendorId KHRONOS(0)+descriptorType 0, version 2, ๋ธ๋ก ํฌ๊ธฐ
|
| 58 |
+
header += struct.pack("<BBBB", model, 1, 1, 0) # colorModel, primaries BT709, transfer LINEAR, flags
|
| 59 |
+
header += struct.pack("<BBBB", *dims)
|
| 60 |
+
header += struct.pack("<8B", plane0, 0, 0, 0, 0, 0, 0, 0)
|
| 61 |
+
block = header + body
|
| 62 |
+
return struct.pack("<I", 4 + len(block)) + block
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@dataclass
|
| 66 |
+
class Ktx2:
|
| 67 |
+
vk_format: int
|
| 68 |
+
width: int
|
| 69 |
+
height: int
|
| 70 |
+
faces: int
|
| 71 |
+
levels: list[list[bytes]] # levels[mip][face] โ mip 0 ์ด ๊ฐ์ฅ ํฌ๋ค
|
| 72 |
+
kv: dict[str, str] | None = None
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def write_ktx2(path: str | Path, tex: Ktx2) -> Path:
|
| 76 |
+
path = Path(path)
|
| 77 |
+
nlev = len(tex.levels)
|
| 78 |
+
for m, lv in enumerate(tex.levels):
|
| 79 |
+
w, h = max(1, tex.width >> m), max(1, tex.height >> m)
|
| 80 |
+
for f in lv:
|
| 81 |
+
if len(f) != level_size(tex.vk_format, w, h):
|
| 82 |
+
raise ValueError(f"mip {m} ํฌ๊ธฐ {len(f)} != ๊ธฐ๋ {level_size(tex.vk_format, w, h)}")
|
| 83 |
+
dfd = _dfd(tex.vk_format)
|
| 84 |
+
kvd = b""
|
| 85 |
+
for k, v in (tex.kv or {}).items():
|
| 86 |
+
kv = k.encode() + b"\0" + v.encode() + b"\0"
|
| 87 |
+
kvd += struct.pack("<I", len(kv)) + kv + b"\0" * ((-len(kv)) % 4)
|
| 88 |
+
|
| 89 |
+
type_size = 1 if tex.vk_format in _BLOCK else 2
|
| 90 |
+
# ์คํ: mip ๋ฐ์ดํฐ ์ ๋ ฌ์ ์ ํํ lcm(ํ
์
/๋ธ๋ก ๋ฐ์ดํธ, 4). ๋ ํฌ๊ฒ ์ก์ผ๋ฉด ktx validate ๊ฐ ๊ฑฐ๋ถํ๋ค.
|
| 91 |
+
unit = _BLOCK[tex.vk_format][2] if tex.vk_format in _BLOCK else _TEXEL[tex.vk_format]
|
| 92 |
+
align = unit * 4 // math.gcd(unit, 4)
|
| 93 |
+
head_len = 12 + 4 * 9 + 4 * 4 + 8 * 2 + 24 * nlev
|
| 94 |
+
dfd_off = head_len
|
| 95 |
+
kvd_off = dfd_off + len(dfd)
|
| 96 |
+
data_start = kvd_off + len(kvd)
|
| 97 |
+
data_start += (-data_start) % align
|
| 98 |
+
# ์์ mip ๋ถํฐ ๋ฐฐ์น
|
| 99 |
+
offsets = [0] * nlev
|
| 100 |
+
blob = b""
|
| 101 |
+
pos = data_start
|
| 102 |
+
for m in reversed(range(nlev)):
|
| 103 |
+
pad = (-pos) % align
|
| 104 |
+
blob += b"\0" * pad
|
| 105 |
+
pos += pad
|
| 106 |
+
offsets[m] = pos
|
| 107 |
+
lvl = b"".join(tex.levels[m])
|
| 108 |
+
blob += lvl
|
| 109 |
+
pos += len(lvl)
|
| 110 |
+
|
| 111 |
+
out = IDENTIFIER
|
| 112 |
+
out += struct.pack("<9I", tex.vk_format, type_size, tex.width, tex.height, 0, 0, tex.faces, nlev, 0)
|
| 113 |
+
out += struct.pack("<4I", dfd_off, len(dfd), kvd_off if kvd else 0, len(kvd))
|
| 114 |
+
out += struct.pack("<2Q", 0, 0)
|
| 115 |
+
for m in range(nlev):
|
| 116 |
+
n = len(b"".join(tex.levels[m]))
|
| 117 |
+
out += struct.pack("<3Q", offsets[m], n, n)
|
| 118 |
+
out += dfd + kvd
|
| 119 |
+
out += b"\0" * (data_start - len(out))
|
| 120 |
+
out += blob
|
| 121 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 122 |
+
path.write_bytes(out)
|
| 123 |
+
return path
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def read_ktx2(path: str | Path) -> Ktx2:
|
| 127 |
+
b = Path(path).read_bytes()
|
| 128 |
+
if b[:12] != IDENTIFIER:
|
| 129 |
+
raise ValueError(f"{path}: KTX2 ๊ฐ ์๋๋๋ค")
|
| 130 |
+
vk_format, _ts, w, h, _d, _layers, faces, nlev, sc = struct.unpack_from("<9I", b, 12)
|
| 131 |
+
if sc:
|
| 132 |
+
raise ValueError(f"{path}: ์ด์์ถ(supercompression {sc}) ๋ฏธ์ง์")
|
| 133 |
+
nlev = max(nlev, 1)
|
| 134 |
+
levels = []
|
| 135 |
+
for m in range(nlev):
|
| 136 |
+
off, n, _ = struct.unpack_from("<3Q", b, 12 + 36 + 16 + 16 + 24 * m)
|
| 137 |
+
per = n // faces
|
| 138 |
+
levels.append([b[off + f * per: off + (f + 1) * per] for f in range(faces)])
|
| 139 |
+
return Ktx2(vk_format, w, h, faces, levels)
|
src/nusim/nusim/io/ply.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ํ์ค 3DGS .ply ์
์ถ๋ ฅ โ ํ์ต๊ธฐ์ Vulkan ๋ ๋๋ฌ ์ฌ์ด์ ์ ์ผํ ์ ์ .
|
| 2 |
+
|
| 3 |
+
ํฌ๋งท์ ์๋ณธ 3DGS (Kerbl et al. 2023) ๊ตฌํ์ด ์ฐ๋ ๊ทธ๋๋ก๋ค. ๊ทธ๋์ SIBR ๋ทฐ์ด,
|
| 4 |
+
SuperSplat ๊ฐ์ ์ธ๋ถ ๋๊ตฌ๋ก๋ ์ด๋ฆฐ๋ค. binary_little_endian, vertex ํ๋์ float32:
|
| 5 |
+
|
| 6 |
+
x y z ์์น (์ฌ ์์ ๊ธฐ์ค, ์๋ ์ฐธ๊ณ )
|
| 7 |
+
nx ny nz ํญ์ 0. ์๋ณธ ํฌ๋งท์ ์๋ฆฌ๋ง ์๋ค
|
| 8 |
+
f_dc_0..2 SH 0์ฐจ ๊ณ์ (RGB)
|
| 9 |
+
f_rest_0..3(K-1)-1 SH ๊ณ ์ฐจ ๊ณ์. **์ฑ๋ ์ฐ์ ** โ R ์ K-1 ๊ฐ, G, B ์์
|
| 10 |
+
opacity logit (sigmoid ์ )
|
| 11 |
+
scale_0..2 log (exp ์ )
|
| 12 |
+
rot_0..3 ์ฟผํฐ๋์ธ (w,x,y,z), ์ ๊ทํ ์
|
| 13 |
+
|
| 14 |
+
์ฌ ์์ : nuScenes ์ ์ญ ์ขํ๋ ์์ฒ m ๋จ์๋ผ float32 ๋ก ๋ด์ผ๋ฉด cm ์ดํ๊ฐ ๋ญ๊ฐ์ง๋ค.
|
| 15 |
+
๊ทธ๋์ ์์น๋ ์ฌ ์์ ์ ๋บ ๊ฐ์ผ๋ก ์ ์ฅํ๊ณ , ์์ ์ ํค๋์ `comment nusim_origin`
|
| 16 |
+
์ float64 ๋ก ๋จ๊ธด๋ค. ํ์ค ๋ฆฌ๋๋ comment ๋ฅผ ๋ฌด์ํ๋ฏ๋ก ํธํ์ฑ์ด ๊นจ์ง์ง ์๋๋ค.
|
| 17 |
+
์ ์ญ ์ขํ = ์ ์ฅ๋ ์์น + origin.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
from dataclasses import dataclass, field
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
_ORIGIN_TAG = "nusim_origin"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class Gaussians:
|
| 32 |
+
"""๊ฐ์ฐ์์ ์งํฉ. ์ ๋ถ ํ์ฑํ ์ (raw) ํ๋ผ๋ฏธํฐ๋ค โ ํ์ต๊ธฐ ๋ด๋ถ ํํ๊ณผ ๊ฐ๋ค."""
|
| 33 |
+
|
| 34 |
+
means: np.ndarray # (N,3) ์ฌ ์์ ๊ธฐ์ค
|
| 35 |
+
scales: np.ndarray # (N,3) log
|
| 36 |
+
quats: np.ndarray # (N,4) wxyz
|
| 37 |
+
opacities: np.ndarray # (N,) logit
|
| 38 |
+
sh0: np.ndarray # (N,1,3)
|
| 39 |
+
shN: np.ndarray # (N,K-1,3), K = (deg+1)^2. deg 0 ์ด๋ฉด (N,0,3)
|
| 40 |
+
origin: np.ndarray = field(default_factory=lambda: np.zeros(3)) # float64, ์ ์ญ ์ขํ
|
| 41 |
+
|
| 42 |
+
def __len__(self) -> int:
|
| 43 |
+
return len(self.means)
|
| 44 |
+
|
| 45 |
+
@property
|
| 46 |
+
def sh_degree(self) -> int:
|
| 47 |
+
k = 1 + self.shN.shape[1]
|
| 48 |
+
deg = int(round(np.sqrt(k))) - 1
|
| 49 |
+
if (deg + 1) ** 2 != k:
|
| 50 |
+
raise ValueError(f"SH ๊ณ์ ๊ฐ์ {k} ๊ฐ (deg+1)^2 ๊ผด์ด ์๋๋๋ค")
|
| 51 |
+
return deg
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _attr_names(n_rest: int) -> list[str]:
|
| 55 |
+
return (
|
| 56 |
+
["x", "y", "z", "nx", "ny", "nz"]
|
| 57 |
+
+ [f"f_dc_{i}" for i in range(3)]
|
| 58 |
+
+ [f"f_rest_{i}" for i in range(n_rest)]
|
| 59 |
+
+ ["opacity"]
|
| 60 |
+
+ [f"scale_{i}" for i in range(3)]
|
| 61 |
+
+ [f"rot_{i}" for i in range(4)]
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def write_ply(path: str | Path, g: Gaussians) -> Path:
|
| 66 |
+
path = Path(path)
|
| 67 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 68 |
+
n = len(g)
|
| 69 |
+
# (N,K-1,3) โ (N,3,K-1) โ ํํํ. ์๋ณธ 3DGS ์ f_rest ์์๊ฐ ์ด๊ฒ์ด๋ค.
|
| 70 |
+
rest = np.ascontiguousarray(g.shN.transpose(0, 2, 1)).reshape(n, -1)
|
| 71 |
+
cols = [
|
| 72 |
+
g.means, np.zeros((n, 3)), g.sh0.reshape(n, 3), rest,
|
| 73 |
+
g.opacities.reshape(n, 1), g.scales, g.quats,
|
| 74 |
+
]
|
| 75 |
+
data = np.concatenate([np.asarray(c, dtype=np.float32) for c in cols], axis=1)
|
| 76 |
+
names = _attr_names(rest.shape[1])
|
| 77 |
+
assert data.shape[1] == len(names)
|
| 78 |
+
|
| 79 |
+
o = np.asarray(g.origin, dtype=np.float64)
|
| 80 |
+
header = "\n".join(
|
| 81 |
+
["ply", "format binary_little_endian 1.0",
|
| 82 |
+
f"comment {_ORIGIN_TAG} {o[0]!r} {o[1]!r} {o[2]!r}",
|
| 83 |
+
f"element vertex {n}"]
|
| 84 |
+
+ [f"property float {a}" for a in names]
|
| 85 |
+
+ ["end_header"]
|
| 86 |
+
) + "\n"
|
| 87 |
+
|
| 88 |
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
| 89 |
+
with open(tmp, "wb") as f:
|
| 90 |
+
f.write(header.encode("ascii"))
|
| 91 |
+
f.write(data.astype("<f4", copy=False).tobytes())
|
| 92 |
+
tmp.replace(path) # ๋ ๋๋ฌ๊ฐ ๋ฐ์ฏค ์ฐ์ธ ํ์ผ์ ์ฝ์ง ์๊ฒ
|
| 93 |
+
return path
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def read_origin(path: str | Path) -> np.ndarray:
|
| 97 |
+
"""ํค๋๋ง ์ฝ์ด ์ฌ ์์ ์ ๋๋ ค์ค๋ค. ๊ฐ์ฐ์์ 200๋ง ๊ฐ์ง๋ฆฌ ply ๋ฅผ ๋ค ์ฝ์ง ์์ผ๋ ค๊ณ ."""
|
| 98 |
+
with open(path, "rb") as f:
|
| 99 |
+
for raw in f:
|
| 100 |
+
tok = raw.decode("ascii", "replace").split()
|
| 101 |
+
if tok[:2] == ["comment", _ORIGIN_TAG] and len(tok) == 5:
|
| 102 |
+
return np.array([float(t) for t in tok[2:5]])
|
| 103 |
+
if tok[:1] == ["end_header"]:
|
| 104 |
+
break
|
| 105 |
+
return np.zeros(3)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def read_count(path: str | Path) -> int:
|
| 109 |
+
"""ํค๋๋ง ์ฝ์ด ๊ฐ์ฐ์์ ์ (element vertex)."""
|
| 110 |
+
with open(path, "rb") as f:
|
| 111 |
+
for raw in f:
|
| 112 |
+
tok = raw.decode("ascii", "replace").split()
|
| 113 |
+
if tok[:2] == ["element", "vertex"]:
|
| 114 |
+
return int(tok[2])
|
| 115 |
+
if tok[:1] == ["end_header"]:
|
| 116 |
+
break
|
| 117 |
+
raise ValueError(f"{path}: element vertex ๊ฐ ์๋ค")
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def read_ply(path: str | Path) -> Gaussians:
|
| 121 |
+
path = Path(path)
|
| 122 |
+
with open(path, "rb") as f:
|
| 123 |
+
if f.readline().strip() != b"ply":
|
| 124 |
+
raise ValueError(f"{path}: ply ํ์ผ์ด ์๋๋๋ค")
|
| 125 |
+
names: list[str] = []
|
| 126 |
+
n = None
|
| 127 |
+
origin = np.zeros(3)
|
| 128 |
+
while True:
|
| 129 |
+
line = f.readline()
|
| 130 |
+
if not line:
|
| 131 |
+
raise ValueError(f"{path}: end_header ๊ฐ ์์ต๋๋ค")
|
| 132 |
+
tok = line.decode("ascii").split()
|
| 133 |
+
if not tok:
|
| 134 |
+
continue
|
| 135 |
+
if tok[0] == "format" and tok[1] != "binary_little_endian":
|
| 136 |
+
raise ValueError(f"{path}: {tok[1]} ๋ ์ง์ํ์ง ์์ต๋๋ค")
|
| 137 |
+
elif tok[0] == "comment" and len(tok) == 5 and tok[1] == _ORIGIN_TAG:
|
| 138 |
+
origin = np.array([float(t) for t in tok[2:5]])
|
| 139 |
+
elif tok[0] == "element":
|
| 140 |
+
if tok[1] != "vertex":
|
| 141 |
+
raise ValueError(f"{path}: vertex ์ธ element({tok[1]}) ๋ ์ง์ํ์ง ์์ต๋๋ค")
|
| 142 |
+
n = int(tok[2])
|
| 143 |
+
elif tok[0] == "property":
|
| 144 |
+
if tok[1] != "float":
|
| 145 |
+
raise ValueError(f"{path}: float ๊ฐ ์๋ ์์ฑ {tok[1]} {tok[2]}")
|
| 146 |
+
names.append(tok[2])
|
| 147 |
+
elif tok[0] == "end_header":
|
| 148 |
+
break
|
| 149 |
+
if n is None:
|
| 150 |
+
raise ValueError(f"{path}: vertex ๊ฐ์๊ฐ ์์ต๋๋ค")
|
| 151 |
+
data = np.frombuffer(f.read(n * len(names) * 4), dtype="<f4").reshape(n, len(names))
|
| 152 |
+
|
| 153 |
+
col = {a: i for i, a in enumerate(names)}
|
| 154 |
+
|
| 155 |
+
def pick(prefix: str, k: int) -> np.ndarray:
|
| 156 |
+
return data[:, [col[f"{prefix}{i}"] for i in range(k)]]
|
| 157 |
+
|
| 158 |
+
n_rest = sum(1 for a in names if a.startswith("f_rest_"))
|
| 159 |
+
if n_rest % 3:
|
| 160 |
+
raise ValueError(f"{path}: f_rest ๊ฐ์ {n_rest} ๊ฐ 3์ ๋ฐฐ์๊ฐ ์๋๋๋ค")
|
| 161 |
+
rest = pick("f_rest_", n_rest).reshape(n, 3, n_rest // 3).transpose(0, 2, 1)
|
| 162 |
+
return Gaussians(
|
| 163 |
+
means=data[:, [col["x"], col["y"], col["z"]]].copy(),
|
| 164 |
+
scales=pick("scale_", 3).copy(),
|
| 165 |
+
quats=pick("rot_", 4).copy(),
|
| 166 |
+
opacities=data[:, col["opacity"]].copy(),
|
| 167 |
+
sh0=pick("f_dc_", 3).reshape(n, 1, 3).copy(),
|
| 168 |
+
shN=np.ascontiguousarray(rest),
|
| 169 |
+
origin=origin,
|
| 170 |
+
)
|
src/nusim/nusim/jobs.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""์์
์ ์(JSON) ์คํ๊ธฐ โ `nusim run <configs/jobs/์ด๋ฆ.json>`.
|
| 2 |
+
|
| 3 |
+
์คํ๋ง๋ค ์
ธ ๋๊ธฐ์ด ์คํฌ๋ฆฝํธ(cache/queue_*.sh)๋ฅผ ์๋ก ์ฐ๋ ๊ฒ์ ๋์ ํ๋ค. ์์
ํ์ผ ํ์:
|
| 4 |
+
|
| 5 |
+
{
|
| 6 |
+
"name": "quality_2026-10-05",
|
| 7 |
+
"vars": {"iters": "15000", "base": "--strategy mcmc --depth-lambda 0.1 --objects agents"},
|
| 8 |
+
"steps": [
|
| 9 |
+
{"name": "pilot-${s}", "foreach": {"s": ["scene-0184", "scene-0402"]},
|
| 10 |
+
"clean": ["outputs/gs_day/${s}"],
|
| 11 |
+
"run": "recon batch --scenes ${s} --no-sky --iters ${iters}"},
|
| 12 |
+
{"name": "exp-fullres", "run": "recon train scene-0003 ${base} --iters ${iters} --downscale 1 --out outputs/gs_exp/fullres"}
|
| 13 |
+
]
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
- run: nusim ํ์ ๋ช
๋ น (๋ฌธ์์ด์ด๋ฉด ๊ณต๋ฐฑ์ผ๋ก ๋๋๋ค, ๋ชฉ๋ก๋ ๋๋ค). ${๋ณ์} ๋ varsยทforeach ๊ฐ์ผ๋ก ๋ฐ๊พผ๋ค.
|
| 17 |
+
- foreach: ๋ณ์๋ง๋ค ๊ฐ ๋ชฉ๋ก โ ๋จ๊ณ๊ฐ ๊ฐ ์๋งํผ ํผ์ณ์ง๋ค (์ฌ๋ฌ ๋ณ์๋ฉด ๊ณฑ). ๊ฐ์ด ๊ฐ์ฒด๋ฉด ${e.id}, ${e.args} ์ฒ๋ผ.
|
| 18 |
+
- clean: ์คํ ์ ์ ์ง์ธ ๋๋ ํฐ๋ฆฌ (๋ค์ ๋ง๋ค ๊ฒฐ๊ณผ).
|
| 19 |
+
- continue_on_error: ์คํจํด๋ ๋ค์ ๋จ๊ณ๋ก (๊ธฐ๋ณธ true โ ๋ฐค์ ๋๋ ๋๊ธฐ์ด์ด ํ ๋จ๊ณ ๋๋ฌธ์ ๋ฉ์ถ์ง ์๊ฒ).
|
| 20 |
+
์ํ๋ outputs/jobs/<name>/state.json โ ๊ฐ์ ์ด๋ฆยท๊ฐ์ ๋ช
๋ น์ผ๋ก ๋๋ ๋จ๊ณ๋ ๋ค์ ์คํํ์ง ์๋๋ค (์ด์ด ํ๊ธฐ).
|
| 21 |
+
๋จ๊ณ๋ง๋ค ๋ก๊ทธ outputs/jobs/<name>/<๋จ๊ณ>.log.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
from __future__ import annotations
|
| 25 |
+
|
| 26 |
+
import itertools
|
| 27 |
+
import json
|
| 28 |
+
import logging
|
| 29 |
+
import re
|
| 30 |
+
import shlex
|
| 31 |
+
import shutil
|
| 32 |
+
import subprocess
|
| 33 |
+
import time
|
| 34 |
+
from pathlib import Path
|
| 35 |
+
|
| 36 |
+
LOG = logging.getLogger(__name__)
|
| 37 |
+
_VAR = re.compile(r"\$\{(\w+(?:\.\w+)*)\}")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _subst(x, env: dict):
|
| 41 |
+
if isinstance(x, str):
|
| 42 |
+
def one(m):
|
| 43 |
+
v = env
|
| 44 |
+
for part in m.group(1).split("."): # ${e.args} โ foreach ๊ฐ์ด ๊ฐ์ฒด์ผ ๋
|
| 45 |
+
if not isinstance(v, dict) or part not in v:
|
| 46 |
+
raise ValueError(f"์ ์๋์ง ์์ ๋ณ์: ${{{m.group(1)}}}")
|
| 47 |
+
v = v[part]
|
| 48 |
+
return str(v)
|
| 49 |
+
return _VAR.sub(one, x)
|
| 50 |
+
if isinstance(x, list):
|
| 51 |
+
return [_subst(v, env) for v in x]
|
| 52 |
+
if isinstance(x, dict):
|
| 53 |
+
return {k: _subst(v, env) for k, v in x.items()}
|
| 54 |
+
return x
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def expand(job: dict) -> list[dict]:
|
| 58 |
+
"""foreach ๋ฅผ ํผ์น๊ณ ๋ณ์๋ฅผ ์ฑ์ด ๋จ๊ณ ๋ชฉ๋ก."""
|
| 59 |
+
base_env = {k: _subst(v, {}) if isinstance(v, str) and "${" not in v else v for k, v in job.get("vars", {}).items()}
|
| 60 |
+
# vars ๋ผ๋ฆฌ ์ฐธ์กฐํ ์ ์๊ฒ ํ ๋ฒ ๋
|
| 61 |
+
base_env = {k: _subst(v, base_env) for k, v in base_env.items()}
|
| 62 |
+
out = []
|
| 63 |
+
for st in job["steps"]:
|
| 64 |
+
loops = st.get("foreach", {})
|
| 65 |
+
keys = list(loops)
|
| 66 |
+
for combo in itertools.product(*(loops[k] for k in keys)) if keys else [()]:
|
| 67 |
+
env = dict(base_env, **dict(zip(keys, combo)))
|
| 68 |
+
s = _subst({k: v for k, v in st.items() if k != "foreach"}, env)
|
| 69 |
+
run = s["run"]
|
| 70 |
+
s["run"] = shlex.split(run) if isinstance(run, str) else list(run)
|
| 71 |
+
s.setdefault("name", " ".join(s["run"][:3]))
|
| 72 |
+
out.append(s)
|
| 73 |
+
names = [s["name"] for s in out]
|
| 74 |
+
dup = {n for n in names if names.count(n) > 1}
|
| 75 |
+
if dup:
|
| 76 |
+
raise ValueError(f"๋จ๊ณ ์ด๋ฆ์ด ๊ฒน์นฉ๋๋ค (์ํ ๊ธฐ๋ก ํค): {sorted(dup)}")
|
| 77 |
+
return out
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def run_job(path: Path, dry: bool = False, log=LOG.info) -> int:
|
| 81 |
+
from nusim.config import load
|
| 82 |
+
from nusim.paths import output_dir, workspace_root
|
| 83 |
+
|
| 84 |
+
job = load(path)
|
| 85 |
+
steps = expand(job)
|
| 86 |
+
wdir = output_dir() / "jobs" / job["name"]
|
| 87 |
+
wdir.mkdir(parents=True, exist_ok=True)
|
| 88 |
+
state_path = wdir / "state.json"
|
| 89 |
+
state = json.loads(state_path.read_text()) if state_path.exists() else {}
|
| 90 |
+
fails = 0
|
| 91 |
+
for k, st in enumerate(steps):
|
| 92 |
+
name, cmd = st["name"], st["run"]
|
| 93 |
+
if state.get(name, {}).get("state") == "done" and state[name].get("run") == cmd:
|
| 94 |
+
log(f"[{k + 1}/{len(steps)}] {name} โ ์ด๋ฏธ ๋๋จ, ๊ฑด๋๋")
|
| 95 |
+
continue
|
| 96 |
+
log(f"[{k + 1}/{len(steps)}] {name}: nusim {' '.join(cmd)}")
|
| 97 |
+
if dry:
|
| 98 |
+
continue
|
| 99 |
+
for c in st.get("clean", []):
|
| 100 |
+
p = Path(c) if Path(c).is_absolute() else workspace_root() / c
|
| 101 |
+
if p.exists():
|
| 102 |
+
shutil.rmtree(p)
|
| 103 |
+
t0 = time.time()
|
| 104 |
+
safe = re.sub(r"[^\w.-]+", "_", name)
|
| 105 |
+
with open(wdir / f"{safe}.log", "w") as f:
|
| 106 |
+
r = subprocess.run(["nusim", *cmd], stdout=f, stderr=subprocess.STDOUT, cwd=workspace_root())
|
| 107 |
+
rec = {"run": cmd, "state": "done" if r.returncode == 0 else "failed", "returncode": r.returncode,
|
| 108 |
+
"seconds": round(time.time() - t0)}
|
| 109 |
+
state[name] = rec
|
| 110 |
+
state_path.write_text(json.dumps(state, indent=2, ensure_ascii=False))
|
| 111 |
+
log(f" {rec['state']} ({rec['seconds'] / 60:.0f}๋ถ)")
|
| 112 |
+
if r.returncode != 0:
|
| 113 |
+
fails += 1
|
| 114 |
+
if not st.get("continue_on_error", job.get("continue_on_error", True)):
|
| 115 |
+
break
|
| 116 |
+
return 1 if fails else 0
|
src/nusim/nusim/log.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ํ๋ก์ ํธ ๊ณต์ฉ ๋ก๊ทธ.
|
| 2 |
+
|
| 3 |
+
๋ชจ๋ ๋ชจ๋์ `logging.getLogger(__name__)` (= "nusim.recon.train" ๊ฐ์ ์ด๋ฆ)๋ก ๋จ๊ธด๋ค.
|
| 4 |
+
`setup()` ์ด ํ ๋ฒ ๋ถ๋ฆฌ๋ฉด:
|
| 5 |
+
์ฝ์ HH:MM:SS ๋ฉ์์ง (์ฌ๋์ด ๋ณด๋ ์งํ ์ํฉ)
|
| 6 |
+
ํ์ผ ๋ ์ง ์๊ฐ.ms ๋ ๋ฒจ ๋ชจ๋ | ๋ฉ์์ง (outputs/logs/<๋ช
๋ น>_<์๊ฐ>.log โ ๋์ค์ ์ถ์ ์ฉ)
|
| 7 |
+
`run_log(path)` ๋ ํ์ต ๊ฐ์ ๊ธด ์์
์ ๊ฒฐ๊ณผ ๋๋ ํฐ๋ฆฌ์ ๊ฐ์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ ๋ ๋จ๊ธด๋ค.
|
| 8 |
+
|
| 9 |
+
ํจ์๋ค์ด ๋ฐ๋ `log=` ์ธ์(์งํ ๋ฉ์์ง ์ฝ๋ฐฑ)์ ๊ธฐ๋ณธ๊ฐ์ ๋ชจ๋ ๋ก๊ฑฐ์ info ๋ค โ print ๊ฐ ์๋๋ค.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import contextlib
|
| 15 |
+
import logging
|
| 16 |
+
import sys
|
| 17 |
+
import time
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
ROOT = "nusim"
|
| 21 |
+
_FILE_FMT = "%(asctime)s.%(msecs)03d %(levelname)-5s %(name)s | %(message)s"
|
| 22 |
+
_CONSOLE_FMT = "%(asctime)s %(message)s"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def get(name: str) -> logging.Logger:
|
| 26 |
+
return logging.getLogger(name if name.startswith(ROOT) else f"{ROOT}.{name}")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def setup(log_dir: Path | None = None, tag: str = "nusim", level: int = logging.INFO,
|
| 30 |
+
debug_file: bool = True) -> Path | None:
|
| 31 |
+
"""์ฝ์ + ํ์ผ ํธ๋ค๋ฌ๋ฅผ ๊ฑด๋ค. ๋ ๋ฒ ๋ถ๋ ค๋ ํธ๋ค๋ฌ๊ฐ ์ค๋ณต๋์ง ์๋๋ค. ๋ก๊ทธ ํ์ผ ๊ฒฝ๋ก๋ฅผ ๋๋ ค์ค๋ค."""
|
| 32 |
+
root = logging.getLogger(ROOT)
|
| 33 |
+
root.setLevel(logging.DEBUG if debug_file else level)
|
| 34 |
+
root.propagate = False
|
| 35 |
+
for h in list(root.handlers):
|
| 36 |
+
root.removeHandler(h)
|
| 37 |
+
con = logging.StreamHandler(sys.stdout)
|
| 38 |
+
con.setLevel(level)
|
| 39 |
+
con.setFormatter(logging.Formatter(_CONSOLE_FMT, "%H:%M:%S"))
|
| 40 |
+
root.addHandler(con)
|
| 41 |
+
path = None
|
| 42 |
+
if log_dir is not None:
|
| 43 |
+
log_dir.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
path = log_dir / f"{tag}_{time.strftime('%Y%m%d-%H%M%S')}.log"
|
| 45 |
+
fh = logging.FileHandler(path, encoding="utf-8")
|
| 46 |
+
fh.setLevel(logging.DEBUG if debug_file else level)
|
| 47 |
+
fh.setFormatter(logging.Formatter(_FILE_FMT, "%Y-%m-%d %H:%M:%S"))
|
| 48 |
+
root.addHandler(fh)
|
| 49 |
+
return path
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@contextlib.contextmanager
|
| 53 |
+
def run_log(path: Path):
|
| 54 |
+
"""with run_log(out_dir / "run.log"): ... โ ๊ทธ ์์ nusim ๋ก๊ทธ๋ฅผ ์ด ํ์ผ์๋ (DEBUG ํฌํจ) ๋จ๊ธด๋ค."""
|
| 55 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 56 |
+
fh = logging.FileHandler(path, encoding="utf-8")
|
| 57 |
+
fh.setLevel(logging.DEBUG)
|
| 58 |
+
fh.setFormatter(logging.Formatter(_FILE_FMT, "%Y-%m-%d %H:%M:%S"))
|
| 59 |
+
root = logging.getLogger(ROOT)
|
| 60 |
+
if not root.handlers: # setup() ์์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ฐ์ธ ๊ฒฝ์ฐ์๋ ์ฝ์์๋ ๋์ค๊ฒ
|
| 61 |
+
setup()
|
| 62 |
+
root.addHandler(fh)
|
| 63 |
+
try:
|
| 64 |
+
yield path
|
| 65 |
+
finally:
|
| 66 |
+
root.removeHandler(fh)
|
| 67 |
+
fh.close()
|
src/nusim/nusim/paths.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""๋ฐ์ดํฐ์
/์์
๊ณต๊ฐ ๊ฒฝ๋ก ํด์.
|
| 2 |
+
|
| 3 |
+
์ปจํ
์ด๋ ์์์๋ nuScenes ๊ฐ /data/nuscenes ์ ์ฝ๊ธฐ์ ์ฉ์ผ๋ก ๋ง์ดํธ๋๊ณ ,
|
| 4 |
+
์ปจํ
์ด๋ ๋ฐ(ํธ์คํธ)์์๋ NAS ์๋ณธ ๊ฒฝ๋ก๋ฅผ ๊ทธ๋๋ก ์ด๋ค.
|
| 5 |
+
์ฝ๋๊ฐ ๋ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ๊ฒฌ๋๋๋ก ์ฌ๊ธฐ ํ ๊ณณ์์๋ง ๊ฒฐ์ ํ๋ค.
|
| 6 |
+
|
| 7 |
+
์ฐ์ ์์: ๋ช
์ ์ธ์ > ํ๊ฒฝ๋ณ์ NUSCENES_ROOT > config/paths.yaml > ์๋ ค์ง ํ๋ณด
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# ์ปจํ
์ด๋ ๋ง์ดํธ ๊ฒฝ๋ก๊ฐ ๋จผ์ , ๊ทธ ๋ค์์ด ํธ์คํธ NAS ๊ฒฝ๋ก.
|
| 17 |
+
_ROOT_CANDIDATES = (
|
| 18 |
+
"/data/nuscenes",
|
| 19 |
+
"/home/mm/nas2/nuscenes_original_junhan/nuscenes/original/full",
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
# ๋ฃจํธ๊ฐ ๋ง๋์ง ํ์ ํ๋ ๊ธฐ์ค. ์ด ์ธ ๊ฐ์ง๊ฐ ์ ๋ถ ์์ด์ผ ์ฐ๋ฆฌ๊ฐ ์ฐ๋ ํ์ดํ๋ผ์ธ์ด ๋๋ค.
|
| 23 |
+
_ROOT_MARKERS = ("v1.0-trainval", "samples", "maps")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def workspace_root() -> Path:
|
| 27 |
+
"""ํ๋ก์ ํธ ๋ฃจํธ. ์ปจํ
์ด๋์์๋ /workspace, ๋ฐ์์๋ ์์ค ํธ๋ฆฌ๋ฅผ ๊ฑฐ์ฌ๋ฌ ์ฐพ๋๋ค."""
|
| 28 |
+
if env := os.environ.get("VKGS_WORKSPACE"):
|
| 29 |
+
return Path(env)
|
| 30 |
+
# .../src/nusim/nusim/paths.py โ 3๋จ๊ณ ์๊ฐ ํ๋ก์ ํธ ๋ฃจํธ
|
| 31 |
+
return Path(__file__).resolve().parents[3]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _looks_like_nuscenes(p: Path) -> bool:
|
| 35 |
+
return all((p / m).exists() for m in _ROOT_MARKERS)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def nuscenes_root(explicit: str | os.PathLike | None = None) -> Path:
|
| 39 |
+
"""nuScenes ๋ฐ์ดํฐ์
๋ฃจํธ๋ฅผ ๋๋ ค์ค๋ค. ๋ชป ์ฐพ์ผ๋ฉด ๋ฌด์์ ์ฐพ์๋์ง ์๋ ค์ฃผ๋ฉฐ ์คํจํ๋ค."""
|
| 40 |
+
tried: list[str] = []
|
| 41 |
+
|
| 42 |
+
for cand in (explicit, os.environ.get("NUSCENES_ROOT")):
|
| 43 |
+
if not cand:
|
| 44 |
+
continue
|
| 45 |
+
p = Path(cand)
|
| 46 |
+
if _looks_like_nuscenes(p):
|
| 47 |
+
return p
|
| 48 |
+
tried.append(f"{p} (์ง์ ๋์ผ๋ {_ROOT_MARKERS} ์ค ์ผ๋ถ๊ฐ ์์)")
|
| 49 |
+
|
| 50 |
+
cfg = workspace_root() / "config" / "paths.yaml"
|
| 51 |
+
if cfg.exists():
|
| 52 |
+
try:
|
| 53 |
+
import yaml
|
| 54 |
+
|
| 55 |
+
data = yaml.safe_load(cfg.read_text()) or {}
|
| 56 |
+
if cand := data.get("nuscenes_root"):
|
| 57 |
+
p = Path(cand)
|
| 58 |
+
if _looks_like_nuscenes(p):
|
| 59 |
+
return p
|
| 60 |
+
tried.append(f"{p} (config/paths.yaml)")
|
| 61 |
+
except Exception as e: # yaml ์ด ์๊ฑฐ๋ ๊นจ์ง ๊ฒฝ์ฐ โ ์น๋ช
์ ์ด์ง ์๋ค
|
| 62 |
+
tried.append(f"{cfg} (์ฝ๊ธฐ ์คํจ: {e})")
|
| 63 |
+
|
| 64 |
+
for cand in _ROOT_CANDIDATES:
|
| 65 |
+
p = Path(cand)
|
| 66 |
+
if _looks_like_nuscenes(p):
|
| 67 |
+
return p
|
| 68 |
+
tried.append(str(p))
|
| 69 |
+
|
| 70 |
+
raise FileNotFoundError(
|
| 71 |
+
"nuScenes ๋ฃจํธ๋ฅผ ์ฐพ์ง ๋ชปํ์ต๋๋ค. ์๋ํ ๊ฒฝ๋ก:\n "
|
| 72 |
+
+ "\n ".join(tried)
|
| 73 |
+
+ "\n\n๋ฃจํธ์๋ ๋ค์์ด ์์ด์ผ ํฉ๋๋ค: "
|
| 74 |
+
+ ", ".join(_ROOT_MARKERS)
|
| 75 |
+
+ "\nํ๊ฒฝ๋ณ์ NUSCENES_ROOT ๋ก ์ง์ ํ๊ฑฐ๋ config/paths.yaml ์ ๊ณ ์น์ธ์."
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@dataclass(frozen=True)
|
| 80 |
+
class DatasetPaths:
|
| 81 |
+
"""nuScenes ๋ฃจํธ ์๋์ ํ์ ๊ฒฝ๋ก ๋ชจ์. ๊ฒฝ๋ก ๋ฌธ์์ด์ ์ฝ๋ ๊ณณ๊ณณ์ ํฉ์ง ์๊ธฐ ์ํจ."""
|
| 82 |
+
|
| 83 |
+
root: Path
|
| 84 |
+
version: str = "v1.0-trainval"
|
| 85 |
+
# ์นด๋ฉ๋ผ ์ด๋ฏธ์ง๋ง ๋ค๋ฅธ ๊ณณ์์ ์ฝ์ ๋ (๊ฐ์ ์๋ ๊ฒฝ๋ก) โ ์: ๋ฐคยท๋น โ ๋ฎ์ผ๋ก ๋ฐ๊พผ ์์ (nusim.aug.translate).
|
| 86 |
+
# ๋ผ์ด๋คยท๋ฉํยท์ง๋๋ ๊ทธ๋๋ก root ์์.
|
| 87 |
+
image_root: Path | None = None
|
| 88 |
+
|
| 89 |
+
@property
|
| 90 |
+
def images(self) -> Path:
|
| 91 |
+
return self.image_root or self.root
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def meta(self) -> Path:
|
| 95 |
+
"""scene.json / sample_data.json ๋ฑ ํ
์ด๋ธ์ด ์๋ ๊ณณ."""
|
| 96 |
+
return self.root / self.version
|
| 97 |
+
|
| 98 |
+
@property
|
| 99 |
+
def samples(self) -> Path:
|
| 100 |
+
"""ํคํ๋ ์ ์ผ์ ๋ฐ์ดํฐ (2 Hz)."""
|
| 101 |
+
return self.root / "samples"
|
| 102 |
+
|
| 103 |
+
@property
|
| 104 |
+
def sweeps(self) -> Path:
|
| 105 |
+
"""๋นํคํ๋ ์ ์ผ์ ๋ฐ์ดํฐ (์นด๋ฉ๋ผ 12 Hz). 3DGS ์ฌ๊ตฌ์ฑ์ ํต์ฌ์ด๋ค โ
|
| 106 |
+
ํคํ๋ ์๋ง ์ฐ๋ฉด ๋ทฐ๊ฐ ๋๋ฌด ์ฑ๊ฒจ์ ๊ฐ์ฐ์์์ด ์๋ ดํ์ง ์๋๋ค."""
|
| 107 |
+
return self.root / "sweeps"
|
| 108 |
+
|
| 109 |
+
@property
|
| 110 |
+
def maps(self) -> Path:
|
| 111 |
+
return self.root / "maps"
|
| 112 |
+
|
| 113 |
+
@property
|
| 114 |
+
def map_expansion(self) -> Path:
|
| 115 |
+
"""singapore-onenorth.json ๋ฑ ๋ฒกํฐ ๋งต. ์ฐจ์ ๊ทธ๋ํ์ ์ถ์ฒ๋ค."""
|
| 116 |
+
return self.root / "maps" / "expansion"
|
| 117 |
+
|
| 118 |
+
@property
|
| 119 |
+
def can_bus(self) -> Path:
|
| 120 |
+
"""scene-XXXX_pose.json ๋ฑ. ego_pose.json ๋ณด๋ค ํจ์ฌ ์กฐ๋ฐํ ์์ฐจ ์ํ๋ก,
|
| 121 |
+
closed-loop ์ฐจ๋ ๋ชจ๋ธ ํ๋์ ์ด๋ค."""
|
| 122 |
+
return self.root / "can_bus"
|
| 123 |
+
|
| 124 |
+
def check(self) -> dict[str, tuple[bool, Path]]:
|
| 125 |
+
"""๊ฐ ํ์ ๊ฒฝ๋ก์ ์กด์ฌ ์ฌ๋ถ. ์ง๋จ(vkgs-doctor)์์ ์ด๋ค."""
|
| 126 |
+
return {
|
| 127 |
+
name: (path.exists(), path)
|
| 128 |
+
for name, path in (
|
| 129 |
+
("meta", self.meta),
|
| 130 |
+
("samples", self.samples),
|
| 131 |
+
("sweeps", self.sweeps),
|
| 132 |
+
("maps", self.maps),
|
| 133 |
+
("map_expansion", self.map_expansion),
|
| 134 |
+
("can_bus", self.can_bus),
|
| 135 |
+
)
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def dataset(explicit: str | os.PathLike | None = None, version: str = "v1.0-trainval") -> DatasetPaths:
|
| 140 |
+
return DatasetPaths(root=nuscenes_root(explicit), version=version)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def cache_dir() -> Path:
|
| 144 |
+
"""ํ์ ์ฐ์ถ๋ฌผ(์ฌ ์ธ๋ฑ์ค ๋ฑ). NAS ๊ฐ ์๋๋ผ ๋ก์ปฌ ๋์คํฌ์ฌ์ผ ํ๋ค โ
|
| 145 |
+
NFS ์๋ณต์ด ์ธ๋ฑ์ค ์กฐํ๋ณด๋ค ๋น์ธ๋ฉด ์บ์์ ์๋ฏธ๊ฐ ์๋ค."""
|
| 146 |
+
d = Path(os.environ.get("VKGS_CACHE", workspace_root() / "cache"))
|
| 147 |
+
d.mkdir(parents=True, exist_ok=True)
|
| 148 |
+
return d
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def output_dir() -> Path:
|
| 152 |
+
d = Path(os.environ.get("VKGS_OUTPUT", workspace_root() / "outputs"))
|
| 153 |
+
d.mkdir(parents=True, exist_ok=True)
|
| 154 |
+
return d
|
src/nusim/nusim/render/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Vulkan ๋ ๋๋ฌ(src/vkgs/vkgs_viewer) ์์ ์ ์ .
|
| 2 |
+
|
| 3 |
+
export.py ์นด๋ฉ๋ผ ๋ฆฌ๊ทธยท๋งต ์ ยทํ๊ฐ ๋ทฐ๋ฅผ ๋ทฐ์ด๊ฐ ์ฝ๋ ํ
์คํธ ํ์ผ๋ก
|
| 4 |
+
compare.py ๊ฐ์ ์นด๋ฉ๋ผ์์ ํ์ต๊ธฐ(gsplat) ๋ ๋ vs Vulkan ๋ ๋ ํฝ์
๋น๊ต (roadmap 2๋จ๊ณ ๊ธฐ์ค)
|
| 5 |
+
|
| 6 |
+
nusim render export scene-0003 # rig.txt / map.txt / test_views.txt
|
| 7 |
+
nusim render compare scene-0003 # ์คํ์คํฌ๋ฆฐ ๋ ๋ ํ PSNR / ํ๊ท ์ค์ฐจ
|
| 8 |
+
nusim render view scene-0003 # ์ฐฝ ๋ชจ๋ ๋ทฐ์ด ์คํ
|
| 9 |
+
"""
|
src/nusim/pyproject.toml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=68", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "nusim"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "nuScenes ์ฑ๊ฐํฌ๋ฅด ๋งต ๊ธฐ๋ฐ Vulkan 3DGS closed-loop ์๋ฎฌ๋ ์ดํฐ"
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
|
| 11 |
+
# ์ค์ ์ค์น๋ docker/requirements.txt ๊ฐ ๋ด๋นํ๋ค (Dockerfile ์ด --no-deps ๋ก ๊ฑด๋ค).
|
| 12 |
+
# ์ฌ๊ธฐ ๋ชฉ๋ก์ ์ปจํ
์ด๋ ๋ฐ์์ ์ธ ๋๋ฅผ ์ํ ๋ฌธ์ ๊ฒธ ์ฌ์์ด๋ค.
|
| 13 |
+
dependencies = [
|
| 14 |
+
"numpy<2.0", "scipy", "Pillow", "opencv-python-headless",
|
| 15 |
+
"pyquaternion", "shapely<2.0", "cachetools", "matplotlib",
|
| 16 |
+
"plyfile", "pyyaml", "typer", "rich", "tqdm",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
[project.scripts]
|
| 20 |
+
vkgs-doctor = "nusim.cli.doctor:main"
|
| 21 |
+
nusim = "nusim.cli.main:main"
|
| 22 |
+
|
| 23 |
+
[tool.setuptools.packages.find]
|
| 24 |
+
where = ["."]
|
| 25 |
+
include = ["nusim*"]
|
src/vkgs/CMakeLists.txt
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cmake_minimum_required(VERSION 3.22)
|
| 2 |
+
project(vkgs LANGUAGES C CXX)
|
| 3 |
+
|
| 4 |
+
# 3DGS compute ๋์คํฐ๋ผ์ด์ ๋ C++20 ์ designated initializer ์ <bit> ๋ฅผ ์ด๋ค.
|
| 5 |
+
set(CMAKE_CXX_STANDARD 20)
|
| 6 |
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
| 7 |
+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # clangd ์ฉ
|
| 8 |
+
|
| 9 |
+
if(NOT CMAKE_BUILD_TYPE)
|
| 10 |
+
set(CMAKE_BUILD_TYPE RelWithDebInfo)
|
| 11 |
+
endif()
|
| 12 |
+
|
| 13 |
+
find_package(Vulkan REQUIRED)
|
| 14 |
+
message(STATUS "Vulkan: ${Vulkan_LIBRARY} (ํค๋ ${Vulkan_INCLUDE_DIR})")
|
| 15 |
+
|
| 16 |
+
# ---------------------------------------------------------------- ์
ฐ์ด๋ ์ปดํ์ผ
|
| 17 |
+
# glslc(shaderc) ๋ฅผ ์ ํธํ๋ ์์ผ๋ฉด glslangValidator ๋ก ํด๋ฐฑํ๋ค.
|
| 18 |
+
# jammy ์์ glslc ๋ ๋ณ๋ ํจํค์ง๋ผ ํญ์ ์๋ค๊ณ ๊ฐ์ ํ ์ ์๋ค.
|
| 19 |
+
# FindVulkan ์ด glslc ๋ฅผ ์ฐพ์ ์ฃผ๋ฉด ๊ทธ๊ฑธ ์ฐ๊ณ , ์๋๋ฉด PATH ์์ ์ฐพ๋๋ค.
|
| 20 |
+
if(Vulkan_GLSLC_EXECUTABLE)
|
| 21 |
+
set(GLSLC ${Vulkan_GLSLC_EXECUTABLE})
|
| 22 |
+
else()
|
| 23 |
+
find_program(GLSLC glslc)
|
| 24 |
+
endif()
|
| 25 |
+
find_program(GLSLANG glslangValidator)
|
| 26 |
+
|
| 27 |
+
if(GLSLC)
|
| 28 |
+
# -I: gs_common.glsl ๊ฐ์ ๊ณตํต ํค๋๋ฅผ #include ํ๊ธฐ ์ํจ
|
| 29 |
+
set(SHADER_CMD ${GLSLC} --target-env=vulkan1.2 -O -I ${CMAKE_CURRENT_SOURCE_DIR}/shaders)
|
| 30 |
+
message(STATUS "์
ฐ์ด๋ ์ปดํ์ผ๋ฌ: ${GLSLC}")
|
| 31 |
+
elseif(GLSLANG)
|
| 32 |
+
# -V ๋ฅผ ๋ฐ๋์ ์ค๋ค. --target-env ๋ง์ผ๋ก SPIR-V ๊ฐ ๋์จ๋ค๊ณ ๊ธฐ๋ํ๋ฉด ์ ๋๋ค โ
|
| 33 |
+
# ๋ฒ์ ์ ๋ฐ๋ผ ๊ฒ์ฆ๋ง ํ๊ณ -o ๋ฅผ ๋ฌด์ํด ๋น ํ์ผ์ด ๋จ๋๋ค.
|
| 34 |
+
set(SHADER_CMD ${GLSLANG} -V --target-env vulkan1.2 -I${CMAKE_CURRENT_SOURCE_DIR}/shaders)
|
| 35 |
+
message(STATUS "์
ฐ์ด๋ ์ปดํ์ผ๋ฌ: ${GLSLANG} (glslc ์์)")
|
| 36 |
+
else()
|
| 37 |
+
message(FATAL_ERROR "glslc ๋ glslangValidator ๋ ์์ต๋๋ค. glslang-tools ๋ฅผ ์ค์นํ์ธ์.")
|
| 38 |
+
endif()
|
| 39 |
+
set(SHADER_OUT_FLAG -o)
|
| 40 |
+
|
| 41 |
+
set(SPIRV_DIR ${CMAKE_BINARY_DIR}/shaders)
|
| 42 |
+
file(MAKE_DIRECTORY ${SPIRV_DIR})
|
| 43 |
+
|
| 44 |
+
# GLSL ํ๋๋ฅผ SPIR-V ๋ก ๊ตฝ๋๋ค. ๊ฒฐ๊ณผ๋ ๋น๋ ๋๋ ํฐ๋ฆฌ์ shaders/ ์๋์ ๋์ธ๋ค.
|
| 45 |
+
function(vkgs_add_shader SRC)
|
| 46 |
+
get_filename_component(NAME ${SRC} NAME)
|
| 47 |
+
set(OUT ${SPIRV_DIR}/${NAME}.spv)
|
| 48 |
+
add_custom_command(
|
| 49 |
+
OUTPUT ${OUT}
|
| 50 |
+
COMMAND ${SHADER_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC} ${SHADER_OUT_FLAG} ${OUT}
|
| 51 |
+
# ์ปดํ์ผ๋ฌ๊ฐ ์กฐ์ฉํ ๋น ํ์ผ์ ๋จ๊ธฐ๋ ๊ฒฝ์ฐ๊ฐ ์์ด ์ฌ๊ธฐ์ ๊ฑธ๋ฌ๋ธ๋ค.
|
| 52 |
+
COMMAND ${CMAKE_COMMAND} -E cat ${OUT} > /dev/null
|
| 53 |
+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SRC} ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gs_common.glsl ${CMAKE_CURRENT_SOURCE_DIR}/shaders/ibl_common.glsl
|
| 54 |
+
${CMAKE_CURRENT_SOURCE_DIR}/shaders/depth_common.glsl ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bindless.glsl
|
| 55 |
+
${CMAKE_CURRENT_SOURCE_DIR}/shaders/gs_radix_common.glsl ${CMAKE_CURRENT_SOURCE_DIR}/shaders/weather_common.glsl
|
| 56 |
+
COMMENT "SPIR-V: ${NAME}"
|
| 57 |
+
VERBATIM)
|
| 58 |
+
list(APPEND VKGS_SPIRV ${OUT})
|
| 59 |
+
set(VKGS_SPIRV ${VKGS_SPIRV} PARENT_SCOPE)
|
| 60 |
+
endfunction()
|
| 61 |
+
|
| 62 |
+
vkgs_add_shader(shaders/smoke.comp)
|
| 63 |
+
vkgs_add_shader(shaders/gs_preprocess.comp)
|
| 64 |
+
vkgs_add_shader(shaders/gs_sort.comp)
|
| 65 |
+
vkgs_add_shader(shaders/gs_radix_hist.comp)
|
| 66 |
+
vkgs_add_shader(shaders/gs_radix_scan.comp)
|
| 67 |
+
vkgs_add_shader(shaders/gs_radix_scatter.comp)
|
| 68 |
+
vkgs_add_shader(shaders/gs_splat.vert)
|
| 69 |
+
vkgs_add_shader(shaders/gs_splat.frag)
|
| 70 |
+
vkgs_add_shader(shaders/line.vert)
|
| 71 |
+
vkgs_add_shader(shaders/line.frag)
|
| 72 |
+
vkgs_add_shader(shaders/env.vert)
|
| 73 |
+
vkgs_add_shader(shaders/env.frag)
|
| 74 |
+
vkgs_add_shader(shaders/pbr.vert)
|
| 75 |
+
vkgs_add_shader(shaders/pbr.frag)
|
| 76 |
+
vkgs_add_shader(shaders/weather.comp)
|
| 77 |
+
vkgs_add_shader(shaders/fxaa.comp)
|
| 78 |
+
vkgs_add_shader(shaders/camera_mode.comp)
|
| 79 |
+
vkgs_add_shader(shaders/precip.vert)
|
| 80 |
+
vkgs_add_shader(shaders/precip.frag)
|
| 81 |
+
vkgs_add_shader(shaders/sky_sim.frag)
|
| 82 |
+
vkgs_add_shader(shaders/pbr_gltf.vert)
|
| 83 |
+
vkgs_add_shader(shaders/pbr_gltf.frag)
|
| 84 |
+
vkgs_add_shader(shaders/shadow.vert)
|
| 85 |
+
vkgs_add_shader(shaders/shadow.frag)
|
| 86 |
+
|
| 87 |
+
# ์
ฐ์ด๋ ๊ทธ๋ํ (configs/shader_graphs/*.json) โ scripts/shader_graph.py ๊ฐ GLSL ๋ก ํผ์นจ โ SPIR-V.
|
| 88 |
+
# ๊ทธ๋ํ JSONยท๋
ธ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(shaders/nodes/*.glsl)ยท์์ฑ๊ธฐ๊ฐ ๋ฐ๋๋ฉด ๋ค์ ๋ง๋ ๋ค. ๋งค๊ฐ๋ณ์ ํ(.params.json)๋
|
| 89 |
+
# SPIR-V ์์ ๋์ฌ ๋ทฐ์ด๊ฐ ์ฌ๋ผ์ด๋ยท์ด๊น๊ฐ์ ์ด๋ค.
|
| 90 |
+
find_program(VKGS_PYTHON NAMES python3 python REQUIRED)
|
| 91 |
+
set(GRAPH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../configs/shader_graphs)
|
| 92 |
+
set(GRAPH_GEN ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/shader_graph.py)
|
| 93 |
+
file(GLOB VKGS_SHADER_GRAPHS CONFIGURE_DEPENDS ${GRAPH_DIR}/*.json)
|
| 94 |
+
file(GLOB VKGS_NODE_LIBS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/nodes/*.glsl)
|
| 95 |
+
foreach(G ${VKGS_SHADER_GRAPHS})
|
| 96 |
+
get_filename_component(GNAME ${G} NAME_WE)
|
| 97 |
+
set(GSRC ${SPIRV_DIR}/gen/${GNAME}.comp)
|
| 98 |
+
set(GOUT ${SPIRV_DIR}/${GNAME}.comp.spv)
|
| 99 |
+
add_custom_command(
|
| 100 |
+
OUTPUT ${GSRC} ${SPIRV_DIR}/${GNAME}.params.json
|
| 101 |
+
COMMAND ${VKGS_PYTHON} ${GRAPH_GEN} ${G} --shaders ${CMAKE_CURRENT_SOURCE_DIR}/shaders --out ${SPIRV_DIR}/gen
|
| 102 |
+
COMMAND ${CMAKE_COMMAND} -E copy ${SPIRV_DIR}/gen/${GNAME}.params.json ${SPIRV_DIR}/${GNAME}.params.json
|
| 103 |
+
DEPENDS ${G} ${GRAPH_GEN} ${VKGS_NODE_LIBS}
|
| 104 |
+
COMMENT "์
ฐ์ด๋ ๊ทธ๋ํ: ${GNAME}"
|
| 105 |
+
VERBATIM)
|
| 106 |
+
add_custom_command(
|
| 107 |
+
OUTPUT ${GOUT}
|
| 108 |
+
COMMAND ${SHADER_CMD} ${GSRC} ${SHADER_OUT_FLAG} ${GOUT}
|
| 109 |
+
DEPENDS ${GSRC} ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bindless.glsl
|
| 110 |
+
COMMENT "SPIR-V: ${GNAME}.comp (์
ฐ์ด๋ ๊ทธ๋ํ)"
|
| 111 |
+
VERBATIM)
|
| 112 |
+
list(APPEND VKGS_SPIRV ${GOUT} ${SPIRV_DIR}/${GNAME}.params.json)
|
| 113 |
+
endforeach()
|
| 114 |
+
add_custom_target(vkgs_shaders ALL DEPENDS ${VKGS_SPIRV})
|
| 115 |
+
|
| 116 |
+
# ---------------------------------------------------------------- ์คํํ์ผ
|
| 117 |
+
# vkgs_probe: ์ปจํ
์ด๋์ Vulkan ๊ฒฝ๋ก๊ฐ ์ง์ง๋ก ๋์ํ๋์ง ๋๊น์ง ํ์ธํ๋ค.
|
| 118 |
+
# ์ฅ์น ์ด๊ฑฐ โ ํ ์์ฑ โ SPIR-V ๋ก๋ โ ์ปดํจํธ ๋์คํจ์น โ ๊ฒฐ๊ณผ ์ฝ๊ธฐ
|
| 119 |
+
# ์ด ์ค ํ๋๋ผ๋ ์กฐ์ฉํ ์คํจํ๋ฉด ๋ ๋๋ฌ๋ ๊ฒ์ ํ๋ฉด๋ง ๋ธ๋ค.
|
| 120 |
+
add_executable(vkgs_probe src/probe.cpp)
|
| 121 |
+
target_link_libraries(vkgs_probe PRIVATE Vulkan::Vulkan)
|
| 122 |
+
target_compile_definitions(vkgs_probe PRIVATE VKGS_SPIRV_DIR="${SPIRV_DIR}")
|
| 123 |
+
add_dependencies(vkgs_probe vkgs_shaders)
|
| 124 |
+
|
| 125 |
+
# vkgs_voxcarve: ๋ ์ด์บ์คํธ ์ผ์์ฉ ๋ณต์
์๋์ ๋น ๊ณต๊ฐ ๊น๊ธฐ (nusim/sim/voxels.py ๊ฐ ๋ถ๋ฅธ๋ค)
|
| 126 |
+
add_executable(vkgs_voxcarve src/voxcarve.cpp)
|
| 127 |
+
find_package(Threads REQUIRED)
|
| 128 |
+
target_link_libraries(vkgs_voxcarve PRIVATE Threads::Threads)
|
| 129 |
+
|
| 130 |
+
# vkgs_viewer: 3DGS .ply ๋ฅผ ๊ทธ๋ฆฐ๋ค (์คํ์คํฌ๋ฆฐ PNG / GLFW ์ฐฝ). roadmap 2a.
|
| 131 |
+
find_package(glfw3 3.3 REQUIRED)
|
| 132 |
+
# Dear ImGui (MIT) โ ๋ทฐ์ด ์ ์ฌ๋ผ์ด๋ UI (ํฉ์ฑ ๋ ์จยท์กฐ๋). third_party/imgui (v1.91.9)
|
| 133 |
+
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/imgui)
|
| 134 |
+
# SPIRV-Reflect (Khronos, Apache-2.0) โ ์
ฐ์ด๋ ๋ฆฌํ๋ ์
(src/shader_reflect.*)
|
| 135 |
+
set(SPVR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/SPIRV-Reflect)
|
| 136 |
+
add_executable(vkgs_viewer src/viewer.cpp src/ply.cpp src/gltf.cpp src/shader_reflect.cpp src/render_graph.cpp ${SPVR_DIR}/spirv_reflect.c
|
| 137 |
+
${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_tables.cpp
|
| 138 |
+
${IMGUI_DIR}/imgui_widgets.cpp ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
|
| 139 |
+
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp)
|
| 140 |
+
# GLFW ํค๋๊ฐ OpenGL ํค๋(GL/gl.h)๋ฅผ ๋์ด์ค์ง ์๊ฒ โ ์ฐ๋ฆฌ๋ Vulkan ๋ง ์ด๋ค
|
| 141 |
+
set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_glfw.cpp PROPERTIES COMPILE_DEFINITIONS GLFW_INCLUDE_NONE)
|
| 142 |
+
target_include_directories(vkgs_viewer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/stb
|
| 143 |
+
${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/cgltf
|
| 144 |
+
${IMGUI_DIR} ${IMGUI_DIR}/backends
|
| 145 |
+
${CMAKE_CURRENT_SOURCE_DIR}/../../third_party
|
| 146 |
+
${SPVR_DIR})
|
| 147 |
+
target_link_libraries(vkgs_viewer PRIVATE Vulkan::Vulkan glfw)
|
| 148 |
+
target_compile_definitions(vkgs_viewer PRIVATE VKGS_SPIRV_DIR="${SPIRV_DIR}"
|
| 149 |
+
VKGS_CONFIG_DEFAULT="${CMAKE_CURRENT_SOURCE_DIR}/../../configs/sim.json"
|
| 150 |
+
VKGS_RENDER_GRAPH_DEFAULT="${CMAKE_CURRENT_SOURCE_DIR}/../../configs/render_graph.json"
|
| 151 |
+
VKGS_TRACY_PROFILER_DEFAULT="${CMAKE_BINARY_DIR}/tracy-profiler/tracy-profiler")
|
| 152 |
+
add_dependencies(vkgs_viewer vkgs_shaders)
|
| 153 |
+
|
| 154 |
+
# Tracy ํ๋กํ์ผ๋ฌ ํด๋ผ์ด์ธํธ (third_party/tracy v0.11.1, BSD-3). ON_DEMAND: ํ๋กํ์ผ๋ฌ๊ฐ ๋ถ์ด ์์ ๋๋ง ๊ธฐ๋ก โ
|
| 155 |
+
# ํ์ ์คํ ๋น์ฉ์ ๊ฑฐ์ ์๋ค. ํ๋กํ์ผ๋ฌ GUIยท์บก์ฒ ๋๊ตฌ ๋น๋๋ scripts/build_tracy.sh.
|
| 156 |
+
option(VKGS_TRACY "Tracy ํ๋กํ์ผ๋ง (CPUยทGPU ๊ตฌ๊ฐ)" ON)
|
| 157 |
+
if(VKGS_TRACY)
|
| 158 |
+
set(TRACY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/tracy)
|
| 159 |
+
target_sources(vkgs_viewer PRIVATE ${TRACY_DIR}/public/TracyClient.cpp)
|
| 160 |
+
target_include_directories(vkgs_viewer PRIVATE ${TRACY_DIR}/public)
|
| 161 |
+
target_compile_definitions(vkgs_viewer PRIVATE TRACY_ENABLE TRACY_ON_DEMAND)
|
| 162 |
+
target_link_libraries(vkgs_viewer PRIVATE dl pthread)
|
| 163 |
+
else()
|
| 164 |
+
target_include_directories(vkgs_viewer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/tracy/public)
|
| 165 |
+
endif()
|
| 166 |
+
|
| 167 |
+
install(TARGETS vkgs_probe vkgs_viewer vkgs_voxcarve RUNTIME DESTINATION bin)
|
| 168 |
+
install(DIRECTORY ${SPIRV_DIR}/ DESTINATION share/vkgs/shaders)
|
src/vkgs/shaders/bindless.glsl
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ์ ์ญ bindless descriptor set (set 0) โ viewer.cpp ์ Bindless ์ ๊ฐ์ bindingยท์ฌ๋กฏ ๋ฒํธ.
|
| 2 |
+
// ๋ชจ๋ ํ์ดํ๋ผ์ธ์ด ์ด set ํ๋์ ๊ฐ์ push constant ๋ฒ์๋ฅผ ์ด๋ค. ๋ฆฌ์์ค๋ '๋ฐฐ์ด + ๋ฒํธ' ๋ก ๊ณ ๋ฅธ๋ค:
|
| 3 |
+
// binding 0 SSBO[] ์
ฐ์ด๋๋ง๋ค ํ์ํ ํ์
์ผ๋ก ๊ฐ์ binding ์ ๋ณ์นญ ์ ์ธํ๋ค
|
| 4 |
+
// binding 1 UBO[]
|
| 5 |
+
// binding 2 sampler2D[] glTF ์ฌ์ง ํ
์ค์ฒ, BRDF LUT
|
| 6 |
+
// binding 3 samplerCube[] IBL specularยทirradiance, ํ์ต๋ ํ๋
|
| 7 |
+
// binding 4 image2D[] 4์ฑ๋ ๋ ๋ ํ๊ฒยท๋ ์จ ๊ฒฐ๊ณผยท๋ฐฐ๊ฒฝยท๊ฐ์ (ํ์ ์๋ storage โ ํ์์ ๋ ๋ ๊ทธ๋ํ JSON)
|
| 8 |
+
// binding 5 image2D[] 1~2์ฑ๋ ๊ฑฐ๋ฆฌ ๋์ (RG: ฮฃzฮฑ, ฮฃฮฑ โ depth_common.glsl)
|
| 9 |
+
// ํ๋๋ฟ์ธ ๋ฆฌ์์ค๋ ๊ณ ์ ์ฌ๋กฏ (์๋ ์์), ์ฌ๋ฌ ๊ฐ์ธ ๋ฆฌ์์ค(์ฌ์งยทํ
์ค์ฒยทํ๋ธ)๋ push constant ๋ ์ ์ญ ์์์ ๋ฒํธ.
|
| 10 |
+
#extension GL_EXT_nonuniform_qualifier : require
|
| 11 |
+
|
| 12 |
+
const uint kSsboGlobals = 0u, kSsboGauss = 1u, kSsboSplats = 2u, kSsboKeys = 3u, kSsboDraw = 4u;
|
| 13 |
+
const uint kUboCamera = 0u, kUboWeather = 1u;
|
| 14 |
+
const uint kImgTarget = 0u, kImgPost = 1u; // rgba32f
|
| 15 |
+
const uint kImgDepthAcc = 0u; // r32f
|
| 16 |
+
|
| 17 |
+
layout(set = 0, binding = 2) uniform sampler2D gTex2D[];
|
| 18 |
+
layout(set = 0, binding = 3) uniform samplerCube gCube[];
|
| 19 |
+
// ์ ์ญ ์์ (SSBO ์ฌ๋กฏ 0): ibl = (specular ํ๋ธ, irradiance ํ๋ธ, BRDF LUT 2D, ํ์ต๋ ํ๋ ํ๋ธ) ๋ฒํธ
|
| 20 |
+
layout(std430, set = 0, binding = 0) readonly buffer GlobalsBuf { ivec4 ibl; ivec4 pad; } gGlobals[];
|
src/vkgs/shaders/depth_common.glsl
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ๊ฑฐ๋ฆฌ ๋์ ๋ฒํผ (๋ ๋ ํจ์ค ์ ์ฒจ๋ถ 1, RG32F) โ ๋ ์จ ํ์ฒ๋ฆฌ(weather.comp)ยท๊ฐ์ ์
์(precip.frag)๊ฐ ์ด๋ค.
|
| 2 |
+
// ๊ทธ๋ฆฌ๋ ๊ฒ๋ง๋ค (zยทฮฑ, ฮฑ) ๋ฅผ ์๊ณผ ๊ฐ์ premultiplied over ๋ก ์๋๋ค:
|
| 3 |
+
// R = ฮฃ zยทฮฑยทT (์นด๋ฉ๋ผ z, m), G = ฮฃ ฮฑยทT = ๋ฐฐ๊ฒฝ์ ๋บ ๋ฎ์ ์ ๋ (0 = ๋ฐฐ๊ฒฝ๋ง, 1 = ๋ถํฌ๋ช
ํ ๊ฒ์ด ๋ค ๊ฐ๋ฆผ)
|
| 4 |
+
// ๋ฐฐ๊ฒฝ(ํ๋ยทํ๊ฒฝ๋งต)์ (0, 0) ์ ฮฑ = 1 ๋ก ์ด๋ค โ ์์ ๋์ ์ ์ง์ฐ๊ณ ๋ฎ์ 0 ์์ ์์. ์ง์ด ๊ฐ๋ 0.
|
| 5 |
+
// ํ๋ฉด ๊น์ด = R / G, ๋ฐฐ๊ฒฝ์ด ๋ณด์ด๋ ๋น์จ = 1 โ G (accum_depth / accum_cover).
|
| 6 |
+
// (2026-10-05 ์ ๊น์ง๋ R ํ๋์ ๋ฐฐ๊ฒฝ 1000 m ๋ฅผ ์์ด '๊ธฐ๋ ๊น์ด' ๋ก ์ผ๋ค โ ๋ฐํฌ๋ช
์ด 3%๋ง ๋จ์๋ 30 m ๊ฐ ๋ํด์ ธ
|
| 7 |
+
// ๋น๋ฐฉ์ธ ๊ฐ๋ฆผยท์๊ฐยท๋
ธ๋ฉด ํ์ ์ด ํ๋ ธ๋ค.)
|
| 8 |
+
const float kNear = 0.2, kFar = 2000.0; // viewer.cpp RenderCam ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฐ๋ค
|
| 9 |
+
const float kSkyDepth = 1000.0;
|
| 10 |
+
|
| 11 |
+
// Vulkan [0,1] ๊น์ด(gl_FragCoord.z) โ ์นด๋ฉ๋ผ z (m). viewer.cpp projection() ์ ์ญ.
|
| 12 |
+
float linear_depth(float zndc) {
|
| 13 |
+
return kFar * kNear / (kFar - zndc * (kFar - kNear));
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// ๋์ ๋ฒํผ ๊ฐ (R, G) โ ํ๋ฉด ์นด๋ฉ๋ผ z (m). ๋ฎ์์ด ๊ฑฐ์ ์์ผ๋ฉด kSkyDepth.
|
| 17 |
+
float accum_depth(vec2 rg) { return rg.y > 0.02 ? rg.x / rg.y : kSkyDepth; }
|
| 18 |
+
float accum_cover(vec2 rg) { return clamp(rg.y, 0.0, 1.0); }
|
| 19 |
+
|
src/vkgs/shaders/env.frag
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// ํ๊ฒฝ๋งต ๋ฐฐ๊ฒฝ. 3DGS ๊ฐ ๋น์ด ์๋(์ํ < 1) ๊ณณ์ผ๋ก ๋ณด์ธ๋ค โ ์คํ๋ซ์ด ๋ค์ ๊ทธ๋ ค์ง๋ฉฐ ๋ฎ๋๋ค.
|
| 4 |
+
#include "ibl_common.glsl"
|
| 5 |
+
#include "depth_common.glsl"
|
| 6 |
+
layout(location = 0) in vec2 vPix;
|
| 7 |
+
layout(location = 0) out vec4 outColor;
|
| 8 |
+
layout(push_constant) uniform PC {
|
| 9 |
+
mat4 c2w_rot;
|
| 10 |
+
vec4 intr;
|
| 11 |
+
vec4 size;
|
| 12 |
+
ivec4 idx; // x = ๋ฐฐ๊ฒฝ์ผ๋ก ์ธ ํ๋ธ ๋ฒํธ (IBL specular ๋๋ ํ์ต๋ ํ๋)
|
| 13 |
+
} pc;
|
| 14 |
+
void main() {
|
| 15 |
+
vec3 dc = vec3((vPix.x - pc.intr.z) / pc.intr.x, (vPix.y - pc.intr.w) / pc.intr.y, 1.0);
|
| 16 |
+
vec3 dw = normalize(mat3(pc.c2w_rot) * dc);
|
| 17 |
+
vec3 lin = textureLod(gCube[pc.idx.x], dw, pc.size.z).rgb;
|
| 18 |
+
// size.w = 1: ํ์ต๋ ํ๋(sky.ktx2) โ LDR ์ ์ค ์์ ์ ํ์ผ๋ก ๋ด์์ ๋ฟ์ด๋ผ ํ์ด๋ผ์ดํธ ์์ถ์ ํ์ง ์๋๋ค
|
| 19 |
+
vec3 c = pc.size.w > 0.5 ? lin : compress_highlights(lin);
|
| 20 |
+
outColor = vec4(linear_to_srgb(c), 1.0);
|
| 21 |
+
}
|
src/vkgs/shaders/env.vert
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
// ํ๋ฉด์ ๋ฎ๋ ์ผ๊ฐํ ํ๋. ํฝ์
โ ์์ ๋ฐฉํฅ์ ํ๋๊ทธ๋จผํธ์์.
|
| 3 |
+
layout(location = 0) out vec2 vPix;
|
| 4 |
+
layout(push_constant) uniform PC {
|
| 5 |
+
mat4 c2w_rot; // ์นด๋ฉ๋ผ โ ์ฌ ํ์ (OpenCV ์ถ)
|
| 6 |
+
vec4 intr; // fx fy cx cy
|
| 7 |
+
vec4 size; // w h lod -
|
| 8 |
+
ivec4 idx; // env.frag ์ฐธ๊ณ
|
| 9 |
+
} pc;
|
| 10 |
+
void main() {
|
| 11 |
+
vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
| 12 |
+
gl_Position = vec4(uv * 2.0 - 1.0, 1.0, 1.0);
|
| 13 |
+
vPix = uv * pc.size.xy;
|
| 14 |
+
}
|
src/vkgs/shaders/fxaa.comp
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#extension GL_EXT_shader_image_load_formatted : require
|
| 4 |
+
// FXAA (Lottes 3.11 ํ์ง ํ๋ฆฌ์
์ ์ค์ธ ๊ฒ) โ ํ๋ฉด ๊ณต๊ฐ ์ํฐ์์ผ๋ฆฌ์ด์ฑ, ์ปดํจํธ ํ ํจ์ค.
|
| 5 |
+
// MSAA ๋ ์คํ๋ซ ๋ธ๋ ๋ฉ์ ์ํ ์๋งํผ ๊ณฑํ๋ฏ๋ก(4ร ์ด๋ฉด ๋์คํฐ 4๋ฐฐ) ์ฐ์ง ์๋๋ค. 3DGS ๋ ์๋ ๋ถ๋๋ฝ๊ณ ,
|
| 6 |
+
// ๊ณ๋จ์ ๋ฉ์(์ฐจ๋)ยท๋งต ์ ๊ฐ์ฅ์๋ฆฌ์ ์๊ธด๋ค โ ๊ทธ ๊ฐ์ฅ์๋ฆฌ๋ง ๋ฃจ๋ง ๋๋น๋ก ์ฐพ์ ๊ทธ ๋ฐฉํฅ์ผ๋ก ์๋๋ค.
|
| 7 |
+
// ์
๋ ฅ post(sRGB) โ ์ถ๋ ฅ color(์ด ์์ ์ ๋ค ์ด ์ฅ๋ฉด ์ ์ด๋ฏธ์ง๋ฅผ ๋น๋ ค ์ด๋ค) โ ๋ ๋ ๊ทธ๋ํ๊ฐ ๋ค์ post ๋ก ๋ณต์ฌ.
|
| 8 |
+
layout(local_size_x = 8, local_size_y = 8) in;
|
| 9 |
+
#include "bindless.glsl"
|
| 10 |
+
layout(set = 0, binding = 4) uniform image2D gImgF4[];
|
| 11 |
+
#define uIn gImgF4[kImgPost]
|
| 12 |
+
#define uOut gImgF4[kImgTarget]
|
| 13 |
+
|
| 14 |
+
const float EDGE_MIN = 1.0 / 24.0; // ์ด๋์ด ๊ณณ์์ ์ด๋ณด๋ค ์์ ๋๋น๋ ๋ฌด์
|
| 15 |
+
const float EDGE_REL = 1.0 / 8.0; // ์ฃผ๋ณ ์ต๋ ๋ฃจ๋ง ๋๋น ์๋ ์๊ณ๊ฐ
|
| 16 |
+
const float SUBPIX = 0.75; // ํฝ์
์ดํ ๊ณ๋จ ์ํ ์ ๋
|
| 17 |
+
const int STEPS = 8;
|
| 18 |
+
|
| 19 |
+
ivec2 gSize;
|
| 20 |
+
vec3 at(ivec2 p) { return imageLoad(uIn, clamp(p, ivec2(0), gSize - 1)).rgb; }
|
| 21 |
+
// ์์ ํ ์ํ (storage image ๋ผ ์ง์ ์๋๋ค)
|
| 22 |
+
vec3 bilerp(vec2 p) {
|
| 23 |
+
p -= 0.5;
|
| 24 |
+
ivec2 i = ivec2(floor(p));
|
| 25 |
+
vec2 f = p - vec2(i);
|
| 26 |
+
return mix(mix(at(i), at(i + ivec2(1, 0)), f.x), mix(at(i + ivec2(0, 1)), at(i + ivec2(1, 1)), f.x), f.y);
|
| 27 |
+
}
|
| 28 |
+
float luma(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }
|
| 29 |
+
|
| 30 |
+
void main() {
|
| 31 |
+
gSize = imageSize(uIn);
|
| 32 |
+
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
|
| 33 |
+
if (p.x >= gSize.x || p.y >= gSize.y) return;
|
| 34 |
+
vec4 src = imageLoad(uIn, p);
|
| 35 |
+
float lM = luma(src.rgb);
|
| 36 |
+
float lN = luma(at(p + ivec2(0, -1))), lS = luma(at(p + ivec2(0, 1)));
|
| 37 |
+
float lW = luma(at(p + ivec2(-1, 0))), lE = luma(at(p + ivec2(1, 0)));
|
| 38 |
+
float lo = min(lM, min(min(lN, lS), min(lW, lE))), hi = max(lM, max(max(lN, lS), max(lW, lE)));
|
| 39 |
+
float range = hi - lo;
|
| 40 |
+
if (range < max(EDGE_MIN, hi * EDGE_REL)) { imageStore(uOut, p, src); return; }
|
| 41 |
+
|
| 42 |
+
float lNW = luma(at(p + ivec2(-1, -1))), lNE = luma(at(p + ivec2(1, -1)));
|
| 43 |
+
float lSW = luma(at(p + ivec2(-1, 1))), lSE = luma(at(p + ivec2(1, 1)));
|
| 44 |
+
// ํฝ์
์ดํ ํผํฉ๋
|
| 45 |
+
float avg = (2.0 * (lN + lS + lW + lE) + lNW + lNE + lSW + lSE) / 12.0;
|
| 46 |
+
float sub = clamp(abs(avg - lM) / range, 0.0, 1.0);
|
| 47 |
+
sub = smoothstep(0.0, 1.0, sub);
|
| 48 |
+
sub = sub * sub * SUBPIX;
|
| 49 |
+
// ๊ฐ์ฅ์๋ฆฌ ๋ฐฉํฅ: ๊ฐ๋ก(์์๋๋ก ๋ฐ๋) ๋๋ ์ธ๋ก
|
| 50 |
+
float eH = abs(lNW + lNE - 2.0 * lN) + 2.0 * abs(lW + lE - 2.0 * lM) + abs(lSW + lSE - 2.0 * lS);
|
| 51 |
+
float eV = abs(lNW + lSW - 2.0 * lW) + 2.0 * abs(lN + lS - 2.0 * lM) + abs(lNE + lSE - 2.0 * lE);
|
| 52 |
+
bool horz = eH >= eV;
|
| 53 |
+
float l1 = horz ? lN : lW, l2 = horz ? lS : lE;
|
| 54 |
+
float g1 = abs(l1 - lM), g2 = abs(l2 - lM);
|
| 55 |
+
bool steep1 = g1 >= g2;
|
| 56 |
+
float grad = 0.25 * max(g1, g2);
|
| 57 |
+
vec2 stepN = horz ? vec2(0.0, steep1 ? -1.0 : 1.0) : vec2(steep1 ? -1.0 : 1.0, 0.0);
|
| 58 |
+
float lEdge = 0.5 * (lM + (steep1 ? l1 : l2));
|
| 59 |
+
vec2 c = vec2(p) + 0.5 + 0.5 * stepN;
|
| 60 |
+
vec2 along = horz ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
| 61 |
+
// ๊ฐ์ฅ์๋ฆฌ๋ฅผ ๋ฐ๋ผ ์์ชฝ ๋ ์ฐพ๊ธฐ
|
| 62 |
+
vec2 a = c - along, b = c + along;
|
| 63 |
+
float da = luma(bilerp(a)) - lEdge, db = luma(bilerp(b)) - lEdge;
|
| 64 |
+
bool doneA = abs(da) >= grad, doneB = abs(db) >= grad;
|
| 65 |
+
for (int k = 0; k < STEPS && !(doneA && doneB); ++k) {
|
| 66 |
+
float s = k < 3 ? 1.0 : 2.0;
|
| 67 |
+
if (!doneA) { a -= along * s; da = luma(bilerp(a)) - lEdge; doneA = abs(da) >= grad; }
|
| 68 |
+
if (!doneB) { b += along * s; db = luma(bilerp(b)) - lEdge; doneB = abs(db) >= grad; }
|
| 69 |
+
}
|
| 70 |
+
vec2 here = vec2(p) + 0.5;
|
| 71 |
+
float distA = horz ? here.x - a.x : here.y - a.y, distB = horz ? b.x - here.x : b.y - here.y;
|
| 72 |
+
bool nearA = distA < distB;
|
| 73 |
+
float dist = min(distA, distB), len = distA + distB;
|
| 74 |
+
bool smaller = lM - lEdge < 0.0;
|
| 75 |
+
bool ok = ((nearA ? da : db) < 0.0) != smaller;
|
| 76 |
+
float off = ok ? 0.5 - dist / len : 0.0;
|
| 77 |
+
off = max(off, sub);
|
| 78 |
+
vec3 col = bilerp(here + stepN * off);
|
| 79 |
+
imageStore(uOut, p, vec4(col, src.a));
|
| 80 |
+
}
|
src/vkgs/shaders/gs_common.glsl
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 3DGS ์
ฐ์ด๋ ๊ณตํต ์ ์. ์์๊ณผ ์์๋ gsplat 1.4.0 (ํ์ต๊ธฐ)๊ณผ ๋๊ฐ์ด ๋ง์ถ๋ค โ
|
| 2 |
+
// ๋ค๋ฅด๋ฉด ํ์ต๊ธฐ ๋ ๋์ Vulkan ๋ ๋๊ฐ ํฝ์
๋จ์๋ก ์ด๊ธ๋๋ค (roadmap 2๋จ๊ณ ํฉ๊ฒฉ ๊ธฐ์ค < 2%).
|
| 3 |
+
|
| 4 |
+
// ๊ฐ์ฐ์์ ํ๋ = vec4 15๊ฐ (240 B). 3D ๊ณต๋ถ์ฐ์ ๋ก๋ ์ CPU ์์ ๋ฏธ๋ฆฌ ๊ณ์ฐํ๋ค.
|
| 5 |
+
struct Gaussian {
|
| 6 |
+
vec4 pos_opacity; // xyz = ์ฌ ์์ ๊ธฐ์ค ์์น, w = sigmoid ์ ์ฉ๋ ๋ถํฌ๋ช
๋
|
| 7 |
+
vec4 cov_a; // ฮฃ xx, xy, xz, yy
|
| 8 |
+
vec4 cov_b; // ฮฃ yz, zz, -, -
|
| 9 |
+
vec4 sh[12]; // SH ๊ณ์ 16๊ฐ ร RGB = 48 float. sh ๊ณ์ k ์ ์ฑ๋ c ๋ [k*3+c]
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
// ํ๋ฉด๊ณต๊ฐ ์คํ๋ซ = vec4 3๊ฐ
|
| 13 |
+
// [0] = (u, v, radius, opacity) u,v ๋ ํฝ์
์ขํ (gsplat ๊ณผ ๊ฐ์ด ํฝ์
์ค์ฌ์ด +0.5)
|
| 14 |
+
// [1] = (conic.xyz, -) 2D ๊ณต๋ถ์ฐ์ ์ญํ๋ ฌ (a, b, c)
|
| 15 |
+
// [2] = (rgb, -)
|
| 16 |
+
|
| 17 |
+
#include "bindless.glsl"
|
| 18 |
+
|
| 19 |
+
layout(std140, set = 0, binding = 1) uniform Camera {
|
| 20 |
+
mat4 w2c; // ์ฌ ์์ ๊ธฐ์ค world โ camera (OpenCV: x ์ฐ, y ํ, z ์ ๋ฐฉ)
|
| 21 |
+
vec4 intr; // fx, fy, cx, cy (ํฝ์
)
|
| 22 |
+
vec4 size; // width, height, near, far
|
| 23 |
+
vec4 campos; // xyz = ์นด๋ฉ๋ผ ์์น (์ฌ ์์ ๊ธฐ์ค), w = ๋์ด ํด๋ฆฝ (์ด๋ณด๋ค ๋์ ๊ฐ์ฐ์์์ ์ ๊ทธ๋ฆผ)
|
| 24 |
+
uvec4 counts; // N, N_pow2, sh_degree, -
|
| 25 |
+
mat4 obj_xform; // ๊ฐ์ฒด ์์
(์์ฐจ ๋ฑ) ๋ก์ปฌ โ ์ฌ ์ขํ. obj.x ๋ฒ์งธ ๊ฐ์ฐ์์๋ถํฐ ์ ์ฉ
|
| 26 |
+
uvec4 obj; // x = ๊ฐ์ฒด ์์ ์ธ๋ฑ์ค (์์ผ๋ฉด N), y = ๋ณด์ด๊ธฐ(1)/์จ๊ธฐ๊ธฐ(0)
|
| 27 |
+
uvec4 objs; // ์ฅ๋ฉด ๊ฐ์ฒด(์ฐจ๋ ์์
๋ฑ, ๋ณ๋ ๋ฒํผ): x = ๊ฐ์ฐ์์ SSBO ์ฌ๋กฏ, y = ๊ฐ์ฒด ๋ฒํธ SSBO ์ฌ๋กฏ,
|
| 28 |
+
// z = ๊ฐ์ฒด ํ SSBO ์ฌ๋กฏ, w = ๊ฐ์ฒด ๊ฐ์ฐ์์ ์. ์ ์ญ ๋ฒํธ counts.x + j ๊ฐ ๊ฐ์ฒด ๊ฐ์ฐ์์ j
|
| 29 |
+
uvec4 mode; // ์นด๋ฉ๋ผ ๋ชจ๋ (camera_mode.comp): x = 0 RGB ยท 1 ๊น์ด ยท 2 ์๋งจํฑ ยท 3 ํ๋ฐฑ,
|
| 30 |
+
// y = ์๋งจํฑ ์ฐจ๋ ์ (RGBA8, ๊ฐ์ฒด ์์
), z = ์๋งจํฑ ํ๋ ์ (RGBA8), w = ๊น์ด ์ต๋ ๊ฑฐ๋ฆฌ m (float ๋นํธ)
|
| 31 |
+
} gCamera[];
|
| 32 |
+
#define cam gCamera[kUboCamera]
|
src/vkgs/shaders/gs_preprocess.comp
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// ๊ฐ์ฐ์์๋ง๋ค: ํฌ์ โ 2D ๊ณต๋ถ์ฐ โ conic/๋ฐ์ง๋ฆ โ SH ์ โ ์ ๋ ฌ ํค.
|
| 4 |
+
// gsplat fully_fused_projection_fwd.cu / spherical_harmonics.cuh ๋ฅผ ๊ทธ๋๋ก ์ฎ๊ฒผ๋ค.
|
| 5 |
+
|
| 6 |
+
layout(local_size_x = 256) in;
|
| 7 |
+
|
| 8 |
+
#include "gs_common.glsl"
|
| 9 |
+
|
| 10 |
+
layout(std430, set = 0, binding = 0) readonly buffer Gaussians { Gaussian g[]; } bGauss[];
|
| 11 |
+
layout(std430, set = 0, binding = 0) writeonly buffer Splats { vec4 splat[]; } bSplats[];
|
| 12 |
+
layout(std430, set = 0, binding = 0) writeonly buffer Keys { uvec2 keys[]; } bKeys[];
|
| 13 |
+
layout(std430, set = 0, binding = 0) buffer DrawArgs {
|
| 14 |
+
uint vertexCount; uint instanceCount; uint firstVertex; uint firstInstance;
|
| 15 |
+
} bDraw[];
|
| 16 |
+
#define g bGauss[kSsboGauss].g
|
| 17 |
+
// ์ฅ๋ฉด ๊ฐ์ฒด (์ฐจ๋ ์์
): ๊ฐ์ฒด ๊ฐ์ฐ์์ j ์ ๊ฐ์ฒด ๋ฒํธ, ๊ฐ์ฒด ํ (๋ฐ์ค ๋ก์ปฌ โ ์ฌ, ๋ณด์ด๊ธฐ)
|
| 18 |
+
layout(std430, set = 0, binding = 0) readonly buffer ObjGaussians { Gaussian og[]; } bObjGauss[];
|
| 19 |
+
layout(std430, set = 0, binding = 0) readonly buffer ObjIds { uint id[]; } bObjIds[];
|
| 20 |
+
struct ObjInstance { mat4 xform; vec4 flags; }; // flags.x = ๋ณด์ด๊ธฐ
|
| 21 |
+
layout(std430, set = 0, binding = 0) readonly buffer ObjTable { ObjInstance inst[]; } bObjTable[];
|
| 22 |
+
#define splat bSplats[kSsboSplats].splat
|
| 23 |
+
#define keys bKeys[kSsboKeys].keys
|
| 24 |
+
#define draw bDraw[kSsboDraw]
|
| 25 |
+
|
| 26 |
+
const float EPS2D = 0.3; // gsplat rasterization(eps2d=0.3) โ ์ ์ญ ํต๊ณผ ๋ธ๋ฌ
|
| 27 |
+
|
| 28 |
+
vec3 sh_color(in Gaussian G, vec3 dir, uint degree) {
|
| 29 |
+
// sh_coeffs_to_color_fast ์ ๊ฐ์ ์์/์์
|
| 30 |
+
vec3 c[16];
|
| 31 |
+
for (int k = 0; k < 16; ++k) {
|
| 32 |
+
int f = k * 3;
|
| 33 |
+
c[k] = vec3(G.sh[f / 4][f % 4], G.sh[(f + 1) / 4][(f + 1) % 4], G.sh[(f + 2) / 4][(f + 2) % 4]);
|
| 34 |
+
}
|
| 35 |
+
vec3 r = 0.2820947917738781 * c[0];
|
| 36 |
+
if (degree < 1) return r;
|
| 37 |
+
vec3 d = normalize(dir);
|
| 38 |
+
float x = d.x, y = d.y, z = d.z;
|
| 39 |
+
r += 0.48860251190292 * (-y * c[1] + z * c[2] - x * c[3]);
|
| 40 |
+
if (degree < 2) return r;
|
| 41 |
+
float z2 = z * z;
|
| 42 |
+
float fTmp0B = -1.092548430592079 * z;
|
| 43 |
+
float fC1 = x * x - y * y;
|
| 44 |
+
float fS1 = 2.0 * x * y;
|
| 45 |
+
r += (0.5462742152960395 * fS1) * c[4] + (fTmp0B * y) * c[5]
|
| 46 |
+
+ (0.9461746957575601 * z2 - 0.3153915652525201) * c[6]
|
| 47 |
+
+ (fTmp0B * x) * c[7] + (0.5462742152960395 * fC1) * c[8];
|
| 48 |
+
if (degree < 3) return r;
|
| 49 |
+
float fTmp0C = -2.285228997322329 * z2 + 0.4570457994644658;
|
| 50 |
+
float fTmp1B = 1.445305721320277 * z;
|
| 51 |
+
float fC2 = x * fC1 - y * fS1;
|
| 52 |
+
float fS2 = x * fS1 + y * fC1;
|
| 53 |
+
r += (-0.5900435899266435 * fS2) * c[9] + (fTmp1B * fS1) * c[10]
|
| 54 |
+
+ (fTmp0C * y) * c[11] + (z * (1.865881662950577 * z2 - 1.119528997770346)) * c[12]
|
| 55 |
+
+ (fTmp0C * x) * c[13] + (fTmp1B * fC1) * c[14] + (-0.5900435899266435 * fC2) * c[15];
|
| 56 |
+
return r;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
void main() {
|
| 60 |
+
uint i = gl_GlobalInvocationID.x;
|
| 61 |
+
uint n = cam.counts.x;
|
| 62 |
+
if (i >= n + cam.objs.w) return;
|
| 63 |
+
// ์ ์ญ ๋ฒํธ i: [0, n) ๋ฐฐ๊ฒฝ (+ ์์ฐจ ์์
), [n, n + objs.w) ์ฅ๋ฉด ๊ฐ์ฒด (๋ณ๋ ๋ฒํผ, ๊ฐ์ฒด๋ง๋ค ์์ธ)
|
| 64 |
+
Gaussian G;
|
| 65 |
+
mat4 X = cam.obj_xform;
|
| 66 |
+
bool is_obj = false;
|
| 67 |
+
if (i >= n) {
|
| 68 |
+
uint j = i - n;
|
| 69 |
+
G = bObjGauss[cam.objs.x].og[j];
|
| 70 |
+
ObjInstance oi = bObjTable[cam.objs.z].inst[bObjIds[cam.objs.y].id[j]];
|
| 71 |
+
if (oi.flags.x < 0.5) return; // ์ง์ ๊ฑฐ๋ ์ด ์๊ฐ์ ์๋ ๊ฐ์ฒด
|
| 72 |
+
X = oi.xform;
|
| 73 |
+
is_obj = true;
|
| 74 |
+
} else {
|
| 75 |
+
G = g[i];
|
| 76 |
+
// ๊ฐ์ฒด ์์
(์์ฐจ): ๋ฐ์ค ๋ก์ปฌ ์ขํ โ ์ด๋ฒ ํ๋ ์์ ์์ฐจ ํฌ์ฆ
|
| 77 |
+
is_obj = i >= cam.obj.x;
|
| 78 |
+
if (is_obj && cam.obj.y == 0u) return;
|
| 79 |
+
}
|
| 80 |
+
vec3 p = G.pos_opacity.xyz;
|
| 81 |
+
if (is_obj) p = (X * vec4(p, 1.0)).xyz;
|
| 82 |
+
// BEV ์์ ๋๋ฌด ์บ๋
ธํผ๊ฐ ์ง๋ฉด์ ๋ฎ์ง ์๋๋ก ์ผ์ ๋์ด ์๋ ๋บ๋ค
|
| 83 |
+
if (p.z > cam.campos.w) return;
|
| 84 |
+
vec3 t = (cam.w2c * vec4(p, 1.0)).xyz;
|
| 85 |
+
float near = cam.size.z, far = cam.size.w;
|
| 86 |
+
if (t.z < near || t.z > far) return;
|
| 87 |
+
|
| 88 |
+
// world ๊ณต๋ถ์ฐ โ camera ๊ณต๋ถ์ฐ
|
| 89 |
+
vec4 a = G.cov_a, b = G.cov_b;
|
| 90 |
+
mat3 S = mat3(a.x, a.y, a.z,
|
| 91 |
+
a.y, a.w, b.x,
|
| 92 |
+
a.z, b.x, b.y);
|
| 93 |
+
if (is_obj) {
|
| 94 |
+
mat3 Ro = mat3(X);
|
| 95 |
+
S = Ro * S * transpose(Ro);
|
| 96 |
+
}
|
| 97 |
+
mat3 W = mat3(cam.w2c);
|
| 98 |
+
mat3 Sc = W * S * transpose(W);
|
| 99 |
+
|
| 100 |
+
// persp_proj: ํ๋ฉด ๋ฐ ๋ฉ๋ฆฌ ์๋ ๊ฐ์ฐ์์์ ์ผ์ฝ๋น์์ด ํญ์ฃผํ์ง ์๊ฒ x/z ๋ฅผ ์๋ฅธ๋ค
|
| 101 |
+
float fx = cam.intr.x, fy = cam.intr.y, cx = cam.intr.z, cy = cam.intr.w;
|
| 102 |
+
float w = cam.size.x, h = cam.size.y;
|
| 103 |
+
float tan_fovx = 0.5 * w / fx, tan_fovy = 0.5 * h / fy;
|
| 104 |
+
float lim_x_pos = (w - cx) / fx + 0.3 * tan_fovx;
|
| 105 |
+
float lim_x_neg = cx / fx + 0.3 * tan_fovx;
|
| 106 |
+
float lim_y_pos = (h - cy) / fy + 0.3 * tan_fovy;
|
| 107 |
+
float lim_y_neg = cy / fy + 0.3 * tan_fovy;
|
| 108 |
+
float rz = 1.0 / t.z, rz2 = rz * rz;
|
| 109 |
+
float tx = t.z * min(lim_x_pos, max(-lim_x_neg, t.x * rz));
|
| 110 |
+
float ty = t.z * min(lim_y_pos, max(-lim_y_neg, t.y * rz));
|
| 111 |
+
vec3 j0 = vec3(fx * rz, 0.0, -fx * tx * rz2);
|
| 112 |
+
vec3 j1 = vec3(0.0, fy * rz, -fy * ty * rz2);
|
| 113 |
+
float c00 = dot(j0, Sc * j0) + EPS2D;
|
| 114 |
+
float c01 = dot(j0, Sc * j1);
|
| 115 |
+
float c11 = dot(j1, Sc * j1) + EPS2D;
|
| 116 |
+
|
| 117 |
+
float det = c00 * c11 - c01 * c01;
|
| 118 |
+
if (det <= 0.0) return;
|
| 119 |
+
vec3 conic = vec3(c11, -c01, c00) / det;
|
| 120 |
+
|
| 121 |
+
float bb = 0.5 * (c00 + c11);
|
| 122 |
+
float v1 = bb + sqrt(max(0.01, bb * bb - det));
|
| 123 |
+
float radius = ceil(3.0 * sqrt(v1));
|
| 124 |
+
vec2 mean2d = vec2(fx * t.x * rz + cx, fy * t.y * rz + cy);
|
| 125 |
+
if (mean2d.x + radius <= 0.0 || mean2d.x - radius >= w ||
|
| 126 |
+
mean2d.y + radius <= 0.0 || mean2d.y - radius >= h) return;
|
| 127 |
+
|
| 128 |
+
vec3 rgb;
|
| 129 |
+
if (cam.mode.x == 2u) { // ์๋งจํฑ ์นด๋ฉ๋ผ: ๋ผ๋ฒจ ์ (ply.cpp ๊ฐ semantic.rgba ๋ฅผ cov_b.w ์ ๋ฃ๋๋ค). ๊ฐ์ฒด ์์
= ์ฐจ๋ ์
|
| 130 |
+
rgb = is_obj ? unpackUnorm4x8(cam.mode.y).rgb : unpackUnorm4x8(floatBitsToUint(G.cov_b.w)).rgb;
|
| 131 |
+
} else {
|
| 132 |
+
rgb = sh_color(G, p - cam.campos.xyz, cam.counts.z);
|
| 133 |
+
rgb = max(rgb + 0.5, vec3(0.0)); // gsplat: clamp_min(colors + 0.5, 0)
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
splat[3 * i + 0] = vec4(mean2d, radius, G.pos_opacity.w);
|
| 137 |
+
splat[3 * i + 1] = vec4(conic, 0.0);
|
| 138 |
+
splat[3 * i + 2] = vec4(rgb, t.z); // w = ์นด๋ฉ๋ผ ๊น์ด โ ๋ฉ์(PBR)์ ๊น์ด ํฉ์ฑ์ฉ
|
| 139 |
+
|
| 140 |
+
// ๋ค์์ ์์ผ๋ก ๊ทธ๋ฆฌ๋ฏ๋ก ๋จผ ๊ฒ์ด ๋จผ์ ์ค๋๋ก ๊น์ด๋ฅผ ๋ค์ง๋๋ค.
|
| 141 |
+
// ์์ float ์ ๋นํธ ํจํด์ ๊ฐ๊ณผ ๋จ์กฐ ๊ด๊ณ๋ผ uint ๋น๊ต๋ก ์ ๋ ฌ๋๋ค.
|
| 142 |
+
// ๋ณด์ด๋ ๊ฒ๋ง ์์ชฝ์ ๋ชจ์๋ค (์ ๋ ฌ์ [0, ๋ณด์ด๋ ์) ๋ง ํ๋ค โ gs_radix_*.comp, bitonic ์ ๋ค๋ฅผ ์ฑ์์)
|
| 143 |
+
uint slot = atomicAdd(draw.instanceCount, 1u);
|
| 144 |
+
keys[slot] = uvec2(0xFFFFFFFEu - floatBitsToUint(t.z), i);
|
| 145 |
+
}
|
src/vkgs/shaders/gs_radix_common.glsl
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// GPU radix ์ ๋ ฌ (LSD, 8๋นํธ ์๋ฆฟ์ ร 4ํจ์ค) ๊ณตํต โ gs_radix_hist / scan / scatter.comp.
|
| 2 |
+
// ์ ๋ ฌ ๋์: keys[0, ๋ณด์ด๋ ์) ์ (key, index). ๋ณด์ด๋ ์๋ GPU ์ ์ธ๋ค์ด๋ ํธ ์ธ์์์ ์ฝ๋๋ค (CPU ์๋ณต ์์).
|
| 3 |
+
// ์ํฌ๊ทธ๋ฃน ์ G ๋ ๊ณ ์ ์ด๊ณ , ์ํฌ๊ทธ๋ฃน g ๋ ์ฐ์ ๋ธ๋ก [gยทB, (g+1)ยทB) ๋ฅผ ๋งก๋๋ค (B = ๋ณด์ด๋ ์ / G ๋ฅผ 256 ๋ฐฐ์๋ก ์ฌ๋ฆผ).
|
| 4 |
+
// ํจ์ค๋ง๋ค ์๋ณธ โ ์์ ๋ฒํผ๋ฅผ ์ค๊ฐ๋ฏ๋ก 4ํจ์ค ๋ค ๊ฒฐ๊ณผ๋ ๋ค์ keys ์ ์๋ค. ์์ ์ ๋ ฌ์ด๋ค.
|
| 5 |
+
#include "bindless.glsl"
|
| 6 |
+
layout(std430, set = 0, binding = 0) buffer Keys { uvec2 keys[]; } bKeys[];
|
| 7 |
+
layout(std430, set = 0, binding = 0) buffer Hist { uint h[]; } bHist[];
|
| 8 |
+
layout(std430, set = 0, binding = 0) readonly buffer DrawArgs {
|
| 9 |
+
uint vertexCount; uint instanceCount; uint firstVertex; uint firstInstance;
|
| 10 |
+
} bDraw[];
|
| 11 |
+
// shift = ์ด๋ฒ ์๋ฆฟ์ ์์น, flip = 0 ์ด๋ฉด keys โ tmp, 1 ์ด๋ฉด tmp โ keys, groups = G, slots = (keys, tmp, hist)
|
| 12 |
+
layout(push_constant) uniform Radix { uint shift; uint flip; uint groups; uint pad; uvec4 slots; } pc;
|
| 13 |
+
#define SRC bKeys[pc.flip == 0u ? pc.slots.x : pc.slots.y].keys
|
| 14 |
+
#define DST bKeys[pc.flip == 0u ? pc.slots.y : pc.slots.x].keys
|
| 15 |
+
#define HIST bHist[pc.slots.z].h
|
| 16 |
+
const uint RADIX = 256u;
|
| 17 |
+
|
| 18 |
+
uint radix_count() { return bDraw[kSsboDraw].instanceCount; }
|
| 19 |
+
uint radix_block(uint count) { return ((count + pc.groups - 1u) / pc.groups + 255u) / 256u * 256u; }
|
src/vkgs/shaders/gs_radix_hist.comp
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#extension GL_EXT_nonuniform_qualifier : require
|
| 4 |
+
// radix 1/3: ์ํฌ๊ทธ๋ฃน๋ง๋ค ์๊ธฐ ๋ธ๋ก์ ์๋ฆฟ์ ํ์คํ ๊ทธ๋จ โ HIST[gยท256 + d]
|
| 5 |
+
layout(local_size_x = 256) in;
|
| 6 |
+
#include "gs_radix_common.glsl"
|
| 7 |
+
|
| 8 |
+
shared uint s_hist[RADIX];
|
| 9 |
+
|
| 10 |
+
void main() {
|
| 11 |
+
uint t = gl_LocalInvocationID.x, g = gl_WorkGroupID.x;
|
| 12 |
+
s_hist[t] = 0u;
|
| 13 |
+
barrier();
|
| 14 |
+
uint count = radix_count(), block = radix_block(count);
|
| 15 |
+
uint b0 = min(g * block, count), b1 = min(b0 + block, count);
|
| 16 |
+
for (uint i = b0 + t; i < b1; i += 256u) atomicAdd(s_hist[(SRC[i].x >> pc.shift) & 255u], 1u);
|
| 17 |
+
barrier();
|
| 18 |
+
HIST[g * RADIX + t] = s_hist[t];
|
| 19 |
+
}
|
src/vkgs/shaders/gs_radix_scan.comp
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#extension GL_EXT_nonuniform_qualifier : require
|
| 4 |
+
// radix 2/3 (์ํฌ๊ทธ๋ฃน ํ๋): HIST[gยท256 + d] โ ๊ทธ (์๋ฆฟ์, ์ํฌ๊ทธ๋ฃน) ์ ์ถ๋ ฅ ์์ ์์น.
|
| 5 |
+
// ์์๋ ์๋ฆฟ์ ๋จผ์ , ๊ฐ์ ์๋ฆฟ์ ์์์ ์ํฌ๊ทธ๋ฃน ์ โ ๊ทธ๋์ ์์ ์ ๋ ฌ์ด ๋๋ค.
|
| 6 |
+
layout(local_size_x = 256) in;
|
| 7 |
+
#include "gs_radix_common.glsl"
|
| 8 |
+
|
| 9 |
+
shared uint s_sum[RADIX];
|
| 10 |
+
|
| 11 |
+
void main() {
|
| 12 |
+
uint d = gl_LocalInvocationID.x;
|
| 13 |
+
uint total = 0u;
|
| 14 |
+
for (uint g = 0u; g < pc.groups; ++g) total += HIST[g * RADIX + d];
|
| 15 |
+
s_sum[d] = total;
|
| 16 |
+
barrier();
|
| 17 |
+
// ์๋ฆฟ์ ํฉ์ ๋ฐฐํ์ ๋์ (HillisโSteele)
|
| 18 |
+
for (uint off = 1u; off < RADIX; off <<= 1u) {
|
| 19 |
+
uint v = d >= off ? s_sum[d - off] : 0u;
|
| 20 |
+
barrier();
|
| 21 |
+
s_sum[d] += v;
|
| 22 |
+
barrier();
|
| 23 |
+
}
|
| 24 |
+
uint run = s_sum[d] - total;
|
| 25 |
+
for (uint g = 0u; g < pc.groups; ++g) {
|
| 26 |
+
uint c = HIST[g * RADIX + d];
|
| 27 |
+
HIST[g * RADIX + d] = run;
|
| 28 |
+
run += c;
|
| 29 |
+
}
|
| 30 |
+
}
|
src/vkgs/shaders/gs_radix_scatter.comp
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#extension GL_EXT_nonuniform_qualifier : require
|
| 4 |
+
#extension GL_KHR_shader_subgroup_basic : require
|
| 5 |
+
#extension GL_KHR_shader_subgroup_ballot : require
|
| 6 |
+
// radix 3/3: ๋ธ๋ก์ 256๊ฐ์ฉ ์ฐจ๋ก๋ก, ๊ฐ์ ์๋ฆฟ์๋ผ๋ฆฌ์ ์์(์์ )๋ฅผ ๋งค๊ฒจ ์ ์๋ฆฌ๋ก ๋ณด๋ธ๋ค.
|
| 7 |
+
// ์์: subgroup ballot 8๋ฒ์ผ๋ก '๊ฐ์ ์๋ฆฟ์์ธ ๋ ์ธ' ๋ง์คํฌ๋ฅผ ๋ง๋ค๊ณ (WLMS), ๊ทธ ์์์ ์์ ๋ ์ธ ์.
|
| 8 |
+
// subgroup ์ฌ์ด๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ (subgroup, ์๋ฆฟ์) ๊ฐ์ ๋์ ์ผ๋ก ์๋๋ค. subgroup ํฌ๊ธฐ 16 ์ด์์ด ํ์ํ๋ค (๋ทฐ์ด๊ฐ ๊ฒ์ฌ).
|
| 9 |
+
layout(local_size_x = 256) in;
|
| 10 |
+
#include "gs_radix_common.glsl"
|
| 11 |
+
|
| 12 |
+
const uint MAX_SG = 16u; // 256 / 16
|
| 13 |
+
shared uint s_off[RADIX]; // ์ด ์ํฌ๊ทธ๋ฃน์ ์๋ฆฟ์๋ณ ๋ค์ ์ถ๋ ฅ ์์น
|
| 14 |
+
shared uint s_sg[MAX_SG][RADIX]; // (subgroup, ์๋ฆฟ์) ๊ฐ์ โ ๋ฐฐํ์ ๋์
|
| 15 |
+
|
| 16 |
+
void main() {
|
| 17 |
+
uint t = gl_LocalInvocationID.x, g = gl_WorkGroupID.x;
|
| 18 |
+
uint nsg = gl_NumSubgroups, sg = gl_SubgroupID;
|
| 19 |
+
s_off[t] = HIST[g * RADIX + t];
|
| 20 |
+
uint count = radix_count(), block = radix_block(count);
|
| 21 |
+
uint b0 = min(g * block, count), b1 = min(b0 + block, count);
|
| 22 |
+
// ์์ ์์ = (subgroup, ๋ ์ธ) ์์ โ ์์ ์ฑ์ ์ํด ๋ก๋ ์์น๋ฅผ ๊ทธ ์์๋ก ์ ํ๋ค
|
| 23 |
+
uint lane_index = sg * gl_SubgroupSize + gl_SubgroupInvocationID;
|
| 24 |
+
for (uint c = b0; c < b1; c += 256u) {
|
| 25 |
+
for (uint s = 0u; s < nsg; ++s) s_sg[s][t] = 0u;
|
| 26 |
+
barrier();
|
| 27 |
+
uint i = c + lane_index;
|
| 28 |
+
bool valid = i < b1;
|
| 29 |
+
uvec2 kv = valid ? SRC[i] : uvec2(0u);
|
| 30 |
+
uint d = (kv.x >> pc.shift) & 255u;
|
| 31 |
+
uvec4 peers = subgroupBallot(valid);
|
| 32 |
+
for (uint b = 0u; b < 8u; ++b) {
|
| 33 |
+
bool bit = ((d >> b) & 1u) != 0u;
|
| 34 |
+
uvec4 bal = subgroupBallot(bit);
|
| 35 |
+
peers &= bit ? bal : ~bal;
|
| 36 |
+
}
|
| 37 |
+
uint rank = subgroupBallotExclusiveBitCount(peers);
|
| 38 |
+
if (valid && subgroupBallotFindLSB(peers) == gl_SubgroupInvocationID)
|
| 39 |
+
s_sg[sg][d] = subgroupBallotBitCount(peers);
|
| 40 |
+
barrier();
|
| 41 |
+
uint run = 0u; // ์ค๋ ๋ t = ์๋ฆฟ์ t
|
| 42 |
+
for (uint s = 0u; s < nsg; ++s) {
|
| 43 |
+
uint v = s_sg[s][t];
|
| 44 |
+
s_sg[s][t] = run;
|
| 45 |
+
run += v;
|
| 46 |
+
}
|
| 47 |
+
barrier();
|
| 48 |
+
if (valid) DST[s_off[d] + s_sg[sg][d] + rank] = kv;
|
| 49 |
+
barrier();
|
| 50 |
+
s_off[t] += run;
|
| 51 |
+
barrier();
|
| 52 |
+
}
|
| 53 |
+
}
|
src/vkgs/shaders/gs_sort.comp
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// bitonic ์ ๋ ฌ ํ ๋จ๊ณ (k, j). (key, index) ์์ key ์ค๋ฆ์ฐจ์์ผ๋ก โ ๊ธฐ์ค ๊ตฌํ (๊ธฐ๋ณธ์ radix, gs_radix_*.comp).
|
| 4 |
+
// ์ ์ฒ๋ฆฌ๊ฐ ๋ณด์ด๋ ๊ฒ๋ง [0, ๋ณด์ด๋ ์) ์ ๋ชจ์ผ๋ฏ๋ก, k = 0 ์ธ '์ฑ์ฐ๊ธฐ' ๋จ๊ณ๊ฐ ๋จผ์ ๋ค์ชฝ์ ์ ๋ณด์ ํค๋ก ์ฑ์ด๋ค.
|
| 5 |
+
// N=2^21 ์ด๋ฉด 231 ๋ฒ ๋์คํจ์น.
|
| 6 |
+
|
| 7 |
+
layout(local_size_x = 256) in;
|
| 8 |
+
|
| 9 |
+
#include "bindless.glsl"
|
| 10 |
+
layout(std430, set = 0, binding = 0) buffer Keys { uvec2 keys[]; } bKeys[];
|
| 11 |
+
layout(std430, set = 0, binding = 0) readonly buffer DrawArgs {
|
| 12 |
+
uint vertexCount; uint instanceCount; uint firstVertex; uint firstInstance;
|
| 13 |
+
} bDraw[];
|
| 14 |
+
#define keys bKeys[kSsboKeys].keys
|
| 15 |
+
layout(push_constant) uniform Step { uint j; uint k; uint n; } pc;
|
| 16 |
+
|
| 17 |
+
void main() {
|
| 18 |
+
uint i = gl_GlobalInvocationID.x;
|
| 19 |
+
if (i >= pc.n) return;
|
| 20 |
+
if (pc.k == 0u) { // ์ฑ์ฐ๊ธฐ: ๋ณด์ด์ง ์๋ ์๋ฆฌ โ ์ ๋ ฌ ํ ๋งจ ๋ค
|
| 21 |
+
if (i >= bDraw[kSsboDraw].instanceCount) keys[i] = uvec2(0xFFFFFFFFu, 0u);
|
| 22 |
+
return;
|
| 23 |
+
}
|
| 24 |
+
uint l = i ^ pc.j;
|
| 25 |
+
if (l <= i) return;
|
| 26 |
+
uvec2 a = keys[i], b = keys[l];
|
| 27 |
+
bool ascending = (i & pc.k) == 0u;
|
| 28 |
+
if (ascending ? (a.x > b.x) : (a.x < b.x)) {
|
| 29 |
+
keys[i] = b;
|
| 30 |
+
keys[l] = a;
|
| 31 |
+
}
|
| 32 |
+
}
|
src/vkgs/shaders/gs_splat.frag
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
// gsplat rasterize_to_pixels_fwd.cu ์ ํฝ์
๊ธฐ์ฌ ๊ณ์ฐ๊ณผ ๊ฐ๋ค.
|
| 3 |
+
// ๋คโ์ ์์๋ก premultiplied over ๋ธ๋ ๋ฉ (src ONE, dst ONE_MINUS_SRC_ALPHA).
|
| 4 |
+
// ์โ๋ค๋ก ํฉ์น๋ gsplat ๊ณผ ์ํ์ ์ผ๋ก ๊ฐ๊ณ , ๋ค๋ฅธ ์ ์ gsplat ์ ์กฐ๊ธฐ ์ข
๋ฃ(T โค 1e-4) ๋ฟ์ด๋ค.
|
| 5 |
+
|
| 6 |
+
layout(location = 0) flat in vec2 vCenter;
|
| 7 |
+
layout(location = 1) flat in vec3 vConic;
|
| 8 |
+
layout(location = 2) flat in vec4 vColor;
|
| 9 |
+
layout(location = 3) flat in float vZ;
|
| 10 |
+
layout(location = 0) out vec4 outColor;
|
| 11 |
+
layout(location = 1) out vec4 outDepth; // ๊ฑฐ๋ฆฌ ๋์ (depth_common.glsl)
|
| 12 |
+
|
| 13 |
+
void main() {
|
| 14 |
+
vec2 d = vCenter - gl_FragCoord.xy; // gl_FragCoord ๋ ํฝ์
์ค์ฌ(+0.5) โ gsplat ์ px ์ ๊ฐ๋ค
|
| 15 |
+
float sigma = 0.5 * (vConic.x * d.x * d.x + vConic.z * d.y * d.y) + vConic.y * d.x * d.y;
|
| 16 |
+
if (sigma < 0.0) discard;
|
| 17 |
+
float alpha = min(0.999, vColor.a * exp(-sigma));
|
| 18 |
+
if (alpha < 1.0 / 255.0) discard;
|
| 19 |
+
outColor = vec4(vColor.rgb * alpha, alpha);
|
| 20 |
+
outDepth = vec4(vZ * alpha, alpha, 0.0, alpha);
|
| 21 |
+
}
|
src/vkgs/shaders/gs_splat.vert
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#include "bindless.glsl"
|
| 4 |
+
// ์ ๋ ฌ๋ ์์๋๋ก ์คํ๋ซ๋ง๋ค ํ๋ฉด์ ๋ ฌ ์ฟผ๋ ํ๋ (3ฯ ๋ฐ์ง๋ฆ). ์ธ์คํด์ค = ๊ฐ์ฐ์์.
|
| 5 |
+
|
| 6 |
+
layout(std430, set = 0, binding = 0) readonly buffer Splats { vec4 splat[]; } bSplats[];
|
| 7 |
+
layout(std430, set = 0, binding = 0) readonly buffer Keys { uvec2 keys[]; } bKeys[];
|
| 8 |
+
#define splat bSplats[kSsboSplats].splat
|
| 9 |
+
#define keys bKeys[kSsboKeys].keys
|
| 10 |
+
layout(push_constant) uniform Viewport { vec2 size; vec2 near_far; } pc;
|
| 11 |
+
|
| 12 |
+
layout(location = 0) flat out vec2 vCenter;
|
| 13 |
+
layout(location = 1) flat out vec3 vConic;
|
| 14 |
+
layout(location = 2) flat out vec4 vColor; // rgb, opacity
|
| 15 |
+
layout(location = 3) flat out float vZ; // ๊ฐ์ฐ์์ ์ค์ฌ์ ์นด๋ฉ๋ผ z (๊ฑฐ๋ฆฌ ๋์ ๋ฒํผ์ฉ)
|
| 16 |
+
|
| 17 |
+
void main() {
|
| 18 |
+
uint idx = keys[gl_InstanceIndex].y;
|
| 19 |
+
vec4 s0 = splat[3 * idx + 0];
|
| 20 |
+
vCenter = s0.xy;
|
| 21 |
+
vConic = splat[3 * idx + 1].xyz;
|
| 22 |
+
vColor = vec4(splat[3 * idx + 2].rgb, s0.w);
|
| 23 |
+
|
| 24 |
+
vec2 corner = vec2(float(gl_VertexIndex & 1) * 2.0 - 1.0, float(gl_VertexIndex >> 1) * 2.0 - 1.0);
|
| 25 |
+
vec2 pix = s0.xy + corner * s0.z;
|
| 26 |
+
// ํฝ์
โ Vulkan NDC. y ๊ฐ ์๋๋ก + ๋ผ ๋ค์ง์ง ์๋๋ค (nusim.data.rig ์ฐธ๊ณ ).
|
| 27 |
+
// ๊น์ด = ๊ฐ์ฐ์์ ์ค์ฌ ๊น์ด๋ฅผ Vulkan [0,1] ๋ก (nusim.data.rig projection ๊ณผ ๊ฐ์ ์).
|
| 28 |
+
// ๋ฉ์(PBR ๊ตฌ, glTF)๊ฐ ๋จผ์ ๊น์ด๋ฅผ ์ฐ๋ฉด ๊ทธ ๋ค์ ์คํ๋ซ์ด ๊น์ด ํ
์คํธ๋ก ๊ฐ๋ ค์ง๋ค.
|
| 29 |
+
float n = pc.near_far.x, f = pc.near_far.y, z = splat[3 * idx + 2].w;
|
| 30 |
+
vZ = z;
|
| 31 |
+
float zndc = f / (f - n) - f * n / ((f - n) * z);
|
| 32 |
+
gl_Position = vec4(pix / pc.size * 2.0 - 1.0, zndc, 1.0);
|
| 33 |
+
}
|
src/vkgs/shaders/ibl_common.glsl
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// IBL ๊ณตํต โ nusim/render/ibl.py ์ ์ง.
|
| 2 |
+
// ํ๋ธ๋งต์ ์ฌ ์ขํ(z ์) ๋ฐฉํฅ ๋ฒกํฐ๋ก ๋ฐ๋ก ์ํ๋งํ๋ค (๊ตฝ๋ ์ชฝ์ด Vulkan ๋ฉด ํ๋ฅผ ๊ทธ๋๋ก ์ผ๋ค).
|
| 3 |
+
// ์ ๊ณต๊ฐ: ํ๋ธ๋งต์ ์ ํ HDR(๊ทผ์ฌ), 3DGS ๋ ์นด๋ฉ๋ผ sRGB(์ ์ค ์). ํ๋ฉด์ 3DGS ์ชฝ์ ๋ง์ถ๋ค.
|
| 4 |
+
|
| 5 |
+
#include "bindless.glsl"
|
| 6 |
+
#define uSpecular gCube[gGlobals[kSsboGlobals].ibl.x] // GGX ํ๋ฆฌํํฐ, mip = ๊ฑฐ์น ๊ธฐ ร (mips-1)
|
| 7 |
+
#define uIrradiance gCube[gGlobals[kSsboGlobals].ibl.y] // ๋ณต์ฌ์กฐ๋ / ฯ
|
| 8 |
+
#define uBrdfLut gTex2D[gGlobals[kSsboGlobals].ibl.z] // (NdotV, ๊ฑฐ์น ๊ธฐ) โ (scale, bias)
|
| 9 |
+
|
| 10 |
+
vec3 linear_to_srgb(vec3 c) {
|
| 11 |
+
c = max(c, vec3(0.0));
|
| 12 |
+
return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), c));
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// ๊ตฝ๊ธฐ ๋์ ํ์ด๋ผ์ดํธ ํ์ฅ(nusim/render/ibl.py expand_highlights: knee 0.75, gain 6)์ ๋๋๋ ค ์นด๋ฉ๋ผ๊ฐ ์ฐ์์
|
| 16 |
+
// ๋ฐ๊ธฐ๋ก โ ๋ฐฐ๊ฒฝ ํ์์ฉ. ์ ๋ฐฉํฅ L = lยท(1 + 6xยฒ), x = clamp((l โ 0.75)/0.25, 0, 1) ์ l ์ ๋ํด ๋จ์กฐ ์ฆ๊ฐ๋ผ
|
| 17 |
+
// ๊ตฌ๊ฐ [0.75, 1] ์์ ์ด๋ถ๋ฒ์ผ๋ก ์ ํํ ํผ๋ค (l โฅ 1 ์ด๋ฉด L = 7l).
|
| 18 |
+
// ์์ ๊ณ ์ ์ ๋ฐ๋ณต l โ L/(1+6x(l)ยฒ) ์ L ๊ณผ L/7 ์ฌ์ด๋ฅผ ์ค๊ฐ๋ฉฐ ๋ฐ์ฐํ๊ณ , ์ง์ ๋ฒ(4ํ) ๋์ ์
๋ ฅ์ ๊ทธ๋๋ก
|
| 19 |
+
// ๋๋ ค์คฌ๋ค โ ์๋ ํ๋ 0.8~1 ์ธ ํ๋ยท๋ฐ์ฌ๊ฐ ์ต๋ 7๋ฐฐ ๋ฐ๊ฒ ํ์๊ฒ ๋ ์๊ฐ๋ค (2026-10-02 ๋ฆฌ๋ทฐ).
|
| 20 |
+
vec3 compress_highlights(vec3 lin) {
|
| 21 |
+
float L = dot(lin, vec3(0.2126, 0.7152, 0.0722));
|
| 22 |
+
if (L <= 0.75) return lin;
|
| 23 |
+
float Li;
|
| 24 |
+
if (L >= 7.0) {
|
| 25 |
+
Li = L / 7.0;
|
| 26 |
+
} else {
|
| 27 |
+
float lo = 0.75, hi = 1.0;
|
| 28 |
+
for (int k = 0; k < 14; ++k) {
|
| 29 |
+
float m = 0.5 * (lo + hi);
|
| 30 |
+
float x = (m - 0.75) / 0.25;
|
| 31 |
+
if (m * (1.0 + 6.0 * x * x) < L) lo = m; else hi = m;
|
| 32 |
+
}
|
| 33 |
+
Li = 0.5 * (lo + hi);
|
| 34 |
+
}
|
| 35 |
+
return lin * (Li / L);
|
| 36 |
+
}
|
src/vkgs/shaders/line.frag
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
layout(location = 0) in vec4 vColor;
|
| 3 |
+
layout(location = 0) out vec4 outColor;
|
| 4 |
+
|
| 5 |
+
void main() {
|
| 6 |
+
outColor = vec4(vColor.rgb * vColor.a, vColor.a); // premultiplied
|
| 7 |
+
}
|
src/vkgs/shaders/line.vert
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// ๋งต ์ค๋ฒ๋ ์ด(์ฐจ์ , ์ฃผํ๊ฐ๋ฅ์์ญ ๊ฒฝ๊ณ, ๊ถค์ ). 3DGS ์์ ๊ฒน์ณ ๊ทธ๋ฆฐ๋ค (๊น์ด ํ
์คํธ ์์).
|
| 4 |
+
// ํฌ์์ nusim.data.rig.CameraPose.projection ๊ณผ ๊ฐ์ Vulkan clip โ ๊ทผํ๋ฉด ํด๋ฆฌํ์ ํ๋์จ์ด๊ฐ ํ๋ค.
|
| 5 |
+
|
| 6 |
+
#include "bindless.glsl"
|
| 7 |
+
// ์ ์ ์ bindless SSBO ์์ ์ง์ ์ฝ๋๋ค (vertex pulling): LineVertex = ์์น xyz(์ฌ ์์ ๊ธฐ์ค) + ์ rgba = float 7๊ฐ
|
| 8 |
+
layout(std430, set = 0, binding = 0) readonly buffer LineVerts { float lv[]; } gLineVerts[];
|
| 9 |
+
layout(push_constant) uniform PC { mat4 viewProj; ivec4 idx; } pc; // idx.x = ์ ์ SSBO ์ฌ๋กฏ
|
| 10 |
+
layout(location = 0) out vec4 vColor;
|
| 11 |
+
|
| 12 |
+
void main() {
|
| 13 |
+
uint b = uint(gl_VertexIndex) * 7u, s = uint(pc.idx.x);
|
| 14 |
+
vec3 inPos = vec3(gLineVerts[s].lv[b], gLineVerts[s].lv[b + 1u], gLineVerts[s].lv[b + 2u]);
|
| 15 |
+
vColor = vec4(gLineVerts[s].lv[b + 3u], gLineVerts[s].lv[b + 4u], gLineVerts[s].lv[b + 5u], gLineVerts[s].lv[b + 6u]);
|
| 16 |
+
gl_Position = pc.viewProj * vec4(inPos, 1.0);
|
| 17 |
+
}
|
src/vkgs/shaders/nodes/isp.glsl
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ์
ฐ์ด๋ ๊ทธ๋ํ ๋
ธ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ โ ์นด๋ฉ๋ผ ISP (scripts/shader_graph.py ๊ฐ ์ฝ๋๋ค).
|
| 2 |
+
//
|
| 3 |
+
// ํจ์ ํ๋ = ๋
ธ๋ ํ๋. ์ด๋ฆ์ด n_<op> ์ธ ํจ์๋ง ๋
ธ๋๋ค. ์๊ทธ๋์ฒ๊ฐ ๊ณง ๋
ธ๋ ์ ์๋ค:
|
| 4 |
+
// ๋ฐํํ n_<op>(์
๋ ฅ..., Ctx x, ๋งค๊ฐ๋ณ์...)
|
| 5 |
+
// - Ctx ์์ ์ธ์ = ์
๋ ฅ ํฌํธ (๋ค๋ฅธ ๋
ธ๋์ ์ถ๋ ฅ๊ณผ ํ์
์ด ๋ง์์ผ ํ๋ค)
|
| 6 |
+
// - Ctx ๋ค์ float ์ธ์ = ๋งค๊ฐ๋ณ์ (์คํ ์ค ์ฌ๋ผ์ด๋๋ก ๋ฐ๊พผ๋ค โ ๋งค๊ฐ๋ณ์ ๋ฒํผ์์ ์ฝ๋๋ค)
|
| 7 |
+
// - Ctx ๊ฐ ์์ผ๋ฉด ์
๋ ฅ๋ง ๋ฐ๋ ์์ ํจ์
|
| 8 |
+
// ๋งค๊ฐ๋ณ์์ ๊ธฐ๋ณธ๊ฐยท๋ฒ์๋ ๋ฐ๋ก ์ ์ฃผ์ '// @param ์ด๋ฆ ๊ธฐ๋ณธ๊ฐ ์ต์๊ฐ ์ต๋๊ฐ' ์ผ๋ก ์ค๋ค (์์ผ๋ฉด 0, 0~1).
|
| 9 |
+
// ๋
ธ๋๋ ํฝ์
ํ๋๋ง ๋ณธ๋ค (์ด์ ํฝ์
์ ์ฝ์ง ์๋๋ค) โ ๊ทธ๋์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ์ด๋ฏธ์ง์ ๋ฎ์ด์จ๋ ์์ ํ๋ค.
|
| 10 |
+
|
| 11 |
+
struct Ctx {
|
| 12 |
+
ivec2 px; // ํฝ์
|
| 13 |
+
vec2 uv; // 0~1
|
| 14 |
+
vec2 size; // ํญ, ๋์ด
|
| 15 |
+
float time; // ์๋ฎฌ๋ ์ด์
์๊ฐ s
|
| 16 |
+
uint frame; // ํ๋ ์ ๋ฒํธ (๋
ธ์ด์ฆ ์จ์)
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
// sRGB ๋ถํธํ โ ์ ํ. ๋ ๋ ๊ฒฐ๊ณผ(post)๋ sRGB ๋ก ๋ถํธํ๋ผ ์๋ค (ํ์ต ์ด๋ฏธ์ง๊ฐ JPEG).
|
| 20 |
+
vec3 n_to_linear(vec3 c) { return mix(c / 12.92, pow((max(c, 0.0) + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c)); }
|
| 21 |
+
vec3 n_to_srgb(vec3 c) {
|
| 22 |
+
c = max(c, vec3(0.0));
|
| 23 |
+
return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), c));
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// ๋
ธ์ถ (EV, ์ ํ ์์)
|
| 27 |
+
// @param ev 0 -3 3
|
| 28 |
+
vec3 n_exposure(vec3 c, Ctx x, float ev) { return c * exp2(ev); }
|
| 29 |
+
|
| 30 |
+
// ํ์ดํธ๋ฐธ๋ฐ์ค โ ์์จ๋(+ ๋ฐ๋ปํ๊ฒ / - ์ฐจ๊ฐ๊ฒ)ยทํดํธ(+ ๋ง์ ํ / - ์ด๋ก) ์ด๋ (์ ํ ์์)
|
| 31 |
+
// @param temperature 0 -1 1
|
| 32 |
+
// @param tint 0 -1 1
|
| 33 |
+
vec3 n_white_balance(vec3 c, Ctx x, float temperature, float tint) {
|
| 34 |
+
vec3 g = vec3(1.0 + 0.3 * temperature, 1.0 - 0.2 * tint, 1.0 - 0.3 * temperature);
|
| 35 |
+
return c * g / dot(g, vec3(0.2126, 0.7152, 0.0722)); // ๋ฐ๊ธฐ๋ ์ ์ง
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// ์ฑ๋ (1 = ๊ทธ๋๋ก, 0 = ํ๋ฐฑ)
|
| 39 |
+
// @param saturation 1 0 2
|
| 40 |
+
vec3 n_saturation(vec3 c, Ctx x, float saturation) {
|
| 41 |
+
float y = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
| 42 |
+
return max(mix(vec3(y), c, saturation), 0.0);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// ๋๋น (์ค๊ฐ ํ์ ๊ธฐ์ค, sRGB ๋ถํธํ ์์)
|
| 46 |
+
// @param contrast 1 0.5 1.5
|
| 47 |
+
vec3 n_contrast(vec3 c, Ctx x, float contrast) { return max((c - 0.5) * contrast + 0.5, 0.0); }
|
| 48 |
+
|
| 49 |
+
// ๋น๋คํ
โ ๋ ์ฆ ์ฃผ๋ณ ๊ฐ๊ด cos^4 (์ ํ ์์). ์์ผ๊ฐ์ ํ๋ฉด ๋๊ฐ ๊ธฐ์ค ๊ทผ์ฌ
|
| 50 |
+
// @param vignette 0 0 1
|
| 51 |
+
vec3 n_vignette(vec3 c, Ctx x, float vignette) {
|
| 52 |
+
vec2 d = (x.uv - 0.5) * vec2(x.size.x / x.size.y, 1.0);
|
| 53 |
+
float cos_t = inversesqrt(1.0 + dot(d, d) * 1.2);
|
| 54 |
+
return c * mix(1.0, pow(cos_t, 4.0), vignette);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// ์ผ์ ๋
ธ์ด์ฆ โ ์ท ๋
ธ์ด์ฆ(๋ฐ๊ธฐ์ ๋น๋ก) + ์ฝ๊ธฐ ๋
ธ์ด์ฆ (์ ํ ์์). ํ๋ ์๋ง๋ค ๋ค๋ฅด๋ค
|
| 58 |
+
// @param shot_noise 0 0 0.05
|
| 59 |
+
// @param read_noise 0 0 0.02
|
| 60 |
+
vec3 n_sensor_noise(vec3 c, Ctx x, float shot_noise, float read_noise) {
|
| 61 |
+
uvec3 s = uvec3(uvec2(x.px), x.frame);
|
| 62 |
+
// PCG ํด์ 3๊ฐ โ ๊ทผ์ฌ ์ ๊ท๋ถํฌ (๊ท ๋ฑ ๋์ ํฉ)
|
| 63 |
+
vec3 n = vec3(0.0);
|
| 64 |
+
for (int k = 0; k < 2; ++k) {
|
| 65 |
+
s = s * 1664525u + 1013904223u;
|
| 66 |
+
s.x += s.y * s.z; s.y += s.z * s.x; s.z += s.x * s.y;
|
| 67 |
+
s ^= s >> 16u;
|
| 68 |
+
s.x += s.y * s.z; s.y += s.z * s.x; s.z += s.x * s.y;
|
| 69 |
+
n += vec3(s) * (1.0 / 4294967296.0) - 0.5;
|
| 70 |
+
}
|
| 71 |
+
n *= 2.449; // ๋ถ์ฐ 1 ๋ก
|
| 72 |
+
return max(c + n * (shot_noise * sqrt(max(c, 0.0)) + read_noise), 0.0);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// ๋ ๊ฐ๋ ์๊ธฐ
|
| 76 |
+
// @param amount 0.5 0 1
|
| 77 |
+
vec3 n_mix(vec3 a, vec3 b, Ctx x, float amount) { return mix(a, b, amount); }
|
| 78 |
+
|
| 79 |
+
// ๋ฐ๊ธฐ (float ์ถ๋ ฅ โ ํ์
๊ฒ์ฌ ์์ยท๋ง์คํฌ์ฉ)
|
| 80 |
+
float n_luma(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
|
| 81 |
+
// ํ์์ผ๋ก (float โ vec3)
|
| 82 |
+
vec3 n_gray(float y) { return vec3(y); }
|
src/vkgs/shaders/pbr.frag
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// split-sum IBL (Karis 2013): ํ์ฐ = ๋ณต์ฌ์กฐ๋ยท์๋ฒ ๋, ์ ๋ฐ์ฌ = ํ๋ฆฌํํฐ(R, ๊ฑฐ์น ๊ธฐ)ยท(F0ยทA + B)
|
| 4 |
+
#include "ibl_common.glsl"
|
| 5 |
+
#include "depth_common.glsl"
|
| 6 |
+
layout(location = 0) in vec3 vWorld;
|
| 7 |
+
layout(location = 1) in vec3 vNormal;
|
| 8 |
+
layout(location = 0) out vec4 outColor;
|
| 9 |
+
layout(location = 1) out vec4 outDepth; // ๊ฑฐ๋ฆฌ ๋์ (depth_common.glsl)
|
| 10 |
+
layout(push_constant) uniform PC {
|
| 11 |
+
mat4 viewProj;
|
| 12 |
+
vec4 center_radius;
|
| 13 |
+
vec4 albedo_rough;
|
| 14 |
+
vec4 metal;
|
| 15 |
+
vec4 campos;
|
| 16 |
+
ivec4 idx;
|
| 17 |
+
} pc;
|
| 18 |
+
|
| 19 |
+
vec3 fresnel_schlick_roughness(float cosT, vec3 F0, float r) {
|
| 20 |
+
return F0 + (max(vec3(1.0 - r), F0) - F0) * pow(1.0 - cosT, 5.0);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
void main() {
|
| 24 |
+
vec3 N = normalize(vNormal);
|
| 25 |
+
vec3 V = normalize(pc.campos.xyz - vWorld);
|
| 26 |
+
vec3 R = reflect(-V, N);
|
| 27 |
+
float NdotV = max(dot(N, V), 1e-4);
|
| 28 |
+
vec3 albedo = pc.albedo_rough.rgb;
|
| 29 |
+
float rough = clamp(pc.albedo_rough.a, 0.0, 1.0);
|
| 30 |
+
float metallic = pc.metal.x;
|
| 31 |
+
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
| 32 |
+
|
| 33 |
+
float mips = float(textureQueryLevels(uSpecular));
|
| 34 |
+
vec3 pref = textureLod(uSpecular, R, rough * (mips - 1.0)).rgb;
|
| 35 |
+
vec2 ab = texture(uBrdfLut, vec2(NdotV, rough)).rg;
|
| 36 |
+
vec3 spec = pref * (F0 * ab.x + ab.y);
|
| 37 |
+
|
| 38 |
+
vec3 kS = fresnel_schlick_roughness(NdotV, F0, rough);
|
| 39 |
+
vec3 kD = (1.0 - kS) * (1.0 - metallic);
|
| 40 |
+
vec3 diffuse = texture(uIrradiance, N).rgb * albedo;
|
| 41 |
+
vec3 lin = kD * diffuse + spec;
|
| 42 |
+
// 3DGS ์ ๊ฐ์ ์นด๋ฉ๋ผ sRGB ๊ณต๊ฐ์ผ๋ก. ํ์ด๋ผ์ดํธ ํ์ฅ๋ ๋๋๋ ค ์ฅ๋ฉด ๋ฐ๊ธฐ์ ๋ง์ถ๋ค.
|
| 43 |
+
outColor = vec4(linear_to_srgb(compress_highlights(lin)), 1.0);
|
| 44 |
+
outDepth = vec4(linear_depth(gl_FragCoord.z), 1.0, 0.0, 1.0);
|
| 45 |
+
}
|
src/vkgs/shaders/pbr.vert
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
#include "bindless.glsl"
|
| 4 |
+
// IBL ๊ฒ์ฆ์ฉ PBR ๋ฉ์ (๊ตฌ). glTF ์ฐจ๋๋ ๊ฐ์ ์
ฐ์ด๋ ๊ฒฝ๋ก๋ก ๋ค์ด์ฌ ๊ฒ์ด๋ค.
|
| 5 |
+
// ์ ์ ์ bindless SSBO ์์ (vertex pulling): ์์น xyz + ๋ฒ์ xyz = float 6๊ฐ
|
| 6 |
+
layout(std430, set = 0, binding = 0) readonly buffer SphereVerts { float sv[]; } gSphereVerts[];
|
| 7 |
+
layout(push_constant) uniform PC {
|
| 8 |
+
mat4 viewProj;
|
| 9 |
+
vec4 center_radius; // ๊ตฌ ์ค์ฌ(์ฌ ์ขํ), ๋ฐ์ง๋ฆ
|
| 10 |
+
vec4 albedo_rough; // rgb ์๋ฒ ๋(์ ํ), ๊ฑฐ์น ๊ธฐ
|
| 11 |
+
vec4 metal; // x ๊ธ์์ฑ
|
| 12 |
+
vec4 campos; // ์นด๋ฉ๋ผ ์์น
|
| 13 |
+
ivec4 idx; // x = ์ ์ SSBO ์ฌ๋กฏ
|
| 14 |
+
} pc;
|
| 15 |
+
layout(location = 0) out vec3 vWorld;
|
| 16 |
+
layout(location = 1) out vec3 vNormal;
|
| 17 |
+
void main() {
|
| 18 |
+
uint b = uint(gl_VertexIndex) * 6u, s = uint(pc.idx.x);
|
| 19 |
+
vec3 inPos = vec3(gSphereVerts[s].sv[b], gSphereVerts[s].sv[b + 1u], gSphereVerts[s].sv[b + 2u]);
|
| 20 |
+
vec3 inNormal = vec3(gSphereVerts[s].sv[b + 3u], gSphereVerts[s].sv[b + 4u], gSphereVerts[s].sv[b + 5u]);
|
| 21 |
+
vec3 w = pc.center_radius.xyz + inPos * pc.center_radius.w;
|
| 22 |
+
vWorld = w;
|
| 23 |
+
vNormal = inNormal;
|
| 24 |
+
gl_Position = pc.viewProj * vec4(w, 1.0);
|
| 25 |
+
}
|
src/vkgs/shaders/pbr_gltf.frag
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#version 450
|
| 2 |
+
#extension GL_GOOGLE_include_directive : require
|
| 3 |
+
// glTF metallic-roughness + clearcoat + transmission(๊ทผ์ฌ), split-sum IBL.
|
| 4 |
+
#include "ibl_common.glsl"
|
| 5 |
+
#include "depth_common.glsl"
|
| 6 |
+
|
| 7 |
+
layout(location = 0) in vec3 vWorld;
|
| 8 |
+
layout(location = 1) in vec3 vNormal;
|
| 9 |
+
layout(location = 2) in vec2 vUV0;
|
| 10 |
+
layout(location = 3) in vec2 vUV1;
|
| 11 |
+
layout(location = 4) in vec4 vTangent;
|
| 12 |
+
layout(location = 0) out vec4 outColor;
|
| 13 |
+
layout(location = 1) out vec4 outDepth; // ๊ฑฐ๋ฆฌ ๋์ (depth_common.glsl)
|
| 14 |
+
|
| 15 |
+
layout(push_constant) uniform PC {
|
| 16 |
+
mat4 model;
|
| 17 |
+
mat4 viewProj;
|
| 18 |
+
vec4 campos;
|
| 19 |
+
ivec4 idx; // x = ์ฌ์ง SSBO ์ฌ๋กฏ, y = ์ฌ์ง ๋ฒํธ
|
| 20 |
+
} pc;
|
| 21 |
+
|
| 22 |
+
struct Material { // std430 โ viewer.cpp add_gltf ์ MatGPU ์ ๊ฐ์ ์์ (304 B)
|
| 23 |
+
vec4 base; // ๊ธฐ๋ณธ์ ๊ณ์ (์ ํ)
|
| 24 |
+
vec4 emissive; // rgb ร emissive_strength
|
| 25 |
+
vec4 mrno; // metallic, roughness, normal_scale, occlusion_strength
|
| 26 |
+
vec4 alpha; // cutoff, mode(0 ๋ถํฌ๋ช
1 ๋ง์คํฌ 2 ๋ธ๋ ๋), transmission, has_normal_tex
|
| 27 |
+
vec4 cc; // clearcoat, clearcoat_roughness, -, -
|
| 28 |
+
ivec4 tc0; // texcoord ๋ฒํธ: base, metal-rough, normal, occlusion
|
| 29 |
+
ivec4 tc1; // emissive, -, -, -
|
| 30 |
+
vec4 xf[10]; // KHR_texture_transform: ์ฌ๋กฏ๋ง๋ค 2ํ (a b c), (d e f)
|
| 31 |
+
ivec4 tex0; // 2D ํ
์ค์ฒ ๋ฒํธ: base, metal-rough, normal, occlusion (์์ผ๋ฉด ํฐ์)
|
| 32 |
+
ivec4 tex1; // emissive, -, -, -
|
| 33 |
+
};
|
| 34 |
+
layout(std430, set = 0, binding = 0) readonly buffer Materials { Material m[]; } gMaterials[];
|
| 35 |
+
Material mat;
|
| 36 |
+
#define tBase gTex2D[mat.tex0.x]
|
| 37 |
+
#define tMetalRough gTex2D[mat.tex0.y]
|
| 38 |
+
#define tNormal gTex2D[mat.tex0.z]
|
| 39 |
+
#define tOcclusion gTex2D[mat.tex0.w]
|
| 40 |
+
#define tEmissive gTex2D[mat.tex1.x]
|
| 41 |
+
|
| 42 |
+
vec2 uv_of(int slot) {
|
| 43 |
+
int tc = slot < 4 ? mat.tc0[slot] : mat.tc1.x;
|
| 44 |
+
vec3 uv = vec3(tc == 0 ? vUV0 : vUV1, 1.0);
|
| 45 |
+
return vec2(dot(mat.xf[2 * slot].xyz, uv), dot(mat.xf[2 * slot + 1].xyz, uv));
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
vec3 fresnel_schlick_roughness(float cosT, vec3 F0, float r) {
|
| 49 |
+
return F0 + (max(vec3(1.0 - r), F0) - F0) * pow(1.0 - cosT, 5.0);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
vec3 ibl_specular(vec3 R, float rough, vec3 F0, float NdotV) {
|
| 53 |
+
float mips = float(textureQueryLevels(uSpecular));
|
| 54 |
+
vec3 pref = textureLod(uSpecular, R, rough * (mips - 1.0)).rgb;
|
| 55 |
+
vec2 ab = texture(uBrdfLut, vec2(NdotV, rough)).rg;
|
| 56 |
+
return pref * (F0 * ab.x + ab.y);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
void main() {
|
| 60 |
+
mat = gMaterials[pc.idx.x].m[pc.idx.y];
|
| 61 |
+
vec4 base = mat.base * texture(tBase, uv_of(0)); // sRGB ๋ทฐ๋ผ ์ํ๋ง์ด ์ ํ์ผ๋ก ๋๋ ค์ค๋ค
|
| 62 |
+
if (mat.alpha.y == 1.0 && base.a < mat.alpha.x) discard;
|
| 63 |
+
|
| 64 |
+
vec4 mr = texture(tMetalRough, uv_of(1));
|
| 65 |
+
float metallic = clamp(mat.mrno.x * mr.b, 0.0, 1.0);
|
| 66 |
+
float rough = clamp(mat.mrno.y * mr.g, 0.03, 1.0);
|
| 67 |
+
float occ = 1.0 + mat.mrno.w * (texture(tOcclusion, uv_of(3)).r - 1.0);
|
| 68 |
+
vec3 emissive = mat.emissive.rgb * texture(tEmissive, uv_of(4)).rgb;
|
| 69 |
+
|
| 70 |
+
vec3 N = normalize(vNormal);
|
| 71 |
+
if (!gl_FrontFacing) N = -N; // ์๋ฉด ์ฌ์ง (์ฐจ ๋ฉ์๋ ์ปฌ๋ง ์์ด ๊ทธ๋ฆฐ๋ค)
|
| 72 |
+
vec3 V = normalize(pc.campos.xyz - vWorld);
|
| 73 |
+
if (mat.alpha.w > 0.5) {
|
| 74 |
+
vec3 T, B;
|
| 75 |
+
if (abs(vTangent.w) > 0.5) {
|
| 76 |
+
T = normalize(vTangent.xyz - N * dot(N, vTangent.xyz));
|
| 77 |
+
B = cross(N, T) * vTangent.w;
|
| 78 |
+
} else { // ํ์ ํธ๊ฐ ์์ผ๋ฉด ํ๋ฉด ๋ฏธ๋ถ์ผ๋ก (cotangent frame)
|
| 79 |
+
vec3 dp1 = dFdx(vWorld), dp2 = dFdy(vWorld);
|
| 80 |
+
vec2 uv = uv_of(2);
|
| 81 |
+
vec2 du1 = dFdx(uv), du2 = dFdy(uv);
|
| 82 |
+
vec3 dp2perp = cross(dp2, N), dp1perp = cross(N, dp1);
|
| 83 |
+
T = dp2perp * du1.x + dp1perp * du2.x;
|
| 84 |
+
B = dp2perp * du1.y + dp1perp * du2.y;
|
| 85 |
+
float inv = inversesqrt(max(dot(T, T), dot(B, B)) + 1e-12);
|
| 86 |
+
T *= inv;
|
| 87 |
+
B *= inv;
|
| 88 |
+
}
|
| 89 |
+
vec3 nt = texture(tNormal, uv_of(2)).xyz * 2.0 - 1.0;
|
| 90 |
+
nt.xy *= mat.mrno.z;
|
| 91 |
+
N = normalize(mat3(T, B, N) * nt);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
float NdotV = max(dot(N, V), 1e-4);
|
| 95 |
+
vec3 R = reflect(-V, N);
|
| 96 |
+
vec3 F0 = mix(vec3(0.04), base.rgb, metallic);
|
| 97 |
+
vec3 spec = ibl_specular(R, rough, F0, NdotV);
|
| 98 |
+
vec3 kS = fresnel_schlick_roughness(NdotV, F0, rough);
|
| 99 |
+
vec3 kD = (1.0 - kS) * (1.0 - metallic);
|
| 100 |
+
vec3 diffuse = texture(uIrradiance, N).rgb * base.rgb;
|
| 101 |
+
|
| 102 |
+
// ํฌ๊ณผ(์ ๋ฆฌ) ๊ทผ์ฌ: ๋ค์ชฝ ์ฅ๋ฉด ๋์ ํ๊ฒฝ๋งต์ ์์ ๋ฐฉํฅ์ผ๋ก ๋น์ถ๋ค + ๋ฐํฌ๋ช
๋ธ๋ ๋ฉ
|
| 103 |
+
float tr = mat.alpha.z;
|
| 104 |
+
vec3 body = kD * diffuse * (1.0 - tr);
|
| 105 |
+
if (tr > 0.0) {
|
| 106 |
+
float mips = float(textureQueryLevels(uSpecular));
|
| 107 |
+
body += tr * (1.0 - kS) * textureLod(uSpecular, -V, rough * (mips - 1.0)).rgb * base.rgb;
|
| 108 |
+
}
|
| 109 |
+
vec3 lin = (body + spec) * occ + emissive;
|
| 110 |
+
|
| 111 |
+
// ํด๋ฆฌ์ด์ฝํธ: ๊ฑฐ์น ๊ธฐ ๋ฎ์ ์ ์ ์ฒด(F0 0.04) ์ธต์ด ์์ ํ ๊ฒน ๋ (์ฐจ ๋์ฅ)
|
| 112 |
+
float cc = mat.cc.x;
|
| 113 |
+
if (cc > 0.0) {
|
| 114 |
+
float ccr = clamp(mat.cc.y, 0.03, 1.0);
|
| 115 |
+
vec3 Fc = fresnel_schlick_roughness(NdotV, vec3(0.04), ccr);
|
| 116 |
+
lin = lin * (1.0 - cc * Fc) + cc * ibl_specular(reflect(-V, normalize(vNormal) * (gl_FrontFacing ? 1.0 : -1.0)),
|
| 117 |
+
ccr, vec3(0.04), NdotV);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
lin *= pc.campos.w;
|
| 121 |
+
vec3 c = linear_to_srgb(compress_highlights(lin));
|
| 122 |
+
float a = 1.0;
|
| 123 |
+
if (mat.alpha.y == 2.0) a = base.a;
|
| 124 |
+
if (tr > 0.0) a = mix(1.0, 0.35, tr) + 0.5 * kS.g * tr; // ์ ๋ฆฌ: ์ ๋ฉด์ ๋น์น๊ณ ๋น์ค๋ฌํ๋ฉด ๋ฐ์ฌ
|
| 125 |
+
a = clamp(a, 0.0, 1.0);
|
| 126 |
+
outColor = vec4(c * a, a); // premultiplied
|
| 127 |
+
outDepth = vec4(linear_depth(gl_FragCoord.z) * a, a, 0.0, a);
|
| 128 |
+
}
|