package com.example.medilightv2android.managers /** * Green Zone 전체 파이프라인의 공유 상수 — Single Source of Truth. * * UrinAI, PeakDetection, Scoring 모두 이 상수를 참조. * 파일 간 임계값 불일치 방지. * * 원본: xBench/GreenZoneConstants.kt (Charles KWON) * 포팅일: 2026-04-17 * * ⚠ 미세 조정 시 이 파일만 수정하면 전체 파이프라인에 반영됨. * 각 상수의 의미와 영향 범위를 아래 주석 참고. */ enum class DetectionMethod { METHOD_A, METHOD_B, METHOD_C } enum class PlacementGuideMode { SIMPLE, BOUNDARY, SWEEP } enum class BvMethod { FRUSTUM, V41 } /** Sensor alignment 알고리즘 — V1=기존 (computePlacementGuide), V2=신규 (alignment.py 포팅). */ enum class AlignmentAlgo { V1, V2 } object GreenZoneConstants { @Volatile var postMaxIdx: Int = 80 @Volatile var detectionMethod: DetectionMethod = DetectionMethod.METHOD_C @Volatile var bvMethod: BvMethod = BvMethod.V41 @Volatile var placementGuideMode: PlacementGuideMode = PlacementGuideMode.SIMPLE @Volatile var alignmentAlgo: AlignmentAlgo = AlignmentAlgo.V1 // dev 토글 — default V1 (legacy) /** Posture pitch 임계값 (deg). |pitch| > 이 값 → LYING, ≤ 이 값 → NON_LYING. dev 모드에서 조정 가능. */ @Volatile var pitchLyingThreshold: Float = 25f /** Posture acc magnitude gate min (g). 이 값 미만이면 classifier UNKNOWN. */ @Volatile var postureAccMagMin: Float = 0.8f /** Posture acc magnitude gate max (g). 이 값 초과면 classifier UNKNOWN. */ @Volatile var postureAccMagMax: Float = 1.2f /** Posture polling 간격 (ms). PiezoMonitoring 에서 mtb 보내는 주기. dev 모드에서 조정 가능. */ @Volatile var posturePollIntervalMs: Long = 1000L // ── Walking detector 임계값 (flowchart 4-2 / 5-2) ───────────────── /** Walking 인정 최소 pitch (deg). |pitch| > 이 값 이라야 walking 후보. */ @Volatile var walkingMinPitchDeg: Float = 45f /** Walking 시작 — gyro 이동평균 (wmean) 임계 (dps). 실측: stand-up + 자세조정 * motion 이 5~10 dps 지속 → 임계 15 로 false-trigger 차단. 진짜 보행은 50+ dps. */ @Volatile var walkingStartWmean: Float = 15f /** Walking 시작 — gyro 이동표준편차 (wstd) 임계 (dps). */ @Volatile var walkingStartWstd: Float = 2f /** Walking 시작 — 두 임계 모두 만족한 채 지속해야 하는 최소 시간 (ms). 실측: stand-up * motion 이 3-5초 → 6초 로 차단. 실제 보행은 무한 지속이라 6초 이상 자동 만족. */ @Volatile var walkingStartHoldMs: Long = 6000L /** Walking 유지 — gyro wmean 임계 (dps). 시작보다 낮음. */ @Volatile var walkingKeepWmean: Float = 2f /** Walking 유지 — wmean 임계 미달 후 종료까지 grace 시간 (ms). 미만 지속이 이 값 넘으면 walking 종료. */ @Volatile var walkingKeepHoldMs: Long = 2000L /** Walking detector sliding window (ms). 이 시간 분량 sample 평균/표준편차. */ @Volatile var walkingWindowMs: Long = 1500L // ═══════════════════════════════════════════════════════════ // 신호 범위 // ═══════════════════════════════════════════════════════════ /** raw ADC 중 사용할 샘플 수. * Kotlin 원본: 90. VesiScan maa 응답은 100 samples 전송하지만 끝부분 10개는 * CRC/noise 가능성 → 앞 90개만 사용. * 영향: UrinAI 탐색 범위, PeakDetection 탐색 범위 */ const val useSamples: Int = 90 /** 초기 ringdown 스킵 (센서 근접 반사 무시). * idx 0~2는 피에조 소자 직접 반사 → 항상 높은 값 → 액체로 오판 방지. * 영향: UrinAI.fw 탐색 시작점, PeakDetection.fw 탐색 시작점 */ const val ringSkip: Int = 3 // ═══════════════════════════════════════════════════════════ // 액체(소변) 판정 임계 — 12-bit ADC (0~4095) // ═══════════════════════════════════════════════════════════ /** Low-echo detection 임계값 (denoised 신호 기준). * denoised ≤ 이 값 → low-echo (소변 후보). * UrinAI의 liquidThrLoose(raw 기준)와 다름 — 여기는 TVD+SG 적용 후 신호 기준. * Python 원본: 1150 (low_echo_detection_method_b.py LOW_ECHO_AMP, 6ch 버전) * 영향: PiezoEchoAnalyzer → low-echo span 탐지, 벽 찾기의 기반 */ @Volatile var lowEchoAmp: Float = 1250f /** 구조 이진화 임계값: raw < 이 값 → 액체(liquid), ≥ → 조직(tissue). * 낮출수록 엄격 (더 확실한 액체만 인정), 높일수록 관대. * Kotlin 원본: 1400. * 영향: UrinAI.binarize → fw/bw 탐색의 기반 */ const val liquidThrLoose: Float = 1400f /** 순수 액체 확인용 엄격 임계값: raw < 이 값 → "확실한 소변". * liquidThrLoose보다 낮아야 함. * 이 기준으로 연속 액체 구간(liquidRun) 측정. * Kotlin 원본: 1100. VesiScan 실측에서 팬텀 low-echo가 1100~1400 범위. * 1100→run=0, 1300→run=2 (미달). 1400으로 상향. * liquidThrLoose(1400)와 동일하게 설정 — 팬텀에서는 loose/strict 구분 불필요. * ⚠ 인체 측정 시 loose > strict 으로 재분리 필요할 수 있음 * 영향: UrinAI.maxRun 계산 → 최종 판정의 핵심 */ const val liquidThrStrict: Float = 1400f /** 최소 연속 순수 액체 길이. * maxRun ≥ 이 값이어야 "소변 있음" 판정. * 소방광(50mL) 대응: chord≈24 중 순수 액체 5+ 필요. * Kotlin 원본: 5. * 영향: UrinAI 최종 판정 (detected 조건 1/3) */ const val minLiquidRun: Int = 5 // ═══════════════════════════════════════════════════════════ // 벽 검출 범위 // ═══════════════════════════════════════════════════════════ /** bw - fw 최소 (chord 최소 길이). * 소방광 한계: 15 → 30mL(chord≈20) 이상 검출 가능. * 50mL(chord≈24): margin 9 ✓ * 30mL(chord≈20): margin 5 ⚠ * 25mL 이하: 검출 불가 (임상적으로 PVR 50mL+ 의미 있는 범위) * Kotlin 원본: 15. * 영향: UrinAI 최종 판정 (detected 조건 2/3), PeakDetection bw 탐색 하한 */ const val minChord: Int = 15 /** bw - fw 최대 (이론적 상한). * USE_SAMPLES(90)가 실질 제한. * Kotlin 원본: 80. * 영향: PeakDetection bw 탐색 상한 */ const val maxChord: Int = 80 // ═══════════════════════════════════════════════════════════ // 품질 // ═══════════════════════════════════════════════════════════ /** (wallPeak - lumenFloor) / wallPeak 최소. * 벽과 소변 사이 대비가 이 값 이상이어야 유효. * 높일수록 엄격 (선명한 벽 요구), 낮출수록 관대. * Kotlin 원본: 0.15. * 영향: UrinAI 최종 판정 (detected 조건 3/3) */ const val contrastMin: Float = 0.15f // ═══════════════════════════════════════════════════════════ // Green Zone Finder // ═══════════════════════════════════════════════════════════ /** score ≥ 이 값 → center lock (Green Zone 확정). * Scoring.THR_EXCELLENT(75)보다 낮아 빠른 UX 확보. * Kotlin 원본: 70. * 영향: Placement Guide에서 Good 판정 기준 */ const val lockThreshold: Int = 70 /** 최대 기억 측정 수 (히스토리). * Kotlin 원본: 20. */ const val maxHistory: Int = 20 // ═══════════════════════════════════════════════════════════ // 물리 상수 (교정용) // ═══════════════════════════════════════════════════════════ /** ADC 샘플링 레이트 (Hz). VesiScan HW 사양. */ const val fs: Double = 400_000.0 /** 기준 조직 음속 (m/s). */ const val cRef: Double = 1540.0 /** 교정용 팬텀 직경 (mm). V=530mL 구. */ const val dPhantomMm: Double = 100.406 /** 팬텀 용적 (mL). */ const val vPhantomMl: Double = 530.0 /** 팬텀 반지름 (mm). */ const val rPhantomMm: Double = 50.203 }