Skip to content

Commit e10a236

Browse files
authored
Merge pull request #24 from JuliaRobotics/feat/3Q20/fix21/interval
add interval animation
2 parents 9a19f3d + e7a9b19 commit e10a236

File tree

4 files changed

+156
-4
lines changed

4 files changed

+156
-4
lines changed

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ addons:
44
apt:
55
packages:
66
- hdf5-tools
7+
- graphviz
78

89
arch:
910
- amd64
@@ -29,7 +30,7 @@ jobs:
2930
fast_finish: true
3031
allow_failures:
3132
- julia: nightly
32-
# - os: osx
33+
- os: osx
3334
# - arch: arm64
3435

3536
notifications:

Project.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "FunctionalStateMachine"
22
uuid = "3e9e306e-7e3c-11e9-12d2-8f8f67a2f951"
33
keywords = ["state machine"]
44
desc = "Functional state machine with stepping and visualization tools."
5-
version = "0.2.1"
5+
version = "0.2.2"
66

77
[deps]
88
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -17,7 +17,9 @@ Requires = "0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 1"
1717
julia = "0.7, 1"
1818

1919
[extras]
20+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
21+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
2022
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2123

2224
[targets]
23-
test = ["Test"]
25+
test = ["Test", "Graphs", "Dates"]

src/StateMachineAnimation.jl

+137-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export
55
drawStateTransitionStep,
66
drawStateMachineHistory,
77
animateStateMachineHistoryByTime,
8-
animateStateMachineHistoryByTimeCompound
8+
animateStateMachineHistoryByTimeCompound,
9+
animateStateMachineHistoryIntervalCompound
910

1011

1112
"""
@@ -287,3 +288,138 @@ function animateStateMachineHistoryByTimeCompound(hists::Dict{Symbol, Vector{Tup
287288
end
288289

289290
end
291+
292+
# count the total number of transitions contained in hists
293+
function getTotalNumberSteps( hists::Dict{Symbol, Vector{Tuple{DateTime, Int, <: Function, T}}} ) where T
294+
totSteps = 0
295+
for (whId, hist) in hists, hi in hist
296+
totSteps += 1
297+
end
298+
return totSteps
299+
end
300+
301+
# point to the start step among all history steps
302+
function getFirstStepHist( hists::Dict{Symbol, Vector{Tuple{DateTime, Int, <: Function, T}}} ) where T
303+
startTime = now()
304+
maxTime = DateTime(0)
305+
# NOTE, this whichId=:null is super important to ensure rendering loop can exit properly
306+
whichId, whichStep = :null, 0
307+
for (whId, hist) in hists, (st,hi) in enumerate(hist)
308+
if hi[1] < startTime
309+
# new starting point indicator
310+
whichId = whId
311+
whichStep = st
312+
startTime = hi[1]
313+
end
314+
if maxTime < hi[1]
315+
maxTime = hi[1]
316+
end
317+
end
318+
return whichId, whichStep, startTime, maxTime
319+
end
320+
321+
# give the next step, closest in time and that has not previously been added to `prevList`.
322+
# Also update prevList
323+
function getNextStepHist!(hists,
324+
intuple::Tuple{Symbol, Int, DateTime},
325+
maxTime::DateTime,
326+
prevList::Dict{Symbol, Vector{Int}} )
327+
#
328+
oldId, oldStep, oldT = intuple
329+
330+
whichId, whichStep, newT = :null, 0, maxTime
331+
for (whId, hist) in hists, (st,hi) in enumerate(hist)
332+
# make sure all options are populated in previous list tracker
333+
if !haskey(prevList, whId) prevList[whId] = Int[]; end
334+
if oldT < hi[1] && Millisecond(0) <= (hi[1] - oldT) < (newT-oldT) &&
335+
!(st in prevList[whId]) # must be a different step than before
336+
# new closest next step
337+
whichId = whId
338+
whichStep = st
339+
newT = hi[1]
340+
end
341+
end
342+
343+
# register this step has previously been taken
344+
if !haskey(prevList, whichId)
345+
prevList[whichId] = Int[]
346+
end
347+
push!(prevList[whichId], whichStep)
348+
349+
return whichId, whichStep, newT
350+
end
351+
352+
353+
# for slower movies, use a slower fps
354+
# run(`ffmpeg -r 10 -i /tmp/caesar/csmCompound/csm_%d.png -c:v libtheora -vf fps=5 -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -q 10 /tmp/caesar/csmCompound/out.ogv`)
355+
# @async run(`totem /tmp/caesar/csmCompound/out.ogv`)
356+
function animateStateMachineHistoryIntervalCompound(hists::Dict{Symbol, Vector{Tuple{DateTime, Int, <: Function, T}}};
357+
interval::Int=2, # frames
358+
# frames::Int=100,
359+
folder="animatestate",
360+
title::String="",
361+
show::Bool=false,
362+
clearstale::Bool=true,
363+
rmfirst::Bool=true ) where T
364+
#
365+
# Dict{Symbol, Vector{Symbol}}
366+
stateVisits = Dict{Symbol, Vector{Symbol}}()
367+
allStates = Vector{Symbol}()
368+
for (csym,hist) in hists
369+
stateVisits, allStates = histStateMachineTransitions(hist,allStates=allStates, stateVisits=stateVisits )
370+
end
371+
372+
#
373+
vg, lookup = histGraphStateMachineTransitions(stateVisits, allStates)
374+
375+
# total draw time and step initialization
376+
# totT = stopT - startT
377+
# totT = Millisecond(round(Int, 1.05*totT.value))
378+
# histsteps = ones(Int, length(hists))
379+
380+
# clear any stale state
381+
clearstale ? clearVisGraphAttributes!(vg) : nothing
382+
383+
totSteps = getTotalNumberSteps(hists)
384+
whId, fsmStep, aniT, maxTime = getFirstStepHist(hists)
385+
prevList = Dict{Symbol, Vector{Int}}()
386+
latestList = Dict{Symbol, Int}(whId => fsmStep)
387+
388+
frameCount = 0
389+
# loop across time
390+
@showprogress "exporting state machine images, $title " for stepCount in 1:totSteps
391+
# which step among the hist fsms is next
392+
if 1 < stepCount
393+
# skip first would-be repeat
394+
whId, fsmStep, aniT = getNextStepHist!(hists, (whId, fsmStep, aniT), maxTime, prevList)
395+
latestList[whId] = fsmStep
396+
end
397+
398+
# loop over all state "known" machines
399+
for (csym, lstep) in latestList
400+
# modify vg for each history
401+
csym == :null ? break : nothing
402+
lbl = getStateLabel(hists[csym][lstep][3])
403+
vertid = lookup[lbl]
404+
setVisGraphOnState!(vg, vertid, appendxlabel=string(csym)*",")
405+
end
406+
407+
# and draw as many frames for that setup
408+
for itr in 1:interval
409+
# increment frame counter
410+
frameCount += 1
411+
# finally render one frame
412+
renderStateMachineFrame(vg,
413+
frameCount,
414+
title=title,
415+
show=false,
416+
folder=folder,
417+
timest=string(split(string(aniT),' ')[1]),
418+
rmfirst=false )
419+
#
420+
end
421+
# clear current frame in prep for the next interval
422+
clearVisGraphAttributes!(vg)
423+
end
424+
425+
end

test/testStateMachine.jl

+13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11

22
using FunctionalStateMachine
3+
using Graphs
4+
using Dates
35
using Test
46

57
## User state functions
@@ -50,4 +52,15 @@ while statemachine(nothing, verbose=true); end
5052
end
5153

5254

55+
@testset "test recording and rendering of an FSM run" begin
56+
57+
statemachine = StateMachine{Nothing}(next=foo!)
58+
while statemachine(nothing, recordhistory=true); end
59+
60+
hists = Dict{Symbol,Vector{Tuple{DateTime,Int,Function,Nothing}}}(:first => statemachine.history)
61+
62+
animateStateMachineHistoryIntervalCompound(hists, interval=1)
63+
64+
end
65+
5366
#

0 commit comments

Comments
 (0)