Skip to content

egemengunel/Coughylyzer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Coughylyzer

On-device cough classification for iOS, powered by Core ML.

Coughylyzer is a native iOS application that records your cough, processes the audio signal using digital signal processing techniques, and runs a hybrid machine learning model entirely on-device to produce a Lung Score — a 0–100 metric reflecting respiratory health patterns. No data ever leaves the device.

This project was developed as a bachelor's thesis exploring the feasibility of acoustic cough analysis on mobile devices using on-device machine learning.

Disclaimer: Coughalyzer is a research prototype and is not a medical device. It cannot diagnose any condition and is not a substitute for professional medical advice.


Table of Contents


Features

  • Fully on-device inference — zero network calls, zero data collection, complete privacy
  • Hybrid ML model — combines a CNN operating on mel spectrograms with engineered peak features for classification
  • Real-time FFT waveform — live audio visualization during recording using the Accelerate framework
  • Automated recording sequence — guided 3-recording flow with animated transitions between each capture
  • Lung Score (0–100) — composite health metric derived from model probabilities and confidence
  • Score history & trends — persistent result tracking with an interactive Swift Charts trend line
  • Custom design system — animated mesh gradients, glass morphism UI (iOS 26), arc gauge, custom typography
  • Zero third-party dependencies — built entirely with Apple frameworks

How It Works

Recording Pipeline

The app captures 3 consecutive 3-second cough recordings at 16 kHz, mono, 16-bit linear PCM. A guided flow walks the user through medical disclaimers and positioning instructions before recording begins. Each recording is processed independently through the ML pipeline, and the results are averaged for a final prediction.

ML Pipeline

Each audio recording goes through a multi-stage signal processing and inference pipeline:

Audio (16kHz WAV)
  │
  ├──► Mel Spectrogram Branch
  │     ├── Extract highest-energy 1-second segment
  │     ├── FFT with Hann windowing (n_fft=2048, hop=1024)
  │     ├── Apply 128-bin mel filterbank (50 Hz – 8000 Hz)
  │     ├── Convert to log scale (dB)
  │     ├── Bilinear interpolation resize to 224×224
  │     └── Output: MLMultiArray [1, 3, 224, 224]
  │
  ├──► Peak Feature Branch
  │     ├── Compute RMS energy envelope (512-sample window, 50% overlap)
  │     ├── Threshold-based peak detection
  │     ├── Extract: num_peaks, mean_height, mean_prominence, mean_width
  │     ├── Z-score normalize using training statistics
  │     └── Output: MLMultiArray [1, 4]
  │
  └──► Core ML Model (CoughylyzerModel)
        ├── Dual-input: spectrogram + peak features
        ├── Output: classLabel ("normal" / "abnormal")
        └── Output: classLabel_probs (probability dictionary)

All DSP operations use Apple's Accelerate framework (vDSP) for hardware-optimized performance.

Lung Score

The Lung Score is a composite metric calculated from the averaged predictions across all 3 recordings:

lungScore = normalProbability × 60
           + (normalProbability − abnormalProbability) × 30
           + confidence × 10

The score is clamped to [0, 100] and maps to four categories:

Score Range Category
80 – 100 Excellent
60 – 79 Good
40 – 59 Fair
0 – 39 Needs Attention

Architecture

The app follows MVVM with a Router-based navigation pattern:

┌──────────────────────────────────────────────────┐
│              CoughylyzerApp (@main)              │
│           RouterViewModifier (NavigationStack)    │
├──────────────────────────────────────────────────┤
│                                                  │
│  Onboarding ──► AppTabView                       │
│                   ├── Results Tab                 │
│                   │     ├── LungScoreCard         │
│                   │     ├── LungScoreChart        │
│                   │     └── ResultView            │
│                   └── Record Tab                  │
│                         ├── RecordWarningView     │
│                         ├── RecordGuideView       │
│                         └── AnimatedRecordButton  │
│                                                  │
├──────────────────────────────────────────────────┤
│  RecordViewModel                                 │
│    └── Recording orchestration & analysis        │
├──────────────────────────────────────────────────┤
│  Helpers                                         │
│    ├── AudioProcessorHelper (mel spectrogram)    │
│    ├── PeakFeatureHelper (peak extraction)       │
│    ├── CoughClassifierHelper (Core ML inference) │
│    └── CoughDataHelper (UserDefaults persistence)│
├──────────────────────────────────────────────────┤
│  Core ML: CoughylyzerModel.mlpackage             │
└──────────────────────────────────────────────────┘

Project Structure

Coughylyzer/
├── CoughylyzerApp.swift              # App entry point
├── ContentView.swift                 # Onboarding gate
│
├── Navigation/
│   ├── Router.swift                  # Observable router with NavigationPath
│   └── RouterViewModifier.swift      # NavigationStack injection
│
├── Views/
│   ├── Onboarding/
│   │   └── OnboardingView.swift      # Welcome screen
│   ├── AppTabView.swift              # Tab bar (Results + Record)
│   ├── RecordView.swift              # Recording screen
│   ├── RecordWarningView.swift       # Medical disclaimers
│   ├── RecordGuideView.swift         # Recording instructions
│   ├── ResultView.swift              # Single result detail
│   └── ResultsView.swift             # Score history & chart
│
├── ViewModels/
│   └── RecordViewModel.swift         # Recording + analysis orchestration
│
├── Models/
│   ├── CoughRecording.swift          # Recording metadata
│   ├── CoughPrediction.swift         # Classification + lung score
│   ├── CoughAnalysisResult.swift     # Composite result model
│   └── ML/
│       └── CoughylyzerModel.mlpackage  # Core ML model (~46 MB)
│
├── Helpers/
│   ├── AudioProcessorHelper.swift    # Mel spectrogram generation (FFT, filterbank)
│   ├── PeakFeatureHelper.swift       # Peak feature extraction (energy, prominence)
│   ├── CoughClassifierHelper.swift   # Core ML inference wrapper
│   └── CoughDataHelper.swift         # UserDefaults persistence
│
├── Design System/
│   ├── Assets/
│   │   ├── Typography.swift          # Type scale (SF Pro, 4 weights)
│   │   ├── Icons.swift               # SF Symbol constants
│   │   ├── Fonts/                    # Bundled SF Pro font files
│   │   ├── DesignSystemAssets.xcassets  # Color palette
│   │   └── CoughylyzerAppIcon.icon   # App icon (Icon Composer)
│   └── Views/
│       ├── AnimatedMeshGradient.swift # 3×3 animated mesh background
│       ├── AnimatedRecordButton.swift # Glass morphism button + live FFT waveform
│       ├── LungScoreCard.swift       # Arc gauge with animated pointer
│       └── LungScoreChart.swift      # Interactive Swift Charts trend line
│
├── Enums/
│   ├── RecordingUiStateType.swift    # Recording state machine
│   ├── RecordingWaveformConstantsType.swift  # FFT display constants
│   └── SheetPageType.swift           # Sheet navigation
│
└── Resources/
    ├── CoughNormalizationConstants.swift  # Z-score stats + audio config
    ├── LungScoreCardConstants.swift       # Score categories & descriptions
    ├── RecordGuideConstants.swift         # Guide step content
    └── RecordWarningConstants.swift       # Disclaimer content

Requirements

Requirement Version
iOS 26.0+
Xcode 26.0+
Swift 6.0
Device iPhone or iPad with microphone

Note: The app uses iOS 26 APIs including GlassEffectContainer, .glassEffect(), .buttonStyle(.glass), and .toolbarTitleDisplayMode(.inlineLarge). It will not compile on earlier SDK versions.


Getting Started

  1. Clone the repository

    git clone https://github.com/egemengunel/coughylyzer.git
    cd coughylyzer
  2. Open in Xcode

    open Coughylyzer.xcodeproj
  3. Select a target device — the app requires a physical device with a microphone for full functionality (the simulator cannot capture audio).

  4. Build and run — no dependency installation required. The Core ML model is bundled in the repository.


The Core ML Model

CoughylyzerModel.mlpackage is a dual-input classifier trained to distinguish between normal and abnormal cough sounds:

Property Value
Input 1 spectrogram[1, 3, 224, 224] float32
Input 2 peak_features[1, 4] float32
Output classLabel"normal" or "abnormal"
Output classLabel_probs — probability dictionary
Weights ~46 MB
Compute Units All (CPU, GPU, Neural Engine)

The model was trained on a dataset of cough audio samples as part of the associated bachelor's thesis. The iOS app replicates the Python training pipeline's preprocessing (mel spectrogram generation and peak feature extraction with matching normalization statistics) to ensure inference consistency.


Tech Stack

Component Technology
UI Framework SwiftUI
ML Inference Core ML
Audio Recording AVFoundation (AVAudioRecorder)
Audio Monitoring AVFoundation (AVAudioEngine)
Signal Processing Accelerate (vDSP)
Charts Swift Charts
Concurrency Swift 6 structured concurrency, @Observable
Persistence UserDefaults (JSON-encoded)
Dependencies None

License

This project is open source.


Built with SwiftUI and Core ML by Egemen Günel

About

My Bachelor's Thesis Project

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages