SmartSpectra C++ SDK
Measure human vitals from video with SmartSpectra C++ SDK.
Loading...
Searching...
No Matches
background_container_impl.hpp
1
2// background_container_impl.h
3// Created by Greg on 4/29/2024.
4// Copyright (C) 2024 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#pragma once
22// === standard library includes (if any) ===
23// === third-party includes (if any) ===
24#include <mediapipe/framework/formats/image_frame.h>
25#include <mediapipe/framework/formats/image_frame_opencv.h>
26#include <mediapipe/framework/port/opencv_imgproc_inc.h>
27#include <physiology/graph/stream_and_packet_names.h>
28// === local includes (if any) ===
29#include "background_container.hpp"
30#include "image_transfer.hpp"
31
33namespace it = image_transfer;
34namespace pe = physiology::edge;
35
36template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
37BackgroundContainer<TDeviceType,
38 TOperationMode,
39 TIntegrationMode>::BackgroundContainer(BackgroundContainer::SettingsType settings):
40 Base(settings) {}
41
42template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
46
47template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
49 if (this->initialized) {
50 LOG(INFO) << "Container already initialized, skipping initialization.";
51 return absl::OkStatus();
52 }
53 LOG(INFO) << "Begin to initialize preprocessing container.";
54 MP_RETURN_IF_ERROR(Base::Initialize());
55 LOG(INFO) << "Finish preprocessing container initialization.";
56 return absl::OkStatus();
57}
58
59template<typename TCallback>
60static absl::Status CheckCallbackNotNull(const std::string& callback_name, const TCallback& callback) {
61 if (callback == nullptr) {
62 return absl::InvalidArgumentError(
63 callback_name + " callback is nullptr. Expecting a valid callback. "
64 "Please ensure your callback doesn't go out of scope and get destroyed while the graph is running."
65 );
66 }
67 return absl::OkStatus();
68}
69
70template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
72 if (!this->initialized) {
73 return absl::FailedPreconditionError("Container not initialized.");
74 }
75 this->running = true;
76 this->operation_context.Reset();
77
78 // Prepare to handle imaging status code changes and updates.
79 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnStatusChange", this->OnStatusChange));
80 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnStatusCode", this->OnStatusCode));
81 MP_RETURN_IF_ERROR(this->graph.ObserveOutputStream(
82 pe::graph::output_streams::kStatusCode,
83 [this](const mediapipe::Packet& status_packet) -> absl::Status {
84 if (!status_packet.IsEmpty()) {
85 physiology::StatusValue status = status_packet.Get<physiology::StatusValue>();
86
87 // Call OnStatusCode for every status update (regardless of change)
88 MP_RETURN_IF_ERROR(this->OnStatusCode(status));
89
90 // Call OnStatusChange only when status actually changes
91 if (status.value() != this->previous_status_code) {
92 this->previous_status_code = status.value();
93 return this->OnStatusChange(status);
94 }
95 }
96 return absl::OkStatus();
97 }
98 ));
99
100 // Prepare to handle core metrics output.
101 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnCoreMetricsOutput", this->OnCoreMetricsOutput));
102 MP_RETURN_IF_ERROR(this->graph.ObserveOutputStream(
103 physiology::edge::graph::output_streams::kMetricsBuffer,
104 [this](const mediapipe::Packet& output_packet) -> absl::Status {
105 if (!output_packet.IsEmpty()) {
106 auto metrics_buffer = output_packet.Get<physiology::MetricsBuffer>();
107 auto timestamp = output_packet.Timestamp();
108 MP_RETURN_IF_ERROR(this->ComputeCorePerformanceTelemetry(metrics_buffer));
109 return this->OnCoreMetricsOutput(metrics_buffer, timestamp.Value());
110 }
111 return absl::OkStatus();
112 }
113 ));
114
115 // Prepare to handle edge metrics output
116 // A separate outer if-clause used here to increase the likelihood of compiler optimizing this out
117 // when we're in spot mode.
118 if (TOperationMode == settings::OperationMode::Continuous) {
119 if (this->settings.enable_edge_metrics) {
120 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnEdgeMetricsOutput", this->OnEdgeMetricsOutput));
121 MP_RETURN_IF_ERROR(this->graph.ObserveOutputStream(
122 physiology::edge::graph::output_streams::kEdgeMetrics,
123 [this](const mediapipe::Packet& output_packet) {
124 if (!output_packet.IsEmpty()) {
125 auto metrics = output_packet.Get<physiology::Metrics>();
126 auto timestamp = output_packet.Timestamp();
127 return this->OnEdgeMetricsOutput(metrics, timestamp.Value());
128 }
129 return absl::OkStatus();
130 }
131 ));
132 }
133 }
134
135 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnVideoOutput", this->OnVideoOutput));
136 MP_RETURN_IF_ERROR(this->graph.ObserveOutputStream(
137 physiology::edge::graph::output_streams::kOutputVideo,
138 [this](const mediapipe::Packet& output_video_packet) -> absl::Status {
139 if (!output_video_packet.IsEmpty()) {
140 cv::Mat output_frame_rgb;
141 MP_RETURN_IF_ERROR(it::GetFrameFromPacket<TDeviceType>(output_frame_rgb,
142 this->device_context,
143 output_video_packet));
144 // Convert to BGR and display.
145 cv::cvtColor(output_frame_rgb, this->output_frame_bgr, cv::COLOR_RGB2BGR);
146 auto timestamp = output_video_packet.Timestamp();
147 return this->OnVideoOutput(this->output_frame_bgr, timestamp.Value());
148 }
149 return absl::OkStatus();
150 }
151 ));
152
153 MP_RETURN_IF_ERROR(CheckCallbackNotNull("OnFrameSentThrough", this->OnFrameSentThrough));
154 MP_RETURN_IF_ERROR(this->graph.ObserveOutputStream(
155 pe::graph::output_streams::kFrameSentThrough,
156 [this](const mediapipe::Packet& output_packet) {
157 if (!output_packet.IsEmpty()) {
158 bool frame_sent_through = output_packet.Get<bool>();
159 auto timestamp = output_packet.Timestamp();
160 return this->OnFrameSentThrough(frame_sent_through, timestamp.Value());
161 }
162 return absl::OkStatus();
163 }
164 ));
165
166 MP_RETURN_IF_ERROR(this->graph.StartRun({}));
167 MP_RETURN_IF_ERROR(this->graph.WaitUntilIdle());
168 this->running = true;
169 return absl::OkStatus();
170}
171
172template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
174 if (!this->initialized) {
175 return absl::FailedPreconditionError("Container not initialized.");
176 }
177 if (!this->running) {
178 return absl::FailedPreconditionError("Graph not started.");
179 }
180 MP_RETURN_IF_ERROR(this->graph.WaitUntilIdle());
181 return absl::OkStatus();
182}
183
184template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
186 if (!this->initialized) {
187 return absl::FailedPreconditionError("Container not initialized.");
188 }
189 if (!this->running) {
190 return absl::FailedPreconditionError("Graph not started.");
191 }
192 this->recording = on;
193 return absl::OkStatus();
194}
195
206template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
207absl::Status BackgroundContainer<TDeviceType,
208 TOperationMode,
209 TIntegrationMode>::AddFrameWithTimestamp(const cv::Mat& frame_rgb, int64_t frame_timestamp_μs) {
210 if (!this->initialized) {
211 return absl::FailedPreconditionError("Container not initialized.");
212 }
213 if (!this->running) {
214 return absl::FailedPreconditionError("Graph not started.");
215 }
216 // Wrap Mat into an ImageFrame.
217 auto input_frame = absl::make_unique<mediapipe::ImageFrame>(
218 mediapipe::ImageFormat::SRGB, frame_rgb.cols, frame_rgb.rows,
219 mediapipe::ImageFrame::kDefaultAlignmentBoundary
220 );
221 cv::Mat input_frame_mat = mediapipe::formats::MatView(input_frame.get());
222 // transfer camera_frame data to input_frame
223 frame_rgb.copyTo(input_frame_mat);
224
225 auto frame_timestamp = mediapipe::Timestamp(frame_timestamp_μs);
226 this->AddFrameTimestampToBenchmarkingInfo(frame_timestamp);
227 // Send recording state to the graph.
228 MP_RETURN_IF_ERROR(
229 this->graph
230 .AddPacketToInputStream(
231 pe::graph::input_streams::kRecording,
232 mediapipe::MakePacket<bool>(this->recording).At(frame_timestamp)
233 )
234 );
235 // Send image packet into the graph.
236 MP_RETURN_IF_ERROR(
237 it::FeedFrameToGraph(std::move(input_frame), this->graph, this->device_context, frame_timestamp_μs,
238 pe::graph::input_streams::kInputVideo)
239 );
240 return absl::OkStatus();
241}
242
243template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
245 if (!this->initialized) {
246 return absl::FailedPreconditionError("Container not initialized.");
247 }
248 if (this->graph.GraphInputStreamsClosed()) {
249 LOG(INFO) << "Graph already stopped.";
250 return absl::OkStatus();
251 }
252 LOG(INFO) << "Closing input streams/packet sources & stopping graph...";
253 MP_RETURN_IF_ERROR(this->graph.CloseAllInputStreams());
254 MP_RETURN_IF_ERROR(this->graph.CloseAllPacketSources());
255 MP_RETURN_IF_ERROR(this->graph.WaitUntilDone());
256 this->previous_status_code = physiology::StatusCode::PROCESSING_NOT_STARTED;
257 this->running = false;
258 LOG(INFO) << "Graph stopped.";
259 return absl::OkStatus();
260}
261
262} // namespace presage::smartspectra::container
Container for background thread processing.
Definition background_container.hpp:40
absl::Status StartGraph()
Definition background_container_impl.hpp:71
absl::Status AddFrameWithTimestamp(const cv::Mat &frame_rgb, int64_t frame_timestamp_μs)
Definition background_container_impl.hpp:209
absl::Status WaitUntilGraphIsIdle()
Definition background_container_impl.hpp:173
BackgroundContainer(SettingsType settings)
Definition background_container_impl.hpp:39
absl::Status Initialize() override
Definition background_container_impl.hpp:48
absl::Status SetRecording(bool on)
Definition background_container_impl.hpp:185
absl::Status StopGraph()
Definition background_container_impl.hpp:244
bool GraphIsRunning() const
Definition background_container_impl.hpp:43
virtual absl::Status Initialize()
Definition container_impl.hpp:68
void AddFrameTimestampToBenchmarkingInfo(const mediapipe::Timestamp &timestamp)
Definition container_impl.hpp:227
Definition background_container.cpp:10