Writing a filter¶
Author: | Bradley Chambers |
---|---|
Contact: | brad.chambers@gmail.com |
Date: | 10/26/2016 |
PDAL can be extended through the development of filter functions.
See also
For more on filters and their role in PDAL, please refer to PDAL Architecture Overview.
Every filter stage in PDAL is implemented as a plugin (sometimes referred to as a “driver”). Filters native to PDAL, such as filters.ferry, are implemented as _static_ filters and are statically linked into the PDAL library. Filters that require extra/optional dependencies, or are external to the core PDAL codebase altogether, such as filters.pmf, are implemented as _shared_ filters, and are built as individual shared libraries, discoverable by PDAL at runtime.
In this tutorial, we will give a brief example of a filter, with notes on how to make it static or shared.
The header¶
First, we provide a full listing of the filter 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 30 31 32 33 34 35 36 37 38 39 | // MyFilter.hpp
#pragma once
#include <pdal/Filter.hpp>
#include <pdal/Stage.hpp>
#include <memory>
namespace pdal
{
class Options;
class PointLayout;
class PointView;
class PDAL_DLL MyFilter : public Filter
{
public:
MyFilter() : Filter()
{}
static void * create();
static int32_t destroy(void *);
std::string getName() const;
private:
double m_value;
Dimension::Id m_myDimension;
virtual void addDimensions(PointLayoutPtr layout);
virtual void addArgs(ProgramArgs& args);
virtual PointViewSet run(PointViewPtr view);
MyFilter& operator=(const MyFilter&); // not implemented
MyFilter(const MyFilter&); // not implemented
};
} // namespace pdal
|
This header should be relatively straightforward, but we will point out three methods that must be declared for the plugin interface to be satisfied.
static void * create();
static int32_t destroy(void *);
std::string getName() const;
In many instances, you should be able to copy this header template verbatim, changing only the filter class name, includes, and member functions/variables as required by your implementation.
The source¶
Again, we start with a full listing of the filter 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 | // MyFilter.cpp
#include "MyFilter.hpp"
#include <pdal/Options.hpp>
#include <pdal/pdal_macros.hpp>
#include <pdal/PointTable.hpp>
#include <pdal/PointView.hpp>
#include <pdal/StageFactory.hpp>
#include <pdal/util/ProgramArgs.hpp>
namespace pdal
{
static PluginInfo const s_info =
PluginInfo("filters.name", "My awesome filter",
"http://link/to/documentation");
CREATE_STATIC_PLUGIN(1, 0, MyFilter, Filter, s_info)
std::string MyFilter::getName() const
{
return s_info.name;
}
void MyFilter::addArgs(ProgramArgs& args)
{
args.add("param", "Some parameter", m_value, 1.0);
}
void MyFilter::addDimensions(PointLayoutPtr layout)
{
layout->registerDim(Dimension::Id::Intensity);
m_myDimension = layout->registerOrAssignDim("MyDimension",
Dimension::Type::Unsigned8);
}
PointViewSet MyFilter::run(PointViewPtr input)
{
PointViewSet viewSet;
viewSet.insert(input);
return viewSet;
}
} // namespace pdal
|
For your filter to be available to PDAL at runtime, it must adhere to the PDAL
plugin interface. As a convenience, we provide the macros in
pdal_macros.hpp
to do just this.
We begin by creating a PluginInfo
struct containing three identifying
elements - the filter name, description, and a link to documentation.
1 2 3 | static PluginInfo const s_info =
PluginInfo("filters.name", "My awesome filter",
"http://link/to/documentation");
|
PDAL requires that filter names always begin with filters.
, and end with a
string that uniquely identifies the filter. The description will be displayed
to users of the PDAL CLI (pdal --drivers
).
Next, we pass the following to the CREATE_STATIC_PLUGIN
macro, in order:
PDAL plugin ABI major version, PDAL plugin ABI minor version, filter class
name, stage type (Filter
), and our PluginInfo
struct.
CREATE_STATIC_PLUGIN(1, 0, MyFilter, Filter, s_info)
To create a shared plugin, we simply change CREATE_STATIC_PLUGIN
to
CREATE_SHARED_PLUGIN
.
Finally, we implement a method to get the plugin name, which is primarily used
by the PDAL CLI when using the --drivers
or --options
arguments.
1 2 3 4 | std::string MyFilter::getName() const
{
return s_info.name;
}
|
Now that the filter has implemented the proper plugin interface, we will begin
to implement some methods that actually implement the filter. First,
getDefaultOptions()
is used to advertise those options that the filter
provides. Within PDAL, this is primarily used as a means of displaying options
via the PDAL CLI with the --options
argument. It provides the user with the
option names, descriptions, and default values.
1 2 3 4 | void MyFilter::addArgs(ProgramArgs& args)
{
args.add("param", "Some parameter", m_value, 1.0);
}
|
The addArgs()
method is used to register and bind any provided options to
the stage. Here, we get the value of param
, if provided, else we populate
m_value
with the default value of 1.0
.
1 2 3 4 5 6 | void MyFilter::addDimensions(PointLayoutPtr layout)
{
layout->registerDim(Dimension::Id::Intensity);
m_myDimension = layout->registerOrAssignDim("MyDimension",
Dimension::Type::Unsigned8);
}
|
In addDimensions()
we make sure that the known Intensity
dimension is
registered. We can also add a custom dimension, MyDimension
, which will be
populated within run()
.
1 2 3 4 5 6 | PointViewSet MyFilter::run(PointViewPtr input)
{
PointViewSet viewSet;
viewSet.insert(input);
return viewSet;
}
|
Finally, we define run()
, which takes as input a PointViewPtr
and
returns a PointViewSet
. It is here that we can transform existing
dimensions, add data to new dimensions, or selectively add/remove individual
points.
We suggest you take a closer look at our existing filters to get an idea of the
power of the Filter
stage and inspiration for your own filters!
StageFactory¶
As of this writing, users must also make a couple of changes to
StageFactory.cpp
to properly register static plugins only (this is not
required for shared plugins). It is our goal to eventually remove this
requirement to further streamline development of add-on plugins.
Note
Modification of StageFactory is required for STATIC plugins only. Dynamic plugins are registered at runtime.
First, add the following line to the beginning of StageFactory.cpp
(adjusting the path and filename as necessary).
#include <MyFilter.hpp>
Next, add the following line of code to the StageFactory
constructor.
PluginManager::initializePlugin(MyFilter_InitPlugin);