# C++ Quickstart (Linux)

Build a terminal app that measures pulse and breathing rate from a webcam using OpenCV for display.

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

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

---

## 1. Install Dependencies

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

Install CMake 3.27+:

```bash
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
```

## 2. Install the SmartSpectra SDK

Add the Presage Debian repository:

```bash
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
```

This installs headers, shared libraries, and a CMake config so `find_package(SmartSpectra)` works.

## 3. Create the Project

```bash
mkdir smartspectra-hello && cd smartspectra-hello
```

### CMakeLists.txt

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

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(SmartSpectra REQUIRED)
find_package(OpenCV REQUIRED)

add_executable(hello_vitals hello_vitals.cpp)

target_link_libraries(hello_vitals
  SmartSpectra::Container
  SmartSpectra::Gui
  ${OpenCV_LIBS}
)
```

### hello_vitals.cpp

```cpp
#include <smartspectra/container/foreground_container.hpp>
#include <smartspectra/container/settings.hpp>
#include <smartspectra/gui/opencv_hud.hpp>
#include <physiology/modules/messages/metrics.h>
#include <physiology/modules/messages/status.h>
#include <glog/logging.h>
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace presage::smartspectra;

int main(int argc, char** argv) {
    google::InitGoogleLogging(argv[0]);
    FLAGS_alsologtostderr = true;

    // Get API key from argument or environment
    std::string api_key;
    if (argc > 1) {
        api_key = argv[1];
    } else if (const char* env_key = std::getenv("SMARTSPECTRA_API_KEY")) {
        api_key = env_key;
    } else {
        std::cout << "Usage: ./hello_vitals YOUR_API_KEY\n";
        std::cout << "Or: export SMARTSPECTRA_API_KEY=YOUR_KEY && ./hello_vitals\n";
        std::cout << "Get a key at: https://physiology.presagetech.com\n";
        return 1;
    }

    std::cout << "Starting SmartSpectra...\n";

    try {
        // Configure for continuous measurement via REST API
        container::settings::Settings<
            container::settings::OperationMode::Continuous,
            container::settings::IntegrationMode::Rest
        > settings;

        // Camera settings
        settings.video_source.device_index = 0;
        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;

        // SDK settings
        settings.headless = false;
        settings.enable_edge_metrics = true;
        settings.verbosity_level = 1;
        settings.continuous.preprocessed_data_buffer_duration_s = 0.5;
        settings.integration.api_key = api_key;

        // Create the processing container
        auto container =
            std::make_unique<container::CpuContinuousRestForegroundContainer>(settings);

        // Create the on-screen HUD for displaying metrics
        auto hud = std::make_unique<gui::OpenCvHud>(10, 0, 1260, 400);

        // Callback: receive vital sign metrics from the API
        auto status = container->SetOnCoreMetricsOutput(
            [&hud](const presage::physiology::MetricsBuffer& metrics,
                   int64_t timestamp) {
                float pulse = 0, breathing = 0;

                if (!metrics.pulse().rate().empty()) {
                    pulse = metrics.pulse().rate().rbegin()->value();
                }
                if (!metrics.breathing().rate().empty()) {
                    breathing = metrics.breathing().rate().rbegin()->value();
                }

                if (pulse > 0 && breathing > 0) {
                    std::cout << "Pulse: " << pulse
                              << " BPM | Breathing: " << breathing
                              << " BPM\n";
                }

                hud->UpdateWithNewMetrics(metrics);
                return absl::OkStatus();
            }
        );
        if (!status.ok()) {
            std::cerr << "Failed to set metrics callback: "
                      << status.message() << "\n";
            return 1;
        }

        // Callback: render each video frame with the HUD overlay
        status = container->SetOnVideoOutput(
            [&hud](cv::Mat& frame, int64_t timestamp) {
                if (auto s = hud->Render(frame); !s.ok()) {
                    std::cerr << "HUD render failed: " << s.message() << "\n";
                }
                cv::imshow("SmartSpectra", frame);

                char key = cv::waitKey(1) & 0xFF;
                if (key == 'q' || key == 27) {
                    return absl::CancelledError("User quit");
                }
                return absl::OkStatus();
            }
        );
        if (!status.ok()) {
            std::cerr << "Failed to set video callback: "
                      << status.message() << "\n";
            return 1;
        }

        // Callback: print imaging/processing status changes
        status = container->SetOnStatusChange(
            [](presage::physiology::StatusValue imaging_status) {
                std::cout << "Status: "
                          << presage::physiology::GetStatusDescription(
                                 imaging_status.value())
                          << "\n";
                return absl::OkStatus();
            }
        );
        if (!status.ok()) {
            std::cerr << "Failed to set status callback: "
                      << status.message() << "\n";
            return 1;
        }

        // Initialize and run
        std::cout << "Initializing camera...\n";
        if (auto s = container->Initialize(); !s.ok()) {
            std::cerr << "Init failed: " << s.message() << "\n";
            return 1;
        }

        std::cout << "Ready! Press 's' to start/stop recording. "
                  << "Press 'q' to quit.\n";

        if (auto s = container->Run(); !s.ok()) {
            std::cerr << "Runtime error: " << s.message() << "\n";
            return 1;
        }

        cv::destroyAllWindows();
        std::cout << "Done.\n";
        return 0;

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
}
```

## 4. Build and Run

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

# Run with your API key
./hello_vitals YOUR_API_KEY

# Or use an environment variable
export SMARTSPECTRA_API_KEY=YOUR_API_KEY
./hello_vitals
```

A window opens showing the camera feed with a metrics overlay. Press **s** to start/stop recording data. Press **q** to quit.

## Keyboard Controls

| Key | Action |
|-----|--------|
| `s` | Start/stop recording |
| `e` | Lock/unlock camera exposure |
| `q` or `Esc` | Quit |

## What's Happening

1. The SDK captures frames from your webcam
2. On-device processing extracts facial features and physiological signals
3. Preprocessed data is sent to the Presage REST API for metric calculation
4. Metrics (pulse rate, breathing rate) stream back via callbacks
5. The OpenCV HUD renders real-time traces and values on the video feed

## Platform Support

| OS | Architecture | Status |
|---|---|---|
| Ubuntu 22.04 / Mint 21 | x86_64 (amd64) | Available |
| Ubuntu 22.04 / Mint 21 | ARM64 (aarch64) | Partners only |
| Ubuntu 24.04 / Mint 22 | x86_64 | Planned |
| Debian 11/12 | x86_64, ARM64 | Partners only |
| RHEL 9 / Fedora 41 | x86_64, ARM64 | Contact support |
| macOS | ARM64 | Planned (from-source) |
| Windows | x86_64 | Contact support |

## Uninstalling

```bash
sudo apt remove libphysiologyedge-dev libsmartspectra-dev
sudo rm /etc/apt/sources.list.d/presage-technologies.list
sudo rm /etc/apt/trusted.gpg.d/presage-technologies.gpg
sudo apt update
```

## Troubleshooting

**CMake can't find SmartSpectra**
- Verify the package installed: `dpkg -l | grep smartspectra`
- Check that `find_package(SmartSpectra)` is in your CMakeLists.txt

**Camera not found / black screen**
- Check device exists: `ls /dev/video*`
- Try a different index: `settings.video_source.device_index = 1`
- Verify V4L: `v4l2-ctl --list-devices`

**"UNAUTHORIZED" status**
- Verify your API key at [physiology.presagetech.com](https://physiology.presagetech.com)
- Check you're passing it correctly (argument or env var)

**Low confidence / no readings**
- Ensure at least 60 lux lighting
- Keep your face centered, 1–3 ft from the camera
- Minimize movement during measurement

## Next Steps

- Browse the [full SDK source and samples](https://github.com/Presage-Security/SmartSpectra/tree/main/cpp)
- Read the [authentication docs](https://github.com/Presage-Security/SmartSpectra/blob/main/docs/authentication.md)
- See the [Python](/docs/quickstart-python), [Java](/docs/quickstart-java), and [JavaScript](/docs/quickstart-javascript) guides for calling the C++ SDK from other languages
