fix: Python 대조 검증 — CRITICAL 차이 7건 수정
Method A (PiezoEchoAnalyzerA.kt): - sliding_scores_1d: edge padding 추가 (np.pad mode="edge" 동일) - robust_z: 짝수 길이 시 두 중간값 평균 (np.median 동일) - plateau threshold: 전체 배열 기반 quantile (잘린 배열 → 전체) - urineLen: post-ant → post-ant-1 (Python 동일) - score: ((ant+post)/2-lowMean)*len → sg[ant]+sg[post] (Python 동일) - cross-validation: vals 미변경 + Math.round() (Python 동일) BV Estimation (PiezoBVEstimator.kt): - computeLrRatio maxRatio 기본값 3.0 → 1.0 (Python 동일) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,7 @@ object PiezoHW {
|
||||
fun computeLrRatio(
|
||||
centerWalls: List<Pair<Int, Int>?>,
|
||||
lateralWalls: List<Pair<Int, Int>?>,
|
||||
maxRatio: Double = 3.0
|
||||
maxRatio: Double = 1.0
|
||||
): Double {
|
||||
val hw = PiezoHW
|
||||
val neighbors = hw.lateralNeighbors
|
||||
|
||||
@@ -68,13 +68,16 @@ class PiezoEchoAnalyzerA private constructor() {
|
||||
if (L < 10) return null
|
||||
val y = DoubleArray(L) { sg[it] + alpha * abs(d1[it]) + beta * abs(d2[it]) }
|
||||
|
||||
// 2) Plateau score
|
||||
// 2) Plateau score — threshold from full array (Python 동일)
|
||||
val platScore = slidingScores1d(sg, scoreWin)
|
||||
val fullSorted = platScore.sorted().toDoubleArray()
|
||||
val qIdx = (fullSorted.size * plateauQ).toInt().coerceIn(0, fullSorted.size - 1)
|
||||
val scoreThr = if (qIdx > 0 && qIdx < fullSorted.size - 1) {
|
||||
(fullSorted[qIdx] + fullSorted[qIdx + 1]) / 2.0
|
||||
} else fullSorted[qIdx]
|
||||
val limit = min(platScore.size, postMaxIdx + 1)
|
||||
val scoreSorted = platScore.sliceArray(0 until limit).sorted().toDoubleArray()
|
||||
val scoreThr = scoreSorted[(scoreSorted.size * plateauQ).toInt().coerceIn(0, scoreSorted.size - 1)]
|
||||
|
||||
// 3) Plateau spans + merge
|
||||
// 3) Plateau spans + merge (postMaxIdx 이내만)
|
||||
var plateauSpans = findPlateauSpans(platScore, limit, scoreThr, plateauMinLen)
|
||||
if (plateauSpans.size > 1) {
|
||||
val merged = mutableListOf(plateauSpans[0])
|
||||
@@ -138,18 +141,17 @@ class PiezoEchoAnalyzerA private constructor() {
|
||||
}
|
||||
|
||||
if (bestLeft < 0 || bestRight < 0 || bestRight <= bestLeft) continue
|
||||
val urineLen = bestRight - bestLeft
|
||||
val urineLen = bestRight - bestLeft - 1
|
||||
if (urineLen < minUrineLen) continue
|
||||
|
||||
val lowSlice = sg.sliceArray(spanStart..spanEnd)
|
||||
val lowMean = lowSlice.average()
|
||||
val wallScore = (sg[bestLeft] + sg[bestRight]) / 2.0 - lowMean
|
||||
|
||||
return LowEchoResult(
|
||||
ant = bestLeft, post = bestRight,
|
||||
lowStart = spanStart, lowEnd = spanEnd,
|
||||
lowMean = lowMean, urineLen = urineLen,
|
||||
score = wallScore * urineLen,
|
||||
score = (sg[bestLeft] + sg[bestRight]).toDouble(),
|
||||
innerPeaks = PiezoEchoAnalyzer.shared.findInnerPeaks(sg, bestLeft, bestRight)
|
||||
)
|
||||
}
|
||||
@@ -166,42 +168,49 @@ class PiezoEchoAnalyzerA private constructor() {
|
||||
val slope = DoubleArray(T)
|
||||
val low = DoubleArray(T)
|
||||
|
||||
// Edge padding (mode="edge") — Python np.pad 동일
|
||||
val xp = DoubleArray(T + 2 * half)
|
||||
for (i in 0 until half) xp[i] = x[0]
|
||||
for (i in 0 until T) xp[i + half] = x[i]
|
||||
for (i in 0 until half) xp[T + half + i] = x[T - 1]
|
||||
|
||||
val tt = DoubleArray(w) { (it - half).toDouble() }
|
||||
var ttSqSum = 0.0
|
||||
for (t in tt) ttSqSum += t * t
|
||||
ttSqSum += 1e-12
|
||||
|
||||
for (i in 0 until T) {
|
||||
val wStart = max(0, i - half)
|
||||
val wEnd = min(T - 1, i + half)
|
||||
val wLen = wEnd - wStart + 1
|
||||
|
||||
val ww = DoubleArray(w) { xp[i + it] }
|
||||
var sum = 0.0; var sqSum = 0.0
|
||||
val vals = mutableListOf<Double>()
|
||||
for (j in wStart..wEnd) { sum += x[j]; sqSum += x[j] * x[j]; vals.add(x[j]) }
|
||||
val mean = sum / wLen
|
||||
flat[i] = sqrt(max(0.0, sqSum / wLen - mean * mean))
|
||||
for (v in ww) { sum += v; sqSum += v * v }
|
||||
val mean = sum / w
|
||||
flat[i] = sqrt(max(0.0, sqSum / w - mean * mean))
|
||||
|
||||
vals.sort()
|
||||
low[i] = vals[(wLen * 0.2).toInt().coerceIn(0, wLen - 1)]
|
||||
val sorted = ww.sorted().toDoubleArray()
|
||||
low[i] = sorted[(w * 0.2).toInt().coerceIn(0, w - 1)]
|
||||
|
||||
var slopeNum = 0.0
|
||||
for (j in wStart..wEnd) {
|
||||
slopeNum += (j - i).toDouble() * (x[j] - mean)
|
||||
for (j in 0 until w) {
|
||||
slopeNum += tt[j] * (ww[j] - mean)
|
||||
}
|
||||
slope[i] = abs(slopeNum / ttSqSum)
|
||||
}
|
||||
|
||||
return DoubleArray(T) { robustZ(low)[it] + robustZ(flat)[it] + robustZ(slope)[it] }
|
||||
val rzLow = robustZ(low)
|
||||
val rzFlat = robustZ(flat)
|
||||
val rzSlope = robustZ(slope)
|
||||
return DoubleArray(T) { rzLow[it] + rzFlat[it] + rzSlope[it] }
|
||||
}
|
||||
|
||||
private fun robustZ(a: DoubleArray): DoubleArray {
|
||||
val sorted = a.sorted().toDoubleArray()
|
||||
val med = sorted[sorted.size / 2]
|
||||
val deviations = DoubleArray(a.size) { abs(a[it] - med) }
|
||||
val n = sorted.size
|
||||
val med = if (n % 2 == 0) (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0 else sorted[n / 2]
|
||||
val deviations = DoubleArray(n) { abs(a[it] - med) }
|
||||
val devSorted = deviations.sorted().toDoubleArray()
|
||||
val mad = devSorted[devSorted.size / 2] + 1e-12
|
||||
return DoubleArray(a.size) { (a[it] - med) / (1.4826 * mad) }
|
||||
val madMed = if (n % 2 == 0) (devSorted[n / 2 - 1] + devSorted[n / 2]) / 2.0 else devSorted[n / 2]
|
||||
val mad = madMed + 1e-12
|
||||
return DoubleArray(n) { (a[it] - med) / (1.4826 * mad) }
|
||||
}
|
||||
|
||||
// ── Plateau span detection ──
|
||||
@@ -256,7 +265,7 @@ class PiezoEchoAnalyzerA private constructor() {
|
||||
if (validCenter.size < 3) return corrected
|
||||
|
||||
for (field in listOf("ant", "post")) {
|
||||
val vals = validCenter.map { if (field == "ant") corrected[it]!!.first else corrected[it]!!.second }.toMutableList()
|
||||
val vals = validCenter.map { if (field == "ant") corrected[it]!!.first else corrected[it]!!.second }
|
||||
for (j in vals.indices) {
|
||||
val neighbors = mutableListOf<Int>()
|
||||
if (j > 0) neighbors.add(vals[j - 1])
|
||||
@@ -264,11 +273,10 @@ class PiezoEchoAnalyzerA private constructor() {
|
||||
if (neighbors.isEmpty()) continue
|
||||
val neighborMean = neighbors.average()
|
||||
if (abs(vals[j] - neighborMean) > crossValMaxGradient) {
|
||||
val newVal = neighborMean.toInt()
|
||||
val newVal = Math.round(neighborMean).toInt()
|
||||
val chIdx = validCenter[j]
|
||||
val w = corrected[chIdx]!!
|
||||
corrected[chIdx] = if (field == "ant") Pair(newVal, w.second) else Pair(w.first, newVal)
|
||||
vals[j] = newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user