Main Content

Transform Point Cloud Using Lidar Viewer

This example shows how to create a custom algorithm to transform a point cloud by using the Lidar Viewer app.

Read Point Cloud

Read point cloud data into the workspace by using the pcread function.

fileName = "highwayScene.pcd";
ptCloud = pcread(fileName);

Import Point Cloud into Lidar Viewer

To open the Lidar Viewer app, enter this command in the MATLAB® command window.

lidarViewer

Alternatively, you can select the app from the Image Processing and Computer Vision section of the Apps tab.

On the app toolstrip, select Import > From Workspace. In the Import from Workspace dialog box, select ptCloud and click OK.

ImportData.png

The app loads the point cloud data and displays it in the Point Cloud Display pane.

Create Custom Preprocessing Algorithm

Create Custom Algorithm

On the app toolstrip, select Edit Point Cloud to open the Edit Point Cloud tab. To create a class-based preprocessing algorithm, select Create Algorithm > Class Template.

MATLAB opens a new script containing the code template and the instructions to create a class-based definition for the algorithm. You can also define the algorithm parameters as user interface (UI) elements using this code template.

This example creates an EditTransform class based on the class template. You can use this algorithm class to transform point clouds.

Write Algorithm Class Definition

Write the class definition for a custom algorithm inherited from the lidar.internal.lidarViewer.edits.EditAlgorithm class.

First, define the name, icon, and description for the algorithm.

    properties (Constant)
        EditName = "Rigid transform";
        Icon    = fullfile(matlabroot, "toolbox", "lidar", "lidar", "+lidar",...
                "+internal", "+lidarViewer", "+view", "+icons", "classTemplate_24.png");
        Description = "Transform point cloud"
    end

Next, specify the custom properties for the algorithm.

    properties (Access = private)

        % Properties related to rotation around x-axis
        RotationAroundX = 0
        RotationAroundXMinValue = -360
        RotationAroundXMaxValue = 360
        RotationAroundXLabel
        RotationAroundXSlider
        RotationAroundXEB

        % Properties related to rotation around y-axis
        RotationAroundY = 0
        RotationAroundYMinValue = -360
        RotationAroundYMaxValue = 360
        RotationAroundYLabel
        RotationAroundYSlider
        RotationAroundYEB

        % Properties related to rotation around z-axis
        RotationAroundZ = 0
        RotationAroundZMinValue = -360
        RotationAroundZMaxValue = 360
        RotationAroundZLabel
        RotationAroundZSlider
        RotationAroundZEB        
        
        % Properties related to translation around x-axis
        TransAcrossX = 0
        TransAcrossXMinValue = -100
        TransAcrossXMaxValue = 100
        TransAcrossXLabel
        TransAcrossXSlider
        TransAcrossXEB

        % Properties related to translation around y-axis
        TransAcrossY = 0
        TransAcrossYMinValue = -100
        TransAcrossYMaxValue = 100
        TransAcrossYLabel
        TransAcrossYSlider
        TransAcrossYEB

        % Properties related to translation around z-axis
        TransAcrossZ = 0
        TransAcrossZMinValue = -100
        TransAcrossZMaxValue = 100
        TransAcrossZLabel
        TransAcrossZSlider
        TransAcrossZEB

    end

Third, specify the function definitions.

The setUpEditOperation function initializes variables of the edit algorithm.

    function setUpEditOperation(this, varargin)
        % Overwrite base class method to set default values for
        % parameters.
        this.PointCloud = varargin{1};
        
        if nargin > 2
            % If you edit the applied algorithm from the History pane,
            % retain the previous parameter values.
            % Otherwise, set default values for the parameters.
            this.RotationAroundX = varargin{2}.RotationAroundX;
            this.RotationAroundY = varargin{2}.RotationAroundY;
            this.RotationAroundZ = varargin{2}.RotationAroundZ;
            this.TransAcrossX = varargin{2}.TransAcrossX;
            this.TransAcrossY = varargin{2}.TransAcrossY;
            this.TransAcrossZ = varargin{2}.TransAcrossZ;
        else
            this.RotationAroundZ = 0;
            this.RotationAroundY = 0;
            this.RotationAroundX = 0;
            this.TransAcrossX = 0;
            this.TransAcrossY = 0;
            this.TransAcrossZ = 0;
        end
    end

The setUpAlgorithmConfigurePanel function adds all the UI elements to the Algorithm Parameters pane in the Lidar Viewer app.

    function setUpAlgorithmConfigurePanel(this,grid)
        % Function to configure the Algorithm Parameters pane to
        % tune the algorithm parameters.

        % Assign width and height of the grid in the Algorithm
        % Parameters pane. For this algorithm, 45 pixels are used for
        % default buttons at the bottom of the panel, 40 pixels for
        % sliders, and 25 pixels for UI text and edit boxes.
        
        grid.ColumnWidth = {'1x', 45};
        grid.RowHeight = {25, 40, 25, 40, 25, 40, 25, 40, 25, 40, 25, 40};
        
        % Create 'Rotation around x-axis' UI text.
        this.RotationAroundXLabel = uilabel('Parent', grid, 'Text', ...
            'Rotation around x-axis', ...
            'Tooltip','Amount of rotation around x-axis(in degrees)');
        this.RotationAroundXLabel.Layout.Row = 1;
        this.RotationAroundXLabel.Layout.Column = 1;
        
        % Create edit box for 'Rotation around x-axis'.
        this.RotationAroundXEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'RotationAroundX'),...
            'Value',num2str(this.RotationAroundX), ...
            'Tooltip','Amount of rotation around x-axis(in degrees)', ...
            'Tag','RotationAroundXEB');
        this.RotationAroundXEB.Layout.Row = 1;
        this.RotationAroundXEB.Layout.Column = 2;
        
        % Create a slider for 'Rotation around x-axis'.
        this.RotationAroundXSlider = uislider('Parent', grid,...
            'Limits', [this.RotationAroundXMinValue, this.RotationAroundXMaxValue], ...
            'MajorTicks', [this.RotationAroundXMinValue, this.RotationAroundXMaxValue], ...
            'Value', this.RotationAroundX,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'RotationAroundX'), ...
            'Tag','RotationAroundXSlider');
        this.RotationAroundXSlider.Layout.Row = 2;
        this.RotationAroundXSlider.Layout.Column = [1 2];            

        % Create 'Rotation around y-axis' UI text.
        this.RotationAroundYLabel = uilabel('Parent', grid, 'Text', ...
            'Rotation around y-axis', ...
            'Tooltip','Amount of rotation around y-axis(in degrees)');
        this.RotationAroundYLabel.Layout.Row = 3;
        this.RotationAroundYLabel.Layout.Column = 1;
        
        % Create an edit box for 'Rotation around y-axis'.
        this.RotationAroundYEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'RotationAroundY'),...
            'Value',num2str(this.RotationAroundY), ...
            'Tooltip','Amount of rotation around y-axis(in degrees)', ...
            'Tag','RotationAroundYEB');
        this.RotationAroundYEB.Layout.Row = 3;
        this.RotationAroundYEB.Layout.Column = 2;
        
        % Create a slider box for 'Rotation around y-axis'.
        this.RotationAroundYSlider = uislider('Parent', grid,...
            'Limits', [this.RotationAroundYMinValue, this.RotationAroundYMaxValue], ...
            'MajorTicks', [this.RotationAroundYMinValue, this.RotationAroundYMaxValue], ...
            'Value', this.RotationAroundY,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'RotationAroundY'), ...
            'Tag','RotationAroundYSlider');
        this.RotationAroundYSlider.Layout.Row = 4;
        this.RotationAroundYSlider.Layout.Column = [1 2];

        % Create 'Rotation around z-axis' UI text.
        this.RotationAroundZLabel = uilabel('Parent', grid, 'Text', ...
            'Rotation around z-axis', ...
            'Tooltip','Amount of rotation around z-axis(in degrees)');
        this.RotationAroundZLabel.Layout.Row = 5;
        this.RotationAroundZLabel.Layout.Column = 1;
        
        % Create an edit box for 'Rotation around z-axis'.
        this.RotationAroundZEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'RotationAroundZ'),...
            'Value',num2str(this.RotationAroundZ), ...
            'Tooltip','Amount of rotation around z-axis(in degrees)', ...
            'Tag','RotationAroundZEB');
        this.RotationAroundZEB.Layout.Row = 5;
        this.RotationAroundZEB.Layout.Column = 2;
        
        % Create a slider box for 'Rotation around z-axis'.
        this.RotationAroundZSlider = uislider('Parent', grid,...
            'Limits', [this.RotationAroundZMinValue, this.RotationAroundZMaxValue], ...
            'MajorTicks', [this.RotationAroundZMinValue, this.RotationAroundZMaxValue], ...
            'Value', this.RotationAroundZ,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'RotationAroundZ'), ...
            'Tag','RotationAroundZSlider');
        this.RotationAroundZSlider.Layout.Row = 6;
        this.RotationAroundZSlider.Layout.Column = [1 2];

        % Create 'Translation about x-axis' UI text.
        this.TransAcrossXLabel = uilabel('Parent', grid,  'Text', ...
            'Translation about x-axis', ...
            'Tooltip','Amount of translation across x-axis');
        this.TransAcrossXLabel.Layout.Row = 7;
        this.TransAcrossXLabel.Layout.Column = 1;
        
        % Create an edit box for 'Translation about x-axis'.
        this.TransAcrossXEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'TransAcrossX'),...
            'Value',num2str(this.TransAcrossX), ...
            'Tooltip','Amount of translation across x-axis', ...
            'Tag','TransAcrossXEB');
        this.TransAcrossXEB.Layout.Row = 7;
        this.TransAcrossXEB.Layout.Column = 2;
        
        % Create a slider box for 'Translation about x-axis'.
        this.TransAcrossXSlider = uislider('Parent', grid,...
            'Limits', [this.TransAcrossXMinValue, this.TransAcrossXMaxValue], ...
            'MajorTicks', [this.TransAcrossXMinValue, this.TransAcrossXMaxValue], ...
            'Value', this.TransAcrossX,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'TransAcrossX'), ...
            'Tag','TransAcrossXSlider');
        this.TransAcrossXSlider.Layout.Row = 8;
        this.TransAcrossXSlider.Layout.Column = [1 2];

        % Create 'Translation about y-axis' UI text.
        this.TransAcrossYLabel = uilabel('Parent', grid,  'Text', ...
            'Translation about y-axis', ...
            'Tooltip','Amount of translation across y-axis');
        this.TransAcrossYLabel.Layout.Row = 9;
        this.TransAcrossYLabel.Layout.Column = 1;
        
        % Create an edit box for 'Translation about y-axis'.
        this.TransAcrossYEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'TransAcrossY'),...
            'Value',num2str(this.TransAcrossY), ...
            'Tooltip','Amount of translation across y-axis', ...
            'Tag','TransAcrossYEB');
        this.TransAcrossYEB.Layout.Row = 9;
        this.TransAcrossYEB.Layout.Column = 2;
        
        % Create a slider box for 'Translation about y-axis'.
        this.TransAcrossYSlider = uislider('Parent', grid,...
            'Limits', [this.TransAcrossYMinValue, this.TransAcrossYMaxValue], ...
            'MajorTicks', [this.TransAcrossYMinValue, this.TransAcrossYMaxValue], ...
            'Value', this.TransAcrossY,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'TransAcrossY'), ...
            'Tag','TransAcrossYSlider');
        this.TransAcrossYSlider.Layout.Row = 10;
        this.TransAcrossYSlider.Layout.Column = [1 2];

        % Create 'Translation about z-axis' UI text.
        this.TransAcrossZLabel = uilabel('Parent', grid,  'Text', ...
            'Translation about z-axis', ...
            'Tooltip','Amount of translation across z-axis');
        this.TransAcrossZLabel.Layout.Row = 11;
        this.TransAcrossZLabel.Layout.Column = 1;
        
        % Create an edit box for 'Translation about z-axis'.
        this.TransAcrossZEB = uieditfield('parent',grid, ...
            'ValueChangedFcn',@(~,evt)editBoxValueChanged(this, evt,'TransAcrossZ'),...
            'Value',num2str(this.TransAcrossZ), ...
            'Tooltip','Amount of translation across z-axis', ...
            'Tag','TransAcrossZEB');
        this.TransAcrossZEB.Layout.Row = 11;
        this.TransAcrossZEB.Layout.Column = 2;
        
        % Create a slider box for 'Translation about z-axis'.
        this.TransAcrossZSlider = uislider('Parent', grid,...
            'Limits', [this.TransAcrossZMinValue, this.TransAcrossZMaxValue], ...
            'MajorTicks', [this.TransAcrossZMinValue, this.TransAcrossZMaxValue], ...
            'Value', this.TransAcrossZ,...
            'ValueChangingFcn',@(~,evt)sliderChanging(this, evt,'TransAcrossZ'), ...
            'Tag','TransAcrossZSlider');
        this.TransAcrossZSlider.Layout.Row = 12;
        this.TransAcrossZSlider.Layout.Column = [1 2];
    end

The getCurrentParams function creates a structure for all the required algorithm parameters.

    function params = getCurrentParams(this)
        % Get method to get the current algorithm parameter values
        % encapsulated in a structure
        params = struct();
        params.RotationAroundZ = this.RotationAroundZ;
        params.RotationAroundY = this.RotationAroundY;
        params.RotationAroundX = this.RotationAroundX;
        params.TransAcrossX = this.TransAcrossX; 
        params.TransAcrossY = this.TransAcrossY; 
        params.TransAcrossZ = this.TransAcrossZ; 
    end

The applyEdits function defines the rigid transformation algorithm of this class. This function contains the code to transform point clouds.

    function pointCloudOut = applyEdits(pointCloudIn, parameters)               
        % Function to apply the rigid transform function to the input
        % point cloud object based on the parameters.
        
        % Rotation around Z-axis
        angle = parameters.RotationAroundZ *(pi/180);
        xTrans = parameters.TransAcrossX;
        yTrans = parameters.TransAcrossY;
        zTrans = parameters.TransAcrossZ;
        A = [cos(angle) sin(angle) 0 0; ...
            -sin(angle) cos(angle) 0 0; ...
            0 0 1 0; ...
            0 0 0 1];
        tform = rigidtform3d(A);
        pointCloudOut = pctransform(pointCloudIn, tform);

        % Rotation around X-axis
        angle = parameters.RotationAroundX *(pi/180);
        A = [1 0 0 0; ...
            0 cos(angle) sin(angle) 0; ...
            0 -sin(angle) cos(angle) 0; ...
            0 0 0 1];
        tform = rigidtform3d(A);
        pointCloudOut = pctransform(pointCloudOut, tform);

        % Rotation around Y-axis
        angle = parameters.RotationAroundY *(pi/180);
        A = [cos(angle) 0 -sin(angle) 0; ...
            0 1 0 0; ...
            sin(angle) 0 cos(angle) 0; ...
            0 0 0 1];
        tform = rigidtform3d(A);
        pointCloudOut = pctransform(pointCloudOut, tform);

        % Translation about X-, Y-, Z-axis
        A = [1 0 0 xTrans; ...
            0 1 0 yTrans; ...
            0 0 1 zTrans; ...
            0 0 0 1];
        tform = rigidtform3d(A);
        pointCloudOut = pctransform(pointCloudOut, tform);      
    end

To dynamically update the point cloud with the latest algorithm parameters, add callback functions for each UI component.

Create a +lidar/+lidarViewer package directory within a folder that is already on the MATLAB path and save EditTransform.m file under that directory.

Import Custom Algorithm into Lidar Viewer

To import the algorithm into Lidar Viewer, on the app toolstrip, select Import Algorithm > Import Class. Then, in the dialog box, select the algorithm class file EditTransform.m and click Open. The app adds this algorithm to the algorithms list on the Edit Point Cloud toolstrip. If you do not see the algorithm in the list, click Refresh List.

Select the Rigid Transform algorithm. You can tune the algorithm parameters in the Algorithm Parameters pane. The Lidar Viewer app dynamically updates the point cloud as you tune the parameters.

After you tune the parameters, click OK to apply the edit algorithm to the point cloud.