Skip to content

Commit 2ba0b30

Browse files
committed
Add dor_numpy: numpy-only dynamic-object-removal adapter.
Detector-free range, scan-ratio, and temporal map cleaning via pip install dynamic-object-removal; includes Semantic-KITTI seq 00/05 SA/DA/AA results.
1 parent 8b60f36 commit 2ba0b30

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Offline (need prior map).
6969
- [x] BeautyMap (Ours 🚀): [RAL'24](https://arxiv.org/abs/2405.07283), [**Official Code**](https://github.com/MKJia/BeautyMap)
7070
- [x] ERASOR: [RAL'21 official link](https://github.com/LimHyungTae/ERASOR), [**benchmark implementation**](https://github.com/Kin-Zhang/ERASOR/tree/feat/no_ros)
7171
- [x] Removert: [IROS 2020 official link](https://github.com/irapkaist/removert), [**benchmark implementation**](https://github.com/Kin-Zhang/removert)
72+
- [x] dynamic-object-removal (numpy-only): [GitHub](https://github.com/rsasaki0109/dynamic-3d-object-removal), [**Benchmark adapter**](methods/dor_numpy) — detector-free range / scan-ratio / temporal, `pip install git+https://github.com/rsasaki0109/dynamic-3d-object-removal.git`
7273

7374
Please note that we provided the comparison methods also but modified a little bit for us to run the experiments quickly, but no modified on their methods' core. Please check the LICENSE of each method in their official link before using it.
7475

methods/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ All Running commands, `-1` means all pcd files in the `pcd` folder, otherwise th
8282
./build/octomap_run ${data_path} ${config.toml} -1
8383
python BeautyMap/main.py --data_dir ${data_path} --run_file_num -1
8484
./build/dufomap_run ${data_path} ${config.toml}
85+
pip install "dynamic-object-removal>=0.3" && python dor_numpy/main.py --data_dir ${data_path} --algorithm range
8586
```
8687

8788
<!-- ./build/removert_run ${data_path} ${config.yaml} -1

methods/dor_numpy/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# dynamic-object-removal (numpy-only)
2+
3+
Detector-free dynamic map cleaning via [dynamic-object-removal](https://github.com/rsasaki0109/dynamic-3d-object-removal) — installable straight from GitHub, numpy only.
4+
5+
## Install
6+
7+
```bash
8+
pip install git+https://github.com/rsasaki0109/dynamic-3d-object-removal.git
9+
```
10+
11+
## Run
12+
13+
From this folder (`methods/dor_numpy/`):
14+
15+
```bash
16+
python main.py --data_dir /path/to/00 --algorithm range
17+
python main.py --data_dir /path/to/00 --algorithm scan_ratio
18+
python main.py --data_dir /path/to/00 --algorithm temporal
19+
```
20+
21+
Each command writes `dor_<algorithm>_output.pcd` into `data_dir`. Use the benchmark's
22+
`export_eval_pcd` + `scripts/py/eval/evaluate_all.py` for SA/DA/AA/HA.
23+
24+
## Semantic-KITTI teaser results (seq 00 / 05)
25+
26+
| algorithm | seq 00 SA | seq 00 DA | seq 00 AA | seq 05 SA | seq 05 DA | seq 05 AA |
27+
|---|---|---|---|---|---|---|
28+
| range | 99.6 | 34.5 | 58.6 | 99.8 | 25.9 | 50.9 |
29+
| scan_ratio | 98.0 | 92.8 | 95.4 | 96.0 | 97.9 | 96.9 |
30+
| temporal | 97.0 | 46.6 | 67.2 | 97.3 | 25.9 | 50.2 |
31+
32+
`scan_ratio` normalizes votes per point: a map point is removed only when a majority
33+
of the scans that actually revisit its polar column flag it as vacated (library default
34+
since v0.4.0). Rarely-observed static points no longer accumulate spurious votes over
35+
the sequence, which lifts SA to ~96-98% at near-unchanged DA.
36+
37+
Reproduce end-to-end (download + eval) from the upstream library repo:
38+
39+
```bash
40+
git clone https://github.com/rsasaki0109/dynamic-3d-object-removal.git
41+
python3 dynamic-3d-object-removal/scripts/run_dynamicmap_benchmark.py --sequences 00 05
42+
```

methods/dor_numpy/main.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/usr/bin/env python3
2+
"""DynamicMap_Benchmark adapter for dynamic-object-removal (numpy-only).
3+
4+
Usage (from a cloned DynamicMap_Benchmark repo, after
5+
``pip install git+https://github.com/rsasaki0109/dynamic-3d-object-removal.git``):
6+
python main.py --data_dir /path/to/00 --algorithm range
7+
python main.py --data_dir /path/to/00 --algorithm scan_ratio
8+
python main.py --data_dir /path/to/00 --algorithm temporal
9+
10+
Writes ``dor_<algorithm>_output.pcd`` into ``data_dir`` (cleaned accumulated map).
11+
Run ``export_eval_pcd`` + ``evaluate_all.py`` in the benchmark repo for SA/DA/AA/HA.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import argparse
17+
from pathlib import Path
18+
19+
import numpy as np
20+
21+
try:
22+
import dynamic_object_removal as core
23+
except ImportError as exc:
24+
raise SystemExit(
25+
"Install first: pip install git+https://github.com/rsasaki0109/dynamic-3d-object-removal.git"
26+
) from exc
27+
28+
29+
def _load_sequence(pcd_dir: Path) -> tuple[np.ndarray, list[tuple[np.ndarray, np.ndarray]], list[tuple[int, int]]]:
30+
scan_files = sorted(pcd_dir.glob("*.pcd"))
31+
if not scan_files:
32+
raise SystemExit(f"No scans in {pcd_dir}")
33+
34+
chunks: list[np.ndarray] = []
35+
scans: list[tuple[np.ndarray, np.ndarray]] = []
36+
slices: list[tuple[int, int]] = []
37+
cursor = 0
38+
for path in scan_files:
39+
scan = core.load_pcd_scan(path)
40+
if scan.viewpoint is None:
41+
raise SystemExit(f"Missing VIEWPOINT in {path}")
42+
origin = scan.viewpoint[:3]
43+
n = len(scan.points)
44+
chunks.append(scan.points)
45+
scans.append((scan.points, origin))
46+
slices.append((cursor, cursor + n))
47+
cursor += n
48+
return np.concatenate(chunks, axis=0), scans, slices
49+
50+
51+
def _clean(
52+
algorithm: str,
53+
acc_map: np.ndarray,
54+
scans: list[tuple[np.ndarray, np.ndarray]],
55+
slices: list[tuple[int, int]],
56+
*,
57+
h_res: float,
58+
v_res: float,
59+
voxel_size: float,
60+
temporal_min_hits: int,
61+
sr_min_votes: int | None,
62+
) -> np.ndarray:
63+
if algorithm == "range":
64+
ground_z = float(np.percentile(acc_map[:, 2], 2))
65+
_, keep = core.clean_map_by_visibility(
66+
acc_map,
67+
scans,
68+
h_res_deg=h_res,
69+
v_res_deg=v_res,
70+
ground_z=ground_z,
71+
)
72+
elif algorithm == "scan_ratio":
73+
# min_votes=None: the library normalizes votes per point by the number of
74+
# scans that revisit its polar column (majority rule).
75+
_, keep = core.clean_map_by_scan_ratio(acc_map, scans, min_votes=sr_min_votes)
76+
elif algorithm == "temporal":
77+
keep = np.ones(len(acc_map), dtype=bool)
78+
tfilter = core.TemporalConsistencyFilter(
79+
voxel_size=voxel_size,
80+
window_size=len(scans),
81+
min_hits=temporal_min_hits,
82+
)
83+
# Two passes over the same scans: the first fills the hit counter
84+
# (window_size == number of scans, so nothing is evicted), the second
85+
# judges each scan against the full-sequence counts — re-inserting a
86+
# scan evicts its own first-pass copy, leaving the counter unchanged.
87+
for s, e in slices:
88+
tfilter.filter(acc_map[s:e])
89+
for s, e in slices:
90+
_, keep_f = tfilter.filter(acc_map[s:e])
91+
keep[s:e] = keep_f
92+
else:
93+
raise SystemExit(f"Unknown algorithm: {algorithm}")
94+
return acc_map[keep]
95+
96+
97+
def main() -> int:
98+
parser = argparse.ArgumentParser(description="DynamicMap_Benchmark adapter (numpy-only).")
99+
parser.add_argument("--data_dir", required=True, help="Sequence folder with pcd/ and gt_cloud.pcd.")
100+
parser.add_argument("--algorithm", choices=["range", "scan_ratio", "temporal"], default="range")
101+
parser.add_argument("--h-res", type=float, default=1.0)
102+
parser.add_argument("--v-res", type=float, default=1.0)
103+
parser.add_argument("--voxel-size", type=float, default=core.DEFAULT_TEMPORAL_VOXEL_SIZE)
104+
parser.add_argument("--temporal-min-hits", type=int, default=2)
105+
parser.add_argument("--sr-min-votes", type=int, default=None,
106+
help="Fixed absolute vote threshold (default: majority of each point's column revisits).")
107+
args = parser.parse_args()
108+
109+
data_dir = Path(args.data_dir)
110+
pcd_dir = data_dir / "pcd"
111+
acc_map, scans, slices = _load_sequence(pcd_dir)
112+
cleaned = _clean(
113+
args.algorithm,
114+
acc_map,
115+
scans,
116+
slices,
117+
h_res=args.h_res,
118+
v_res=args.v_res,
119+
voxel_size=args.voxel_size,
120+
temporal_min_hits=args.temporal_min_hits,
121+
sr_min_votes=args.sr_min_votes,
122+
)
123+
out = data_dir / f"dor_{args.algorithm}_output.pcd"
124+
core.save_points(out, cleaned, fmt="pcd")
125+
print(f"Wrote {len(cleaned):,} points -> {out}")
126+
return 0
127+
128+
129+
if __name__ == "__main__":
130+
raise SystemExit(main())

0 commit comments

Comments
 (0)