Public API
Note, that not all exported functions are considered as part of the public API. The private API is not mature yet, expect it to change.
Representing a point cloud
RANSAC.RANSACCloud
— Typestruct RANSACCloud{A<:AbstractArray, B<:AbstractArray, C<:AbstractArray}
A struct to wrap a point cloud. Stores the vertices, the normals, the octree of the vertices, the subsets (as an array of arrays of vertice indexes), an array to indicate if a point is part of an already extracted primitive, the size of the point cloud (number of vertices), the weight of each octree level (populated during the construction of the octree, by copying the given element), an array to store the sum of the score of already extracted primitives for each octree level.
RANSAC.RANSACCloud
— MethodRANSACCloud(vertices, normals, numofsubsets::Int)
Construct a RANSACCloud
with numofsubsets
number of random subsets. Vertices and normals are converted to array of SVector
s.
Arguments
vertices
: an array of vertices.normals
: an array of surface normals.numofsubsets::Int
: number of subsets.force_eltype::Union{Nothing,DataType}=nothing
: an element type can be forced for the normals and vertices (in practiceFloat64
orFloat32
). If not specified, the element type of the passedvertices
andnormals
will be used.
RANSAC.RANSACCloud
— MethodRANSACCloud(vertices, normals, subsets; force_eltype::Union{Nothing,DataType}=nothing)
Construct a RANSACCloud
with the given subsets
. Vertices and normals are converted to array of SVector
s.
Arguments
vertices
: an array of vertices.normals
: an array of surface normals.subsets::Vector{Vector{Int}}
: a list of indexes for each subset.force_eltype::Union{Nothing,DataType}=nothing
: an element type can be forced for the normals and vertices (in practiceFloat64
orFloat32
). If not specified, the element type of the passedvertices
andnormals
will be used.
The above constructors copy the vertices and normals and convert them to arrays of SVector
s. If you want to pass the arrays directly, without modification, you can use the following function:
RANSAC.nomodRANSACCloud
— FunctionnomodRANSACCloud(vertices, normals, subsets)
Construct a RANSACCloud
without touching the vertices, normals and subsets. Other fields are computed.
Arguments
vertices
: an array of vertices.normals
: an array of surface normals.subsets::Vector{Vector{Int}}
: a list of indexes for each subset.
Parameters
For parameters nested named tuples are used, because it's easy to construct them, change their values or extend them. Earlier Parameters.jl was used, but I could not solve the extension part, then came the named tuples. You can construct it by hand, use the exported ransacparameters()
function or load from a YAML file.
Structure of the parameters
The easiest way to construct the desired named tuple is to use the ransacparameters()
function.
RANSAC.ransacparameters
— Functionransacparameters(p::T=DEFAULT_PARAMETERS; kwargs...) where {T<:NamedTuple}
Construct a NamedTuple
based on a previous one, defaulting to DEFAULT_PARAMETERS
and override it with the kwargs. Check the docs and examples for more.
Examples
julia> p1 = ransacparameters()
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9),
plane = (ϵ = 0.3, α = 0.08726646259971647),
cone = (ϵ = 0.3, α = 0.08726646259971647, minconeopang = 0.03490658503988659),
cylinder = (ϵ = 0.3, α = 0.08726646259971647),
sphere = (ϵ = 0.3, α = 0.08726646259971647, sphere_par = 0.1))
julia> p2 = ransacparameters(p1; sphere=(ϵ=0.9, α=deg2rad(1),), plane=(ϵ=1.0,))
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9),
plane = (ϵ = 1.0, α = 0.08726646259971647),
cone = (ϵ = 0.3, α = 0.08726646259971647, minconeopang = 0.03490658503988659),
cylinder = (ϵ = 0.3, α = 0.08726646259971647),
sphere = (ϵ = 0.9, α = 0.017453292519943295, sphere_par = 0.1))
ransacparameters(p::AbstractArray; kwargs...)
Construct a NamedTuple
for a given types of shapes using defaultparameters
and override it with the kwargs. Check the docs and examples for more.
Examples
julia> p1 = ransacparameters([FittedSphere, FittedCylinder])
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9,
shape_types = UnionAll[FittedSphere, FittedCylinder], τ = 900, itermax = 1000,
extract_s = :nofminset, terminate_s = :nofminset),
common = (collin_threshold = 0.2, parallelthrdeg = 1.0),
sphere = (ϵ = 0.3, α = 0.08726646259971647, sphere_par = 0.02),
cylinder = (ϵ = 0.3, α = 0.08726646259971647))
julia> p2 = ransacparameters([FittedSphere, FittedCylinder], sphere=(ϵ=0.01,), cylinder=(α=0.02,))
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9,
shape_types = UnionAll[FittedSphere, FittedCylinder], τ = 900, itermax = 1000,
extract_s = :nofminset, terminate_s = :nofminset),
common = (collin_threshold = 0.2, parallelthrdeg = 1.0),
sphere = (ϵ = 0.01, α = 0.08726646259971647, sphere_par = 0.02),
cylinder = (ϵ = 0.3, α = 0.02))
As the docstring shows, you can construct a new one based on an old one, and give keyword arguments that will overwrite the old values. Note, that the key-values that are in the keyword arguments will be overwritten, not the named tuple itself (so the values not listed in the keyword argument will not change).
As you can see in the above examples, the parameter must have two fields:iteration
, common
and the primitive types that you want to fit (sphere
, plane
, etc.). Note, that p.iteration.shape_types
field controls which primitives are fitted. Another important thing regarding the ransacparameter()
function, that the keyword named tuple must have a trailing comma, so this is good:
p2 = ransacparameters([FittedSphere, FittedCylinder], sphere=(ϵ=0.01,))
, but the following is NOT:
p2 = ransacparameters([FittedSphere, FittedCylinder], sphere=(ϵ=0.01))
The ransacparameters()
function uses the not exported default...parameters()
function, whose docstrings describes which parameters control what:
RANSAC.defaultcommonparameters
— Functiondefaultcommonparameters()
Construct a NamedTuple
with the default common parameters.
Examples
julia> defaultcommonparameters()
(common = (collin_threshold = 0.2, parallelthrdeg = 1.0),)
Implementation
This section describes the role of the common parameters.
collin_threshold
: 3 points can be nearly collinear, in some cases they must be filtered. See the code of:fit(::Type{FittedPlane}, p, n, params)
.parallelthrdeg
: threshold for two vectos being parallel, in degrees. Ifabs(dot(a,b))>cosd(parallelthrdeg)
,a
andb
are considered to be parallel.
RANSAC.defaultiterationparameters
— Functiondefaultiterationparameters(shape_types)
Construct a named tuple with the default iteration parameters. shape_types
is an array of FittedShape
s, that controls which primitives you want to fit to the point cloud.
Examples
julia> RANSAC.defaultiterationparameters([FittedPlane])
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9,
shape_types = UnionAll[FittedPlane], τ = 900, itermax = 1000,
extract_s = :nofminset, terminate_s = :nofminset),)
julia> RANSAC.defaultiterationparameters([FittedPlane, FittedSphere, FittedCone])
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9,
shape_types = UnionAll[FittedPlane, FittedSphere, FittedCone], τ = 900, itermax = 1000,
extract_s = :nofminset, terminate_s = :nofminset),)
Implementation
drawN
: number of points to be sampled (length of a minimal subset).minsubsetN
: number of minimal sets sampled in one iteration.prob_det
: probability of detection.τ
: minimal shape size.itermax
: maximum number of iteration.shape_types
: shapes that are fitted to the point cloud (array of types).extract_s
,terminate_s
: they are for easier testing, do not delete or modify it.
Check RANSAC.defaultshapeparameters
as well.
The defaultparameters
function joins these together:
RANSAC.defaultparameters
— Functiondefaultparameters(shape_types::Vector{T}) where {T}
Construct a NamedTuple
with the given shape types and the default parameters.
Examples
julia> defaultparameters([FittedSphere, FittedPlane])
(iteration = (drawN = 3, minsubsetN = 15, prob_det = 0.9,
shape_types = UnionAll[FittedSphere, FittedPlane], τ = 900, itermax = 1000,
extract_s = :nofminset, terminate_s = :nofminset),
common = (collin_threshold = 0.2, parallelthrdeg = 1.0),
sphere = (ϵ = 0.3, α = 0.08726646259971647, sphere_par = 0.02),
plane = (ϵ = 0.3, α = 0.08726646259971647))
If you wish to change the type of floating point values from the default Float64
to anything else, you can use the setfloattype
function.
RANSAC.setfloattype
— Functionsetfloattype(nt, T)
Convert every Real
but not Integer
value to type T
in a NamedTuple
recursively.
Construct by hand
As parameters are plain named tuples, one can easily construct their own. The above functions make heavy use of the merge
function. Check the code, if you wish.
Parse from YAML file
With the help of YAML.jl one can easily read the parameters from a YAML file. As shown below, you can specify which parameters you want to change (the others are going to be the default ones).
An example file (config.yml
):
plane:
- ϵ: 0.1
- α: 0.01
sphere:
- ϵ: 0.2
- α: 0.05
# parameter in sphere fitting
- sphere_par: 0.01
cone:
- ϵ: 1.
- α: 3.14
# filter those cones, whose opening angle is less than `minconeopang` radians
- minconeopang: 1.
iteration:
# number of points to be sampled (length of a minimal subset)
- drawN: 9
# number of minimal sets sampled in one iteration
- minsubsetN: 2
# probability of detection
- prob_det: 0.999
# minimal shape size
- τ: 10000
# maximum number of iteration
- itermax: 100000
# shapes that are fitted to the point cloud
- shape_types:
- plane
- sphere
- cone
common:
# threshold of two vectors being parallel (in degrees)
- parallelthrdeg: 0.5
# threshold of points being collinear
- collin_threshold: 0.3
Then you can use the readconfig
function to read the file:
julia> readconfig("config.yml")
(iteration = (drawN = 9, minsubsetN = 2, prob_det = 0.999,
shape_types = UnionAll[FittedPlane, FittedSphere, FittedCone], τ = 10000,
itermax = 100000, extract_s = :nofminset, terminate_s = :nofminset),
common = (collin_threshold = 0.3, parallelthrdeg = 0.5),
plane = (ϵ = 0.1, α = 0.01), cone = (ϵ = 1.0, α = 3.14, minconeopang = 1.0),
cylinder = (ϵ = 0.3, α = 0.08726646259971647),
sphere = (ϵ = 0.2, α = 0.05, sphere_par = 0.01))
RANSAC.readconfig
— Functionreadconfig(fname; toextend=DEFAULT_PARAMETERS, shapedict=DEFAULT_SHAPE_DICT)
Read a config file to a NamedTuple
. A "base" ntuple is expected, that gets overwritten/extended with the values in the config file. A Dict{String,FittedShape}
dictionary is also expected, that translates the string primitive types to julia types.
Arguments
fname
: name of the config file.toextend=DEFAULT_PARAMETERS
: a named tuple, that will be overwritten/extended with the values from the config file.shapedict=DEFAULT_SHAPE_DICT
: a dictionary that translates the string primitive names to julia types.
RANSAC.DEFAULT_PARAMETERS
— Constantconst DEFAULT_PARAMETERS = defaultparameters([FittedPlane, FittedCone, FittedCylinder, FittedSphere])
RANSAC.DEFAULT_SHAPE_DICT
— Constantconst DEFAULT_SHAPE_DICT = Dict("plane"=>FittedPlane, "cone"=>FittedCone, "cylinder"=>FittedCylinder, "sphere"=>FittedSphere)
Primitives
RANSAC.FittedShape
— TypeAn abstract type that supertypes all the fitted shapes.
RANSAC.FittedPlane
— Typestruct FittedPlane{A<:AbstractVector,B<:AbstractVector} <: FittedShape
Plane primitive, defined by one of its point, and its normalvector.
RANSAC.FittedSphere
— Typestruct FittedSphere{A<:AbstractArray, R<:Real} <: FittedShape
Sphere primitive, defined by its center and radius. Also stored, if the normals point outwards of the shape.
RANSAC.FittedCylinder
— Typestruct FittedCylinder{A<:AbstractArray, R<:Real} <: FittedShape
Cylinder primitive, defined by its axis direction, a point that lies on its axis, and its radius. Also stored, if the normals point outwards of the shape.
RANSAC.FittedCone
— Typestruct FittedCone{A<:AbstractArray, R<:Real} <: FittedShape
Cone primitive, defined by its apex, its axis (that points from the apex towards the opening), and its opening angle in radians. Also stored, if the normals point outwards of the shape.
Iteration
The ransac()
function does the iteration.
RANSAC.ransac
— Functionransac(pc, params, setenabled; reset_rand = false)
Run the RANSAC algorithm on a pointcloud with the given parameters.
Return the extracted primitives and the time it took to run the algorithm (in seconds).
Arguments
pc::RANSACCloud
: the point cloud.params::NamedTuple
: parameters.setenabled::Bool
: iftrue
: set every point to enabled.reset_rand::Bool=false
: iftrue
, resets the random seed withRandom.seed!(1234)
ransac(pc, params; reset_rand = false)
Run the RANSAC algorithm on a pointcloud with the given parameters.
Return the extracted primitives and the time it took to run the algorithm (in seconds).
Arguments
pc::RANSACCloud
: the point cloud.params::NamedTuple
: parameters.reset_rand::Bool=false
: iftrue
, resets the random seed withRandom.seed!(1234)
The iteration returns an array of ExtractedShape
:
RANSAC.ExtractedShape
— TypeExtractedShape{S<:FittedShape}
Store an extraced primitive (FittedShape
) and the points that belong to the shape as Vector{Int}
.
Implementation
When constructing in a refit
function, just call the constructor with: ExtractedShape(shape, compatible_points)
.
Exporting the results
With the help of JSON.jl, the resulted shapes can be easily saved to JSON files. For this purpose, the exportJSON()
function can be used. Note that io
must be specified, the "default" fallback to stdout
is not implemented.
RANSAC.exportJSON
— FunctionprintJSON(io::IO, s, indent)
Print a FittedShape
, ExtractedShape
or a vector of them to io
as a JSON string. With indent
given, it prints a representation with newlines and indents.
Arguments:
io::IO
: must be specified, usestdout
for interactive purposes.s
: aFittedShape
,ExtractedShape
or a vector of one of them.indent::Int
: indentation level.
printJSON(io::IO, s)
Print a FittedShape
, ExtractedShape
or a vector of them to io
as a compact JSON string.
Arguments:
io::IO
: must be specified, usestdout
for interactive purposes.s
: aFittedShape
,ExtractedShape
or a vector of one of them.
A few examples:
julia> using RANSAC, StaticArrays
julia> s1 = FittedPlane(SVector(0,0,1.), SVector(12.5, 7, 24))
FittedPlane{SArray{Tuple{3},Float64,1,3}}
normal: [12.5, 7.0, 24.0], point: [0.0, 0.0, 1.0]
julia> exportJSON(stdout, s1)
{"point":[0.0,0.0,1.0],"normal":[12.5,7.0,24.0],"type":"plane"}
julia> exportJSON(stdout, s1, 2)
{
"point": [
0.0,
0.0,
1.0
],
"normal": [
12.5,
7.0,
24.0
],
"type": "plane"
}
It is advised to export shapes in an array for easier processing (though I'm not a JSON expert):
julia> exportJSON(stdout, [s1])
{"primitives":[{"point":[0.0,0.0,1.0],"normal":[12.5,7.0,24.0],"type":"plane"}]}
julia> exportJSON(stdout, [s1], 2)
{
"primitives": [
{
"point": [
0.0,
0.0,
1.0
],
"normal": [
12.5,
7.0,
24.0
],
"type": "plane"
}
]
}
Works of course for different primitives:
julia> s2 = FittedSphere(SVector(1.2, 3., 5.), 1.5, true)
FittedSphere{SArray{Tuple{3},Float64,1,3}, Float64}
center: [1.2, 3.0, 5.0], R: 1.5, outwards
julia> exportJSON(stdout, [s1, s2], 2)
{
"primitives": [
{
"point": [
0.0,
0.0,
1.0
],
"normal": [
12.5,
7.0,
24.0
],
"type": "plane"
},
{
"outwards": true,
"radius": 1.5,
"center": [
1.2,
3.0,
5.0
],
"type": "sphere"
}
]
}
As can be seen above, in these cases an array of "primitives"
is printed. Under the hood, the toDict()
function does the job of converting the primitives to Dict
s.
RANSAC.toDict
— MethodtoDict(s::FittedShape)
Convert s
to a Dict{String,Any}
. It's "type" is defined by strt
. All fields of the struct is saved to the dict.
RANSAC.toDict
— MethodtoDict(a::Vector{T}) where {T<:Union{FittedShape,ExtractedShape}}
Convert a vector of shapes to a Dict{String,Any}
. The top key is a "primitive", whose value is the array of the shapes. See the documentation for examples.