https://github.com/frchase/iOS-Neuro-mapping.git
Kalman Filter:
class KalmanFilter: ObservableObject {
let initialMeasurementError = 1.0
let processNoise = 0.01
var state: Double
var errorCovariance: Double
init(initialState: Double, initialErrorCovariance: Double) {
state = initialState
errorCovariance = initialErrorCovariance
}
func update(measurement: Double) {
let predictedState = state
let predictedErrorCovariance = errorCovariance + processNoise
// Correction step
let kalmanGain = predictedErrorCovariance / (predictedErrorCovariance + initialMeasurementError)
state = predictedState + kalmanGain * (measurement - predictedState)
errorCovariance = (1 - kalmanGain) * predictedErrorCovariance
}
func processAccelerometerDataWithKalmanFilter(dataArray: inout [(x: Double, y: Double, z: Double)], newData: (x: Double, y: Double, z: Double)) {
// Apply Kalman filter to the latest accelerometer data
if dataArray.isEmpty{
dataArray.append(newData)
}else{
let dataarraylast = dataArray[dataArray.count-1]
let kalmanFilterX = KalmanFilter(initialState: dataarraylast.0, initialErrorCovariance: initialMeasurementError)
kalmanFilterX.update(measurement: newData.x)
let kalmanFilterY = KalmanFilter(initialState: dataarraylast.1, initialErrorCovariance: initialMeasurementError)
kalmanFilterY.update(measurement: newData.y)
let kalmanFilterZ = KalmanFilter(initialState: dataarraylast.2, initialErrorCovariance: initialMeasurementError)
kalmanFilterZ.update(measurement: newData.z)
print("Filtered Accelerometer Data: X: \(kalmanFilterX.state), Y: \(kalmanFilterY.state), Z: \(kalmanFilterZ.state)")
dataArray.append((kalmanFilterX.state,kalmanFilterY.state,kalmanFilterZ.state))
}
}
var opencvDataArray: [(x: Double, y: Double, z: Double)] = []
func testKalmanFilter() {
let start: Double = 0.0
let end: Double = 10.0
let steps: Int = 10
let stepSize: Double = (end - start) / Double(steps - 1)
var linearDataArray: [(x: Double, y: Double, z: Double)] = []
for i in 0..<steps {
// Simulate a linear trajectory with some noise
let noise: Double = 0.1 // change if needed
let x_val = start + stepSize * Double(i) + Double.random(in: -noise...noise)
let y_val = 0.0 + Double.random(in: -noise...noise)
let z_val = 0.0 + Double.random(in: -noise...noise)
let newData = (x: x_val, y: y_val, z: z_val)
processAccelerometerDataWithKalmanFilter(dataArray: &linearDataArray, newData: newData)
}
print("Processed Linear Trajectory Data Array: \(linearDataArray)")
}
}
- This code includes the implementation of the Kalman Filter in Swift and debugging code as well. To test the debugging code, you may use a <button> or any other trigger method to see visible data changes. The client requested specifically just a linear trajectory test as that will suffice the usage and validate the effectiveness of the filter.
Apple Watch Data Streaming:
func watchData() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = WatchConnectivityDelegate.shared
session.activate()
print("supported")
NotificationCenter.default.addObserver(forName: .receivedWatchData, object: nil, queue: .main) { notification in
if let data = notification.userInfo?["data"] as? [String: Double] {
// Update the received data on the ContentView
self.receivedData = data
}
}
}
}
- This code chunk provides the setup for sending the processed Apple Watch streaming data into the Kalman Filter for testing the decrease in errors when accelerometer data is perceived.
import Foundation
import WatchConnectivity
class WatchConnectivityDelegate: NSObject, WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print(error?.localizedDescription ?? "")
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
static let shared = WatchConnectivityDelegate()
// Handle the received message from the Watch
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
// Process the received data on the iPhone side
print("Received message on iPhone: \(message)")
if let data = message as? [String: Double] {
// Post a notification to update the ContentView
NotificationCenter.default.post(name: .receivedWatchData, object: nil, userInfo: ["data": data])
}
}
}
extension Notification.Name {
static let receivedWatchData = Notification.Name("ReceivedWatchDataNotification")
}
- This is where the streaming data is processed from raw accelerometer data to data segments that can be constantly streamed to the Kalman Filter which allows us to provide a second dimension of error handling.