DeepStream Python Apps

repository·master·Indexed 23 days ago

https://github.com/nvidia-ai-iot/deepstream_python_apps

Python bindings and sample applications for the NVIDIA DeepStream SDK. It enables the construction of AI-powered video analytics pipelines using Python by providing a Pybind11-generated interface to access DeepStream MetaData structures and functions. Includes support for x86, Jetson, and SBSA platforms, along with samples for object detection, tracking, Triton inference server integration, and custom user metadata handling.

Tokens
38.8K
Snippets
81
Records
165
Agent score
83%

What's inside deepstream_python_apps

  1. Custom NvDsUserMeta Bindings Guide Overview

    master

    This guide provides instructions for advanced users who need to extend the DeepStream Python bindings by creating custom C/C++ data structures and exposing them to Python.

    The workflow involves:

    1. Defining a custom data structure in C/C++.
    2. Writing custom bindings for that structure.
    3. Attaching the structure to an NvDsUserMeta object as user_meta_data.
    4. Accessing the custom data in downstream Gst Elements within the Python pipeline.

    Prerequisite: Users should be familiar with the basic DeepStream Python bindings before attempting this guide.

  2. How nvmsgconv and nvmsgbroker work in DeepStream

    master

    The deepstream-test4 sample demonstrates a metadata-to-cloud pipeline:

    1. Metadata Creation: The application creates NVDS_META_EVENT_MSG type metadata and attaches it to the buffer. This metadata can include custom objects via the extMsg and extMsgSize fields.
    2. nvmsgconv Plugin: This plugin reads the NVDS_META_EVENT_MSG metadata and generates a JSON payload following the "DeepStream Schema". Static properties for the schema are read from a configuration file (e.g., dstest4_msgconv_config.txt). The resulting payload is attached to the buffer as NVDS_META_PAYLOAD type metadata.
    3. nvmsgbroker Plugin: This plugin extracts the NVDS_META_PAYLOAD metadata and sends it to the backend server using the specified protocol adaptor APIs.

    Custom Metadata: To extend NvDsEventMsgMeta with custom structures, assign your structure pointer to extMsg and set extMsgSize. If your custom object requires complex memory management, you must implement/extend event_msg_meta_copy_func() and event_msg_meta_release_func() in bindschema.cpp.

  3. How MetaData memory management works in pyds

    master
    Because MetaData is shared between Python and C/C++ code paths, the Python garbage collector cannot safely manage the lifetime of shared memory. To ensure objects persist for downstream C/C++ plugins, you must use specific allocation functions provided by the bindings instead of standard Python constructors. Using constructors like NvDsEventMsgMeta() will cause the object to be freed by the garbage collector when it goes out of scope in Python, potentially causing crashes in downstream C/C++ components.
  4. Cast void* pointers to DeepStream metadata types using .cast()

    master

    In DeepStream, metadata is often stored in a GList where the data field is a void* (exposed as a capsule in Python). To use this data as a specific metadata type (like NvDsFrameMeta or NvDsObjectMeta), you must call the .cast() method on the class.

    This method performs the C-style cast and returns the object in a way that Python can interact with, while ensuring the underlying C++ memory ownership remains unchanged.

  5. Optimize probe() callbacks to prevent pipeline delays

    master

    If you see warnings about buffers being dropped or the pipeline being unable to perform in real time, it may be due to heavy processing in probe() callbacks.

    Key considerations:

    • probe() callbacks are synchronous; they hold the buffer (info.get_buffer()) and prevent it from traversing the pipeline until the callback returns.
    • Complex loops or heavy logic inside a Python probe() callback can introduce significant software-induced delays.
  6. Understand DeepStream Python Bindings

    master
    DeepStream pipelines are constructed using Gst Python (the GStreamer framework's Python bindings). To access DeepStream-specific MetaData, this repository provides Python bindings generated using Pybind11. These bindings allow a Python interface to access MetaData structures and functions.
  7. Handle C strings and memory allocation for properties

    master

    When binding string fields (like sensorStr in NvDsEventMsgMeta) that require deep copying between Python and C, use a macro to define getter and setter logic. This ensures that when a Python string is assigned to a C struct, memory is correctly allocated on the C side.

    The STRING_PROPERTY Macro Pattern: This macro defines a getter that returns the C address and a setter that uses calloc to allocate memory and str.copy to move the content.

    Binding a string property:

    py::class_<NvDsEventMsgMeta>(m, "NvDsEventMsgMeta", pydsdoc::metaschema::EventmsgDoc::descr)
        .def_property("sensorStr", STRING_PROPERTY(NvDsEventMsgMeta, sensorStr))

    Python Usage Pattern: To safely copy a Python string into a C-allocated struct field, use a helper like pyds.get_string() to resolve the address before assignment:

    test_string = 'test message ' + str(frame_number)
    data = pyds.alloc_custom_struct(user_meta)
    data.message = test_string # python string object at first
    data.message = pyds.get_string(data.message)
    #define STRING_PROPERTY(TYPE, FIELD)                               \n        [](const TYPE &self)->size_t {                             \n            return (size_t)self.FIELD;                             \n        },                                                         \n        [](TYPE &self, std::string str) {                          \n            int strSize = str.size();                              \n            self.FIELD = (char*)calloc(strSize + 1, sizeof(char)); \n            str.copy(self.FIELD, strSize);                         \n        },                                                         \n        py::return_value_policy::reference 
    
    // Binding example
    py::class_<NvDsEventMsgMeta>(m, "NvDsEventMsgMeta",
                                    pydsdoc::metaschema::EventmsgDoc::descr)
        .def_property("sensorStr",
                        STRING_PROPERTY(NvDsEventMsgMeta, sensorStr))
    
    // Python usage
    test_string = 'test message ' + str(frame_number)
    data = pyds.alloc_custom_struct(user_meta)
    data.message = test_string
    data.message = pyds.get_string(data.message)
  8. Understand the Python bindings directory structure

    master

    The Python bindings project uses a specific directory structure to organize declarations, definitions, and documentation:

    • docstrings/: Contains header files where docstrings for the Python API are declared and defined.
    • include/bind/: Contains header files that declare the binding functions (e.g., bind{header_name}.hpp). These are separated by their corresponding C API header files found in the DeepStream root.
    • include/nvds/: Contains additional C header files used by the bindings.
    • src/: Contains the C++ source files where binding functions are defined, along with utilities and the pybind11 module definition.
  9. Send custom GstEvents from Python to downstream elements

    master

    Directly type-casting certain GStreamer data types (like GstEvent *) in Python can be difficult. To circumvent this, implement a binding in PyDS that accepts a GstElement (passed as a size_t from Python) and performs the event creation and sending within the C++ binding layer.

    For example, to send a stream reset event using gst_nvevent_new_stream_reset(source_id), create a binding that wraps gst_element_send_event and the specific NVEVENT creation function. This allows the Python application to receive a simple boolean indicating if the event was handled successfully.

    m.def("gst_element_send_nvevent_new_stream_reset",
            [](size_t gst_element, int source_id) {
                auto *element = reinterpret_cast<GstElement *>(gst_element);
                return gst_element_send_event(element, gst_nvevent_new_stream_reset(source_id));
            },
            pydsdoc::methodsDoc::gst_element_send_nvevent_new_stream_reset);
  10. How to write custom bindings for custom data structures

    master

    When you need to attach data structures that are not part of the standard C API headers to DeepStream buffers, you can use NvDsUserMeta. To make these structures accessible in Python, you must write custom Pybind11 bindings.

    This process involves:

    1. Defining the C++ structure: Create a header file (e.g., custom_data.hpp) defining your struct.
    2. Creating a bindings header: Create a header (e.g., bindcustom.hpp) following the bind{header_name}.hpp convention to declare the binding function.
    3. Implementing the bindings: In a .cpp file, use Pybind11 to map the struct members and implement memory management functions.
    4. Memory Management: You must provide a copy_func and a release_func to NvDsUserMeta to handle deep copying and memory deallocation of your custom data.
    5. Integration: Add the new files to CMakeLists.txt and register the submodule in pyds.cpp.
  11. Bind a C-style struct using pybind11

    master

    To expose a C-style struct to Python, use the pybind11::class_ method. You must provide the module, the name of the struct, and a docstring.

    Step 1: Declare the binding function

    Create a header (e.g., include/bind/bindnvdsmeta.hpp) to declare the submodule binding function:

    #include "pyds.hpp"
    #include "../docstrings/pydocumentation.h"
    
    namespace py = pybind11;
    
    namespace pydeepstream {
        void bindnvdsmeta(py::module &m);
    }

    Step 2: Define the binding function

    In the corresponding source file (e.g., src/bindnvdsmeta.cpp), implement the function:

    #include "bind_string_property_definitions.h"
    #include "bindnvdsmeta.hpp"
    
    namespace py = pybind11;
    
    namespace pydeepstream {
    
    void bindnvdsmeta(py::module &m) {
        // Binding code goes here
    }
    }

    Step 3: Bind the struct and its members

    Use py::class_ to bind the struct. To expose data members (Plain Old Data types), use .def_readwrite().

    void bindnvdsmeta(py::module &m) {
        py::class_<NvDsObjectMeta>(m, "NvDsObjectMeta",
                                    pydsdoc::nvmeta::ObjectMetaDoc::descr)
            .def(py::init<>()) // Constructor wrapper
            .def_readwrite("base_meta", &NvDsObjectMeta::base_meta);
    }

    Note: If you bind a member that is itself a struct (like base_meta), you will only be able to access its internal members in Python once that specific struct type is also bound.

    void bindnvdsmeta(py::module &m) {
        py::class_<NvDsObjectMeta>(m, "NvDsObjectMeta",
                                    pydsdoc::nvmeta::ObjectMetaDoc::descr)
            .def(py::init<>()); // Constructor wrapper, empty for struct
                                    .def_readwrite("base_meta", &NvDsObjectMeta::base_meta);
    }
  12. Install the pyds Python bindings

    master

    The pyds.so module (DeepStream SDK Python bindings) is not automatically installed by the SDK. To install it into the standard path, navigate to your DeepStream library directory and run the provided setup.py.

    cd /opt/nvidia/deepstream/deepstream/lib
    python3 setup.py install