ground truth action only press once!
Browse files
gradio-web/gradio_callbacks.py
CHANGED
|
@@ -855,17 +855,25 @@ def _is_valid_coords_text(coords_text: str) -> bool:
|
|
| 855 |
return True
|
| 856 |
|
| 857 |
|
| 858 |
-
def on_option_select(uid, option_value, coords_str=None):
|
| 859 |
"""
|
| 860 |
处理选项选择事件
|
| 861 |
"""
|
| 862 |
default_msg = _ui_text("coords", "not_needed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
|
| 864 |
if option_value is None:
|
| 865 |
LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
|
| 866 |
session = get_session(uid) if uid else None
|
| 867 |
base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW) if session else _LIVE_OBS_UPDATE_SKIP
|
| 868 |
-
return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log()
|
| 869 |
|
| 870 |
# 更新session活动时间(选择选项操作)
|
| 871 |
if uid:
|
|
@@ -874,7 +882,7 @@ def on_option_select(uid, option_value, coords_str=None):
|
|
| 874 |
session = get_session(uid)
|
| 875 |
if not session:
|
| 876 |
LOGGER.warning("on_option_select: missing session uid=%s", _uid_for_log(uid))
|
| 877 |
-
return default_msg, _live_obs_update(interactive=False), format_log_markdown(_ui_text("log", "session_error"))
|
| 878 |
|
| 879 |
option_idx = _parse_option_idx(option_value)
|
| 880 |
base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
|
|
@@ -891,13 +899,14 @@ def on_option_select(uid, option_value, coords_str=None):
|
|
| 891 |
_ui_text("coords", "select_keypoint"),
|
| 892 |
_live_obs_update(value=base_img, interactive=True, waiting_for_keypoint=True),
|
| 893 |
_keypoint_selection_log(),
|
|
|
|
| 894 |
)
|
| 895 |
|
| 896 |
LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
|
| 897 |
-
return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log()
|
| 898 |
|
| 899 |
|
| 900 |
-
def on_reference_action(uid):
|
| 901 |
"""
|
| 902 |
自动获取并回填当前步参考 action + 像素坐标(不执行)。
|
| 903 |
"""
|
|
@@ -912,6 +921,7 @@ def on_reference_action(uid):
|
|
| 912 |
gr.update(),
|
| 913 |
_ui_text("coords", "not_needed"),
|
| 914 |
format_log_markdown(_ui_text("log", "session_error")),
|
|
|
|
| 915 |
)
|
| 916 |
|
| 917 |
LOGGER.info("on_reference_action uid=%s env=%s", _uid_for_log(uid), getattr(session, "env_id", None))
|
|
@@ -926,6 +936,7 @@ def on_reference_action(uid):
|
|
| 926 |
gr.update(),
|
| 927 |
gr.update(),
|
| 928 |
format_log_markdown(_ui_text("log", "reference_action_error", error=exc)),
|
|
|
|
| 929 |
)
|
| 930 |
|
| 931 |
if not isinstance(reference, dict) or not reference.get("ok", False):
|
|
@@ -937,14 +948,17 @@ def on_reference_action(uid):
|
|
| 937 |
gr.update(),
|
| 938 |
gr.update(),
|
| 939 |
format_log_markdown(_ui_text("log", "reference_action_status", message=message)),
|
|
|
|
| 940 |
)
|
| 941 |
|
| 942 |
option_idx = reference.get("option_idx")
|
|
|
|
| 943 |
option_label = str(reference.get("option_label", "")).strip()
|
| 944 |
option_action = str(reference.get("option_action", "")).strip()
|
| 945 |
option_action = get_ui_action_text(getattr(session, "env_id", None), option_action)
|
| 946 |
need_coords = bool(reference.get("need_coords", False))
|
| 947 |
coords_xy = reference.get("coords_xy")
|
|
|
|
| 948 |
|
| 949 |
updated_img = current_img
|
| 950 |
coords_text = _ui_text("coords", "not_needed")
|
|
@@ -980,6 +994,7 @@ def on_reference_action(uid):
|
|
| 980 |
gr.update(value=option_idx),
|
| 981 |
coords_text,
|
| 982 |
format_log_markdown(log_text),
|
|
|
|
| 983 |
)
|
| 984 |
|
| 985 |
|
|
|
|
| 855 |
return True
|
| 856 |
|
| 857 |
|
| 858 |
+
def on_option_select(uid, option_value, coords_str=None, suppress_next_option_change=False):
|
| 859 |
"""
|
| 860 |
处理选项选择事件
|
| 861 |
"""
|
| 862 |
default_msg = _ui_text("coords", "not_needed")
|
| 863 |
+
|
| 864 |
+
if suppress_next_option_change:
|
| 865 |
+
LOGGER.debug(
|
| 866 |
+
"on_option_select suppressed uid=%s option=%s",
|
| 867 |
+
_uid_for_log(uid),
|
| 868 |
+
option_value,
|
| 869 |
+
)
|
| 870 |
+
return gr.update(), gr.update(), gr.update(), False
|
| 871 |
|
| 872 |
if option_value is None:
|
| 873 |
LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
|
| 874 |
session = get_session(uid) if uid else None
|
| 875 |
base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW) if session else _LIVE_OBS_UPDATE_SKIP
|
| 876 |
+
return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log(), False
|
| 877 |
|
| 878 |
# 更新session活动时间(选择选项操作)
|
| 879 |
if uid:
|
|
|
|
| 882 |
session = get_session(uid)
|
| 883 |
if not session:
|
| 884 |
LOGGER.warning("on_option_select: missing session uid=%s", _uid_for_log(uid))
|
| 885 |
+
return default_msg, _live_obs_update(interactive=False), format_log_markdown(_ui_text("log", "session_error")), False
|
| 886 |
|
| 887 |
option_idx = _parse_option_idx(option_value)
|
| 888 |
base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
|
|
|
|
| 899 |
_ui_text("coords", "select_keypoint"),
|
| 900 |
_live_obs_update(value=base_img, interactive=True, waiting_for_keypoint=True),
|
| 901 |
_keypoint_selection_log(),
|
| 902 |
+
False,
|
| 903 |
)
|
| 904 |
|
| 905 |
LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
|
| 906 |
+
return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log(), False
|
| 907 |
|
| 908 |
|
| 909 |
+
def on_reference_action(uid, current_option_value=None):
|
| 910 |
"""
|
| 911 |
自动获取并回填当前步参考 action + 像素坐标(不执行)。
|
| 912 |
"""
|
|
|
|
| 921 |
gr.update(),
|
| 922 |
_ui_text("coords", "not_needed"),
|
| 923 |
format_log_markdown(_ui_text("log", "session_error")),
|
| 924 |
+
False,
|
| 925 |
)
|
| 926 |
|
| 927 |
LOGGER.info("on_reference_action uid=%s env=%s", _uid_for_log(uid), getattr(session, "env_id", None))
|
|
|
|
| 936 |
gr.update(),
|
| 937 |
gr.update(),
|
| 938 |
format_log_markdown(_ui_text("log", "reference_action_error", error=exc)),
|
| 939 |
+
False,
|
| 940 |
)
|
| 941 |
|
| 942 |
if not isinstance(reference, dict) or not reference.get("ok", False):
|
|
|
|
| 948 |
gr.update(),
|
| 949 |
gr.update(),
|
| 950 |
format_log_markdown(_ui_text("log", "reference_action_status", message=message)),
|
| 951 |
+
False,
|
| 952 |
)
|
| 953 |
|
| 954 |
option_idx = reference.get("option_idx")
|
| 955 |
+
current_option_idx = _parse_option_idx(current_option_value)
|
| 956 |
option_label = str(reference.get("option_label", "")).strip()
|
| 957 |
option_action = str(reference.get("option_action", "")).strip()
|
| 958 |
option_action = get_ui_action_text(getattr(session, "env_id", None), option_action)
|
| 959 |
need_coords = bool(reference.get("need_coords", False))
|
| 960 |
coords_xy = reference.get("coords_xy")
|
| 961 |
+
suppress_next_option_change = option_idx != current_option_idx
|
| 962 |
|
| 963 |
updated_img = current_img
|
| 964 |
coords_text = _ui_text("coords", "not_needed")
|
|
|
|
| 994 |
gr.update(value=option_idx),
|
| 995 |
coords_text,
|
| 996 |
format_log_markdown(log_text),
|
| 997 |
+
suppress_next_option_change,
|
| 998 |
)
|
| 999 |
|
| 1000 |
|
gradio-web/test/test_reference_action_callbacks.py
CHANGED
|
@@ -48,7 +48,7 @@ def test_on_reference_action_success_updates_option_and_coords(monkeypatch, relo
|
|
| 48 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 49 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 50 |
|
| 51 |
-
img_update, option_update, coords_text, log_html = callbacks.on_reference_action("uid-1")
|
| 52 |
|
| 53 |
assert img_update.get("__type__") == "update"
|
| 54 |
assert isinstance(img_update.get("value"), Image.Image)
|
|
@@ -56,6 +56,7 @@ def test_on_reference_action_success_updates_option_and_coords(monkeypatch, relo
|
|
| 56 |
assert img_update.get("elem_classes") == config.get_live_obs_elem_classes()
|
| 57 |
assert option_update.get("value") == 2
|
| 58 |
assert coords_text == "5, 6"
|
|
|
|
| 59 |
expected_log = config.UI_TEXT["log"]["reference_action_message_with_coords"].format(
|
| 60 |
option_label="c",
|
| 61 |
option_action="press the button",
|
|
@@ -71,13 +72,14 @@ def test_on_reference_action_session_missing(monkeypatch, reload_module):
|
|
| 71 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 72 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
|
| 73 |
|
| 74 |
-
img_update, option_update, coords_text, log_html = callbacks.on_reference_action("uid-missing")
|
| 75 |
|
| 76 |
assert img_update.get("__type__") == "update"
|
| 77 |
assert img_update.get("value") is None
|
| 78 |
assert option_update.get("__type__") == "update"
|
| 79 |
assert coords_text == config.UI_TEXT["coords"]["not_needed"]
|
| 80 |
assert log_html == config.UI_TEXT["log"]["session_error"]
|
|
|
|
| 81 |
|
| 82 |
|
| 83 |
def test_on_reference_action_error_message_from_reference(monkeypatch, reload_module):
|
|
@@ -88,8 +90,33 @@ def test_on_reference_action_error_message_from_reference(monkeypatch, reload_mo
|
|
| 88 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 89 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 90 |
|
| 91 |
-
_img, _opt, _coords, log_html = callbacks.on_reference_action("uid-1")
|
| 92 |
assert log_html == config.UI_TEXT["log"]["reference_action_status"].format(message="bad ref")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
|
| 95 |
def test_on_option_select_resets_to_keypoint_wait_state_for_point_action(monkeypatch, reload_module):
|
|
@@ -100,12 +127,24 @@ def test_on_option_select_resets_to_keypoint_wait_state_for_point_action(monkeyp
|
|
| 100 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 101 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 102 |
|
| 103 |
-
coords_text, img_update, log_text = callbacks.on_option_select("uid-1", 0, "12, 34")
|
| 104 |
|
| 105 |
assert coords_text == config.UI_TEXT["coords"]["select_keypoint"]
|
| 106 |
assert img_update.get("interactive") is True
|
| 107 |
assert img_update.get("elem_classes") == config.get_live_obs_elem_classes(waiting_for_keypoint=True)
|
| 108 |
assert log_text == config.UI_TEXT["log"]["keypoint_selection_prompt"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
|
| 111 |
def test_on_map_click_clears_wait_state_and_restores_action_prompt(monkeypatch, reload_module):
|
|
@@ -147,10 +186,11 @@ def test_on_reference_action_uses_configured_action_text_override(monkeypatch, r
|
|
| 147 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 148 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 149 |
|
| 150 |
-
_img, _option_update, coords_text, log_html = callbacks.on_reference_action("uid-1")
|
| 151 |
|
| 152 |
assert coords_text == config.UI_TEXT["coords"]["not_needed"]
|
| 153 |
assert log_html == config.UI_TEXT["log"]["reference_action_message"].format(
|
| 154 |
option_label="a",
|
| 155 |
option_action="move forward↓",
|
| 156 |
)
|
|
|
|
|
|
| 48 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 49 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 50 |
|
| 51 |
+
img_update, option_update, coords_text, log_html, suppress_flag = callbacks.on_reference_action("uid-1", None)
|
| 52 |
|
| 53 |
assert img_update.get("__type__") == "update"
|
| 54 |
assert isinstance(img_update.get("value"), Image.Image)
|
|
|
|
| 56 |
assert img_update.get("elem_classes") == config.get_live_obs_elem_classes()
|
| 57 |
assert option_update.get("value") == 2
|
| 58 |
assert coords_text == "5, 6"
|
| 59 |
+
assert suppress_flag is True
|
| 60 |
expected_log = config.UI_TEXT["log"]["reference_action_message_with_coords"].format(
|
| 61 |
option_label="c",
|
| 62 |
option_action="press the button",
|
|
|
|
| 72 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 73 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
|
| 74 |
|
| 75 |
+
img_update, option_update, coords_text, log_html, suppress_flag = callbacks.on_reference_action("uid-missing", None)
|
| 76 |
|
| 77 |
assert img_update.get("__type__") == "update"
|
| 78 |
assert img_update.get("value") is None
|
| 79 |
assert option_update.get("__type__") == "update"
|
| 80 |
assert coords_text == config.UI_TEXT["coords"]["not_needed"]
|
| 81 |
assert log_html == config.UI_TEXT["log"]["session_error"]
|
| 82 |
+
assert suppress_flag is False
|
| 83 |
|
| 84 |
|
| 85 |
def test_on_reference_action_error_message_from_reference(monkeypatch, reload_module):
|
|
|
|
| 90 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 91 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 92 |
|
| 93 |
+
_img, _opt, _coords, log_html, suppress_flag = callbacks.on_reference_action("uid-1", None)
|
| 94 |
assert log_html == config.UI_TEXT["log"]["reference_action_status"].format(message="bad ref")
|
| 95 |
+
assert suppress_flag is False
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def test_on_reference_action_same_selected_option_does_not_set_suppression(monkeypatch, reload_module):
|
| 99 |
+
callbacks = reload_module("gradio_callbacks")
|
| 100 |
+
|
| 101 |
+
session = _FakeSession(
|
| 102 |
+
{
|
| 103 |
+
"ok": True,
|
| 104 |
+
"option_idx": 0,
|
| 105 |
+
"option_label": "a",
|
| 106 |
+
"option_action": "pick up the cube",
|
| 107 |
+
"need_coords": True,
|
| 108 |
+
"coords_xy": [3, 4],
|
| 109 |
+
"message": "ok",
|
| 110 |
+
}
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 114 |
+
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 115 |
+
|
| 116 |
+
_img, _option_update, coords_text, _log_html, suppress_flag = callbacks.on_reference_action("uid-1", 0)
|
| 117 |
+
|
| 118 |
+
assert coords_text == "3, 4"
|
| 119 |
+
assert suppress_flag is False
|
| 120 |
|
| 121 |
|
| 122 |
def test_on_option_select_resets_to_keypoint_wait_state_for_point_action(monkeypatch, reload_module):
|
|
|
|
| 127 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 128 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 129 |
|
| 130 |
+
coords_text, img_update, log_text, suppress_flag = callbacks.on_option_select("uid-1", 0, "12, 34", False)
|
| 131 |
|
| 132 |
assert coords_text == config.UI_TEXT["coords"]["select_keypoint"]
|
| 133 |
assert img_update.get("interactive") is True
|
| 134 |
assert img_update.get("elem_classes") == config.get_live_obs_elem_classes(waiting_for_keypoint=True)
|
| 135 |
assert log_text == config.UI_TEXT["log"]["keypoint_selection_prompt"]
|
| 136 |
+
assert suppress_flag is False
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def test_on_option_select_suppresses_programmatic_reference_change(reload_module):
|
| 140 |
+
callbacks = reload_module("gradio_callbacks")
|
| 141 |
+
|
| 142 |
+
coords_update, img_update, log_update, suppress_flag = callbacks.on_option_select("uid-1", 0, "12, 34", True)
|
| 143 |
+
|
| 144 |
+
assert coords_update.get("__type__") == "update"
|
| 145 |
+
assert img_update.get("__type__") == "update"
|
| 146 |
+
assert log_update.get("__type__") == "update"
|
| 147 |
+
assert suppress_flag is False
|
| 148 |
|
| 149 |
|
| 150 |
def test_on_map_click_clears_wait_state_and_restores_action_prompt(monkeypatch, reload_module):
|
|
|
|
| 186 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 187 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
|
| 188 |
|
| 189 |
+
_img, _option_update, coords_text, log_html, suppress_flag = callbacks.on_reference_action("uid-1", None)
|
| 190 |
|
| 191 |
assert coords_text == config.UI_TEXT["coords"]["not_needed"]
|
| 192 |
assert log_html == config.UI_TEXT["log"]["reference_action_message"].format(
|
| 193 |
option_label="a",
|
| 194 |
option_action="move forward↓",
|
| 195 |
)
|
| 196 |
+
assert suppress_flag is True
|
gradio-web/test/test_ui_phase_machine_runtime_e2e.py
CHANGED
|
@@ -1415,6 +1415,190 @@ def test_keypoint_wait_state_pulses_live_obs_and_updates_system_log(monkeypatch)
|
|
| 1415 |
demo.close()
|
| 1416 |
|
| 1417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1418 |
def test_live_obs_client_resize_fills_width_and_keeps_click_mapping(monkeypatch):
|
| 1419 |
callbacks = importlib.reload(importlib.import_module("gradio_callbacks"))
|
| 1420 |
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
|
|
|
| 1415 |
demo.close()
|
| 1416 |
|
| 1417 |
|
| 1418 |
+
def test_reference_action_single_click_applies_coords_without_wait_state(monkeypatch):
|
| 1419 |
+
config_module = importlib.reload(importlib.import_module("config"))
|
| 1420 |
+
callbacks = importlib.reload(importlib.import_module("gradio_callbacks"))
|
| 1421 |
+
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 1422 |
+
|
| 1423 |
+
fake_obs = np.zeros((24, 48, 3), dtype=np.uint8)
|
| 1424 |
+
fake_obs[:, :] = [15, 20, 25]
|
| 1425 |
+
fake_obs_img = Image.fromarray(fake_obs)
|
| 1426 |
+
|
| 1427 |
+
class FakeSession:
|
| 1428 |
+
env_id = "BinFill"
|
| 1429 |
+
raw_solve_options = [
|
| 1430 |
+
{"label": "a", "action": "pick the left cube", "available": [object()]},
|
| 1431 |
+
{"label": "b", "action": "pick the right cube", "available": [object()]},
|
| 1432 |
+
]
|
| 1433 |
+
available_options = [
|
| 1434 |
+
("a. pick the left cube", 0),
|
| 1435 |
+
("b. pick the right cube", 1),
|
| 1436 |
+
]
|
| 1437 |
+
|
| 1438 |
+
def get_pil_image(self, use_segmented=False):
|
| 1439 |
+
_ = use_segmented
|
| 1440 |
+
return fake_obs_img.copy()
|
| 1441 |
+
|
| 1442 |
+
def get_reference_action(self):
|
| 1443 |
+
return {
|
| 1444 |
+
"ok": True,
|
| 1445 |
+
"option_idx": 0,
|
| 1446 |
+
"option_label": "a",
|
| 1447 |
+
"option_action": "pick the left cube",
|
| 1448 |
+
"need_coords": True,
|
| 1449 |
+
"coords_xy": [5, 6],
|
| 1450 |
+
"message": "ok",
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
def fake_init_app(_request=None):
|
| 1454 |
+
return (
|
| 1455 |
+
"uid-reference-action",
|
| 1456 |
+
gr.update(visible=True), # main_interface
|
| 1457 |
+
gr.update(
|
| 1458 |
+
value=fake_obs_img.copy(),
|
| 1459 |
+
interactive=False,
|
| 1460 |
+
elem_classes=config_module.get_live_obs_elem_classes(),
|
| 1461 |
+
), # img_display
|
| 1462 |
+
config_module.UI_TEXT["log"]["action_selection_prompt"], # log_output
|
| 1463 |
+
gr.update(
|
| 1464 |
+
choices=[
|
| 1465 |
+
("a. pick the left cube", 0),
|
| 1466 |
+
("b. pick the right cube", 1),
|
| 1467 |
+
],
|
| 1468 |
+
value=None,
|
| 1469 |
+
), # options_radio
|
| 1470 |
+
"goal", # goal_box
|
| 1471 |
+
gr.update(
|
| 1472 |
+
value=config_module.UI_TEXT["coords"]["not_needed"],
|
| 1473 |
+
visible=True,
|
| 1474 |
+
interactive=False,
|
| 1475 |
+
), # coords_box
|
| 1476 |
+
gr.update(value=None, visible=False), # video_display
|
| 1477 |
+
gr.update(visible=False, interactive=False), # watch_demo_video_btn
|
| 1478 |
+
"BinFill (Episode 1)", # task_info_box
|
| 1479 |
+
"Completed: 0", # progress_info_box
|
| 1480 |
+
gr.update(interactive=True), # restart_episode_btn
|
| 1481 |
+
gr.update(interactive=True), # next_task_btn
|
| 1482 |
+
gr.update(interactive=True), # exec_btn
|
| 1483 |
+
gr.update(visible=False), # video_phase_group
|
| 1484 |
+
gr.update(visible=True), # action_phase_group
|
| 1485 |
+
gr.update(visible=True), # control_panel_group
|
| 1486 |
+
gr.update(value="hint"), # task_hint_display
|
| 1487 |
+
gr.update(visible=False), # loading_overlay
|
| 1488 |
+
gr.update(interactive=True), # reference_action_btn
|
| 1489 |
+
)
|
| 1490 |
+
|
| 1491 |
+
monkeypatch.setattr(ui_layout, "init_app", fake_init_app)
|
| 1492 |
+
monkeypatch.setattr(callbacks, "get_session", lambda uid: FakeSession())
|
| 1493 |
+
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 1494 |
+
|
| 1495 |
+
demo = ui_layout.create_ui_blocks()
|
| 1496 |
+
|
| 1497 |
+
port = _free_port()
|
| 1498 |
+
host = "127.0.0.1"
|
| 1499 |
+
root_url = f"http://{host}:{port}/"
|
| 1500 |
+
|
| 1501 |
+
app = FastAPI(title="reference-action-single-click-test")
|
| 1502 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 1503 |
+
|
| 1504 |
+
config = uvicorn.Config(app, host=host, port=port, log_level="error")
|
| 1505 |
+
server = uvicorn.Server(config)
|
| 1506 |
+
thread = threading.Thread(target=server.run, daemon=True)
|
| 1507 |
+
thread.start()
|
| 1508 |
+
_wait_http_ready(root_url)
|
| 1509 |
+
|
| 1510 |
+
try:
|
| 1511 |
+
with sync_playwright() as p:
|
| 1512 |
+
browser = p.chromium.launch(headless=True)
|
| 1513 |
+
page = browser.new_page(viewport={"width": 1280, "height": 900})
|
| 1514 |
+
page.goto(root_url, wait_until="domcontentloaded")
|
| 1515 |
+
page.wait_for_selector("#main_interface_root", state="visible", timeout=15000)
|
| 1516 |
+
page.wait_for_selector("#live_obs img", timeout=15000)
|
| 1517 |
+
page.wait_for_selector("#reference_action_btn button, button#reference_action_btn", timeout=15000)
|
| 1518 |
+
|
| 1519 |
+
expected_reference_log = config_module.UI_TEXT["log"]["reference_action_message_with_coords"].format(
|
| 1520 |
+
option_label="a",
|
| 1521 |
+
option_action="pick the left cube",
|
| 1522 |
+
coords_text="5, 6",
|
| 1523 |
+
)
|
| 1524 |
+
page.locator("#reference_action_btn button, button#reference_action_btn").first.click()
|
| 1525 |
+
|
| 1526 |
+
page.wait_for_function(
|
| 1527 |
+
"""(state) => {
|
| 1528 |
+
const coordsRoot = document.getElementById('coords_box');
|
| 1529 |
+
const coordsField = coordsRoot?.querySelector('textarea, input');
|
| 1530 |
+
const logRoot = document.getElementById('log_output');
|
| 1531 |
+
const logField = logRoot?.querySelector('textarea, input');
|
| 1532 |
+
const liveObs = document.getElementById('live_obs');
|
| 1533 |
+
const checked = document.querySelector('#action_radio input[type="radio"]:checked');
|
| 1534 |
+
const coordsValue = coordsField ? coordsField.value.trim() : '';
|
| 1535 |
+
const logValue = logField ? logField.value.trim() : (logRoot?.textContent || '').trim();
|
| 1536 |
+
return (
|
| 1537 |
+
!!checked &&
|
| 1538 |
+
checked.value === state.checkedValue &&
|
| 1539 |
+
coordsValue === state.coordsValue &&
|
| 1540 |
+
logValue === state.logValue &&
|
| 1541 |
+
!!liveObs &&
|
| 1542 |
+
!liveObs.classList.contains(state.waitClass)
|
| 1543 |
+
);
|
| 1544 |
+
}""",
|
| 1545 |
+
arg={
|
| 1546 |
+
"checkedValue": "0",
|
| 1547 |
+
"coordsValue": "5, 6",
|
| 1548 |
+
"logValue": expected_reference_log,
|
| 1549 |
+
"waitClass": config_module.LIVE_OBS_KEYPOINT_WAIT_CLASS,
|
| 1550 |
+
},
|
| 1551 |
+
timeout=5000,
|
| 1552 |
+
)
|
| 1553 |
+
|
| 1554 |
+
classes_after_reference = _read_elem_classes(page, "live_obs")
|
| 1555 |
+
assert classes_after_reference is not None
|
| 1556 |
+
assert config_module.LIVE_OBS_KEYPOINT_WAIT_CLASS not in classes_after_reference
|
| 1557 |
+
assert _read_coords_box_value(page) == "5, 6"
|
| 1558 |
+
assert _read_log_output_value(page) == expected_reference_log
|
| 1559 |
+
|
| 1560 |
+
page.locator("#action_radio input[type='radio']").nth(1).check(force=True)
|
| 1561 |
+
page.wait_for_function(
|
| 1562 |
+
"""(state) => {
|
| 1563 |
+
const coordsRoot = document.getElementById('coords_box');
|
| 1564 |
+
const coordsField = coordsRoot?.querySelector('textarea, input');
|
| 1565 |
+
const logRoot = document.getElementById('log_output');
|
| 1566 |
+
const logField = logRoot?.querySelector('textarea, input');
|
| 1567 |
+
const liveObs = document.getElementById('live_obs');
|
| 1568 |
+
const checked = document.querySelector('#action_radio input[type="radio"]:checked');
|
| 1569 |
+
const coordsValue = coordsField ? coordsField.value.trim() : '';
|
| 1570 |
+
const logValue = logField ? logField.value.trim() : (logRoot?.textContent || '').trim();
|
| 1571 |
+
return (
|
| 1572 |
+
!!checked &&
|
| 1573 |
+
checked.value === state.checkedValue &&
|
| 1574 |
+
coordsValue === state.coordsValue &&
|
| 1575 |
+
logValue === state.logValue &&
|
| 1576 |
+
!!liveObs &&
|
| 1577 |
+
liveObs.classList.contains(state.waitClass)
|
| 1578 |
+
);
|
| 1579 |
+
}""",
|
| 1580 |
+
arg={
|
| 1581 |
+
"checkedValue": "1",
|
| 1582 |
+
"coordsValue": config_module.UI_TEXT["coords"]["select_keypoint"],
|
| 1583 |
+
"logValue": config_module.UI_TEXT["log"]["keypoint_selection_prompt"],
|
| 1584 |
+
"waitClass": config_module.LIVE_OBS_KEYPOINT_WAIT_CLASS,
|
| 1585 |
+
},
|
| 1586 |
+
timeout=5000,
|
| 1587 |
+
)
|
| 1588 |
+
|
| 1589 |
+
classes_after_manual_change = _read_elem_classes(page, "live_obs")
|
| 1590 |
+
assert classes_after_manual_change is not None
|
| 1591 |
+
assert config_module.LIVE_OBS_KEYPOINT_WAIT_CLASS in classes_after_manual_change
|
| 1592 |
+
assert _read_coords_box_value(page) == config_module.UI_TEXT["coords"]["select_keypoint"]
|
| 1593 |
+
assert _read_log_output_value(page) == config_module.UI_TEXT["log"]["keypoint_selection_prompt"]
|
| 1594 |
+
|
| 1595 |
+
browser.close()
|
| 1596 |
+
finally:
|
| 1597 |
+
server.should_exit = True
|
| 1598 |
+
thread.join(timeout=10)
|
| 1599 |
+
demo.close()
|
| 1600 |
+
|
| 1601 |
+
|
| 1602 |
def test_live_obs_client_resize_fills_width_and_keeps_click_mapping(monkeypatch):
|
| 1603 |
callbacks = importlib.reload(importlib.import_module("gradio_callbacks"))
|
| 1604 |
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
gradio-web/test/test_ui_text_config.py
CHANGED
|
@@ -43,12 +43,13 @@ def test_on_option_select_uses_configured_select_keypoint_and_log_messages(monke
|
|
| 43 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 44 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
|
| 45 |
|
| 46 |
-
coords_text, img_update, log_text = callbacks.on_option_select("uid-1", 0, None)
|
| 47 |
|
| 48 |
assert coords_text == "pick a point from config"
|
| 49 |
assert img_update.get("interactive") is True
|
| 50 |
assert callbacks.get_live_obs_elem_classes(waiting_for_keypoint=True) == img_update.get("elem_classes")
|
| 51 |
assert log_text == "custom log prompt from config"
|
|
|
|
| 52 |
|
| 53 |
|
| 54 |
def test_precheck_execute_inputs_uses_configured_before_execute_message(monkeypatch, reload_module):
|
|
@@ -112,11 +113,12 @@ def test_missing_session_paths_use_configured_session_error(monkeypatch, reload_
|
|
| 112 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 113 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
|
| 114 |
|
| 115 |
-
_img, _option_update, coords_text, log_text = callbacks.on_reference_action("uid-missing")
|
| 116 |
map_img, map_coords, map_log = callbacks.on_map_click("uid-missing", None, None)
|
| 117 |
|
| 118 |
assert coords_text == callbacks.UI_TEXT["coords"]["not_needed"]
|
| 119 |
assert log_text == "Session Error From Config"
|
|
|
|
| 120 |
assert map_img.get("__type__") == "update"
|
| 121 |
assert map_img.get("value") is None
|
| 122 |
assert map_coords == callbacks.UI_TEXT["coords"]["not_needed"]
|
|
|
|
| 43 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 44 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
|
| 45 |
|
| 46 |
+
coords_text, img_update, log_text, suppress_flag = callbacks.on_option_select("uid-1", 0, None, False)
|
| 47 |
|
| 48 |
assert coords_text == "pick a point from config"
|
| 49 |
assert img_update.get("interactive") is True
|
| 50 |
assert callbacks.get_live_obs_elem_classes(waiting_for_keypoint=True) == img_update.get("elem_classes")
|
| 51 |
assert log_text == "custom log prompt from config"
|
| 52 |
+
assert suppress_flag is False
|
| 53 |
|
| 54 |
|
| 55 |
def test_precheck_execute_inputs_uses_configured_before_execute_message(monkeypatch, reload_module):
|
|
|
|
| 113 |
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 114 |
monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
|
| 115 |
|
| 116 |
+
_img, _option_update, coords_text, log_text, suppress_flag = callbacks.on_reference_action("uid-missing", None)
|
| 117 |
map_img, map_coords, map_log = callbacks.on_map_click("uid-missing", None, None)
|
| 118 |
|
| 119 |
assert coords_text == callbacks.UI_TEXT["coords"]["not_needed"]
|
| 120 |
assert log_text == "Session Error From Config"
|
| 121 |
+
assert suppress_flag is False
|
| 122 |
assert map_img.get("__type__") == "update"
|
| 123 |
assert map_img.get("value") is None
|
| 124 |
assert map_coords == callbacks.UI_TEXT["coords"]["not_needed"]
|
gradio-web/ui_layout.py
CHANGED
|
@@ -586,6 +586,7 @@ def create_ui_blocks():
|
|
| 586 |
uid_state = gr.State(value=None)
|
| 587 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
| 588 |
current_task_env_state = gr.State(value=None)
|
|
|
|
| 589 |
live_obs_timer = gr.Timer(value=1.0 / LIVE_OBS_REFRESH_HZ, active=True)
|
| 590 |
|
| 591 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
|
@@ -925,8 +926,8 @@ def create_ui_blocks():
|
|
| 925 |
|
| 926 |
options_radio.change(
|
| 927 |
fn=on_option_select,
|
| 928 |
-
inputs=[uid_state, options_radio, coords_box],
|
| 929 |
-
outputs=[coords_box, img_display, log_output],
|
| 930 |
)
|
| 931 |
|
| 932 |
watch_demo_video_btn.click(
|
|
@@ -939,8 +940,8 @@ def create_ui_blocks():
|
|
| 939 |
|
| 940 |
reference_action_btn.click(
|
| 941 |
fn=on_reference_action,
|
| 942 |
-
inputs=[uid_state],
|
| 943 |
-
outputs=[img_display, options_radio, coords_box, log_output],
|
| 944 |
)
|
| 945 |
|
| 946 |
exec_btn.click(
|
|
|
|
| 586 |
uid_state = gr.State(value=None)
|
| 587 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
| 588 |
current_task_env_state = gr.State(value=None)
|
| 589 |
+
suppress_next_option_change_state = gr.State(value=False)
|
| 590 |
live_obs_timer = gr.Timer(value=1.0 / LIVE_OBS_REFRESH_HZ, active=True)
|
| 591 |
|
| 592 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
|
|
|
| 926 |
|
| 927 |
options_radio.change(
|
| 928 |
fn=on_option_select,
|
| 929 |
+
inputs=[uid_state, options_radio, coords_box, suppress_next_option_change_state],
|
| 930 |
+
outputs=[coords_box, img_display, log_output, suppress_next_option_change_state],
|
| 931 |
)
|
| 932 |
|
| 933 |
watch_demo_video_btn.click(
|
|
|
|
| 940 |
|
| 941 |
reference_action_btn.click(
|
| 942 |
fn=on_reference_action,
|
| 943 |
+
inputs=[uid_state, options_radio],
|
| 944 |
+
outputs=[img_display, options_radio, coords_box, log_output, suppress_next_option_change_state],
|
| 945 |
)
|
| 946 |
|
| 947 |
exec_btn.click(
|