docs: Python↔Android 1:1 검증 결과 + BLE 재연결 시스템 상세 문서화
- 전수 상수 비교 테이블 (V0/V1/V2 좌표, 각도, 음향 상수, lr_ratio) - 알고리즘 수준 비교 (전처리, low-echo, lr_ratio, BV 추정) - 결론: 모든 항목 완벽 일치 (Adaptive TGC는 Python에도 미포함) - BLE 재연결 시스템 흐름도 + Watchdog + GATT 에러 경로 설명 - BLE 디버그 로거 사용법 - GreenZoneConstants.kt 주석 오류 수정 (1600→1150) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -632,7 +632,202 @@ Android: app내부저장소/vesiscan_logs/YYYY-MM-DD.jsonl
|
||||
|
||||
---
|
||||
|
||||
# 12. 향후 과제
|
||||
# 12. Python ↔ Android 1:1 검증 결과
|
||||
|
||||
## 검증 대상
|
||||
|
||||
| Python 파일 | Android 파일 | 검증 결과 |
|
||||
|------------|-------------|----------|
|
||||
| config_6ch.py | PiezoBVEstimator.kt (PiezoHW) | ✅ 완벽 일치 |
|
||||
| denoising.py | PiezoEchoAnalyzer.kt (denoise, sgSmooth) | ✅ 완벽 일치 |
|
||||
| low_echo_detection_method_b.py | PiezoEchoAnalyzer.kt (detectLowEcho) | ✅ 완벽 일치 |
|
||||
| bv_estimation.py (6ch) | PiezoBVEstimator.kt (estimateBladderVolume6ch) | ✅ 완벽 일치 |
|
||||
| span_utils.py | PiezoEchoAnalyzer.kt (findPeaks1D, mergeClose) | ✅ 완벽 일치 |
|
||||
|
||||
## 상수 비교 (전수 검증)
|
||||
|
||||
### 하드웨어 프리셋 (config_6ch.py ↔ PiezoHW)
|
||||
|
||||
| 항목 | Python V0 | Android V0 | Python V1 | Android V1 | Python V2 | Android V2 |
|
||||
|------|-----------|------------|-----------|------------|-----------|------------|
|
||||
| CH0 z | 21.0 | 21.0 ✅ | 20.1 | 20.1 ✅ | 19.3 | 19.3 ✅ |
|
||||
| CH1 z | 14.0 | 14.0 ✅ | 12.8 | 12.8 ✅ | 13.0 | 13.0 ✅ |
|
||||
| CH2 z | 7.0 | 7.0 ✅ | 7.0 | 7.0 ✅ | 6.7 | 6.7 ✅ |
|
||||
| CH3 z | 0.0 | 0.0 ✅ | 0.0 | 0.0 ✅ | 0.0 | 0.0 ✅ |
|
||||
| CH4/5 z | 10.5 | 10.5 ✅ | 9.9 | 9.9 ✅ | 9.85 | 9.85 ✅ |
|
||||
| CH0 deg | 0.0 | 0.0 ✅ | 6.89 | 6.89 ✅ | 0.0 | 0.0 ✅ |
|
||||
| CH1 deg | 0.0 | 0.0 ✅ | 0.0 | 0.0 ✅ | -6.89 | -6.89 ✅ |
|
||||
| CH2 deg | 0.0 | 0.0 ✅ | -6.89 | -6.89 ✅ | -13.66 | -13.66 ✅ |
|
||||
| CH3 deg | 0.0 | 0.0 ✅ | -13.66 | -13.66 ✅ | -20.20 | -20.20 ✅ |
|
||||
| CH4/5 deg | 0.0 | 0.0 ✅ | -6.87 | -6.87 ✅ | -6.87 | -6.87 ✅ |
|
||||
| CH4/5 LR | 0.0 | 0.0 ✅ | ∓3.42 | ∓3.42 ✅ | ∓3.42 | ∓3.42 ✅ |
|
||||
|
||||
### 음향 상수
|
||||
|
||||
| 상수 | Python (6ch) | Android | 일치 |
|
||||
|------|-------------|---------|------|
|
||||
| DISTANCE_PER_SAMPLE | 1.771 mm | 1.771 mm | ✅ |
|
||||
| DELAY_OFFSET_MM | 6.85 mm | 6.85 mm | ✅ |
|
||||
| AREA_K | π/4 | π/4 | ✅ |
|
||||
|
||||
### Low-echo Detection 상수
|
||||
|
||||
| 상수 | Python | Android | 일치 |
|
||||
|------|--------|---------|------|
|
||||
| LOW_ECHO_AMP | 1150 | 1150 | ✅ |
|
||||
| LOW_MIN_LEN | 3 | 3 | ✅ |
|
||||
| MERGE_GAP_MAX | 3 | 3 | ✅ |
|
||||
| PEAK_SEARCH_WIN | 20 | 20 | ✅ |
|
||||
| POST_MAX_IDX | 80 | 80 | ✅ |
|
||||
| MIN_PEAK_MARGIN | 30 | 30 | ✅ |
|
||||
| MIN_URINE_LEN | 3 | 3 | ✅ |
|
||||
|
||||
### LR Ratio 상수
|
||||
|
||||
| 상수 | Python | Android | 일치 |
|
||||
|------|--------|---------|------|
|
||||
| DEFAULT_LR_RATIO | 1.0 | 1.0 | ✅ |
|
||||
| LR_RATIO_NO_DETECTION | 1.0 | 1.0 | ✅ |
|
||||
| LR_RATIO_INVALID | 1.2 | 1.2 | ✅ |
|
||||
| LR_PRIOR | 1.2 | 1.2 | ✅ |
|
||||
|
||||
## 알고리즘 비교 (로직 수준)
|
||||
|
||||
### 전처리 (denoising.py ↔ PiezoEchoAnalyzer.denoise)
|
||||
|
||||
```
|
||||
Python: denoise() → sg_smooth() (SG 필터만, TVD 없음)
|
||||
Android: denoise() → sgSmooth() (SG 필터만, TVD 없음)
|
||||
|
||||
SG 계수: [-3, 12, 17, 12, -3] / 35 (양쪽 동일)
|
||||
Edge 처리: 양 끝 2샘플에 2차 다항식 피팅 (양쪽 동일)
|
||||
```
|
||||
|
||||
**Adaptive TGC**: Python 공유 코드에 미포함. 원은지님이 언급했으나 `denoising.py`에 TGC 코드 없음.
|
||||
|
||||
### Low-echo Detection (Prominence 기반)
|
||||
|
||||
```
|
||||
1. low_mask = sg ≤ 1150 (양쪽 동일 ✅)
|
||||
2. span 추출 (≥3칸) (양쪽 동일 ✅)
|
||||
3. 인접 span 합치기 (gap≤3) (양쪽 동일 ✅)
|
||||
- gap 내 peak > 1155면 합치지 않음
|
||||
4. 양쪽 벽 prominence 탐색 (양쪽 동일 ✅)
|
||||
- ant: 오른쪽 valley 기준
|
||||
- post: 왼쪽 valley 기준
|
||||
- 최대 3개 후보 중 prominence 최대
|
||||
5. post > 80 → 뒤쪽 절반에서 재탐색 (양쪽 동일 ✅)
|
||||
6. urineLen = post - ant - 1 (양쪽 동일 ✅)
|
||||
```
|
||||
|
||||
### LR Ratio 계산 (compute_lr_ratio)
|
||||
|
||||
```
|
||||
1. Center AP 직경: CH1/CH2의 (post-ant)×cos(θ) 평균 (양쪽 동일 ✅)
|
||||
2. Lateral 직경:
|
||||
P = cos(α)·cos(β), Q = cos(α)·sin(β)
|
||||
D_lateral = (dFar-dNear)·P
|
||||
y_mid = sensor_x + d_mid·Q (양쪽 동일 ✅)
|
||||
3-A. 2개 lateral → 타원 방정식:
|
||||
u = yL·yR·(yR-yL) / denom
|
||||
d = -(u·(rL²-1) + yL²) / (2·yL)
|
||||
lr_raw = 2√u / D_center (양쪽 동일 ✅)
|
||||
3-B. 1개 lateral → 중심 가정:
|
||||
b = |y_mid| / √(1-r²)
|
||||
lr_raw = b / (D_center/2) (양쪽 동일 ✅)
|
||||
4. Shrinkage:
|
||||
confidence = ((1-avg_ratio)/0.10).clamp(0,1)
|
||||
lr = 1.2 + (lr_raw-1.2)·confidence (양쪽 동일 ✅)
|
||||
```
|
||||
|
||||
### BV 추정 (Frustum + Cap)
|
||||
|
||||
```
|
||||
1. Edge 필터: CH3 < CH1/CH2 × 0.9 → short (양쪽 동일 ✅)
|
||||
2. sample → mm: d = 6.85 + idx × 1.771 (양쪽 동일 ✅)
|
||||
3. 직경: D = (dPost-dAnt) × cos(θ) (양쪽 동일 ✅)
|
||||
4. 단면적: S = (π/4) × D² × lr_ratio (양쪽 동일 ✅)
|
||||
5. y좌표: y = sensorZ + dMid × sin(θ) (양쪽 동일 ✅)
|
||||
6. y 오름차순 정렬 (양쪽 동일 ✅)
|
||||
7. 포물선 피팅: Phase 0(edge-peak) → Phase 1(1.5σ) → Phase 2(b_eff)
|
||||
(양쪽 동일 ✅)
|
||||
8. Frustum: V = (h/3)·(S₁+S₂+√(S₁·S₂)) (양쪽 동일 ✅)
|
||||
9. Bottom cap: V = S·h/2 + π·h³/6 (항상 구) (양쪽 동일 ✅)
|
||||
10. Top cap: cone(정상) or sphere(edge short) (양쪽 동일 ✅)
|
||||
```
|
||||
|
||||
## 미구현 항목
|
||||
|
||||
| 항목 | Python | Android | 비고 |
|
||||
|------|--------|---------|------|
|
||||
| Adaptive TGC | 공유 코드에 없음 | 없음 | 원은지님 확인 필요 |
|
||||
| Phantom preset (lrRatio) | 있음 | 없음 | 디버깅용, 앱 불필요 |
|
||||
| Hybrid mode (coord) | 있음 (disabled) | 없음 | Python에서도 미사용 |
|
||||
|
||||
---
|
||||
|
||||
# 13. BLE 자동 재연결 시스템
|
||||
|
||||
## 흐름도
|
||||
|
||||
```
|
||||
정상 통신 중 (5초마다 msn/rsn 주고받음)
|
||||
↓
|
||||
기기와 멀어짐 (RSSI -80 → -88 → 응답 없음)
|
||||
↓
|
||||
[경로 A] Android GATT 에러 콜백 (status≠0)
|
||||
→ GATT_ERROR 로그
|
||||
→ wasConnected=true → scheduleAutoReconnect()
|
||||
|
||||
[경로 B] GATT 콜백 안 옴 (좀비 연결)
|
||||
→ Watchdog Thread (별도 스레드, 5초마다 체크)
|
||||
→ 15초 무응답 → WATCHDOG_TIMEOUT
|
||||
→ 강제 disconnect → scheduleAutoReconnect()
|
||||
↓
|
||||
scheduleAutoReconnect()
|
||||
├── 1차: 3초 후 → BLE 스캔 8초 (기기 이름/MAC 매칭)
|
||||
├── 2차: 5초 후 → BLE 스캔 8초
|
||||
├── 3차: 10초 후 → BLE 스캔 8초
|
||||
├── 4차: 10초 후 → BLE 스캔 8초
|
||||
└── 5차: 10초 후 → BLE 스캔 8초
|
||||
↓ 발견 시
|
||||
connectGatt(autoConnect=false) → 빠른 연결 (1~2초)
|
||||
↓ 5회 모두 실패 시
|
||||
빨간 배너 "Could not reconnect" + 수동 Reconnect 버튼
|
||||
```
|
||||
|
||||
## Watchdog 상세
|
||||
|
||||
- **실행 위치**: 별도 daemon Thread (BLE handler 블로킹 무관)
|
||||
- **체크 주기**: 5초
|
||||
- **타임아웃**: 15초 (마지막 RX 기준)
|
||||
- **GATT close**: watchdog Thread에서 직접 호출 (스레드 안전)
|
||||
- **UI 갱신**: handler.post로 메인 스레드에서 처리
|
||||
|
||||
## GATT 에러 경로 (핵심 버그 수정)
|
||||
|
||||
Android BLE는 disconnect 시 `STATE_DISCONNECTED` 대신 `GATT 에러(status≠0)`로
|
||||
콜백을 보내는 경우가 많음. 기존에는 에러 경로에서 재연결을 시작하지 않아서
|
||||
첫 끊김 시 재연결이 안 되는 버그가 있었음.
|
||||
|
||||
수정: `wasConnected=true`이면 에러 경로에서도 `scheduleAutoReconnect()` 호출.
|
||||
|
||||
## BLE 디버그 로거
|
||||
|
||||
모든 TX/RX 패킷을 파싱된 형태로 기록:
|
||||
|
||||
```
|
||||
TX: "maa 6ch measure [8B]"
|
||||
RX: "rsn battery=3850mV (100%) [8B]"
|
||||
RX: "reb 100samples [208B]"
|
||||
RX: "raa all-ch complete [8B]"
|
||||
```
|
||||
|
||||
내보내기: Downloads/VesiScan_BLE_날짜시간.log
|
||||
|
||||
---
|
||||
|
||||
# 14. 향후 과제
|
||||
|
||||
| 항목 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
|
||||
@@ -36,7 +36,7 @@ object GreenZoneConstants {
|
||||
/** Low-echo detection 임계값 (denoised 신호 기준).
|
||||
* denoised ≤ 이 값 → low-echo (소변 후보).
|
||||
* UrinAI의 liquidThrLoose(raw 기준)와 다름 — 여기는 TVD+SG 적용 후 신호 기준.
|
||||
* Python 원본: 1600 (low_echo_detection_method_b.py LOW_ECHO_AMP)
|
||||
* Python 원본: 1150 (low_echo_detection_method_b.py LOW_ECHO_AMP, 6ch 버전)
|
||||
* 영향: PiezoEchoAnalyzer → low-echo span 탐지, 벽 찾기의 기반 */
|
||||
const val lowEchoAmp: Float = 1150f
|
||||
|
||||
|
||||
Reference in New Issue
Block a user