docs: low echo detection + Placement CV threshold 문서 업데이트
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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" 활성화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user