fix(alignment): V3 buildRecord BV 리스트 필터 제거 — Python 1:1

Python `position_records:151` 는 `if bv is not None: bvs.append(float(bv.volume_ml))`
으로 nan/0 필터 없이 그대로 추가. nan 발생 시 std/mean 모두 nan → bv_cv=nan
이 되어 `selectCm` tie 정렬에서 +inf 로 취급 (뒷순위).

기존 Kotlin 은 `if (volumeMl.isFinite() && volumeMl > 0)` 필터가 있어 nan/0
케이스에서 Python 과 다른 bv_cv 산출. cm=0 (BV 발산) 처럼 일부 trace 만
이상값일 때 값 오차 발생. Rule A 최종 선택 결과는 동일하지만 line-by-line
대조에서 유일하게 차이나는 지점이라 Python 과 완전 일치하도록 제거.

BV null (검출 실패) 은 여전히 skip (Python 도 동일).
mean/std 결과가 nan/inf 이면 bvCv=null 로 정규화 (Python nan 표현 = Kotlin null).
This commit is contained in:
2026-07-15 14:47:27 +09:00
parent 9387552e18
commit fcdce12503
@@ -197,16 +197,25 @@ class CenterAligner(
r?.let { Pair(it.ant, it.post) }
}
val bv = estimateBladderVolume6ch(allWalls)
bv?.volumeMl?.let { if (it.isFinite() && it > 0) bvs.add(it) }
// Python `position_records`: `if bv is not None: bvs.append(float(bv.volume_ml))`
// nan/0 필터 없이 그대로 추가. nan 발생 시 std/mean 모두 nan → bv_cv=null →
// `selectCm` tie 정렬에서 +inf 로 취급되어 뒷순위로 밀림 (Python 과 동일 동작).
bv?.let { bvs.add(it.volumeMl) }
}
// Python: `float(np.std(bvs) / np.mean(bvs)) if len(bvs)>=2 and np.mean(bvs) else nan`
// nan 이 하나라도 있으면 std/mean 모두 nan → 결과 nan (Kotlin: null).
val bvCv: Double? = if (bvs.size >= 2) {
val m = bvs.average()
if (m != 0.0) {
if (m != 0.0 && m.isFinite()) {
val sd = sqrt(bvs.map { (it - m) * (it - m) }.average())
sd / m
val cv = sd / m
if (cv.isFinite()) cv else null
} else null
} else null
val meanBv = if (bvs.isNotEmpty()) bvs.average() else null
val meanBv = if (bvs.isNotEmpty()) {
val m = bvs.average()
if (m.isFinite()) m else null
} else null
return AlignPositionRecord(
alignCm = cm, nch = nch, ch3Detected = ch3Detected,