Skip to content

Commit e7ebc41

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 e7ebc41

4 files changed

Lines changed: 169 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 "dynamic-object-removal>=0.3"`
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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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)`pip install dynamic-object-removal`, numpy only.
4+
5+
## Install
6+
7+
```bash
8+
pip install "dynamic-object-removal>=0.3"
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 | 88.4 | 97.6 | 92.9 | 93.5 | 97.5 | 95.5 |
30+
| temporal | 97.0 | 46.6 | 67.2 | 97.3 | 25.9 | 50.2 |
31+
32+
`scan_ratio` removes a map point only when at least 15% of the scans flag its polar
33+
column as vacated (library default since v0.3.0) — single-sweep false positives from
34+
occlusion or sparse sampling don't accumulate over the sequence.
35+
36+
Reproduce end-to-end (download + eval) from the upstream library repo:
37+
38+
```bash
39+
git clone https://github.com/rsasaki0109/dynamic-3d-object-removal.git
40+
python3 dynamic-3d-object-removal/scripts/run_dynamicmap_benchmark.py --sequences 00 05
41+
```

methods/dor_numpy/main.py

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

0 commit comments

Comments
 (0)