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>
This commit is contained in:
2026-05-11 17:43:39 +09:00
parent ef38aa6eaf
commit a1042b808c
3 changed files with 343 additions and 96 deletions
+58 -63
View File
@@ -1,6 +1,7 @@
# VesiScan-Basic User Guide
## App Version: VBTAND0101
## Last Updated: 2026-05-11
---
@@ -23,17 +24,17 @@
## 2. Connecting the Device
1. Tap **Start** on the Home screen
2. The app will scan for nearby VesiScan devices
2. The app will scan for nearby VesiScan devices (sorted alphabetically)
3. When your device appears in the list, tap it to connect
4. **Complete the Bluetooth pairing** when the system dialog appears
- Do NOT press Cancel — the app will not proceed without pairing
- A "Pairing Required" dialog will guide you through the process
- Tap **Pair** on the system Bluetooth dialog — canceling will block the connection
- The app waits up to 30 seconds for pairing to complete
5. Once paired, you will move to the Personalization screen
### Personalization
- Set your **posture** (standing/sitting/lying)
- Set the **sensor position**
- Set the **Max Volume** (default: 500 mL)
- Tap **Next** to proceed to Sensor Alignment
- Set your **Max Volume** (200-800 mL, default: 500 mL)
- Tap **Continue** to proceed to Sensor Alignment
---
@@ -41,45 +42,49 @@
This step ensures the sensor is positioned correctly over your bladder for accurate measurement.
### Step 1: Vertical Alignment
### Before Starting
- Place VesiScan above the pubic bone
- Apply ultrasound gel between the sensor and your skin
- Tap the **Start Alignment** button (large orange button)
1. **Place the VesiScan-Basic device just above your pubic bone**
2. Apply ultrasound gel between the sensor and your skin
3. Tap the **Start Alignment** button
4. The app will begin scanning and provide direction:
- **"Raise the sensor up"** — slide the device upward toward your navel
- **"Lower the sensor down"** — slide the device downward toward your pubic bone
- **"Raise the sensor slightly"** / **"Lower the sensor slightly"** — make small adjustments
5. Move the sensor **slowly, 1-2mm at a time**
6. When the vertical position is correct, the app will show **Step 2/3**
### Step 1/3: Vertical Alignment
> The hint text will show animated dots (e.g., "Raise the sensor up.", "Raise the sensor up..", "Raise the sensor up...") to indicate the system is actively scanning.
The app will begin scanning and provide direction:
- **"Slide up ↑"** — slide the device upward toward your navel
- **"Slide down ↓"** — slide the device downward toward your pubic bone
- **"Slide up slightly ↑"** / **"Slide down slightly ↓"** — make small adjustments
- Move the sensor **slowly, 1-2mm at a time**
- When the vertical position is correct, the app will show **Step 2/3**
### Step 2: Lateral Alignment
> The hint text will show animated dots (e.g., "Slide up ↑.", "Slide up ↑..", "Slide up ↑...") to indicate the system is actively scanning.
1. The app will first show **"Checking left/right balance..."** for a few seconds
### Step 2/3: Lateral Alignment
1. The app will first show **"Checking..."** for a few seconds
2. Then it will guide you:
- **"Move left"** — slide the device to the left
- **"Move right"** — slide the device to the right
- **"Slide left ←"** — slide the device to the left
- **"Slide right →"** — slide the device to the right
3. Adjust until both lateral sensors (CH4 and CH5) detect the bladder equally
4. When balanced, the app will show **Step 3/3**
### Step 3: Final Check (Green Zone)
### Step 3/3: Final Check (Green Zone)
1. The app checks overall alignment quality
2. If everything is optimal, the hint will show **"Optimal position!"**
3. The screen will hold this state for **7 seconds** to confirm stability
4. Tap **Alignment Complete** to proceed to the measurement screen
1. The app checks overall alignment quality (CV + LR deviation)
2. If everything is optimal, the hint will show **"In position!"**
3. The screen background turns green and holds this state for **7 seconds** to confirm stability
4. The **Start Scanning** button (green) becomes active
5. Tap **Start Scanning** to proceed to the measurement screen
### If Alignment is Lost
- If the sensor shifts significantly after reaching Optimal:
- The app will show **"Position lost. Place sensor above pubic bone, then press Start"**
- If the sensor shifts significantly after reaching "In position!":
- The app allows up to **3 consecutive failures** before exiting GREEN
- After 3 failures: **"Position lost. Restart above pubic bone."**
- The scanning will stop automatically
- Re-place the sensor and tap **Start Alignment** again
### If the Sensor is Detached
- If the sensor loses contact with skin:
- The app will show **"Sensor detached. Place above pubic bone, then press Start"**
- The app will show **"Sensor detached. Restart above pubic bone."**
- Re-attach the sensor with gel and tap **Start Alignment**
### Tips
@@ -111,7 +116,7 @@ After alignment, you will see the main measurement screen with a donut chart.
Current Measurement | 65%
280 mL | 325/500 mL
[Void/Catheterize] [Auto] [Spot]
[Void/Catheterization] [Auto Scan] [Single Scan]
```
### Understanding the Display
@@ -127,48 +132,47 @@ After alignment, you will see the main measurement screen with a donut chart.
---
## 5. Auto Measurement
## 5. Auto Scan
1. Tap the **Auto** button (orange)
2. The device will measure continuously every ~1.5 seconds
1. Tap the **Auto Scan** button (orange)
2. The device will measure continuously (minimum 800ms interval)
3. For the first 5 measurements, the display will show **"—"** (collecting data)
4. After 5+ measurements, the **trimmed mean** value will be displayed
4. After 5+ measurements, the **trimmed mean** value will be displayed (10-sample window, top/bottom excluded)
5. The donut chart will update every 5 seconds
6. Tap **Stop** (red) to end Auto measurement
6. Tap **Stop Scan** (red) to end Auto measurement
### Auto Measurement Failure
- If measurements fail **5 times in a row** (sensor shifted or lost contact):
- A dialog will appear: **"Sensor Position Issue"**
- Tap **Go to Alignment** to re-align the sensor
- If 2+ channels fail **5 times in a row** (sensor shifted or lost contact):
- A dialog will appear: **"Position lost. Restart from alignment"**
- Tap **Realign** to re-align the sensor
- Or tap **Dismiss** to stay on the measurement screen
---
## 6. Spot Measurement
## 6. Single Scan (Spot)
1. Tap the **Spot** button
2. The device will take **5 consecutive measurements** (~4 seconds total)
1. Tap the **Single Scan** button
2. The device will take **5 consecutive measurements** (~4 seconds total, max 8 attempts)
3. A **spinner** will show during measurement
4. The result (trimmed mean of 5 readings) will be displayed immediately
5. The donut chart and Current Measurement card will update
> During Spot measurement, the Auto and Void buttons are disabled.
> During Single Scan, the Auto Scan and Void buttons are disabled.
---
## 7. Void / Catheterize
## 7. Void / Catheterization
1. Tap the **Void / Catheterize** button
1. Tap the **Void / Catheterization** button
2. The current measurement is recorded to the voiding diary
3. The catheter count decreases by 1
4. The bladder level resets to 0
5. A toast message **"Voiding recorded"** will appear
5. A toast message **"Recorded"** will appear
### Managing Catheters
- The catheter count is shown at the top of the screen (hospital icon + number)
- **Tap the catheter count** to add more catheters
- Enter the number and tap **Add**
- Default starting count: **15 catheters**
---
@@ -192,11 +196,6 @@ Tap the **gear icon** at the top right to open settings.
| SG Filter | Savitzky-Golay noise filter on/off |
| Post Max | Maximum sample index for wall detection (filters floor reflections) |
### Saving Settings
- Adjust values using sliders and toggles
- Tap **Save** to apply and close
- Tap **Cancel** to close without changes (note: slider changes apply in real-time)
---
## 9. Navigation
@@ -214,14 +213,13 @@ Tap the **gear icon** at the top right to open settings.
| Problem | Solution |
|---------|----------|
| "Place sensor above pubic bone" won't go away | Make sure the sensor is attached with gel, then press **Start Alignment** |
| Alignment keeps showing "Raise/Lower" | Move the sensor **very slowly**, 1-2mm at a time |
| "Place VesiScan above the pubic bone" won't go away | Make sure the sensor is attached with gel, then press **Start Alignment** |
| Alignment keeps showing "Slide up/down" | Move the sensor **very slowly**, 1-2mm at a time |
| Auto measurement shows "—" | Wait for at least 5 measurements to accumulate |
| Donut chart value doesn't change | Max volume updates every 5 seconds |
| Spot measurement takes too long | Each Spot takes ~4 seconds (5 readings) |
| "Sensor Position Issue" dialog appears | The sensor may have shifted — go to Alignment |
| Single Scan takes too long | Each scan takes ~4 seconds (5 readings) |
| "Position lost" dialog appears | The sensor may have shifted — go to Alignment |
| Bluetooth connection lost | The app will attempt auto-reconnect (up to 5 times) |
| App freezes on measurement screen | Check BLE connection, try disconnecting and reconnecting |
| Pairing dialog appears but app won't proceed | You must tap **Pair** — canceling will block the connection |
---
@@ -230,13 +228,10 @@ Tap the **gear icon** at the top right to open settings.
| LED State | Meaning |
|-----------|---------|
| Green blink (0.5s) | Bluetooth scanning |
| Green on 1s / off 3s | Device idle (not attached) |
| Orange blink (1s) | Sensor Alignment in progress |
| Green solid | Alignment complete (Optimal position) |
| Orange fast blink (3Hz) | Error detected |
| Orange solid | Charging |
| Blue solid | Charging complete |
| State 0 | OFF (default) |
| State 4 | Sensor detached warning |
| State 5 | Alignment searching (orange) |
| State 6 | Alignment complete / In position (green) |
---
+80 -33
View File
@@ -2,7 +2,7 @@
## 작성일: 2026-04-23
## 작성자: dwjang
## 마지막 업데이트: 2026-05-09 (v4) — VBTAND0101
## 마지막 업데이트: 2026-05-11 (v5) — VBTAND0101
---
@@ -383,34 +383,46 @@ Android: estimateBladderVolume6ch(6채널)
[CH0] 0° ← 가장 위 (배꼽쪽, y=21mm)
```
## 방향 안내 로직 — 3모드 (2026-05-07 업데이트)
## 방향 안내 로직 — 3모드 (2026-05-11 업데이트)
개발자 모드 Settings에서 전환 가능: **Gradient** (기본) / Boundary / **SWEEP**
### 조작 흐름 (Start 버튼 필수)
```
화면 진입 → "Place VesiScan above the pubic bone, then press Start"
↓ Start Alignment 버튼 탭 (96dp 대형)
전체 상태 리셋 → 1초 간격 연속 스캔 시작
↓
VERTICAL → LATERAL → GREEN (순차, 건너뛰기 없음)
↓ GREEN 7초 유지 + 3회 연속 실패 안 하면
Start Scanning 버튼 활성화 (초록색)
```
### Gradient 모드 (기본) — 신호 길이 비교
```
시작: 치골 위 → 위로 올림
시작: 치골 위
1단계 VERTICAL:
0~1채널: ch0/ch1 감지→↑, ch2/ch3 감지→↓
2+채널: weighted center gradient
weightedCenter = Σ(ch_pos × len) / Σ(len)
gradient = weightedCenter - 1.5
gradient > 0 → ↓ (ch3쪽 길다 = 방광 아래)
gradient < 0 → ↑ (ch0쪽 길다 = 방광 위)
|gradient| < 0.4 + lenVariation < 0.5 → 상하 OK
gradient > 0 → "Slide down ↓" (ch3쪽 길다 = 방광 아래)
gradient < 0 → "Slide up ↑" (ch0쪽 길다 = 방광 위)
|gradient| < 0.3 + centerDetected >= 4 → "Checking..." → LATERAL
2단계 LATERAL:
|len4 - len5| ≤ 10 sample → 바로 GREEN
lrSymmetry ≥ 0.5 → GREEN
아니면 len 차이 방향으로 안내
|len4 - len5| ≤ 10 sample → "Final check..." → GREEN
lrSymmetry ≥ 0.5 → "Final check..." → GREEN
아니면 "Slide left ←" / "Slide right →"
3단계 GREEN:
CV ≤ 0.12 (10회마다 +0.03 완화)
|lrDev| ≤ 0.35
둘 다 통과 → Green!
CV만 간당간당 → "Almost there..." (GREEN에 머물며 재시도)
CV ≤ threshold (V0=0.07, V1/V2=0.10, 5회마다 +0.03 완화)
|lrDev| ≤ 0.20
둘 다 통과 → "In position!" (7초 hold)
3회 연속 실패 → "Position lost. Restart above pubic bone." + 스캔 중지
```
### SWEEP 모드 (CKLaw) — 위에서 아래로 쓸어내리기
@@ -446,26 +458,44 @@ Android: estimateBladderVolume6ch(6채널)
| 상수 | 값 | 의미 |
|------|-----|------|
| DEFAULT_CV_THR | 0.12 | 기본 CV 임계 (10회마다 +0.03 완화) |
| DEFAULT_LR_DEV_THR | 0.35 | LR 비대칭 임계 |
| DEFAULT_CV_THR | V0=0.07, V1/V2=0.10, 기타=0.08 | 기본 CV 임계 |
| CV 완화 | +0.03 per 5 scans | 5회마다 임계값 증가 |
| DEFAULT_LR_DEV_THR | 0.20 | LR 비대칭 임계 |
| 절대 LR 차이 | ≤10 sample | LATERAL 즉시 통과 |
| R-peak tolerance | 1.5mm | SWEEP 모드 GREEN gate |
| 디바운스 | 1.5초 | phase 전환은 즉시, 같은 phase 힌트만 디바운스 |
| GREEN hold | 7초 | GREEN 진입 후 최소 유지 시간 |
| GREEN exit | 3회 연속 실패 | hold 이후 이탈 조건 |
| 디바운스 | 1.5초 (같은 phase) / 3초 (phase 전환) | |
| Phase 보호 | 3스캔 | phase 전환 후 힌트 강제 유지 |
### 힌트 텍스트 (모든 방향 힌트에 화살표 포함)
| 상황 | 텍스트 |
|------|--------|
| 상하 이동 | "Slide up ↑" / "Slide down ↓" / "Slide up slightly ↑" / "Slide down slightly ↓" |
| 좌우 이동 | "Slide left ←" / "Slide right →" |
| 확인 중 | "Checking..." |
| 최종 확인 | "Final check..." |
| 최적 위치 | "In position!" |
| 위치 이탈 | "Position lost. Restart above pubic bone." |
| 센서 미부착 | "Sensor detached. Restart above pubic bone." |
## 연속 스캔 동작
```
"Start Scanning" 버튼 탭
"Start Alignment" 버튼 탭 (waitingForStart → false)
↓
전체 상태 리셋 (phase, score, debounce, greenFail 등)
↓
즉시 첫 스캔 (maa 6채널)
↓ 결과: CV, 방향, Score 표시
3초 대기
↓ 결과: CV, 방향, Score 표시 (28sp 대형 힌트)
1초 대기
↓
자동 재스캔 (repeatCount 증가 → threshold 완화)
자동 재스캔 (scanCount 증가 → CV threshold 완화)
↓
... 반복 (사용자가 Stop 누를 때까지)
... 반복 (GREEN 도달 or 이탈 시 자동 중지)
is_pass = true → Lock → "Start Measurement" 활성화
is_pass = true → Lock → 7초 hold → "Start Scanning" 버튼 활성화
```
---
@@ -504,20 +534,22 @@ Spot 버튼 탭
화면에 "305 ml" 표시! (도넛차트 + 레벨 업데이트)
```
## Auto 모드 (2026-05-07 업데이트)
## Auto Scan 모드 (2026-05-11 업데이트)
별도 Auto 버튼으로 자동 반복 측정:
- 1.5초 간격 자동 측정
- 첫 5회: "Measuring... (n/5)" 로딩 표시
- 5회 이상: **sliding window trimmed mean** (10개 윈도우) → 안정된 값 표시
- Stop 버튼으로 종료
"Auto Scan" 버튼으로 자동 반복 측정:
- 최소 800ms 간격 자동 측정 (BLE maa throttle 보장)
- 첫 5회: "—" 표시 (데이터 수집 중)
- 5회 이상: **sliding window trimmed mean** (10개 윈도우, 최대/최소 각 1개 제외 → 8개 평균)
- "Stop Scan" 버튼으로 종료
- Auto fail: 2ch+ missing 5회 연속 → "Position lost. Restart from alignment" 다이얼로그
## Spot 모드 (2026-05-07 업데이트)
## Single Scan 모드 (2026-05-11 업데이트)
Spot 버튼 1탭 → **3회 측정 → median** (중간값 깜빡임 없음):
- 최대 6회까지 재시도 (유효값 3개 수집 목표)
- 측정 간격 300ms (BLE 겹침 방지)
"Single Scan" 버튼 1탭 → **5회 측정 → trimmed mean**:
- 최대 8회까지 재시도 (유효값 5개 수집 목표, 최소 3개)
- 측정 간격: maa 800ms throttle이 간격 보장
- 중간 값 화면 미표시 (isSpotInProgress 플래그)
- 1초 쿨다운 후 재사용 가능
## 측정 실패 시 자동 재시도
@@ -1058,7 +1090,15 @@ Start Scanning (3초 간격 자동 반복)
║ PASS → LED 6 (ALIGN_COMPLETE) + Lock ║
╚═══════════════════════════════════════╝
↓ Green!
"Start Measurement" 버튼 활성화
"Start Scanning" 버튼 활성화 (초록, 96dp)
```
### 태블릿 대응
```
fontScale = (screenWidth / 360f).coerceIn(1.0, 1.8)
→ 폰(360dp)=1.0배, 태블릿(600dp+)=~1.6배
버튼/힌트 텍스트, InfoCard, 배터리/카테터 표시 등 전체 적용
```
### 패킷 드랍 대응
@@ -1267,6 +1307,13 @@ ADC 파싱 시 Big/Little Endian을 자동 감지:
| ADC CSV 저장 | ✅ 완료 | 세션별 파일 |
| Canvas 하복부 실루엣 | ✅ 완료 | PNG 제거, CKLaw Bezier torso |
| i18n (한국어/영어) | ✅ 완료 | strings.xml |
| UI 텍스트 통일 | ✅ 완료 | revised 문서 반영 (Slide up ↑, In position! 등) |
| 태블릿 대응 | ✅ 완료 | fontScale, 비율 기반 레이아웃, 힌트/버튼 확대 |
| 페어링 다이얼로그 | ✅ 완료 | bonding 대기 중 안내 표시 |
| 앱 아이콘 | ✅ 완료 | 방광이 캐릭터 PNG (5 density) |
| Placement 화살표 수정 | ✅ 완료 | 디바운스/보호 중에도 화살표 항상 표시 |
| GREEN 안정성 | ✅ 완료 | 7초 hold + 3회 연속 실패 exit + phase 보호 |
| IMU 모션 감지 | 🔜 예정 | vesiscanbasicAndroid에서 포팅 예정 |
| Placement 팬텀 테스트 | 🔜 예정 | Gradient vs SWEEP 비교 |
| 5-Anchor calibration | 🔜 검토 | CKLaw 전체 포팅 여부 결정 |
| 인체 임상 검증 | 🔜 예정 | 상수 재조정 |
+205
View File
@@ -0,0 +1,205 @@
# 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
```