SmartSpectra C++ SDK
Measure human vitals from video with SmartSpectra C++ SDK.
Loading...
Searching...
No Matches
initialization_impl.hpp
1
2// initialization_impl.h
3// Created by Greg on 2/12/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#include <string>
24#include <regex>
25// === third-party includes (if any) ===
26#include <physiology/modules/graph_tweaks.h>
27#include <physiology/graph/stream_and_packet_names.h>
28#include <physiology/modules/geometry.hpp>
29#include <mediapipe/framework/port/logging.h>
30#include <mediapipe/framework/port/file_helpers.h>
31#include <mediapipe/framework/port/status_macros.h>
32#include <mediapipe/framework/port/parse_text_proto.h>
33#include <mediapipe/framework/calculator.pb.h>
34#include <absl/status/statusor.h>
35#include <opencv2/highgui.hpp>
36// === local includes (if any) ===
37#include "initialization.hpp"
38#include "configuration.hpp"
39#ifdef ENABLE_CUSTOM_SERVER
40#include "custom_rest_settings.hpp"
41#endif
42// @formatter:off
43#ifdef __linux__
44#include <smartspectra/video_source/camera/camera_v4l2.hpp>
45namespace pcam_v4l2 = presage::camera::v4l2;
46#endif
47// @formatter:on
48#include <smartspectra/video_source/camera/camera_opencv.hpp>
49
50namespace presage::smartspectra::container::initialization {
51
52namespace pcam = presage::camera;
53namespace pcam_cv = presage::camera::opencv;
54namespace pe = physiology::edge;
55
56static void AddGeneralSidePackets(
57 std::map<std::string, mediapipe::Packet>& input_side_packets,
58 const settings::GeneralSettings& settings
59) {
60 if (settings.enable_phasic_bp.has_value()) {
61 input_side_packets[pe::graph::input_side_packets::kEnablePhasicBp] =
62 mediapipe::MakePacket<bool>(settings.enable_phasic_bp.value());
63 }
64 if (settings.enable_eda.has_value()) {
65 input_side_packets[pe::graph::input_side_packets::kEnableEda] =
66 mediapipe::MakePacket<bool>(settings.enable_eda.value());
67 }
68 input_side_packets[pe::graph::input_side_packets::kEnableDenseFaceMeshPoints] =
69 mediapipe::MakePacket<bool>(settings.enable_dense_facemesh_points);
70 input_side_packets[pe::graph::input_side_packets::kEnableEdgeMetrics] =
71 mediapipe::MakePacket<bool>(settings.enable_edge_metrics);
72 input_side_packets[pe::graph::input_side_packets::kModelDirectory] =
73 mediapipe::MakePacket<std::string>(PHYSIOLOGY_EDGE_MODEL_DIRECTORY);
74 if (settings.use_full_range_face_detection.has_value()){
75 input_side_packets[pe::graph::input_side_packets::kUseFullRangeFaceDetection] =
76 mediapipe::MakePacket<bool>(settings.use_full_range_face_detection.value());
77 }
78 if (settings.use_full_pose_landmarks.has_value()) {
79 input_side_packets[pe::graph::input_side_packets::kUseFullPoseLandmarks] =
80 mediapipe::MakePacket<bool>(settings.use_full_pose_landmarks.value());
81 }
82 if (settings.enable_pose_landmark_segmentation.has_value()) {
83 input_side_packets[pe::graph::input_side_packets::kEnablePoseLandmarkSegmentation] =
84 mediapipe::MakePacket<bool>(settings.enable_pose_landmark_segmentation.value());
85 }
86 if (settings.enable_micromotion.has_value()){
87 input_side_packets[pe::graph::input_side_packets::kEnableMicromotion] =
88 mediapipe::MakePacket<bool>(settings.enable_micromotion.value());
89 }
90 input_side_packets[pe::graph::input_side_packets::kLogTransferTimingInfo] =
91 mediapipe::MakePacket<bool>(settings.log_transfer_timing_info);
92 if (settings.video_output_directory.has_value()) {
93 input_side_packets[pe::graph::input_side_packets::kVideoOutputDirectory] =
94 mediapipe::MakePacket<std::string>(settings.video_output_directory.value());
95 }
96}
97
98template<settings::OperationMode TOperationMode, settings::IntegrationMode TIntegrationMode, bool TLog>
99inline absl::StatusOr<mediapipe::CalculatorGraphConfig> InitializeGraphConfig(
100 const std::string& graph_file_path,
101 const settings::Settings<TOperationMode, TIntegrationMode>& settings,
102 bool binary_graph
103) {
104 std::string calculator_graph_config_contents;
105 MP_RETURN_IF_ERROR(
106 mediapipe::file::GetContents(
107 graph_file_path,
108 &calculator_graph_config_contents,
109 /*read_as_binary=*/binary_graph
110 )
111 );
112 if (TLog) {
113 LOG(INFO) << "Scaling input in graph: " << (settings.scale_input ? "true" : "false");
114 }
115 mediapipe::CalculatorGraphConfig config;
116 if (binary_graph) {
117 RET_CHECK(config.ParseFromArray(calculator_graph_config_contents.c_str(),
118 calculator_graph_config_contents.length()));
119 } else {
120 if (!settings.scale_input) {
121 // get rid of input scaling
122 presage::graph_tweaks::SetOutputWidthAndHeightToZeroIfPresent(calculator_graph_config_contents);
123 }
124 if (TLog) {
125 if (settings.print_graph_contents) {
126 LOG(INFO) << "Get calculator graph config contents: " << calculator_graph_config_contents;
127 }
128 }
129 config = mediapipe::ParseTextProtoOrDie<mediapipe::CalculatorGraphConfig>(calculator_graph_config_contents);
130 }
131
132 config.add_executor();
133
134 return config;
135}
136
137
138template<settings::IntegrationMode TIntegrationMode>
139inline absl::Status SupplyGraphIntegrationSettings(
140 std::map<std::string, mediapipe::Packet>& input_side_packets,
141 const settings::IntegrationSettings<TIntegrationMode>& integration_settings
142);
143
144template<>
145inline absl::Status SupplyGraphIntegrationSettings(
146 std::map<std::string, mediapipe::Packet>& input_side_packets,
147 const settings::IntegrationSettings<settings::IntegrationMode::Grpc>& integration_settings
148) {
149 input_side_packets[pe::graph::input_side_packets::grpc::kGrpcCorePortNumber] = mediapipe::MakePacket<uint16_t>(
150 integration_settings.port_number);
151 return absl::OkStatus();
152}
153
154template<>
155inline absl::Status SupplyGraphIntegrationSettings(
156 std::map<std::string, mediapipe::Packet>& input_side_packets,
157 const settings::IntegrationSettings<settings::IntegrationMode::Rest>& integration_settings
158) {
159 input_side_packets[pe::graph::input_side_packets::kApiKey] = mediapipe::MakePacket<std::string>(integration_settings.api_key);
160
161#ifdef ENABLE_CUSTOM_SERVER
162 // Apply custom server configuration if enabled
163 if (integration_settings.continuous_server_url.has_value()) {
164 settings::CustomServerConfiguration config;
165 config.continuous_server_url = integration_settings.continuous_server_url;
166
167 MP_RETURN_IF_ERROR(settings::ApplyCustomServerConfig(config));
168 }
169#endif
170
171 return absl::OkStatus();
172}
173
174template<typename TSettings>
175inline absl::Status InitializeGraphWithConfig(
176 mediapipe::CalculatorGraph& graph,
177 const mediapipe::CalculatorGraphConfig& config,
178 const TSettings& settings
179);
180
181template<settings::IntegrationMode TIntegrationMode>
182inline absl::Status InitializeGraphWithConfig(
183 mediapipe::CalculatorGraph& graph,
184 const mediapipe::CalculatorGraphConfig& config,
185 const settings::Settings<settings::OperationMode::Continuous, TIntegrationMode>& settings
186) {
187 std::map<std::string, mediapipe::Packet> input_side_packets;
188 AddGeneralSidePackets(input_side_packets, settings);
189 input_side_packets[pe::graph::input_side_packets::continuous::kPreprocessedDataBufferDuration] =
190 mediapipe::MakePacket<double>(settings.continuous.preprocessed_data_buffer_duration_s);
191
192 const double lower_buffer_duration_threshold_s = 0.2;
193 if (settings.continuous.preprocessed_data_buffer_duration_s < lower_buffer_duration_threshold_s &&
194 lower_buffer_duration_threshold_s-settings.continuous.preprocessed_data_buffer_duration_s > 1e-6) {
195 return absl::InvalidArgumentError(
196 "The preprocessed data buffer duration is set to less than "
197 + std::to_string(lower_buffer_duration_threshold_s) +
198 " seconds. This currently may cause Physiology Core to fail in producing metrics.");
199 }
200 MP_RETURN_IF_ERROR(SupplyGraphIntegrationSettings(input_side_packets, settings.integration));
201 MP_RETURN_IF_ERROR(graph.Initialize(config, input_side_packets));
202 return absl::OkStatus();
203}
204
205template<settings::IntegrationMode TIntegrationMode>
206inline absl::Status InitializeGraphWithConfig(
207 mediapipe::CalculatorGraph& graph,
208 const mediapipe::CalculatorGraphConfig& config,
209 const settings::Settings<settings::OperationMode::Spot, TIntegrationMode>& settings
210) {
211 std::map<std::string, mediapipe::Packet> input_side_packets;
212 input_side_packets[pe::graph::input_side_packets::spot::kSpotDurationS] =
213 mediapipe::MakePacket<double>(settings.spot.spot_duration_s);
214 AddGeneralSidePackets(input_side_packets, settings);
215 MP_RETURN_IF_ERROR(SupplyGraphIntegrationSettings(input_side_packets, settings.integration));
216 MP_RETURN_IF_ERROR(graph.Initialize(config, input_side_packets));
217 return absl::OkStatus();
218}
219
220template<
221 platform_independence::DeviceType TDeviceType,
222 settings::OperationMode TOperationMode,
223 settings::IntegrationMode TIntegrationMode,
224 bool TLog
225>
226absl::Status InitializeGraph(
227 mediapipe::CalculatorGraph& graph,
228 const std::string& graph_file_path,
229 const settings::Settings<TOperationMode, TIntegrationMode>& settings,
230 bool binary_graph
231) {
232 if (TLog) {
233 LOG(INFO) << "Initialize the calculator graph.";
234 LOG(INFO) << "OpenGl buffers used in graph: "
235 << (TDeviceType == platform_independence::DeviceType::OpenGl ? "true" : "false");
236 }
237 auto status_or_config =
238 InitializeGraphConfig<TOperationMode, TIntegrationMode, TLog>(graph_file_path, settings, binary_graph);
239
240 if (!status_or_config.ok()) {
241 return status_or_config.status();
242 }
243 mediapipe::CalculatorGraphConfig config = status_or_config.value();
244 return InitializeGraphWithConfig(graph, config, settings);
245}
246
247template<platform_independence::DeviceType TDeviceType>
248inline absl::Status InitializeComputingDevice_Internal(
249 mediapipe::CalculatorGraph& graph,
250 platform_independence::DeviceContext<TDeviceType>& device_context
251);
252
253template<>
254inline absl::Status InitializeComputingDevice_Internal<platform_independence::DeviceType::Cpu>(
255 mediapipe::CalculatorGraph& graph,
256 platform_independence::DeviceContext<platform_independence::DeviceType::Cpu>& device_context
257) {
258 return absl::OkStatus();
259}
260
261#ifdef WITH_OPENGL
262
263template<>
264inline absl::Status InitializeComputingDevice_Internal<platform_independence::DeviceType::OpenGl>(
265 mediapipe::CalculatorGraph& graph,
266 platform_independence::DeviceContext<platform_independence::DeviceType::OpenGl>& device_context
267) {
268 MP_ASSIGN_OR_RETURN(auto gpu_resources, mediapipe::GpuResources::Create());
269 MP_RETURN_IF_ERROR(graph.SetGpuResources(std::move(gpu_resources)));
270 device_context.gpu_helper.InitializeForTest(graph.GetGpuResources().get());
271 return absl::OkStatus();
272}
273
274#endif
275
276template<platform_independence::DeviceType TDeviceType, bool TLog>
277absl::Status InitializeComputingDevice(
278 mediapipe::CalculatorGraph& graph,
279 platform_independence::DeviceContext<TDeviceType>& device_context
280) {
281 if (TLog) {
282 LOG(INFO) << "Initialize the compute device.";
283 }
284 return InitializeComputingDevice_Internal(graph, device_context);
285}
286
287static std::string SubstituteVideoSinkTemplate(
288 const std::string& template_string,
289 const cv::Size& output_resolution,
290 const float output_fps
291) {
292 std::string result = template_string;
293
294 // Create regular expressions for the placeholders
295 std::regex width_regex("%width%");
296 std::regex height_regex("%height%");
297 std::regex fps_regex("%fps%");
298
299 // Perform the substitutions
300 result = std::regex_replace(result, width_regex, std::to_string(output_resolution.width));
301 result = std::regex_replace(result, height_regex, std::to_string(output_resolution.height));
302 result = std::regex_replace(result, fps_regex, std::to_string(output_fps));
303
304 return result;
305}
306
307template<platform_independence::DeviceType TDeviceType, bool TLog>
308absl::Status InitializeVideoSink(
309 cv::VideoWriter& stream_writer,
310 const cv::Size& input_resolution,
311 const std::string& destination,
312 const float output_fps,
313 settings::VideoSinkMode video_sink_mode
314) {
315 if (!destination.empty() && video_sink_mode != settings::VideoSinkMode::Unknown_EnumEnd) {
316 if (TLog) {
317 LOG(INFO) << "Initialize the video sink.";
318 }
319 cv::Size output_resolution = input_resolution;
320 switch (video_sink_mode) {
321 case settings::VideoSinkMode::MJPG:
322 stream_writer.open(
323 destination, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),
324 output_fps, output_resolution, true
325 );
326 break;
327 case settings::VideoSinkMode::GSTREAMER_TEMPLATED:
328 stream_writer.open(
329 SubstituteVideoSinkTemplate(destination, output_resolution, output_fps),
330 cv::CAP_GSTREAMER, 0, output_fps, output_resolution, true
331 );
332 break;
333 default:
334 return absl::InvalidArgumentError(absl::StrCat("Unsupported video sink mode with int code ",
335 static_cast<int>(video_sink_mode)));
336 }
337 RET_CHECK(stream_writer.isOpened());
338 }
339 return absl::OkStatus();
340}
341
342template<bool TLog>
343absl::Status InitializeGui(const settings::GeneralSettings& settings, const std::string& window_name) {
344 if (TLog) {
345 LOG(INFO) << "Initialize the graphical user interface.";
346 }
347 // only display when (1) live, OR (2) prerecorded and !headless. Only permit headless when prerecorded
348 if (!settings.headless) {
349 cv::namedWindow(window_name, /*flags=WINDOW_AUTOSIZE*/ 1);
350 }
351 return absl::OkStatus();
352}
353
354} // presage::smartspectra::container::initialization