hand / app.py
Neil67's picture
Adjust app layout
7ca8154 verified
Raw
History Blame Contribute Delete
5.83 kB
from pathlib import Path
import gradio as gr
from gesture_logic import FIRST, LAST, NEXT, PREV, NONE, infer_gesture_command
from hand_tracker import detect_hand
from state import ImageState
ASSETS_DIR = Path("assets")
APP_CSS = """
#app-shell {
max-width: 1280px;
margin: 0 auto;
}
.app-heading h1 {
font-size: 26px;
line-height: 1.2;
margin-bottom: 4px;
}
.app-heading p {
color: #4b5563;
margin-top: 0;
}
.preview-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #ffffff;
padding: 12px;
}
.preview-title {
font-weight: 700;
margin: 0 0 8px;
}
.preview-image {
height: min(46vw, 430px);
min-height: 280px;
overflow: hidden;
}
.preview-image img,
.preview-image video {
width: 100% !important;
height: 100% !important;
object-fit: contain !important;
}
.preview-image .icon-wrap,
.preview-image .empty,
.preview-image .label-wrap {
display: none !important;
}
#status-box textarea {
font-size: 16px;
font-weight: 600;
}
.command-row button {
min-height: 44px;
}
@media (max-width: 900px) {
.preview-image {
height: 320px;
}
}
"""
def build_status(command: str, index: int, total: int) -> str:
if total == 0:
return f"Command: {command} | Index: 0/0 (no images in assets/)"
return f"Command: {command} | Index: {index + 1}/{total}"
def apply_command(command: str, current_index: int, webcam_frame=None):
image_state = ImageState(str(ASSETS_DIR))
total = len(image_state.image_paths)
if total > 0:
if current_index is None:
image_state.index = 0
else:
image_state.index = max(0, min(int(current_index), total - 1))
# Placeholder call to keep the future hand-tracking interface in place.
_ = detect_hand(webcam_frame)
if command == NEXT:
image_state.next_image()
elif command == PREV:
image_state.prev_image()
elif command == FIRST:
image_state.first_image()
elif command == LAST:
image_state.last_image()
current_image = image_state.get_current_image()
total = len(image_state.image_paths)
status = build_status(command, image_state.index, total)
return current_image, status, image_state.index
def on_prev(current_index: int):
return apply_command(PREV, current_index, None)
def on_next(current_index: int):
return apply_command(NEXT, current_index, None)
def on_first(current_index: int):
return apply_command(FIRST, current_index, None)
def on_last(current_index: int):
return apply_command(LAST, current_index, None)
def on_webcam_frame(webcam_frame, current_index: int):
hand_data = detect_hand(webcam_frame)
command = infer_gesture_command(hand_data)
return apply_command(command, current_index, webcam_frame)
def build_demo():
initial_state = ImageState(str(ASSETS_DIR))
initial_image = initial_state.get_current_image()
initial_total = len(initial_state.image_paths)
initial_status = build_status(NONE, initial_state.index, initial_total)
with gr.Blocks(title="手勢影像播放器", elem_id="app-shell") as demo:
gr.Markdown(
"# 手勢影像播放器\n使用按鈕或單手手勢切換圖片。",
elem_classes="app-heading",
)
with gr.Row():
with gr.Column(scale=1, elem_classes="preview-card"):
gr.Markdown("網路攝影機", elem_classes="preview-title")
webcam_input = gr.Image(
sources=["webcam"],
type="numpy",
streaming=True,
show_label=False,
height=420,
elem_classes="preview-image",
placeholder="",
)
with gr.Column(scale=1, elem_classes="preview-card"):
gr.Markdown("目前圖片", elem_classes="preview-title")
current_image = gr.Image(
value=initial_image,
type="numpy",
show_label=False,
height=420,
elem_classes="preview-image",
buttons=[],
placeholder="",
)
status_text = gr.Textbox(
value=initial_status,
label="狀態",
interactive=False,
elem_id="status-box",
)
state_holder = gr.State(initial_state.index)
with gr.Row(elem_classes="command-row"):
btn_prev = gr.Button("PREV")
btn_next = gr.Button("NEXT")
btn_first = gr.Button("FIRST")
btn_last = gr.Button("LAST")
btn_prev.click(
fn=on_prev,
inputs=[state_holder],
outputs=[current_image, status_text, state_holder],
)
btn_next.click(
fn=on_next,
inputs=[state_holder],
outputs=[current_image, status_text, state_holder],
)
btn_first.click(
fn=on_first,
inputs=[state_holder],
outputs=[current_image, status_text, state_holder],
)
btn_last.click(
fn=on_last,
inputs=[state_holder],
outputs=[current_image, status_text, state_holder],
)
webcam_input.stream(
fn=on_webcam_frame,
inputs=[webcam_input, state_holder],
outputs=[current_image, status_text, state_holder],
)
webcam_input.change(
fn=on_webcam_frame,
inputs=[webcam_input, state_holder],
outputs=[current_image, status_text, state_holder],
)
return demo
if __name__ == "__main__":
app = build_demo()
app.launch(show_error=True, css=APP_CSS)