Sensor Visualization Example Tutorial

This guide covers the implementation of two ADTF filters. One of them creates data for the sensor visualization. The second filter receives and displays the incoming data inside the main scene. The functional logic of this example is split into the following parts:

Sensor Data Generator Filter

The Sensor Data Generator Filter creates data for the Sensor Visualization Filter. Two sensor data objects are continuously updated and sent to the visualization.


  cmake_minimum_required(VERSION 3.10.0)

  set(EXAMPLE_NAME sensor_data_generator_example)
  set(PLUGIN_BINARY_OUTPUT_DIR "bin")
  
  find_package(qt3ddisplay REQUIRED COMPONENTS base)
  find_package(Qt5 COMPONENTS Core OpenGL Quick Qml QuickWidgets 3DCore 3DExtras 3DRender 3DInput)
  
  project(${EXAMPLE_NAME} VERSION 1.0.0)
  
  set(CMAKE_AUTOMOC ON)
  set(CMAKE_AUTORCC ON)
  
  # Adds the project to the Visual Studio solution, which when build
  # creates a shared object called sensor_data_generator_example.adtfplugin
  adtf_add_filter(${EXAMPLE_NAME}
                  ${EXAMPLE_NAME}_filter.cpp
                  ${EXAMPLE_NAME}_filter.h
                  qml/${EXAMPLE_NAME}_filter_resources.qrc
                  qml/${EXAMPLE_NAME}_filter_main.qml
                  )
  
  set_target_properties(${EXAMPLE_NAME} PROPERTIES 
                  AUTORCC true 
                  AUTOUIC true)
  
  target_link_libraries(${EXAMPLE_NAME} PRIVATE
                  adtf::qt3d::base
                  )
  
  # Adds the INSTALL project to the Visual Studio solution, which when build
  # copies our filter to the subdirectory given as the second argument into ${PLUGIN_BINARY_OUTPUT_DIR}
  adtf_install_filter(${EXAMPLE_NAME} ${PLUGIN_BINARY_OUTPUT_DIR})
  
  set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER examples/guides)
  
  # Generates a plugindescription for our filter
  adtf_create_plugindescription(
      TARGET
          ${EXAMPLE_NAME}
      PLUGIN_SUBDIR
          ${PLUGIN_BINARY_OUTPUT_DIR}
  )

    

Now use the CMake-GUI to fill in all the information required by CMake to create the Visual Studio solution. Then open thesensor_data_generator_exampleproject in the solution explorer.

The header file of the filter

Just like before we continue by creating our sensor_data_generator_example_filter.h header file.

            
#pragma once

#include <qt3ddisplay/base/qt3d_base_filter.h>

 // To implement a Filter which can be used in 3D-Display we subclass adtf::qt3d::cQt3DBaseFilter.
class cQt3DSensorDataGenerator : public adtf::qt3d::cQt3DBaseFilter
{

public:
    // This macros provides some meta information about our Implementation.
    // This will be exposed by the plugin class factory.
    ADTF_CLASS_ID_NAME(cQt3DSensorDataGenerator,
        "sensorDataGenerator.qt3d.cid",
        "Qt3D Sensor Data Generator");

    // Constructor
    cQt3DSensorDataGenerator();

    // Destructor
    ~cQt3DSensorDataGenerator() = default;

};


            
          

The source file of the filter

Now we need to write the sensor_data_generator_example_filter.cpp source file.

            
#include "sensor_data_generator_example_filter.h"

// For simplicity use the necessary namespaces.
using namespace adtf::ucom;
using namespace adtf::streaming;
using namespace adtf::services;
using namespace adtf::base;
using namespace adtf::mediadescription;
using namespace adtf::qt3d;

// The code behind the macro creates a plugin and the main entries to the plugin DLL or shared object.
// The cQt3DSensorDataGenerator class will be available through the plugins class factory.
ADTF_PLUGIN("Qt3D Data Generator", cQt3DSensorDataGenerator)

// The main QML file gets loaded from the base class.
cQt3DSensorDataGenerator::cQt3DSensorDataGenerator():
    cQt3DBaseFilter(":/modules/Qt3DSensorDataGenerator/sensor_data_generator_example_filter_main.qml", false)
{
    // sets a short description for the component
    set_description(*this, "The sensor data generator example filter creates data for the sensor visualization filter.");

    // set help link to jump to documentation from ADTF Configuration Editor
    set_help_link(*this, "../doc/guides/tutorial_sensor_visualization_example.html");
}


            
          

The QML file of the filter

Now we need to write the sensor_data_generator_example_filter_main.qml qml file.

            
// Import the Qt libaries.
import QtQuick 2.12

import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.12


Item
{
    id: root

    // Use a property to create an output pin.
    property var outputPin

    // Define properties to store random sensor data.
    property vector3d sensorPos
    property vector3d rotation

    property int angleCounter: 0
    property int circleRoadwayDiameter: 5

    // Use the onCompleted handler to create stream types and an output pin.
    Component.onCompleted:
    {
        // The types object offers several methods to define stream types.
        // Here we use the createDefinition call were we can build our output data description.
        var colorType = types.createDefinition("tColor")
                   .add("nRed", "tUInt8")
                   .add("nGreen", "tUInt8")
                   .add("nBlue", "tUInt8")
                   .add("nAlpha", "tUInt8")

        var vector3DType = types.createDefinition("t3DVector")
                   .add("fX", "tFloat64")
                   .add("fY", "tFloat64")
                   .add("fZ", "tFloat64")

        var objectType = types.createDefinition("t3DSensorData")
                   .add("nIdx", "tUInt32")
                   .add("oColor", colorType)
                   .add("oPosition", vector3DType)
                   .add("oRange", "tFloat64")
                   .add("oRotation", vector3DType)
                   .add("oOpeningAngle", "tFloat64")

        // Create an output pin.
        root.outputPin = filter.createOutputPin("data", objectType);

        // We connect a callback to the timer runner.
        filter.createRunner("generate_sensor_data").trigger.connect(function(timestamp) {
            if(outputPin) {

                // Create a first object of type "t3DSensorData" and write it to the output pin.
                outputPin.write(timestamp, {
                                    nIdx: 0,
                                    oColor: { nRed: 64, nGreen: 224, nBlue: 208, nAlpha: 200 },
                                    oPosition: { fX: root.sensorPos.x, fY: root.sensorPos.y, fZ: root.sensorPos.z },
                                    oRange: 3,
                                    oRotation: { fX: 0, fY: 0, fZ: 0 },
                                    oOpeningAngle: 360
                                 })

                // Create a second object of type "t3DSensorData" and write it to the output pin.
                outputPin.write(timestamp, {
                                    nIdx: 1,
                                    oColor: { nRed: 255, nGreen: 130, nBlue: 25, nAlpha: 200 },
                                    oPosition: { fX: root.sensorPos.x, fY: root.sensorPos.y, fZ: root.sensorPos.z },
                                    oRange: 10,
                                    oRotation: { fX: root.rotation.x, fY: root.rotation.y, fZ: root.rotation.z },
                                    oOpeningAngle: 45
                                 })

                // We increment the angle counter each cycle.
                incrementCounter()
                // Based on the counter we calculate the center position of the sensor.
                root.sensorPos = generatePosition(root.angleCounter)
                // Based on the counter we calculate the rotation for the second sensor.
                root.rotation = Qt.vector3d(0, root.angleCounter, 0)
            }
        })
    }

    // Returns a vector3d with the center position of a sensor.
    // We use the diameter of the desired circle and create a track on the XZ plane.
    function generatePosition(count) {
        return Qt.vector3d(circleRoadwayDiameter * Math.sin(count / 180 * Math.PI), 0.25, circleRoadwayDiameter * Math.cos(count / 180 * Math.PI))
    }

    // Increments the counter by one.
    // The counter gets reseted if 360 is reached.
    function incrementCounter() {
        if(root.angleCounter <= 360) {
            root.angleCounter++
        } else {
            root.angleCounter = 1
        }
    }

}

            
          

The resources file of the filter

Now we need to write the sensor_data_generator_example_filter_resources.qrc resources file. For detailed information about the Qt Resource System have a look at Qt Resources System.

            
<RCC>
    <qresource prefix="/modules/Qt3DSensorDataGenerator">
        <file>sensor_data_generator_example_filter_main.qml</file>
    </qresource>
</RCC>

            
          

Sensor Visualization Filter

The Visualization Filter receives incoming data and visualizes the area which is covered by the respective sensor. A custom geometry which is part of the AdtfQt3DBase library based on the Qt GeometryRenderer is used to visualize the sensor area. A detailed guide how to write an own geometry will be provided in an upcoming version.


  cmake_minimum_required(VERSION 3.10.0)

  set(EXAMPLE_NAME sensor_visualization_example)
  set(PLUGIN_BINARY_OUTPUT_DIR "bin")
  
  find_package(qt3ddisplay REQUIRED COMPONENTS base qmlbase)
  find_package(Qt5 COMPONENTS Core OpenGL Quick Qml QuickWidgets 3DCore 3DExtras 3DRender 3DInput)
  
  project(${EXAMPLE_NAME} VERSION 1.0.0)
  
  set(CMAKE_AUTOMOC ON)
  set(CMAKE_AUTORCC ON)
  
  # Adds the project to the Visual Studio solution, which when build
  # creates a shared object called sensor_visualization_example.adtfplugin
  adtf_add_filter(${EXAMPLE_NAME}
                  ${EXAMPLE_NAME}_filter.cpp
                  ${EXAMPLE_NAME}_filter.h
                  qml/${EXAMPLE_NAME}_filter_resources.qrc
                  qml/${EXAMPLE_NAME}_filter_main.qml
                  )
  
  set_target_properties(${EXAMPLE_NAME} PROPERTIES 
                  AUTORCC true 
                  AUTOUIC true)
  
  target_link_libraries(${EXAMPLE_NAME} PRIVATE
                  adtf::qt3d::base
                  Qt5::3DRender
                  )
  
  # Adds the INSTALL project to the Visual Studio solution, which when build
  # copies our filter to the subdirectory given as the second argument into ${PLUGIN_BINARY_OUTPUT_DIR}
  adtf_install_filter(${EXAMPLE_NAME} ${PLUGIN_BINARY_OUTPUT_DIR})
  
  set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER examples/guides)
  
  # Generates a plugindescription for our filter
  adtf_create_plugindescription(
      TARGET
          ${EXAMPLE_NAME}
      PLUGIN_SUBDIR
          ${PLUGIN_BINARY_OUTPUT_DIR}
  )

    

Now use the CMake-GUI to fill in all the information required by CMake to create the Visual Studio solution. Then open thesensor_visualization_exampleproject in the solution explorer.

The header file of the filter

Just like before we continue by creating our sensor_visualization_example_filter.h header file.

            
#pragma once

#include <qt3ddisplay/base/qt3d_base_filter.h>

 // To implement a Filter which can be used in 3D-Display we subclass adtf::qt3d::cQt3DBaseFilter.
class cQt3DSensorVisualization : public adtf::qt3d::cQt3DBaseFilter
{

public:
    // This macros provides some meta information about our Implementation.
    // This will be exposed by the plugin class factory.
    ADTF_CLASS_ID_NAME(cQt3DSensorVisualization,
        "sensorVisualization.qt3d.cid",
        "Qt3D Sensor Visualization");

    // Constructor
    cQt3DSensorVisualization();

    // Destructor
    ~cQt3DSensorVisualization() = default;

};


            
          

The source file of the filter

Now we need to write the sensor_visualization_example_filter.cpp source file.

            
#include "sensor_visualization_example_filter.h"

// For simplicity use the necessary namespaces.
using namespace adtf::ucom;
using namespace adtf::streaming;
using namespace adtf::services;
using namespace adtf::base;
using namespace adtf::mediadescription;
using namespace adtf::qt3d;

// The code behind the macro creates a plugin and the main entries to the plugin DLL or shared object.
// The cQt3DSensorVisualization class will be available through the plugins class factory.
ADTF_PLUGIN("Qt3D Sensor Visualization", cQt3DSensorVisualization)

// The main QML file gets loaded from the base class.
cQt3DSensorVisualization::cQt3DSensorVisualization():
    cQt3DBaseFilter(":/modules/Qt3DSensorVisualization/sensor_visualization_example_filter_main.qml", false)
{
    // sets a short description for the component
    set_description(*this, "The sensor visualization example filter creates a plane 3D visualization for given sensor data.");

    // set help link to jump to documentation from ADTF Configuration Editor
    set_help_link(*this, "../doc/guides/tutorial_sensor_visualization_example.html");
}


            
          

The QML file of the filter

Now we need to write the sensor_visualization_example_filter_main.qml qml file.

            
// Import the Qt libaries.
import QtQuick 2.12
import QtQml.Models 2.12

import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.12

// Import the Qt3DBase library.
import AdtfQt3DBase 1.0


Item
{
    id: root

    property var sensorVisualizationDelegate: sensorVisualization

    // Use a ListModel to store incoming data.
    ListModel {
        id: incomingSensorData
    }


    // Use the onCompleted handler to connect the filter to the incoming data and create an interface client.
    Component.onCompleted: {
        // Create an input pin.
        const input = filter.createInputPin("sensorData")

        // Store incoming data to ListModel.
        input.sample.connect(function(sample) {
            incomingSensorData.set(sample.data.nIdx, sample.data)
        })

        const qtshared = filter.createInterfaceClient("qtshared", "qtshared")

        // Set the parent of your implementation to 'rootEntity' of the 3D-Display.
        filter.connected.connect(function() {
            if (qtshared.connected) {
                sensorVisualizationRootEntity.parent = qtshared.getObject("rootEntity")
            }
        })
    }


    Entity {
        id: sensorVisualizationRootEntity

        // Set a name which is displayed in the VisualElementsView.
        objectName: "sensorVisualization"

        // The NodeInstantiator creates a sensor visualization component for each incoming dataset.
        NodeInstantiator {
            model: incomingSensorData
            delegate: root.sensorVisualizationDelegate
        }
    }

    Component {
        id: sensorVisualization

        // Add a custom Entity which consists of a Geometry, Transform and a Material component.
        // We use the data which is stored in the ListModel.
        Entity {
            id: sensorVisualizationEntity

            // Set an object name which is displayed in the VisualElementsView.
            objectName: "Sensor " + (nIdx + 1)

            // The GeometryRenderer provides the functionality to draw a custom geometry.
            // Here we use the Qt3DSensorAreaGeometry which is part of the AdtfQt3DBase library.
            GeometryRenderer {
                id: geometryRenderer
                primitiveType: GeometryRenderer.Triangles
                vertexCount: sensorAreaGeometry.count
                geometry: Qt3DSensorAreaGeometry {
                    id: sensorAreaGeometry
                    openingAngle: oOpeningAngle
                    range: oRange
                }
            }

            // Use the Transform component to apply all incoming properties.
            Transform {
                id: transform
                translation: convertVector3d(oPosition)

                property vector3d rotationVec: convertVector3d(oRotation)
                rotationX: rotationVec.x
                rotationY: rotationVec.y
                rotationZ: rotationVec.z
            }

            // Set the Material component of the entity and apply all incoming properties.
            PhongAlphaMaterial {
                id: material
                alpha: oColor.nAlpha / 255.0
                ambient: convertColor(oColor)
            }

            components: [geometryRenderer, transform, material]
        }
    }

    // Converts the incoming data vector structure to a Qt.vector3d object.
    function convertVector3d(vector) {
        return Qt.vector3d(vector.fX, vector.fY, vector.fZ)
    }

    // Converts the incoming data vector structure to a Qt.rgba object.
    function convertColor(color) {
        return Qt.rgba(color.nRed / 255.0, color.nGreen / 255.0, color.nBlue / 255.0, 1.0)
    }

}


            
          

The resources file of the filter

Now we need to write the sensor_data_generator_example_filter_resources.qrc resources file. For detailed information about the Qt Resource System have a look at Qt Resources System.

            
<RCC>
    <qresource prefix="/modules/Qt3DSensorVisualization">
        <file>sensor_visualization_example_filter_main.qml</file>
    </qresource>
</RCC>

            
          

Finally we need to build both Projects.

Project setup

Now we want to integrate and test our filters in an ADTF Project. We can accomplish this using the Configuration Editor. The following example also includes the Grid Filter.

Sensor visualization session

Finally, we can run the session and visualize our data.

Sensor visualization

Where to go next?

Have a look at the Index.