Skip to content

Commit 20cb584

Browse files
committed
add web-based kitti viewer
1 parent 93e5bb7 commit 20cb584

22 files changed

+3386
-5
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ RUN APT_INSTALL="apt-get install -y --no-install-recommends" && \
7474

7575
RUN PIP_INSTALL="python -m pip --no-cache-dir install --upgrade" && \
7676
$PIP_INSTALL \
77-
shapely fire pybind11 pyqtgraph tensorboardX protobuf \
78-
pyopengl pyqt5 matplotlib scikit-image numba pillow
77+
shapely fire pybind11 tensorboardX protobuf \
78+
scikit-image numba pillow
7979

8080
WORKDIR /root
8181
RUN wget https://dl.bintray.com/boostorg/release/1.68.0/source/boost_1_68_0.tar.gz

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ cd ./second.pytorch/second
3232
It is recommend to use Anaconda package manager.
3333

3434
```bash
35-
pip install shapely fire pybind11 pyqtgraph tensorboardX protobuf
35+
pip install shapely fire pybind11 tensorboardX protobuf scikit-image numba pillow
3636
```
3737

3838
If you don't have Anaconda:
@@ -204,7 +204,23 @@ python ./pytorch/train.py evaluate --config_path=./configs/car.config --model_di
204204

205205
Currently there is a problem that training and evaluating in docker is very slow.
206206

207-
## Try Kitti Viewer (Unstable)
207+
## Try Kitti Viewer Web
208+
209+
1. run ```python ./kittiviewer/backend.py --port=xxxx``` in your server/local.
210+
211+
2. run ```cd ./kittiviewer/frontend && python -m http.server``` to launch a local web server.
212+
213+
3. open your browser and enter http://127.0.0.1:8000.
214+
215+
4. input backend (http://your_server:your_backend_port)
216+
217+
5. input root path, info path and det path (optional)
218+
219+
6. click load, loadDet (optional), then click plot.
220+
221+
![GuidePic](https://raw.githubusercontent.com/traveller59/second.pytorch/master/images/viewerweb.png)
222+
223+
## Try Kitti Viewer (Deprecated)
208224

209225
You should use kitti viewer based on pyqt and pyqtgraph to check data before training.
210226

images/viewerweb.png

1.71 MB
Loading

second/core/box_np_ops.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,4 +859,9 @@ def assign_label_to_voxel(gt_boxes, coors, voxel_size, coors_range):
859859
axis=2)
860860
gt_surfaces = corner_to_surfaces_3d(gt_box_corners)
861861
ret = points_in_convex_polygon_3d_jit(voxel_centers, gt_surfaces)
862-
return np.any(ret, axis=1).astype(np.int64)
862+
return np.any(ret, axis=1).astype(np.int64)
863+
864+
def change_box3d_center_(box3d, src, dst):
865+
dst = np.array(dst, dtype=box3d.dtype)
866+
src = np.array(src, dtype=box3d.dtype)
867+
box3d[..., :3] += box3d[..., 3:6] * (dst - src)

second/kittiviewer/backend.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import fire
2+
import os
3+
import numpy as np
4+
import base64
5+
import json
6+
import time
7+
from flask import Flask, jsonify, request
8+
from flask_cors import CORS
9+
10+
import pickle
11+
import sys
12+
from functools import partial
13+
from pathlib import Path
14+
15+
import second.core.box_np_ops as box_np_ops
16+
import second.core.preprocess as prep
17+
from second.core.anchor_generator import AnchorGenerator
18+
from second.core.box_coders import GroundBox3dCoder
19+
from second.core.point_cloud.point_cloud_ops import points_to_voxel
20+
from second.core.region_similarity import (
21+
DistanceSimilarity, NearestIouSimilarity, RotateIouSimilarity)
22+
from second.core.sample_ops import (
23+
sample_from_database_v2, sample_from_database_v3, sample_from_database_v4,
24+
DataBaseSamplerV2)
25+
from second.core.target_assigner import TargetAssigner
26+
from second.data import kitti_common as kitti
27+
from second.protos import pipeline_pb2
28+
from second.utils.eval import get_coco_eval_result, get_official_eval_result
29+
from second.pytorch.inference import TorchInferenceContext
30+
from second.utils.progress_bar import list_bar
31+
32+
app = Flask("second")
33+
CORS(app)
34+
35+
class SecondBackend:
36+
def __init__(self):
37+
self.root_path = None
38+
self.info_path = None
39+
self.kitti_infos = None
40+
self.image_idxes = None
41+
self.dt_annos = None
42+
43+
44+
BACKEND = SecondBackend()
45+
46+
def error_response(msg):
47+
response = {}
48+
response["status"] = "error"
49+
response["message"] = "[ERROR]" + msg
50+
print("[ERROR]" + msg)
51+
return response
52+
53+
54+
@app.route('/api/readinfo', methods=['POST'])
55+
def readinfo():
56+
global BACKEND
57+
instance = request.json
58+
root_path = Path(instance["root_path"])
59+
60+
61+
response = {"status": "normal"}
62+
if not (root_path / "training").exists():
63+
response["status"] = "error"
64+
response["message"] = "ERROR: your root path is incorrect."
65+
print("ERROR: your root path is incorrect.")
66+
return response
67+
BACKEND.root_path = root_path
68+
info_path = Path(instance["info_path"])
69+
70+
if not info_path.exists():
71+
response["status"] = "error"
72+
response["message"] = "ERROR: info file not exist."
73+
print("ERROR: your root path is incorrect.")
74+
return response
75+
BACKEND.info_path = info_path
76+
with open(info_path, 'rb') as f:
77+
kitti_infos = pickle.load(f)
78+
BACKEND.kitti_infos = kitti_infos
79+
BACKEND.image_idxes = [info["image_idx"] for info in kitti_infos]
80+
response["image_indexes"] = BACKEND.image_idxes
81+
82+
response = jsonify(results=[response])
83+
response.headers['Access-Control-Allow-Headers'] = '*'
84+
return response
85+
86+
@app.route('/api/read_detection', methods=['POST'])
87+
def read_detection():
88+
global BACKEND
89+
instance = request.json
90+
det_path = Path(instance["det_path"])
91+
response = {"status": "normal"}
92+
if BACKEND.root_path is None:
93+
return error_response("root path is not set")
94+
if BACKEND.kitti_infos is None:
95+
return error_response("kitti info is not loaded")
96+
97+
if Path(det_path).is_file():
98+
with open(det_path, "rb") as f:
99+
dt_annos = pickle.load(f)
100+
else:
101+
dt_annos = kitti.get_label_annos(det_path)
102+
BACKEND.dt_annos = dt_annos
103+
response = jsonify(results=[response])
104+
response.headers['Access-Control-Allow-Headers'] = '*'
105+
return response
106+
107+
108+
@app.route('/api/get_pointcloud', methods=['POST'])
109+
def get_pointcloud():
110+
global BACKEND
111+
instance = request.json
112+
response = {}
113+
if BACKEND.root_path is None:
114+
return error_response("root path is not set")
115+
if BACKEND.kitti_infos is None:
116+
return error_response("kitti info is not loaded")
117+
image_idx = instance["image_idx"]
118+
idx = BACKEND.image_idxes.index(image_idx)
119+
kitti_info = BACKEND.kitti_infos[idx]
120+
rect = kitti_info['calib/R0_rect']
121+
P2 = kitti_info['calib/P2']
122+
Trv2c = kitti_info['calib/Tr_velo_to_cam']
123+
if 'annos' in kitti_info:
124+
annos = kitti_info['annos']
125+
labels = annos['name']
126+
num_obj = len([n for n in annos['name'] if n != 'DontCare'])
127+
dims = annos['dimensions'][:num_obj]
128+
loc = annos['location'][:num_obj]
129+
rots = annos['rotation_y'][:num_obj]
130+
gt_boxes_camera = np.concatenate(
131+
[loc, dims, rots[..., np.newaxis]], axis=1)
132+
gt_boxes = box_np_ops.box_camera_to_lidar(
133+
gt_boxes_camera, rect, Trv2c)
134+
box_np_ops.change_box3d_center_(gt_boxes, src=[0.5, 0.5, 0], dst=[0.5, 0.5, 0.5])
135+
locs = gt_boxes[:, :3]
136+
dims = gt_boxes[:, 3:6]
137+
rots = np.concatenate([np.zeros([num_obj, 2], dtype=np.float32), -gt_boxes[:, 6:7]], axis=1)
138+
frontend_annos = {}
139+
response["locs"] = locs.tolist()
140+
response["dims"] = dims.tolist()
141+
response["rots"] = rots.tolist()
142+
response["labels"] = labels[:num_obj].tolist()
143+
144+
v_path = str(Path(BACKEND.root_path) / kitti_info['velodyne_path'])
145+
with open(v_path, 'rb') as f:
146+
pc_str = base64.encodestring(f.read())
147+
response["pointcloud"] = pc_str.decode("utf-8")
148+
if "with_det" in instance and instance["with_det"]:
149+
if BACKEND.dt_annos is None:
150+
return error_response("det anno is not loaded")
151+
dt_annos = BACKEND.dt_annos[idx]
152+
dims = dt_annos['dimensions']
153+
num_obj = dims.shape[0]
154+
loc = dt_annos['location']
155+
rots = dt_annos['rotation_y']
156+
labels = dt_annos['name']
157+
158+
dt_boxes_camera = np.concatenate(
159+
[loc, dims, rots[..., np.newaxis]], axis=1)
160+
dt_boxes = box_np_ops.box_camera_to_lidar(
161+
dt_boxes_camera, rect, Trv2c)
162+
box_np_ops.change_box3d_center_(dt_boxes, src=[0.5, 0.5, 0], dst=[0.5, 0.5, 0.5])
163+
locs = dt_boxes[:, :3]
164+
dims = dt_boxes[:, 3:6]
165+
rots = np.concatenate([np.zeros([num_obj, 2], dtype=np.float32), -dt_boxes[:, 6:7]], axis=1)
166+
response["dt_locs"] = locs.tolist()
167+
response["dt_dims"] = dims.tolist()
168+
response["dt_rots"] = rots.tolist()
169+
response["dt_labels"] = labels.tolist()
170+
response["dt_scores"] = dt_annos["score"].tolist()
171+
172+
# if "score" in annos:
173+
# response["score"] = score.tolist()
174+
response = jsonify(results=[response])
175+
response.headers['Access-Control-Allow-Headers'] = '*'
176+
print("send response!")
177+
return response
178+
179+
def main(port=16666):
180+
app.run(host='0.0.0.0', threaded=True, port=port)
181+
182+
if __name__ == '__main__':
183+
fire.Fire()

0 commit comments

Comments
 (0)