# Python Quickstart (Linux)

Use the SmartSpectra C++ SDK from Python to measure pulse and breathing rate from a webcam.

**Platform:** Ubuntu 22.04 / Linux Mint 21 (x86_64) only

**Requirements:** Python 3.8+, webcam, API key from [physiology.presagetech.com](https://physiology.presagetech.com)

> **Note:** There is no `pip install` package for SmartSpectra. This guide builds a Python-callable C bridge around the C++ SDK.

---

## 1. Install the C++ SDK

Install build tools and the SmartSpectra SDK from the Presage PPA:

```bash
sudo apt update
sudo apt install -y build-essential libcurl4-openssl-dev libssl-dev \
  pkg-config libv4l-dev libgles2-mesa-dev libunwind-dev gpg curl

# CMake 3.27+
curl -L -o cmake-3.27.0-linux-x86_64.sh \
  https://github.com/Kitware/CMake/releases/download/v3.27.0/cmake-3.27.0-linux-x86_64.sh
chmod +x cmake-3.27.0-linux-x86_64.sh
sudo ./cmake-3.27.0-linux-x86_64.sh --skip-license --prefix=/usr/local

# Presage PPA
curl -s "https://presage-security.github.io/PPA/KEY.gpg" | gpg --dearmor | \
  sudo tee /etc/apt/trusted.gpg.d/presage-technologies.gpg >/dev/null
sudo curl -s --compressed -o /etc/apt/sources.list.d/presage-technologies.list \
  "https://presage-security.github.io/PPA/presage-technologies.list"
sudo apt update
sudo apt install libsmartspectra-dev

# OpenCV (for the display window)
sudo apt install -y libopencv-dev
```

## 2. Create the Project

```bash
mkdir smartspectra-python && cd smartspectra-python
```

### The C Bridge

The C++ SDK has a C++ API. To call it from Python, we create a thin C shared library that wraps the key operations.

**smartspectra_bridge.cpp:**

```cpp
#include <smartspectra/container/foreground_container.hpp>
#include <smartspectra/container/settings.hpp>
#include <physiology/modules/messages/metrics.h>
#include <physiology/modules/messages/status.h>
#include <glog/logging.h>
#include <opencv2/opencv.hpp>
#include <memory>
#include <mutex>
#include <atomic>
#include <cstring>

using namespace presage::smartspectra;

// Stored metrics (latest readings)
struct LatestMetrics {
    std::mutex mu;
    float pulse_rate = 0;
    float breathing_rate = 0;
    bool has_data = false;
};

struct SmartSpectraHandle {
    std::unique_ptr<container::CpuContinuousRestForegroundContainer> container;
    LatestMetrics metrics;
    std::atomic<bool> running{false};
};

extern "C" {

// Create and configure a SmartSpectra instance
SmartSpectraHandle* smartspectra_create(const char* api_key, int camera_index) {
    static bool glog_initialized = false;
    if (!glog_initialized) {
        google::InitGoogleLogging("smartspectra_python");
        FLAGS_alsologtostderr = false;
        glog_initialized = true;
    }

    auto handle = new SmartSpectraHandle();

    container::settings::Settings<
        container::settings::OperationMode::Continuous,
        container::settings::IntegrationMode::Rest
    > settings;

    settings.video_source.device_index = camera_index;
    settings.video_source.capture_width_px = 1280;
    settings.video_source.capture_height_px = 720;
    settings.video_source.codec = presage::camera::CaptureCodec::MJPG;
    settings.video_source.auto_lock = true;
    settings.headless = true;  // No OpenCV window from C++ side
    settings.enable_edge_metrics = true;
    settings.verbosity_level = 0;
    settings.continuous.preprocessed_data_buffer_duration_s = 0.5;
    settings.integration.api_key = api_key;

    handle->container =
        std::make_unique<container::CpuContinuousRestForegroundContainer>(
            settings);

    // Metrics callback — store latest values
    handle->container->SetOnCoreMetricsOutput(
        [handle](const presage::physiology::MetricsBuffer& metrics,
                 int64_t timestamp) {
            std::lock_guard<std::mutex> lock(handle->metrics.mu);
            if (!metrics.pulse().rate().empty()) {
                handle->metrics.pulse_rate =
                    metrics.pulse().rate().rbegin()->value();
            }
            if (!metrics.breathing().rate().empty()) {
                handle->metrics.breathing_rate =
                    metrics.breathing().rate().rbegin()->value();
            }
            handle->metrics.has_data = true;
            return absl::OkStatus();
        }
    );

    // Video callback — just keep the loop alive (headless)
    handle->container->SetOnVideoOutput(
        [handle](cv::Mat& frame, int64_t timestamp) {
            if (!handle->running.load()) {
                return absl::CancelledError("Stopped");
            }
            return absl::OkStatus();
        }
    );

    // Status callback — print to stderr
    handle->container->SetOnStatusChange(
        [](presage::physiology::StatusValue status) {
            fprintf(stderr, "[SmartSpectra] Status: %s\n",
                presage::physiology::GetStatusDescription(
                    status.value()).c_str());
            return absl::OkStatus();
        }
    );

    return handle;
}

// Initialize camera and processing pipeline
int smartspectra_initialize(SmartSpectraHandle* handle) {
    if (!handle) return -1;
    auto status = handle->container->Initialize();
    return status.ok() ? 0 : -1;
}

// Start processing (blocks the calling thread)
int smartspectra_run(SmartSpectraHandle* handle) {
    if (!handle) return -1;
    handle->running.store(true);
    auto status = handle->container->Run();
    return status.ok() ? 0 : -1;
}

// Signal the processing loop to stop
void smartspectra_stop(SmartSpectraHandle* handle) {
    if (handle) {
        handle->running.store(false);
    }
}

// Read the latest metrics (thread-safe)
int smartspectra_get_metrics(SmartSpectraHandle* handle,
                            float* pulse_rate,
                            float* breathing_rate) {
    if (!handle) return -1;
    std::lock_guard<std::mutex> lock(handle->metrics.mu);
    if (!handle->metrics.has_data) return -1;
    *pulse_rate = handle->metrics.pulse_rate;
    *breathing_rate = handle->metrics.breathing_rate;
    return 0;
}

// Clean up
void smartspectra_destroy(SmartSpectraHandle* handle) {
    delete handle;
}

}  // extern "C"
```

**CMakeLists.txt:**

```cmake
cmake_minimum_required(VERSION 3.27.0)
project(SmartSpectraBridge CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(SmartSpectra REQUIRED)
find_package(OpenCV REQUIRED)

add_library(smartspectra_bridge SHARED smartspectra_bridge.cpp)

target_link_libraries(smartspectra_bridge
  SmartSpectra::Container
  ${OpenCV_LIBS}
)
```

### Build the Bridge

```bash
mkdir build && cd build
cmake ..
make -j$(nproc)
cd ..
```

This produces `build/libsmartspectra_bridge.so`.

## 3. Write the Python App

**hello_vitals.py:**

```python
import ctypes
import threading
import time
import os
import sys

# Load the C bridge library
bridge = ctypes.CDLL("./build/libsmartspectra_bridge.so")

# Define function signatures
bridge.smartspectra_create.restype = ctypes.c_void_p
bridge.smartspectra_create.argtypes = [ctypes.c_char_p, ctypes.c_int]

bridge.smartspectra_initialize.restype = ctypes.c_int
bridge.smartspectra_initialize.argtypes = [ctypes.c_void_p]

bridge.smartspectra_run.restype = ctypes.c_int
bridge.smartspectra_run.argtypes = [ctypes.c_void_p]

bridge.smartspectra_stop.argtypes = [ctypes.c_void_p]

bridge.smartspectra_get_metrics.restype = ctypes.c_int
bridge.smartspectra_get_metrics.argtypes = [
    ctypes.c_void_p,
    ctypes.POINTER(ctypes.c_float),
    ctypes.POINTER(ctypes.c_float),
]

bridge.smartspectra_destroy.argtypes = [ctypes.c_void_p]


def main():
    # Get API key
    api_key = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("SMARTSPECTRA_API_KEY")
    if not api_key:
        print("Usage: python hello_vitals.py YOUR_API_KEY")
        print("Or:    export SMARTSPECTRA_API_KEY=YOUR_KEY")
        print("Get a key at: https://physiology.presagetech.com")
        sys.exit(1)

    camera_index = 0

    print("Creating SmartSpectra instance...")
    handle = bridge.smartspectra_create(api_key.encode(), camera_index)
    if not handle:
        print("Failed to create SmartSpectra instance")
        sys.exit(1)

    print("Initializing camera...")
    if bridge.smartspectra_initialize(handle) != 0:
        print("Failed to initialize")
        bridge.smartspectra_destroy(handle)
        sys.exit(1)

    # Run processing in a background thread (it blocks)
    def run_processing():
        bridge.smartspectra_run(handle)

    thread = threading.Thread(target=run_processing, daemon=True)
    thread.start()

    print("Running! Reading metrics every 2 seconds. Press Ctrl+C to stop.\n")

    pulse = ctypes.c_float()
    breathing = ctypes.c_float()

    try:
        while thread.is_alive():
            time.sleep(2)
            if bridge.smartspectra_get_metrics(
                handle, ctypes.byref(pulse), ctypes.byref(breathing)
            ) == 0:
                print(
                    f"Pulse: {pulse.value:.0f} BPM | "
                    f"Breathing: {breathing.value:.1f} BPM"
                )
            else:
                print("Waiting for metrics...")
    except KeyboardInterrupt:
        print("\nStopping...")

    bridge.smartspectra_stop(handle)
    thread.join(timeout=5)
    bridge.smartspectra_destroy(handle)
    print("Done.")


if __name__ == "__main__":
    main()
```

## 4. Run

```bash
python3 hello_vitals.py YOUR_API_KEY

# Or with environment variable
export SMARTSPECTRA_API_KEY=YOUR_API_KEY
python3 hello_vitals.py
```

Output:

```
Creating SmartSpectra instance...
Initializing camera...
Running! Reading metrics every 2 seconds. Press Ctrl+C to stop.

Waiting for metrics...
Waiting for metrics...
Pulse: 72 BPM | Breathing: 16.2 BPM
Pulse: 73 BPM | Breathing: 15.8 BPM
^C
Stopping...
Done.
```

## How It Works

```
Python (ctypes) → C bridge (.so) → SmartSpectra C++ SDK → Camera + REST API
```

1. `hello_vitals.py` loads the C bridge shared library via `ctypes`
2. The bridge wraps the SmartSpectra C++ container API with `extern "C"` functions
3. Processing runs in a background thread — the C++ SDK captures frames and sends preprocessed data to the Presage REST API
4. Python polls for the latest metrics on the main thread

## Platform Support

This approach only works on **Linux x86_64** (Ubuntu 22.04 / Mint 21) because the SmartSpectra C++ SDK Debian package is only available for that platform. See the [C++ quickstart](/docs/quickstart-cpp) for the full platform matrix.

## Troubleshooting

**`OSError: cannot open shared object file`**
- Make sure `build/libsmartspectra_bridge.so` exists — run `make` in the `build/` directory
- Or set `LD_LIBRARY_PATH`: `export LD_LIBRARY_PATH=./build:$LD_LIBRARY_PATH`

**Camera not found**
- Check: `ls /dev/video*`
- Try `camera_index = 1` in the Python script

**No metrics after 60+ seconds**
- Verify your API key is valid
- Check lighting (at least 60 lux) and face positioning

For other issues, see the [C++ troubleshooting](/docs/quickstart-cpp#troubleshooting) section.

## Next Steps

- See the [C++ quickstart](/docs/quickstart-cpp) for the full C++ API details
- Browse the [SDK source](https://github.com/Presage-Security/SmartSpectra/tree/main/cpp)
