diff --git a/USER_GUIDE.md b/USER_GUIDE.md index dab369e..08a8597 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -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) | --- diff --git a/VesiScan_Android_Pipeline_Summary.md b/VesiScan_Android_Pipeline_Summary.md index 761d868..c953b2c 100644 --- a/VesiScan_Android_Pipeline_Summary.md +++ b/VesiScan_Android_Pipeline_Summary.md @@ -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 전체 포팅 여부 결정 | | 인체 임상 검증 | 🔜 예정 | 상수 재조정 | diff --git a/docs/ALGORITHM_COMPARISON.md b/docs/ALGORITHM_COMPARISON.md new file mode 100644 index 0000000..5226373 --- /dev/null +++ b/docs/ALGORITHM_COMPARISON.md @@ -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 +```