amanda-cgu commited on
Commit
0c9453b
·
verified ·
1 Parent(s): bc37b4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -35
app.py CHANGED
@@ -1,12 +1,11 @@
1
  # app.py — 無菌手套姿勢檢測系統(F-segment 偵測)複賽正式版
2
  # 說明:
3
- # - 結合 Mediapipe Pose + Hands自動估計腰線,並檢測雙手「腰部以上」與「指尖朝上」比例
4
- # - AND 規則:腰部以上比例 >= PASS_TH_POS 且 指尖朝上比例 >= PASS_TH_UP 才算通過
5
- # - 本版 UI 為複賽「正式精簡版」,保留
6
- # 1) 上傳與分析按鈕
7
- # 2) 結果表 + CSV 下載
8
- # 3)整摘要
9
- # 4) 三個可展開的說明面板(標註資訊/系統限制/未來改善)
10
 
11
  import os
12
  import cv2
@@ -16,7 +15,7 @@ import tempfile
16
  import gradio as gr
17
 
18
  # -----------------------------------------------------------------------------
19
- # Mediapipe 相關設定(若無法匯入,則自動降級為僅回報錯誤)
20
  # -----------------------------------------------------------------------------
21
  _HAS_MP = True
22
  _HAS_POSE = True
@@ -38,7 +37,7 @@ MAX_LONG_SIDE = 960 # 影像長邊縮放上限(避免太大)
38
  DEFAULT_WAIST_RATIO = 0.65
39
  CLAMP_LOW = 0.58 # 自動腰線 y 下限
40
  CLAMP_HIGH = 0.72 # 自動腰線 y 上限
41
- WAIST_DELTA_Y = -0.05 # 自動結果上再微調 Δy(負值代表往上移)
42
 
43
  # 判定門檻(本次複賽設定)
44
  PASS_TH_POS = 50 # 腰部門檻 (%)
@@ -58,7 +57,7 @@ COLS = [
58
  # 工具函式
59
  # -----------------------------------------------------------------------------
60
  def _to_path(f):
61
- """ Gradio File 物件或字串轉成檔案路徑。"""
62
  if isinstance(f, str):
63
  return f
64
  if hasattr(f, "name"):
@@ -82,8 +81,8 @@ def _resize_keep_ar(frame, max_long=MAX_LONG_SIDE):
82
  def _is_index_up(lms, h):
83
  """
84
  判斷單手的食指是否「明顯朝上」:
85
- - 利用 PIP→TIP 向量的角度(避免手指平躺或往側邊)
86
- - 並考慮 tip 與 wrist 的高度差(tip 要高於手腕一定距離)
87
  """
88
  tip = lms[mp_hands.HandLandmark.INDEX_FINGER_TIP.value]
89
  pip = lms[mp_hands.HandLandmark.INDEX_FINGER_PIP.value]
@@ -92,9 +91,8 @@ def _is_index_up(lms, h):
92
  dy = (pip.y - tip.y) * h
93
  dx = abs(pip.x - tip.x) * h
94
  angle_deg = np.degrees(np.arctan2(dy, dx))
95
- height_diff = (wrist.y - tip.y) * h # tip 比 wrist 高多少(像素)
96
 
97
- # 角度大於 25 度,且 tip 明顯高於 wrist,才算「朝上」
98
  return (angle_deg > 25) and (height_diff > 40)
99
 
100
  # -----------------------------------------------------------------------------
@@ -135,7 +133,6 @@ def _auto_waist_ratio_pose(video_path, sample_frames=40,
135
  res = pose.process(rgb)
136
  if res.pose_landmarks:
137
  lms = res.pose_landmarks.landmark
138
- # 23 / 24 為左右 hip
139
  hip_y = (lms[23].y + lms[24].y) / 2.0
140
  ys.append(hip_y)
141
  idx += step
@@ -271,9 +268,8 @@ def analyze_one_video(video_path):
271
  for lmset in res.multi_hand_landmarks:
272
  lms = lmset.landmark
273
  wrist = lms[mp_hands.HandLandmark.WRIST.value]
274
- pos_flags.append((wrist.y * h) <= y_ref) # 腰部以上
275
- up_flags.append(_is_index_up(lms, h)) # 指尖朝上
276
-
277
  # 要求雙手皆符合(若只偵測到一手,就以那一手為準)
278
  if len(pos_flags) >= 2:
279
  this_pos = all(pos_flags[:2])
@@ -304,7 +300,11 @@ def analyze_one_video(video_path):
304
  acc = (ok_both / valid) * 100.0
305
  passed_valid = f"{ok_both}/{valid}"
306
 
307
- result_str = "通過" if (above_ratio >= PASS_TH_POS and up_ratio >= PASS_TH_UP) else "未通過"
 
 
 
 
308
  valid_total = f"{valid}/{total_frames}"
309
 
310
  return [
@@ -318,7 +318,7 @@ def analyze_one_video(video_path):
318
  # -----------------------------------------------------------------------------
319
  # 多支影片分析核心 + 統整摘要
320
  # -----------------------------------------------------------------------------
321
- def run_core(files, state)):
322
  """實際進行分析並回傳 df 和 CSV 路徑。"""
323
  if not files:
324
  df = pd.DataFrame([], columns=COLS)
@@ -326,18 +326,11 @@ def run_core(files, state)):
326
  return None, df, [], tmp_path
327
 
328
  rows = []
329
- total = len(files)
330
-
331
- for i, f in enumerate(files):
332
- if total > 0:
333
-
334
  path = _to_path(f)
335
  if os.path.exists(path):
336
  rows.append(analyze_one_video(path))
337
 
338
- # 分析全部完成
339
-
340
-
341
  state_new = (state or []) + rows
342
  df = pd.DataFrame(state_new, columns=COLS)
343
 
@@ -376,13 +369,15 @@ def build_summary_text(df: pd.DataFrame) -> str:
376
  return text
377
 
378
 
379
- def run_with_status(files, state)):
380
  """供按鈕呼叫:更新狀態列 + DataFrame + CSV + 摘要。"""
 
381
  status_text = "🟠 系統正在分析影片,請稍候…"
382
 
383
  files2, df, state2, csv_path = run_core(files, state)
384
  summary_text = build_summary_text(df)
385
 
 
386
  status_text = "🟢 分析完成,可以檢視表格並下載 CSV 報表。"
387
 
388
  return status_text, files2, df, state2, csv_path, summary_text
@@ -398,7 +393,7 @@ def do_clear(state):
398
  # Gradio UI 建構
399
  # -----------------------------------------------------------------------------
400
  with gr.Blocks(
401
- title="醫療技術AI王:無菌手套姿勢檢測系統(F-segment 偵測)",
402
  css="""
403
  #status-text {
404
  color: #1565c0; /* 深藍色狀態列 */
@@ -408,7 +403,7 @@ with gr.Blocks(
408
  """
409
  ) as demo:
410
 
411
- # LOGO 與標題(目前先用文字呈現;真正圖檔由 HuggingFace README 的 banner/logo 負責)
412
  gr.Markdown("# 👑 醫療技術AI王")
413
  gr.Markdown("## 🧤 無菌手套姿勢檢測系統(F-segment 偵測)")
414
 
@@ -427,7 +422,7 @@ with gr.Blocks(
427
  # 按鈕列
428
  with gr.Row():
429
  btn_run = gr.Button("分析", variant="primary", scale=1)
430
- btn_csv = gr.DownloadButton("下載 CSV 報表", value=None, scale=1)
431
  btn_clear = gr.Button("清除", variant="secondary", scale=1)
432
 
433
  # 狀態儲存
@@ -447,7 +442,7 @@ with gr.Blocks(
447
  # 額外說明面板(可收合)
448
  with gr.Accordion("📘 本次標註資訊(可展開)", open=False):
449
  gr.Markdown("""
450
- ### 🔍 本次標準片與錯片
451
  - **標準片(應通過)**:FV3、FV4、FV5、FV6
452
  - **錯片(應不通過)**:WFV6-2、WFV6
453
 
@@ -476,7 +471,7 @@ with gr.Blocks(
476
  - 若資料量充足,可進一步訓練專用的 F-segment 深度學習模型。
477
  """)
478
 
479
- # 事件綁定:使用 Gradio 內建 minimal 進度條 + 自訂說明文字
480
  btn_run.click(
481
  fn=run_with_status,
482
  inputs=[files, table_state],
@@ -492,7 +487,7 @@ with gr.Blocks(
492
  )
493
 
494
  # -----------------------------------------------------------------------------
495
- # 啟動
496
  # -----------------------------------------------------------------------------
497
  if __name__ == "__main__":
498
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  # app.py — 無菌手套姿勢檢測系統(F-segment 偵測)複賽正式版
2
  # 說明:
3
+ # 1) 結合 Mediapipe Pose + Hands / 自動腰線偵測,並檢測雙手「腰部以上」與「指尖朝上」比例
4
+ # 2) AND 規則:腰部以上比例 >= PASS_TH_POS 且 指尖朝上比例 >= PASS_TH_UP 才算通過
5
+ # 3) 本版 UI 「正式評圖版」精簡介面
6
+ # a. 上傳影片區塊
7
+ # b. 統整結果表 + CSV 下載
8
+ # c. 三個可展開的說明區塊(標註資訊 / 系限制 / 未來改善)
 
9
 
10
  import os
11
  import cv2
 
15
  import gradio as gr
16
 
17
  # -----------------------------------------------------------------------------
18
+ # Mediapipe 相關設定(若無法匯入,則自動降級為僅顯示錯誤)
19
  # -----------------------------------------------------------------------------
20
  _HAS_MP = True
21
  _HAS_POSE = True
 
37
  DEFAULT_WAIST_RATIO = 0.65
38
  CLAMP_LOW = 0.58 # 自動腰線 y 下限
39
  CLAMP_HIGH = 0.72 # 自動腰線 y 上限
40
+ WAIST_DELTA_Y = -0.05 # 自動腰線 Δy 微調(負值代表往上移)
41
 
42
  # 判定門檻(本次複賽設定)
43
  PASS_TH_POS = 50 # 腰部門檻 (%)
 
57
  # 工具函式
58
  # -----------------------------------------------------------------------------
59
  def _to_path(f):
60
+ """ gr.File / dict / 字串 轉成實際檔案路徑。"""
61
  if isinstance(f, str):
62
  return f
63
  if hasattr(f, "name"):
 
81
  def _is_index_up(lms, h):
82
  """
83
  判斷單手的食指是否「明顯朝上」:
84
+ - 利用 PIP→TIP 向量的角度
85
+ - 並考慮 tip 與 wrist 的高度差
86
  """
87
  tip = lms[mp_hands.HandLandmark.INDEX_FINGER_TIP.value]
88
  pip = lms[mp_hands.HandLandmark.INDEX_FINGER_PIP.value]
 
91
  dy = (pip.y - tip.y) * h
92
  dx = abs(pip.x - tip.x) * h
93
  angle_deg = np.degrees(np.arctan2(dy, dx))
94
+ height_diff = (wrist.y - tip.y) * h
95
 
 
96
  return (angle_deg > 25) and (height_diff > 40)
97
 
98
  # -----------------------------------------------------------------------------
 
133
  res = pose.process(rgb)
134
  if res.pose_landmarks:
135
  lms = res.pose_landmarks.landmark
 
136
  hip_y = (lms[23].y + lms[24].y) / 2.0
137
  ys.append(hip_y)
138
  idx += step
 
268
  for lmset in res.multi_hand_landmarks:
269
  lms = lmset.landmark
270
  wrist = lms[mp_hands.HandLandmark.WRIST.value]
271
+ pos_flags.append((wrist.y * h) <= y_ref)
272
+ up_flags.append(_is_index_up(lms, h))
 
273
  # 要求雙手皆符合(若只偵測到一手,就以那一手為準)
274
  if len(pos_flags) >= 2:
275
  this_pos = all(pos_flags[:2])
 
300
  acc = (ok_both / valid) * 100.0
301
  passed_valid = f"{ok_both}/{valid}"
302
 
303
+ result_str = (
304
+ "通過"
305
+ if (above_ratio >= PASS_TH_POS and up_ratio >= PASS_TH_UP)
306
+ else "未通過"
307
+ )
308
  valid_total = f"{valid}/{total_frames}"
309
 
310
  return [
 
318
  # -----------------------------------------------------------------------------
319
  # 多支影片分析核心 + 統整摘要
320
  # -----------------------------------------------------------------------------
321
+ def run_core(files, state):
322
  """實際進行分析並回傳 df 和 CSV 路徑。"""
323
  if not files:
324
  df = pd.DataFrame([], columns=COLS)
 
326
  return None, df, [], tmp_path
327
 
328
  rows = []
329
+ for f in files:
 
 
 
 
330
  path = _to_path(f)
331
  if os.path.exists(path):
332
  rows.append(analyze_one_video(path))
333
 
 
 
 
334
  state_new = (state or []) + rows
335
  df = pd.DataFrame(state_new, columns=COLS)
336
 
 
369
  return text
370
 
371
 
372
+ def run_with_status(files, state):
373
  """供按鈕呼叫:更新狀態列 + DataFrame + CSV + 摘要。"""
374
+ # 進入分析狀態
375
  status_text = "🟠 系統正在分析影片,請稍候…"
376
 
377
  files2, df, state2, csv_path = run_core(files, state)
378
  summary_text = build_summary_text(df)
379
 
380
+ # 分析完成狀態
381
  status_text = "🟢 分析完成,可以檢視表格並下載 CSV 報表。"
382
 
383
  return status_text, files2, df, state2, csv_path, summary_text
 
393
  # Gradio UI 建構
394
  # -----------------------------------------------------------------------------
395
  with gr.Blocks(
396
+ title="無菌手套姿勢檢測系統(F-segment 偵測)",
397
  css="""
398
  #status-text {
399
  color: #1565c0; /* 深藍色狀態列 */
 
403
  """
404
  ) as demo:
405
 
406
+ # 標題
407
  gr.Markdown("# 👑 醫療技術AI王")
408
  gr.Markdown("## 🧤 無菌手套姿勢檢測系統(F-segment 偵測)")
409
 
 
422
  # 按鈕列
423
  with gr.Row():
424
  btn_run = gr.Button("分析", variant="primary", scale=1)
425
+ btn_csv = gr.DownloadButton("下載 CSV 報表", visible=False, scale=1)
426
  btn_clear = gr.Button("清除", variant="secondary", scale=1)
427
 
428
  # 狀態儲存
 
442
  # 額外說明面板(可收合)
443
  with gr.Accordion("📘 本次標註資訊(可展開)", open=False):
444
  gr.Markdown("""
445
+ ### 🔍 本次標
446
  - **標準片(應通過)**:FV3、FV4、FV5、FV6
447
  - **錯片(應不通過)**:WFV6-2、WFV6
448
 
 
471
  - 若資料量充足,可進一步訓練專用的 F-segment 深度學習模型。
472
  """)
473
 
474
+ # 事件綁定:顯示 Gradio 內建 minimal 進度條即可
475
  btn_run.click(
476
  fn=run_with_status,
477
  inputs=[files, table_state],
 
487
  )
488
 
489
  # -----------------------------------------------------------------------------
490
+ # 啟動(本地測試用;在 HuggingFace 會由 Spaces 自動呼叫 demo)
491
  # -----------------------------------------------------------------------------
492
  if __name__ == "__main__":
493
  demo.launch(server_name="0.0.0.0", server_port=7860)