feat: Method C (V4.1) 대규모 업데이트 — 2-pass pipeline + ChordConsensus
새 알고리즘 모듈 (7개): - ImpulseReject: 2-pass Hampel filter (lumen 내 impulse 제거) - MedianFilter: running-median pre-filter (window=7, speckle 제거) - MorphClose: morphological closing on lumen mask (disabled) - StaLta: STA/LTA impulse detector (Allen 1978, wall edge vs reverberation) - ChordConsensus: Tukey MAD outlier + Fischler-Bolles pair test - DetectionSanity: detection sanity checks - SweepStabilizer: sweep temporal stability 변경된 모듈 (9개): - V41Detector: 2-pass pipeline (ImpulseReject.detectWithLumenClean), BModeScore V41_WEIGHTS (wamp+stalta), ChordConsensus filtering - BvEstimation: ChordConsensus filter → trusted channel only - DetectLumenFirst: running-median pre-filter, V41_PARAMS, mergeGapMax=5, Kremkau half-amplitude ant fallback - AnatomicalGate: antDepthMin 22→10mm, strict threshold 제거 - BModeScore: V41_WEIGHTS (far=0.15, dark=0.10, wamp=0.45, stalta=0.20) - WallSelect: MAX_PEAK_CANDIDATES_POST=64 - SpanUtils, Otsu, BvDispatchResult 업데이트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Medithings Co., Ltd.
|
||||
* Author: Charles KWON OhJun <charleskwon@medithings.co.kr>
|
||||
* Project: CharlesKWONsLaw — wall-detect live compare
|
||||
*
|
||||
* Stability check on V41 detection outputs. Complements SweepStabilizer:
|
||||
* the stabilizer guards the INPUT, this guards the OUTPUT.
|
||||
*
|
||||
* Per-channel rules (relative to the previous live frame):
|
||||
* • antIdx jump > maxIndexJump → unstable
|
||||
* • postIdx jump > maxIndexJump → unstable
|
||||
* • |chord change| / chord > maxChordRatio → unstable
|
||||
*
|
||||
* "unstable" is a flag for the diagnostic log, NOT a hard reject — V41's
|
||||
* own anatomical gate already drops genuinely-bad detections. This layer
|
||||
* just surfaces "this channel's reading just jumped, double-check".
|
||||
*/
|
||||
package com.example.medilightv2android.walldetect
|
||||
|
||||
import com.example.medilightv2android.walldetect.dto.ChannelResult
|
||||
import kotlin.math.abs
|
||||
|
||||
class DetectionSanity(
|
||||
private val maxIndexJump: Int = 8,
|
||||
private val maxChordRatio: Double = 0.20,
|
||||
private val channels: Int = 6,
|
||||
) {
|
||||
private val lastAnt = IntArray(channels) { -1 }
|
||||
private val lastPost = IntArray(channels) { -1 }
|
||||
|
||||
data class ChannelReport(
|
||||
val channel: Int,
|
||||
val antJump: Int?,
|
||||
val postJump: Int?,
|
||||
val chordChangeRatio: Double?,
|
||||
val unstable: Boolean,
|
||||
)
|
||||
|
||||
fun reset() {
|
||||
for (i in lastAnt.indices) { lastAnt[i] = -1; lastPost[i] = -1 }
|
||||
}
|
||||
|
||||
fun check(perChannel: List<ChannelResult>): List<ChannelReport> {
|
||||
val reports = mutableListOf<ChannelReport>()
|
||||
for ((ch, cr) in perChannel.withIndex()) {
|
||||
val ant = cr.antIdx
|
||||
val post = cr.postIdx
|
||||
|
||||
val prevA = lastAnt.getOrElse(ch) { -1 }
|
||||
val prevP = lastPost.getOrElse(ch) { -1 }
|
||||
|
||||
var antJump: Int? = null
|
||||
var postJump: Int? = null
|
||||
var chordRatio: Double? = null
|
||||
var unstable = false
|
||||
|
||||
if (ant != null && prevA >= 0) {
|
||||
val jump = abs(ant - prevA)
|
||||
antJump = jump
|
||||
if (jump > maxIndexJump) unstable = true
|
||||
}
|
||||
if (post != null && prevP >= 0) {
|
||||
val jump = abs(post - prevP)
|
||||
postJump = jump
|
||||
if (jump > maxIndexJump) unstable = true
|
||||
}
|
||||
if (ant != null && post != null && prevA >= 0 && prevP >= 0) {
|
||||
val curChord = (post - ant).toDouble()
|
||||
val prevChord = (prevP - prevA).toDouble()
|
||||
if (prevChord > 0.0) {
|
||||
val r = abs(curChord - prevChord) / prevChord
|
||||
chordRatio = r
|
||||
if (r > maxChordRatio) unstable = true
|
||||
}
|
||||
}
|
||||
|
||||
reports += ChannelReport(
|
||||
channel = ch,
|
||||
antJump = antJump,
|
||||
postJump = postJump,
|
||||
chordChangeRatio = chordRatio,
|
||||
unstable = unstable,
|
||||
)
|
||||
|
||||
// Update state ONLY when we have a valid current detection,
|
||||
// otherwise comparison against -1 sentinel resumes after gap.
|
||||
if (ant != null) lastAnt[ch] = ant
|
||||
if (post != null) lastPost[ch] = post
|
||||
}
|
||||
return reports
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user