Writing a kernel

Author:Bradley Chambers
Contact:brad.chambers@gmail.com
Date:11/02/2017

PDAL’s command-line application can be extended through the development of kernel functions. In this tutorial, we will give a brief example.

The header

First, we provide a full listing of the kernel header.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// MyKernel.hpp

#pragma once

#include <pdal/Kernel.hpp>
#include <pdal/plugin.hpp>

#include <string>

namespace pdal
{

class PDAL_DLL MyKernel : public Kernel
{
public:
    static void * create();
    static int32_t destroy(void *);
    std::string getName() const;
    int execute(); // override

private:
    MyKernel();
    void addSwitches(ProgramArgs& args);

    std::string m_input_file;
    std::string m_output_file;
};

} // namespace pdal

As with other plugins, the MyKernel class needs to have the following three methods declared for the plugin interface to be satisfied:

    static void * create();
    static int32_t destroy(void *);
    std::string getName() const;

The source

Again, we start with a full listing of the kernel source.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// MyKernel.cpp

#include "MyKernel.hpp"

#include <pdal/Filter.hpp>
#include <pdal/Kernel.hpp>
#include <pdal/Options.hpp>
#include <pdal/pdal_macros.hpp>
#include <pdal/StageFactory.hpp>
#include <pdal/PointTable.hpp>

#include <memory>
#include <string>


namespace pdal {

  static PluginInfo const s_info {
    "kernels.mykernel",
    "MyKernel",
    "http://link/to/documentation"
  };

  CREATE_SHARED_PLUGIN(1, 0, MyKernel, Kernel, s_info);
  std::string MyKernel::getName() const { return s_info.name; }

  MyKernel::MyKernel() : Kernel()
  {}

  void MyKernel::addSwitches(ProgramArgs& args)
  {
      args.add("input,i", "Input filename", m_input_file).setPositional();
      args.add("output,o", "Output filename", m_output_file).setPositional();
  }

  int MyKernel::execute()
  {
    PointTable table;
    StageFactory f;

    Stage& reader = makeReader(m_input_file, "readers.las");

    // Options should be added in the call to makeFilter, makeReader,
    // or makeWriter so that the system can override them with those
    // provided on the command line when applicable.
    Options filterOptions;
    filterOptions.add("step", 10);
    Stage& filter = makeFilter("filters.decimation", reader, filterOptions);

    Stage& writer = makeWriter(m_output_file, filter, "writers.text");
    writer.prepare(table);
    writer.execute(table);

    return 0;
  }

} // namespace pdal

In your kernel implementation, you will use a macro defined in pdal_macros. This macro registers the plugin with the Kernel factory. It is only required by plugins.

  std::string MyKernel::getName() const { return s_info.name; }

Note

A static plugin macro can also be used to integrate the kernel with the main code. This will not be described here. Using this as a shared plugin will be described later.

To build up a processing pipeline in this example, we need to create two objects: the pdal::PointTable and the pdal::StageFactory. The latter is used to create the various stages that will be used within the kernel.

    StageFactory f;

The pdal::Reader is created from the pdal::StageFactory, and is specified by the stage name, in this case an LAS reader. For brevity, we provide the reader a single option, the filename of the file to be read.

    // Options should be added in the call to makeFilter, makeReader,
    // or makeWriter so that the system can override them with those
    // provided on the command line when applicable.

The pdal::Filter is also created from the pdal::StageFactory. Here, we create a decimation filter that will pass every tenth point to subsequent stages. We also specify the input to this stage, which is the reader.

    filterOptions.add("step", 10);
    Stage& filter = makeFilter("filters.decimation", reader, filterOptions);

    Stage& writer = makeWriter(m_output_file, filter, "writers.text");
    writer.prepare(table);

Finally, the pdal::Writer is created from the pdal::StageFactory. This writers.text, takes as input the previous stage (the filters.decimation) and the output filename as its sole option.

    return 0;
  }

} // namespace pdal

The final two steps are to prepare and execute the pipeline. This is achieved by calling prepare and execute on the final stage.

When compiled, a dynamic library file will be created; in this case, libpdal_plugin_kernel_mykernel.dylib

Put this file in whatever directory PDAL_DRIVER_PATH is pointing to. Then, if you run pdal --help, you should see mykernel listed in the possible commands.

To run this kernel, you would use pdal mykernel -i <input las file> -o <output text file>.

Compilation

Set up a CMakeLists.txt file to compile your kernel against PDAL:

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 2.8.12)
project(KernelTutorial)

find_package(PDAL 1.6.0 REQUIRED CONFIG)

add_library(pdal_plugin_kernel_mykernel SHARED MyKernel.cpp)
target_link_libraries(pdal_plugin_kernel_mykernel PRIVATE ${PDAL_LIBRARIES})
target_include_directories(pdal_plugin_kernel_mykernel PRIVATE
                            ${PDAL_INCLUDE_DIRS})