package com.medithings.vesiscan.managers import com.google.gson.Gson import com.google.gson.JsonParser import com.medithings.vesiscan.walldetect.MethodDRunner import com.medithings.vesiscan.walldetect.algo.methodd.MethodDParams import org.junit.Test import java.io.File import kotlin.math.sqrt /** * cm=3 세션의 11 trace 각각 BV + 중간값을 dump 해서 Python (cm3_python.json) 과 대조. * divergent trace 특정용. */ class Cm3PerTraceDumpTest { private val session = "c:/Projects/medilightv2android/data123/dwjang_HUMAN-kai_VBT26040302_SUPINE_ALIGN_3CM_2026-07-06_164842.json" private val outPath = "C:/Users/장동우/AppData/Local/Temp/claude/c--Projects-medilightv2android/76311e3b-2bb5-4c52-ab8a-8d89cb1052c2/scratchpad/cm3_kotlin.json" private fun loadCycles(path: String, maxN: Int = 20): List> { val root = JsonParser.parseReader(File(path).bufferedReader()).asJsonObject val recs = root.getAsJsonArray("records") val cycles = mutableListOf>() for (r in recs) { if (cycles.size >= maxN) break val obj = r.asJsonObject val chs = obj.getAsJsonArray("channels") ?: continue if (chs.size() != 6) continue val byCh = HashMap() for (ce in chs) { val co = ce.asJsonObject val ch = co.get("ch").asInt val data = co.getAsJsonArray("data") ?: continue if (data.size() != 100) continue byCh[ch] = DoubleArray(100) { data[it].asDouble } } if (byCh.size != 6) continue cycles.add((0..5).map { byCh[it]!! }) } return cycles } private fun meanScan(cycles: List>): List { val nCh = cycles[0].size return List(nCh) { ch -> val len = cycles[0][ch].size DoubleArray(len) { i -> var s = 0.0 for (c in cycles) s += c[ch][i] s / cycles.size } } } @Test fun `dump cm=3 per-trace BV for python comparison`() { PiezoHW.activePreset = PiezoHW.DevicePreset.V1 val cycles = loadCycles(session, 20) val params = MethodDParams.DEFAULT val win = 10 val out = mutableListOf>() for (i in 0..cycles.size - win) { val tr = meanScan(cycles.subList(i, i + win)) val results = MethodDRunner.detectMultichannel(tr, params, applyCross = true) val walls = results.map { r -> if (r == null) null else WallWithSpan(r.antRefined, r.postRefined, r.lowStart, r.lowEnd) } val bv = estimateBv(walls) val rec = LinkedHashMap() rec["trace_idx"] = i rec["walls"] = walls.map { w -> w?.let { listOf(it.ant, it.post, it.lowStart, it.lowEnd) } } if (bv == null) { rec["bv"] = null } else { val bvMap = LinkedHashMap() bvMap["volume_ml"] = bv.volumeMl bvMap["bottom_h_mm"] = bv.bottomHMm bvMap["top_h_mm"] = bv.topHMm bvMap["cap_b_si_mm"] = bv.capBSiMm bvMap["cap_c_ap_mm"] = bv.capCApMm bvMap["cap_mean_residual"] = bv.capMeanResidual bvMap["V_bottom_mm3"] = bv.vBottomMm3 bvMap["V_top_mm3"] = bv.vTopMm3 bvMap["V_core_mm3"] = bv.vCoreMm3 bvMap["valid_channels"] = bv.validChannels bvMap["d_mm"] = bv.dMm.toList() rec["bv"] = bvMap } out.add(rec) } File(outPath).writeText(Gson().toJson(out)) println("saved: $outPath") println() println("trace | BV(mL) | bot_h | top_h | b_si | c_ap | nch") for (r in out) { @Suppress("UNCHECKED_CAST") val bv = r["bv"] as? Map if (bv == null) { println(" ${r["trace_idx"]} ") } else { val vol = (bv["volume_ml"] as? Double) ?: 0.0 val bh = (bv["bottom_h_mm"] as? Double) ?: 0.0 val th = (bv["top_h_mm"] as? Double) ?: 0.0 val bs = (bv["cap_b_si_mm"] as? Double) ?: 0.0 val ca = (bv["cap_c_ap_mm"] as? Double) ?: 0.0 val n = (bv["valid_channels"] as? List<*>)?.size ?: 0 println(" %3d %10.3f %8.3f %8.3f %8.3f %8.3f %3d".format( r["trace_idx"], vol, bh, th, bs, ca, n)) } } } }