From fde4d0ad0511a64e22c5d701b12c77a530ab3f70 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Fri, 24 Apr 2026 10:30:41 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20low=20echo=20detection=20+=20Placement?= =?UTF-8?q?=20CV=20threshold=20=EB=AC=B8=EC=84=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Low echo: VALLEY_STOP_RISE=50, EDGE_DIST_DECAY=0.12, edge boundary, score 공식 - Placement: CV threshold 방식 전체 설명 (CV 계산, SI/LR 판정, severity, relaxation) - 기존 urineLen score 방식 → CV threshold 방식으로 전면 교체 Co-Authored-By: Claude Opus 4.6 (1M context) --- VesiScan_Android_Pipeline_Summary.md | 145 +++++++++++++++++++-------- 1 file changed, 103 insertions(+), 42 deletions(-) diff --git a/VesiScan_Android_Pipeline_Summary.md b/VesiScan_Android_Pipeline_Summary.md index 01f30da..c980835 100644 --- a/VesiScan_Android_Pipeline_Summary.md +++ b/VesiScan_Android_Pipeline_Summary.md @@ -231,7 +231,7 @@ UrinAI가 "소변 있다!" 하면, **정확히 어디서 어디까지**인지 | 상수 | 값 | 의미 | |------|-----|------| -| `lowEchoAmp` | **1150** | denoised ≤ 이 값 = low-echo (iOS: 1600) | +| `lowEchoAmp` | **1150** | denoised ≤ 이 값 = low-echo | | `lowMinLen` | 3 | low-echo span 최소 길이 | | `mergeGapMax` | 3 | 인접 span 합치기 최대 gap | | `peakSearchWin` | 20 | 벽 peak 탐색 범위 | @@ -239,8 +239,10 @@ UrinAI가 "소변 있다!" 하면, **정확히 어디서 어디까지**인지 | `minPeakMargin` | 30 | peak 최소 높이 = low_mean + 30 | | `minUrineLen` | 3 | 최소 소변 구간 길이 | | `maxPeakCandidates` | 3 | 방향별 최대 후보 수 | +| `valleyStopRise` | **50** | valley 스캔 시 상승 early stop 임계 (기존 10) | +| `edgeDistDecay` | **0.12** | edge 거리 penalty 계수 (신규) | -### 알고리즘 (Prominence 기반, 2026-04-20 업데이트) +### 알고리즘 (Prominence + Edge Distance, 2026-04-24 업데이트) ``` 1. 디노이징된 신호에서 1150 이하인 연속 구간 찾기 @@ -248,17 +250,20 @@ UrinAI가 "소변 있다!" 하면, **정확히 어디서 어디까지**인지 2. 인접한 구간 합치기 (gap ≤ 3칸이면 합침) 단, gap 안에 1155 이상인 peak이 있으면 합치지 않음 - (그건 벽일 수 있으니까!) 3. 양쪽에서 벽 peak 후보 찾기 (±20칸 범위) - 후보 중 prominence가 가장 큰 것을 선택 + - edge 포함 boundary: left [leftLo:edge+1], right [edge-1:rightHi+1] + - 후보 중 score가 가장 높은 것을 선택 - prominence란? - = peak 높이 - 옆 valley(골짜기) 깊이 - = "이 peak이 주변보다 얼마나 튀어나왔는가" + score = prominence / (1 + 0.12 × distance_from_edge) - 전벽(ant): peak 오른쪽(소변 방향) valley 기준 - 후벽(post): peak 왼쪽(소변 방향) valley 기준 + prominence = peak 높이 - valley(소변 방향) + distance = |peak위치 - low-echo edge| + + → low-echo에 가까운 peak이 더 높은 점수 (먼 peak은 패널티) + + valley 스캔 시 최소값에서 50 이상 올라가면 early stop + (기존 10에서 50으로 상향 → 더 깊은 valley까지 탐색) 4. 후벽이 80칸 넘으면 → 구간 뒤쪽 절반에서 다시 찾기 @@ -413,52 +418,108 @@ Android: estimateBladderVolume6ch(6채널) | - | - | ✅ | ❌ | "센서를 **오른쪽**으로 옮기세요" (RIGHT) | | ❌ | ❌ | ❌ | ❌ | "방광이 안 잡힙니다. 센서 접촉을 확인하세요" | -### 2단계: urineLen 기반 정밀 가이드 (4채널 이상 잡힌 후) +### 2단계: CV threshold 정밀 가이드 (center 3채널 이상 잡힌 후) 4채널 이상 잡히더라도, 방광 **중앙**에 정확히 위치해야 측정이 정확합니다. -각 채널의 소변 구간 길이(urineLen)를 비교하여 정밀 가이드를 제공합니다. +각 채널의 **cos-corrected depth(mm)**의 변동계수(CV)로 배치 품질을 판정합니다. + +### CV (Coefficient of Variation) 계산 ``` -잘 맞은 경우 (대칭): 치우친 경우 (비대칭): -CH0 ████████████ (긴) CH0 ██████ (짧) -CH1 ████████████████(긴) CH1 ████████████ (긴) -CH2 ████████████████(긴) CH2 ██████████ (중간) -CH3 ████████████ (긴) CH3 ████ (짧) -→ Score: 90% → Score: 45%, "Move UP" +1. 각 center 채널(CH0~CH3)의 ant/post wall 위치를 mm로 변환: + depth_mm = (post_mm - ant_mm) × cos(beam_angle) + +2. 유효 채널(≥3개)의 depth 평균과 표준편차 계산: + CV = std(depths) / mean(depths) + +3. 판정: + CV ≤ 0.15 → PASS (Best Placement) + CV > 0.15 → FAIL → 방향 안내 ``` -**세로 판정:** -- CH0의 urineLen이 CH3보다 1.5배 이상 → "Move UP" (방광이 위쪽) -- CH3이 CH0보다 1.5배 이상 → "Move DOWN" (방광이 아래쪽) - -**가로 판정:** -- CH4가 CH5보다 5칸 이상 길면 → "Move RIGHT" (왼쪽 치우침) -- CH5가 CH4보다 5칸 이상 길면 → "Move LEFT" (오른쪽 치우침) - -### Placement Score 계산 +### 왜 CV인가? ``` -점수 = 세로대칭(30%) + 가로균형(30%) + 중앙크기(40%) +잘 맞은 경우 (중앙): 치우친 경우 (가장자리): +CH0: depth=45mm CH0: depth=15mm ← 짧음 +CH1: depth=52mm CH1: depth=48mm +CH2: depth=50mm CH2: depth=42mm +CH3: depth=42mm CH3: depth=10mm ← 짧음 -세로 대칭 = 1 - |CH0_len - CH3_len| / max(CH0_len, CH3_len) -가로 균형 = 1 - |CH4_len - CH5_len| / max(CH4_len, CH5_len) -중앙 크기 = avg(CH1_len, CH2_len) / 40 (40칸이면 만점) - -Score ≥ 80%: "Perfect position! 🎯" -Score ≥ 70%: Lock (측정 시작 가능) -Score < 50%: 방향 가이드 표시 +CV = 0.09 → PASS ✅ CV = 0.52 → FAIL ❌ + weakest = CH3 → "MOVE UP" ``` -## "위치 확인" 버튼을 누르면 무슨 일이? +중앙을 관통하면 모든 채널의 depth가 비슷 → CV 작음. +가장자리를 스치면 일부 채널이 짧음 → CV 큼. + +### SI(세로) 방향 판정 ``` -1. 앱이 센서에 "maa" 명령 (6채널 전부 측정해!) -2. 각 채널마다: - - PiezoEchoAnalyzer로 벽 탐지 + urineLen 계산 - - 실패 시 UrinAI.detect()로 fallback -3. 감지된 채널 = 초록 (urineLen 숫자 표시), 안 된 채널 = 빨강 -4. 1단계 or 2단계 가이드 표시 + Placement Score -5. Score ≥ 70% 또는 4채널 이상 → "Start Measurement" 버튼 활성화 +deviation[i] = (depth[i] - mean) / mean + +weakest = 가장 deviation이 큰 채널 + +severity: + |dev| < 0.10 → slight + 0.10 ≤ < 0.25 → moderate + |dev| ≥ 0.25 → severe + +방향: + weakest가 CH0/CH1 (top) → MOVE DOWN + weakest가 CH2/CH3 (bottom) → MOVE UP +``` + +### LR(가로) 판정 + +``` +depth_CH4 = raw_depth × cos(SI_angle) × cos(LR_angle) +depth_CH5 = raw_depth × cos(SI_angle) × cos(LR_angle) + +lr_dev = (depth_CH4 - depth_CH5) / mean(depth_CH4, depth_CH5) + +|lr_dev| ≤ 0.15 → PASS +lr_dev > 0 → MOVE RIGHT (왼쪽이 더 길음) +lr_dev < 0 → MOVE LEFT (오른쪽이 더 길음) +``` + +### 최종 판정 + +``` +is_pass = CV ≤ cv_thr AND |lr_dev| ≤ lr_dev_thr + +cv_thr = 0.15 + repeatCount × 0.02 (반복 시 완화) +lr_dev_thr = 0.15 (고정) +``` + +### Threshold Relaxation (반복 완화) + +``` +Scan 흐름: + 1회차: CV_thr = 0.15 + 2회차: CV_thr = 0.17 + 3회차: CV_thr = 0.19 + ... + n회차: CV_thr = 0.15 + n × 0.02 + +→ Best Placement가 아닌 경우 반복 측정 동안 기준을 완화하여 + 사용자가 무한히 조절하지 않도록 함 +``` + +## 연속 스캔 동작 + +``` +"Start Scanning" 버튼 탭 + ↓ +즉시 첫 스캔 (maa 6채널) + ↓ 결과: CV, 방향, Score 표시 +3초 대기 + ↓ +자동 재스캔 (repeatCount 증가 → threshold 완화) + ↓ +... 반복 (사용자가 Stop 누를 때까지) + +is_pass = true → Lock → "Start Measurement" 활성화 ``` ---