Files
VesiscanClinicalAndroid/ppt/alignment_mode_presentation_outline.md
T
dw.jang bcbc7b440c refactor: 데모 브랜치 패키지 통일 com.example.medilightv2android → com.medithings.vesiscan
사용자앱 (feature/tab-navigation) 과 패키지 이름 일치. namespace + applicationId
둘 다 com.medithings.vesiscan 로 변경 (기존 데모 앱은 재설치 필요).

## 변경 범위
- Kotlin 148 파일: package + import 문 (714 occurrences)
- 디렉토리 이동: com/example/medilightv2android → com/medithings/vesiscan
  (main, test, androidTest 각각)
- app/build.gradle.kts: namespace, applicationId
- docs/FLAVOR_DEMO_STABLE.md: 참조 갱신
- V41DetectorCH4Test: BvDispatchResult.methodChosen → method (dto field name fix)

## 주의
- applicationId 가 바뀌므로 기존 데모 앱 (com.example.medilightv2android.demo) 은
  Android 관점에서 다른 앱으로 취급 — 재설치 시 PIN/설정 초기화됨.
- Fresh install 권장. 기존 앱 (com.example...) 은 별도로 uninstall 필요.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 14:30:43 +09:00

452 lines
14 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.
# Sensor Alignment Mode — 발표 자료 outline
## 슬라이드 구조 (총 15-18장 권장)
---
### 슬라이드 1: 제목
**Sensor Alignment Mode**
방광 측정용 6채널 piezo 센서의 자동 정렬 가이드
- VesiScan-Basic 앱
- 발표자: 장동우
- 2026-06-30
---
### 슬라이드 2: Why Alignment Matters
**문제: 센서 위치가 살짝만 어긋나도 BV 측정 정확도 급락**
| 정렬 오차 | center wall 검출 | BV bias | 임상 결과 |
|---|---|---|---|
| 0 cm (정렬) | 100% | ±10% | 정확 |
| 2 cm 오프셋 | 40% | +50% (lateral fail → lr=1.0) | 과대 |
| 3 cm 오프셋 | 31% | +40% | 과대 |
| 4 cm 오프셋 | 13% | -120 mL | 측정 불가 |
→ **임상 적용 = alignment 가 first-class problem**
(실측 데이터: kai SUPINE 195 mL, 2 devices × 5 alignments × 31 cycles)
---
### 슬라이드 3: Hardware Background
**6채널 piezo 센서 배치 (정면 단면도)**
```
┌─────────────────┐
│ 센서 패드 (probe)│
│ │
│ CH0 ● (top, 20.1mm 위)
│ CH1 ● (12.8mm 위)
│ CH2 ● (7.0mm 위)
│ CH3 ● (basis, 0mm)
│ CH4● ●CH5 (lateral, ±10mm 좌우)
└─────────────────┘
```
- **Center channels (CH0-3)**: SI (superior-inferior, 위-아래) 축
- **Lateral channels (CH4-5)**: LR (left-right) 축
- 각 channel = 펄스 송신 → 에코 수신 → ant/post wall index 검출
- 100 samples / channel, ~1.98 mm/sample
---
### 슬라이드 4: 정렬의 목표
**좋은 측정을 위한 sensor 위치 조건:**
1. **CH3 (가장 아래) 가 방광 위에 있어야** (anatomical anchor)
- CH3 미검출 → 센서가 치골 아래 (방광 못 봄)
- CH3 검출 → 방광 lower pole 위치 확인됨
2. **모든 center channel (CH0-3) 이 방광 단면 위에 있어야**
- 일부만 검출 → 방광 edge 에 걸침 → BV 계산 부정확
3. **Lateral (CH4, CH5) 이 좌우 균형:**
- |u4 - u5| ≤ 8 samples (~16mm) 면 균형
- 차이 크면 한쪽으로 치우침
이 3 조건을 **실시간 자동 가이드** 로 충족시키는 게 alignment mode 의 역할.
---
### 슬라이드 5: V1 vs V2 비교
| | V1 (legacy) | V2 (Method D) |
|---|---|---|
| **알고리즘** | computePlacementGuide (단발) | RollingAligner + AlignGuide4Stage |
| **데이터 누적** | 1 cycle 즉시 판단 | N-cycle sliding window 평균 |
| **Phase** | 없음 (score 0~100 단일) | 5-phase state machine |
| **노이즈 robust** | 약함 (cycle 마다 score flap) | 강함 (10-cycle 평균 후 결정) |
| **임상 적용** | 일반 사용자 home use | clinical R&D / 정밀 측정 |
**현재 default:** 일반 사용자 = V1, 임상 clinical session = V2
발표 focus = **V2 (Method D)**
---
### 슬라이드 6: V2 전체 흐름
```
[BLE 연결] → [Personalization] → [Placement V2 진입] → [4-Phase 진행] → [측정]
↓
매 cycle mtb 송신 (~1Hz)
↓
6채널 raw + IMU 응답
↓
RollingAligner (sliding window)
↓
MethodDRunner.detectMultichannel
↓
AlignGuide4Stage (state machine)
↓
AdvisorState (action, hint, msg)
↓
UI 화살표 + 텍스트 표시
```
---
### 슬라이드 7: RollingAligner — 데이터 누적
**왜 평균을 내는가?**
Single cycle 데이터의 noise:
- per-cycle wall index 변동: ±5 samples (~10mm)
- per-cycle BV 변동: ±50 mL
- → 매 cycle 판단하면 화살표 마구 흔들림
**Sliding window 평균:**
- Window size = N cycles (phase 별 조정)
- Buffer 6채널 × 100 sample 그대로 평균 → "avg" signal
- avg → MethodDRunner 통과 → 안정적 wall 위치
```kotlin
fun push(raw6) {
buf.addLast(raw6)
while (buf.size > windowSize) buf.removeFirst()
val avg = average(buf) // 6 × 100 평균
val dets = MethodDRunner.detectMultichannel(avg)
...
}
```
**Window size:**
- INITIAL_ACCUM / VERTICAL / CH3_STABILIZE: **10**
- LR_BALANCE: **5** (반응성 우선)
- FINAL_CONFIRM: **5** (재확인)
---
### 슬라이드 8: Method D — Wall Detection Algorithm
**Per-channel pipeline:**
```
raw signal (100 samples)
↓
1. Heavy SG denoise (강한 smoothing — wall 위치용)
2. Light SG denoise (약한 smoothing — peak 보존)
↓
3. TGC pipeline (Time Gain Compensation)
- depth attenuation 보정
- adaptive ratio_min = 0.1
↓
4. OS-CFAR detector (ordered statistic — constant false alarm)
- cos(beam_angle) 로 otsu_ratio per channel 보정
- urine span detection (low-echo 구간 찾기)
- shoulder prominence γ = 1.5
↓
5. Cross-channel ant tie-break (in-place)
- 이웃 채널의 ant 후보들 비교
- top1/top2 ratio < 1.3 OR z-position |Δ| < 8mm
- → 같은 depth 의 ant 우선 선택 (해부학 일관성)
↓
MethodDResult { ant, post, urineLen, score }
```
**Python `for_app_share` reference 와 1:1 정렬** (2026-06-30 commit b733d4f).
---
### 슬라이드 9: AlignGuide4Stage — 4-Phase 상태 기계
```
┌────────────────────────────────────────────────────────────┐
│ │
│ ① INITIAL_ACCUM (10 cycles) │
│ "잠시 그대로 두세요 (n/10)" │
│ 데이터 누적만, 아직 판단 X │
│ ↓ │
│ ② VERTICAL_CLIMB │
│ CH3 미검출 → "↑ 위로 조금씩 올리세요" │
│ CH3 3 연속 검출 → 다음 │
│ ↓ │
│ ③ CH3_STABILIZE (10 cycles) │
│ CH3 안정 유지 확인 │
│ CH3 잃음 → ②로 복귀 │
│ bestCenterSet 저장 │
│ ↓ │
│ ④ LR_BALANCE │
│ lost channel → ↑/↓ 회복 (3 연속 lost 필요) │
│ |u4-u5| ≤ 8 → 균형, ⑤ 진입 │
│ |u4-u5| > 8 → ← or → 작은쪽으로 │
│ ↓ │
│ ⑤ FINAL_CONFIRM (5 cycles) │
│ buf clear → fresh 5 cycle 누적 │
│ |u4-u5| ≤ 8 유지 → "✓ 정렬 완료!" │
│ 실패 → ④로 │
│ │
└────────────────────────────────────────────────────────────┘
```
---
### 슬라이드 10: Phase 1 — INITIAL_ACCUM
**역할:** RollingAligner buffer 를 10 cycles 채우기.
**왜 필요한가?**
- buffer 1개 frame 만 있으면 평균 = single cycle (noise 그대로)
- 10 frame 평균 → σ가 √10 ≈ 3배 감소
**UI:**
- 화살표 없음
- "잠시 그대로 두세요 (5/10)" 진행률 표시
- 사용자는 그냥 가만히 들고 있기
**기간:** ~10초 (1Hz × 10 cycles)
---
### 슬라이드 11: Phase 2 — VERTICAL_CLIMB
**역할:** CH3 (가장 아래 채널) 가 방광 위에 오도록 유도.
**Logic:**
```kotlin
if (!s.ch3) {
verticalHitStreak = 0 // false detection 거부
return MOVE_UP // "↑ 위로 조금씩"
}
verticalHitStreak++
if (verticalHitStreak < 3) {
return STOP // "✓ 확인 중 (n/3)"
}
phase = CH3_STABILIZE
```
**핵심:**
- CH3 3 회 연속 검출되어야 다음 phase
- 1회만 검출 (false echo 가능성) → 일시 멈춤 + 확인 중
**UI:**
- 화살표: ↑
- 메시지: "위로 조금씩 올리세요" / "확인 중 (n/3)"
---
### 슬라이드 12: Phase 3 — CH3_STABILIZE
**역할:** CH3 가 안정적으로 잡힌 위치인지 10 cycles 확인.
**Logic:**
```kotlin
if (!s.ch3) {
phase = VERTICAL_CLIMB // 잃음 → 처음으로
return
}
stabilizeCount++
if (stabilizeCount >= 10) {
bestCenterSet = curSet // 현재 잡힌 채널 set 저장
phase = LR_BALANCE
}
```
**핵심:**
- bestCenterSet 저장: 이 시점 잡힌 center channel (예: {1, 2, 3} 또는 {0, 1, 2, 3})
- 이후 LR_BALANCE 에서 이 set 유지 여부 확인
**UI:**
- 화살표: ✓ (멈춰 있어요)
- 메시지: "좋아요! 그 위치 유지 (n/10)"
---
### 슬라이드 13: Phase 4 — LR_BALANCE (좌우 균형)
**역할:** lateral channel (CH4, CH5) 의 신호 차이로 좌우 균형 잡기.
**Logic 1 — 채널 lost 감지:**
```kotlin
val lost = bestCenterSet - curSet
if (lost.isNotEmpty()) {
lostStreak++
if (lostStreak >= 3) { // hysteresis (6/30 추가)
if (3 in lost) return MOVE_UP // CH3 잃음 → 위
else return MOVE_DOWN // 다른 채널 잃음 → 아래
}
return STOP // noise, 무시
} else {
lostStreak = 0
}
```
**Logic 2 — 좌우 균형:**
```kotlin
val imbal = abs(u4 - u5)
if (imbal <= 8) { // LAT_TOL (~16mm)
phase = FINAL_CONFIRM
return STOP "✓ 균형 도달"
}
if (u4 < u5) return MOVE_RIGHT // CH4 신호 짧음 → 우측이 멀음
else return MOVE_LEFT // CH5 신호 짧음 → 좌측이 멀음
```
**Deadband:** 방향 반전 시 imbal 이 이전 + 3 이상 증가 + 2 연속 → 반전 (oscillation 차단)
---
### 슬라이드 14: Phase 5 — FINAL_CONFIRM
**역할:** 마지막 5 cycles 로 정렬 안정성 재확인.
**Logic:**
```kotlin
buf.clear() // RollingAligner buffer 초기화 (fresh window)
finalConfirmCount = 0
// 5 cycles 동안 imbal ≤ 8 유지하면 STOP
while (finalConfirmCount < 5) {
if (imbal > 8 || ch3 lost) {
phase = LR_BALANCE // 실패 → 복귀
return
}
finalConfirmCount++
return STOP "✓ 최종 확인 중 (n/5)"
}
return STOP "✓ 정렬 완료 — 측정 가능"
```
**핵심:**
- buf clear → 이전 cycles 영향 제거
- 5 fresh cycles 로 안정성 재검증
- 실패 시 LR_BALANCE 로 즉시 복귀
---
### 슬라이드 15: 최근 개선 (2026-06-30)
**1. lost streak hysteresis (50b8620, b2546d1)**
문제: 실측 데이터 (244 cycle 정렬 세션) 분석:
- LR_BALANCE 에서 |u4-u5| 항상 8 이하 (좌우 균형 OK)
- 하지만 CH1, CH3 가 cycle 마다 15%, 12% 노이즈 깜빡임
- 매 cycle `lost` 감지 → 화살표 띄움 → 다음 cycle 복구 → STOP 반복
- **사용자 체감: "어디로 옮길지 모르겠음"**
해결: 같은 채널이 3 연속 commit 에서 lost 여야 회복 모드.
- 화살표 flap rate 55% → ~5% 예상
---
**2. 그래프 ↔ 알고리즘 일치 (9e52019, 886d403)**
문제:
- 그래프: real-time single cycle raw ADC
- 알고리즘 입력: 10-cycle averaged signal
- 사용자가 그래프 보면서 "wall 보이는데 알고리즘 미검출" 또는 반대
해결: V2 활성 시 그래프도 RollingAligner.lastAvg 사용.
- `PlacementWaveformChart(overrideRaw = avgRaw)`
- 알고리즘 입력 == 시각화 신호 == 사용자 판단
---
### 슬라이드 16: 실측 검증 — kai 195 mL ALIGNMENT 세션
**11 sessions: 2 devices × 5 alignments × ~31 cycles**
| Step | center detect % | BV bias |
|---|---|---|
| 0 cm | 70% | -36 mL |
| 1 cm | 62% | -28 |
| 2 cm | 40% | +55 (lr=1.0 fallback) |
| 3 cm | 31% | +42 |
| 4 cm | 13% | -120 (fail) |
**Key finding:**
- Method D 자체는 정상 동작 (0 cm: center 70%, 1 cm: 62%)
- 2 cm 부터는 detection 불완전 → BV 부정확
- **alignment 가 충실히 0-1 cm 수렴해야 임상 정확도 보장**
→ V2 의 4-phase 가 0-1 cm 자동 수렴하는지가 핵심 검증 포인트
---
### 슬라이드 17: 알고리즘 파라미터 (Tunable)
`AlignmentConstants.kt`:
- `LAT_TOL = 8` samples (~16mm) — 좌우 균형 허용 오차
- `ANT_TIEBREAK_RATIO = 1.3` — cross-channel ant tiebreak
- `ANT_NEARFIELD_TOL_MM = 8.0`
`AlignGuide4Stage.kt`:
- `accumKVertical = 10` cycles
- `accumKLateral = 5` cycles
- `accumKConfirm = 5` cycles
- `verticalHitRequired = 3`
- `lrReverseDeadband = 3` samples
- `lrReverseConsecutive = 2`
- `lostStreakRequired = 3` ← **신규 (6/30)**
- `finalConfirmTarget = 5`
`MethodDParams.kt`:
- `oscfarWin = 9` (이전 5)
- `shoulderPromGamma = 1.5` (이전 1.0)
- `otsuRatio` per channel = base × cos(angle)
dev mode 에서 슬라이더로 실시간 튜닝 가능.
---
### 슬라이드 18: 결론 + 향후 과제
**현재 상태:**
- V2 (Method D) 4-phase 알고리즘 안정 동작
- 실측 데이터로 검증 완료 (kai 11 sessions)
- 최근 lost streak / 그래프 일치 개선
- Python reference 와 1:1 정렬
**향후 검증/개선:**
1. **임상 N=20+ 환자** 데이터로 alignment 수렴률 / 시간 측정
2. **3 cm 이상에서도 측정 거부** logic 강화 (현재는 측정 진행됨)
3. **자동 measurement 트리거** — 정렬 완료 시 사용자 액션 없이 측정 시작
4. **alignment 실패 mode** 처리 — 30초 내 수렴 못 하면 가이드 다이얼로그
5. **POSTURE-aware** — supine 외 자세에서 alignment 동작 검증
---
## 추가 자료 (appendix)
### A. 코드 위치
- `app/.../managers/AlignmentAdvisorV2.kt` — main state machine
- `app/.../walldetect/MethodDRunner.kt` — wall detection
- `app/.../walldetect/algo/methodd/` — Method D internals
- `app/.../ui/views/monitoring/PlacementGuideView.kt` — UI 통합
### B. 검증 툴
- `tools/analyze_csv.py` — App CSV → Python pipeline 비교
- ablation mode: DPS / lr_floor 등 각 fix 영향
### C. 관련 commits
- `02bed02` (appshare) — Method D 02bed02 원본
- `64c15a8` (demo) — Method D 02bed02 Kotlin 포팅
- `b733d4f` (feature) — BV Python 1:1 정렬
- `50b8620` (feature) — lost streak hysteresis
- `9e52019` — 그래프 10-cycle 평균 표시