From c3817c9f804ab033bfbad87c53ff43b4b2a36f24 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 18 Jun 2026 11:11:53 +0900 Subject: [PATCH] =?UTF-8?q?fix(align=20V1):=20GREEN=20=EA=B2=8C=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20ch0/ch3=20edge=20=EB=8C=80=EC=B9=AD=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 증상: 270 mL 방광인데 220 mL 로 측정 (~50 mL 저평가). 원인 — 센서가 살짝 아래쪽에 부착돼 ch3 가 방광 하단 가장자리 만 보고 짧게 잡힘. ch0~ch3 CV 는 0.10 임계 안에 떨어져서 "In position!" 통과되지만 BV 결과는 저평가. 원리: CV 는 4채널 전체 변동성 — outlier 1개를 N=4 로 희석. 예: depths (75,78,75,65) → mean 73.25, std 4.79, cv 0.065 → CV 0.10 통과지만 ch3 가 10mm 짧음. 수정: edge 대칭 게이트 (|d_ch0 - d_ch3| / mean(d0, d3) ≤ 0.08) 추가. CV 와 직교 — 균일 케이스는 영향 없고, edge outlier 케이스만 잡음. - DEFAULT_EDGE_ASYM_THR = 0.08 도입 - computePlacementGuide GREEN 체크에 edgeAsym/edgePass 계산 - isPass = cv ≤ cvThr && lrPass && rPeakOk && **edgePass** - hint 분기: edgeAsym > 0 → "Slide up slightly ↑ (ch3 short)" edgeAsym < 0 → "Slide down slightly ↓ (ch0 short)" - Log 에 edgeAsym/edgePass 추가 CV(0.10) 는 기존 유지. edge 만 보강. Co-Authored-By: Claude Opus 4.7 --- .../ui/views/monitoring/PlacementGuideView.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) 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 139abad..4ad3445 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 @@ -1496,6 +1496,9 @@ private val DEFAULT_CV_THR: Double else -> 0.08 } private const val DEFAULT_LR_DEV_THR = 0.20 +/** Edge 대칭 — |d_ch0 - d_ch3| / mean(d_ch0, d_ch3). ch3/ch0 가장자리 비대칭(센서 위치 오프셋) + * 잡는 게이트. CV(전체 균일성)는 outlier 1개를 희석시키므로 별도 검사 필요. */ +private const val DEFAULT_EDGE_ASYM_THR = 0.08 /** * Gradient 기반 Placement Guide @@ -1867,14 +1870,33 @@ private fun computePlacementGuide( } } + // Edge 대칭 (ch0 vs ch3) — 센서 비대칭 위치 잡기. + // 정상 정렬이면 ch0/ch3 가장자리 깊이가 비슷. 한쪽이 짧으면 센서 위치가 + // 그쪽 방광 가장자리로 치우침 (BV 저평가의 주 원인). + var edgeAsym = 0.0 + var edgePass = true + val w0 = if (0 < walls.size) walls[0] else null + val w3 = if (3 < walls.size) walls[3] else null + if (w0 != null && w3 != null) { + val d0 = ((offset + w0.second * dps) - (offset + w0.first * dps)) * + kotlin.math.cos(degAll[0] * Math.PI / 180.0) + val d3 = ((offset + w3.second * dps) - (offset + w3.first * dps)) * + kotlin.math.cos(degAll[3] * Math.PI / 180.0) + val edgeMean = (d0 + d3) / 2.0 + if (edgeMean > 0) { + edgeAsym = (d0 - d3) / edgeMean + edgePass = kotlin.math.abs(edgeAsym) <= DEFAULT_EDGE_ASYM_THR + } + } + // R-peak gate (SWEEP 모드에서만 적용) val isSweep = com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode == com.example.medilightv2android.managers.PlacementGuideMode.SWEEP val rPeakOk = !isSweep || currentRMm == null || bestRMm <= 0.0 || (bestRMm - currentRMm) <= 1.5 - val isPass = cv <= cvThr && lrPass && rPeakOk + val isPass = cv <= cvThr && lrPass && rPeakOk && edgePass android.util.Log.d("PlacementGuide", - "GREEN check: cv=${"%.3f".format(cv)} cvThr=${"%.3f".format(cvThr)} cvOk=${cv <= cvThr} lrPass=$lrPass lrDev=${"%.3f".format(lrDev)} rPeakOk=$rPeakOk → isPass=$isPass") + "GREEN check: cv=${"%.3f".format(cv)} cvThr=${"%.3f".format(cvThr)} cvOk=${cv <= cvThr} lrPass=$lrPass lrDev=${"%.3f".format(lrDev)} edgeAsym=${"%.3f".format(edgeAsym)} edgePass=$edgePass rPeakOk=$rPeakOk → isPass=$isPass") if (isPass) { val score = ((1.0 - cv / cvThr).coerceIn(0.0, 1.0) * 100).toInt().coerceAtLeast(70) @@ -1911,6 +1933,14 @@ private fun computePlacementGuide( !lrPass && lrDev < -DEFAULT_LR_DEV_THR -> { adjustHint = "Slide right →"; adjustIcon = "arrow.right" } + // Edge 비대칭 — ch3 짧음 (센서 너무 아래) → 위로 미세 조정 + !edgePass && edgeAsym > 0 -> { + adjustHint = "Slide up slightly ↑ (ch3 short)"; adjustIcon = "arrow.up" + } + // Edge 비대칭 — ch0 짧음 (센서 너무 위) → 아래로 미세 조정 + !edgePass && edgeAsym < 0 -> { + adjustHint = "Slide down slightly ↓ (ch0 short)"; adjustIcon = "arrow.down" + } gradient > 0.3 -> { adjustHint = "Slide down ↓"; adjustIcon = "arrow.down" }