Skip to content

Commit 5952dfd

Browse files
authored
Merge pull request #942 from JuliaRobotics/23Q1/enh/objclosures
allow loopObject on OAS factor for closures
2 parents a7c62f9 + 1c1d122 commit 5952dfd

4 files changed

Lines changed: 161 additions & 25 deletions

File tree

src/3rdParty/_PCL/_PCL.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import IncrementalInference: ArrayPartition
4444
# export PCLHeader, PointCloud
4545
# export AbstractBoundingBox, AxisAlignedBoundingBox, OrientedBoundingBox
4646
# export getCorners
47+
# export exportPointCloudWorld
4748

4849
# bring in the types
4950
include("entities/PCLTypes.jl")

src/3rdParty/_PCL/services/PointCloud.jl

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ function PointCloud(
216216
end
217217

218218
function PointCloud(
219-
xyz::AbstractMatrix{<:Real};
220-
kwargs...
221-
)
219+
xyz::AbstractMatrix{<:Real};
220+
kwargs...
221+
)
222222
#
223223
PointCloud(
224224
view(xyz,:,1),
@@ -228,6 +228,17 @@ function PointCloud(
228228
)
229229
end
230230

231+
function PointCloud(
232+
xyz::AbstractVector{<:AbstractVector{<:Real}};
233+
kwargs...
234+
)
235+
@cast mat[i,d] := xyz[i][d]
236+
PointCloud(
237+
mat;
238+
kwargs...
239+
)
240+
end
241+
231242

232243
# https://pointclouds.org/documentation/conversions_8h_source.html#l00166
233244
function PointCloud(

src/3rdParty/_PCL/services/PointCloudUtils.jl

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ function findObjectVariablesFromWorld(
303303
minList::Int = 1,
304304
maxList::Int = 999999,
305305
minrange=1.0,
306-
varList::AbstractVector{Symbol} = sort(ls(dfg,varPattern;tags); lt=DFG.natural_lt)[minList:maxList],
306+
varList::AbstractVector{Symbol} = sort(ls(dfg,varPattern;tags); lt=DFG.natural_lt)[minList:minimum([end;maxList])],
307307
blobLabel = r"PCLPointCloud2",
308308
sortList::Bool=true,
309309
checkhash::Bool=false
@@ -433,7 +433,12 @@ function calcAxes3D(
433433
end
434434
end
435435
# costs = [distance(Mr, R0, R); distance(Mr, R0, Rx); distance(Mr, R0, Ry)]
436-
arr[argmin(costs)]
436+
if 0 < length(costs)
437+
arr[argmin(costs)]
438+
else
439+
@warn "not able to approximate an orientation for the new object, using default idenity"
440+
R0
441+
end
437442
end
438443

439444
ArrayPartition(SVector...), SMatrix{3,3}(R_enh))
@@ -464,4 +469,52 @@ function Base.convert(
464469
PointCloud(xyz)
465470
end
466471

472+
"""
473+
$SIGNATURES
474+
475+
Return a PointCloud with all the points in the world frame coordinates, given the solveKey.
476+
477+
See also: [`saveLAS`](@ref)
478+
"""
479+
function exportPointCloudWorld(
480+
dfg::AbstractDFG;
481+
varList::AbstractVector{Symbol} = sort(ls(dfg); lt=DFG.natural_lt),
482+
solveKey::Symbol = :default,
483+
# TODO update to blobs saved as LAS files instead
484+
getpointcloud::Function = (v)->_PCL.getDataPointCloud(dfg, v, Regex("PCLPointCloud2"); checkhash=false),
485+
downsample::Int=1,
486+
minrange = 0.0,
487+
maxrange = 9999.0,
488+
)
489+
M = SpecialEuclidean(3)
490+
ϵ0 = ArrayPartition(SVector(0,0,0.),SMatrix{3,3}(1,0,0,0,1,0,0,0,1.)) # MJL.identity_element(M)
491+
pc_map = _PCL.PointCloud()
492+
# loop through all variables in the given list
493+
for vl in varList
494+
pc_ = getpointcloud(vl)
495+
if pc_ isa Nothing
496+
@warn "Skipping variable without point cloud" vl
497+
continue
498+
end
499+
pc = PointCloud(pc_)
500+
501+
pts_a = (s->[s.x;s.y;s.z]).(pc.points)
502+
pts_a = _filterMinRange(pts_a, minrange, maxrange)
503+
pc = PointCloud(pts_a)
504+
505+
v = getVariable(dfg, Symbol(vl))
506+
if !(:parametric in listSolveKeys(v))
507+
@warn "Skipping $vl which does not have solveKey :parametric"
508+
continue
509+
end
510+
w_Cwp = calcPPE(v; solveKey).suggested
511+
wPp = Manifolds.exp(M,ϵ0,Manifolds.hat(M,ϵ0,w_Cwp))
512+
# wPp = getSolverData(v, solveKey).val[1]
513+
wPC = apply(M, wPp, pc)
514+
cat(pc_map, wPC; reuse=true)
515+
end
516+
517+
pc_map
518+
end
519+
467520
#

src/objects/ObjectAffordanceSubcloud.jl

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ Base.@kwdef struct ObjectAffordanceSubcloud <: AbstractManifoldMinimize
7575
added to the same object variable later by the user. See [`IIF.rebuildFactorMetadata!`](@ref).
7676
FIXME: there is a hack, should not be using Serialization.deserialize on a PCLPointCloud2, see Caesar.jl#921 """
7777
p_PC_blobIds::Vector{UUID} = Vector{UUID}()
78+
"""
79+
Object Affordance factor can be used to "close loops" if the user knows two object are the same.
80+
E.g. say two OAS factors already make two :OBJECT_AFFORDANCE variables of the same physical object,
81+
then creating a third and loop obj variable and third OAS factor with .loopObject=true against the
82+
existing two object variables should effectively "close the loop".
83+
"""
84+
loopObject::Bool = false
85+
"""
86+
Alignment parameters
87+
"""
88+
alignParams::Dict{Symbol,Int} = Dict{Symbol,Int}(
89+
:max_iterations => 40,
90+
:correspondences => 500,
91+
:neighbors => 50
92+
)
7893
end
7994

8095
# function Base.getproperty(oas::ObjectAffordanceSubcloud, f::Symbol)
@@ -127,6 +142,18 @@ function _findObjPriors(dfg::AbstractDFG, fvars::AbstractVector{<:DFGVariable})
127142
return objpriors, op_PCs
128143
end
129144

145+
function _findObjAffSubcFactor(dfg::AbstractDFG, objl::Symbol)
146+
# find oas factor label where objl is first variable
147+
fcts = ls(dfg, objl)
148+
# loop through all factors on this obj variable
149+
for fc in getFactor.(dfg, fcts)
150+
vo = getVariableOrder(fc)
151+
# check that it is the OAS factor defining this vo[1] obj Variable
152+
if 1 < length(vo) && vo[1] == objl
153+
return getLabel(fc)
154+
end
155+
end
156+
end
130157

131158
function _defaultOASCache(
132159
dfg::AbstractDFG,
@@ -160,13 +187,26 @@ function _defaultOASCache(
160187

161188
# NOTE, obj variable first, pose variables are [2:end]
162189
for (i,vl) in enumerate(getLabel.(fvars)[2:end])
163-
p_PC = _PCL.getDataPointCloud(dfg, vl, fct.p_PC_blobIds[i]; checkhash=false) |> _PCL.PointCloud
164-
p_SC = _PCL.getSubcloud(p_PC, fct.p_BBos[i]; minrange, maxrange)
190+
lhat_T_p_, p_SC = if !fct.loopObject
191+
p_PC = _PCL.getDataPointCloud(dfg, vl, fct.p_PC_blobIds[i]; checkhash=false) |> _PCL.PointCloud
192+
p_SC_ = _PCL.getSubcloud(p_PC, fct.p_BBos[i]; minrange, maxrange)
193+
fct.lhat_Ts_p[i], p_SC_
194+
else
195+
# use this when OAS is used to close loop on already existing ObjAff variables
196+
@assert :OBJECT_AFFORDANCE in getTags(fvars[i]) "ObjAffSubc factor with .loopObject = true must connect to variables with the tag :OBJECT_AFFORDANCE"
197+
foaslb = _findObjAffSubcFactor(dfg, vl)
198+
# get the obj aff point cloud
199+
o_SC_ = Caesar.assembleObjectCache(dfg, foaslb)
200+
# transform obj aff clouds to same reference frame
201+
w_T_o_ = fct.lhat_Ts_p[i] |> deepcopy # getBelief(dfg, vl) |> mean
202+
# return transform to common frame and object frame subcloud
203+
w_T_o_, o_SC_
204+
end
205+
# sanity check
165206
if 0 === length(p_SC)
166207
error("ObjectAffordance factor cannot use empty subcloud on $(vl)")
167208
end
168-
169-
lhat_T_p_ = fct.lhat_Ts_p[i]
209+
# use mutable format
170210
lhat_T_p = ArrayPartition(
171211
MVector(lhat_T_p_.x[1]...),
172212
MMatrix{size(lhat_T_p_.x[2])...}(lhat_T_p_.x[2])
@@ -229,6 +269,14 @@ function IncrementalInference.preambleCache(
229269
for i in 1:length(cache.ohat_SCs)
230270
push!(cache.o_Ts_ohat, e0)
231271
end
272+
k_ = keys(fct.alignParams) |> collect
273+
v_ = values(fct.alignParams) |> collect
274+
arr = []
275+
for i in 1:length(k_)
276+
push!(arr, (k_[i]=>v_[i]))
277+
end
278+
tup = (arr...,)
279+
lookws = (;tup...)
232280

233281
# finalize object point clouds for cache
234282
# align if there if there is at least one LIE transform and cloud available.
@@ -237,7 +285,8 @@ function IncrementalInference.preambleCache(
237285
_PCL.alignPointCloudsLOOIters!(
238286
cache.o_Ts_ohat,
239287
cache.ohat_SCs,
240-
true
288+
true;
289+
lookws...
241290
)
242291
end
243292

@@ -361,8 +410,18 @@ function generateObjectAffordanceFromWorld!(
361410
w_BBobj::_PCL.AbstractBoundingBox;
362411
solveKey::Symbol = :default,
363412
pcBlobLabel = r"PCLPointCloud2",
364-
modelprior::Union{Nothing,<:_PCL.PointCloud}=nothing
413+
modelprior::Union{Nothing,<:_PCL.PointCloud}=nothing,
414+
loopObject::Bool = false,
415+
alignParams::Dict{Symbol,Int} = Dict{Symbol,Int}(
416+
:max_iterations => 40,
417+
:correspondences => 500,
418+
:neighbors => 50
419+
)
365420
)
421+
if 0 === length(vlbs)
422+
@error "No variables from which to build and object affordance."
423+
return nothing
424+
end
366425
M = SpecialEuclidean(3) # getManifold(Pose3)
367426
# add the object variable
368427
addVariable!(dfg, olb, Pose3; tags=[:OBJECT_AFFORDANCE])
@@ -374,24 +433,34 @@ function generateObjectAffordanceFromWorld!(
374433
end
375434

376435
# add the object affordance subcloud factors
377-
oas = ObjectAffordanceSubcloud()
436+
oas = ObjectAffordanceSubcloud(;loopObject, alignParams)
378437

379438
for vlb in vlbs
380439
# make sure PPE is set on this solveKey
381440
setPPE!(dfg, vlb, solveKey)
382-
# lhat frame is some local frame where object subclouds roughly fall together (could be host start from world frame)
383-
p_BBo, p_T_lhat = _PCL.transformFromWorldToLocal(dfg, vlb, w_BBobj; solveKey)
384-
lhat_T_p_ = inv(M, p_T_lhat)
385-
# make immutable data type
386-
lhat_T_p = ArrayPartition(SA[lhat_T_p_.x[1]...], SMatrix{3,3}(lhat_T_p_.x[2]))
387-
p_PC_blobId = getDataEntry(dfg, vlb, pcBlobLabel).id
388-
push!(oas.p_BBos, p_BBo)
389-
push!(oas.lhat_Ts_p, lhat_T_p)
390-
push!(oas.p_PC_blobIds, p_PC_blobId)
441+
if !loopObject
442+
# lhat frame is some local frame where object subclouds roughly fall together (could be host start from world frame)
443+
p_BBo, p_T_lhat = _PCL.transformFromWorldToLocal(dfg, vlb, w_BBobj; solveKey)
444+
lhat_T_p_ = inv(M, p_T_lhat)
445+
# make immutable data type
446+
lhat_T_p = ArrayPartition(SA[lhat_T_p_.x[1]...], SMatrix{3,3}(lhat_T_p_.x[2]))
447+
p_PC_blobId = getDataEntry(dfg, vlb, pcBlobLabel).id
448+
push!(oas.p_BBos, p_BBo)
449+
push!(oas.lhat_Ts_p, lhat_T_p)
450+
push!(oas.p_PC_blobIds, p_PC_blobId)
451+
else
452+
w_T_o = getBelief(dfg, vlb, solveKey) |> mean
453+
push!(oas.lhat_Ts_p, w_T_o) # inv(M, w_T_o)) # do not understand the inverse?
454+
end
391455
end
392456

393-
addFactor!(dfg, [olb; vlbs], oas)
394-
457+
try
458+
addFactor!(dfg, [olb; vlbs], oas)
459+
catch err
460+
deleteVariable!(dfg, olb)
461+
rethrow(err)
462+
end
463+
395464
oas
396465
end
397466

@@ -517,7 +586,8 @@ function protoObjectCheck!(
517586
minrange = 0.75,
518587
varList::AbstractVector{Symbol}=_PCL.findObjectVariablesFromWorld(dfg, w_BBo; solveKey, limit, minpoints, selection, minList, maxList),
519588
obl::Symbol = :testobj,
520-
align::Symbol = :fine
589+
align::Symbol = :fine,
590+
oaskw...
521591
)
522592
#
523593
try
@@ -529,7 +599,8 @@ function protoObjectCheck!(
529599
obl,
530600
varList,
531601
w_BBo;
532-
solveKey
602+
solveKey,
603+
oaskw...
533604
)
534605

535606
flb = intersect((ls.(dfg, varList))..., ls(dfg, obl))[1]

0 commit comments

Comments
 (0)