fix: UX feedback + BV ellipse outlier (#22) + README v3 update
- #1 배터리 최적화 페이지 이동 제거 (ForegroundService+WakeLock만) - #2 Auto 첫 측정 대기 시 "Measuring... (n/5)" loading 표시 - #3 Placement 멘트 정리 — 방향만 남기기 (CH 언급 제거) - #4 CH0~CH5 dot row 개발자 모드 전용 - #5 Spot 재시도 로직 (최대 6회, 유효 3개 목표, 300ms 간격) - LR 좌우 조건 완화: ±8mm, symmetry≥0.5, lrDev≤0.35 - |len4-len5|≤10 → LATERAL 건너뛰고 바로 GREEN - GREEN 미달 시 CV만 간당간당하면 GREEN 유지 ("Almost there...") - CV 기본 임계 0.08→0.12, 디바운스 3초→1.5초 - BV 타원 피팅 outlier 반복 제거 (appshare #22) - README v3 업데이트 (3모드 placement, BV 파이프라인, 상태표) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## 작성일: 2026-04-23
|
||||
## 작성자: dwjang
|
||||
## 마지막 업데이트: 2026-04-24 (v2)
|
||||
## 마지막 업데이트: 2026-05-07 (v3)
|
||||
|
||||
---
|
||||
|
||||
@@ -286,53 +286,30 @@ UrinAI가 "소변 있다!" 하면, **정확히 어디서 어디까지**인지
|
||||
|
||||
---
|
||||
|
||||
## 5.5 LR Ratio 계산 — 타원 단면 추정
|
||||
## 5.5 LR Ratio 계산 — x-z 타원 피팅 (2026-05-07 업데이트, appshare #21)
|
||||
|
||||
### iOS와 다른 점
|
||||
### 알고리즘 (x-z 평면 6점 타원 피팅)
|
||||
|
||||
```
|
||||
iOS: lr_ratio = 1.0 (고정, 원형 단면 가정)
|
||||
Android: CH4/CH5 lateral에서 동적으로 타원 비율 계산
|
||||
```
|
||||
1. CH4/CH5 벽 좌표 (x-z 평면):
|
||||
- ant/post 벽의 x좌표: sx + d × cos(α) × sin(β)
|
||||
- ant/post 벽의 z좌표: d × cos(α) × cos(β)
|
||||
→ 최대 4개 lateral 점
|
||||
|
||||
### 알고리즘
|
||||
2. Center 벽 좌표를 lateral SI 높이로 보간:
|
||||
- CH1, CH2의 z_ant, z_post를 선형 보간 (x=0)
|
||||
→ 2개 center 점
|
||||
|
||||
```
|
||||
1. Center AP 직경 계산 (CH1, CH2 기준)
|
||||
- 각 이웃 채널의 (post-ant) × cos(θ) → 평균 = D_center
|
||||
- D_center ≤ 0 → lr_ratio = 1.2 (invalid fallback)
|
||||
3. 6개 경계점으로 x-z 축 정렬 타원 피팅:
|
||||
- 정규화 후 α·x̂² + β·ẑ² + γ·x̂ + δ·ẑ = 1
|
||||
- least squares → b_lr (LR 반축), a_ap (AP 반축)
|
||||
- lr_ratio = b_lr / a_ap (하한 1.0)
|
||||
|
||||
2. Lateral 직경 계산 (CH4, CH5 각각)
|
||||
- P = cos(alpha) × cos(beta) [SI 보정]
|
||||
- Q = cos(alpha) × sin(beta) [LR 성분]
|
||||
- D_lateral = (dFar - dNear) × P
|
||||
- y_mid = sensor_x + d_mid × Q [LR 위치]
|
||||
- ratio = D_lateral / D_center
|
||||
|
||||
3. LR ratio 계산
|
||||
- 2개 lateral → 타원 방정식 풀기:
|
||||
u = yL × yR × (yR - yL) / denom
|
||||
d = -(u × (rL² - 1) + yL²) / (2 × yL)
|
||||
b² = u + d²
|
||||
lr_raw = 2√u / D_center
|
||||
|
||||
- 1개 lateral → 중심 가정:
|
||||
b = |y_mid| / √(1 - r²)
|
||||
lr_raw = b / (D_center/2)
|
||||
|
||||
4. Shrinkage (Bayesian 사후확률)
|
||||
- confidence = ((1 - avg_ratio) / 0.10).clamp(0, 1)
|
||||
4. Shrinkage:
|
||||
- confidence = ((1 - avg_chord_ratio) / 0.10).clamp(0, 1)
|
||||
- lr_final = lr_prior(1.2) + (lr_raw - 1.2) × confidence
|
||||
```
|
||||
|
||||
### Fallback 값
|
||||
|
||||
| 상황 | lr_ratio |
|
||||
|------|----------|
|
||||
| lateral 데이터 없음 | 1.0 (원형) |
|
||||
| lateral 있지만 계산 실패 | 1.2 (해부학적 기본값) |
|
||||
| 정상 계산 | 동적 값 (shrinkage 적용) |
|
||||
|
||||
---
|
||||
|
||||
## 5.6 BV Estimation — 용적 계산
|
||||
@@ -363,20 +340,24 @@ Android: estimateBladderVolume6ch(6채널)
|
||||
총 부피 = 바닥 + 층1 + 층2 + 층3 + 뚜껑
|
||||
```
|
||||
|
||||
### 10단계 파이프라인
|
||||
### 파이프라인 (2026-05-07 업데이트, appshare #21/#22)
|
||||
|
||||
| Step | 뭘 하나? | 수식 |
|
||||
|------|---------|------|
|
||||
| 0 | Center wall 보간/정리 | gap=1 보간, gap≥2 top 그룹 제거 |
|
||||
| 1 | Edge 채널 필터 | CH3이 CH1/CH2 대비 0.9배 미만이면 short |
|
||||
| 2 | 칸 번호 → 실제 거리(mm) | d = 6.85 + 칸번호 × **1.771** |
|
||||
| 2 | 칸 번호 → 실제 거리(mm) | d = 6.85 + 칸번호 × DPS |
|
||||
| 2-1 | Post median outlier 제거 | median ±25% 밖 채널 제거 (≥3ch) |
|
||||
| 3 | 방광 직경 구하기 (각도 보정) | D = (뒷벽거리 - 앞벽거리) × cos(각도) |
|
||||
| 4 | 단면적 구하기 | S = (π/4) × D² × **lr_ratio** |
|
||||
| 5 | 각 층의 높이 위치 | y = 센서위치 + 중간점 × sin(각도) |
|
||||
| 4 | 단면적 구하기 | S = (π/4) × D² × lr_ratio |
|
||||
| 5 | 각 층의 높이 위치 + 벽 좌표 | y, z 벽 좌표 (타원 피팅용) |
|
||||
| 6 | 아래→위 순서로 정렬 | - |
|
||||
| 7 | 포물선으로 보정 | 2차 곡선 맞추기 + 이상치 수정 |
|
||||
| 7 | y-z 타원 피팅 → cap 높이 | 반복 outlier 제거 (잔차>0.5 시 worst 제거, 최소 5점) |
|
||||
| 7-1 | b_si 상한 | a_ap × 1.3 (해부학적 SI/AP 제한) |
|
||||
| 7-2 | Top cap 상한 | 미검출 상위 채널 빔 y좌표 기준 제한 |
|
||||
| 8 | 층별 부피 합산 | 절두체 공식 |
|
||||
| 9 | 바닥 뚜껑 (항상 구) | V = S×h/2 + π×h³/6 |
|
||||
| 10 | 윗 뚜껑 (원뿔 or 구) | 원뿔: V = S×h/3 / 구: 위와 동일 |
|
||||
| 9 | 바닥 뚜껑 (spherical cap) | V = S×h/2 + π×h³/6 |
|
||||
| 10 | 윗 뚜껑 (cone) | V = S×h/3 |
|
||||
|
||||
### 물리 상수
|
||||
|
||||
@@ -402,109 +383,74 @@ Android: estimateBladderVolume6ch(6채널)
|
||||
[CH0] 0° ← 가장 위 (배꼽쪽, y=21mm)
|
||||
```
|
||||
|
||||
## 방향 안내 로직 (2단계)
|
||||
## 방향 안내 로직 — 3모드 (2026-05-07 업데이트)
|
||||
|
||||
### 1단계: 이진 판정 (4채널 미만일 때)
|
||||
개발자 모드 Settings에서 전환 가능: **Gradient** (기본) / Boundary / **SWEEP**
|
||||
|
||||
**물리적 프로브 배치 기준:**
|
||||
- CH0 = 배꼽 방향 (위)
|
||||
- CH3 = 치골 방향 (아래)
|
||||
|
||||
| CH0(배꼽) | CH3(치골) | CH4(왼) | CH5(오) | 안내 |
|
||||
|-----------|----------|---------|---------|------|
|
||||
| ✅ | ❌ | - | - | "센서를 **위로** 올리세요" (UP) |
|
||||
| ❌ | ✅ | - | - | "센서를 **아래로** 내리세요" (DOWN) |
|
||||
| - | - | ❌ | ✅ | "센서를 **왼쪽**으로 옮기세요" (LEFT) |
|
||||
| - | - | ✅ | ❌ | "센서를 **오른쪽**으로 옮기세요" (RIGHT) |
|
||||
| ❌ | ❌ | ❌ | ❌ | "방광이 안 잡힙니다. 센서 접촉을 확인하세요" |
|
||||
|
||||
### 2단계: CV threshold 정밀 가이드 (center 3채널 이상 잡힌 후)
|
||||
|
||||
4채널 이상 잡히더라도, 방광 **중앙**에 정확히 위치해야 측정이 정확합니다.
|
||||
각 채널의 **cos-corrected depth(mm)**의 변동계수(CV)로 배치 품질을 판정합니다.
|
||||
|
||||
### CV (Coefficient of Variation) 계산
|
||||
### Gradient 모드 (기본) — 신호 길이 비교
|
||||
|
||||
```
|
||||
1. 각 center 채널(CH0~CH3)의 ant/post wall 위치를 mm로 변환:
|
||||
depth_mm = (post_mm - ant_mm) × cos(beam_angle)
|
||||
시작: 치골 위 → 위로 올림
|
||||
|
||||
2. 유효 채널(≥3개)의 depth 평균과 표준편차 계산:
|
||||
CV = std(depths) / mean(depths)
|
||||
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
|
||||
|
||||
3. 판정:
|
||||
CV ≤ 0.15 → PASS (Best Placement)
|
||||
CV > 0.15 → FAIL → 방향 안내
|
||||
2단계 LATERAL:
|
||||
|len4 - len5| ≤ 10 sample → 바로 GREEN
|
||||
lrSymmetry ≥ 0.5 → GREEN
|
||||
아니면 len 차이 방향으로 안내
|
||||
|
||||
3단계 GREEN:
|
||||
CV ≤ 0.12 (10회마다 +0.03 완화)
|
||||
|lrDev| ≤ 0.35
|
||||
둘 다 통과 → Green!
|
||||
CV만 간당간당 → "Almost there..." (GREEN에 머물며 재시도)
|
||||
```
|
||||
|
||||
### 왜 CV인가?
|
||||
### SWEEP 모드 (CKLaw) — 위에서 아래로 쓸어내리기
|
||||
|
||||
```
|
||||
잘 맞은 경우 (중앙): 치우친 경우 (가장자리):
|
||||
CH0: depth=45mm CH0: depth=15mm ← 짧음
|
||||
CH1: depth=52mm CH1: depth=48mm
|
||||
CH2: depth=50mm CH2: depth=42mm
|
||||
CH3: depth=42mm CH3: depth=10mm ← 짧음
|
||||
시작: 배꼽 근처 → 아래로 내림
|
||||
|
||||
CV = 0.09 → PASS ✅ CV = 0.52 → FAIL ❌
|
||||
weakest = CH3 → "MOVE UP"
|
||||
1단계 VERTICAL:
|
||||
① 0채널: "Place near navel, slide down ↓"
|
||||
② 1~3채널: "Slide down ↓" (채널이 하나씩 켜짐)
|
||||
③ 4채널: sagittalArgMaxCh로 정렬 판정
|
||||
argmax=0 → 정중앙! → LATERAL
|
||||
argmax=1~3 → "Slide down" (아직 더 내려야)
|
||||
④ 4채널→줄어듦: "Past optimum — slide back up ↑"
|
||||
+ 좌우 차이 ≤10 → LATERAL 건너뛰고 바로 GREEN
|
||||
|
||||
2단계 LATERAL:
|
||||
lateralOffsetMm (mm 단위 정밀 안내)
|
||||
|offset| ≤ 8mm → GREEN
|
||||
|
||||
3단계 GREEN:
|
||||
CV + LR dev + R-peak gate (bestR - currentR ≤ 1.5mm)
|
||||
```
|
||||
|
||||
중앙을 관통하면 모든 채널의 depth가 비슷 → CV 작음.
|
||||
가장자리를 스치면 일부 채널이 짧음 → CV 큼.
|
||||
|
||||
### SI(세로) 방향 판정
|
||||
### Boundary 모드 — 올라갔다 내려오기
|
||||
|
||||
```
|
||||
deviation[i] = (depth[i] - mean) / mean
|
||||
|
||||
weakest = 가장 deviation이 큰 채널
|
||||
|
||||
severity:
|
||||
|dev| < 0.10 → slight
|
||||
0.10 ≤ < 0.25 → moderate
|
||||
|dev| ≥ 0.25 → severe
|
||||
|
||||
방향:
|
||||
weakest가 CH0/CH1 (top) → MOVE DOWN
|
||||
weakest가 CH2/CH3 (bottom) → MOVE UP
|
||||
시작: 치골 위 → 위로 올림
|
||||
채널 수 최대→감소 감지 → "Too high, lower" → 복귀 시 LATERAL
|
||||
```
|
||||
|
||||
### LR(가로) 판정
|
||||
### GREEN 판정 상수
|
||||
|
||||
```
|
||||
depth_CH4 = raw_depth × cos(SI_angle) × cos(LR_angle)
|
||||
depth_CH5 = raw_depth × cos(SI_angle) × cos(LR_angle)
|
||||
|
||||
lr_dev = (depth_CH4 - depth_CH5) / mean(depth_CH4, depth_CH5)
|
||||
|
||||
|lr_dev| ≤ 0.15 → PASS
|
||||
lr_dev > 0 → MOVE RIGHT (왼쪽이 더 길음)
|
||||
lr_dev < 0 → MOVE LEFT (오른쪽이 더 길음)
|
||||
```
|
||||
|
||||
### 최종 판정
|
||||
|
||||
```
|
||||
is_pass = CV ≤ cv_thr AND |lr_dev| ≤ lr_dev_thr
|
||||
|
||||
cv_thr = 0.15 + repeatCount × 0.02 (반복 시 완화)
|
||||
lr_dev_thr = 0.15 (고정)
|
||||
```
|
||||
|
||||
### Threshold Relaxation (반복 완화)
|
||||
|
||||
```
|
||||
Scan 흐름:
|
||||
1회차: CV_thr = 0.15
|
||||
2회차: CV_thr = 0.17
|
||||
3회차: CV_thr = 0.19
|
||||
...
|
||||
n회차: CV_thr = 0.15 + n × 0.02
|
||||
|
||||
→ Best Placement가 아닌 경우 반복 측정 동안 기준을 완화하여
|
||||
사용자가 무한히 조절하지 않도록 함
|
||||
```
|
||||
| 상수 | 값 | 의미 |
|
||||
|------|-----|------|
|
||||
| DEFAULT_CV_THR | 0.12 | 기본 CV 임계 (10회마다 +0.03 완화) |
|
||||
| DEFAULT_LR_DEV_THR | 0.35 | LR 비대칭 임계 |
|
||||
| 절대 LR 차이 | ≤10 sample | LATERAL 즉시 통과 |
|
||||
| R-peak tolerance | 1.5mm | SWEEP 모드 GREEN gate |
|
||||
| 디바운스 | 1.5초 | phase 전환은 즉시, 같은 phase 힌트만 디바운스 |
|
||||
|
||||
## 연속 스캔 동작
|
||||
|
||||
@@ -558,13 +504,20 @@ Spot 버튼 탭
|
||||
화면에 "305 ml" 표시! (도넛차트 + 레벨 업데이트)
|
||||
```
|
||||
|
||||
## 롱프레스 자동측정
|
||||
## Auto 모드 (2026-05-07 업데이트)
|
||||
|
||||
Spot 버튼을 **길게 누르면** 자동 측정 모드:
|
||||
- 30초마다 자동으로 측정
|
||||
- 빨간색 "Auto" 배지 표시
|
||||
- 한 번 탭하면 중지
|
||||
- 구현: `detectTapGestures(onLongPress)` + `LaunchedEffect` 30초 루프
|
||||
별도 Auto 버튼으로 자동 반복 측정:
|
||||
- 1.5초 간격 자동 측정
|
||||
- 첫 5회: "Measuring... (n/5)" 로딩 표시
|
||||
- 5회 이상: **sliding window trimmed mean** (10개 윈도우) → 안정된 값 표시
|
||||
- Stop 버튼으로 종료
|
||||
|
||||
## Spot 모드 (2026-05-07 업데이트)
|
||||
|
||||
Spot 버튼 1탭 → **3회 측정 → median** (중간값 깜빡임 없음):
|
||||
- 최대 6회까지 재시도 (유효값 3개 수집 목표)
|
||||
- 측정 간격 300ms (BLE 겹침 방지)
|
||||
- 중간 값 화면 미표시 (isSpotInProgress 플래그)
|
||||
|
||||
## 측정 실패 시 자동 재시도
|
||||
|
||||
@@ -1293,24 +1246,27 @@ ADC 파싱 시 Big/Little Endian을 자동 감지:
|
||||
| 항목 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| BLE 6채널 수신 | ✅ 완료 | |
|
||||
| Low-echo Detection (prominence + edge penalty) | ✅ 완료 | VALLEY_STOP=50, EDGE_DECAY=0.12 |
|
||||
| Frustum BV 추정 (6ch) | ✅ 완료 | center 4ch + lateral lr_ratio(≥1.0) |
|
||||
| lr_ratio 동적 계산 | ✅ 완료 | CH4/CH5 타원 추정, 하한 1.0 |
|
||||
| Placement Guide 2단계 순차 | ✅ 완료 | 상하(3ch+크기)→좌우(대칭)→CV Green |
|
||||
| 미부착 감지 (DetachmentDetection) | ✅ 완료 | mean/std < 100 → LED 4 |
|
||||
| LED 모드 (mls?) BE 포맷 | ✅ 완료 | Placement 3단계 (4→5→6→0) |
|
||||
| BLE 자동 reconnect | ✅ 완료 | 무한 3초 재시도 + watchdog |
|
||||
| BLE 디버그 로거 | ✅ 완료 | TX/RX 파싱 + Downloads 내보내기 |
|
||||
| BLE 명령어 Endian 통일 | ✅ 완료 | 테스터 exe와 100% 동일 (BE+공백) |
|
||||
| 자동측정 (IMU 1s + 측정 10s) | ✅ 완료 | |
|
||||
| 런타임 캘리브레이션 | ✅ 완료 | Threshold 슬라이더 + DPS 직접입력 + SG 토글 |
|
||||
| 도넛차트 max volume 표시 | ✅ 완료 | maxVolumeMl 직접 표시 |
|
||||
| 패킷 드랍 자동 retry | ✅ 완료 | 6ch 미만 수신 시 자동 재전송 (최대 2회) |
|
||||
| ADC CSV 저장 | ✅ 완료 | Downloads/VesiScan_ADC/일별.csv + 설정값 포함 |
|
||||
| Endian 감지 안정화 | ✅ 완료 | VBT=BE 기본, 애매한 경우 기기 기반 fallback |
|
||||
| Python ↔ Android 1:1 검증 | ✅ 완료 | 전수 상수 비교 |
|
||||
| 금요일 APK 배포 | 🔜 예정 | Gitea 릴리즈 |
|
||||
| mbb 전체측정 전환 | 🔜 예정 | rbb: 헤더 파싱 검증 후 |
|
||||
| Adaptive TGC | 🔜 예정 | 원은지님 코드 확인 필요 |
|
||||
| Placement Score 캘리브레이션 | 🔜 예정 | 팬텀 실측 기반 CV threshold 조정 |
|
||||
| Foreground Service (화면 잠금 BLE 유지) | ✅ 완료 | PARTIAL_WAKE_LOCK 4시간 |
|
||||
| Low-echo Detection Method B | ✅ 완료 | VALLEY_STOP=50, EDGE_DECAY=0.12 |
|
||||
| V4.1 CharlesKWON Method C | ✅ 완료 | 45 파일 walldetect 패키지 |
|
||||
| Method A (Plateau-based) | ✅ 완료 | contrastRatio × cos(θ) |
|
||||
| Frustum BV 추정 (6ch) | ✅ 완료 | y-z 타원 피팅 cap + outlier 제거 (#21/#22) |
|
||||
| lr_ratio x-z 타원 피팅 | ✅ 완료 | 6점 타원 피팅 (#21) |
|
||||
| Center wall repair | ✅ 완료 | gap=1 보간, gap≥2 top 제거 (#21) |
|
||||
| BV 타원 피팅 outlier 제거 | ✅ 완료 | 반복 worst 제거, 잔차≤0.5, b_si≤a_ap×1.3 (#22) |
|
||||
| Back Reflection 처리 | ✅ 완료 | consensus, amp_ratio=1.5, min_votes=3 |
|
||||
| Placement Guide 3모드 | ✅ 완료 | Gradient / Boundary / SWEEP |
|
||||
| SWEEP (BladderSphereSeek) | ✅ 완료 | Kasa circle fit + RTracker + argmax |
|
||||
| Gradient placement | ✅ 완료 | weighted center + R-peak gate GREEN |
|
||||
| 개발자 모드 | ✅ 완료 | 캐릭터 3탭 토글, dev-only settings/graphs/logs |
|
||||
| Auto 측정 (sliding window) | ✅ 완료 | 10-sample trimmed mean, 대기 중 loading |
|
||||
| Spot 측정 (재시도) | ✅ 완료 | 3회 median, 최대 6회 재시도, 깜빡임 없음 |
|
||||
| 미부착 감지 | ✅ 완료 | mean/std < 30 → LED 4 |
|
||||
| BLE 자동 reconnect + watchdog | ✅ 완료 | 15초 timeout, IMU heartbeat |
|
||||
| BLE 디버그 로거 | ✅ 완료 | 실시간 파일 append |
|
||||
| ADC CSV 저장 | ✅ 완료 | 세션별 파일 |
|
||||
| Canvas 하복부 실루엣 | ✅ 완료 | PNG 제거, CKLaw Bezier torso |
|
||||
| i18n (한국어/영어) | ✅ 완료 | strings.xml |
|
||||
| Placement 팬텀 테스트 | 🔜 예정 | Gradient vs SWEEP 비교 |
|
||||
| 5-Anchor calibration | 🔜 검토 | CKLaw 전체 포팅 여부 결정 |
|
||||
| 인체 임상 검증 | 🔜 예정 | 상수 재조정 |
|
||||
|
||||
Reference in New Issue
Block a user