feat: adaptive threshold 구현 (percentile + 고정값 blended)
detectLowEcho: 고정 1150 → adaptive threshold (기본 활성화) 1. ringSkip(3) 이후 신호에서 q25/q50/q75 계산 2. percentile 기반: q25 + iqr*0.3 ~ q50 - iqr*0.3 3. 고정값(1150) 범위 제한: ×0.7 ~ ×1.5 (805~1725) 4. 가중 평균: 고정 40% + adaptive 60% → 신호 레벨에 자동 적응하되 급격한 변동 방지 WaveformChart: threshold 점선도 adaptive 값으로 표시 useAdaptiveThreshold 플래그로 on/off 전환 가능 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -321,11 +321,48 @@ class PiezoEchoAnalyzer private constructor() {
|
||||
return max(q25, median - 0.5 * iqr)
|
||||
}
|
||||
|
||||
/** 단일 1D 채널 → urine region 탐지 (고정 임계값만 사용) */
|
||||
var useAdaptiveThreshold: Boolean = true
|
||||
|
||||
/** 단일 1D 채널 → urine region 탐지 */
|
||||
fun detectLowEcho(raw: DoubleArray, denoised: DoubleArray): LowEchoResult? {
|
||||
val sg = denoised
|
||||
if (sg.size < 10) return null
|
||||
return detectLowEchoCore(sg = sg, threshold = lowEchoAmpDefault)
|
||||
|
||||
if (!useAdaptiveThreshold) {
|
||||
return detectLowEchoCore(sg = sg, threshold = lowEchoAmpDefault)
|
||||
}
|
||||
|
||||
// Adaptive threshold: percentile + depth 보상 조합
|
||||
val thr = computeAdaptiveThreshold(sg)
|
||||
return detectLowEchoCore(sg = sg, threshold = thr)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adaptive threshold: 신호 통계 + depth attenuation 보상
|
||||
*
|
||||
* 1) Percentile 기반 베이스라인: 전체 신호의 q25~median 사이에서 결정
|
||||
* 2) 초반 peak(피부 반사) 제외: ringSkip(3) 이후 사용
|
||||
* 3) 고정 threshold와의 가중 평균으로 급격한 변동 방지
|
||||
*/
|
||||
fun computeAdaptiveThreshold(sg: DoubleArray): Double {
|
||||
val skip = GreenZoneConstants.ringSkip
|
||||
val usable = if (sg.size > skip + 10) sg.sliceArray(skip until sg.size) else sg
|
||||
|
||||
val sorted = usable.sorted()
|
||||
val n = sorted.size
|
||||
val q25 = sorted[n / 4]
|
||||
val q50 = sorted[n / 2]
|
||||
val q75 = sorted[3 * n / 4]
|
||||
val iqr = q75 - q25
|
||||
|
||||
// Percentile 기반: 소변 영역은 보통 하위 25~50%
|
||||
val percThr = max(q25 + iqr * 0.3, q50 - iqr * 0.3)
|
||||
|
||||
// 고정값과 adaptive의 가중 평균 (급격한 변동 방지)
|
||||
val adaptive = percThr.coerceIn(lowEchoAmpDefault * 0.7, lowEchoAmpDefault * 1.5)
|
||||
val blended = lowEchoAmpDefault * 0.4 + adaptive * 0.6
|
||||
|
||||
return blended
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-1
@@ -985,7 +985,9 @@ private fun WaveformChart(
|
||||
val raw = buffer.map { it.toDouble() }.toDoubleArray()
|
||||
val denoised = analyzer.denoise(raw)
|
||||
val maxVal = 2500f
|
||||
val threshold = analyzer.lowEchoAmpDefault.toFloat()
|
||||
val threshold = if (analyzer.useAdaptiveThreshold)
|
||||
analyzer.computeAdaptiveThreshold(denoised).toFloat()
|
||||
else analyzer.lowEchoAmpDefault.toFloat()
|
||||
val distPerSample = 1.771f // mm per sample
|
||||
|
||||
val yLabels = listOf(0, 500, 1000, 1500, 2000, 2500)
|
||||
|
||||
Reference in New Issue
Block a user