Add blending with lookup. In theory.

This commit is contained in:
Steven Robertson 2012-04-14 22:56:29 -07:00
parent a4178c60fb
commit 627bcf8921
5 changed files with 121 additions and 25 deletions

View File

@ -9,14 +9,73 @@ from itertools import izip_longest
from scipy.ndimage.filters import gaussian_filter1d
import spectypes
import spec
import util
from util import get
import specs
from use import Wrapper
from util import get, json_encode, resolve_spec
import variations
# TODO: move to better place before checkin
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
def node_to_anim(node, half):
if half:
osrc, odst = -0.25, 0.25
else:
osrc, odst = 0, 1
src = apply_temporal_offset(node, osrc)
dst = apply_temporal_offset(node, odst)
edge = dict(blend=dict(duration=odst-osrc, xform_sort='natural'))
return blend(src, dst, edge)
def edge_to_anim(gdb, edge):
edge = resolve(gdb, edge)
src, osrc = _split_ref_id(edge.link.src)
dst, odst = _split_ref_id(edge.link.dst)
src = apply_temporal_offset(resolve(gdb, src), osrc)
dst = apply_temporal_offset(resolve(gdb, dst), odst)
return blend(src, dst, edit)
def resolve(gdb, item):
"""
Given an item, recursively retrieve its base items, then merge according
to type. Returns the merged dict.
"""
is_edge = (item['type'] == 'edge')
spec = specs.toplevels[item['type']]
def go(i):
if i.get('base') is not None:
return go(gdb.get(i['base'])) + [i]
return [i]
items = map(flatten, go(item))
out = {}
for k in set(ik for i in items for ik in i):
sp = _resolve_spec(spec, k)
vs = [i.get(k) for i in items if k in i]
# TODO: dict and list negation; early-stage removal of negated knots?
if is_edge and isinstance(sp, (Spline, List)):
r = sum(vs, [])
else:
r = vs[-1]
out[k] = r
return unflatten(out)
def _split_ref_id(s):
sp = s.split('@')
if len(sp) == 1:
return sp, 0
return sp[0], float(sp[1])
def apply_temporal_offset(node, offset=0):
"""
Given a ``node`` dict, return a node with all periodic splines rotated by
``offset * velocity``, with the same velocity.
"""
class TemporalOffsetWrapper(Wrapper):
def wrap_spline(self, path, spec, val):
if spec.period is not None and isinstance(val, list) and val[1]:
position, velocity = val
return [position + offset * velocity, velocity]
return val
wr = TemporalOffsetWrapper(node)
return wr.visit(wr)
def blend(src, dst, edit={}):
"""
@ -26,19 +85,22 @@ def blend(src, dst, edit={}):
animation. These should be plain node dicts (hierarchical, pre-merged,
and adjusted for loop temporal offset).
``edit`` is an optional edit dict, also hierarchical and pre-merged.
``edge`` is an edge dict, also hierarchical and pre-merged. (It can be
empty, in violation of the spec, to support rendering straight from nodes
without having to insert anything into the genome database.)
Returns the animation spec as a plain dict.
"""
# By design, the blend element will contain only scalar values (no
# splines or hierarchy), so this can be done blindly
opts = dict(default_blend_opts)
opts = {}
for d in src, dst, edit:
opts.update(d.get('blend', {}))
opts = Wrapper(opts, specs.blend)
blended = merge_nodes(specs.node, src, dst, edit, opts['nloops'])
name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
explicit=zip(*opts.get('xform_map', [])))
blended = merge_nodes(specs.node, src, dst, edit, opts.nloops)
name_map = sort_xforms(src['xforms'], dst['xforms'], opts.xform_sort,
explicit=zip(*opts.xform_map))
blended['xforms'] = {}
for (sxf_key, dxf_key) in name_map:
@ -49,7 +111,7 @@ def blend(src, dst, edit={}):
blended['xforms'][bxf_key] = blend_xform(
src['xforms'].get(sxf_key),
dst['xforms'].get(dxf_key),
xf_edits, opts['nloops'])
xf_edits, opts.nloops)
if 'final_xform' in src or 'final_xform' in dst:
blended['final_xform'] = blend_xform(src.get('final_xform'),
@ -58,7 +120,7 @@ def blend(src, dst, edit={}):
# TODO: write 'info' section
# TODO: palflip
blended['type'] = 'animation'
blended.setdefault('time', {})['duration'] = opts['duration']
blended.setdefault('time', {})['duration'] = opts.duration
return blended
def merge_edits(sv, av, bv):
@ -75,16 +137,16 @@ def merge_edits(sv, av, bv):
else:
return bv if bv is not None else av
def tospline(spl, src, dst, edit, loops):
def split_node_val(val):
if val is None:
return spl.default, 0
if isinstance(val, (int, float)):
return val, 0
return val
def split_node_val(spl, val):
if val is None:
return spl.default, 0
if isinstance(val, (int, float)):
return val, 0
return val
sp, sv = split_node_val(src) # position, velocity
dp, dv = split_node_val(dst)
def tospline(spl, src, dst, edit, loops):
sp, sv = split_node_val(spl, src) # position, velocity
dp, dv = split_node_val(spl, dst)
# For variation parameters, copy missing values instead of using defaults
if spl.var:
@ -285,4 +347,4 @@ def palflip(gnm):
if __name__ == "__main__":
import sys, json
a, b, c = [json.load(open(f+'.json')) for f in 'abc']
print util.json_encode(blend(a, b, c))
print json_encode(blend(a, b, c))

View File

@ -8,6 +8,9 @@ import numpy as np
from variations import var_params
import util
# Re-exported
from blend import node_to_anim, edge_to_anim
class XMLGenomeParser(object):
"""
Parse an XML genome into a list of dictionaries.
@ -163,8 +166,10 @@ def apply_structure(struct, src):
out[l[0]] = v
return out
def convert_flame(flame):
return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
def flam3_to_node(flame):
n = util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
n['type'] = 'node'
return n
def convert_file(path):
"""Quick one-shot conversion for an XML genome."""
@ -173,7 +178,7 @@ def convert_file(path):
warnings.warn("Lot of flames in this file. Sure it's not a "
"frame-based animation?")
for flame in flames:
yield convert_flame(flame)
yield flam3_to_node(flame)
if __name__ == "__main__":
import sys

View File

@ -68,6 +68,13 @@ time = (
, 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
})
blend = (
{ 'nloops': scalar(2)
, 'duration': scalar(2)
, 'xform_sort': enum('weightflip weight natural color')
, 'xform_map': list_(list_(String('xfid'), d='A pair of src, dst IDs'))
})
base = (
{ 'name': String("Human-readable name of this work")
, 'camera': camera

View File

@ -62,6 +62,19 @@ class Wrapper(object):
return self.spec.type
return self.spec[name]
@classmethod
def visit(cls, obj):
"""
Visit every node. Note that for simplicity, this function will be
called on all elements (i.e. pivoting to a new Wrapper type inside the
wrapping function and overriding visit() won't do anything).
"""
if isinstance(obj, (Wrapper, dict)):
return dict((k, cls.visit(obj[k])) for k in obj)
elif isinstance(obj, list):
return [cls.visit(o) for o in obj]
return obj
def __getattr__(self, name):
return self.wrap(name, self.get_spec(name), self._val.get(name))
@ -155,6 +168,7 @@ class SplineEval(object):
return times, vals, t, scale
def __call__(self, itime, deriv=0):
# TODO: respect 'interp' THIS IS IMPORTANT.
times, vals, t, scale = self.find_knots(itime)
m1 = (vals[2] - vals[0]) / (1.0 - times[0])

View File

@ -2,6 +2,7 @@ import base64
import numpy as np
from cuburn.code.util import crep
import spectypes
def get(dct, default, *keys):
if len(keys) == 1:
@ -49,6 +50,13 @@ def unflatten(kvlist):
go(out, k.split('.'), v)
return out
def resolve_spec(sp, path):
for name in path:
if isinstance(sp, spectypes.Map):
sp = sp.type
else:
sp = sp[name]
return sp
def palette_decode(datastrs):
"""