Program Listing for File codecs.hpp

Holoscan v2.1.0

Return to documentation for file (include/holoscan/core/codecs.hpp)

Copy
Copied!
            

/* * SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef HOLOSCAN_CORE_CODECS_HPP #define HOLOSCAN_CORE_CODECS_HPP #include <complex> #include <cstdint> #include <functional> #include <memory> #include <string> #include <type_traits> #include <typeindex> #include <typeinfo> #include <unordered_map> #include <utility> #include <vector> #include "./common.hpp" #include "./endpoint.hpp" #include "./errors.hpp" #include "./expected.hpp" #include "./type_traits.hpp" #include "gxf/core/expected.hpp" #include "gxf/serialization/serialization_buffer.hpp" // Note: Currently the GXF UCX extension transmits using little-endian byte order. All hardware // supported by Holoscan SDK is also little endian. To support big endian platforms, we // would need to update the codecs in this file to properly handle endianness. namespace holoscan { // codec template struct so users can define their own codec class // i.e. following the design of yaml-cpp's YAML::convert template <typename T> struct codec; // Codec type 1: trivial binary types (float, etc.) // // Types that can be serialized by writing binary data where the number // of bytes to be written is based on sizeof(typeT). template <typename typeT> static inline expected<size_t, RuntimeError> serialize_trivial_type(const typeT& value, Endpoint* endpoint) { return endpoint->write_trivial_type(&value); } template <typename typeT> static inline expected<typeT, RuntimeError> deserialize_trivial_type(Endpoint* endpoint) { typeT encoded; auto maybe_value = endpoint->read_trivial_type(&encoded); if (!maybe_value) { return forward_error(maybe_value); } return encoded; } // TODO: currently not handling integer types separately template <typename typeT> struct codec { static expected<size_t, RuntimeError> serialize(const typeT& value, Endpoint* endpoint) { return serialize_trivial_type<typeT>(value, endpoint); } static expected<typeT, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_trivial_type<typeT>(endpoint); } }; // Codec type 3: basic container types // // For vectorT types storing multiple items contiguously in memory // (e.g. std::vector, std::array, std::string). // // It requires that vectorT have methods size(), data(), and operator[] #pragma pack(push, 1) struct ContiguousDataHeader { size_t size; uint8_t bytes_per_element; }; #pragma pack(pop) template <typename vectorT> static inline expected<size_t, RuntimeError> serialize_binary_blob(const vectorT& data, Endpoint* endpoint) { ContiguousDataHeader header; header.size = data.size(); header.bytes_per_element = header.size > 0 ? sizeof(data[0]) : 1; auto size = endpoint->write_trivial_type<ContiguousDataHeader>(&header); if (!size) { return forward_error(size); } auto size2 = endpoint->write(data.data(), header.size * header.bytes_per_element); if (!size2) { return forward_error(size2); } return size.value() + size2.value(); } template <typename vectorT> static inline expected<vectorT, RuntimeError> deserialize_binary_blob(Endpoint* endpoint) { ContiguousDataHeader header; auto header_size = endpoint->read_trivial_type<ContiguousDataHeader>(&header); if (!header_size) { return forward_error(header_size); } vectorT data; data.resize(header.size); auto result = endpoint->read(data.data(), header.size * header.bytes_per_element); if (!result) { return forward_error(result); } return data; } // codec for vector of trivially serializable typeT template <typename typeT> struct codec<std::vector<typeT>> { static expected<size_t, RuntimeError> serialize(const std::vector<typeT>& value, Endpoint* endpoint) { return serialize_binary_blob<std::vector<typeT>>(value, endpoint); } static expected<std::vector<typeT>, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_binary_blob<std::vector<typeT>>(endpoint); } }; // deserialize_array is exactly like deserialize_binary_blob, but without a call to resize() template <typename arrayT> static inline expected<arrayT, RuntimeError> deserialize_array(Endpoint* endpoint) { ContiguousDataHeader header; auto header_size = endpoint->read_trivial_type<ContiguousDataHeader>(&header); if (!header_size) { return forward_error(header_size); } arrayT data; auto result = endpoint->read(data.data(), header.size * header.bytes_per_element); if (!result) { return forward_error(result); } return data; } // codec for array of trivially serializable typeT and size N template <typename typeT, size_t N> struct codec<std::array<typeT, N>> { static expected<size_t, RuntimeError> serialize(const std::array<typeT, N>& value, Endpoint* endpoint) { return serialize_binary_blob<std::array<typeT, N>>(value, endpoint); } static expected<std::array<typeT, N>, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_array<std::array<typeT, N>>(endpoint); } }; // codec for std::string template <> struct codec<std::string> { static expected<size_t, RuntimeError> serialize(const std::string& value, Endpoint* endpoint) { return serialize_binary_blob<std::string>(value, endpoint); } static expected<std::string, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_binary_blob<std::string>(endpoint); } }; // will hold Python cloudpickle strings in this container to differentiate from std::string struct CloudPickleSerializedObject { std::string serialized; }; // codec for CloudPickleSerializedObject template <> struct codec<CloudPickleSerializedObject> { static expected<size_t, RuntimeError> serialize(const CloudPickleSerializedObject& value, Endpoint* endpoint) { return serialize_binary_blob<std::string>(value.serialized, endpoint); } static expected<CloudPickleSerializedObject, RuntimeError> deserialize(Endpoint* endpoint) { auto maybe_string = deserialize_binary_blob<std::string>(endpoint); if (!maybe_string) { return forward_error(maybe_string); } CloudPickleSerializedObject cloudpickle_obj{std::move(maybe_string.value())}; return cloudpickle_obj; } }; // Codec type 4: serialization of std::vector<bool> only // // Performs bit-packing/unpacking to/from uint8_t type for more efficient serialization. // codec of std::vector<bool> template <> struct codec<std::vector<bool>> { static expected<size_t, RuntimeError> serialize(const std::vector<bool>& data, Endpoint* endpoint) { size_t total_bytes = 0; size_t num_bits = data.size(); size_t num_bytes = (num_bits + 7) / 8; // the number of bytes needed to store the bits auto size = endpoint->write_trivial_type<size_t>(&num_bits); if (!size) { return forward_error(size); } total_bytes += size.value(); std::vector<uint8_t> packed_data(num_bytes, 0); // Create a vector to store the packed data for (size_t i = 0; i < num_bits; ++i) { if (data[i]) { packed_data[i / 8] |= (1 << (i % 8)); // Pack the bits into the bytes } } auto result = endpoint->write(packed_data.data(), packed_data.size()); if (!result) { return forward_error(result); } total_bytes += result.value(); return total_bytes; } static expected<std::vector<bool>, RuntimeError> deserialize(Endpoint* endpoint) { size_t num_bits; auto size = endpoint->read_trivial_type<size_t>(&num_bits); if (!size) { return forward_error(size); } size_t num_bytes = (num_bits + 7) / 8; // Calculate the number of bytes needed to store the bits std::vector<uint8_t> packed_data(num_bytes, 0); // Create a vector to store the packed data auto result = endpoint->read(packed_data.data(), packed_data.size()); if (!result) { return forward_error(result); } std::vector<bool> data(num_bits, false); // Create a vector to store the unpacked data for (size_t i = 0; i < num_bits; ++i) { if (packed_data[i / 8] & (1 << (i % 8))) { // Unpack the bits from the bytes data[i] = true; } } return data; } }; // Codec type 5: serialization of nested container types // e.g. std::vector<std::vector<float>>, std::vector<std::string> // template <typename typeT> static inline expected<size_t, RuntimeError> serialize_vector_of_vectors(const typeT& vectors, Endpoint* endpoint) { size_t total_size = 0; // header is just the total number of vectors size_t num_vectors = vectors.size(); auto size = endpoint->write_trivial_type<size_t>(&num_vectors); if (!size) { return forward_error(size); } total_size += size.value(); using vectorT = typename typeT::value_type; // now transmit each individual vector for (const auto& vector : vectors) { size = codec<vectorT>::serialize(vector, endpoint); if (!size) { return forward_error(size); } total_size += size.value(); } return total_size; } template <typename typeT> static inline expected<typeT, RuntimeError> deserialize_vector_of_vectors(Endpoint* endpoint) { size_t num_vectors; auto size = endpoint->read_trivial_type<size_t>(&num_vectors); if (!size) { return forward_error(size); } using vectorT = typename typeT::value_type; std::vector<vectorT> data; data.reserve(num_vectors); for (size_t i = 0; i < num_vectors; i++) { auto vec = codec<vectorT>::deserialize(endpoint); if (!vec) { return forward_error(vec); } data.push_back(vec.value()); } return data; } template <typename typeT> struct codec<std::vector<std::vector<typeT>>> { static expected<size_t, RuntimeError> serialize(const std::vector<std::vector<typeT>>& value, Endpoint* endpoint) { return serialize_vector_of_vectors<std::vector<std::vector<typeT>>>(value, endpoint); } static expected<std::vector<std::vector<typeT>>, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_vector_of_vectors<std::vector<std::vector<typeT>>>(endpoint); } }; template <> struct codec<std::vector<std::string>> { static expected<size_t, RuntimeError> serialize(const std::vector<std::string>& value, Endpoint* endpoint) { return serialize_vector_of_vectors<std::vector<std::string>>(value, endpoint); } static expected<std::vector<std::string>, RuntimeError> deserialize(Endpoint* endpoint) { return deserialize_vector_of_vectors<std::vector<std::string>>(endpoint); } }; // codec for shared_ptr types // Serializes the contents of the shared_ptr. On deserialize, a new shared_ptr to the deserialized // value is returned. template <typename typeT> struct codec<std::shared_ptr<typeT>> { static expected<size_t, RuntimeError> serialize(std::shared_ptr<typeT> value, Endpoint* endpoint) { return codec<typeT>::serialize(*value, endpoint); } static expected<std::shared_ptr<typeT>, RuntimeError> deserialize(Endpoint* endpoint) { auto value = codec<typeT>::deserialize(endpoint); if (!value) { return forward_error(value); } return std::make_shared<typeT>(value.value()); } }; } // namespace holoscan #endif/* HOLOSCAN_CORE_CODECS_HPP */

© Copyright 2022-2024, NVIDIA. Last updated on Jun 17, 2024.