Skip to content

Commit 9a227aa

Browse files
authored
saveDFG and loadDFG with metadata and Tar.jl (#1025)
* saveDFG and loadDFG with metadata and Tar.jl * Allow loadDFG with old format * save/loadDFG SolverParams
1 parent 5d6a35f commit 9a227aa

File tree

10 files changed

+193
-16
lines changed

10 files changed

+193
-16
lines changed

Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ version = "0.22.0"
55
[deps]
66
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
77
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
8+
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
89
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
910
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
1011
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1112
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1213
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
1314
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
15+
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
1416
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
1517
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1618
ManifoldsBase = "3362f125-f0bb-47a3-aa74-596ffd7ef2fb"
@@ -23,6 +25,7 @@ SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
2325
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2426
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2527
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
28+
Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
2629
TensorCast = "02d47bb6-7ce6-556a-be16-bb1710789e2b"
2730
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"
2831
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
@@ -35,6 +38,7 @@ DFGPlots = "GraphPlot"
3538

3639
[compat]
3740
CSV = "0.10"
41+
CodecZlib = "0.7"
3842
Colors = "0.10, 0.11, 0.12"
3943
Distributions = "0.23, 0.24, 0.25"
4044
DocStringExtensions = "0.8, 0.9"
@@ -50,6 +54,7 @@ RecursiveArrayTools = "2"
5054
Reexport = "1"
5155
StaticArrays = "1"
5256
StructTypes = "1"
57+
Tar = "1.9"
5358
TensorCast = "0.3.3, 0.4"
5459
TimeZones = "1.3.1"
5560
julia = "1.9"

src/DistributedFactorGraphs.jl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ using TensorCast
3131
using ProgressMeter
3232
using SHA
3333
using FileIO
34+
import Tar
35+
import CodecZlib
36+
3437
using OrderedCollections
3538
export OrderedDict
3639

@@ -70,7 +73,24 @@ export getDescription, setDescription!,
7073
getSessionData, setSessionData!,
7174
getAddHistory
7275

73-
export getSessionBlobEntry, addSessionBlobEntry!
76+
export getSessionBlobEntry,
77+
getSessionBlobEntries,
78+
addSessionBlobEntry!,
79+
addSessionBlobEntries!,
80+
updateSessionBlobEntry!,
81+
deleteSessionBlobEntry!,
82+
getRobotBlobEntry,
83+
getRobotBlobEntries,
84+
addRobotBlobEntry!,
85+
addRobotBlobEntries!,
86+
updateRobotBlobEntry!,
87+
deleteRobotBlobEntry!,
88+
getUserBlobEntry,
89+
getUserBlobEntries,
90+
addUserBlobEntry!,
91+
addUserBlobEntries!,
92+
updateUserBlobEntry!,
93+
deleteUserBlobEntry!
7494

7595
export getBlobStore,
7696
addBlobStore!,

src/FileDFG/services/FileDFG.jl

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ v1 = addVariable!(dfg, :a, ContinuousScalar, tags = [:POSE], solvable=0)
1717
saveDFG(dfg, "/tmp/saveDFG.tar.gz")
1818
```
1919
"""
20-
function saveDFG(folder::AbstractString, dfg::AbstractDFG)
20+
function saveDFG(folder::AbstractString, dfg::AbstractDFG; saveMetadata::Bool=true)
2121

2222
# TODO: Deprecate the folder functionality
2323

@@ -46,29 +46,40 @@ function saveDFG(folder::AbstractString, dfg::AbstractDFG)
4646
end
4747
# Factors
4848
@showprogress "saving factors" for f in factors
49-
fPacked = packFactor(dfg, f)
49+
fPacked = packFactor(f)
5050
JSON3.write("$factorFolder/$(f.label).json", fPacked)
5151
end
52+
#GraphsDFG metadata
53+
if saveMetadata
54+
@assert isa(dfg, GraphsDFG) "only metadata for GraphsDFG are supported"
55+
@info "saving dfg metadata"
56+
fgPacked = GraphsDFGs.packDFGMetadata(dfg)
57+
JSON3.write("$savepath/dfg.json", fgPacked)
58+
end
5259

5360
savedir = dirname(savepath) # is this a path of just local name? #344 -- workaround with unique names
5461
savename = basename(string(savepath))
5562
@assert savename != ""
5663
destfile = joinpath(savedir, savename*".tar.gz")
57-
# FIXME, switch to Tar.jl and Transcode Zlib / Codec, see #351
58-
if length(savedir) != 0
59-
run( pipeline(`tar -zcf - -C $savedir $savename`, stdout="$destfile"))
60-
else
61-
run( pipeline(`tar -zcf - $savename`, stdout="$destfile"))
62-
end
64+
65+
#create Tarbal using Tar.jl #351
66+
tar_gz = open(destfile, write=true)
67+
tar = CodecZlib.GzipCompressorStream(tar_gz)
68+
Tar.create(joinpath(savedir,savename), tar)
69+
close(tar)
70+
#not compressed version
71+
# Tar.create(joinpath(savedir,savename), destfile)
72+
6373
Base.rm(joinpath(savedir,savename), recursive=true)
6474
end
6575
# support both argument orders, #581
6676
saveDFG(dfg::AbstractDFG, folder::AbstractString) = saveDFG(folder, dfg)
6777

78+
#TODO loadDFG(dst::AbstractString) to load an equivalent dfg, but defined in IIF
79+
6880
"""
6981
$(SIGNATURES)
70-
Load a DFG from a saved folder. Always provide the IIF module as the second
71-
parameter.
82+
Load a DFG from a saved folder.
7283
7384
# Example
7485
```julia
@@ -81,7 +92,7 @@ loadDFG!(dfg, "/tmp/savedgraph.tar.gz")
8192
ls(dfg)
8293
```
8394
"""
84-
function loadDFG!(dfgLoadInto::AbstractDFG, dst::AbstractString)
95+
function loadDFG!(dfgLoadInto::AbstractDFG, dst::AbstractString; overwriteDFGMetadata::Bool=true, useDeprExtract::Bool=false)
8596

8697
#
8798
# loaddir gets deleted so needs to be unique
@@ -112,8 +123,31 @@ function loadDFG!(dfgLoadInto::AbstractDFG, dst::AbstractString)
112123
@info "loadDFG! detected a gzip $dstname -- unpacking via $loaddir now..."
113124
Base.rm(folder, recursive=true, force=true)
114125
# unzip the tar file
115-
run(`tar -zxf $dstname -C $loaddir`)
126+
127+
# TODO deprecated, remove. Kept for legacy support if older tarbals
128+
if useDeprExtract
129+
@warn "Old FileDFG compressed tar files are deprecated, load with useDeprExtract=true and use saveDFG again to update"
130+
run(`tar -zxf $dstname -C $loaddir`)
131+
else
132+
tar_gz = open(dstname)
133+
tar = CodecZlib.GzipDecompressorStream(tar_gz)
134+
Tar.extract(tar, folder)
135+
close(tar)
136+
end
137+
138+
#or for non-compressed
139+
# Tar.extract(dstname, folder)
116140
end
141+
142+
#GraphsDFG metadata
143+
if overwriteDFGMetadata
144+
@assert isa(dfgLoadInto, GraphsDFG) "Only GraphsDFG metadata are supported"
145+
@info "loading dfg metadata"
146+
jstr = read("$folder/dfg.json", String)
147+
fgPacked = JSON3.read(jstr, GraphsDFGs.PackedGraphsDFG)
148+
GraphsDFGs.unpackDFGMetadata!(dfgLoadInto, fgPacked)
149+
end
150+
117151
# extract the factor graph from fileDFG folder
118152
variables = DFGVariable[]
119153
factors = DFGFactor[]
@@ -175,5 +209,31 @@ function loadDFG!(dfgLoadInto::AbstractDFG, dst::AbstractString)
175209
return dfgLoadInto
176210
end
177211

178-
# to be extended by users with particular choices in dispatch.
179-
function loadDFG end
212+
function loadDFG(file::AbstractString)
213+
214+
# add if doesn't have .tar.gz extension
215+
if !contains(basename(file), ".tar.gz")
216+
file *= ".tar.gz"
217+
end
218+
# check the file actually exists
219+
@assert isfile(file) "cannot find file $file"
220+
221+
# only extract dfg.json to rebuild DFG object
222+
tar_gz = open(file)
223+
tar = CodecZlib.GzipDecompressorStream(tar_gz)
224+
loaddir = Tar.extract(hdr -> contains(hdr.path, "dfg.json"), tar)
225+
close(tar)
226+
227+
#Only GraphsDFG metadata supported
228+
jstr = read("$loaddir/dfg.json", String)
229+
fgPacked = JSON3.read(jstr, GraphsDFGs.PackedGraphsDFG)
230+
dfg = GraphsDFGs.unpackDFGMetadata(fgPacked)
231+
232+
233+
@debug "DFG.loadDFG! is deleting a temp folder created during unzip, $loaddir"
234+
# cleanup temporary folder
235+
Base.rm(loaddir, recursive=true, force=true)
236+
237+
return loadDFG!(dfg, file)
238+
239+
end

src/GraphsDFG/GraphsDFG.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ using DocStringExtensions
55
using UUIDs
66
using JSON3
77
using OrderedCollections
8+
using StructTypes
89

910
using ...DistributedFactorGraphs
1011

@@ -59,6 +60,7 @@ using .FactorGraphs
5960
# Imports
6061
include("entities/GraphsDFG.jl")
6162
include("services/GraphsDFG.jl")
63+
include("services/GraphsDFGSerialization.jl")
6264

6365
# Exports
6466
export GraphsDFG
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using InteractiveUtils
2+
3+
@kwdef struct PackedGraphsDFG{T<:AbstractParams}
4+
description::String
5+
userLabel::String
6+
robotLabel::String
7+
sessionLabel::String
8+
userData::Dict{Symbol, SmallDataTypes}
9+
robotData::Dict{Symbol, SmallDataTypes}
10+
sessionData::Dict{Symbol, SmallDataTypes}
11+
userBlobEntries::OrderedDict{Symbol, BlobEntry}
12+
robotBlobEntries::OrderedDict{Symbol, BlobEntry}
13+
sessionBlobEntries::OrderedDict{Symbol, BlobEntry}
14+
addHistory::Vector{Symbol}
15+
solverParams::T
16+
solverParams_type::String = string(typeof(solverParams))
17+
#TODO
18+
# blobStores::Dict{Symbol, AbstractBlobStore}
19+
end
20+
21+
StructTypes.StructType(::Type{PackedGraphsDFG}) = StructTypes.AbstractType()
22+
StructTypes.subtypekey(::Type{PackedGraphsDFG}) = :solverParams_type
23+
#TODO look at StructTypes.@register_struct_subtype when new StructTypes.jl is tagged (for type field)
24+
25+
function StructTypes.subtypes(::Type{PackedGraphsDFG})
26+
subs = subtypes(AbstractParams)
27+
NamedTuple(map(s->Symbol(s)=>PackedGraphsDFG{s}, subs))
28+
end
29+
30+
##
31+
"""
32+
$(SIGNATURES)
33+
Packing function to serialize DFG metadata from.
34+
"""
35+
function packDFGMetadata(fg::GraphsDFG)
36+
commonfields = intersect(fieldnames(PackedGraphsDFG), fieldnames(GraphsDFG))
37+
props = (k => getproperty(fg, k) for k in commonfields)
38+
return PackedGraphsDFG(;props...)
39+
end
40+
41+
function unpackDFGMetadata(packed::PackedGraphsDFG)
42+
commonfields = intersect(fieldnames(GraphsDFG), fieldnames(PackedGraphsDFG))
43+
props = (k => getproperty(packed, k) for k in commonfields)
44+
GraphsDFG(;props...)
45+
end
46+
47+
function unpackDFGMetadata!(dfg::GraphsDFG, packed::PackedGraphsDFG)
48+
commonfields = intersect(fieldnames(GraphsDFG), fieldnames(PackedGraphsDFG))
49+
props = (k => getproperty(packed, k) for k in commonfields)
50+
foreach(props) do (k,v)
51+
setproperty!(dfg, k, v)
52+
end
53+
return dfg
54+
end

src/entities/AbstractDFG.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ abstract type AbstractDFG{T<:AbstractParams} end
3737
$(TYPEDEF)
3838
Empty structure for solver parameters.
3939
"""
40-
struct NoSolverParams <: AbstractParams
40+
@kwdef struct NoSolverParams <: AbstractParams
41+
d::Int = 0#FIXME JSON3.jl error MethodError: no method matching read(::StructTypes.SingletonType, ...
4142
end
4243

44+
StructTypes.StructType(::NoSolverParams) = StructTypes.Struct()
45+
4346
"""
4447
Types valid for small data.
4548
"""

test/data/0_23_0.tar.gz

2.03 KB
Binary file not shown.

test/data/0_23_0_meta.tar.gz

2.11 KB
Binary file not shown.
File renamed without changes.

test/fileDFGTests.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@ using UUIDs
4242
#test multihypo
4343
addFactor!(dfg, [:x1, :x2, :x3], LinearRelative(Normal(50.0,2.0)), multihypo = [1, 0.3, 0.7])
4444

45+
46+
#test user/robot/session metadata
47+
48+
49+
#test user/robot/session blob entries
50+
be = BlobEntry(
51+
nothing,
52+
nothing,
53+
uuid4(),
54+
:testing2,
55+
:store,
56+
"",
57+
"",
58+
"",
59+
"",
60+
"",
61+
ZonedDateTime(2023, 2, 3, 20, tz"UTC+1"),
62+
"BlobEntry",
63+
string(DFG._getDFGVersion())
64+
)
65+
66+
addSessionBlobEntry!(dfg, be)
67+
#TODO addRobotBlobEntry!(dfg, be)
68+
#TODO addUserBlobEntry!(dfg, be)
69+
smallUserData = Dict{Symbol, SmallDataTypes}(:a => "42", :b => "small_user")
70+
smallRobotData = Dict{Symbol, SmallDataTypes}(:a => "43", :b => "small_robot")
71+
smallSessionData = Dict{Symbol, SmallDataTypes}(:a => "44", :b => "small_session")
72+
73+
setUserData!(dfg, smallUserData)
74+
setRobotData!(dfg, smallRobotData)
75+
setSessionData!(dfg, smallSessionData)
76+
77+
4578
# Save and load the graph to test.
4679
saveDFG(filename, dfg)
4780

0 commit comments

Comments
 (0)