obs fps control
Browse files- gradio-web/config.py +2 -0
- gradio-web/gradio_callbacks.py +30 -14
- gradio-web/ui_layout.py +2 -1
gradio-web/config.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
| 4 |
# --- Configuration ---
|
| 5 |
VIDEO_PLAYBACK_FPS = 30.0 # Frame rate for demonstration video playback
|
| 6 |
USE_SEGMENTED_VIEW = False # Set to True to use segmented view, False to use original image
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# 主界面两列宽度比例 (Keypoint Selection : Right Panel)
|
| 9 |
KEYPOINT_SELECTION_SCALE = 1
|
|
|
|
| 4 |
# --- Configuration ---
|
| 5 |
VIDEO_PLAYBACK_FPS = 30.0 # Frame rate for demonstration video playback
|
| 6 |
USE_SEGMENTED_VIEW = False # Set to True to use segmented view, False to use original image
|
| 7 |
+
LIVE_OBS_REFRESH_HZ = 20.0 # Live observation refresh frequency in Hz
|
| 8 |
+
KEYFRAME_DOWNSAMPLE_FACTOR = 1 # Keep 1 frame out of every N streamed frames
|
| 9 |
|
| 10 |
# 主界面两列宽度比例 (Keypoint Selection : Right Panel)
|
| 11 |
KEYPOINT_SELECTION_SCALE = 1
|
gradio-web/gradio_callbacks.py
CHANGED
|
@@ -31,7 +31,14 @@ from state_manager import (
|
|
| 31 |
)
|
| 32 |
from image_utils import draw_marker, save_video, concatenate_frames_horizontally
|
| 33 |
from user_manager import user_manager
|
| 34 |
-
from config import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
from process_session import ScrewPlanFailureError, ProcessSessionProxy
|
| 36 |
from note_content import get_task_hint
|
| 37 |
|
|
@@ -43,6 +50,15 @@ _LIVE_OBS_REFRESH_LOCK = threading.Lock()
|
|
| 43 |
LOGGER = logging.getLogger("robomme.callbacks")
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def _uid_for_log(uid):
|
| 47 |
if not uid:
|
| 48 |
return "<none>"
|
|
@@ -199,7 +215,7 @@ def switch_to_execute_phase(uid):
|
|
| 199 |
_LIVE_OBS_REFRESH[uid] = {
|
| 200 |
"frame_queue": queue.Queue(),
|
| 201 |
"last_base_count": base_count,
|
| 202 |
-
"
|
| 203 |
}
|
| 204 |
return (
|
| 205 |
gr.update(interactive=False), # options_radio
|
|
@@ -233,14 +249,14 @@ def _get_live_obs_refresh_state(uid, base_count=0):
|
|
| 233 |
_LIVE_OBS_REFRESH[uid] = {
|
| 234 |
"frame_queue": queue.Queue(),
|
| 235 |
"last_base_count": int(base_count),
|
| 236 |
-
"
|
| 237 |
}
|
| 238 |
return _LIVE_OBS_REFRESH[uid]
|
| 239 |
|
| 240 |
|
| 241 |
def _enqueue_live_obs_frames(uid, base_frames):
|
| 242 |
"""
|
| 243 |
-
Push newly appended base_frames into per-uid FIFO queue with
|
| 244 |
"""
|
| 245 |
if not uid:
|
| 246 |
return 0
|
|
@@ -255,22 +271,22 @@ def _enqueue_live_obs_frames(uid, base_frames):
|
|
| 255 |
with _LIVE_OBS_REFRESH_LOCK:
|
| 256 |
state["frame_queue"] = queue.Queue()
|
| 257 |
state["last_base_count"] = current_count
|
| 258 |
-
state["
|
| 259 |
return 0
|
| 260 |
|
| 261 |
if current_count <= last_count:
|
| 262 |
return frame_queue.qsize()
|
| 263 |
|
| 264 |
new_frames = frames[last_count:current_count]
|
| 265 |
-
|
| 266 |
for frame in new_frames:
|
| 267 |
-
if
|
| 268 |
frame_queue.put(frame)
|
| 269 |
-
|
| 270 |
|
| 271 |
with _LIVE_OBS_REFRESH_LOCK:
|
| 272 |
state["last_base_count"] = current_count
|
| 273 |
-
state["
|
| 274 |
return frame_queue.qsize()
|
| 275 |
|
| 276 |
|
|
@@ -285,8 +301,8 @@ def _wait_for_live_obs_queue_drain(uid, max_wait_sec=None, empty_grace_sec=0.2,
|
|
| 285 |
queue0 = state0.get("frame_queue") if state0 else None
|
| 286 |
initial_qsize = int(queue0.qsize()) if queue0 is not None else 0
|
| 287 |
if max_wait_sec is None:
|
| 288 |
-
#
|
| 289 |
-
max_wait_sec = min(30.0, max(2.0, initial_qsize * 0.
|
| 290 |
|
| 291 |
start = time.time()
|
| 292 |
empty_since = None
|
|
@@ -329,7 +345,7 @@ def _prepare_refresh_frame(frame):
|
|
| 329 |
def refresh_live_obs(uid, ui_phase):
|
| 330 |
"""
|
| 331 |
Poll latest cached frame during execute phase.
|
| 332 |
-
Updates live_obs
|
| 333 |
"""
|
| 334 |
if ui_phase != "execution_playback":
|
| 335 |
return gr.update()
|
|
@@ -1108,12 +1124,12 @@ def execute_step(uid, option_idx, coords_str):
|
|
| 1108 |
)
|
| 1109 |
|
| 1110 |
# Execute frames are produced in batch when execute_action returns from worker process.
|
| 1111 |
-
# Enqueue them now, then wait briefly for the
|
| 1112 |
_enqueue_live_obs_frames(uid, getattr(session, "base_frames", None))
|
| 1113 |
_wait_for_live_obs_queue_drain(uid)
|
| 1114 |
LOGGER.debug("execute_step playback drain complete uid=%s", _uid_for_log(uid))
|
| 1115 |
|
| 1116 |
-
# 注意:执行阶段画面由 live_obs 的
|
| 1117 |
|
| 1118 |
progress_update = gr.update() # 默认不更新 progress
|
| 1119 |
task_update = gr.update()
|
|
|
|
| 31 |
)
|
| 32 |
from image_utils import draw_marker, save_video, concatenate_frames_horizontally
|
| 33 |
from user_manager import user_manager
|
| 34 |
+
from config import (
|
| 35 |
+
EXECUTE_LIMIT_OFFSET,
|
| 36 |
+
KEYFRAME_DOWNSAMPLE_FACTOR,
|
| 37 |
+
LIVE_OBS_REFRESH_HZ,
|
| 38 |
+
SESSION_TIMEOUT,
|
| 39 |
+
USE_SEGMENTED_VIEW,
|
| 40 |
+
should_show_demo_video,
|
| 41 |
+
)
|
| 42 |
from process_session import ScrewPlanFailureError, ProcessSessionProxy
|
| 43 |
from note_content import get_task_hint
|
| 44 |
|
|
|
|
| 50 |
LOGGER = logging.getLogger("robomme.callbacks")
|
| 51 |
|
| 52 |
|
| 53 |
+
def _should_enqueue_sample(sample_index: int) -> bool:
|
| 54 |
+
factor = max(1, int(KEYFRAME_DOWNSAMPLE_FACTOR))
|
| 55 |
+
return sample_index % factor == 0
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _live_obs_refresh_interval_sec() -> float:
|
| 59 |
+
return 1.0 / max(float(LIVE_OBS_REFRESH_HZ), 1.0)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
def _uid_for_log(uid):
|
| 63 |
if not uid:
|
| 64 |
return "<none>"
|
|
|
|
| 215 |
_LIVE_OBS_REFRESH[uid] = {
|
| 216 |
"frame_queue": queue.Queue(),
|
| 217 |
"last_base_count": base_count,
|
| 218 |
+
"sample_index": 0,
|
| 219 |
}
|
| 220 |
return (
|
| 221 |
gr.update(interactive=False), # options_radio
|
|
|
|
| 249 |
_LIVE_OBS_REFRESH[uid] = {
|
| 250 |
"frame_queue": queue.Queue(),
|
| 251 |
"last_base_count": int(base_count),
|
| 252 |
+
"sample_index": 0,
|
| 253 |
}
|
| 254 |
return _LIVE_OBS_REFRESH[uid]
|
| 255 |
|
| 256 |
|
| 257 |
def _enqueue_live_obs_frames(uid, base_frames):
|
| 258 |
"""
|
| 259 |
+
Push newly appended base_frames into per-uid FIFO queue with configurable downsampling.
|
| 260 |
"""
|
| 261 |
if not uid:
|
| 262 |
return 0
|
|
|
|
| 271 |
with _LIVE_OBS_REFRESH_LOCK:
|
| 272 |
state["frame_queue"] = queue.Queue()
|
| 273 |
state["last_base_count"] = current_count
|
| 274 |
+
state["sample_index"] = 0
|
| 275 |
return 0
|
| 276 |
|
| 277 |
if current_count <= last_count:
|
| 278 |
return frame_queue.qsize()
|
| 279 |
|
| 280 |
new_frames = frames[last_count:current_count]
|
| 281 |
+
sample_index = int(state.get("sample_index", 0))
|
| 282 |
for frame in new_frames:
|
| 283 |
+
if _should_enqueue_sample(sample_index) and frame is not None:
|
| 284 |
frame_queue.put(frame)
|
| 285 |
+
sample_index += 1
|
| 286 |
|
| 287 |
with _LIVE_OBS_REFRESH_LOCK:
|
| 288 |
state["last_base_count"] = current_count
|
| 289 |
+
state["sample_index"] = sample_index
|
| 290 |
return frame_queue.qsize()
|
| 291 |
|
| 292 |
|
|
|
|
| 301 |
queue0 = state0.get("frame_queue") if state0 else None
|
| 302 |
initial_qsize = int(queue0.qsize()) if queue0 is not None else 0
|
| 303 |
if max_wait_sec is None:
|
| 304 |
+
# Timer-driven playback + small buffer, capped to keep UI responsive.
|
| 305 |
+
max_wait_sec = min(30.0, max(2.0, initial_qsize * (_live_obs_refresh_interval_sec() + 0.02) + 1.0))
|
| 306 |
|
| 307 |
start = time.time()
|
| 308 |
empty_since = None
|
|
|
|
| 345 |
def refresh_live_obs(uid, ui_phase):
|
| 346 |
"""
|
| 347 |
Poll latest cached frame during execute phase.
|
| 348 |
+
Updates live_obs using the configured gr.Timer interval.
|
| 349 |
"""
|
| 350 |
if ui_phase != "execution_playback":
|
| 351 |
return gr.update()
|
|
|
|
| 1124 |
)
|
| 1125 |
|
| 1126 |
# Execute frames are produced in batch when execute_action returns from worker process.
|
| 1127 |
+
# Enqueue them now, then wait briefly for the configured timer to drain FIFO playback.
|
| 1128 |
_enqueue_live_obs_frames(uid, getattr(session, "base_frames", None))
|
| 1129 |
_wait_for_live_obs_queue_drain(uid)
|
| 1130 |
LOGGER.debug("execute_step playback drain complete uid=%s", _uid_for_log(uid))
|
| 1131 |
|
| 1132 |
+
# 注意:执行阶段画面由 live_obs 的配置化轮询间隔刷新。
|
| 1133 |
|
| 1134 |
progress_update = gr.update() # 默认不更新 progress
|
| 1135 |
task_update = gr.update()
|
gradio-web/ui_layout.py
CHANGED
|
@@ -10,6 +10,7 @@ import gradio as gr
|
|
| 10 |
|
| 11 |
from config import (
|
| 12 |
CONTROL_PANEL_SCALE,
|
|
|
|
| 13 |
KEYPOINT_SELECTION_SCALE,
|
| 14 |
RIGHT_TOP_ACTION_SCALE,
|
| 15 |
RIGHT_TOP_LOG_SCALE,
|
|
@@ -159,7 +160,7 @@ def create_ui_blocks():
|
|
| 159 |
|
| 160 |
uid_state = gr.State(value=None)
|
| 161 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
| 162 |
-
live_obs_timer = gr.Timer(value=
|
| 163 |
|
| 164 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
| 165 |
progress_info_box = gr.Textbox(visible=False)
|
|
|
|
| 10 |
|
| 11 |
from config import (
|
| 12 |
CONTROL_PANEL_SCALE,
|
| 13 |
+
LIVE_OBS_REFRESH_HZ,
|
| 14 |
KEYPOINT_SELECTION_SCALE,
|
| 15 |
RIGHT_TOP_ACTION_SCALE,
|
| 16 |
RIGHT_TOP_LOG_SCALE,
|
|
|
|
| 160 |
|
| 161 |
uid_state = gr.State(value=None)
|
| 162 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
| 163 |
+
live_obs_timer = gr.Timer(value=1.0 / LIVE_OBS_REFRESH_HZ, active=True)
|
| 164 |
|
| 165 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
| 166 |
progress_info_box = gr.Textbox(visible=False)
|