SmartSpectra C++ SDK
Measure human vitals from video with SmartSpectra C++ SDK.
Loading...
Searching...
No Matches
preprocessing_foreground_container_impl.hpp
1
2// preprocessing_foreground_container_impl.hpp
3// Created by greg on 1/13/25.
4// Copyright (C) 2025 Presage Security, Inc.
5//
6// This program is free software; you can redistribute it and/or
7// modify it under the terms of the GNU Lesser General Public
8// License as published by the Free Software Foundation; either
9// version 3 of the License, or (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with this program; if not, write to the Free Software Foundation,
18// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21// === standard library includes (if any) ===
22// === third-party includes (if any) ===
23#include <mediapipe/framework/port/status_macros.h>
24#include <mediapipe/framework/port/opencv_core_inc.h>
25#include <mediapipe/framework/port/opencv_imgproc_inc.h>
26#include <physiology/graph/stream_and_packet_names.h>
27#include <physiology/modules/messages/metrics.h>
28// === local includes (if any) ===
29#include "preprocessing_foreground_container.hpp"
30#include "preprocessing_stream_and_packet_names.hpp"
31#include <smartspectra/container/packet_helpers.hpp>
32
33
34#pragma once
35
36namespace presage::smartspectra::lab {
37namespace c = container;
38namespace poller = container::output_stream_poller_wrapper;
39namespace pe = physiology::edge;
40namespace pg = preprocessing_graph;
41namespace ph = container::packet_helpers;
42
43
44template<
45 platform_independence::DeviceType TDeviceType,
46 c::settings::OperationMode TOperationMode,
47 c::settings::IntegrationMode TIntegrationMode
48>
49absl::Status PreprocessingForegroundContainer<TDeviceType,
50 TOperationMode,
51 TIntegrationMode>::InitializeOutputDataPollers() {
52 MP_RETURN_IF_ERROR(this->preprocessed_data_poller.Initialize(this->graph, pg::output_streams::kPreprocessedData));
53 // Initialize optional debug pollers (safe even if not used)
54 // These streams exist in preprocessing graphs when tracking is enabled.
55 // Ignore Initialize errors to avoid failing runs in graphs missing these outputs.
56 (void)this->points_chest_poller.Initialize(this->graph, pg::output_streams::kPointsChest);
57 (void)this->points_abdomen_poller.Initialize(this->graph, pg::output_streams::kPointsAbdomen);
58 (void)this->points_glutes_poller.Initialize(this->graph, pg::output_streams::kPointsGlutes);
59 (void)this->points_knees_poller.Initialize(this->graph, pg::output_streams::kPointsKnees);
60 (void)this->transformed_size_poller.Initialize(this->graph, pg::output_streams::kTransformedSize);
61 return absl::OkStatus();
62}
63
64template<platform_independence::DeviceType TDeviceType, container::settings::OperationMode TOperationMode, container::settings::IntegrationMode TIntegrationMode>
65PreprocessingForegroundContainer<TDeviceType, TOperationMode, TIntegrationMode>::PreprocessingForegroundContainer(
66 PreprocessingForegroundContainer::SettingsType settings
67): Base(settings) {}
68
69template<
70 platform_independence::DeviceType TDeviceType,
71 c::settings::OperationMode TOperationMode,
72 c::settings::IntegrationMode TIntegrationMode
73>
74absl::Status PreprocessingForegroundContainer<TDeviceType,
75 TOperationMode,
76 TIntegrationMode>::HandleOutputData(int64_t frame_timestamp) {
77 bool got_metrics_output;
78 nlohmann::json preprocessed_data;
79 MP_RETURN_IF_ERROR(ph::GetPacketContentsIfAny(
80 preprocessed_data, got_metrics_output, preprocessed_data_poller.Get(), pg::output_streams::kPreprocessedData,
81 this->settings.verbosity_level > 1
82 ));
83 if (got_metrics_output) {
84 MP_RETURN_IF_ERROR(this->OnPreprocessedDataOutput(preprocessed_data, frame_timestamp));
85 if (TOperationMode == c::settings::OperationMode::Spot) {
86 // reset to start state
87 this->recording = false;
88 if (this->load_video) {
89 this->keep_grabbing_frames = false;
90 } else if (this->settings.video_source.auto_lock &&
91 this->video_source->SupportsExposureControls()) {
92 MP_RETURN_IF_ERROR(this->video_source->TurnOnAutoExposure());
93 }
94 }
95 }
96 return absl::OkStatus();
97}
98
99template<
100 platform_independence::DeviceType TDeviceType,
101 container::settings::OperationMode TOperationMode,
102 container::settings::IntegrationMode TIntegrationMode
103>
105const {
106 return c::settings::IntegrationModeTraits<TIntegrationMode>::kPreprocessingDataFormat;
107}
108
109template<
110 platform_independence::DeviceType TDeviceType,
111 container::settings::OperationMode TOperationMode,
112 container::settings::IntegrationMode TIntegrationMode
113>
117
118template<
119 platform_independence::DeviceType TDeviceType,
120 c::settings::OperationMode TOperationMode,
121 c::settings::IntegrationMode TIntegrationMode
122>
123absl::Status PreprocessingForegroundContainer<TDeviceType,
124 TOperationMode,
125 TIntegrationMode>::DrawDebugOverlays(cv::Mat& output_frame_bgr) {
126 using ::presage::physiology::Landmarks;
127
128 bool got_packet;
129
130 // Verbosity and transformed size capture (once)
131 const bool verbose = this->Base::settings.verbosity_level > 3;
132 if (!this->transformed_size.has_value()) {
133 std::pair<int, int> ts;
134 bool size_available;
135 MP_RETURN_IF_ERROR(ph::GetPacketContentsIfAny(
136 ts, size_available, this->transformed_size_poller.Get(), pg::output_streams::kTransformedSize, verbose
137 ));
138 if (size_available) {
139 this->transformed_size = cv::Size(ts.first, ts.second);
140 }
141 }
142 // Precompute scale factors (multiply inside point loop only)
143 double scale_x = 1.0, scale_y = 1.0;
144 if (this->transformed_size.has_value() && output_frame_bgr.data &&
145 this->transformed_size->width > 0 && this->transformed_size->height > 0) {
146 scale_x = static_cast<double>(output_frame_bgr.cols) / static_cast<double>(this->transformed_size->width);
147 scale_y = static_cast<double>(output_frame_bgr.rows) / static_cast<double>(this->transformed_size->height);
148 }
149
150 auto draw_region = [&, this](const Landmarks& landmarks, const cv::Scalar& color, const std::string& label, double alpha) {
151 if (!output_frame_bgr.data || landmarks.value_size() == 0) return;
152 std::vector<cv::Point> points;
153 points.reserve(landmarks.value_size());
154 for (int i = 0; i < landmarks.value_size(); ++i) {
155 // 0.0f is magic value for "undefined"
156 if (landmarks.value(i).x() != 0.0f){
157 int x = cvRound(landmarks.value(i).x() * scale_x);
158 int y = cvRound(landmarks.value(i).y() * scale_y);
159 points.emplace_back(cv::Point(x, y));
160 }
161 }
162 for (const auto& point : points) {
163 cv::circle(output_frame_bgr, point, 3, color, cv::FILLED);
164 }
165 if (points.size() >= 3) {
166 std::vector<cv::Point> hull;
167 cv::convexHull(points, hull); // compute hull points directly
168 cv::Mat mask = cv::Mat::zeros(output_frame_bgr.size(), CV_8UC1);
169 std::vector<std::vector<cv::Point>> hulls = {hull};
170 cv::fillPoly(mask, hulls, cv::Scalar(255));
171 cv::Mat color_mask;
172 cv::cvtColor(mask, color_mask, cv::COLOR_GRAY2BGR);
173 color_mask.setTo(color, mask);
174 cv::addWeighted(color_mask, alpha, output_frame_bgr, 1.0 - alpha, 0, output_frame_bgr);
175 // Outline for clarity
176 const int thickness = 2;
177 cv::polylines(output_frame_bgr, hulls, true, color, thickness);
178 // Label near centroid
179 cv::Point2d centroid(0, 0);
180 // conditional here on the off-chance our hull computation errored somehow and got 0 points
181 if (hull.size() > 0){
182 for(const auto& point : hull){
183 centroid.x += point.x;
184 centroid.y += point.y;
185 }
186 centroid.x /= static_cast<double>(hull.size());
187 centroid.y /= static_cast<double>(hull.size());
188 }
189 cv::putText(output_frame_bgr, label, cv::Point(static_cast<int>(centroid.x), static_cast<int>(centroid.y)),
190 cv::FONT_HERSHEY_SIMPLEX, 0.5, color, 1, cv::LINE_AA);
191 }
192 };
193
194 auto draw_if_available = [&](auto& poller,
195 const char* stream_name,
196 const char* label,
197 const cv::Scalar& color,
198 double alpha) -> absl::Status {
199 Landmarks region_points;
200 bool available;
201 MP_RETURN_IF_ERROR(ph::GetPacketContentsIfAny(
202 region_points, available, poller.Get(), stream_name, verbose
203 ));
204 if (available) draw_region(region_points, color, label, alpha);
205 return absl::OkStatus();
206 };
207
208 MP_RETURN_IF_ERROR(draw_if_available(this->points_chest_poller, pg::output_streams::kPointsChest, "chest", cv::Scalar(0, 0, 255), 0.30));
209 MP_RETURN_IF_ERROR(draw_if_available(this->points_abdomen_poller, pg::output_streams::kPointsAbdomen, "abdomen", cv::Scalar(0, 255, 0), 0.25));
210 MP_RETURN_IF_ERROR(draw_if_available(this->points_glutes_poller, pg::output_streams::kPointsGlutes, "glutes", cv::Scalar(255, 0, 0), 0.20));
211 MP_RETURN_IF_ERROR(draw_if_available(this->points_knees_poller, pg::output_streams::kPointsKnees, "knees", cv::Scalar(0, 255, 255), 0.20));
212
213 return absl::OkStatus();
214}
215
216} // namespace presage::smartspectra::lab
Definition preprocessing_foreground_container.hpp:38
absl::Status HandleOutputData(int64_t frame_timestamp) override
Definition preprocessing_foreground_container_impl.hpp:76
std::string GetGraphFilePrefix() const override
Definition preprocessing_foreground_container_impl.hpp:114
std::string GetThirdGraphFileSuffix() const override
Definition preprocessing_foreground_container_impl.hpp:104
absl::Status InitializeOutputDataPollers() override
Definition preprocessing_foreground_container_impl.hpp:51
Definition background_container.cpp:10