diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/clinical/ClinicalLiveView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/clinical/ClinicalLiveView.kt index dc3d2a0..7061a40 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/clinical/ClinicalLiveView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/clinical/ClinicalLiveView.kt @@ -78,10 +78,38 @@ fun ClinicalLiveView(appState: AppState) { // 한 사이클 = piezo 완료 + imu 완료. piezo가 먼저 오면 buffer에 보관, imu 도착 시 합쳐 push. var pendingPiezo by remember { mutableStateOf?>(null) } + // V4.1 wall detection — 채널별 ant/post peak 표시용 (사내 테스트 가시화 요청) + val v41 = remember { com.example.medilightv2android.walldetect.V41Detector() } + var channelWalls by remember { mutableStateOf?>>(List(6) { null }) } + DisposableEffect(Unit) { bleManager.piezoCollector.onMultiChannelComplete = { channels -> lastChannels = channels pendingPiezo = channels + // V4.1로 채널별 ant/post 즉시 계산 (실패해도 측정 자체는 영향 없음) + runCatching { + val adcMatrix = (0 until 6).map { ch -> + channels.find { it.channel == ch }?.buffer?.map { it.toInt() } ?: List(100) { 0 } + } + val probe = com.example.medilightv2android.walldetect.dto.ProbeProfileDto( + name = com.example.medilightv2android.managers.PiezoHW.presetName, + rMm = 50.2, anteriorAnchorMm = 32.0, + fsHz = 537500, cMmPerUs = 1.54, + anglesDeg = com.example.medilightv2android.managers.PiezoHW.degreeAll.toList() + ) + val sweepInput = com.example.medilightv2android.walldetect.dto.SweepInput( + requestId = "clinical_${System.currentTimeMillis()}", + timestampMs = System.currentTimeMillis(), + deviceId = bleManager.connectedDeviceName.value, + sweepSeq = 0, probe = probe, adc = adcMatrix + ) + val result = v41.detect(sweepInput) + channelWalls = List(6) { ch -> + val r = result.perChannel.firstOrNull { it.ch == ch } + val a = r?.antIdx; val p = r?.postIdx + if (a != null && p != null) Pair(a, p) else null + } + } } bleManager.imuCollector.onComplete = { imuSamples -> lastImu = imuSamples @@ -147,6 +175,7 @@ fun ClinicalLiveView(appState: AppState) { // ── 6ch grid (2 rows × 3 cols) ── SixChannelGrid( channels = lastChannels, + walls = channelWalls, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) ) @@ -272,6 +301,7 @@ fun ClinicalLiveView(appState: AppState) { @Composable private fun SixChannelGrid( channels: List, + walls: List?>, modifier: Modifier = Modifier ) { val channelColors = listOf( @@ -289,6 +319,7 @@ private fun SixChannelGrid( channelIdx = ch, buffer = data?.buffer ?: emptyList(), color = channelColors[ch], + walls = walls.getOrNull(ch), ) } } @@ -297,13 +328,16 @@ private fun SixChannelGrid( } } -/** 한 채널 그래프 (raw ADC waveform) */ +/** 한 채널 그래프 (raw ADC waveform + V4.1 ant/post 마커) */ @Composable private fun ChannelChart( channelIdx: Int, buffer: List, color: Color, + walls: Pair?, ) { + val antColor = Color(0xFF4CAF50) // green = anterior wall + val postColor = Color(0xFFFF9800) // orange = posterior wall Card( shape = RoundedCornerShape(6.dp), colors = CardDefaults.cardColors(containerColor = Color.White), @@ -320,6 +354,12 @@ private fun ChannelChart( Spacer(modifier = Modifier.width(4.dp)) Text("CH$channelIdx", fontSize = 10.sp, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.weight(1f)) + if (walls != null) { + Text("a${walls.first}", fontSize = 9.sp, color = antColor, fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.width(3.dp)) + Text("p${walls.second}", fontSize = 9.sp, color = postColor, fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.width(4.dp)) + } if (buffer.isNotEmpty()) { val peak = buffer.maxOrNull()?.toInt() ?: 0 Text("$peak", fontSize = 9.sp, color = MlSecondaryText) @@ -348,6 +388,19 @@ private fun ChannelChart( ) } + // V4.1 ant/post 마커 (waveform 뒤에 그려서 가려지지 않게 alpha 조정) + if (walls != null) { + val (antIdx, postIdx) = walls + if (antIdx in 0 until n) { + val x = antIdx * xScale + drawLine(antColor.copy(alpha = 0.75f), Offset(x, 0f), Offset(x, h), strokeWidth = 1.5f) + } + if (postIdx in 0 until n) { + val x = postIdx * xScale + drawLine(postColor.copy(alpha = 0.75f), Offset(x, 0f), Offset(x, h), strokeWidth = 1.5f) + } + } + // waveform val path = Path() for (i in buffer.indices) {