Files
VesiscanClinicalAndroid/app/src/main/java/com/medithings/vesiscan/managers/GreenZoneConstants.kt
T
dw.jang 748ccbd796 revert(bv): demo-final default → METHOD_D 로 원복 (250ml 재현)
D-P (legacy bcbc7b4) 로직 진단:
  · Halir-Flusser ellipse fit · adaptive_large_bladder_relax · b_si_floor 미탑재.
  · 300ml phantom 시연 값 = 130~150ml (원리상 큰 방광 처리 불가).
  · 사용자 관찰 250ml 는 METHOD_D (Halir + adaptive) 의 결과 · D-P 로는 재현 불가.

조치:
  · default = METHOD_D 복귀. 250ml 스케일 즉시 회복.
  · D-P 는 dev-mode BV chip 에 "D-P" 로 남김 (phantom 소볼륨 실험 · 로직 대조용).
  · Legacy fork 파일 (PiezoBVEstimatorLegacyDP.kt) 유지 · 삭제 안 함.
  · DPS 1.968 은 유지 (사용자 별도 지시).

빌드: BUILD SUCCESSFUL 2s.
2026-08-11 15:14:27 +09:00

225 lines
13 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.medithings.vesiscan.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, METHOD_D }
enum class PlacementGuideMode { SIMPLE, BOUNDARY, SWEEP }
/**
* BV 계산 방법.
* - FRUSTUM : legacy analyzer walls + estimateBladderVolume6ch (Tanaka frustum)
* - V41 : Method C 의 SphereFit (methodCBvMl 직접 사용)
* - METHOD_D : Method D walls (cross-channel ant tie-break + OS-CFAR) + estimateBladderVolume6ch
* → Python `for_app_share` 임상 검증 pipeline 와 1:1 정렬 (2026-06-30).
*
* 사용 지침 (phantom 측정 vs 임상):
* - 구형 phantom (QC/calibration) : V41 (SphereFit) 권장. METHOD_D 는 lr_ratio < 1.0
* 영역에서 BV underestimate 가능 (대칭 phantom 검증 시 -38% 오차 사례).
* - 임상 인체 측정 : METHOD_D 권장. 인체 방광은 비대칭이므로 lr_ratio
* [0.5, 1.5] 자유 범위가 anatomically 올바름. Python algorithm = appshare 팀
* reference 동작과 정확 일치 (lr floor / shrinkage 의도적으로 없음).
* - METHOD_D 에 phantom-style lr floor (1.0) 를 추가하지 말 것. 사용자 결정
* 2026-06-30: "phantom QC 는 V41 로, 인체는 Python 1:1".
*/
// METHOD_D_P: 2026-08-11 신설. Python parity fix (Halir-Flusser ellipse fit ·
// adaptive_large_bladder_relax · b_si_floor · subsample refined 등) 도입 이전
// (bcbc7b4 · 2026-07-02) 시점 BV 로직 격리 fork. phantom (구 가정) 시연 재현용.
// demo-final default. 인체 (타원 방광) 정확도 필요 시 METHOD_D 사용.
enum class BvMethod { FRUSTUM, V41, METHOD_D, METHOD_D_P }
/** Sensor alignment 알고리즘 — V1=기존 (computePlacementGuide), V2=신규 (alignment.py 포팅). */
enum class AlignmentAlgo { V1, V2 }
object GreenZoneConstants {
@Volatile var postMaxIdx: Int = 80
// 2026-08-04: default detectionMethod METHOD_C → METHOD_D 로 변경했었음.
// 2026-08-10 ROLLBACK (demo-final 전용 · 사용자 요청): 8/4 이전 METHOD_C 로 복귀.
// METHOD_D 는 dev-mode 에서 수동 전환 가능. cloud-mvp 는 METHOD_D 유지.
@Volatile var detectionMethod: DetectionMethod = DetectionMethod.METHOD_C
// 2026-08-04: V41 → METHOD_D 로 변경 · 2026-08-10 ROLLBACK V41 로 복귀.
// 2026-08-10 RE-ADJUST (사용자 요청): demo-final default 최종 조합 =
// detectionMethod = METHOD_C (walls 검출 · phantom-검증 파이프라인)
// bvMethod = METHOD_D (BV 계산 · Python parity + adaptive_large_bladder_relax + b_si_floor)
// Method D BV 는 estimateBv(walls) wrapper 로 라우팅 (PiezoMonitoringView).
// 2026-08-11 (rev): default → METHOD_D 로 복귀.
// METHOD_D_P (legacy Frustum+cap) 는 Halir-Flusser · adaptive_large_bladder_relax
// 미탑재라 큰 방광 (~300ml phantom) 에서 원리상 값이 150 대까지 낮게 나옴.
// 사용자 관찰 250ml 는 METHOD_D (Halir+adaptive 적용) 결과. dev 토글에는 D-P 남김.
@Volatile var bvMethod: BvMethod = BvMethod.METHOD_D
/**
* lr_ratio 강제 override (algorithm 팀 최신 표준: piezophantomtest 4830e7c).
* - null : computed (기존 Python `compute_lr_ratio` 알고리즘)
* - Double : 강제 값 (algorithm 팀 default = 1.0)
*
* Phantom 실측 검증: computed lr=0.68 → BV -38%. lr=1.0 fixed → -1.6% (APP 일치).
* 6채널 hardware LR 분해능 한계로 computed lr 신뢰도 낮음. Dev toggle 전환 가능.
*/
@Volatile var lrRatioOverride: Double? = 1.0
@Volatile var placementGuideMode: PlacementGuideMode = PlacementGuideMode.SIMPLE
@Volatile var alignmentAlgo: AlignmentAlgo = AlignmentAlgo.V1 // default V1 (일반 사용자). V2 (Method D) 는 clinical alignment session 전용.
/** 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 idle 폴링 주기.
* 이력:
* 6/30 : mtb (1Hz × 15sample) → msp (5Hz × 1sample) — piezo 무음 확보, 주기 200ms
* (walking window 1.5초 안에 7-8 샘플 확보용)
* 7/03 : msp → mim (1Hz × 15sample, 무음) — piezo 무음 유지 + 시계열 15샘플 확보.
* 단일 폴링당 300ms 시계열 얻으므로 주기 1000ms 로 완화 (BLE 왕복 5배 감소).
* 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 지속. 진짜 보행은 50+ dps. 10 으로 완화하여 짧은 보행도 잡음. */
@Volatile var walkingStartWmean: Float = 10f
/** Walking 시작 — gyro 이동표준편차 (wstd) 임계 (dps). */
@Volatile var walkingStartWstd: Float = 2f
/** Walking 시작 — 두 임계 모두 만족한 채 지속해야 하는 최소 시간 (ms).
* 실측: stand-up motion 이 3-5초 지속 → 4초는 아슬아슬. 대신 walkingStartWmean=10dps
* 가 stand-up (5-10dps) 을 이미 걸러줌 → 4초로 UX 반응성 확보 (사용자 요청 7/03).
* 실제 보행은 지속되므로 4초 이상 자동 만족. */
@Volatile var walkingStartHoldMs: Long = 4000L
/** 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
}