From 1beb0737c6b71e3a2b4541c2c0e65551640b51fc Mon Sep 17 00:00:00 2001 From: jjangddu Date: Fri, 24 Apr 2026 15:27:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Placement=20=ED=8C=90=EC=A0=95=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20BLE=20=EB=94=94=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EB=A1=9C=EA=B1=B0=EC=97=90=20=EA=B8=B0=EB=A1=9D=20+=20README?= =?UTF-8?q?=20=EB=8C=80=ED=8F=AD=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BLE 로그 추가: - DETACHED mean=12.3 std=8.7 - PLACEMENT_SEARCHING cv=0.234 dir=MOVE UP moderate score=35 - PLACEMENT_PASS cv=0.112 lr_dev=0.089 score=78 README 추가 섹션: - 17. 미부착 감지 알고리즘 상세 (DetachmentDetection) - 원리, 상수, 실측 안전 마진 설명 - 18. Placement 전체 파이프라인 흐름도 - Detachment → Echo → CV → LR → LED 판정 - BLE 로그 출력 예시 - 향후 과제에 미부착 감지 완료 항목 추가 Co-Authored-By: Claude Opus 4.6 (1M context) --- VesiScan_Android_Pipeline_Summary.md | 153 ++++++++++++++++-- .../ui/views/monitoring/PlacementGuideView.kt | 3 + 2 files changed, 146 insertions(+), 10 deletions(-) diff --git a/VesiScan_Android_Pipeline_Summary.md b/VesiScan_Android_Pipeline_Summary.md index 1d14544..1e948dc 100644 --- a/VesiScan_Android_Pipeline_Summary.md +++ b/VesiScan_Android_Pipeline_Summary.md @@ -965,21 +965,153 @@ CRC16.buildCommandASCII("mid", " ") // → "mid? " + CRC | 6 | ALIGN_COMPLETE | 정렬 모드 탐지 완료 | | 7 | ERROR | 오류 발생 | -### Placement Guide 연동 +### Placement Guide 연동 (3단계 판정) ``` -Start Scanning → mls?(5) ALIGN_SEARCHING (LED 탐지 모드) - ↓ -3초 간격 스캔 중... - ↓ -CV PASS → mls?(6) ALIGN_COMPLETE (LED 완료) - ↓ -Stop Scanning → mls?(0) OFF +매 3초 스캔 → + │ + ├─ [1단계] 미부착 감지 (DetachmentDetection) + │ mean(|y|) < 100 AND std(y) < 100 → 미부착 + │ → LED 4 (DETACH_WARNING) + │ → "Sensor detached — check contact" + │ + ├─ [2단계] Placement 판정 (CV threshold) + │ CV > 0.15 또는 |lr_dev| > 0.15 → 부적절 + │ → LED 5 (ALIGN_SEARCHING) + │ → 방향 가이드 (MOVE UP/DOWN/LEFT/RIGHT) + │ + └─ [3단계] 최적 위치 + CV ≤ 0.15 AND |lr_dev| ≤ 0.15 → PASS + → LED 6 (ALIGN_COMPLETE) + → "Best Placement!" + +Stop Scanning → LED 0 (OFF) ``` --- -# 17. 향후 과제 +# 17. 미부착 감지 (Detachment Detection) + +### 원본: detachment_detection.py (원은지님, 2026-04-24) + +### 알고리즘 + +``` +입력: 6채널 ADC raw 데이터 (100 samples × 6ch) + +1. Center 채널 추출 (CH0~CH3, 4채널) + +2. Baseline 제거: + baseline = median(signal[80:100]) ← 끝부분 20개 샘플의 중앙값 + y = signal[20:80] - baseline ← 중간 60개 샘플에서 baseline 차감 + +3. 특징 계산 (채널별): + mean_abs = mean(|y|) ← 절대값 평균 (신호 크기) + std = std(y) ← 표준편차 (신호 변동성) + +4. 4채널 평균: + mean_abs = avg(mean_abs_ch0, ..., mean_abs_ch3) + std_val = avg(std_ch0, ..., std_ch3) + +5. 판정 ("and" rule): + 미부착 = mean_abs < 100 AND std_val < 100 +``` + +### 왜 이 방식인가? + +``` +부착된 경우: 미부착된 경우: + 초음파가 조직을 통과 → 반사 공기 중 → 신호 거의 없음 + signal[20:80] 구간에 변동 있음 signal[20:80] 구간이 평탄 + mean_abs >> 100, std >> 100 mean_abs < 100, std < 100 + +실측 데이터 (원은지님): + 미부착 max: mean=33.7, std=43.4 ← threshold 100보다 훨씬 낮음 + 부적절 min: mean=254.8, std=234.7 ← threshold 100보다 훨씬 높음 + → ~2.5배 안전 마진 +``` + +### 상수 + +| 상수 | 값 | 의미 | +|------|-----|------| +| `WINDOW_START` | 20 | 분석 구간 시작 (ringdown 이후) | +| `WINDOW_END` | 80 | 분석 구간 끝 | +| `BASELINE_WINDOW_START` | 80 | baseline 구간 시작 | +| `BASELINE_WINDOW_END` | 100 | baseline 구간 끝 | +| `THR_MEAN` | 100 | mean(|y|) 임계값 | +| `THR_STD` | 100 | std(y) 임계값 | + +--- + +# 18. Placement 전체 파이프라인 (CV threshold + Detachment + LED) + +### 원본: placement.py (원은지님, 2026-04-24) + +### 전체 흐름도 + +``` +Start Scanning (3초 간격 자동 반복) + ↓ +[매 스캔] + ↓ +6채널 ADC 수신 (maa? → reb:×6 → raa:) + ↓ +┌───────────────────────────────────────┐ +│ 1. Detachment Detection │ +│ signal[20:80] - median([80:100]) │ +│ mean(|y|) < 100 AND std < 100? │ +│ → YES: LED 4 (미부착) → 다음 스캔 │ +└───────────────────────────────────────┘ + ↓ NO (부착됨) +┌───────────────────────────────────────┐ +│ 2. Echo Analysis (6채널) │ +│ SG denoise → Low-echo detection │ +│ → ant/post wall 위치 (mm) │ +└───────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────┐ +│ 3. CV Threshold (Center CH0~CH3) │ +│ depth_mm = (post-ant) × DPS × cos(θ)│ +│ CV = std(depths) / mean(depths) │ +│ CV ≤ 0.15? → PASS │ +└───────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────┐ +│ 4. LR Deviation (CH4 vs CH5) │ +│ lr_dev = (depth4-depth5) / mean │ +│ |lr_dev| ≤ 0.15? → PASS │ +└───────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────┐ +│ 5. 최종 판정 │ +│ CV PASS AND LR PASS │ +│ → YES: LED 6 (최적 위치) → Lock │ +│ → NO: LED 5 (탐색 중) │ +│ + 방향 가이드 │ +│ (MOVE UP/DOWN/LEFT/RIGHT) │ +│ + severity (slight/moderate/severe) │ +└───────────────────────────────────────┘ + ↓ +Threshold Relaxation (반복 시): + CV_thr = 0.15 + scanCount × 0.02 + → 오래 조절해도 안 되면 기준 완화 + +Lock → "Start Measurement" 버튼 활성화 +``` + +### BLE 디버그 로그 출력 예시 + +``` +13:45:39 WARN DETACHED mean=12.3 std=8.7 +13:45:42 INFO PLACEMENT_SEARCHING cv=0.234 dir=MOVE UP moderate score=35 +13:45:45 INFO PLACEMENT_SEARCHING cv=0.180 dir=MOVE DOWN slight score=52 +13:45:48 INFO PLACEMENT_PASS cv=0.112 lr_dev=0.089 score=78 +``` + +--- + +# 19. 향후 과제 | 항목 | 상태 | 비고 | |------|------|------| @@ -994,7 +1126,8 @@ Stop Scanning → mls?(0) OFF | BLE 디버그 로거 | ✅ 완료 | TX/RX 파싱 + Downloads 내보내기 | | ADC 로그 저장 | ✅ 완료 | 6ch raw ADC → JSONL | | 기기 정보 조회 (mid?) | ✅ 완료 | 연결 시 자동 (HW/SN/FW) | -| LED 모드 (mls?) | ✅ 완료 | Placement 연동 (5→6→0) | +| 미부착 감지 (DetachmentDetection) | ✅ 완료 | mean/std < 100 → LED 4 | +| LED 모드 (mls?) | ✅ 완료 | Placement 3단계 (4→5→6→0) | | IMU 쿼리 (msp?) | ✅ 완료 | 1초 간격, 자동측정 루프 | | 자동측정 (IMU 1s + 측정 10s) | ✅ 완료 | | | iOS 동기화 (UI) | ✅ 완료 | 알림, Quick Save, 일지 | diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt index 010fc99..e0abc68 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt @@ -328,6 +328,7 @@ fun PlacementGuideView(appState: AppState) { directionIcon = "exclamationmark.triangle" placementScore = 0 bleManager.sendLedMode(4) // DETACH_WARNING + bleManager.debugLogger.warn("DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}") Log.w("PlacementGuide", "DETACHED mean=${ "%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}") } else { @@ -339,8 +340,10 @@ fun PlacementGuideView(appState: AppState) { if (guideResult.isPass) { isLocked = true bleManager.sendLedMode(6) // ALIGN_COMPLETE + bleManager.debugLogger.info("PLACEMENT_PASS cv=${"%.3f".format(guideResult.cv)} lr_dev=${"%.3f".format(guideResult.lrDev)} score=${guideResult.score}") } else { bleManager.sendLedMode(5) // ALIGN_SEARCHING + bleManager.debugLogger.info("PLACEMENT_SEARCHING cv=${"%.3f".format(guideResult.cv)} dir=${guideResult.direction} ${guideResult.severity} score=${guideResult.score}") } }