Commit Graph

46 Commits

Author SHA1 Message Date
dw.jang 45242f8256 tune(walking): default Start Wmean 4→15, Start Hold 1s→6s (사용자 실측 기준)
증상: 앉아있다 일어서기만 해도 walking false-trigger.

원인: 사용자 실측 데이터 분석 — stand-up + 자세 조정 motion 이 wmean
      5-10 dps 로 약 3-5초 지속. 이전 default (Start Wmean=4, Hold=1s) 는
      이 motion 을 walking 으로 잡음.

수정:
  Start Wmean: 4 → 15  (stand-up 의 typical wmean 보다 위)
  Start Hold:  1초 → 6초 (stand-up settling 보다 김)

근거:
  - 사용자 측정 stand-up motion: wmean 5-10 (저강도) ~ 100 (spike)
  - 진짜 보행 (body-mounted, 예상): wmean 50+ 지속
  - Hold 6초 — 정상 보행은 무한 지속이라 자동 만족, stand-up 은 5초 미만

비고:
  - 임계는 여전히 dev slider 로 조정 가능 (이전 commit 18a4cee)
  - 사용자별 / 디바이스별 튜닝 가능
  - 향후 periodicity check (FFT) 추가 시 임계 다시 완화 가능

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 12:29:14 +09:00
dw.jang 80f3982352 fix(walking): sit-to-stand burst window leak — pitch transition 시 buffer 비움
증상: 앉아있다 일어서기만 해도 WALKING false-trigger (두 번 연속 발생).

원인 (로그 분석):
  앉아있음(pitch=20-30°) → 일어남 burst (gyro 90-220 dps) → 서있음(pitch=82°)

  타임라인:
    T=0  pitch=37°  wmean=49  (burst 시작, pitch < 45 → pitchOk=false)
    T=1  pitch=14°  wmean=110 (더 큰 burst, pitchOk=false)
    T=2  pitch=32°  wmean=174 (burst 최고, pitchOk=false)
    T=3  pitch=82°  wmean=190 ← pitchOk=true 된 순간 window 에 burst 가득
                              → start cond 즉시 만족, 카운트 시작
    T=4  pitch=50°  wmean=82  (burst 빠지는 중)
    T=5  pitch=46°  wmean=12  (거의 빠짐, 그래도 > Start Wmean(4))
    T=6  pitch=57°  wmean=24  → "WALKING start (held 3091ms)" ❌

  핵심: pitch 가 45° 통과한 순간 window 안엔 이미 burst data. burst 가 다
  빠지기 전에 (~1.5초) + 3초 hold 누적 = false-trigger.

수정: WalkingDetector 에 lastPitchOk 추적 + false→true 전이 시
      buffer.clear() + startCondSinceMs=null.

  if (pitchOk && !lastPitchOk && !walking) {
      buffer.clear()           // burst data 폐기
      startCondSinceMs = null  // hold 카운트 리셋
  }

  결과: pitch 가 45° 넘는 순간부터 fresh sample 만 누적. 사용자가 일어서서
        가만히 있으면 wmean=1-2 (정상 정지값) → cond 안 met → trigger 안 됨.
        실제 보행 시작하면 새 wmean 이 30-50+ 까지 올라가 → 3초 hold → trigger.

  walking 중에는 pitchOk false→true 가 발생해도 (잠깐 앉았다 일어남 등) buffer
  안 비움 (walking 상태 유지 — keep cond 가 처리).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 12:18:38 +09:00
dw.jang 18a4cee00d feat(dev): 자세/walking 모든 임계 슬라이더 추가 (튜닝용)
사용자: walking 이 너무 예민하게 뜸. 모든 임계를 dev settings 에서 조절하고 싶음.

추가된 슬라이더 (Pitch Threshold 아래에 그룹별 정렬):

1) Posture
   - Pitch Threshold (lying ↔ non-lying)         기존
   - Poll Interval                                 기존

2) Walking Thresholds
   - Min Pitch (walking 인정 최소 pitch)           기존
   - Start Wmean (시작 평균 gyro)                  기존
   - Start Wstd (시작 표준편차)                    기존
   - Keep Wmean (유지 평균) — range 0.5~30 (이전 10) 신규 범위↑
   - **Start Hold** (시작 조건 hold 시간, 0.2~5초)  ⭐신규
   - **Keep Grace** (종료 grace 시간, 0.5~10초)     ⭐신규
   - **Window** (sliding window 크기, 0.5~5초)     ⭐신규

3) Acc Magnitude Gate
   - **Min** (UNKNOWN 하한, 0.3~1.0g)              ⭐신규
   - **Max** (UNKNOWN 상한, 1.0~3.0g)              ⭐신규

내부 변경:
  - GreenZoneConstants 에 postureAccMagMin/Max 추가
  - ImuPostureClassifier 의 const ACC_MAG_MIN/MAX → GreenZoneConstants 참조
    (런타임 변경 가능)

총 11개 슬라이더 — 사용자가 실측하며 튜닝 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 11:37:46 +09:00
dw.jang 6bf399c7b9 fix(walking): chained failure — acc gate fail 시 walking 잃던 버그
증상: 로그캣 분석 — 활발히 걷는 중 (accMag > 1.2g) 일수록 화면에 안 보임.
  11:24:03 NON_LYING → WALKING 성공
  11:24:04 accMag=1.80 → UNKNOWN, walking 도 grace 들어감
  11:24:05 grace 2초 초과 → walking 종료
  → 활발 보행 = 가장 위험한 순간에 알람 사라짐 (정반대)

원인 두 가지:
  1) Classifier 가 acc gate fail 시 pitch=0 반환
     → WalkingDetector 가 pitch=0 받아서 pitchOk=false → grace 진입
  2) Composite 로직: r.posture==UNKNOWN 이면 WALKING 무시하고 UNKNOWN 표시

수정:
  1) ImuPostureClassifier: pitch 계산을 acc gate 이전으로 옮김
     → UNKNOWN Result 에도 실제 pitch 값 포함
     → WalkingDetector 가 정확한 pitch 로 grace 판단 가능

  2) PiezoMonitoringView composite 순서 변경:
     when {
       isWalkingNow -> WALKING          // 우선 (자체 게이트 신뢰)
       r.posture==UNKNOWN -> UNKNOWN
       else -> r.posture
     }
     walking detector 가 walking 이라고 하면 acc gate 실패해도 표시.
     walking 자체에 시간/wmean/wstd 게이트 있어 false-positive 거의 없음.

테스트 시나리오:
  정지 lying → 흔들면서 걷기 (pitch>45°) → walking 검출 → 격하게 흔들기
  (accMag>1.2g) 도 → "Walking" 계속 표시 + 빨간 팝업 유지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 11:27:58 +09:00
dw.jang ecdec7c97c feat(posture): Walking detector (flowchart Path B) + 2단계 알람
새 flowchart 의 walking 검출 추가:

1) Posture enum: UNKNOWN/LYING/NON_LYING + **WALKING** (4-state)

2) WalkingDetector.kt 신규 (flowchart Path B 구현)
   - Sliding window (default 1.5초) buffer 에 gyro magnitude 누적
   - 매 cycle 마다 wmean (이동평균) + wstd (이동표준편차) 계산
   - State machine:
     IDLE → WALKING:
       |pitch| > 45° AND wmean > 4 AND wstd > 2
       → 1초 이상 연속 만족 시 전이
     WALKING → IDLE:
       |pitch| > 45° AND wmean > 2
       → 미달 후 2초 grace 지나면 전이

3) GreenZoneConstants 에 walking 임계 7개 추가 (모두 dev 조정 가능):
   - walkingMinPitchDeg=45, walkingStartWmean=4, walkingStartWstd=2,
     walkingStartHoldMs=1000, walkingKeepWmean=2, walkingKeepHoldMs=2000,
     walkingWindowMs=1500

4) PiezoMonitoring:
   - walkingDetector 인스턴스
   - imuCollector.onComplete: classifier + detector 동시 호출,
     composite posture (WALKING > NON_LYING > LYING) 결정
   - NON_WALKING → WALKING 전이 시 critical 알람 trigger

5) UI:
   - 상단 status bar: Walking 아이콘 (DirectionsWalk, 빨강) 추가
   - 기존 wakeup 팝업 색: 빨강 → 주황 (warning level)
   - 신규 walking 팝업: 빨강 + DirectionsWalk + "환자가 걷고 있습니다 — fall risk"

6) Dev settings: walking 임계 슬라이더 4개 추가
   (Min Pitch / Start Wmean / Start Wstd / Keep Wmean)
   sliding window / hold 시간은 default 유지 (필요 시 추가)

2단계 알람 체계 (의료적):
  - 주황 (warning): LYING N초 → NON_LYING/WALKING 전이 (일어남 감지)
  - 빨강 (critical): NON_WALKING → WALKING 전이 (보행 시작 — fall risk 최고)

Tag phase2-pre-walking (28a8769) 이전 상태로 복원 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 10:28:43 +09:00
dw.jang 28a8769fa3 tune(posture): default polling 3s → 1s (실시간 자세 감지)
낙상 예방 시나리오상 자세 변화 즉시 감지 중요 → 1초 polling 으로 단축.
canSendMaa() 500ms throttle + mtb 응답 ~400ms 이라 1초가 사실상 한계.
사용자가 slider 로 더 늦출 수도 있음 (1~15초 범위).

BLE 부담: device active ~40% (mtb 한 cycle ≈ 400ms × 매초).
배터리 영향은 짧은 모니터링 세션 (수십분~수시간) 수준에선 무시 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 09:20:32 +09:00
dw.jang 0bb484ae45 feat(posture): Poll Interval 슬라이더 + default 10s → 3s
증상: 자세 갱신이 10초마다라 너무 느림. 빠르게 테스트하고 싶음.

수정:
  1) GreenZoneConstants.posturePollIntervalMs (default 3000ms) 신규
     - polling loop 의 delay(10000) → delay(GreenZoneConstants.posturePollIntervalMs)
     - 슬라이더 변경 즉시 다음 iteration 부터 적용

  2) PiezoMonitoring dev settings: Poll Interval 슬라이더 추가
     - 위치: Lying Duration 슬라이더 바로 아래
     - 범위: 1초 ~ 15초 (연속)
     - 표시: "X.X초"
     - 변경 시 GreenZoneConstants 에 즉시 반영

default 변경: 10초 → 3초.
  - mtb 한 cycle ≈ 400ms, 3초 polling = device 활동 ≈ 13% (가벼움)
  - 1초까지 줄여도 캐싱/throttle 충돌 거의 없음 (canSendMaa 500ms gate)

BLE 부담 vs 응답성 trade-off:
  | interval | 1시간 device 사용 | 자세 변화 최대 지연 |
  |   1초    |     ~24분 (40%)   |     ~1초           |
  |   3초    |     ~8분 (13%)    |     ~3초           |
  |   5초    |     ~5분 (8%)     |     ~5초           |
  |   10초   |     ~2.5분 (4%)   |     ~10초          |

낙상 예방엔 1-3초가 적정. 평상시 사용엔 5-10초.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 09:14:32 +09:00
dw.jang a175540291 fix(posture): pitch 분류 invert + PiezoMonitoring 설정에도 기상 알람 토글
증상 1: 디바이스 평평하게 둠 → "Upright" / 세움 → "Lying" (또는 기존 빌드의
        "Sitting") 으로 사용자 직관과 반대.

진단: 사용자 raw 데이터 분석
  - 평평 (AZ dominant, AZ ≈ 1g): pitch ≈ 0° → 코드가 NON_LYING (Upright) 반환
  - 세움 (AX dominant, AX ≈ 1g): pitch ≈ 80~90° → 코드가 LYING 반환
  사용자 직관: 평평=Lying, 세움=Upright → 코드 inversion.

수정 1: ImuPostureClassifier 의 분류 조건 invert
  - 기존: |pitch| > threshold → LYING
  - 신규: |pitch| ≤ threshold → LYING (작은 pitch = AZ 가 중력 = 센서 평평)
  - dev slider hint 도 "|pitch|≤N°→Lying" 으로 동기화

수정 2: PiezoMonitoring 의 dev settings overlay 에도 기상 알람 토글 추가
  - 기존: PiezoPersonalizationView 에만 존재
  - 신규: dev settings panel (Pitch Threshold slider 아래) 에 동일 토글
  - 두 위치 모두 같은 WakeupAlarmPrefs 사용 — sync 됨
  - 사용자가 monitoring 페이지에서 바로 켜고 끌 수 있음

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:39:46 +09:00
dw.jang 9a5fa16f94 fix(posture): gyro gate 제거 + UNKNOWN streak 화면 표시
증상: 디바이스를 lying 상태로 둬도 "Upright" 표시가 안 바뀜.

진단 (raw 로그 분석):
  - 디바이스 정지 상태에서도 gyro y 축 ≈ -25 dps 일관 표시
  - 이는 IMU 칩의 **calibration bias** (정지 시 zero offset 안 됨)
  - flowchart 의 gyro gate (9.16 dps) 가 항상 fail → classifier 영구 UNKNOWN
  - currentPosture 가 UNKNOWN 시 업데이트 안 되는 로직 때문에 첫 valid
    값 (NON_LYING, 사용자가 손에 들고 진입한 순간) 으로 stuck
  - 사용자가 눕혀도 분류 자체가 안 되니 "Upright" 그대로

수정:
  1) ImuPostureClassifier: gyro gate 제거
     - accel magnitude gate (0.8-1.2g) 가 큰 모션은 이미 거름
     - gyro magnitude 는 Result/log 에 계속 노출 (디버그용)
     - flowchart 그대로 따르려면 gyro 도 활용해야 하지만, bias 큰
       디바이스에선 무용지물이라 실용성 우선

  2) PiezoMonitoringView: UNKNOWN streak 카운터
     - 3회 연속 UNKNOWN 이면 currentPosture = UNKNOWN 으로 강제 표시
     - silent stuck 상태 (잘못된 자세 계속 보여줌) 방지
     - 잠시 1-2 cycle 동안 motion artifact 로 UNKNOWN 떠도 직전
       자세 유지 (이전 동작 보존)

  3) Log: unknownStreak 도 출력 → 디버그 시 stuck 케이스 가시화

테스트:
  - lying → upright 전환 시 즉시 "Upright" 표시되어야 함
  - 정지 시 "Lying" 또는 "Upright" 안정적 유지
  - 큰 모션 (들어올림 등) 중에는 3 cycle 후 "—" 표시

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:25:58 +09:00
dw.jang af469558e6 feat(posture): Phase 2 — 기상 알람 (LYING/NON_LYING + Settings 토글 + 빨간 팝업)
요구사항: 설정 탭에서 알람 on/off → on 상태에서 일어남 감지 시 빨간 팝업.

변경:

1) ImuPostureClassifier 2-state 단순화
   - 기존: SUPINE/SITTING/STANDING (3 state)
   - 신규: LYING / NON_LYING (2 state) + UNKNOWN
   - sitting/standing 구분 불필요 (낙상 예방엔 "누웠나/일어났나" 만 중요)
   - 분류: |pitch| > threshold → LYING, ≤ → NON_LYING

2) GreenZoneConstants.pitchLyingThreshold (default 25°)
   - @Volatile var — runtime 변경 가능
   - dev 모드 설정에서 slider 로 조정 (10~70°, 11 steps)

3) WakeupAlarmPrefs — SharedPreferences 저장
   - isEnabled (default false)
   - lyingDurationSec (default 180 = 3분)

4) WakeupDetector — state machine
   - LYING 진입 시 timestamp 기록
   - LYING → NON_LYING 전이 시 elapsed 측정
   - elapsed ≥ lyingDurationSec 이면 onWakeup 콜백
   - 1회 발동 후 LYING 복귀까지 재무장 안 됨 (스팸 방지)
   - UNKNOWN 은 무시 (motion artifact 흔들림 영향 X)

5) PiezoMonitoringView
   - posture 아이콘 매핑 LYING (Hotel, 파랑) / NON_LYING (AccessibilityNew, 초록) / UNKNOWN
   - imuCollector.onComplete 콜백에서 wakeupDetector.update(posture) 호출
   - showWakeupAlarm Boolean state + 빨간 Dialog (Color 0xFFD32F2F)
     · Warning 아이콘 + "환자가 일어났습니다" 메세지
     · 누운 시간 표시
     · 확인 버튼 (사용자 dismiss 필수, 자동 닫힘 X)
     · dismissOnBackPress/ClickOutside = false (실수 dismiss 방지)
   - Demo 모드: posture LYING 고정 (BLE 없음)

6) PiezoPersonalizationView — WakeupAlarmCard 신규
   - 빨간 톤 카드 + Switch
   - "기상 알람 — 환자가 일어나면 빨간 팝업으로 알림 (낙상 예방)"
   - 토글 즉시 WakeupAlarmPrefs.setEnabled

7) Dev 설정 (PiezoMonitoringView 의 dev panel)
   - "Pitch Threshold" 슬라이더 추가 (10°~70°)
   - GreenZoneConstants.pitchLyingThreshold 직접 조정
   - 실시간 반영 — 다음 cycle 부터 적용

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:17:58 +09:00
dw.jang cf476971a5 feat(posture): Phase 1 — flowchart 기반 자세 분류 + 도넛 우상단 아이콘 (demo-final)
allignment flowchart "Posture classification" 알고리즘 적용. Phase 1
범위: 자세 분류 + 화면 표시만 (알람/감지 없음).

1) ImuPostureClassifier.kt 신규
   flowchart 단계 그대로:
   - Acc magnitude 게이트: 0.8 ≤ |a| ≤ 1.2 (motion/bad reject)
   - Gyro magnitude 게이트: |g| ≤ 9.16 dps (회전 reject)
   - Pitch = atan(ax / sqrt(ay² + az²)) × 180/π
   - 분류:
     · |pitch| < 25°  → SUPINE
     · 25-70°         → SITTING
     · > 70°          → STANDING
   - 게이트 실패 시 UNKNOWN + 사유 반환
   - 입력: ImuSample 리스트 (한 응답 15 sample 평균)
   ※ flowchart 의 "|pitch| > 25; supine" 는 typo 로 판단 → < 25 적용

2) PiezoMonitoringView IMU polling 추가
   - 5초 주기로 sendImuQuery() → rim: 응답 → classifier
   - UNKNOWN 결과 시 직전 자세 유지 (artifact 흔들림 방지)
   - demo mode: SUPINE 으로 fix (BLE 없음)

3) 도넛 차트 우상단 자세 아이콘 표시 (Battery 옆)
   - SUPINE   → Icons.Hotel (파랑)
   - SITTING  → Icons.AirlineSeatReclineNormal (주황)
   - STANDING → Icons.AccessibilityNew (초록)
   - UNKNOWN  → Icons.HelpOutline (회색)
   - 아이콘 + 텍스트 라벨 (Lying/Sitting/Standing/—)

Phase 2 (다음): Settings 토글 + wake-up state machine + 빨간 팝업.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 12:24:22 +09:00
dw.jang 1fc1a20bb9 fix(align V2): LR_BALANCE 좌우 화살표 oscillation 차단 (deadband + 연속 악화)
증상: ch3 확인 통과 후 LR_BALANCE 단계에서 좌/우 화살표가 빠른
      간격으로 번갈아 나타남.

원인:
  AlignGuide4Stage.LR_BALANCE 의 hill-climbing 반전 조건이
  imbal > lateralImbal 만 검사 → 1 sample 라도 늘면 즉시 반전.
  - 노이즈/호흡 으로 imbal 가 ±1-2 fluctuate → 잘못된 반전
  - sliding window 평균 (10-frame) 잔여 효과: 방향 바꿔도 직전
    데이터 섞여 imbal 변화 못 따라옴 → 가짜 "악화" 판정
  - 사용자 반응 지연 동안 imbal 더 누적 → 또 반전 → ←→←→ 진동

수정 (A + C 조합):
  1) A. 반전 deadband (lrReverseDeadband = 3 samples ≈ 6 mm)
     imbal 가 직전보다 "deadband 초과" 증가해야 악화로 카운트.
     LAT_TOL(8) 의 약 1/3 — 정렬 완료 조건 위협하지 않으면서 노이즈
     흡수.
  2) C. 연속 N회 악화 요구 (lrReverseConsecutive = 2)
     deadband 초과 증가가 2 commit 연속이어야 방향 반전.
     단발 노이즈/sliding window 잔여 효과 흡수.

  상태 추가: lateralWorseStreak Int (reset 포함).
  로직:
    - 개선 또는 deadband 안 변동 → 같은 방향 유지, streak = 0
    - deadband 초과 증가 → streak++
    - streak >= 2 → 방향 반전, streak = 0
  lateralImbal 은 매 commit 갱신 (기존 동작 유지).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 15:36:19 +09:00
dw.jang 718016af7a feat: V2 alignment (Method D + alignment.py) demo-final 에 이식
feature/tab-navigation 의 V2 코드를 demo-final 로 가져와 임상 alignment session
에서 V1/V2 둘 다 선택 가능하도록.

복사된 파일 (feature/tab-navigation 그대로):
  walldetect/algo/SgSmoothGeneric.kt
  walldetect/algo/methodd/MethodDParams.kt
  walldetect/algo/methodd/MethodDPreprocessing.kt
  walldetect/algo/methodd/MethodDSpan.kt
  walldetect/algo/methodd/MethodDWallSelect.kt
  walldetect/algo/methodd/MethodDTgc.kt
  walldetect/MethodDDetector.kt
  walldetect/MethodDRunner.kt
  managers/AlignmentAdvisorV2.kt
    (RollingAligner + AlignGuide4Stage + AlignmentAdvice — alignment.py 1:1)

확장:
  walldetect/algo/Otsu.kt
    + otsu1dWithSeparability(values) — Python otsu_1d 의 (threshold, sep) tuple 호환
    method_d span fallback gate 에서 사용

  managers/GreenZoneConstants.kt
    + enum AlignmentAlgo { V1, V2 }
    + @Volatile var alignmentAlgo: AlignmentAlgo = V1 (default = legacy)

  ui/views/monitoring/PlacementGuideView.kt
    + val v2Aligner = remember { RollingAligner() }
    + V2 분기 (alignmentAlgo == V2 시): raw 6ch → push → state(accum/commit)
      → directionHint/directionIcon/placementScore/LED 설정
      → bleManager.debugLogger.info("ALIGN_V2 phase=... state=... bestSet=...")
    + ⚙ Settings overlay 에 V1 (legacy) / V2 (new) 토글 (보라색)
    + addAlignmentFrame 호출 시 V2 활성이면 v2Aligner.phase.name 사용 (4-stage)

  ui/views/clinical/ClinicalHomeView.kt
    + ClinicalSession.alignmentAlgo 를 현재 GreenZoneConstants.alignmentAlgo 값으로 채움
      (이전 demo-final 은 "V1" 하드코딩)
    → V2 토글 후 Sensor Alignment 진입 시 alignment_algo="V2" 가 json 에 기록

영향:
  - 기존 V1 동작 그대로 (default 가 V1, 토글 안 하면 동일)
  - V2 선택 시 demo-final 에서도 4-stage 진행 가능 + json 라벨 V2 명시
  - 알고리즘 토글은 dev 모드 (⚙ 버튼) 에서만 가능 — 일반 사용자 노출 X

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 14:03:34 +09:00
dw.jang ec3be6fa5d feat: Sensor Alignment에 IMU 통합 (mtb? + rim: 정식 처리)
PPT 'VesiScan-Basic IMU FW 진행사항_260521' 사양 기반 구현.
실측 1회 (2026-05-27 12:24, 평평한 책상)로 endian/필드 순서/시간 순서 확정 후 정식 적용.

확정 사양 (실측):
- rim: 패킷 188B = [tag 4B "rim:"] [num 2B BE] [data 180B = 15 samples × 12B] [crc 2B]
- 1 sample = 12B: Ax 2B + Ay 2B + Az 2B + Gx 2B + Gy 2B + Gz 2B (모두 BE int16)
- Accel ±4g sensitivity: /8192 g/LSB
- Gyro ±500 dps sensitivity: /65.536 dps/LSB
- 샘플 순서: samples[0] = oldest (FIFO 순)

신규 파일:
- ble/ImuPacketCollector.kt — rim: 파서. ImuSample data class + 15-sample list 콜백
- services/ImuCsvLogger.kt — Clinical 모드 한정 imu.csv 자동 저장
  컬럼: scan_id, timestamp, clinical_label, firmware_version, sample_idx, ax_g, ay_g, az_g, gx_dps, gy_dps, gz_dps
- managers/ImuAnalysis.kt — 자세 추정/Tilt/흔들림 RMS 유틸 (간이, 임상 보정 필요)

수정:
- BleManager: imuCollector 필드 + sendMtb() 정식 명령
  (canSendMaa() 게이트 maa와 공유. piezo + IMU 동시 캡처)
- BleManager: rim: 핸들러 정식화 (imuCollector.parseRim 위임)
- PlacementGuideView: sendAllChannels → sendMtb 교체, imuCollector.onComplete 등록
  Clinical 모드일 때 ImuCsvLogger.log() 자동 호출
  Dev 모드 한정 상단 칩에 "IMU: Supine · tilt=N° · shake=N.Ndps" 표시

데이터 흐름:
  performScan → sendMtb? → 응답:
    1) reb × 6 (Piezo, 기존 처리)
    2) raa (Piezo end, 기존 onMultiChannelComplete 트리거)
    3) rim (IMU, 신규 imuCollector.onComplete → lastImuSamples 갱신 + imu.csv)

분석:
- ImuAnalysis.estimatePosture: Accel 평균으로 Supine/Sitting/Standing/Tilted/Moving 판정
- shakeRmsDps > 30 → "Moving" (자세 판정 보류)
- tiltDegrees: z축 vs 중력 각도

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:19:20 +09:00
Charles KWON 833c110d2f feat: 측정 가로모드 대응 + Ant Offset + Scan Log + 다중 sanity check
## 측정 화면 (PiezoMonitoringView)
- BoxWithConstraints + donutFitSize: 가로/작은 화면에서도 donut+Bladdy+buttons
  모두 들어오도록 height-aware sizing (cap 120~360dp)
- nonDevSpacing: available height 기반 단계 (18/22/28/36) — 큰 화면 답답함 해소
- center 4 중 1개라도 missing → 즉시 realign prompt (이전: AutoMeasure 5회 누적)
- 6채널 중 3개 이상 missing 시 위치 이탈 알림 (모드 무관)
- 다이얼로그 문구: "Position Lost or Insufficient Gel"
- V41 결과 후처리 — walls를 PiezoEchoAnalyzer 기준으로 강제 (UI 차트와 일관)
  · ant = analyzer.result.ant
  · post = analyzer.result.lowEnd + 3
- CH3 secondary echo 잡힘 / CH4·CH5 lateral ant too-deep 문제 해결
- Bladdy 3-tap → Developer Mode toggle (측정 화면에서도)

## Placement 가이드 (PlacementGuideView)
- 가로 모드 대응: bodyW = min(w, h*0.7f) — 태블릿 가로에서 portrait 비율 유지
- Sensor 이미지를 Canvas drawImage 로 이동 — bladder oval 좌표계와 정확히 정렬
- Start Alignment 버튼: enabled = true 명시

## Settings overlay
- "Ant Offset" 숫자 입력 필드 (0~20mm, dev mode 전용)
  · BV 계산 시 ant 를 입력값만큼 probe 쪽으로 당김 (chord 증가 → BV 증가)
  · mm→samples 변환은 PiezoHW.distancePerSample
  · V41 SphereFit BV 는 offset≠0 일 때 우회 → 6ch estimate
- "Version" row 추가 (BuildConfig.VERSION_NAME)
- Save 버튼: 실제 UserStorage 영구 저장 + state 재전파
  · AppState.savePiezoSettingsNow() 추가
- 전체 overlay 에 verticalScroll + Card maxHeight 90% — dev 컨텐츠 길어도 Save 도달

## DPS 동기화 (긴급 수정)
설정의 DPS 변경이 V41Detector(Geometry/AnatomicalGate)에 반영 안 되던 문제:
- WdConfig.DPS_DEFAULT: const val → @Volatile @JvmField var
- PiezoHW.distancePerSample setter 가 WdConfig.DPS_DEFAULT 자동 동기화
- 시작 시점 1.968.also { WdConfig.DPS_DEFAULT = it } 로 두 값 일치

## 버전
- versionName: VBTAND0101 → 2026005-101
- versionCode: 2 → 3

## 신규 모듈
- services/ScanLogStore.kt — dev mode 진단 로그 (마지막 Single Scan summary)
  · WALLS CSV (ch,ant,post,urine), DATA CSV (raw ADC), antOffsetMm
- ui/components/ScanLogDialog.kt — 클립보드 복사 다이얼로그
  · xbench (CharlesKWONsLaw) MbbLogDialog 패턴 차용
  · SelectionContainer + monospace + Copy/Clear/Close
- 측정 화면 dev panel 에 "Scan Log (N)" 버튼 추가

## 데이터 모델
- PiezoSettings.antOffsetMm: Int = 0 필드 추가

## 단위 테스트
- V41DetectorCH4Test.kt — 2026-05-06 floating-signal 캡쳐 6채널 raw 를
  V41Detector.detect 에 통과시켜 결과 출력 (CH4 가짜 wall 진단용)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 18:01:33 +09:00
dw.jang 758930796f fix: critical code review — thread safety, state management
CRITICAL fixes:
- #1: lastMultiChannelResult 변경을 handler.post로 (메인 스레드 보장)
- #3: measureDoneSignal var=remember → val mutableStateOf (recomposition 안전)
- #34,35: BleManager bluetoothGatt/tx/rxCharacteristic/lastRxTimestamp @Volatile
- #66: GreenZoneConstants 모든 mutable var @Volatile
- #56: PiezoHW.distancePerSample @Volatile
- nested handler.post 제거 (불필요한 프레임 지연)
- maa throttle deadlock 방지 (Boolean 반환 + state 리셋)
- Plot/Log 불일치 수정 (channelLogs를 allWalls 기준으로 통일)
- Placement 센서 위치/화살표 조정, gradient dead zone, CV relaxation
- 개발자 모드 CV 표시, 가이드 상자 항상 표시

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-08 16:25:06 +09:00
dw.jang c819d615d0 fix: LR 방향 수정 + Almost there 제거 + postMaxIdx 슬라이더
- 좌우 방향 전면 수정: ch4(왼쪽)길다→←, ch5(오른쪽)길다→→
  ch4만 감지→← (기기 오른쪽 치우침), ch5만→→
- Almost there 제거: 방향OK+CV미달 → 그냥 GREEN 통과
- postMaxIdx 런타임 슬라이더 (개발자 설정, 기본 80, 범위 50-95)
  Method A/B 공통 적용 — 바닥반사 peak 무시

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-08 09:41:44 +09:00
dw.jang e5b252b314 feat: Detection/BV 분리 설정 + Spot 5회 trimmedMean + UI 여유
- Detection(A/B/C)과 BV(Frustum/V41) 독립 설정 (개발자 모드)
  기본값: Detection=C, BV=V41
- Spot: 3회 median → 5회 1.5초간격 trimmed mean (Auto와 동일)
  최대 8회 재시도, 최소 유효 3개
- 일반 모드 도넛 320dp, 간격 28dp, 버튼 62dp (화면 여유)
- Placement mm 표시 제거 — 방향만 안내

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 17:28:04 +09:00
dw.jang 1b70ec809d fix: preset 감지 오류 (BV 2배 차이 근본원인) + 도넛 비율 + placement 자동통과
- autoDetectPreset: 기기이름 뒤에서 3번째 글자로 각도타입 판별
  VBT26050301 → '3' → V2 (기존: VBT2604 매칭 실패 → V0 → 각도 0° → BV 절반)
- 도넛차트: level 단계별 → maxVolumeMl/maxVolume 연속 비율로 변경
- Placement "Almost there" 3회 연속 → 자동 GREEN 통과

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 16:13:13 +09:00
dw.jang b73921eb2b 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>
2026-05-07 15:36:14 +09:00
dw.jang 139ebcbbb1 refactor: BV estimation — y-z/x-z ellipse fitting, wall repair, outlier removal
Port of appshare #21 merge (bv_estimation.py):
- repairCenterWallsFor6ch: gap=1 보간, gap>=2 top 그룹 제거
- computeLrRatio: chord 비율 → x-z 6점 타원 피팅 (b_lr/a_ap)
- Post-median outlier 제거 (median ±25%, ≥3ch)
- Cap 높이: ellipseCapHeights 삭제 → y-z 8점 타원 피팅 inline
- Top cap 상한: 미검출 상위 채널 빔 y좌표 기준 제한

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 12:06:41 +09:00
dw.jang 53a07195f8 feat: developer mode, SWEEP placement, gradient guide, spot flicker fix
- Developer mode: 캐릭터 3탭 토글, dev-only settings/graphs/logs
- SWEEP placement (CKLaw): BladderSphereSeek circle fit + RTracker
  + argmax + coronal lateral + R-peak gate GREEN lock
- Gradient placement: weighted center gradient 기반 상하 판정,
  방향 로직 수정 (ch0=위, ch3=아래), 초기 멘트 "치골 위에 배치"
- Spot 측정: isSpotInProgress 플래그로 중간값 화면 깜빡임 제거
- 좌우 애매한 ↔ 멘트 제거 → 구체적 방향 또는 상태 안내

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 11:32:03 +09:00
dw.jang d6e981e1d2 feat: 채널별 빔 각도 보정 (Python PR#20 동기화)
Method B:
- 채널별 otsu_ratio = OTSU_RATIO × cos(θ) 보정
- 큰 각도 채널은 threshold 완화 → 약한 후위벽 감지 개선
- detectLowEcho에 otsuRatioOverride 파라미터 추가
- analyzeMultiChannelWithTgc: initial + rerun 모두 각도 보정 적용

Method A:
- 채널별 min_wall_contrast_ratio = 1.2 × cos(θ) 보정
- detectMethodA에 contrastRatioOverride 파라미터 추가
- analyzeChannel에서 PiezoHW.degreeAll 기반 자동 계산

Monitoring:
- Detachment LED: 상태 변경 시에만 전송 (wasDetached 추적)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 16:32:31 +09:00
dw.jang 258bad9a26 feat: Foreground Service + Detachment 개선 + Placement 가이드 모드
Foreground Service (BleForegroundService.kt):
- BLE 연결 시 자동 시작, 해제 시 자동 중단
- PARTIAL_WAKE_LOCK (최대 4시간)
- Notification: "VesiScan - BLE 기기 연결 중..."
- connectedDevice foregroundServiceType
- Doze 모드에서도 BLE 통신 유지
- keepScreenOn도 유지 (추가 안전장치)

Detachment 개선:
- THR_MEAN/STD 100→30 (완전 탈착만 감지, 거리 약화 무시)
- 6채널 모두 수신됐을 때만 체크 (BLE 거리 문제와 구분)
- Placement: if(false) 제거 → 활성화 + "센서가 부착되지 않았습니다"
- Monitoring: 정상 복귀 시 LED OFF

Placement 가이드 모드:
- Simple (기본): 3+채널 → 바로 좌우 (기존)
- Boundary: 올라갔다 내려오기 패턴 (경계 감지 → 최적점 복귀)
- 설정 톱니바퀴에 Simple/Boundary 스위치

BLE 로그:
- 역순 표시 (최신이 위) — 스크롤 위치 유지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 16:09:54 +09:00
dw.jang 3370fd8087 fix: 후면반사 오판방지 수정 + 1채널 구 BV 추가
BackReflection.kt (Python 동기화):
- consensusIdx: min_votes=3 (후보 3개 미만이면 None)
- buildCandidate: post≈global_br이면 None 반환 (post를 BR로 판단 안함)
- isSuspiciousPostPeak: post>ant → post>ant×1.5 (amp_ratio=1.5)
- isReflectionOnlyMultichannel: post>ant → post>ant×1.5

BV Estimation:
- 1채널 구(sphere) 계산 추가 (Python _single_channel_bv)
  - V = (4/3)πR³, R = D/2 (cos 보정)
  - centerWallCount >= 1이면 BV 계산 (기존 3 → 1)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 17:42:30 +09:00
dw.jang 30c1d4d8db feat: Python 알고리즘 대규모 동기화 — 후면반사 재작성 + detect_core + wall selection
BackReflection.kt (신규):
- 합의 기반 다채널 BR 시스템 (Python pipeline.py 1:1)
- buildCandidate: post 뒤 strongest peak 또는 post 자체
- consensusIdx: outlier 제거 후 median
- isReflectionOnlyMultichannel: 조건A(shared_idx) + 조건B(post 클러스터)
- isSuspiciousPostPeak: post>ant 또는 shared 근접
- selectIdx + resolveIndices: 최종 BR/suppress 위치 결정
- chooseResult: rerun vs initial fallback 로직
- suppress: valley early-stop 50.0 (over-erasure 방지)

PiezoEchoAnalyzer.kt:
- Otsu: separability/fallback 제거, OTSU_RATIO 0.85→0.9, Otsu-only 단일 시도
- detectLowEchoCore: 비대칭 score (ant_reliability × ant_depth + post_depth)
  - FP: postProm < minPeakMargin reject
  - FP: sg[ant/post] < threshold reject
  - wallLowMeanMinRatio 제거
- selectWallByProminence: ANT=nearest outer peak, POST=edge_score
- findRightValley/findLeftValley: descending-first logic (Python 동일)
- refineRightEdge: 병합 span 우측 트리밍
- POST 먼저 찾고 ANT는 반사 fallback (post-e 거리 대칭)
- analyzeMultiChannelWithTgc: Python pipeline.py 전체 흐름
  - TGC 전체 채널 통합 (applyTgcMultichannel)
  - initial detect → BR candidate → consensus → reflection_only
  - resolve → suppress → rerun → choose
- LOW_ECHO_AMP: 1250 (config_6ch 동일)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 16:34:18 +09:00
dw.jang e528bc6761 fix: Python 대조 CRITICAL 10건 수정 (Method A/B 완전 동기화)
Method B:
- #1 Otsu: limit 제거 → 전체 신호로 계산
- #2 detectLowEcho: 2단계 fallback (Otsu→None이면 고정1250 재시도)
- #3 LOW_ECHO_AMP: 1150 → 1250
- #4 low mask: analysisLimit 제한 제거 → 전체 신호 마스킹
- #5 postH >= threshold 체크 제거 (Python에 없음)
- #6 merge gap < 3: 무조건 병합 (peak_check_min_gap=3)
- #7 TGC: raw fit → log 압축 후 fit (Python fit_attenuation_lines 동일)
- #8 Back reflection: 다채널 consensus → 채널별 개별 (Python pipeline.py 동일)

Method A:
- #9 sliding_scores_1d low quantile: floor index → np.quantile 선형 보간
- #10 score_thr: 중간값 근사 → np.quantile(0.5) 선형 보간
- npQuantile() 헬퍼 함수 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 14:33:58 +09:00
dw.jang 6614e915d6 feat: Method B TGC+FP필터, Placement UI 리디자인, 네비게이션 개선
Method B 알고리즘 (Python 동기화):
- TGC 파이프라인: applyTgc (감쇠 보상, adaptive ratio)
- 후면반사 제거: findBackReflectionIdx + suppressBackReflection
- 멀티채널 합의: findBackReflectionMultichannel (median)
- FP 필터: wallMin/lowMean < 1.15, score < 3000
- peakMin: max(lowMean+margin, threshold)
- Otsu: separability 반환 + OTSU_RATIO(0.85) + fallback(sep<0.3)
- urineLen: post-ant → post-ant-1 (Python 동기화)
- ant fallback: span 시작 직전 (단조 감소 대응)

Placement UI 리디자인:
- 상반신 일러스트 (Canvas: 몸통+배꼽+치골+방광+센서+화살표)
- Green Zone: 배경 animateColorAsState 전환 + 체크마크
- 가이드 순서: 치골→위로→CH0 안잡히면 아래로→좌우 대칭
- 디바운스 4초 유지, 힌트 텍스트 Green시 초록
- 좌우 힌트: CH4/CH5 비교 기반 방향 제시
- 설정 톱니바퀴 + floating 패널 (Method A/B/C + Otsu)
- 채널 dot row 별도 배치
- Green score: CV 0.10, LR dev 0.25 (엄격화)

도넛차트 UI:
- Max Vol 카드 제거 (4→3칸), InfoCard 높이 통일
- Void/Catheterize 버튼 (2줄, 56dp)
- Quick Save: 실제 측정값 표시 + volume 0 리셋
- 기본 Method C

네비게이션:
- Placement 뒤로가기 → 연결중이면 Monitoring (Home 안감)
- Home Start → 연결중이면 바로 Monitoring (DeviceScan 스킵)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 16:56:51 +09:00
dw.jang c9cd3cb3ce feat: Method C (V4.1 CharlesKWON) 통합 + Placement 설정 패널
Method C (walldetect/ 패키지, 38파일):
- V41Detector: OS-CFAR threshold + wavelet denoise + anatomical gate + B-mode score
- Multi-method BV dispatch: frustum(≥2ch) / Verathon(1ch) / sphere fit(cross-check)
- Kasa→LM sphere fitting, sub-sample parabolic refinement
- 패키지 이동: com.medithings.charleskwonslaw → com.example.medilightv2android

통합:
- 설정 UI: A/B 스위치 → A/B/C 버튼 그룹 (A=보라, B=파랑, C=주황)
- Monitoring: Method C 선택 시 V41Detector.detect() → BV dispatch 결과 사용
- Placement: 선택된 Method(A/B/C) 반영 + 톱니바퀴 설정 패널 추가

Placement 그래프 개선:
- Threshold 수평선 (주황 점선)
- Y축 눈금 (0, 1k, 2k)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 11:06:22 +09:00
dw.jang 9e350d6d40 fix: Python 대조 검증 — CRITICAL 차이 7건 수정
Method A (PiezoEchoAnalyzerA.kt):
- sliding_scores_1d: edge padding 추가 (np.pad mode="edge" 동일)
- robust_z: 짝수 길이 시 두 중간값 평균 (np.median 동일)
- plateau threshold: 전체 배열 기반 quantile (잘린 배열 → 전체)
- urineLen: post-ant → post-ant-1 (Python 동일)
- score: ((ant+post)/2-lowMean)*len → sg[ant]+sg[post] (Python 동일)
- cross-validation: vals 미변경 + Math.round() (Python 동일)

BV Estimation (PiezoBVEstimator.kt):
- computeLrRatio maxRatio 기본값 3.0 → 1.0 (Python 동일)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 19:00:17 +09:00
dw.jang c849943b4e feat: Otsu adaptive threshold, Method A 포팅, BV ellipse cap, UI 정리
Algorithm:
- Otsu 1D adaptive threshold 포팅 (span_utils.py otsu_1d, 기본 ON)
- Method A 벽 검출 포팅 (PiezoEchoAnalyzerA.kt: plateau score + prominence)
- Method A cross-validation (채널간 중앙값/gradient 보정)
- BV cap 높이: parabolic → ellipse 피팅 (8점 축정렬 타원)
- Bottom cap: sphere, Top cap: cone (Python bv_estimation.py 동기화)
- 설정에 Method A/B 토글 (왼쪽=A, 오른쪽=B)

Logging:
- BLE 로그 + CSV에 otsu threshold 값 기록
- 측정 로그에 method label (A/B) 표시

UI:
- 설정 패널: floating overlay로 변경 + MaxVolume 슬라이더 추가
- Onboarding: 1페이지로 단순화 (Pager/indicator 제거)
- Registration: 한 페이지에 전체 입력 + Skip 버튼
- Placement: 로딩 인디케이터/스캔 텍스트 삭제

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 18:43:56 +09:00
dw.jang 5cdae8a86e fix+UX: BLE 중복수신 수정, peak detection 개선, UI 대폭 정리
BLE:
- GATT 다중연결 race condition 수정 (reconnectConnecting 플래그)
- onCharacteristicChanged API 레벨 분기 (dual callback 방지)
- DeviceScan: isConnected → isServiceReady 전환 (서비스 준비 후 화면전환)
- Placement: mpa/maa 500ms 딜레이 (power on 대기)
- Disconnect & Unbond 기능 추가 (msr? + removeBond + 저장정보 삭제)
- autoConnect 삭제 (HomeView)

Algorithm:
- 후위벽 피크 >= threshold 조건 추가 (false positive 차단)
- low-echo 분석범위 80샘플 제한 (뒤쪽 노이즈 무시)
- urineLen: post-ant-1 → post-ant 수정
- Placement: UrinAI fallback 제거 (PiezoEchoAnalyzer만 사용)
- 기기 프리셋 autoDetect index 7→8 수정 (VBT26040Nxx)
- dps 기본값 1.771 → 1.968

UI:
- 도넛차트: Home버튼 삭제, Auto/Catheter count/History 삭제
- 3버튼 레이아웃 (Catheterize/Auto/Spot)
- 배터리 아이콘 (60+초록/30+주황/빨강) + 설정 톱니바퀴 분리
- SensorSelect/Consent 페이지 스킵
- 온보딩: VesiScan-Basic, NIRS 페이지 삭제
- MaxVolume: Step 표시 삭제, mL 오른쪽 배치
- BackHandler 전체 재정리 (자연스러운 뒤로가기)
- BLE 신호 기준 완화 (Good≥-80, Fair≥-90)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 17:03:37 +09:00
dw.jang 7dea5b2eca feat: SG Filter on/off 토글 추가 (Settings 패널)
- PiezoEchoAnalyzer.useSgFilter: true/false로 SG 필터 on/off
- OFF 시 raw ADC 그대로 low-echo detection에 사용
- Settings 패널에 Switch 토글 추가 (기본 ON)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 17:34:21 +09:00
dw.jang f391f404b4 feat: Settings에 Low Echo Threshold + Distance/Sample 슬라이더 추가
1. Low Echo Threshold: 800~2000 슬라이더 (기본 1150, 주황색)
   → GreenZoneConstants.lowEchoAmp 런타임 변경
   → CH4/CH5 threshold 조절로 lateral 감지 개선

2. Distance/Sample: 1.5~2.5mm 슬라이더 (기본 1.771, 초록색)
   → PiezoHW.distancePerSample 런타임 변경
   → 기기별 캘리브레이션 가능

3. 도넛차트 ml 실시간 표시: estimatedVolume → lastVolumeMl 직접 표시
   → 측정 즉시 정확한 ml 반영 (기존: 레벨 변환 후 재계산으로 지연)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 16:50:31 +09:00
dw.jang 73ceb40ed4 fix: lr_ratio 하한 1.0 제한 (500ml 팬텀에서 100ml로 과소평가 수정)
원인: CH4/CH5 lateral이 center 대비 짧아서 lr_ratio=0.32 계산
→ S = π/4 × D² × 0.32 → 용적 68% 과소평가

수정: computeLrRatio() 반환값에 .coerceAtLeast(1.0) 추가
원형(1.0)보다 좁은 방광 단면은 물리적으로 불가능하므로 하한 제한

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 15:53:16 +09:00
dw.jang ea19b74c00 feat: DetachmentDetection 포팅 + Placement LED 4/5/6 동적 제어
DetachmentDetection (detachment_detection.py 1:1 포팅):
- ADC [20:80] 구간에서 baseline([80:100] median) 차감
- mean(|y|) < 100 AND std(y) < 100 → 미부착(detached)
- center 4채널(CH0~CH3) 기준

PlacementGuide LED 제어:
- 미부착 → mls?(4) DETACH_WARNING + "Sensor detached" 메시지
- 탐색 중 (CV > threshold) → mls?(5) ALIGN_SEARCHING
- 최적 위치 (PASS) → mls?(6) ALIGN_COMPLETE
- 스캔 종료 → mls?(0) OFF

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 15:21:23 +09:00
dw.jang 994d5e0c46 fix: adaptive threshold 비활성화 (기본 false, 고정 1150 복원)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 15:06:13 +09:00
dw.jang 6889136783 feat: adaptive threshold 구현 (percentile + 고정값 blended)
detectLowEcho: 고정 1150 → adaptive threshold (기본 활성화)
  1. ringSkip(3) 이후 신호에서 q25/q50/q75 계산
  2. percentile 기반: q25 + iqr*0.3 ~ q50 - iqr*0.3
  3. 고정값(1150) 범위 제한: ×0.7 ~ ×1.5 (805~1725)
  4. 가중 평균: 고정 40% + adaptive 60%
  → 신호 레벨에 자동 적응하되 급격한 변동 방지

WaveformChart: threshold 점선도 adaptive 값으로 표시
useAdaptiveThreshold 플래그로 on/off 전환 가능

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 14:58:28 +09:00
dw.jang 4cdd69ef3d feat: low echo detection + Placement 알고리즘 업데이트 (원은지님 공유)
Low Echo Detection 업데이트:
- VALLEY_STOP_RISE: 10→50 (valley 스캔 early stop 임계)
- EDGE_DIST_DECAY: 0.12 (신규, edge 거리 penalty)
- Edge boundary 확장: left [leftLo:edge+1], right [edge-1:rightHi+1]
- Peak scoring: prominence → prominence / (1 + 0.12 × dist)
  (low echo에 가까운 peak이 더 높은 점수)

Placement 알고리즘 전면 교체 (CV threshold 방식):
- 기존 urineLen score → cos-corrected depth의 CV(변동계수)
- CV ≤ 0.15 → PASS (Best Placement)
- CV > 0.15 → FAIL → weakest channel 기반 방향 안내
  - CH0/CH1(top) weakest → MOVE DOWN
  - CH2/CH3(bottom) weakest → MOVE UP
  - severity: slight(<10%) / moderate(10~25%) / severe(≥25%)
- LR deviation: CH4/CH5 cos-corrected depth 비교
  - |lr_dev| ≤ 0.15 → PASS
  - lr_dev > 0 → MOVE RIGHT, < 0 → MOVE LEFT
- 반복 시 CV threshold 완화 (+0.02/회)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 10:22:05 +09:00
dw.jang 98d3b11a46 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>
2026-04-23 16:18:49 +09:00
dw.jang c114592ab1 feat: 6ch 알고리즘 포팅 + urineLen 기반 정밀 Placement Guide
6ch 알고리즘 (config_6ch.py / bv_estimation.py 포팅):
- PiezoHW: V0/V1/V2 프리셋 (Snell's law 굴절 각도)
- computeLrRatio(): CH4/CH5 lateral에서 타원 단면 비율 동적 계산
- estimateBladderVolume6ch(): center 4ch + lateral lr_ratio
- denoise(): TVD 제거, SG 필터만 사용
- LOW_ECHO_AMP: 1600→1150, distancePerSample: 1.771

Placement Guide 2단계:
- 1단계: 이진 판정 (잡힘/안잡힘) → 기본 방향
- 2단계: urineLen 기반 정밀 가이드 (대칭+균형+크기)
  - 세로: CH0/CH3 비율 1.5배 이상이면 방향 제시
  - 가로: CH4/CH5 차이 5칸 이상이면 방향 제시
- Placement Score = 세로대칭(30%) + 가로균형(30%) + 중앙크기(40%)
- 각 채널에 urineLen 숫자 표시

VesiScan_Android_Pipeline_Summary.md 문서 작성

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 10:22:57 +09:00
dwjang 5a084ac5a9 feat: auto-detect device preset from BLE name
VBT260400xy → FLAT, VBT260402xy → NEW_CASE2, VBT260403xy → NEW_CASE1
2025MEDIP → LEGACY_5CH

Auto-called on BLE connect. Logged to debug.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 17:29:38 +09:00
dwjang c1c6dfc16a feat: port algorithm core from iOS (4 files)
GreenZoneConstants.kt — all 16 constants (thresholds, physical)
UrinAI.kt — urine gatekeeper (binary threshold detection)
PiezoEchoAnalyzer.kt — low-echo detection with prominence-based wall selection
PiezoBVEstimator.kt — Frustum BV estimation with device presets

1:1 port from Swift, same logic, same constants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 16:14:07 +09:00
dw.jang 0e13f2bbb6 feat: consent screen, reminder system, medical metrics, sex field, design tokens, code quality
A. Reminder: ReminderService (AlarmManager chain), ReminderSettingsView,
   bell icon in Piezo top bar, reschedule on catheterization
B. Consent: ConsentView (3 mandatory items), onboarding→consent→registration flow,
   persistent consent storage, sex field in User + Registration Step 2
C. Diary: 24hUV, NUV, NPI, max capacity, day/night frequency metrics card
D. Design: DesignTokens.kt, 48dp touch targets, DonutChart semantics, pressScale modifier
E. Quality: BuildConfig.DEBUG log wrapping, DateFormatter caching, background I/O for diary store

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 14:26:02 +09:00
dw.jang 254381dbaa feat: BLE stability fixes, NIRS streaming improvements, catheter count feature
- Fix BLE API deprecation warnings with API 33+ version checks
- Move isServiceReady to onDescriptorWrite for reliable CCCD setup
- Fix battery polling: immediate query on connect, pause during NIRS streaming
- Add mcj retry mechanism for NIRS streaming reliability
- Add debug logging to BLE, NIRS parsing, and processing pipeline
- Add catheter count feature to Piezo monitoring (persistent count, add dialog, auto-decrement on catheterize)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:26:36 +09:00
dw.jang a8532917b4 Initial commit: VesiscanBasic Android app
Port of MedilightV2 iOS app to Android with Kotlin + Jetpack Compose.
Includes BLE connectivity, NIRS/Piezo sensor modes, Rive animations,
interactive measurement history charts, and foldable device support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:27:00 +09:00