From 122d9631f33f6a147fab8b0cd9e7b5d41be30746 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Fri, 26 Jun 2026 10:37:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(alignment):=20Option=20A=20=E2=80=94=20V2?= =?UTF-8?q?=20=ED=99=9C=EC=84=B1=20=EC=8B=9C=20graph=20detection=20?= =?UTF-8?q?=EB=8F=84=20method=5Fd=20=EA=B2=B0=EA=B3=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20+=202-column=20=EA=B7=B8=EB=9E=98=ED=94=84=20+=20Y?= =?UTF-8?q?=EC=B6=95=201.5=C3=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Option A: - V2 활성 시 channelDetected 를 method_d 의 summary.detected 로 덮어씀 - PiezoEchoAnalyzer (wall pair 양쪽 필요) ↔ method_d (low-echo span만) 의 결과 차이 해소 - ch3 같은 한쪽 벽 약한 채널이 "메시지=검출 / 그래프=미검출" 이던 시각 모순 해결 UI: - 6개 echo waveform 세로 적층 → 2-column (좌: ch0/1/2/3 머리→치골, 우: ch4/5 좌·우 lateral) - Y축 그래프 높이 80dp → 120dp (1.5×) 로 peak 가시성 ↑ Co-Authored-By: Claude Opus 4.7 --- .../ui/views/monitoring/PlacementGuideView.kt | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 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 63f8993..d6c00b0 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 @@ -668,12 +668,12 @@ fun PlacementGuideView(appState: AppState) { verticalArrangement = Arrangement.spacedBy(6.dp) ) { Text("Echo Waveforms", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary) - val graphMethod = com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod - val graphAnalyzerA = com.example.medilightv2android.managers.PiezoEchoAnalyzerA.shared - for (ch in 0 until 6) { + // 채널별 그래프 항목 — 좌/우 column 에 4개/2개씩 분배 (해부학적 위치 직관). + // Y 축 높이 80→120 (1.5×) 로 peak 잘 보이게. + @Composable + fun ChannelGraphItem(ch: Int) { val chData = lastChannelData.find { it.channel == ch } val detected = if (ch < channelDetected.size) channelDetected[ch] else false - // 그래프 벽 표시: lastWallResults에서 가져옴 (선택된 Method 결과와 동일) val wallPair = if (ch < lastWallResults.size) lastWallResults[ch] else null val showWalls = if (wallPair != null) { com.example.medilightv2android.managers.LowEchoResult( @@ -706,17 +706,30 @@ fun PlacementGuideView(appState: AppState) { analyzer = analyzer, antIndex = showWalls?.ant, postIndex = showWalls?.post, - modifier = Modifier.fillMaxWidth().height(80.dp) + modifier = Modifier.fillMaxWidth().height(120.dp) ) } else { - Box(modifier = Modifier.fillMaxWidth().height(80.dp).background(Color.Gray.copy(alpha = 0.05f), RoundedCornerShape(4.dp)), + Box(modifier = Modifier.fillMaxWidth().height(120.dp).background(Color.Gray.copy(alpha = 0.05f), RoundedCornerShape(4.dp)), contentAlignment = Alignment.Center) { Text("No data", fontSize = 10.sp, color = MlSecondaryText.copy(alpha = 0.5f)) } } } - if (ch < 5) { - HorizontalDivider(color = Color.Gray.copy(alpha = 0.2f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + // 좌: 0/1/2/3 (머리 → 치골 방향) + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { + for (ch in 0..3) { + ChannelGraphItem(ch) + if (ch < 3) HorizontalDivider(color = Color.Gray.copy(alpha = 0.2f)) + } + } + // 우: 4/5 (좌·우 lateral) + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { + for (ch in 4..5) { + ChannelGraphItem(ch) + if (ch < 5) HorizontalDivider(color = Color.Gray.copy(alpha = 0.2f)) + } } } } @@ -857,6 +870,14 @@ fun PlacementGuideView(appState: AppState) { else DoubleArray(100) } val st = v2Aligner.push(raw6) + // Option A — UI graph detection 도 method_d 결과로 통일 (V2 활성 시). + // PiezoEchoAnalyzer 는 wall pair (ant+post) 둘 다 필요해 한쪽 약하면 미검출. + // method_d 는 low-echo span 만 봐서 검출. 알고리즘 ↔ 시각 일치를 위해 덮어씀. + st.summary?.detected?.let { md -> + for (i in 0 until 6) { + if (i < md.size) channelDetected[i] = md[i] + } + } bleManager.debugLogger.info("ALIGN_V2 phase=${v2Aligner.phase} state=${st.state} action=${st.action} u4=${st.summary?.u4} u5=${st.summary?.u5} ch3=${st.summary?.ch3} bestSet=${v2Aligner.bestCenterSet}") if (st.state == "accum") { directionHint = st.msg