diff --git a/cue-ios/CueIOS/Models/LsvAnalysis.swift b/cue-ios/CueIOS/Models/LsvAnalysis.swift index 9099f2a..59f4e30 100644 --- a/cue-ios/CueIOS/Models/LsvAnalysis.swift +++ b/cue-ios/CueIOS/Models/LsvAnalysis.swift @@ -41,22 +41,22 @@ func findExtrema(_ v: [Float], _ iSmooth: [Float], minProminence: Float) -> [(In } } - return candidates.filter { (idx, isMax) in + var result: [(Int, Bool)] = [] + for (idx, isMax) in candidates { let val = iSmooth[idx] let leftSlice = iSmooth[..= minProminence + let lb = leftSlice.min() ?? val + let rb = rightSlice.min() ?? val + if val - max(lb, rb) >= minProminence { result.append((idx, isMax)) } } else { - leftBound = leftSlice.max() ?? val - rightBound = rightSlice.max() ?? val - return min(leftBound, rightBound) - val >= minProminence + let lb = leftSlice.max() ?? val + let rb = rightSlice.max() ?? val + if min(lb, rb) - val >= minProminence { result.append((idx, isMax)) } } } + return result } /// Detect Q/HQ redox peak in the -100 to +600 mV window. @@ -75,13 +75,16 @@ func detectQhqPeak(_ points: [LsvPoint]) -> Float? { let extrema = findExtrema(vVals, smoothed, minProminence: prominence) - let candidates = extrema - .filter { $0.1 && vVals[$0.0] >= -100 && vVals[$0.0] <= 600 } - .max(by: { smoothed[$0.0] < smoothed[$1.0] }) - - if let (idx, _) = candidates { - return vVals[idx] + var bestIdx: Int? = nil + var bestVal: Float = -.infinity + for (idx, isMax) in extrema { + guard isMax, vVals[idx] >= -100, vVals[idx] <= 600 else { continue } + if smoothed[idx] > bestVal { + bestVal = smoothed[idx] + bestIdx = idx + } } + if let idx = bestIdx { return vVals[idx] } return nil } @@ -108,38 +111,33 @@ func deriveClPotentials(_ points: [LsvPoint]) -> ClPotentials { let extrema = findExtrema(vVals, smoothed, minProminence: prominence) // v_free: most prominent cathodic peak (isMax==false) in +300 to -300 mV - let freePeak = extrema - .filter { !$0.1 && vVals[$0.0] >= -300 && vVals[$0.0] <= 300 } - .min(by: { smoothed[$0.0] < smoothed[$1.0] }) - - let vFree: Float - let vFreeDetected: Bool - let freeIdx: Int? - if let (idx, _) = freePeak { - vFree = vVals[idx] - vFreeDetected = true - freeIdx = idx - } else { - vFree = 100 - vFreeDetected = false - freeIdx = nil + var vFree: Float = 100 + var vFreeDetected = false + var freeIdx: Int? = nil + var freeBest: Float = .infinity + for (idx, isMax) in extrema { + guard !isMax, vVals[idx] >= -300, vVals[idx] <= 300 else { continue } + if smoothed[idx] < freeBest { + freeBest = smoothed[idx] + vFree = vVals[idx] + vFreeDetected = true + freeIdx = idx + } } // v_total: secondary cathodic peak between (vFree-100) and -500, excluding free peak let totalHi = vFree - 100 let totalLo: Float = -500 - let totalPeak = extrema - .filter { !$0.1 && vVals[$0.0] >= totalLo && vVals[$0.0] <= totalHi && $0.0 != freeIdx } - .min(by: { smoothed[$0.0] < smoothed[$1.0] }) - - var vTotal: Float - let vTotalDetected: Bool - if let (idx, _) = totalPeak { - vTotal = vVals[idx] - vTotalDetected = true - } else { - vTotal = vFree - 300 - vTotalDetected = false + var vTotal: Float = vFree - 300 + var vTotalDetected = false + var totalBest: Float = .infinity + for (idx, isMax) in extrema { + guard !isMax, vVals[idx] >= totalLo, vVals[idx] <= totalHi, idx != freeIdx else { continue } + if smoothed[idx] < totalBest { + totalBest = smoothed[idx] + vTotal = vVals[idx] + vTotalDetected = true + } } vTotal = max(vTotal, -400) @@ -179,18 +177,30 @@ func detectLsvPeaks(_ points: [LsvPoint]) -> [LsvPeak] { } // largest peak in positive voltage region -> freeCl - let freeCl = extrema - .filter { $0.1 && vVals[$0.0] >= 0 } - .max(by: { smoothed[$0.0] < smoothed[$1.0] }) - if let (idx, _) = freeCl { + var freeClIdx: Int? = nil + var freeClVal: Float = -.infinity + for (idx, isMax) in extrema { + guard isMax, vVals[idx] >= 0 else { continue } + if smoothed[idx] > freeClVal { + freeClVal = smoothed[idx] + freeClIdx = idx + } + } + if let idx = freeClIdx { peaks.append(LsvPeak(vMv: vVals[idx], iUa: smoothed[idx], kind: .freeCl)) } // largest peak in negative voltage region -> totalCl - let totalCl = extrema - .filter { $0.1 && vVals[$0.0] < 0 } - .max(by: { smoothed[$0.0] < smoothed[$1.0] }) - if let (idx, _) = totalCl { + var totalClIdx: Int? = nil + var totalClVal: Float = -.infinity + for (idx, isMax) in extrema { + guard isMax, vVals[idx] < 0 else { continue } + if smoothed[idx] > totalClVal { + totalClVal = smoothed[idx] + totalClIdx = idx + } + } + if let idx = totalClIdx { peaks.append(LsvPeak(vMv: vVals[idx], iUa: smoothed[idx], kind: .totalCl)) }