Libra-1995 commited on
Commit
c9fc56f
·
1 Parent(s): b71b188

feat: init repo

Browse files
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM hyzhou404/hugsim_server:0.1
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive \
4
+ TZ=UTC \
5
+ HF_HUB_ENABLE_HF_TRANSFER=1
6
+
7
+ ENV PATH="${HOME}/miniconda3/bin:${PATH}"
8
+ ARG PATH="${HOME}/miniconda3/bin:${PATH}"
9
+
10
+ WORKDIR /app
11
+
12
+ USER 1000
13
+
14
+ ENV PATH /app/miniconda/bin:$PATH
15
+
16
+ SHELL ["conda", "run","--no-capture-output", "-p","/app/env", "/bin/bash", "-c"]
17
+
18
+ COPY --chown=1000:1000 ./web_server.py /app/web_server.py
19
+ COPY --chown=1000:1000 ./docker/web_server_config/scene-0383-medium-00.yaml /app/docker/web_server_config/scene-0383-medium-00.yaml
20
+ COPY --chown=1000:1000 ./download_pre_datas.py /app/download_pre_datas.py
21
+
22
+ ENV TCNN_CUDA_ARCHITECTURES 75
23
+
24
+ ENV TORCH_CUDA_ARCH_LIST "7.5"
25
+
26
+ ENV http_proxy=
27
+ ENV https_proxy=
28
+
29
+ RUN ./.pixi/envs/default/bin/python /app/download_pre_datas.py
30
+
31
+ CMD ["./.pixi/envs/default/bin/python", "web_server.py"]
docker/web_server_config/scene-0383-medium-00.yaml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mode: medium_00
2
+ plan_list:
3
+ - - 2.0
4
+ - 30.0
5
+ - -0.3
6
+ - 0
7
+ - 0
8
+ - '2024_07_07_05_43_00'
9
+ - ConstantPlanner
10
+ - {}
11
+ iteration: 30000
12
+ sh_degree: 3
13
+ load_HD_map: false
14
+ white_background: false
15
+ data_device: cpu
16
+ data_type: nuscenes
17
+ start_euler:
18
+ - 0.0
19
+ - 0.0
20
+ - 0.0
21
+ start_ab:
22
+ - 0.0
23
+ - 0.0
24
+ start_velo: 1
25
+ start_steer: 0
26
+ target: 60
27
+ scene_name: scene-0383
28
+ affine: false
29
+ quiet: false
30
+ ignore_dynamic: false
31
+ unicycle: false
download_pre_datas.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import zipfile
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from huggingface_hub import snapshot_download
6
+
7
+ snapshot_download(repo_id='XDimLab/HUGSIM',revision='main',local_dir='/app/app_datas/PAMI2024/release/',local_dir_use_symlinks=False,allow_patterns=['3DRealCar/**'],repo_type='dataset')
8
+ snapshot_download(repo_id='XDimLab/HUGSIM',revision='main',local_dir='/app/app_datas/PAMI2024/release/ss',local_dir_use_symlinks=False,allow_patterns=['scenes/nuscenes/scene-0383.zip'], repo_type='dataset')
9
+
10
+ for file in Path("/app/app_datas/PAMI2024/release/ss/scenes/nuscenes").rglob("*.zip"):
11
+ file_path = file.as_posix()
12
+ dir_path = file.parent.as_posix()
13
+ with zipfile.ZipFile(file_path, 'r') as zip_ref:
14
+ zip_ref.extractall(dir_path)
web_server.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import pickle
4
+ import json
5
+ import threading
6
+ import time
7
+ import io
8
+ import enum
9
+ import hugsim_env
10
+ from collections import deque, OrderedDict
11
+ from datetime import datetime, timedelta
12
+ from typing import Any, Dict
13
+ sys.path.append(os.getcwd())
14
+
15
+ from fastapi import FastAPI, Body, Header, HTTPException, Depends
16
+ from fastapi.responses import HTMLResponse, Response
17
+ from omegaconf import OmegaConf
18
+ from huggingface_hub import HfApi, hf_hub_download
19
+ import open3d as o3d
20
+ import numpy as np
21
+ import gymnasium
22
+ import uvicorn
23
+
24
+ from sim.utils.sim_utils import traj2control, traj_transform_to_global
25
+ from sim.utils.score_calculator import hugsim_evaluate
26
+
27
+ IN_HUGGINGFACE_SPACE = os.getenv('IN_HUGGINGFACE_SPACE', 'false') == 'true'
28
+ STOP_SPACE_TIMEOUT = int(os.getenv('STOP_SPACE_TIMEOUT', '7200'))
29
+ HF_TOKEN = os.getenv('HF_TOKEN', None)
30
+ SPACE_PARAMS = json.loads(os.getenv('PARAMS', '{}'))
31
+
32
+ class GlobalState:
33
+ done = False
34
+
35
+
36
+ class SubmissionStatus(enum.Enum):
37
+ PENDING = 0
38
+ QUEUED = 1
39
+ PROCESSING = 2
40
+ SUCCESS = 3
41
+ FAILED = 4
42
+
43
+
44
+ def download_submission_info() -> Dict[str, Any]:
45
+ """
46
+ Download the submission info from Hugging Face Hub.
47
+ Args:
48
+ team_id (str): The team ID.
49
+ Returns:
50
+ Dict[str, Any]: The submission info.
51
+ """
52
+ submission_info_path = hf_hub_download(
53
+ repo_id=SPACE_PARAMS["competition_id"],
54
+ filename=f"submission_info/{SPACE_PARAMS['team_id']}.json",
55
+ repo_type="dataset",
56
+ token=HF_TOKEN
57
+ )
58
+ with open(submission_info_path, 'r') as f:
59
+ submission_info = json.load(f)
60
+
61
+ return submission_info
62
+
63
+
64
+ def upload_submission_info(user_submission_info: Dict[str, Any]):
65
+ user_submission_info_json = json.dumps(user_submission_info, indent=4)
66
+ user_submission_info_json_bytes = user_submission_info_json.encode("utf-8")
67
+ user_submission_info_json_buffer = io.BytesIO(user_submission_info_json_bytes)
68
+ api = HfApi(token=HF_TOKEN)
69
+ api.upload_file(
70
+ path_or_fileobj=user_submission_info_json_buffer,
71
+ path_in_repo=f"submission_info/{SPACE_PARAMS['team_id']}.json",
72
+ repo_id=SPACE_PARAMS["competition_id"],
73
+ repo_type="dataset",
74
+ )
75
+
76
+
77
+ def update_submission_status(status):
78
+ user_submission_info = download_submission_info()
79
+ for submission in user_submission_info["submissions"]:
80
+ if submission["submission_id"] == SPACE_PARAMS["submission_id"]:
81
+ submission["status"] = status
82
+ break
83
+ upload_submission_info(user_submission_info)
84
+
85
+
86
+ def auto_stop():
87
+ """
88
+ Automatically stop the server after a certain timeout.
89
+ """
90
+ stop_deadline = datetime.now() + timedelta(seconds=STOP_SPACE_TIMEOUT)
91
+ while 1:
92
+ if datetime.now() > stop_deadline:
93
+ update_submission_status(SubmissionStatus.FAILED.value)
94
+ break
95
+ if GlobalState.done:
96
+ update_submission_status(SubmissionStatus.SUCCESS.value)
97
+ break
98
+ time.sleep(60)
99
+
100
+ server_space_id = SPACE_PARAMS["server_space_id"]
101
+ client_space_id = SPACE_PARAMS["client_space_id"]
102
+ api = HfApi(token=HF_TOKEN)
103
+ api.delete_repo(
104
+ repo_id=server_space_id,
105
+ repo_type="space"
106
+ )
107
+ api.delete_repo(
108
+ repo_id=client_space_id,
109
+ repo_type="space"
110
+ )
111
+
112
+ if IN_HUGGINGFACE_SPACE:
113
+ # Start a thread to automatically stop the server after a timeout
114
+ auto_stop_thread = threading.Thread(target=auto_stop, daemon=True)
115
+ auto_stop_thread.start()
116
+ update_submission_status(SubmissionStatus.PROCESSING.value)
117
+
118
+
119
+ class FifoDict:
120
+ def __init__(self, max_size: int):
121
+ self.max_size = max_size
122
+ self._order_dict = OrderedDict()
123
+ self.locker = threading.Lock()
124
+
125
+ def push(self, key: str, value: Any):
126
+ with self.locker:
127
+ if key in self._order_dict:
128
+ self._order_dict.move_to_end(key)
129
+ return
130
+ if len(self._order_dict) >= self.max_size:
131
+ self._order_dict.popitem(last=False)
132
+ self._order_dict[key] = value
133
+
134
+ def get(self, key: str) -> Any:
135
+ return self._order_dict.get(key, None)
136
+
137
+
138
+ class EnvHandler:
139
+ def __init__(self, cfg, output):
140
+ self.cfg = cfg
141
+ self.output = output
142
+ self.env = gymnasium.make('hugsim_env/HUGSim-v0', cfg=cfg, output=output)
143
+ self._lock = threading.Lock()
144
+ self.reset_env()
145
+
146
+ def reset_env(self):
147
+ """
148
+ Reset the environment and initialize variables.
149
+ """
150
+ self._cnt = 0
151
+ self._done = False
152
+ self._save_data = {'type': 'closeloop', 'frames': []}
153
+ self._obs, self._info = self.env.reset()
154
+ self._log_list = deque(maxlen=100)
155
+ self._log("Environment reset complete.")
156
+
157
+ def get_current_state(self):
158
+ """
159
+ Get the current state of the environment.
160
+ """
161
+ return {
162
+ "obs": self._obs,
163
+ "info": self._info,
164
+ }
165
+
166
+ @property
167
+ def has_done(self) -> bool:
168
+ """
169
+ Check if the episode is done.
170
+ Returns:
171
+ bool: True if the episode is done, False otherwise.
172
+ """
173
+ return self._done
174
+
175
+ @property
176
+ def log_list(self) -> deque:
177
+ """
178
+ Get the log list.
179
+ Returns:
180
+ deque: The log list containing recent log messages.
181
+ """
182
+ return self._log_list
183
+
184
+ def execute_action(self, plan_traj: np.ndarray) -> bool:
185
+ """
186
+ Execute the action based on the planned trajectory.
187
+ Args:
188
+ plan_traj (Any): The planned trajectory to follow.
189
+ Returns:
190
+ bool: True if the episode is done, False otherwise.
191
+ """
192
+ acc, steer_rate = traj2control(plan_traj, self._info)
193
+ action = {'acc': acc, 'steer_rate': steer_rate}
194
+ self._log("Executing action:", action)
195
+
196
+ self._obs, _, terminated, truncated, self._info = self.env.step(action)
197
+ self._cnt += 1
198
+ self._done = terminated or truncated or self._cnt > 400
199
+
200
+ imu_plan_traj = plan_traj[:, [1, 0]]
201
+ imu_plan_traj[:, 1] *= -1
202
+ global_traj = traj_transform_to_global(imu_plan_traj, self._info['ego_box'])
203
+ self._save_data['frames'].append({
204
+ 'time_stamp': self._info['timestamp'],
205
+ 'is_key_frame': True,
206
+ 'ego_box': self._info['ego_box'],
207
+ 'obj_boxes': self._info['obj_boxes'],
208
+ 'obj_names': ['car' for _ in self._info['obj_boxes']],
209
+ 'planned_traj': {
210
+ 'traj': global_traj,
211
+ 'timestep': 0.5
212
+ },
213
+ 'collision': self._info['collision'],
214
+ 'rc': self._info['rc']
215
+ })
216
+
217
+ if not self._done:
218
+ return False
219
+
220
+ with open(os.path.join(self.output, 'data.pkl'), 'wb') as wf:
221
+ pickle.dump([self._save_data], wf)
222
+
223
+ ground_xyz = np.asarray(o3d.io.read_point_cloud(os.path.join(output, 'ground.ply')).points)
224
+ scene_xyz = np.asarray(o3d.io.read_point_cloud(os.path.join(output, 'scene.ply')).points)
225
+ results = hugsim_evaluate([self._save_data], ground_xyz, scene_xyz)
226
+ with open(os.path.join(output, 'eval.json'), 'w') as f:
227
+ json.dump(results, f)
228
+
229
+ self._log("Evaluation results saved.")
230
+ return True
231
+
232
+ def _log(self, *messages):
233
+ log_message = f"[{str(datetime.now())}]" + " ".join([str(msg) for msg in messages]) + "\n"
234
+ with self._lock:
235
+ self._log_list.append(log_message)
236
+
237
+
238
+ class WebServer:
239
+ def __init__(self, env_handler: EnvHandler, auth_token: str):
240
+ self.env_handler = env_handler
241
+ self.auth_token = auth_token
242
+ self._init_app()
243
+ self._result_dict= FifoDict(max_size=30)
244
+
245
+ def run(self):
246
+ uvicorn.run(self._app, host="0.0.0.0", port=7860, workers=1)
247
+
248
+ def _reset_endpoint(self):
249
+ self.env_handler.reset_env()
250
+ return {"success": True}
251
+
252
+ def _get_current_state_endpoint(self):
253
+ state = self.env_handler.get_current_state()
254
+ return Response(content=pickle.dumps(state), media_type="application/octet-stream")
255
+
256
+ def _load_numpy_ndarray_json_str(self, json_str: str) -> np.ndarray:
257
+ """
258
+ Load a numpy ndarray from a JSON string.
259
+ """
260
+ data = json.loads(json_str)
261
+ return np.array(data["data"], dtype=data["dtype"]).reshape(data["shape"])
262
+
263
+ def _execute_action_endpoint(
264
+ self,
265
+ plan_traj: str = Body(..., embed=True),
266
+ transaction_id: str = Body(..., embed=True),
267
+ ):
268
+ cache_result = self._result_dict.get(transaction_id)
269
+ if cache_result is not None:
270
+ return Response(content=cache_result, media_type="application/octet-stream")
271
+
272
+ if self.env_handler.has_done:
273
+ result = pickle.dumps({"done": done, "state": None})
274
+ self._result_dict.push(transaction_id, result)
275
+ return Response(content=result, media_type="application/octet-stream")
276
+
277
+ plan_traj = self._load_numpy_ndarray_json_str(plan_traj)
278
+ done = self.env_handler.execute_action(plan_traj)
279
+ GlobalState.done = done
280
+ if done:
281
+ result = pickle.dumps({"done": done, "state": None})
282
+ self._result_dict.push(transaction_id, result)
283
+ return Response(content=result, media_type="application/octet-stream")
284
+
285
+ state = self.env_handler.get_current_state()
286
+ result = pickle.dumps({"done": done, "state": state})
287
+ self._result_dict.push(transaction_id, result)
288
+ return Response(content=result, media_type="application/octet-stream")
289
+
290
+ def _main_page_endpoint(self):
291
+ log_str = "\n".join(self.env_handler.log_list)
292
+ html_content = f"""
293
+ <html><body><pre>{log_str}</pre></body></html>
294
+ <script>
295
+ setTimeout(function() {{
296
+ window.location.reload();
297
+ }}, 5000);
298
+ </script>
299
+ """
300
+ return HTMLResponse(content=html_content)
301
+
302
+ def _verify_token(self, auth_token: str = Header(...)):
303
+ if self.auth_token and self.auth_token != auth_token:
304
+ raise HTTPException(status_code=401)
305
+
306
+ def _init_app(self):
307
+ self._app = FastAPI()
308
+ self._app.add_api_route("/reset", self._reset_endpoint, methods=["POST"], dependencies=[Depends(self._verify_token)])
309
+ self._app.add_api_route("/get_current_state", self._get_current_state_endpoint, methods=["GET"], dependencies=[Depends(self._verify_token)])
310
+ self._app.add_api_route("/execute_action", self._execute_action_endpoint, methods=["POST"], dependencies=[Depends(self._verify_token)])
311
+ self._app.add_api_route("/", self._main_page_endpoint, methods=["GET"])
312
+
313
+
314
+ # TODO: add code to update submission info
315
+ if __name__ == "__main__":
316
+ # Using fixed paths for web server
317
+ ad = "uniad"
318
+ base_path = os.path.join(os.path.dirname(__file__), 'docker', "web_server_config", 'nuscenes_base.yaml')
319
+ # unknown config
320
+ scenario_path = os.path.join(os.path.dirname(__file__), 'docker', "web_server_config", 'scene-0383-medium-00.yaml')
321
+ camera_path = os.path.join(os.path.dirname(__file__), 'docker', "web_server_config", 'nuscenes_camera.yaml')
322
+ kinematic_path = os.path.join(os.path.dirname(__file__), 'docker', "web_server_config", 'kinematic.yaml')
323
+
324
+ scenario_config = OmegaConf.load(scenario_path)
325
+ base_config = OmegaConf.load(base_path)
326
+ camera_config = OmegaConf.load(camera_path)
327
+ kinematic_config = OmegaConf.load(kinematic_path)
328
+ cfg = OmegaConf.merge(
329
+ {"scenario": scenario_config},
330
+ {"base": base_config},
331
+ {"camera": camera_config},
332
+ {"kinematic": kinematic_config}
333
+ )
334
+ cfg.base.output_dir = cfg.base.output_dir + ad
335
+
336
+ model_path = os.path.join(cfg.base.model_base, cfg.scenario.scene_name)
337
+ model_config = OmegaConf.load(os.path.join(model_path, 'cfg.yaml'))
338
+ model_config.update({"model_path": "/app/app_datas/PAMI2024/release/ss/scenes/nuscenes/scene-0383"})
339
+ cfg.update(model_config)
340
+
341
+ output = os.path.join(cfg.base.output_dir, cfg.scenario.scene_name+"_"+cfg.scenario.mode)
342
+ os.makedirs(output, exist_ok=True)
343
+
344
+ print("Output directory:", output)
345
+ env_handler = EnvHandler(cfg, output)
346
+ print("Environment handler initialized.")
347
+ web_server = WebServer(env_handler, auth_token=os.getenv('HUGSIM_AUTH_TOKEN'))
348
+ print("Web server initialized.")
349
+ web_server.run()