Files
VesiscanClinicalAndroid/docs/ALGORITHM_COMPARISON.md
T
dw.jang a1042b808c docs: README 전체 업데이트 (v5, 2026-05-11 기준)
- Pipeline Summary: GREEN 상수 (CV preset별, LR 0.20, 7s hold, 3fail exit),
  힌트 텍스트 표 추가, Spot 5회/Auto 10윈도우, 태블릿 fontScale,
  향후 과제 갱신 (IMU 포팅 예정)
- USER_GUIDE: 전체 재작성 — Start flow, 힌트 화살표(↑↓←→),
  페어링 다이얼로그, Void/Catheterization, Single Scan/Auto Scan,
  Checking/Final check/In position! 텍스트 반영
- ALGORITHM_COMPARISON: Placement 섹션 현행화 — Start 버튼 필수,
  phase protection, 화살표 항상 갱신, GREEN 7s+3fail

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 17:43:39 +09:00

206 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Method A / B / C Algorithm Comparison & Placement Test
## Wall Detection Comparison
### Pipeline
```
Method A: raw → SG → composite(sg+|d1|+|d2|) → plateau score → plateau spans
→ prominence wall (ANT=nearest, POST=edge_score)
→ cross-validation → BV(frustum+ellipse cap, 1ch=sphere)
※ 채널별 min_wall_contrast_ratio × cos(θ) 보정
Method B: raw → SG → TGC(multichannel log-fit) → initial detect(Otsu×0.9×cos(θ))
→ BR candidate(per-ch, post-only, prominence≥50)
→ consensus(median, outlier_tol=4, min_votes=3)
→ is_reflection_only(shared+cluster, amp_ratio=1.5)
→ resolve(suspicious post: post>ant×1.5)
→ suppress(valley early-stop 50.0) → rerun detect
→ choose(rerun vs initial) → BV(frustum+ellipse cap, 1ch=sphere)
Method C: raw → MedianFilter(win=7) → SG+wavelet
→ ImpulseReject(2-pass Hampel, lumen clean)
→ OS-CFAR(per-sample) → lumen mask
→ prominence wall + subsample refine
→ StaLta impulse purity check
→ anatomical gate (antDepth≥10mm, chord 5-110mm)
→ BModeScore V41 (wamp=0.45, stalta=0.20, far=0.15, dark=0.10)
→ ChordConsensus (Tukey MAD + Fischler-Bolles pair test)
→ BV dispatch(frustum/chord/sphere, trusted channels only)
```
### Stage-by-Stage
| Stage | **A (Plateau)** | **B (Otsu+TGC+BR)** | **C (V4.1 CFAR)** |
|-------|-----------------|----------------------|---------------------|
| Pre-filter | none | none | MedianFilter(win=7) + ImpulseReject(Hampel) |
| Denoising | SG(5,2) | SG(5,2) → log-TGC | SG(5,2) + db4 wavelet 3-level |
| Threshold | sliding plateau score Q=0.5 | Otsu×0.9×cos(θ) | OS-CFAR (win=15, rank=40%, scale=1.05) |
| Low-echo mask | platScore < thr | sg <= otsu (full) | sg <= median(CFAR) |
| Span merge | gap≤5, simple | gap<3: unconditional, ≥3: wall check | mergeGapMax=5, gapPeakMargin=50 |
| Span refine | none | refineRightEdge | none |
| Wall select | ANT=nearest, POST=edge_score, decay=0.15 | ANT=nearest, POST=edge_score, decay=0.12 | prominence + subsample, MAX_CANDIDATES_POST=64 |
| Valley logic | descending-first, rise=5 | descending-first, rise=50 | rise=50 |
| Extra conditions | contrast×cos(θ)≥1.2, prom≥3 | peakMin=max(low+30,thr), sg[ant/post]≥thr, postProm≥30 | Kremkau ant fallback, antMinIdx=7 |
| Score | sg[ant]+sg[post] | asymmetric ant_reliability | BModeScore V41 (wamp+stalta+far+dark+grad) |
| FP filter | cross-validation | score<3000 | AnatomicalGate + ChordConsensus |
| Impulse check | none | none | StaLta (STA/LTA ratio) |
| Channel filter | none | none | ChordConsensus (MAD outlier + pair test) |
| Back reflection | none | consensus multichannel BR | none |
| Fallback | none | ant=max(0,s-(post-e)) | B-mode tier |
### BV Estimation
| | **A** | **B** | **C** |
|---|---|---|---|
| Model | shared with B | frustum + ellipse cap | Multi-dispatch (trusted ch only) |
| 1-channel | sphere V=(4/3)πR³ | same | Verathon chord |
| Channel filter | none | none | ChordConsensus (drop inconsistent) |
| Cap height | 8-point ellipse LSQ | same | fixed: bottom=hemisphere, top=cone |
| LR ratio | shared with B | 2-lateral ellipse solve + shrinkage | 1/avg_ratio, clamp[1.0,1.6] |
| Confidence | none | none | 0.95(4C+2L) ~ 0.20(1ch) |
| DPS | PiezoHW (1.968) | same | 1.9309 |
### Angle Correction (PR#20)
Both Method A and B apply per-channel beam angle correction:
- **Method B**: `otsu_ratio = OTSU_RATIO × cos(θ)` per channel
- **Method A**: `min_wall_contrast_ratio = 1.2 × cos(θ)` per channel
- Larger angles → lower threshold → better detection of weak posterior walls
### Back Reflection System (Method B)
```
Per-channel:
1. initial detect → (ant, post)
2. build_candidate: strongest peak AFTER post (prominence≥50)
- global BR ≈ post (±3) → None (no separate BR)
- else → strongest significant peak after post
Multichannel:
3. consensus: median, outlier_tol=4, min_votes=3
4. is_reflection_only: post > ant × 1.5 (amp_ratio)
- Condition A: shared_idx based
- Condition B: post cluster based
Per-channel resolve:
5. is_suspicious_post: post > ant × 1.5 OR post ≈ shared (±2)
6. select_idx → resolve → suppress(valley early-stop 50.0)
7. rerun detect → choose(rerun vs initial, min_gap=8)
```
### Method C V4.1 New Features (2026-05-04)
```
Pre-processing:
MedianFilter(win=7) → remove speckle bumps
ImpulseReject(2-pass Hampel) → clean 1-3 sample impulses in lumen
Detection:
DetectLumenFirst V41_PARAMS:
mergeGapMax=5, gapPeakMargin=50
maxCandidatesPost=64, medianWin=7, antMinIdx=7
Kremkau half-amplitude ant fallback
Quality:
StaLta: STA/LTA ratio → wall edge vs reverberation discrimination
BModeScore V41_WEIGHTS:
wamp=0.45 (wall-peak amplitude)
stalta=0.20 (impulse purity)
far=0.15, dark=0.10, ant=0.05, post=0.05
Channel Filtering:
ChordConsensus: Tukey MAD outlier + Fischler-Bolles pair test
→ drop geometrically inconsistent channels before BV
Anatomical Gate:
antDepthMin: 22 → 10mm (Neyman-Pearson loose prior)
strict threshold removed → delegated to B-mode score
```
---
## Python vs Kotlin Sync Status (2026-05-04)
### Method A/B: 18 functions compared — CRITICAL 0, MINOR 5
All config constants match: LOW_ECHO_AMP=1250, OTSU_RATIO=0.9, MIN_SCORE=3000,
EDGE_DIST_DECAY=0.12, VALLEY_STOP_RISE=50, etc.
### Method C: Direct copy from CharlesKWONsLaw (45 files, package renamed)
---
## Placement Test Algorithm
### Overview (2026-05-11 updated)
```
Screen entry → "Place VesiScan above the pubic bone, then press Start"
↓ Start Alignment 버튼 탭 (waitingForStart=false)
전체 상태 리셋
↓
[1-second interval loop]
↓
mpa (first time, 500ms delay) → maa → 6ch reb
↓
Detachment check (6ch received, THR=30) → LED 4 if detached
↓
Wall detection (Method A/B/C) → urineLen ≥ 12
↓
computePlacementGuide() → 3-stage guide (VERTICAL → LATERAL → GREEN)
↓
Debounce (2 consecutive + 1.5s/3s hold) + Phase protection (3 scans)
↓
Arrow direction: always updated (directionIcon synced with guideResult)
↓
LED: stage change only (4=detach, 5=searching, 6=complete)
↓
GREEN: 7s hold + 3 consecutive fail to exit
↓
Screen exit → LED OFF
```
### Guide Modes
**Gradient** (default): weighted center gradient → LATERAL → GREEN
**Boundary**: up → detect boundary (max drops) → come back → LATERAL
**SWEEP** (CKLaw): navel → slide down → sagittalArgMaxCh → LATERAL → GREEN
### 3-Stage Guide
**Stage 1 VERTICAL**: detect count + gradient + "Slide up/down ↑↓"
**Stage 2 LATERAL**: |len4-len5| ≤ 10 → GREEN, else "Slide left/right ←→"
**Stage 3 GREEN**: CV ≤ threshold AND |LR dev| ≤ 0.20 → "In position!" (7s hold)
### Measurement Modes
- **Auto Scan**: min 800ms gap, 10-sample trimmed mean (max/min removed, 8 avg)
- **Single Scan (Spot)**: 5회 측정 → trimmed mean (max 8 attempts, min 3)
- valid ≥ 4 channels only
- Analysis on coroutine (Dispatchers.Default)
- Auto fail: 2ch+ missing 5 consecutive → realign dialog
- Detachment → LED 4 (once), recovery → LED 0 (once)
- Foreground Service: BLE connection maintained during screen off
- BLE 로그: 실시간 파일 기록 (Downloads/VesiScan_BLE_*.log, 화면 잠금에도 보존)
- CSV: 세션별 파일 (YYYY-MM-DD_HHmmss.csv)
### Placement GREEN 기준
```
CV threshold = DEFAULT_CV_THR + (scanCount / 5) × 0.03
DEFAULT_CV_THR: V0=0.07, V1/V2=0.10, 기타=0.08
LR deviation ≤ 0.20
GREEN hold: 7초 (greenLockedAtMs 기준)
GREEN exit: 3회 연속 실패 (greenFailCount)
```
### Test Results (2026-05-04, 500ml phantom, Method C V4.1)
```
총 측정: 223회
평균: 492.4ml (오차 -1.5%)
±15% 이내: 223/223 (100%)
범위: 441~537ml
```