-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #517 from JuliaRobotics/21Q4/enh/scalarfieldtests
standardize old ScalarFields test code
- Loading branch information
Showing
8 changed files
with
275 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# ScalarField functions related to Images.jl | ||
|
||
@info "Loading RoME.jl tools related to both ImageCore.jl and ImageIO.jl" | ||
|
||
using .ImageCore | ||
using .ImageIO | ||
|
||
export generateField_CanyonDEM | ||
|
||
|
||
""" | ||
$SIGNATURES | ||
Loads a sample DEM (as if simulated) on a regular grid... It's the Grand Canyon, 18x18km, 17m | ||
""" | ||
function generateField_CanyonDEM( scale=1, N=100; | ||
x_is_north=true, | ||
x_min::Real=-9000, x_max::Real=9000, | ||
y_min::Real=-9000, y_max::Real=9000) | ||
# | ||
filepath = joinpath(dirname(dirname(@__DIR__)), "data","CanyonDEM.png") | ||
img_ = load(filepath) .|> Gray | ||
img_ = scale.*Float64.(img_) | ||
|
||
N_ = minimum([N; size(img_)...]) | ||
img = img_[1:N_, 1:N_] | ||
|
||
# flip image so x-axis in plot is North and y-axis is West (ie img[-north,west], because top left is 0,0) | ||
_img_ = if x_is_north | ||
_img = collect(img') | ||
reverse(_img, dims=2) | ||
else | ||
# flip so north is down along with Images.jl [i,j] --> (x,y) | ||
reverse(img_, dims=1) | ||
end | ||
|
||
x = range(x_min, x_max, length = size(_img_,1)) # North | ||
y = range(y_min, y_max, length = size(_img_,2)) # East | ||
|
||
return (x, y, _img_) | ||
end | ||
|
||
|
||
|
||
# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# ScalarField related functions loaded when Interpolations.jl is available. | ||
|
||
|
||
""" | ||
$SIGNATURES | ||
Load gridded elevation data `dem` into a factor graph `fg` as a collection | ||
of Point3 variables. Each variable is connected to its 4-neighborhood by | ||
relative Point3Point3 MvNormal constraints with mean defined by their | ||
relative position on the grid (`x`, `y`) and covariance `meshEdgeSigma`. | ||
""" | ||
function _buildGraphScalarField!( fg::AbstractDFG, | ||
dem::AbstractMatrix, # assume grayscale image for now | ||
x::AbstractVector, | ||
y::AbstractVector; | ||
solvable::Int=0, | ||
marginalized::Bool=true, | ||
meshEdgeSigma=diagm([1;1;1]), | ||
refKey::Symbol = :simulated ) | ||
# | ||
# assume regular grid | ||
dx, dy = x[2]-x[1], y[2]-y[1] | ||
for i in 1:length(x) | ||
for j in 1:length(y) | ||
s = Symbol("pt$(i)_$(j)") # unique identifier | ||
pt = addVariable!(fg, s, Point3, solvable=solvable) | ||
setMarginalized!(pt, marginalized) # assume solveKey=:default | ||
|
||
# ... | ||
refVal = [x[i];y[j];dem[i,j]] | ||
simPPE = DFG.MeanMaxPPE(refKey, refVal, refVal, refVal) | ||
setPPE!(pt, refKey, typeof(simPPE), simPPE) | ||
|
||
# Regular grid triangulation: | ||
# add factor to (i-1,j) | | ||
# add factor to (i, j-1) - | ||
# add factor to (i-1, j-1) \ | ||
|
||
# no edges to prev row on first row | ||
if i>1 | ||
dVal1 = dem[i,j]-dem[i-1,j] | ||
f = Point3Point3(MvNormal([dx, 0, dVal1], meshEdgeSigma)) | ||
addFactor!(fg, [Symbol("pt$(i-1)_$(j)"), s], f, solvable=solvable, graphinit=false) | ||
end | ||
|
||
# no edges to prev column on first column | ||
if j>1 | ||
dVal2 = dem[i,j]-dem[i,j-1] | ||
f = Point3Point3(MvNormal([0, dy, dVal2], meshEdgeSigma)) | ||
addFactor!(fg, [Symbol("pt$(i)_$(j-1)"),s], f, solvable=solvable, graphinit=false) | ||
end | ||
|
||
# no edges to add on first element | ||
if i>1 && j>1 | ||
dVal3 = dem[i,j]-dem[i-1,j-1] | ||
f = Point3Point3(MvNormal([dx, dy, dVal3], meshEdgeSigma)) | ||
addFactor!(fg,[Symbol("pt$(i-1)_$(j-1)"),s], f, solvable=solvable, graphinit=false) | ||
end | ||
|
||
end | ||
end | ||
|
||
nothing | ||
end | ||
|
||
|
||
|
||
|
||
# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# test scalar field | ||
|
||
using Test | ||
using ImageCore, ImageIO | ||
using TensorCast | ||
using Interpolations | ||
using RoME | ||
|
||
|
||
## | ||
|
||
@testset "Basic low-res ScalarField localization" begin | ||
## | ||
|
||
# # load dem (18x18km span, ~17m/px) | ||
x_min, x_max = -9000, 9000 | ||
y_min, y_max = -9000, 9000 | ||
# north is regular map image up | ||
global img | ||
x, y, img = RoME.generateField_CanyonDEM(1, 100, x_is_north=false, x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max) | ||
|
||
|
||
|
||
## modify to generate elevation measurements (data/smallData as in Boxy) and priors | ||
|
||
dem = Interpolations.LinearInterpolation((x,y), img) # interpolated DEM | ||
elevation(p) = dem[getPPE(fg, p, :simulated).suggested[1:2]'...] | ||
sigma_e = 0.01 # elevation measurement uncertainty | ||
|
||
## test buildDEMSimulated | ||
|
||
im = (j->((i->dem[i,j]).(x))).(y); | ||
@cast im_[i,j] := im[j][i]; | ||
@test norm(im_ - img) < 1e-10 | ||
|
||
|
||
## | ||
|
||
|
||
function cb(fg_, lastpose) | ||
global dem, img | ||
|
||
# query DEM at ground truth | ||
z_e = elevation(lastpose) | ||
|
||
# generate noisy measurement | ||
@info "Callback for DEM heatmap priors" lastpose ls(fg_, lastpose) z_e | ||
|
||
# create prior | ||
hmd = HeatmapDensityRegular(img, (x,y), z_e, sigma_e, N=10000, sigma_scale=1) | ||
pr = PartialPriorPassThrough(hmd, (1,2)) | ||
addFactor!(fg_, [lastpose], pr, tags=[:DEM;], graphinit=false, nullhypo=0.1) | ||
nothing | ||
end | ||
|
||
|
||
## Testing | ||
|
||
# 0. init empty FG w/ datastore | ||
fg = initfg() | ||
storeDir = joinLogPath(fg,"data") | ||
mkpath(storeDir) | ||
datastore = FolderStore{Vector{UInt8}}(:default_folder_store, storeDir) | ||
addBlobStore!(fg, datastore) | ||
|
||
# new feature, going to temporarily disable as WIP | ||
getSolverParams(fg).attemptGradients = false | ||
|
||
## | ||
|
||
# 1. load DEM into the factor graph | ||
# point uncertainty - 2.5m horizontal, 1m vertical | ||
# horizontal uncertainty chosen so that 3sigma is approx half the resolution | ||
if false | ||
sigma = diagm([2.5, 2.5, 1.0]) | ||
@time loadDEM!(fg, img, (x), (y), meshEdgeSigma=sigma); | ||
end | ||
|
||
## | ||
|
||
# 2. generate trajectory | ||
|
||
μ0 = [-7000;-2000.0;pi/2] | ||
@time generateCanonicalFG_Helix2DSlew!(10, posesperturn=30, radius=1500, dfg=fg, μ0=μ0, graphinit=false, postpose_cb=cb) #, slew_x=1/20) | ||
deleteFactor!(fg, :x0f1) | ||
|
||
## | ||
|
||
# ensure specific solve settings | ||
getSolverParams(fg).useMsgLikelihoods = true | ||
getSolverParams(fg).graphinit = false | ||
getSolverParams(fg).treeinit = true | ||
|
||
## optional prior at start | ||
|
||
mu0 = getPPE(fg, :x0, :simulated).suggested | ||
pr0 = PriorPose2(MvNormal(mu0, 0.01.*[1;1;1;])) | ||
addFactor!(fg, [:x0], pr0) | ||
|
||
## | ||
|
||
tree = solveTree!(fg); | ||
|
||
## check at least the first five poses | ||
|
||
for lb in sortDFG(ls(fg,r"x\d+"))[1:5] | ||
sim = getPPE(fg, lb, :simulated).suggested | ||
ppe = getPPE(fg, lb).suggested | ||
@test isapprox(sim[1:2], ppe[1:2], atol=300) | ||
@test isapprox(sim[3], ppe[3], atol=0.5) | ||
end | ||
|
||
## | ||
|
||
try | ||
|
||
for lb in sortDFG(ls(fg,r"x\d+"))[6:end] | ||
sim = getPPE(fg, lb, :simulated).suggested | ||
ppe = getPPE(fg, lb).suggested | ||
@test isapprox(sim[1:2], ppe[1:2], atol=300) | ||
@test isapprox(sim[3], ppe[3], atol=0.5) | ||
end | ||
|
||
catch | ||
@error "ScalarField test failure on latter half poses" | ||
end | ||
|
||
## | ||
end | ||
|
||
## | ||
|
||
# using Cairo, RoMEPlotting | ||
# Gadfly.set_default_plot_size(35cm,20cm) | ||
|
||
# plotSLAM2D_KeyAndSim(fg) | ||
|
||
|
||
## |