diff --git a/Makefile b/Makefile index e5d2b7ab..a369cec6 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ publish-local-extra: build-extra docker tag savant-deepstream$(PLATFORM_SUFFIX)-extra ghcr.io/insight-platform/savant-deepstream$(PLATFORM_SUFFIX)-extra build: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --target base \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ @@ -38,7 +38,7 @@ build: -t savant-deepstream$(PLATFORM_SUFFIX) . build-adapters-deepstream: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --target adapters \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ @@ -47,14 +47,14 @@ build-adapters-deepstream: -t savant-adapters-deepstream$(PLATFORM_SUFFIX) . build-adapters-gstreamer: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --build-arg SAVANT_RS_VERSION=$(SAVANT_RS_VERSION) \ -f docker/Dockerfile.adapters-gstreamer \ -t savant-adapters-gstreamer$(PLATFORM_SUFFIX) . build-adapters-py: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --build-arg SAVANT_RS_VERSION=$(SAVANT_RS_VERSION) \ -f docker/Dockerfile.adapters-py \ @@ -63,7 +63,7 @@ build-adapters-py: build-adapters-all: build-adapters-py build-adapters-gstreamer build-adapters-deepstream build-extra-packages: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --target extra$(PLATFORM_SUFFIX)-builder \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ @@ -78,7 +78,7 @@ build-extra-packages: savant-extra$(PLATFORM_SUFFIX)-builder build-extra: - docker buildx build \ + docker build \ --platform $(PLATFORM) \ --target deepstream$(PLATFORM_SUFFIX)-extra \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ @@ -89,7 +89,7 @@ build-extra: build-opencv: build-opencv-amd64 build-opencv-arm64 build-opencv-amd64: - docker buildx build \ + docker buildx build --load \ --platform linux/amd64 \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ -f docker/Dockerfile.deepstream-opencv \ @@ -100,7 +100,7 @@ build-opencv-amd64: savant-opencv-builder build-opencv-arm64: - docker buildx build \ + docker buildx build --load \ --platform linux/arm64 \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ -f docker/Dockerfile.deepstream-opencv \ @@ -112,7 +112,7 @@ build-opencv-arm64: build-docs: rm -rf docs/source/reference/api/generated - docker buildx build \ + docker build \ --target docs \ --build-arg DEEPSTREAM_VERSION=$(DEEPSTREAM_VERSION) \ --build-arg SAVANT_RS_VERSION=$(SAVANT_RS_VERSION) \ @@ -164,7 +164,7 @@ run-dev: -v `pwd`/var:$(PROJECT_PATH)/var \ -v /tmp/zmq-sockets:/tmp/zmq-sockets \ --entrypoint /bin/bash \ - savant-deepstream$(PLATFORM_SUFFIX)-extra + savant-deepstream$(PLATFORM_SUFFIX) clean: find . -type d -name __pycache__ -exec rm -rf {} \+ diff --git a/docker/Dockerfile.deepstream b/docker/Dockerfile.deepstream index 12245d7c..f09c8a34 100644 --- a/docker/Dockerfile.deepstream +++ b/docker/Dockerfile.deepstream @@ -1,4 +1,4 @@ -ARG DEEPSTREAM_VERSION +ARG DEEPSTREAM_VERSION=7.0 ARG DEEPSTREAM_DEVEL_IMAGE=$DEEPSTREAM_VERSION-triton-multiarch ARG DEEPSTREAM_BASE_IMAGE=$DEEPSTREAM_VERSION-samples-multiarch FROM nvcr.io/nvidia/deepstream:$DEEPSTREAM_DEVEL_IMAGE AS builder diff --git a/docs/source/advanced_topics/0_dead_stream_eviction.rst b/docs/source/advanced_topics/0_dead_stream_eviction.rst index 61199fbf..b5fd7c1c 100644 --- a/docs/source/advanced_topics/0_dead_stream_eviction.rst +++ b/docs/source/advanced_topics/0_dead_stream_eviction.rst @@ -22,6 +22,6 @@ Take a look at the ``default.yml`` for details: .. literalinclude:: ../../../savant/config/default.yml :language: YAML - :lines: 195-228 + :lines: 193-226 You can override only required parameters in your module YAML configuration file. Also, take a look at corresponding environment variables helping to configure the parameters without specifying them in the module config. diff --git a/docs/source/advanced_topics/14_jetson_dla.rst b/docs/source/advanced_topics/14_jetson_dla.rst index 19fe836d..29d05ebf 100644 --- a/docs/source/advanced_topics/14_jetson_dla.rst +++ b/docs/source/advanced_topics/14_jetson_dla.rst @@ -10,14 +10,12 @@ To use DLA, you need to specify the target device in the YAML `config`. Use the .. code-block:: yaml - element: nvinfer@detector - name: LPDNet + name: yolo model: - remote: - url: https://api.ngc.nvidia.com/v2/models/nvidia/tao/lpdnet/versions/pruned_v2.2/zip format: onnx - model_file: LPDNet_usa_pruned_tao5.onnx + model_file: yolo.onnx precision: int8 - int8_calib_file: usa_cal_8.6.1.bin + int8_calib_file: calib.bin batch_size: 16 enable_dla: true # allocate this model on DLA use_dla_core: 1 # use DLA core 1 diff --git a/docs/source/savant_101/12_module_definition.rst b/docs/source/savant_101/12_module_definition.rst index a108b5b3..2119f379 100644 --- a/docs/source/savant_101/12_module_definition.rst +++ b/docs/source/savant_101/12_module_definition.rst @@ -18,7 +18,7 @@ Modules are executed within specially prepared docker containers. If a module do docker pull ghcr.io/insight-platform/savant-deepstream:latest -* Deepstream 6.4 capable Nvidia edge devices (Jetson AGX Orin, Orin NX, Orin Nano) +* Deepstream 7.0 capable Nvidia edge devices (Jetson AGX Orin, Orin NX, Orin Nano) .. code-block:: bash @@ -54,7 +54,7 @@ The following parameters are defined for a Savant module by default: .. literalinclude:: ../../../savant/config/default.yml :language: YAML - :lines: 1-195 + :lines: 1-193 .. note:: diff --git a/docs/source/savant_101/12_pipeline.rst b/docs/source/savant_101/12_pipeline.rst index 8401debc..6502bf32 100644 --- a/docs/source/savant_101/12_pipeline.rst +++ b/docs/source/savant_101/12_pipeline.rst @@ -17,7 +17,7 @@ Default module configuration file already defines the :py:attr:`~savant.config.s .. literalinclude:: ../../../savant/config/default.yml :language: YAML - :lines: 195- + :lines: 193- It is possible to redefine them, but the encouraged operation mode assumes the use of ZeroMQ source and sink. diff --git a/docs/source/savantdev/0_configure_doc_env.rst b/docs/source/savantdev/0_configure_doc_env.rst index 4b467ec7..2e24c88e 100644 --- a/docs/source/savantdev/0_configure_doc_env.rst +++ b/docs/source/savantdev/0_configure_doc_env.rst @@ -15,8 +15,8 @@ We recommend pulling Nvidia docker containers separately because the Nvidia regi .. code-block:: bash - docker pull nvcr.io/nvidia/deepstream:6.4-samples-multiarch - docker pull nvcr.io/nvidia/deepstream:6.4-triton-multiarch + docker pull nvcr.io/nvidia/deepstream:7.0-samples-multiarch + docker pull nvcr.io/nvidia/deepstream:7.0-triton-multiarch Build The Dockerized Sphinx Runtime ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/samples/conditional_video_processing/demo.yml b/samples/conditional_video_processing/demo.yml index c8743df8..416725eb 100644 --- a/samples/conditional_video_processing/demo.yml +++ b/samples/conditional_video_processing/demo.yml @@ -52,9 +52,6 @@ pipeline: checksum_url: s3://savant-data/models/peoplenet/peoplenet_pruned_v2.0.md5 parameters: endpoint: https://eu-central-1.linodeobjects.com - # or get the model directly from NGC API - # peoplenet v2.0 - # url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplenet/versions/pruned_v2.0/zip" # model file name, without location model_file: resnet34_peoplenet_pruned.etlt # v2.0 Accuracy: 84.3 Size 20.9 MB diff --git a/samples/kafka_redis_adapter/demo.yml b/samples/kafka_redis_adapter/demo.yml index 9f4ce782..68996e31 100644 --- a/samples/kafka_redis_adapter/demo.yml +++ b/samples/kafka_redis_adapter/demo.yml @@ -34,9 +34,6 @@ pipeline: checksum_url: s3://savant-data/models/peoplenet/peoplenet_pruned_v2.0.md5 parameters: endpoint: https://eu-central-1.linodeobjects.com - # or get the model directly from NGC API - # peoplenet v2.0 - # url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplenet/versions/pruned_v2.0/zip" # model file name, without location model_file: resnet34_peoplenet_pruned.etlt # v2.0 Accuracy: 84.3 Size 20.9 MB diff --git a/samples/license_plate_recognition/README.md b/samples/license_plate_recognition/README.md index fbd21341..3a06c876 100644 --- a/samples/license_plate_recognition/README.md +++ b/samples/license_plate_recognition/README.md @@ -8,7 +8,7 @@ Preview: Tested on platforms: -- Nvidia Turing +- Nvidia Turing, Ampere - Nvidia Jetson Orin family Demonstrated adapters: diff --git a/samples/license_plate_recognition/module.yml b/samples/license_plate_recognition/module.yml index 4d5cd769..bb499416 100644 --- a/samples/license_plate_recognition/module.yml +++ b/samples/license_plate_recognition/module.yml @@ -73,11 +73,12 @@ pipeline: name: LPDNet model: remote: - url: https://api.ngc.nvidia.com/v2/models/nvidia/tao/lpdnet/versions/pruned_v2.2/zip + url: s3://savant-data/models/lpdnet/lpdnet_v2.2.zip + checksum_url: s3://savant-data/models/lpdnet/lpdnet_v2.2.md5 + parameters: + endpoint: https://eu-central-1.linodeobjects.com format: onnx model_file: LPDNet_usa_pruned_tao5.onnx - precision: int8 - int8_calib_file: usa_cal_8.6.1.bin batch_size: 16 input: object: yolov8n.Car diff --git a/samples/multiple_rtsp/demo.yml b/samples/multiple_rtsp/demo.yml index 9c9a9402..e94f92a2 100644 --- a/samples/multiple_rtsp/demo.yml +++ b/samples/multiple_rtsp/demo.yml @@ -34,9 +34,6 @@ pipeline: checksum_url: s3://savant-data/models/peoplenet/peoplenet_pruned_v2.0.md5 parameters: endpoint: https://eu-central-1.linodeobjects.com - # or get the model directly from NGC API - # peoplenet v2.0 - # url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplenet/versions/pruned_v2.0/zip" # model file name, without location model_file: resnet34_peoplenet_pruned.etlt # v2.0 Accuracy: 84.3 Size 20.9 MB diff --git a/samples/nvidia_car_classification/flavors/README.md b/samples/nvidia_car_classification/flavors/README.md index 6bb01f7b..6dd4323f 100644 --- a/samples/nvidia_car_classification/flavors/README.md +++ b/samples/nvidia_car_classification/flavors/README.md @@ -100,16 +100,3 @@ Run this sample config with ```bash python scripts/run_module.py samples/nvidia_car_classification/module.yml ``` - -## Module configuration using etlt models - -It is also simple enough to swap the models in the pipeline for other ones. For example, -replace the sample models from the DeepStream SDK with TAO models available from [Nvidia NGC](https://catalog.ngc.nvidia.com/). - -Config file that uses etlt models for this sample is provided in the [module-etlt-config.yml](module-etlt-config.yml). - -Run this sample config with - -```bash -python scripts/run_module.py samples/nvidia_car_classification/flavors/module-etlt-config.yml -``` diff --git a/samples/nvidia_car_classification/flavors/module-etlt-config.yml b/samples/nvidia_car_classification/flavors/module-etlt-config.yml deleted file mode 100644 index 2303a938..00000000 --- a/samples/nvidia_car_classification/flavors/module-etlt-config.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: ${oc.env:MODULE_NAME, 'nvidia_car_classification'} - -parameters: - output_frame: - codec: ${oc.env:CODEC, 'raw-rgba'} - draw_func: - module: savant.deepstream.drawfunc - class_name: NvDsDrawFunc - -pipeline: - elements: - # detector - - element: nvinfer@detector - name: DashCamNet - model: - remote: - url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/dashcamnet/versions/pruned_v1.0.4/zip" - model_file: resnet18_dashcamnet_pruned.onnx - # label_file: labels.txt - precision: int8 - int8_calib_file: resnet18_dashcamnet_pruned_int8.txt - batch_size: 1 - input: - layer_name: input_1 - shape: [3, 544, 960] - scale_factor: 0.00392156862745098 - output: - num_detected_classes: 4 - layer_names: [output_cov/Sigmoid, output_bbox/BiasAdd] - objects: - - class_id: 0 - label: Car -# - class_id: 1 -# label: Bicycle -# - class_id: 2 -# label: Person -# - class_id: 3 -# label: Roadsign - - # tracker - - element: nvtracker - properties: - ll-lib-file: /opt/nvidia/deepstream/deepstream/lib/libnvds_nvmultiobjecttracker.so - ll-config-file: ${oc.env:PROJECT_PATH}/samples/assets/tracker/config_tracker_NvDCF_perf.yml - tracker-width: 640 - tracker-height: 384 - #display-tracking-id: 0 - - # Car Make classifier - - element: nvinfer@classifier - name: VehicleMakeNet - model: - format: etlt - remote: - url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/vehiclemakenet/versions/pruned_v1.0.1/zip" - model_file: resnet18_vehiclemakenet_pruned.etlt - label_file: labels.txt - precision: int8 - int8_calib_file: vehiclemakenet_int8.txt - batch_size: 16 - input: - object: DashCamNet.Car - layer_name: input_1 - shape: [3, 224, 224] - offsets: [103.939, 116.779, 123.68] - output: - layer_names: [predictions/Softmax] - converter: - module: savant.converter.classifier - class_name: TensorToLabelConverter - attributes: - - name: car_make - labels: - - acura - - audi - - bmw - - chevrolet - - chrysler - - dodge - - ford - - gmc - - honda - - hyundai - - infiniti - - jeep - - kia - - lexus - - mazda - - mercedes - - nissan - - subaru - - toyota - - volkswagen - - # Car Type classifier - - element: nvinfer@classifier - name: VehicleTypeNet - model: - format: etlt - remote: - url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/vehicletypenet/versions/pruned_v1.0.1/zip" - model_file: resnet18_vehicletypenet_pruned.etlt - label_file: labels.txt - precision: int8 - int8_calib_file: vehicletypenet_int8.txt - batch_size: 16 - input: - object: DashCamNet.Car - layer_name: input_1 - shape: [3, 224, 224] - offsets: [103.939, 116.779, 123.68] - output: - layer_names: [predictions/Softmax] - converter: - module: savant.converter.classifier - class_name: TensorToLabelConverter - attributes: - - name: car_type - labels: - - coupe - - largevehicle - - sedan - - suv - - truck - - van diff --git a/samples/peoplenet_detector/module.yml b/samples/peoplenet_detector/module.yml index a54d9501..445b2687 100644 --- a/samples/peoplenet_detector/module.yml +++ b/samples/peoplenet_detector/module.yml @@ -95,9 +95,6 @@ pipeline: checksum_url: s3://savant-data/models/peoplenet/peoplenet_pruned_v2.0.md5 parameters: endpoint: https://eu-central-1.linodeobjects.com - # or get the model directly from NGC API - # peoplenet v2.0 - # url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplenet/versions/pruned_v2.0/zip" # model file name, without location model_file: resnet34_peoplenet_pruned.etlt # v2.0 Accuracy: 84.3 Size 20.9 MB diff --git a/samples/template/src/module/module.yml b/samples/template/src/module/module.yml index 47fda7a1..0fa20d25 100644 --- a/samples/template/src/module/module.yml +++ b/samples/template/src/module/module.yml @@ -62,9 +62,6 @@ pipeline: checksum_url: s3://savant-data/models/peoplenet/peoplenet_pruned_v2.0.md5 parameters: endpoint: https://eu-central-1.linodeobjects.com - # or get the model directly from NGC API - # peoplenet v2.0 - # url: "https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplenet/versions/pruned_v2.0/zip" # model file name, without location model_file: resnet34_peoplenet_pruned.etlt # v2.0 Accuracy: 84.3 Size 20.9 MB diff --git a/savant/config/default.yml b/savant/config/default.yml index 52a99431..7e43e66c 100644 --- a/savant/config/default.yml +++ b/savant/config/default.yml @@ -5,7 +5,7 @@ name: ${oc.env:MODULE_NAME} parameters: # Logging specification string in the rust env_logger's format # https://docs.rs/env_logger/latest/env_logger/ - # The string is parsed and Python logging is setup accordingly + # The string is parsed and Python logging is set up accordingly # e.g. "info", or "info,insight::savant::target=debug" log_level: ${oc.env:LOGLEVEL, 'INFO'} @@ -186,7 +186,7 @@ parameters: # and reloaded in case changes are detected dev_mode: ${oc.decode:${oc.env:DEV_MODE, False}} - # Shutdown authorization key. If set, module will shutdown when it receives + # Shutdown authorization key. If set, module will shut down when it receives # a Shutdown message with this key. # shutdown_auth: "shutdown-auth" diff --git a/savant/config/schema.py b/savant/config/schema.py index 28f78d2e..5abaf922 100644 --- a/savant/config/schema.py +++ b/savant/config/schema.py @@ -48,7 +48,8 @@ def height(self) -> int: @dataclass class SourceShaper: """A class that will use an object implementing - :py:class:`~savant.base.source_shaper.BaseSourceShaper` to define a source shape for each source. + :py:class:`~savant.base.source_shaper.BaseSourceShaper` to define a source shape + for each source. For example, @@ -73,7 +74,8 @@ class SourceShaper: class FrameParameters: """Pipeline processing frame parameters. - .. note:: When ``shaper`` is specified, ``width``, ``height`` and ``padding`` must be not set. + .. note:: When ``shaper`` is specified, ``width``, ``height`` and ``padding`` + must be not set. """ width: Optional[int] = None @@ -86,7 +88,9 @@ class FrameParameters: """Add paddings to the frame before processing""" geometry_base: int = 8 - """Base value for frame parameters. All frame parameters must be divisible by this value.""" + """Base value for frame parameters. + All frame parameters must be divisible by this value. + """ shaper: Optional[SourceShaper] = None """Custom source shaper definition.""" @@ -586,6 +590,18 @@ class Pipeline: sink: List[SinkElement] = field(default_factory=list) """Sink elements of a pipeline.""" + pipeline_class: Optional[str] = None + """Pipeline class name with full module path, + e.g. ``savant.deepstream.pipeline.NvDsPipeline``. + If not set, the default pipeline class specified in the entrypoint will be used. + """ + + runner_class: Optional[str] = None + """Pipeline runner class name with full module path, + e.g. ``savant.deepstream.runner.NvDsPipelineRunner``. + If not set, the default runner class specified in the entrypoint will be used. + """ + @dataclass class Module: diff --git a/savant/converter/yolo.py b/savant/converter/yolo.py index 20ff4973..e8c3171d 100644 --- a/savant/converter/yolo.py +++ b/savant/converter/yolo.py @@ -17,18 +17,21 @@ class TensorToBBoxConverter(BaseObjectModelOutputConverter): def __init__( self, confidence_threshold: float = 0.25, - top_k: int = 3000, nms_iou_threshold: float = 0.0, + top_k: int = 3000, + class_ids: Tuple[int] = None, ): """ :param confidence_threshold: Select detections with confidence greater than specified. - :param top_k: Maximum number of output detections. :param nms_iou_threshold: Class agnostic NMS IoU threshold. + :param top_k: Maximum number of output detections. + :param class_ids: Filter detections by class. """ self.confidence_threshold = confidence_threshold self.nms_iou_threshold = nms_iou_threshold self.top_k = top_k + self.class_ids = class_ids super().__init__() def __call__( @@ -89,6 +92,13 @@ def __call__( bboxes[:, 0] += bboxes[:, 2] / 2 bboxes[:, 1] += bboxes[:, 3] / 2 + # filter by class + if self.class_ids: + class_mask = np.isin(class_ids, self.class_ids) + bboxes = bboxes[class_mask] + class_ids = class_ids[class_mask] + confidences = confidences[class_mask] + # filter by confidence if self.confidence_threshold: conf_mask = confidences > self.confidence_threshold @@ -96,7 +106,7 @@ def __call__( class_ids = class_ids[conf_mask] confidences = confidences[conf_mask] - # TODO: ability to filter by class, by size (width, height) and aspect ratio + # TODO: ability to filter by size (width, height) and aspect ratio # apply class agnostic NMS (all classes are treated as one) if self.nms_iou_threshold > 0 and len(confidences) > 1: nms_mask = nms_cpu(bboxes, confidences, self.nms_iou_threshold, self.top_k) diff --git a/savant/deepstream/meta/frame.py b/savant/deepstream/meta/frame.py index b230f605..4c66b8e6 100644 --- a/savant/deepstream/meta/frame.py +++ b/savant/deepstream/meta/frame.py @@ -12,6 +12,7 @@ from savant.api.constants import DEFAULT_NAMESPACE from savant.api.parser import parse_attribute_value from savant.deepstream.meta.object import _NvDsObjectMetaImpl +from savant.deepstream.utils.attribute import nvds_remove_obj_attrs from savant.meta.errors import MetaValueError from savant.meta.object import ObjectMeta from savant.utils.logging import LoggerMixin @@ -218,6 +219,9 @@ def remove_obj_meta(self, object_meta: ObjectMeta): if object_meta.uid in self._objects: del self._objects[object_meta.uid] if object_meta.object_meta_impl: + nvds_remove_obj_attrs( + self.frame_meta, object_meta.object_meta_impl.ds_object_meta + ) pyds.nvds_remove_obj_meta_from_frame( self.frame_meta, object_meta.object_meta_impl.ds_object_meta ) diff --git a/savant/deepstream/metadata.py b/savant/deepstream/metadata.py index ed3f6a0f..b82e44bd 100644 --- a/savant/deepstream/metadata.py +++ b/savant/deepstream/metadata.py @@ -92,7 +92,7 @@ def nvds_obj_meta_output_converter( track_box=track_box, attributes=[], ), - IdCollisionResolutionPolicy.Error, + IdCollisionResolutionPolicy.GenerateNewId, ) diff --git a/savant/deepstream/nvinfer/element_config.py b/savant/deepstream/nvinfer/element_config.py index 913c17b5..870993fb 100644 --- a/savant/deepstream/nvinfer/element_config.py +++ b/savant/deepstream/nvinfer/element_config.py @@ -371,19 +371,48 @@ def process(self, msg, kwargs): 'Model output layer names (model.output.layer_names) required.' ) + label_file = model_config.get( + 'label_file', + ( + nvinfer_config['property'].get('labelfile-path') + if nvinfer_config + else None + ), + ) + label_file_path = model_path / label_file if label_file else None + if label_file and not label_file_path.is_file(): + raise NvInferConfigException(f'Label file "{label_file_path}" not found.') + # model type-specific parameters - if issubclass(model_type, ObjectModel): + if issubclass(model_type, AttributeModel): + if label_file: + with open(label_file_path, encoding='utf8') as file_obj: + attr_labels = [line.split(';') for line in file_obj.read().splitlines() if line] + if not attr_labels: + raise NvInferConfigException( + 'No labels found in label file "%s".', label_file + ) + if len(attr_labels) != len(model_config.output.attributes): + raise NvInferConfigException( + 'Number of labels in label file does not match ' + 'the number of model output attributes.' + ) + for attr, labels in zip(model_config.output.attributes, attr_labels): + if attr.labels: + logger.warning( + 'Model output attribute labels for "%s" ' + 'are defined in the model config ' + 'and will be used instead of labels from "%s".', + attr.name, + label_file, + ) + else: + attr.labels = labels + + elif issubclass(model_type, ObjectModel): # model_config.output.objects is mandatory for object models, # but it may be autogenerated based on labelfile or num_detected_classes - label_file = model_config.get( - 'label_file', - ( - nvinfer_config['property'].get('labelfile-path') - if nvinfer_config - else None - ), - ) if model_config.output.objects: # highest priority is using manually defined model_config.output.objects if label_file: @@ -394,27 +423,25 @@ def process(self, msg, kwargs): ) elif label_file: - # try to load model object labels from file - label_file_path = model_path / label_file - if label_file_path.is_file(): - with open(label_file_path.resolve(), encoding='utf8') as file_obj: - model_config.output.objects = [ - NvInferObjectModelOutputObject(class_id=class_id, label=label) - for class_id, label in enumerate(file_obj.read().splitlines()) - ] - if model_config.output.num_detected_classes: - logger.warning( - 'Ignoring manually set value for ' - '(model_config.output.num_detected_classes) ' - 'because labelfile is used.' - ) - model_config.output.num_detected_classes = len( - model_config.output.objects - ) - logger.info( - 'Model object labels have been loaded from "%s".', - label_file_path, + # load model object labels from file + with open(label_file_path.resolve(), encoding='utf8') as file_obj: + model_config.output.objects = [ + NvInferObjectModelOutputObject(class_id=class_id, label=label) + for class_id, label in enumerate(file_obj.read().splitlines()) + ] + if model_config.output.num_detected_classes: + logger.warning( + 'Ignoring manually set value for ' + '(model_config.output.num_detected_classes) ' + 'because labelfile is used.' ) + model_config.output.num_detected_classes = len( + model_config.output.objects + ) + logger.info( + 'Model object labels have been loaded from "%s".', + label_file_path, + ) elif model_config.output.num_detected_classes: # generate labels (enumerate) model_config.output.objects = [ diff --git a/savant/deepstream/utils/object.py b/savant/deepstream/utils/object.py index 6e2a4cb1..1020c3a8 100644 --- a/savant/deepstream/utils/object.py +++ b/savant/deepstream/utils/object.py @@ -1,10 +1,12 @@ """DeepStream object utils.""" +from random import randrange from typing import Optional, Tuple, Union import pyds from pysavantboost import NvRBboxCoords, add_rbbox_to_object_meta, get_rbbox from savant_rs.primitives.geometry import BBox, RBBox +from savant_rs.utils import AtomicCounter from savant.deepstream.meta.constants import MAX_LABEL_SIZE from savant.meta.constants import DEFAULT_CONFIDENCE, UNTRACKED_OBJECT_ID @@ -19,6 +21,8 @@ from .iterator import nvds_obj_user_meta_iterator from .meta_types import OBJ_DRAW_LABEL_META_TYPE +OBJECT_ID_GENERATOR = AtomicCounter(randrange(0, 1_000_000_000)) + class IncorrectBBoxType(Exception): """Exception on errors when working with type of object on frame.""" @@ -119,7 +123,7 @@ def nvds_set_obj_uid( """ if obj_meta.misc_obj_info[InformationType.OBJECT_HASH_KEY]: raise UIDError('The object already has a unique key') - obj_uid = nvds_generate_obj_uid(frame_meta, obj_meta) + obj_uid = OBJECT_ID_GENERATOR.next obj_meta.misc_obj_info[InformationType.OBJECT_HASH_KEY] = obj_uid return obj_uid @@ -143,29 +147,6 @@ def nvds_get_obj_uid( return obj_uid -def nvds_generate_obj_uid( - frame_meta: pyds.NvDsFrameMeta, obj_meta: pyds.NvDsObjectMeta -) -> int: - """Generates a unique id for object. - - :param frame_meta: NvDsFrameMeta. - :param obj_meta: NvDsObjectMeta. - :return: unique object id. - """ - bbox = nvds_get_obj_bbox(obj_meta) - return hash( - ( - frame_meta.source_id, - frame_meta.frame_num, - obj_meta.obj_label, - bbox.xc, - bbox.yc, - bbox.width, - bbox.height, - ) - ) - - def nvds_get_obj_bbox(nvds_obj_meta: pyds.NvDsObjectMeta) -> Union[BBox, RBBox]: """Get the bounding box for specified object meta from Deepstream meta structures. diff --git a/savant/entrypoint/main.py b/savant/entrypoint/main.py index 875326d0..033558a8 100644 --- a/savant/entrypoint/main.py +++ b/savant/entrypoint/main.py @@ -2,15 +2,15 @@ isort:skip_file """ +import importlib import os import signal from pathlib import Path -from threading import Thread -from typing import IO, Any, Union +from typing import IO, Any, Union, Type from savant.gstreamer import Gst # should be first from savant.config import ModuleConfig -from savant.config.schema import ElementGroup, ModelElement +from savant.config.schema import ElementGroup, ModelElement, Module from savant.deepstream.encoding import check_encoder_is_available from savant.deepstream.nvinfer.build_engine import build_engine from savant.deepstream.nvinfer.model import NvInferModel @@ -49,7 +49,19 @@ def main(module_config: Union[str, Path, IO[Any]]): logger.info(get_starting_message('module')) # load module config - config = ModuleConfig().load(module_config) + config: Module = ModuleConfig().load(module_config) + + PipelineClass: Type[NvDsPipeline] = NvDsPipeline + if config.pipeline.pipeline_class: + pipeline_class_path = config.pipeline.pipeline_class.split('.') + pipeline_module = importlib.import_module('.'.join(pipeline_class_path[:-1])) + PipelineClass = getattr(pipeline_module, pipeline_class_path[-1]) + + RunnerClass: Type[NvDsPipelineRunner] = NvDsPipelineRunner + if config.pipeline.runner_class: + runner_class_path = config.pipeline.runner_class.split('.') + runner_module = importlib.import_module('.'.join(runner_class_path[:-1])) + RunnerClass = getattr(runner_module, runner_class_path[-1]) # reconfigure savant logger with updated loglevel update_logging(config.parameters['log_level']) @@ -75,19 +87,19 @@ def main(module_config: Union[str, Path, IO[Any]]): ): return False - pipeline = NvDsPipeline( + pipeline = PipelineClass( config.name, config.pipeline, **config.parameters, ) try: - with NvDsPipelineRunner(pipeline, status_filepath) as runner: + with RunnerClass(pipeline, status_filepath) as runner: try: for msg in pipeline.stream(): sink(msg, **dict(module_name=config.name)) except KeyboardInterrupt: - logger.info('Shutting down.') + logger.info('Shutting down module "%s".', config.name) except Exception as exc: # pylint: disable=broad-except logger.error(exc, exc_info=True) # TODO: Sometimes pipeline hangs when exit(1) or not exit at all is called. @@ -95,7 +107,7 @@ def main(module_config: Union[str, Path, IO[Any]]): # sink adapter is not available. os._exit(1) # pylint: disable=protected-access except Exception as exc: # pylint: disable=broad-except - logger.error(exc, exc_info=True) + logger.error('Module "%s" error %s', config.name, exc, exc_info=True) exit(1) if runner.error is not None: