Final Deliverable: Project Code

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.