How do I run a plugin written in Python with Python bindings

• Hardware Platform (Jetson / GPU)
Jetson
• DeepStream Version
6.2
• JetPack Version (valid for Jetson only)
5.1.1
• TensorRT Version
TensorRT 8.5.2.2
• Issue Type( questions, new requirements, bugs)
How do I run a plugin written in Python with Python bindings?

Hi,
I am currently trying to create a custom plugin in Python, and as a test, I successfully ran the ‘gst_helloworld’ with GStreamer.

sudo apt install gstreamer1.0-python3-plugin-loader
GST_DEBUG=4 GST_PLUGIN_PATH=$GST_PLUGIN_PATH:$PWD/gst gst-launch-1.0 videotestsrc ! "video/x-raw,format=RGB" ! gst_helloworld ! videoconvert ! autovideosink sync=false

However, when I implement the pipeline in Python and run it, I notice that various methods are not functioning. There are no specific errors, but the print statements within the methods are not being executed.

I would like to handle post-processing after inference using ‘nvinfer’ with a custom plugin in Python. I would appreciate it if you could teach me how to create a plugin that is compatible with Python bindings.


import gi
import sys

# sys.path.append("")

gi.require_version("Gst", "1.0")
from gi.repository import GLib, Gst
from common.is_aarch_64 import is_aarch64
from common.bus_call import bus_call

import pyds
import cv2

import os

os.environ["GST_PLUGIN_PATH"] = ":/home/username/gst_helloworld/gst"


def main():

    Gst.init(None)
    print("Creating Pipeline \n ")
    pipeline = Gst.Pipeline()

    if not pipeline:
        sys.stderr.write(" Unable to create Pipeline \n")

    # Source element for reading from the file
    print("Creating Source \n ")

    source = Gst.ElementFactory.make("v4l2src", "usb-cam-source")
    if not source:
        sys.stderr.write(" Unable to create Source \n")

    caps_v4l2src = Gst.ElementFactory.make("capsfilter", "v4l2src_caps")
    if not caps_v4l2src:
        sys.stderr.write(" Unable to create v4l2src capsfilter \n")

    caps_v4l2src1 = Gst.ElementFactory.make("capsfilter", "v4l2src_caps1")
    if not caps_v4l2src1:
        sys.stderr.write(" Unable to create v4l2src capsfilter1 \n")

    helloworld = Gst.ElementFactory.make("gst_helloworld", "gst_test")
    if not helloworld:
        sys.stderr.write(" Unable to create helloworld \n")

    print("Creating Video Converter \n")

    # videoconvert to make sure a superset of raw formats are supported
    vidconvsrc = Gst.ElementFactory.make("videoconvert", "convertor_src")
    if not vidconvsrc:
        sys.stderr.write(" Unable to create videoconvert \n")

    vidconvsink = Gst.ElementFactory.make("videoconvert", "convertor_src1")
    if not vidconvsrc:
        sys.stderr.write(" Unable to create videoconvert sink\n")

    sink = Gst.ElementFactory.make("autovideosink", "autosink")
    if not sink:
        sys.stderr.write(" Unable to create autosink\n")

    caps_v4l2src.set_property("caps", Gst.Caps.from_string("video/x-raw,  format=YUY2"))
    caps_v4l2src1.set_property("caps", Gst.Caps.from_string("video/x-raw,  format=RGB"))
    source.set_property("device", "/dev/video0")

    print("Adding elements to Pipeline \n")
    pipeline.add(source)
    pipeline.add(caps_v4l2src)
    pipeline.add(vidconvsrc)
    pipeline.add(caps_v4l2src1)
    pipeline.add(helloworld)
    pipeline.add(vidconvsink)
    pipeline.add(sink)

    print("Linking elements in the Pipeline \n")

    source.link(caps_v4l2src)
    caps_v4l2src.link(vidconvsrc)
    vidconvsrc.link(caps_v4l2src1)
    caps_v4l2src1.link(helloworld)
    helloworld.link(vidconvsink)
    vidconvsink.link(sink)

    loop = GLib.MainLoop()
    bus = pipeline.get_bus()
    bus.add_signal_watch()
    bus.connect("message", bus_call, loop)

    # start play back and listen to events
    print("Starting pipeline \n")
    pipeline.set_state(Gst.State.PLAYING)
    try:
        loop.run()
    except:
        pass

    # cleanup
    pipeline.set_state(Gst.State.NULL)


if __name__ == "__main__":
    sys.exit(main())

So strange, I also encountered problem, let me look into this issue

If I call the element in deepstream_test1_app.c, it works fine, but strangely it doesn’t work in deepstream_test_1.py

Thank you for your response.

I am in a similar situation.
For the time being, I set aside the sample plugin and created my own by inheriting from another class.


import gi

gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject
import cv2
import numpy as np


class RGBConverter(Gst.Element):
    GST_PLUGIN_NAME = "test"
    __gstmetadata__ = ("RGB Converter", "Filter/Video", "Converts RGB video frames to specified format", "YourName")

    __gproperties__ = {
        "format": (
            str,
            "format to convert",
            "Set the video format to convert to",
            "BGR",
            GObject.ParamFlags.READWRITE,
        )
    }

    _supported_formats = ["BGR", "GBR"]

    def __init__(self):
        super(RGBConverter, self).__init__()
        self._format = "BGR"

        self.srcpad = Gst.Pad.new_from_template(
            Gst.PadTemplate.new(
                "src",
                Gst.PadDirection.SRC,
                Gst.PadPresence.ALWAYS,
                Gst.Caps.from_string("video/x-raw, format={BGR, GBR}"),
            ),
            "src",
        )
        self.add_pad(self.srcpad)

        self.sinkpad = Gst.Pad.new_from_template(
            Gst.PadTemplate.new(
                "sink", Gst.PadDirection.SINK, Gst.PadPresence.ALWAYS, Gst.Caps.from_string("video/x-raw, format=RGB")
            ),
            "sink",
        )
        self.sinkpad.set_chain_function_full(self.chain, None)
        self.sinkpad.set_event_function_full(self.sink_event, None)
        self.add_pad(self.sinkpad)

    def do_get_property(self, prop):
        if prop.name == "format":
            return self._format
        else:
            raise AttributeError("Unknown property %s" % prop.name)

    def do_set_property(self, prop, value):
        if prop.name == "format":
            self._format = value
        else:
            raise AttributeError("Unknown property %s" % prop.name)

    def sink_event(self, pad, parent, event):
        return self.srcpad.push_event(event)

    def chain(self, pad, parent, buffer):

        result, map_info = buffer.map(Gst.MapFlags.READ | Gst.MapFlags.WRITE)
        if not result:
            return Gst.FlowReturn.ERROR

        frame = np.frombuffer(map_info.data, dtype=np.uint8)

        frame = frame.reshape((480, 640, 3))

        if self._format == "BGR":
            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        elif self._format == "GRAY":

            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

            frame = np.stack((frame,) * 3, axis=-1)

        buffer.fill(0, frame.tobytes())

        buffer.unmap(map_info)
        return self.srcpad.push(buffer)


GObject.type_register(RGBConverter)
__gstelementfactory__ = (RGBConverter.GST_PLUGIN_NAME, Gst.Rank.NONE, RGBConverter)


The plugin that I created by inheriting Gst.Element worked well, but since the class and the arguments are different, I still don’t understand why the helloworld sample doesn’t work with the Python bindings.

If you have any information, I would appreciate it if you could share it.

I don’t know, I guess this is a bug of pygobject. Here is a sample based on your plugin and deepstream_test_1.py

You need to modify the following places:

self.srcpad = Gst.Pad.new_from_template(
            Gst.PadTemplate.new(
                "src",
                Gst.PadDirection.SRC,
                Gst.PadPresence.ALWAYS,
                Gst.Caps.from_string("video/x-raw, format=BGR"),
            ),
            "src",
        )

frame = frame.reshape((1080, 1920, 3))

deepstream_test_1.py

#!/usr/bin/env python3

################################################################################
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 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.
################################################################################

import sys
sys.path.append('../')
import os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import GLib, Gst
from common.platform_info import PlatformInfo
from common.bus_call import bus_call

import pyds

PGIE_CLASS_ID_VEHICLE = 0
PGIE_CLASS_ID_BICYCLE = 1
PGIE_CLASS_ID_PERSON = 2
PGIE_CLASS_ID_ROADSIGN = 3
MUXER_BATCH_TIMEOUT_USEC = 33000

def osd_sink_pad_buffer_probe(pad,info,u_data):
    frame_number=0
    num_rects=0

    gst_buffer = info.get_buffer()
    if not gst_buffer:
        print("Unable to get GstBuffer ")
        return

    # Retrieve batch metadata from the gst_buffer
    # Note that pyds.gst_buffer_get_nvds_batch_meta() expects the
    # C address of gst_buffer as input, which is obtained with hash(gst_buffer)
    batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
    l_frame = batch_meta.frame_meta_list
    while l_frame is not None:
        try:
            # Note that l_frame.data needs a cast to pyds.NvDsFrameMeta
            # The casting is done by pyds.NvDsFrameMeta.cast()
            # The casting also keeps ownership of the underlying memory
            # in the C code, so the Python garbage collector will leave
            # it alone.
            frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
        except StopIteration:
            break

        #Intiallizing object counter with 0.
        obj_counter = {
            PGIE_CLASS_ID_VEHICLE:0,
            PGIE_CLASS_ID_PERSON:0,
            PGIE_CLASS_ID_BICYCLE:0,
            PGIE_CLASS_ID_ROADSIGN:0
        }
        frame_number=frame_meta.frame_num
        num_rects = frame_meta.num_obj_meta
        l_obj=frame_meta.obj_meta_list
        while l_obj is not None:
            try:
                # Casting l_obj.data to pyds.NvDsObjectMeta
                obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
            except StopIteration:
                break
            obj_counter[obj_meta.class_id] += 1
            obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.8) #0.8 is alpha (opacity)
            try: 
                l_obj=l_obj.next
            except StopIteration:
                break

        # Acquiring a display meta object. The memory ownership remains in
        # the C code so downstream plugins can still access it. Otherwise
        # the garbage collector will claim it when this probe function exits.
        display_meta=pyds.nvds_acquire_display_meta_from_pool(batch_meta)
        display_meta.num_labels = 1
        py_nvosd_text_params = display_meta.text_params[0]
        # Setting display text to be shown on screen
        # Note that the pyds module allocates a buffer for the string, and the
        # memory will not be claimed by the garbage collector.
        # Reading the display_text field here will return the C address of the
        # allocated string. Use pyds.get_string() to get the string content.
        py_nvosd_text_params.display_text = "Frame Number={} Number of Objects={} Vehicle_count={} Person_count={}".format(frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON])

        # Now set the offsets where the string should appear
        py_nvosd_text_params.x_offset = 10
        py_nvosd_text_params.y_offset = 12

        # Font , font-color and font-size
        py_nvosd_text_params.font_params.font_name = "Serif"
        py_nvosd_text_params.font_params.font_size = 10
        # set(red, green, blue, alpha); set to White
        py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0)

        # Text background color
        py_nvosd_text_params.set_bg_clr = 1
        # set(red, green, blue, alpha); set to Black
        py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0)
        # Using pyds.get_string() to get display_text as string
        print(pyds.get_string(py_nvosd_text_params.display_text))
        pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
        try:
            l_frame=l_frame.next
        except StopIteration:
            break
			
    return Gst.PadProbeReturn.OK	


def main(args):
    # Check input arguments
    if len(args) != 2:
        sys.stderr.write("usage: %s <media file or uri>\n" % args[0])
        sys.exit(1)

    platform_info = PlatformInfo()
    # Standard GStreamer initialization
    Gst.init(None)

    # Create gstreamer elements
    # Create Pipeline element that will form a connection of other elements
    print("Creating Pipeline \n ")
    pipeline = Gst.Pipeline()

    if not pipeline:
        sys.stderr.write(" Unable to create Pipeline \n")

    # Source element for reading from the file
    print("Creating Source \n ")
    source = Gst.ElementFactory.make("filesrc", "file-source")
    if not source:
        sys.stderr.write(" Unable to create Source \n")

    # Since the data format in the input file is elementary h264 stream,
    # we need a h264parser
    print("Creating H264Parser \n")
    h264parser = Gst.ElementFactory.make("h264parse", "h264-parser")
    if not h264parser:
        sys.stderr.write(" Unable to create h264 parser \n")

    # Use nvdec_h264 for hardware accelerated decode on GPU
    print("Creating Decoder \n")
    decoder = Gst.ElementFactory.make("nvv4l2decoder", "nvv4l2-decoder")
    if not decoder:
        sys.stderr.write(" Unable to create Nvv4l2 Decoder \n")

    # Create nvstreammux instance to form batches from one or more sources.
    streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
    if not streammux:
        sys.stderr.write(" Unable to create NvStreamMux \n")

    # Use nvinfer to run inferencing on decoder's output,
    # behaviour of inferencing is set through config file
    pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
    if not pgie:
        sys.stderr.write(" Unable to create pgie \n")

    # Use convertor to convert from NV12 to RGBA as required by nvosd
    nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor")
    if not nvvidconv:
        sys.stderr.write(" Unable to create nvvidconv \n")

    # Create OSD to draw on the converted RGBA buffer
    nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")
    if not nvosd:
        sys.stderr.write(" Unable to create nvosd \n")

    nvvidconv_cpu = Gst.ElementFactory.make("nvvideoconvert", "convertor_to_cpu")
    if not nvvidconv_cpu:
        sys.stderr.write(" Unable to create nvvidconv_cpu \n")
    nvvidconv_cpu.set_property("compute-hw", 1)

    caps_cpu = Gst.Caps.from_string("video/x-raw, format=RGB")
    filter_cpu = Gst.ElementFactory.make("capsfilter", "filter_cpu")
    if not filter_cpu:
        sys.stderr.write(" Unable to get the caps filter_cpu \n")
    filter_cpu.set_property("caps", caps_cpu)

    gst_helloworld = Gst.ElementFactory.make("test", "gst_helloworld")
    if not gst_helloworld:
        sys.stderr.write(" Unable to create gst_helloworld \n")

    nvvidconv_gpu = Gst.ElementFactory.make("nvvideoconvert", "convertor_to_gpu")
    if not nvvidconv_gpu:
        sys.stderr.write(" Unable to create nvvidconv_gpu \n")
    nvvidconv_gpu.set_property("compute-hw", 1)

    caps_gpu = Gst.Caps.from_string("video/x-raw(memory:NVMM), format=NV12")
    filter_gpu = Gst.ElementFactory.make("capsfilter", "filter_gpu")
    if not filter_gpu:
        sys.stderr.write(" Unable to get the caps filter_gpu \n")
    filter_gpu.set_property("caps", caps_gpu)

    # Finally render the osd output
    if platform_info.is_integrated_gpu():
        print("Creating nv3dsink \n")
        sink = Gst.ElementFactory.make("nv3dsink", "nv3d-sink")
        if not sink:
            sys.stderr.write(" Unable to create nv3dsink \n")
    else:
        if platform_info.is_platform_aarch64():
            print("Creating nv3dsink \n")
            sink = Gst.ElementFactory.make("nv3dsink", "nv3d-sink")
        else:
            print("Creating EGLSink \n")
            #sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
            sink = Gst.ElementFactory.make("nvvideoencfilesinkbin", "nvvideo-renderer")
            sink.set_property("output-file", "output.mp4")
        if not sink:
            sys.stderr.write(" Unable to create egl sink \n")

    print("Playing file %s " %args[1])
    source.set_property('location', args[1])
    if os.environ.get('USE_NEW_NVSTREAMMUX') != 'yes': # Only set these properties if not using new gst-nvstreammux
        streammux.set_property('width', 1920)
        streammux.set_property('height', 1080)
        streammux.set_property('batched-push-timeout', MUXER_BATCH_TIMEOUT_USEC)
    
    streammux.set_property('batch-size', 1)
    pgie.set_property('config-file-path', "dstest1_pgie_config.txt")

    print("Adding elements to Pipeline \n")
    pipeline.add(source)
    pipeline.add(h264parser)
    pipeline.add(decoder)
    pipeline.add(streammux)
    pipeline.add(pgie)
    pipeline.add(nvvidconv)
    pipeline.add(nvosd)
    pipeline.add(nvvidconv_cpu)
    pipeline.add(filter_cpu)
    pipeline.add(gst_helloworld)
    pipeline.add(nvvidconv_gpu)
    pipeline.add(filter_gpu)
    pipeline.add(sink)

    # we link the elements together
    # file-source -> h264-parser -> nvh264-decoder ->
    # nvinfer -> nvvidconv -> nvosd -> video-renderer
    print("Linking elements in the Pipeline \n")
    source.link(h264parser)
    h264parser.link(decoder)

    sinkpad = streammux.request_pad_simple("sink_0")
    if not sinkpad:
        sys.stderr.write(" Unable to get the sink pad of streammux \n")
    srcpad = decoder.get_static_pad("src")
    if not srcpad:
        sys.stderr.write(" Unable to get source pad of decoder \n")
    srcpad.link(sinkpad)
    streammux.link(pgie)
    pgie.link(nvvidconv)
    nvvidconv.link(nvosd)
    nvosd.link(nvvidconv_cpu)
    nvvidconv_cpu.link(filter_cpu)
    filter_cpu.link(gst_helloworld)
    gst_helloworld.link(nvvidconv_gpu)
    nvvidconv_gpu.link(filter_gpu)
    filter_gpu.link(sink)

    # create an event loop and feed gstreamer bus mesages to it
    loop = GLib.MainLoop()
    bus = pipeline.get_bus()
    bus.add_signal_watch()
    bus.connect ("message", bus_call, loop)

    # Lets add probe to get informed of the meta data generated, we add probe to
    # the sink pad of the osd element, since by that time, the buffer would have
    # had got all the metadata.
    osdsinkpad = nvosd.get_static_pad("sink")
    if not osdsinkpad:
        sys.stderr.write(" Unable to get sink pad of nvosd \n")

    osdsinkpad.add_probe(Gst.PadProbeType.BUFFER, osd_sink_pad_buffer_probe, 0)

    # start play back and listen to events
    print("Starting pipeline \n")
    pipeline.set_state(Gst.State.PLAYING)
    try:
        loop.run()
    except:
        pass
    # cleanup
    pipeline.set_state(Gst.State.NULL)

if __name__ == '__main__':
    sys.exit(main(sys.argv))

There is no update from you for a period, assuming this is not an issue anymore. Hence we are closing this topic. If need further support, please open a new one. Thanks

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.