fix(bv): PiezoMonitoringView Method D BV → estimateBv() wrapper — Python 1:1
D2: 기존 PiezoMonitoringView 는 Method D BV 를 estimateBladderVolume6ch()
직접 호출로 계산 → adaptive_large_bladder_relax + b_si_floor + subsample
refined walls 모두 미적용. Alignment V3 / Clinical Live 는 이미
estimateBv(walls) wrapper 통과 (Python 1:1) 로 정상 동작 중이었음.
- MethodDResult 를 methodDDets 변수에 캐시 (METHOD_D detection 경로 +
useMethodDBv 분기 둘 다).
- BV 계산 분기에 useMethodDBv 케이스 추가: WallWithSpan(antRefined,
postRefined, lowStart, lowEnd) 만들어서 estimateBv(walls) 호출.
antOffset 은 refined ant 에 samples 단위 subtract (기존 Int 경로와
동일 시맨틱).
- 신규 test MethodDBvWrapperParityTest:
· wrapper (Monitoring D2) == wrapper (Alignment/Clinical) : bit-exact
· wrapper vs legacy int-only : data123 33 케이스에서 최대 20.4% 차이
(96 mL @ 471 mL). Legacy 가 큰 방광에서 systematically 저추정.
3 소비자 경로 (Monitoring · Alignment V3 · Clinical Live) 모두 이제
Python `runners.estimate_bv` 와 완전 동일.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+41
-9
@@ -273,6 +273,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
var rawLrRatio = 1.0
|
||||
var totalWallCount = 0
|
||||
val allWalls: MutableList<Pair<Int, Int>?> = MutableList(6) { null }
|
||||
// Method D detection 결과 (refined ant/post + span) — Method D BV wrapper
|
||||
// 경로에서 재사용해 adaptive_large_bladder_relax 등 Python 1:1 적용.
|
||||
var methodDDets: List<com.medithings.vesiscan.walldetect.MethodDResult?>? = null
|
||||
|
||||
val validChannels = channels.filter { it.isValid }
|
||||
val allChannelsReceived = channels.filter { it.buffer.size >= 20 }.size >= 6
|
||||
@@ -352,6 +355,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
val dets = com.medithings.vesiscan.walldetect.MethodDRunner
|
||||
.detectMultichannel(signals)
|
||||
methodDDets = dets // BV wrapper 경로에서 refined+span 재사용
|
||||
for (i in 0..5) {
|
||||
val r = dets.getOrNull(i)
|
||||
if (r != null) allWalls[i] = Pair(r.ant, r.post)
|
||||
@@ -416,15 +420,17 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
cd?.buffer?.map { it.toDouble() }?.toDoubleArray() ?: DoubleArray(0)
|
||||
}
|
||||
if (signals.all { it.isEmpty() }) allWalls
|
||||
// 2026-07-02: NEW BV lib (piezophantomtest 4830e7c) lumen_inset_frac=0.15
|
||||
// MethodDResult 의 (ls, le) 로 벽을 lumen 안쪽으로 15% 이동.
|
||||
else com.medithings.vesiscan.walldetect.MethodDRunner
|
||||
.detectMultichannel(signals)
|
||||
.map { r -> r?.let {
|
||||
com.medithings.vesiscan.managers.applyLumenInsetOne(
|
||||
it.ant, it.post, it.lowStart, it.lowEnd
|
||||
)
|
||||
} }
|
||||
else {
|
||||
// 2026-07-27 (D2 fix): Method D 결과를 methodDDets 에 저장해두고
|
||||
// BV 계산은 아래 useMethodDBv 분기에서 estimateBv(walls) wrapper 통과.
|
||||
// Wrapper 가 lumen_inset + adaptive_large_bladder_relax + b_si_floor 모두
|
||||
// 자동 적용 → Python 1:1. 여기서는 sourceAllWalls 에 int (ant,post) 만
|
||||
// 반환 (기존 wall-count 게이트 · UI 표시용).
|
||||
val dets = com.medithings.vesiscan.walldetect.MethodDRunner
|
||||
.detectMultichannel(signals)
|
||||
methodDDets = dets
|
||||
dets.map { r -> r?.let { Pair(it.ant, it.post) } }
|
||||
}
|
||||
} else allWalls
|
||||
val effectiveAllWalls: List<Pair<Int, Int>?> =
|
||||
if (antOffsetSamples == 0) sourceAllWalls
|
||||
@@ -447,6 +453,32 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
(0..5).map { allWalls[it]?.let { w -> w.second - w.first } ?: 0 })
|
||||
logService.logMeasurement(methodCBvMl, channelLogs, rawADC)
|
||||
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, methodCBvMl)
|
||||
} else if (useMethodDBv && methodDDets != null && effectiveCenterWalls >= 4) {
|
||||
// 2026-07-27 (D2 fix): Method D BV → estimateBv(walls) wrapper 로 라우팅.
|
||||
// Refined ant/post + span 전달 → wrapper 가 lumen_inset(0.15) +
|
||||
// Halir-Flusser + adaptive_large_bladder_relax + b_si_floor 자동 적용.
|
||||
// Python `runners.estimate_bv` 와 1:1 (Alignment V3 / Clinical Live 와 동일 경로).
|
||||
// antOffset 은 refined ant 에 subtract (기존 Int 경로와 동일 시맨틱).
|
||||
val walls = methodDDets!!.map { r -> r?.let {
|
||||
val antAdj = maxOf(0.0, it.antRefined - antOffsetSamples.toDouble())
|
||||
com.medithings.vesiscan.managers.WallWithSpan(
|
||||
ant = antAdj, post = it.postRefined,
|
||||
lowStart = it.lowStart, lowEnd = it.lowEnd,
|
||||
)
|
||||
} }
|
||||
val bvResult = com.medithings.vesiscan.managers.estimateBv(walls)
|
||||
if (bvResult != null) {
|
||||
rawVolumeMl = bvResult.volumeMl
|
||||
rawLrRatio = bvResult.lrRatio
|
||||
val mode = if (bvResult.validChannels.size <= 1) "sphere" else "frustum"
|
||||
Log.d("PiezoMonitor", "BV[$methodLabel-wrap]: ${"%.1f".format(bvResult.volumeMl)}ml " +
|
||||
"($mode, lr=${"%.2f".format(bvResult.lrRatio)}, valid=${bvResult.validChannels.size}/6)")
|
||||
bleManager.debugLogger.measurementResult(bvResult.volumeMl, bvResult.lrRatio, totalWallCount,
|
||||
(0..5).map { allWalls[it]?.let { w -> w.second - w.first } ?: 0 },
|
||||
otsuThreshold = analyzer.lastOtsuThreshold)
|
||||
logService.logMeasurement(bvResult.volumeMl, channelLogs, rawADC)
|
||||
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, bvResult.volumeMl, bvResult.lrRatio)
|
||||
}
|
||||
} else if (effectiveCenterWalls >= 4) {
|
||||
// 검증 로그 — ant offset 비활성(0)이 아닐 때 chord 변화 출력
|
||||
if (antOffsetSamples != 0) {
|
||||
|
||||
Reference in New Issue
Block a user