SmartSpectra C++ SDK
Measure human vitals from video with SmartSpectra C++ SDK.
Loading...
Searching...
No Matches
container_impl.hpp
1
2// container_impl.h
3// Created by Greg on 2/16/24.
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// === configuration header ===
24#include "configuration.hpp"
25// === third-party includes (if any) ===
26#include <mediapipe/framework/port/logging.h>
27#include <mediapipe/framework/port/opencv_core_inc.h>
28#include <mediapipe/framework/port/opencv_highgui_inc.h>
29#include <mediapipe/framework/formats/image_frame.h>
30#include <mediapipe/framework/formats/image_frame_opencv.h>
31#include <physiology/graph/stream_and_packet_names.h>
32// === local includes (if any) ===
33#include "container.hpp"
34#include "initialization.hpp"
35#include "image_transfer.hpp"
36#include "packet_helpers.hpp"
37#include "benchmarking.hpp"
38#include "keyboard_input.hpp"
39#include "json_file_io.hpp"
40
41
43
44namespace pi = platform_independence;
45namespace init = initialization;
46namespace keys = keyboard_input;
47namespace ph = packet_helpers;
48namespace it = image_transfer;
49namespace bench = benchmarking;
50
51template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
53 settings(std::move(settings)),
54 graph(),
55 device_context(),
56 operation_context(settings.operation),
57 status(physiology::BuildStatusValue(physiology::StatusCode::PROCESSING_NOT_STARTED,
58 std::chrono::duration_cast<std::chrono::microseconds>(
59 std::chrono::system_clock::now().time_since_epoch()).count()
60 )){};
61
62
63template<
64 platform_independence::DeviceType TDeviceType,
65 settings::OperationMode TOperationMode,
66 settings::IntegrationMode TIntegrationMode
67>
69 if (this->initialized) {
70 // Nothing to do.
71 return absl::OkStatus();
72 }
73 // OpenCV version check needed for some video capture functions / video interface registry
74 static_assert(CV_MAJOR_VERSION > 4 || (CV_MAJOR_VERSION >= 4 && CV_MINOR_VERSION >= 2),
75 "OpenCV 4.2 or above is required");
76
77 MP_ASSIGN_OR_RETURN(std::filesystem::path graph_path, GetGraphFilePath());
78 MP_RETURN_IF_ERROR(
79 init::InitializeGraph<TDeviceType>(this->graph,
80 graph_path.string(),
81 this->settings,
82 this->settings.binary_graph)
83 );
84 MP_RETURN_IF_ERROR(init::InitializeComputingDevice<TDeviceType>(this->graph, this->device_context));
85
86 initialized = true;
87 return absl::OkStatus();
88}
89
90
91template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
93 return settings::AbslUnparseFlag(TIntegrationMode);
94}
95
96
97template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
101
102template<platform_independence::DeviceType TDeviceType, settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode>
103absl::StatusOr<std::filesystem::path>
105 std::string device_type = pi::AbslUnparseFlag(TDeviceType);
106 std::string operation_mode = settings::AbslUnparseFlag(TOperationMode);
107 std::string third_graph_suffix = this->GetThirdGraphFileSuffix();
108 std::string extension = binary_graph ? ".binarypb" : ".pbtxt";
109 std::string prefix = this->GetGraphFilePrefix();
110 std::filesystem::path graph_directory = PHYSIOLOGY_EDGE_GRAPH_DIRECTORY;
111 auto graph_file_path = graph_directory /
112 (prefix + "_" + device_type + "_" + operation_mode + "_" + third_graph_suffix + extension);
113 if (this->settings.verbosity_level > 1) {
114 LOG(INFO) << "Retrieving graph from path: " << graph_file_path.string();
115 }
116 return graph_file_path;
117}
118
119
120template<typename TCallback>
121absl::Status CheckCallbackNotNull(const TCallback& callback) {
122 if (callback == nullptr) {
123 return absl::InvalidArgumentError(
124 "Callback cannot be nullptr."
125 );
126 }
127 return absl::OkStatus();
128}
129
130template<
131 platform_independence::DeviceType TDeviceType,
132 settings::OperationMode TOperationMode,
133 settings::IntegrationMode TIntegrationMode
134>
136 const std::function<absl::Status(physiology::StatusValue)>& on_status_change
137) {
138 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_status_change));
139 this->OnStatusChange = on_status_change;
140 return absl::OkStatus();
141}
142
143template<
144 platform_independence::DeviceType TDeviceType,
145 settings::OperationMode TOperationMode,
146 settings::IntegrationMode TIntegrationMode
147>
149 const std::function<absl::Status(physiology::StatusValue)>& on_status_code
150) {
151 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_status_code));
152 this->OnStatusCode = on_status_code;
153 return absl::OkStatus();
154}
155
156template<
157 platform_independence::DeviceType TDeviceType,
158 settings::OperationMode TOperationMode,
159 settings::IntegrationMode TIntegrationMode
160>
161absl::Status Container<TDeviceType, TOperationMode, TIntegrationMode>::SetOnEdgeMetricsOutput(
162 const std::function<absl::Status(const physiology::Metrics&, int64_t input_timestamp)>& on_edge_metrics_output
163) {
164 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_edge_metrics_output));
165 this->OnEdgeMetricsOutput = on_edge_metrics_output;
166 return absl::OkStatus();
167}
168
169template<
170 platform_independence::DeviceType TDeviceType,
171 settings::OperationMode TOperationMode,
172 settings::IntegrationMode TIntegrationMode
173>
175 const std::function<absl::Status(const physiology::MetricsBuffer&, int64_t input_timestamp)>& on_core_metrics_output
176) {
177 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_core_metrics_output));
178 this->OnCoreMetricsOutput = on_core_metrics_output;
179 return absl::OkStatus();
180}
181
182template<
183 platform_independence::DeviceType TDeviceType,
184 settings::OperationMode TOperationMode,
185 settings::IntegrationMode TIntegrationMode
186>
187absl::Status Container<TDeviceType, TOperationMode, TIntegrationMode>::SetOnVideoOutput(
188 const std::function<absl::Status(cv::Mat&, int64_t)>& on_video_output
189) {
190 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_video_output));
191 this->OnVideoOutput = on_video_output;
192 return absl::OkStatus();
193}
194
195template<
196 platform_independence::DeviceType TDeviceType,
197 settings::OperationMode TOperationMode,
198 settings::IntegrationMode TIntegrationMode
199>
201 const std::function<absl::Status(bool, int64_t)>& on_dropped_frame
202) {
203 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_dropped_frame));
204 this->OnFrameSentThrough = on_dropped_frame;
205 return absl::OkStatus();
206}
207
208template<
209 platform_independence::DeviceType TDeviceType,
210 settings::OperationMode TOperationMode,
211 settings::IntegrationMode TIntegrationMode
212>
214 const std::function<absl::Status(double, double, int64_t)>& on_effective_core_fps_output
215) {
216 MP_RETURN_IF_ERROR(CheckCallbackNotNull(on_effective_core_fps_output));
217 this->OnCorePerformanceTelemetry = on_effective_core_fps_output;
218 return absl::OkStatus();
219}
220
221
222template<
223 platform_independence::DeviceType TDeviceType,
224 settings::OperationMode TOperationMode,
225 settings::IntegrationMode TIntegrationMode
226>
228 if (this->OnCorePerformanceTelemetry.has_value() && this->recording) {
229 // Calculate the offset of frame capture time from system time
230 if (!offset_from_system_time.has_value()) {
231 double current_system_seconds =
232 std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch()).count();
233 offset_from_system_time = current_system_seconds - timestamp.Seconds();
234 }
235 this->frames_in_graph_timestamps.insert(timestamp.Value());
236 }
237}
238
246template<
247 platform_independence::DeviceType TDeviceType,
248 settings::OperationMode TOperationMode,
249 settings::IntegrationMode TIntegrationMode
250>
252 const physiology::MetricsBuffer& metrics_buffer
253) {
254 if (this->OnCorePerformanceTelemetry.has_value()) {
255 double current_system_seconds =
256 std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch()).count();
257
258 auto last_buffer_input_timestamp = metrics_buffer.metadata().frame_timestamp();
259 auto last_buffer_input_timestamp_location =
260 this->frames_in_graph_timestamps.find(last_buffer_input_timestamp);
261 auto first_buffer_input_timestamp = *this->frames_in_graph_timestamps.begin();
262
263 // want to be using buffer frame count further (which is captured during send/receive),
264 // NOT last_output_timestamp_loc - this->frames_in_graph_timestamps.begin(),
265 // because some input frames may have been dropped.
266
267 // erase all frames associated with this buffer (even dropped ones)
268 this->frames_in_graph_timestamps.erase(this->frames_in_graph_timestamps.begin(),
269 last_buffer_input_timestamp_location);
270
271 // compute buffer latency
272 double absolute_last_output_system_seconds =
273 mediapipe::Timestamp(last_buffer_input_timestamp).Seconds() + offset_from_system_time.value();
274 double buffer_latency_seconds = current_system_seconds - absolute_last_output_system_seconds;
275
276 // add buffer benchmarking information to buffer to compute averages later
277 this->metrics_buffer_benchmarking_info_buffer.push_back(
278 MetricsBufferBenchmarkingInfo{
279 first_buffer_input_timestamp,
280 last_buffer_input_timestamp,
281 metrics_buffer.metadata().frame_count(),
282 buffer_latency_seconds
283 }
284 );
285
286 // clear out benchmarking information from buffer from before the current window (using window duration)
287 // (approximate, since we use last output timestamp)
288 auto metrics_buffer_benchmarking_info_location =
289 this->metrics_buffer_benchmarking_info_buffer.begin();
290 auto current_window_start = last_buffer_input_timestamp - this->fps_averaging_window_microseconds;
291 while (metrics_buffer_benchmarking_info_location != this->metrics_buffer_benchmarking_info_buffer.end()
292 && metrics_buffer_benchmarking_info_location->last_timestamp < current_window_start) {
293 metrics_buffer_benchmarking_info_location++;
294 }
295 if (metrics_buffer_benchmarking_info_location > this->metrics_buffer_benchmarking_info_buffer.begin() + 1) {
296 this->metrics_buffer_benchmarking_info_buffer.erase(this->metrics_buffer_benchmarking_info_buffer.begin(),
297 metrics_buffer_benchmarking_info_location--);
298 }
299
300 // compute total average framerate
301 int64_t window_total_microseconds =
302 last_buffer_input_timestamp - this->metrics_buffer_benchmarking_info_buffer.begin()->first_timestamp;
303 int32_t window_frame_count = 0;
304 double aggregate_latency_seconds = 0.0;
305 for (const auto& buffer_benchmarking_info: this->metrics_buffer_benchmarking_info_buffer) {
306 window_frame_count += buffer_benchmarking_info.frame_count;
307 aggregate_latency_seconds += buffer_benchmarking_info.latency_seconds;
308 }
309
310 // note: exclude the very last frame from the count, since it's "incomplete" when it's just captured,
311 // and the window ends with it being just captured
312 double effective_core_fps =
313 static_cast<double>(window_frame_count - 1) * 1000000.0 /
314 static_cast<double>(window_total_microseconds);
315
316 double effective_core_latency_seconds =
317 aggregate_latency_seconds / this->metrics_buffer_benchmarking_info_buffer.size();
318
319 MP_RETURN_IF_ERROR(this->OnCorePerformanceTelemetry.value()(
320 effective_core_fps, effective_core_latency_seconds, first_buffer_input_timestamp
321 ));
322
323 }
324 return absl::OkStatus();
325}
326
327} // namespace presage::smartspectra::container
Container(SettingsType settings)
Definition container_impl.hpp:52
absl::Status SetOnStatusChange(const std::function< absl::Status(physiology::StatusValue)> &on_status_change)
Definition container_impl.hpp:135
absl::Status ComputeCorePerformanceTelemetry(const physiology::MetricsBuffer &metrics_buffer)
Definition container_impl.hpp:251
absl::Status SetOnCoreMetricsOutput(const std::function< absl::Status(const physiology::MetricsBuffer &, int64_t input_timestamp)> &on_core_metrics_output)
Definition container_impl.hpp:174
absl::Status SetOnCorePerformanceTelemetry(const std::function< absl::Status(double, double, int64_t)> &on_effective_core_fps_output)
Definition container_impl.hpp:213
absl::Status SetOnFrameSentThrough(const std::function< absl::Status(bool frame_sent_through, int64_t input_timestamp)> &on_dropped_frame)
Definition container_impl.hpp:200
virtual std::string GetThirdGraphFileSuffix() const
Definition container_impl.hpp:92
virtual std::string GetGraphFilePrefix() const
Definition container_impl.hpp:98
virtual absl::Status Initialize()
Definition container_impl.hpp:68
absl::StatusOr< std::filesystem::path > GetGraphFilePath(bool binary_graph=true) const
Definition container_impl.hpp:104
absl::Status SetOnStatusCode(const std::function< absl::Status(physiology::StatusValue)> &on_status_code)
Definition container_impl.hpp:148
void AddFrameTimestampToBenchmarkingInfo(const mediapipe::Timestamp &timestamp)
Definition container_impl.hpp:227
Definition background_container.cpp:10