mirror of
https://github.com/stevenrobertson/cuburn.git
synced 2025-02-05 03:30:05 -05:00
Checkpoint! Renders again. Many fixes outstanding.
This commit is contained in:
parent
9aa1a94aa1
commit
b53f703e6e
@ -260,13 +260,13 @@ haloclip(float4 *pixbuf, const float *denbuf, float gamma) {
|
||||
colorcliplib = devlib(deps=[yuvlib], defs=r'''
|
||||
__global__ void
|
||||
colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow,
|
||||
float linrange, float lingam, float3 bkgd)
|
||||
float linrange, float lingam)
|
||||
{
|
||||
GET_IDX(i);
|
||||
float4 pix = pixbuf[i];
|
||||
|
||||
if (pix.w <= 0) {
|
||||
pixbuf[i] = make_float4(bkgd.x, bkgd.y, bkgd.z, 0.0f);
|
||||
pixbuf[i] = make_float4(0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
pix.y -= 0.5f * pix.w;
|
||||
@ -321,10 +321,6 @@ colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow,
|
||||
pix.y += (1.0f - vibrance) * powf(opix.y, gamma);
|
||||
pix.z += (1.0f - vibrance) * powf(opix.z, gamma);
|
||||
|
||||
pix.x += (1.0f - alpha) * bkgd.x;
|
||||
pix.y += (1.0f - alpha) * bkgd.y;
|
||||
pix.z += (1.0f - alpha) * bkgd.z;
|
||||
|
||||
pix.x = fminf(1.0f, pix.x);
|
||||
pix.y = fminf(1.0f, pix.y);
|
||||
pix.z = fminf(1.0f, pix.z);
|
||||
|
@ -2,16 +2,14 @@ from collections import OrderedDict
|
||||
from itertools import cycle
|
||||
import numpy as np
|
||||
|
||||
from cuburn.genome.use import Wrapper, SplineEval
|
||||
|
||||
import util
|
||||
from util import Template, assemble_code, devlib, binsearchlib, ringbuflib
|
||||
from color import yuvlib
|
||||
from mwc import mwclib
|
||||
|
||||
class GenomePackerName(str):
|
||||
"""Class to indicate that a property is precalculated on the device"""
|
||||
pass
|
||||
|
||||
class GenomePackerView(object):
|
||||
class PackerWrapper(Wrapper):
|
||||
"""
|
||||
Obtain accessors in generated code.
|
||||
|
||||
@ -25,47 +23,46 @@ class GenomePackerView(object):
|
||||
code and an interpolator for use in generating that code. This conversion
|
||||
is done when the property is coerced into a string by the templating
|
||||
mechanism, so you can easily nest objects by saying, for instance,
|
||||
{{pcp.camera.rotation}} from within templated code. The accessed property
|
||||
must be a SplEval object, or a precalculated value (see
|
||||
``GenomePackerPrecalc``).
|
||||
|
||||
Index operations are converted to property accesses as well, so that you
|
||||
don't have to make a mess with 'getattr' in your code: {{pcp.xforms[x]}}
|
||||
works just fine. This means, however, that no arrays can be packed
|
||||
directly; they must be converted to have string-based keys first, and
|
||||
any loops must be unrolled in your code.
|
||||
{{pcp.camera.rotation}} from within templated code.
|
||||
"""
|
||||
def __init__(self, packer, ptr_name, wrapped, prefix=()):
|
||||
self.packer = packer
|
||||
self.ptr_name = ptr_name
|
||||
self.wrapped = wrapped
|
||||
self.prefix = prefix
|
||||
def __init__(self, packer, val, spec=None, path=()):
|
||||
super(PackerWrapper, self).__init__(val, spec)
|
||||
self.packer, self.path = packer, path
|
||||
|
||||
def wrap_dict(self, path, spec, val):
|
||||
return type(self)(self.packer, val, spec, path)
|
||||
|
||||
def wrap_spline(self, path, spec, val):
|
||||
return PackerSpline(self.packer, path, spec)
|
||||
|
||||
def __getattr__(self, name):
|
||||
w = getattr(self.wrapped, name)
|
||||
return type(self)(self.packer, self.ptr_name, w, self.prefix+(name,))
|
||||
# As with the Genome class, we're all-dict, no-array here
|
||||
__getitem__ = lambda s, n: getattr(s, str(n))
|
||||
path = self.path + (name,)
|
||||
if path in self.packer.packed_precalc:
|
||||
return self.packer.devname(path)
|
||||
return super(PackerWrapper, self).__getattr__(name)
|
||||
|
||||
def _precalc(self):
|
||||
"""Create a GenomePackerPrecalc object. See that class for details."""
|
||||
return PrecalcWrapper(self.packer, self._val, self.spec, self.path)
|
||||
|
||||
class PackerSpline(object):
|
||||
def __init__(self, packer, path, spec):
|
||||
self.packer, self.path, self.spec = packer, path, spec
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns the packed name in a format suitable for embedding directly
|
||||
into device code.
|
||||
"""
|
||||
# So evil. When the template calls __str__ to format the output, we
|
||||
# allocate things. This makes for neater embedded code, which is where
|
||||
# the real complexity lies, but it also means printf() debugging when
|
||||
# templating will screw with the allocation tables!
|
||||
if not isinstance(self.wrapped, GenomePackerName):
|
||||
self.packer._require(self.prefix)
|
||||
# TODO: verify namespace stomping, etc
|
||||
return '%s.%s' % (self.ptr_name, '_'.join(self.prefix))
|
||||
# When the template calls __str__ to format one of these splines, this
|
||||
# allocates the corresponding spline.
|
||||
return self.packer._require(self.spec, self.path)
|
||||
|
||||
def _precalc(self):
|
||||
"""Create a GenomePackerPrecalc object. See that class for details."""
|
||||
return GenomePackerPrecalc(self.packer, self.ptr_name,
|
||||
self.wrapped, self.prefix)
|
||||
class PrecalcSpline(PackerSpline):
|
||||
def __str__(self):
|
||||
return self.packer._require_pre(self.spec, self.path)
|
||||
|
||||
class GenomePackerPrecalc(GenomePackerView):
|
||||
class PrecalcWrapper(PackerWrapper):
|
||||
"""
|
||||
Insert precalculated values into the packed genome.
|
||||
|
||||
@ -91,35 +88,24 @@ class GenomePackerPrecalc(GenomePackerView):
|
||||
|
||||
Example:
|
||||
|
||||
def do_precalc(px):
|
||||
pcam = px._precalc()
|
||||
def do_precalc(pcam):
|
||||
pcam._code(Template('''
|
||||
{{pcam._set('prop_sin')}} = sin({{pcam.prop}});
|
||||
''').substitute(pcam=pcam))
|
||||
|
||||
def gen_code(px):
|
||||
return Template('''
|
||||
{{do_precalc(px)}}
|
||||
{{do_precalc(px._precalc())}}
|
||||
printf("The sin of %g is %g.", {{px.prop}}, {{px.prop_sin}});
|
||||
''').substitute(px=px)
|
||||
"""
|
||||
def __init__(self, packer, ptr_name, wrapped, prefix):
|
||||
super(GenomePackerPrecalc, self).__init__(packer, 'out', wrapped, prefix)
|
||||
def __str__(self):
|
||||
return self.packer._require_pre(self.prefix)
|
||||
def _magscale(self):
|
||||
"""
|
||||
This is a temporary hack which turns on magnitude scaling for the
|
||||
value on which it is called. Takes the place of __str__ serialization.
|
||||
"""
|
||||
return self.packer._require_pre(self.prefix, True)
|
||||
def wrap_spline(self, path, spec, val):
|
||||
return PrecalcSpline(self.packer, path, spec)
|
||||
|
||||
def _set(self, name):
|
||||
fullname = self.prefix + (name,)
|
||||
self.packer._pre_alloc(fullname)
|
||||
# This just modifies the underlying object, because I'm too lazy right
|
||||
# now to ghost the namespace
|
||||
self.wrapped[name] = GenomePackerName('_'.join(fullname))
|
||||
return '%s->%s' % (self.ptr_name, self.wrapped[name])
|
||||
path = self.path + (name,)
|
||||
return self.packer._pre_alloc(path)
|
||||
|
||||
def _code(self, code):
|
||||
self.packer.precalc_code.append(code)
|
||||
|
||||
@ -127,26 +113,27 @@ class GenomePacker(object):
|
||||
"""
|
||||
Packs a genome for use in iteration.
|
||||
"""
|
||||
def __init__(self, tname):
|
||||
def __init__(self, tname, ptr_name, spec):
|
||||
"""
|
||||
Create a new DataPacker.
|
||||
|
||||
``tname`` is the name of the structure typedef that will be emitted
|
||||
via this object's ``decls`` property.
|
||||
"""
|
||||
self.tname = tname
|
||||
self.tname, self.ptr_name, self.spec = tname, ptr_name, spec
|
||||
# We could do this in the order that things are requested, but we want
|
||||
# to be able to treat the direct stuff as a list so this function
|
||||
# doesn't unroll any more than it has to. So we separate things into
|
||||
# direct requests, and those that need precalculation.
|
||||
# Values of OrderedDict are unused; basically, it's just OrderedSet.
|
||||
self.packed_direct = OrderedDict()
|
||||
# Feel kind of bad about this, but it's just under the threshold of
|
||||
# being worth refactoring to be agnostic to interpolation types
|
||||
self.packed_direct_mag = OrderedDict()
|
||||
self.genome_precalc = OrderedDict()
|
||||
self.packed_precalc = OrderedDict()
|
||||
self.precalc_code = []
|
||||
|
||||
self.ns = {}
|
||||
|
||||
self._len = None
|
||||
self.decls = None
|
||||
self.defs = None
|
||||
@ -156,29 +143,37 @@ class GenomePacker(object):
|
||||
self.search_rounds = util.DEFAULT_SEARCH_ROUNDS
|
||||
|
||||
def __len__(self):
|
||||
"""Length in elements. (*4 for length in bytes.)"""
|
||||
assert self._len is not None, 'len() called before finalize()'
|
||||
return self._len
|
||||
|
||||
def view(self, ptr_name, wrapped_obj, prefix):
|
||||
def view(self, val={}):
|
||||
"""Create a DataPacker view. See DataPackerView class for details."""
|
||||
self.ns[prefix] = wrapped_obj
|
||||
return GenomePackerView(self, ptr_name, wrapped_obj, (prefix,))
|
||||
return PackerWrapper(self, val, self.spec)
|
||||
|
||||
def _require(self, name):
|
||||
def _require(self, spec, path):
|
||||
"""
|
||||
Called to indicate that the named parameter from the original genome
|
||||
must be available during interpolation.
|
||||
"""
|
||||
self.packed_direct[name] = None
|
||||
if spec.interp == 'mag':
|
||||
self.packed_direct_mag[path] = None
|
||||
else:
|
||||
self.packed_direct[path] = None
|
||||
return self.devname(path)
|
||||
|
||||
def _require_pre(self, name, mag_scaling=False):
|
||||
def _require_pre(self, spec, path):
|
||||
i = len(self.genome_precalc) << self.search_rounds
|
||||
self.genome_precalc[name] = None
|
||||
name = 'catmull_rom_mag' if mag_scaling else 'catmull_rom'
|
||||
return '%s(×[%d], &knots[%d], time)' % (name, i, i)
|
||||
self.genome_precalc[path] = None
|
||||
func = 'catmull_rom_mag' if spec.interp == 'mag' else 'catmull_rom'
|
||||
return '%s(×[%d], &knots[%d], time)' % (func, i, i)
|
||||
|
||||
def _pre_alloc(self, name):
|
||||
self.packed_precalc[name] = None
|
||||
def _pre_alloc(self, path):
|
||||
self.packed_precalc[path] = None
|
||||
return '%s->%s' % (self.ptr_name, '_'.join(path))
|
||||
|
||||
def devname(self, path):
|
||||
return '%s.%s' % (self.ptr_name, '_'.join(path))
|
||||
|
||||
def finalize(self):
|
||||
"""
|
||||
@ -187,20 +182,18 @@ class GenomePacker(object):
|
||||
# At the risk of packing a few things more than once, we don't
|
||||
# uniquify the overall precalc order, sparing us the need to implement
|
||||
# recursive code generation
|
||||
self.packed = self.packed_direct.keys() + self.packed_precalc.keys()
|
||||
self.genome = self.packed_direct.keys() + self.genome_precalc.keys()
|
||||
direct = self.packed_direct.keys() + self.packed_direct_mag.keys()
|
||||
self.packed = direct + self.packed_precalc.keys()
|
||||
self.genome = direct + self.genome_precalc.keys()
|
||||
|
||||
self._len = len(self.packed)
|
||||
|
||||
decls = self._decls.substitute(packed=self.packed, tname=self.tname)
|
||||
defs = self._defs.substitute(
|
||||
packed_direct=self.packed_direct, tname=self.tname,
|
||||
precalc_code=self.precalc_code,
|
||||
search_rounds=self.search_rounds)
|
||||
decls = self._decls.substitute(**self.__dict__)
|
||||
defs = self._defs.substitute(**self.__dict__)
|
||||
|
||||
return devlib(deps=[catmullromlib], decls=decls, defs=defs)
|
||||
|
||||
def pack(self, pool=None):
|
||||
def pack(self, gnm, pool=None):
|
||||
"""
|
||||
Return a packed copy of the genome ready for uploading to the GPU,
|
||||
as two float32 NDArrays for the knot times and values.
|
||||
@ -213,50 +206,59 @@ class GenomePacker(object):
|
||||
times, knots = np.empty((2, len(self.genome), width), 'f4')
|
||||
times.fill(1e9)
|
||||
|
||||
for idx, gname in enumerate(self.genome):
|
||||
attr = self.ns[gname[0]]
|
||||
for g in gname[1:]:
|
||||
attr = getattr(attr, g)
|
||||
times[idx,:len(attr.knots[0])] = attr.knots[0]
|
||||
knots[idx,:len(attr.knots[1])] = attr.knots[1]
|
||||
for idx, path in enumerate(self.genome):
|
||||
attr = gnm
|
||||
for name in path:
|
||||
attr = attr[name]
|
||||
attr = SplineEval.normalize(attr)
|
||||
times[idx,:len(attr[0])] = attr[0]
|
||||
knots[idx,:len(attr[1])] = attr[1]
|
||||
return times, knots
|
||||
|
||||
_defs = Template(r"""
|
||||
__global__ void interp_{{tname}}(
|
||||
{{tname}}* out,
|
||||
{{tname}}* {{ptr_name}},
|
||||
const float *times, const float *knots,
|
||||
float tstart, float tstep, int maxid)
|
||||
{
|
||||
int id = gtid();
|
||||
if (id >= maxid) return;
|
||||
out = &out[id];
|
||||
{{ptr_name}} = &{{ptr_name}}[id];
|
||||
float time = tstart + id * tstep;
|
||||
|
||||
float *outf = reinterpret_cast<float*>(out);
|
||||
float *outf = reinterpret_cast<float*>({{ptr_name}});
|
||||
|
||||
{{py:lpd = len(packed_direct)}}
|
||||
{{py:lpdm = len(packed_direct_mag)}}
|
||||
|
||||
// TODO: unroll pragma?
|
||||
for (int i = 0; i < {{len(packed_direct)}}; i++) {
|
||||
for (int i = 0; i < {{lpd}}; i++) {
|
||||
int j = i << {{search_rounds}};
|
||||
outf[i] = catmull_rom(×[j], &knots[j], time);
|
||||
}
|
||||
|
||||
for (int i = {{lpd}}; i < {{lpd+lpdm}}; i++) {
|
||||
int j = i << {{search_rounds}};
|
||||
outf[i] = catmull_rom_mag(×[j], &knots[j], time);
|
||||
}
|
||||
|
||||
// Advance 'times' and 'knots' to the purely generated sections, so that
|
||||
// the pregenerated statements emitted by _require_pre are correct.
|
||||
times = ×[{{len(packed_direct)<<search_rounds}}];
|
||||
knots = &knots[{{len(packed_direct)<<search_rounds}}];
|
||||
times = ×[{{(lpd+lpdm)<<search_rounds}}];
|
||||
knots = &knots[{{(lpd+lpdm)<<search_rounds}}];
|
||||
|
||||
{{for hunk in precalc_code}}
|
||||
if (1) {
|
||||
{
|
||||
{{hunk}}
|
||||
}
|
||||
}
|
||||
{{endfor}}
|
||||
}
|
||||
""")
|
||||
|
||||
_decls = Template(r"""
|
||||
typedef struct {
|
||||
{{for name in packed}}
|
||||
float {{'_'.join(name)}};
|
||||
{{for path in packed}}
|
||||
float {{'_'.join(path)}};
|
||||
{{endfor}}
|
||||
} {{tname}};
|
||||
|
||||
|
@ -7,54 +7,53 @@ import interp
|
||||
from util import Template, devlib, ringbuflib
|
||||
from mwc import mwclib
|
||||
|
||||
def precalc_densities(pcp, std_xforms):
|
||||
import cuburn.genome.spec
|
||||
|
||||
def precalc_densities(cp):
|
||||
# This pattern recurs a few times for precalc segments. Unfortunately,
|
||||
# namespace stuff means it's not easy to functionalize this boilerplate
|
||||
pre_cp = pcp._precalc()
|
||||
pre_cp._code(Template(r"""
|
||||
cp._code(Template(r"""
|
||||
float sum = 0.0f;
|
||||
|
||||
{{for n in std_xforms}}
|
||||
float den_{{n}} = {{pre_cp.xforms[n].density}};
|
||||
{{for n in cp.xforms}}
|
||||
float den_{{n}} = {{cp.xforms[n].weight}};
|
||||
sum += den_{{n}};
|
||||
{{endfor}}
|
||||
|
||||
float rsum = 1.0f / sum;
|
||||
sum = 0.0f;
|
||||
|
||||
{{for n in std_xforms[:-1]}}
|
||||
{{for n in cp.xforms.keys()[:-1]}}
|
||||
sum += den_{{n}} * rsum;
|
||||
{{pre_cp._set('den_' + n)}} = sum;
|
||||
{{cp._set('den_' + n)}} = sum;
|
||||
{{endfor}}
|
||||
""", name='precalc_densities').substitute(locals()))
|
||||
""", name='precalc_densities').substitute(cp=cp))
|
||||
|
||||
def precalc_chaos(pcp, std_xforms):
|
||||
pre_cp = pcp._precalc()
|
||||
pre_cp._code(Template("""
|
||||
def precalc_chaos(cp):
|
||||
cp._code(Template("""
|
||||
float sum, rsum;
|
||||
|
||||
{{for p in std_xforms}}
|
||||
{{for p in cp.xforms}}
|
||||
sum = 0.0f;
|
||||
|
||||
{{for n in std_xforms}}
|
||||
float den_{{p}}_{{n}} = {{pre_cp.xforms[p].chaos[n]}};
|
||||
{{for n in cp.xforms}}
|
||||
float den_{{p}}_{{n}} = {{cp.xforms[n].weight}}
|
||||
* {{cp.xforms[p].chaos[n]}};
|
||||
sum += den_{{p}}_{{n}};
|
||||
{{endfor}}
|
||||
|
||||
rsum = 1.0f / sum;
|
||||
sum = 0.0f;
|
||||
|
||||
{{for n in std_xforms[:-1]}}
|
||||
{{for n in cp.xforms.keys()[:-1]}}
|
||||
sum += den_{{p}}_{{n}} * rsum;
|
||||
{{pre_cp._set('chaos_%s_%s' % (p, n))}} = sum;
|
||||
{{cp._set('chaos_%s_%s' % (p, n))}} = sum;
|
||||
{{endfor}}
|
||||
|
||||
{{endfor}}
|
||||
""", name='precalc_chaos').substitute(locals()))
|
||||
|
||||
def precalc_camera(pcam):
|
||||
pre_cam = pcam._precalc()
|
||||
""", name='precalc_chaos').substitute(cp=cp))
|
||||
|
||||
def precalc_camera(cam):
|
||||
# Maxima code to check my logic:
|
||||
# matrix([1,0,0.5*width + g],[0,1,0.5*height+g],[0,0,1])
|
||||
# . matrix([width * scale,0,0], [0,width * scale,0], [0,0,1])
|
||||
@ -62,41 +61,41 @@ def precalc_camera(pcam):
|
||||
# . matrix([1,0,-cenx],[0,1,-ceny],[0,0,1])
|
||||
# . matrix([X],[Y],[1]);
|
||||
|
||||
pre_cam._code(Template(r"""
|
||||
float rot = {{pre_cam.rotation}} * M_PI / 180.0f;
|
||||
cam._code(Template(r"""
|
||||
float rot = {{cam.rotation}} * M_PI / 180.0f;
|
||||
float rotsin = sin(rot), rotcos = cos(rot);
|
||||
float cenx = {{pre_cam.center.x}}, ceny = {{pre_cam.center.y}};
|
||||
float scale = {{pre_cam.scale}} * acc_size.width;
|
||||
float cenx = {{cam.center.x}}, ceny = {{cam.center.y}};
|
||||
float scale = {{cam.scale}} * acc_size.width;
|
||||
|
||||
{{pre_cam._set('xx')}} = scale * rotcos;
|
||||
{{pre_cam._set('xy')}} = scale * -rotsin;
|
||||
{{pre_cam._set('xo')}} = scale * (rotsin * ceny - rotcos * cenx)
|
||||
+ 0.5f * acc_size.awidth;
|
||||
{{cam._set('xx')}} = scale * rotcos;
|
||||
{{cam._set('xy')}} = scale * -rotsin;
|
||||
{{cam._set('xo')}} = scale * (rotsin * ceny - rotcos * cenx)
|
||||
+ 0.5f * acc_size.awidth;
|
||||
|
||||
{{pre_cam._set('yx')}} = scale * rotsin;
|
||||
{{pre_cam._set('yy')}} = scale * rotcos;
|
||||
{{pre_cam._set('yo')}} = scale * -(rotsin * cenx + rotcos * ceny)
|
||||
+ 0.5f * acc_size.aheight;
|
||||
""", 'precalc_camera').substitute(locals()))
|
||||
{{cam._set('yx')}} = scale * rotsin;
|
||||
{{cam._set('yy')}} = scale * rotcos;
|
||||
{{cam._set('yo')}} = scale * -(rotsin * cenx + rotcos * ceny)
|
||||
+ 0.5f * acc_size.aheight;
|
||||
""", 'precalc_camera').substitute(cam=cam))
|
||||
|
||||
def precalc_xf_affine(px):
|
||||
pre = px._precalc()
|
||||
pre._code(Template(r"""
|
||||
float pri = {{pre.angle}} * M_PI / 180.0f;
|
||||
float spr = {{pre.spread}} * M_PI / 180.0f;
|
||||
px._code(Template(r"""
|
||||
float pri = {{px.angle}} * M_PI / 180.0f;
|
||||
float spr = {{px.spread}} * M_PI / 180.0f;
|
||||
|
||||
float magx = {{pre.magnitude.x._magscale()}};
|
||||
float magy = {{pre.magnitude.y._magscale()}};
|
||||
float magx = {{px.magnitude.x}};
|
||||
float magy = {{px.magnitude.y}};
|
||||
|
||||
{{pre._set('xx')}} = magx * cos(pri-spr);
|
||||
{{pre._set('yx')}} = -magx * sin(pri-spr);
|
||||
{{pre._set('xy')}} = -magy * cos(pri+spr);
|
||||
{{pre._set('yy')}} = magy * sin(pri+spr);
|
||||
{{pre._set('xo')}} = {{pre.offset.x._magscale()}};
|
||||
{{pre._set('yo')}} = -{{pre.offset.y._magscale()}};
|
||||
""", 'precalc_xf_affine').substitute(locals()))
|
||||
{{px._set('xx')}} = magx * cos(pri-spr);
|
||||
{{px._set('yx')}} = -magx * sin(pri-spr);
|
||||
{{px._set('xy')}} = -magy * cos(pri+spr);
|
||||
{{px._set('yy')}} = magy * sin(pri+spr);
|
||||
{{px._set('xo')}} = {{px.offset.x}};
|
||||
{{px._set('yo')}} = -{{px.offset.y}};
|
||||
""", 'precalc_xf_affine').substitute(px=px))
|
||||
|
||||
def apply_affine(x, y, xo, yo, packer):
|
||||
def apply_affine(names, packer):
|
||||
x, y, xo, yo = names.split()
|
||||
return Template("""
|
||||
{{xo}} = {{packer.xx}} * {{x}} + {{packer.xy}} * {{y}} + {{packer.xo}};
|
||||
{{yo}} = {{packer.yx}} * {{x}} + {{packer.yy}} * {{y}} + {{packer.yo}};
|
||||
@ -126,25 +125,24 @@ __device__
|
||||
void apply_xf_{{xfid}}(float &ox, float &oy, float &color, mwc_st &rctx) {
|
||||
float tx, ty;
|
||||
|
||||
{{precalc_xf_affine(px.affine)}}
|
||||
{{apply_affine('ox', 'oy', 'tx', 'ty', px.affine)}}
|
||||
{{precalc_xf_affine(px.pre_affine._precalc())}}
|
||||
{{apply_affine('ox oy tx ty', px.pre_affine)}}
|
||||
|
||||
ox = 0;
|
||||
oy = 0;
|
||||
|
||||
{{for name in xform.variations}}
|
||||
if (1) {
|
||||
{{py:pv = px.variations[name]}}
|
||||
{{for name, pv in px.variations.items()}}
|
||||
{
|
||||
float w = {{pv.weight}};
|
||||
{{variations.var_code[name].substitute(locals())}}
|
||||
}
|
||||
}
|
||||
{{endfor}}
|
||||
|
||||
{{if 'post' in xform}}
|
||||
{{if 'post_affine' in px}}
|
||||
tx = ox;
|
||||
ty = oy;
|
||||
{{precalc_xf_affine(px.post)}}
|
||||
{{apply_affine('tx', 'ty', 'ox', 'oy', px.post)}}
|
||||
{{precalc_xf_affine(px.post_affine._precalc())}}
|
||||
{{apply_affine('tx ty ox oy', px.post_affine)}}
|
||||
{{endif}}
|
||||
|
||||
float csp = {{px.color_speed}};
|
||||
@ -152,10 +150,8 @@ void apply_xf_{{xfid}}(float &ox, float &oy, float &color, mwc_st &rctx) {
|
||||
};
|
||||
"""
|
||||
|
||||
def iter_xf_body(pcp, xfid, xform):
|
||||
px = pcp.xforms[xfid]
|
||||
def iter_xf_body(cp, xfid, px):
|
||||
tmpl = Template(iter_xf_body_code, 'apply_xf_'+xfid)
|
||||
|
||||
g = dict(globals())
|
||||
g.update(locals())
|
||||
return tmpl.substitute(g)
|
||||
@ -176,13 +172,16 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
|
||||
int this_rb_idx = rb_incr(rb->head, blockDim.x * threadIdx.y + threadIdx.x);
|
||||
mwc_st rctx = msts[this_rb_idx];
|
||||
|
||||
{{precalc_camera(pcp.camera)}}
|
||||
{{precalc_camera(cp.camera._precalc())}}
|
||||
if (threadIdx.y == 5 && threadIdx.x == 4) {
|
||||
float ditherwidth = {{pcp.camera.dither_width}} * 0.5f;
|
||||
{{pcp.camera.xo}} += ditherwidth * mwc_next_11(rctx);
|
||||
{{pcp.camera.yo}} += ditherwidth * mwc_next_11(rctx);
|
||||
if (blockIdx.x == 0)
|
||||
printf("Hiya %f\n", {{cp.camera.xx}});
|
||||
float ditherwidth = {{cp.camera.dither_width}} * 0.5f;
|
||||
{{cp.camera.xo}} += ditherwidth * mwc_next_11(rctx);
|
||||
{{cp.camera.yo}} += ditherwidth * mwc_next_11(rctx);
|
||||
}
|
||||
|
||||
|
||||
// TODO: spare the register, reuse at call site?
|
||||
int time = blockIdx.x >> 4;
|
||||
float color_dither = 0.49f * mwc_next_11(rctx);
|
||||
@ -229,24 +228,26 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
|
||||
color = mwc_next_01(rctx);
|
||||
}
|
||||
|
||||
|
||||
{{py:xk = cp.xforms.keys()}}
|
||||
{{if chaos_used}}
|
||||
|
||||
{{precalc_chaos(pcp, std_xforms)}}
|
||||
{{precalc_chaos(cp)}}
|
||||
|
||||
// For now, we don't attempt to use the swap buffer when chaos is used
|
||||
float xfsel = mwc_next_01(rctx);
|
||||
|
||||
{{for prior_xform_idx, prior_xform_name in enumerate(std_xforms)}}
|
||||
{{for prior_xform_idx, prior_xform_name in enumerate(xk)}}
|
||||
if (last_xf_used == {{prior_xform_idx}}) {
|
||||
{{for xform_idx, xform_name in enumerate(std_xforms[:-1])}}
|
||||
if (xfsel <= {{pcp['chaos_'+prior_xform_name+'_'+xform_name]}}) {
|
||||
{{for xform_idx, xform_name in enumerate(xk[:-1])}}
|
||||
if (xfsel <= {{cp['chaos_'+prior_xform_name+'_'+xform_name]}}) {
|
||||
apply_xf_{{xform_name}}(x, y, color, rctx);
|
||||
last_xf_used = {{xform_idx}};
|
||||
} else
|
||||
{{endfor}}
|
||||
{
|
||||
apply_xf_{{std_xforms[-1]}}(x, y, color, rctx);
|
||||
last_xf_used = {{len(std_xforms)-1}};
|
||||
apply_xf_{{xk[-1]}}(x, y, color, rctx);
|
||||
last_xf_used = {{len(xk)-1}};
|
||||
}
|
||||
} else
|
||||
{{endfor}}
|
||||
@ -256,18 +257,18 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
|
||||
}
|
||||
|
||||
{{else}}
|
||||
{{precalc_densities(pcp, std_xforms)}}
|
||||
{{precalc_densities(cp._precalc())}}
|
||||
float xfsel = cosel[threadIdx.y];
|
||||
|
||||
{{for xform_idx, xform_name in enumerate(std_xforms[:-1])}}
|
||||
if (xfsel <= {{pcp['den_'+xform_name]}}) {
|
||||
{{for xform_idx, xform_name in enumerate(xk[:-1])}}
|
||||
if (xfsel <= {{cp['den_'+xform_name]}}) {
|
||||
apply_xf_{{xform_name}}(x, y, color, rctx);
|
||||
last_xf_used = {{xform_idx}};
|
||||
} else
|
||||
{{endfor}}
|
||||
{
|
||||
apply_xf_{{std_xforms[-1]}}(x, y, color, rctx);
|
||||
last_xf_used = {{len(std_xforms)-1}};
|
||||
apply_xf_{{xk[-1]}}(x, y, color, rctx);
|
||||
last_xf_used = {{len(xk)-1}};
|
||||
}
|
||||
|
||||
// Rotate points between threads.
|
||||
@ -298,18 +299,14 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
|
||||
continue;
|
||||
}
|
||||
|
||||
{{if 'final' in cp.xforms}}
|
||||
float cx, cy, cc;
|
||||
{{if 'final_xform' in cp}}
|
||||
float fx = x, fy = y, fcolor = color;
|
||||
apply_xf_final(fx, fy, fcolor, rctx);
|
||||
{{endif}}
|
||||
|
||||
float cx, cy, cc;
|
||||
|
||||
{{if 'final' in cp.xforms}}
|
||||
{{apply_affine('fx', 'fy', 'cx', 'cy', pcp.camera)}}
|
||||
{{apply_affine('fx fy cx cy', cp.camera)}}
|
||||
cc = fcolor;
|
||||
{{else}}
|
||||
{{apply_affine('x', 'y', 'cx', 'cy', pcp.camera)}}
|
||||
{{apply_affine('x y cx cy', cp.camera)}}
|
||||
cc = color;
|
||||
{{endif}}
|
||||
|
||||
@ -407,11 +404,9 @@ oflow_end:
|
||||
}
|
||||
'''
|
||||
|
||||
def iter_body(cp, pcp):
|
||||
# For legacy reasons, 'cp' is used here instead of 'genome'.
|
||||
def iter_body(cp):
|
||||
tmpl = Template(iter_body_code, 'iter_body')
|
||||
NWARPS = NTHREADS / 32
|
||||
std_xforms = [n for n in sorted(cp.xforms) if n != 'final']
|
||||
|
||||
# TODO: detect this properly and use it
|
||||
chaos_used = False
|
||||
@ -420,12 +415,15 @@ def iter_body(cp, pcp):
|
||||
vars.update(locals())
|
||||
return tmpl.substitute(vars)
|
||||
|
||||
def mkiterlib(genome):
|
||||
packer = interp.GenomePacker('iter_params')
|
||||
pcp = packer.view('params', genome, 'cp')
|
||||
def mkiterlib(gnm):
|
||||
packer = interp.GenomePacker('iter_params', 'params',
|
||||
cuburn.genome.spec.anim)
|
||||
cp = packer.view(gnm)
|
||||
|
||||
iterbody = iter_body(genome, pcp)
|
||||
bodies = [iter_xf_body(pcp, i, x) for i, x in sorted(genome.xforms.items())]
|
||||
iterbody = iter_body(cp)
|
||||
bodies = [iter_xf_body(cp, i, x) for i, x in sorted(cp.xforms.items())]
|
||||
if 'final_xform' in cp:
|
||||
bodies.append(iter_xf_body(cp, 'final', cp.final_xform))
|
||||
bodies.append(iterbody)
|
||||
packer_lib = packer.finalize()
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ def mkdsc(dim, ch):
|
||||
format=cuda.array_format.FLOAT)
|
||||
|
||||
class Filter(object):
|
||||
def apply(self, fb, gnm, dim, tc, stream=None):
|
||||
def apply(self, fb, gprof, params, dim, tc, stream=None):
|
||||
"""
|
||||
Queue the application of this filter. When the live stream finishes
|
||||
executing the last item enqueued by this method, the result must be
|
||||
@ -32,15 +32,10 @@ class Filter(object):
|
||||
|
||||
class Bilateral(Filter, ClsMod):
|
||||
lib = code.filters.bilaterallib
|
||||
def __init__(self, directions=8, r=15, sstd=6, cstd=0.05,
|
||||
dstd=1.5, dpow=0.8, gspeed=4.0):
|
||||
# TODO: expose these parameters on the genome, or at least on the
|
||||
# profile, and set them by a less ugly mechanism
|
||||
for n in 'directions r sstd cstd dstd dpow gspeed'.split():
|
||||
setattr(self, n, locals()[n])
|
||||
super(Bilateral, self).__init__()
|
||||
radius = 15
|
||||
directions = 8
|
||||
|
||||
def apply(self, fb, gnm, dim, tc, stream=None):
|
||||
def apply(self, fb, gprof, params, dim, tc, stream=None):
|
||||
# Helper variables and functions to keep it clean
|
||||
sb = 16 * dim.astride
|
||||
bs = sb * dim.ah
|
||||
@ -53,7 +48,7 @@ class Bilateral(Filter, ClsMod):
|
||||
for pattern in range(self.directions):
|
||||
# Scale spatial parameter so that a "pixel" is equivalent to an
|
||||
# actual pixel at 1080p
|
||||
sstd = self.sstd * dim.w / 1920.
|
||||
sstd = params.spatial_std(tc) * dim.w / 1920.
|
||||
|
||||
tref.set_address_2d(fb.d_front, dsc, sb)
|
||||
|
||||
@ -67,38 +62,39 @@ class Bilateral(Filter, ClsMod):
|
||||
grad_tref.set_address_2d(fb.d_side, grad_dsc, sb / 4)
|
||||
|
||||
launch2('bilateral', self.mod, stream, dim,
|
||||
fb.d_back, i32(pattern), i32(self.r),
|
||||
f32(sstd), f32(self.cstd), f32(self.dstd),
|
||||
f32(self.dpow), f32(self.gspeed),
|
||||
fb.d_back, i32(pattern), i32(self.radius),
|
||||
f32(sstd), f32(params.color_std(tc)),
|
||||
f32(params.density_std(tc)), f32(params.density_pow(tc)),
|
||||
f32(params.gradient(tc)),
|
||||
texrefs=[tref, grad_tref])
|
||||
fb.flip()
|
||||
|
||||
class Logscale(Filter, ClsMod):
|
||||
lib = code.filters.logscalelib
|
||||
def apply(self, fb, gnm, dim, tc, stream=None):
|
||||
def apply(self, fb, gprof, params, dim, tc, stream=None):
|
||||
"""Log-scale in place."""
|
||||
k1 = f32(gnm.color.brightness(tc) * 268 / 256)
|
||||
k1 = f32(params.brightness(tc) * 268 / 256)
|
||||
# Old definition of area is (w*h/(s*s)). Since new scale 'ns' is now
|
||||
# s/w, new definition is (w*h/(s*s*w*w)) = (h/(s*s*w))
|
||||
area = dim.h / (gnm.camera.scale(tc) ** 2 * dim.w)
|
||||
k2 = f32(1.0 / (area * gnm.spp(tc)))
|
||||
area = dim.h / (params.scale(tc) ** 2 * dim.w)
|
||||
k2 = f32(1.0 / (area * gprof.spp(tc)))
|
||||
launch2('logscale', self.mod, stream, dim,
|
||||
fb.d_front, fb.d_front, k1, k2)
|
||||
|
||||
class HaloClip(Filter, ClsMod):
|
||||
lib = code.filters.halocliplib
|
||||
def apply(self, fb, gnm, dim, tc, stream=None):
|
||||
gam = f32(1 / gnm.color.gamma(tc) - 1)
|
||||
def apply(self, fb, gprof, params, dim, tc, stream=None):
|
||||
gam = f32(1 / params.gamma(tc) - 1)
|
||||
|
||||
dsc = mkdsc(dim, 1)
|
||||
tref = mktref(self.mod, 'chan1_src')
|
||||
|
||||
launch2('apply_gamma', self.mod, stream, dim,
|
||||
fb.d_side, fb.d_front, gam)
|
||||
tref.set_address_2d(fb.d_side, dsc, 4 * dim.astride)
|
||||
tref.set_address_2d(fb.d_side, dsc, 4 * params.astride)
|
||||
launch2('den_blur_1c', self.mod, stream, dim,
|
||||
fb.d_back, i32(0), i32(0), texrefs=[tref])
|
||||
tref.set_address_2d(fb.d_back, dsc, 4 * dim.astride)
|
||||
tref.set_address_2d(fb.d_back, dsc, 4 * params.astride)
|
||||
launch2('den_blur_1c', self.mod, stream, dim,
|
||||
fb.d_side, i32(1), i32(0), texrefs=[tref])
|
||||
|
||||
@ -107,17 +103,22 @@ class HaloClip(Filter, ClsMod):
|
||||
|
||||
class ColorClip(Filter, ClsMod):
|
||||
lib = code.filters.colorcliplib
|
||||
def apply(self, fb, gnm, dim, tc, stream=None):
|
||||
def apply(self, fb, gprof, params, dim, tc, stream=None):
|
||||
# TODO: implement integration over cubic splines?
|
||||
gam = f32(1 / gnm.color.gamma(tc))
|
||||
vib = f32(gnm.color.vibrance(tc))
|
||||
hipow = f32(gnm.color.highlight_power(tc))
|
||||
lin = f32(gnm.color.gamma_threshold(tc))
|
||||
gam = f32(1 / params.gamma(tc))
|
||||
vib = f32(params.vibrance(tc))
|
||||
hipow = f32(params.highlight_power(tc))
|
||||
lin = f32(params.gamma_threshold(tc))
|
||||
lingam = f32(lin ** (gam-1.0) if lin > 0 else 0)
|
||||
bkgd = vec.make_float3(
|
||||
gnm.color.background.r(tc),
|
||||
gnm.color.background.g(tc),
|
||||
gnm.color.background.b(tc))
|
||||
|
||||
launch2('colorclip', self.mod, stream, dim,
|
||||
fb.d_front, gam, vib, hipow, lin, lingam, bkgd)
|
||||
fb.d_front, gam, vib, hipow, lin, lingam)
|
||||
|
||||
# Ungainly but practical.
|
||||
filter_map = dict(bilateral=Bilateral, logscale=Logscale, haloclip=HaloClip,
|
||||
colorclip=ColorClip)
|
||||
def create(gprof):
|
||||
# TODO: redesign this (should not have to care about internals of
|
||||
# use.Wrapper in order to find types from TypedList elements)
|
||||
filts = gprof._val.get('filters') or gprof.spec['filters'].defaults
|
||||
return [filter_map[f['type']]() for f in filts]
|
||||
|
473
cuburn/genome.py
473
cuburn/genome.py
@ -1,473 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import base64
|
||||
import warnings
|
||||
import xml.parsers.expat
|
||||
import numpy as np
|
||||
|
||||
from code.variations import var_code, var_params
|
||||
from code.util import crep
|
||||
|
||||
class SplEval(object):
|
||||
_mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1],
|
||||
[1,-1, 0, 0], [-2, 3, 0, 0]])
|
||||
_deriv = np.matrix(np.diag([3,2,1], 1))
|
||||
|
||||
def __init__(self, knots, v0=None, v1=None):
|
||||
self.knots = self.normalize(knots, v0, v1)
|
||||
|
||||
@staticmethod
|
||||
def normalize(knots, v0=None, v1=None):
|
||||
if isinstance(knots, (int, float)):
|
||||
knots = [0.0, knots, 1.0, knots]
|
||||
elif not np.all(np.diff(np.float32(np.asarray(knots))[::2]) > 0):
|
||||
raise ValueError("Spline times are non-monotonic. (Use "
|
||||
"nextafterf()-spaced times to anchor tangents.)")
|
||||
|
||||
# If stabilizing knots are missing before or after the edges of the
|
||||
# [0,1] interval, add them.
|
||||
if knots[0] >= 0:
|
||||
if v0 is None:
|
||||
v0 = (knots[3] - knots[1]) / float(knots[2] - knots[0])
|
||||
knots = [-2, knots[3] - (knots[2] + 2) * v0] + knots
|
||||
if knots[-2] <= 1:
|
||||
if v1 is None:
|
||||
v1 = (knots[-1] - knots[-3]) / float(knots[-2] - knots[-4])
|
||||
knots.extend([3, knots[-3] + (3 - knots[-4]) * v1])
|
||||
|
||||
knotarray = np.zeros((2, len(knots)/2))
|
||||
knotarray.T.flat[:] = knots
|
||||
return knotarray
|
||||
|
||||
def find_knots(self, itime):
|
||||
idx = np.searchsorted(self.knots[0], itime) - 2
|
||||
idx = max(0, min(idx, len(self.knots[0]) - 4))
|
||||
|
||||
times = self.knots[0][idx:idx+4]
|
||||
vals = self.knots[1][idx:idx+4]
|
||||
# Normalize to [0,1]
|
||||
t = itime - times[1]
|
||||
times = times - times[1]
|
||||
scale = 1 / times[2]
|
||||
t = t * scale
|
||||
times = times * scale
|
||||
return times, vals, t, scale
|
||||
|
||||
def __call__(self, itime, deriv=0):
|
||||
times, vals, t, scale = self.find_knots(itime)
|
||||
|
||||
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
|
||||
m2 = (vals[3] - vals[1]) / times[3]
|
||||
|
||||
mat = self._mat
|
||||
if deriv:
|
||||
mat = mat * (scale * self._deriv) ** deriv
|
||||
val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T
|
||||
return val[0,0]
|
||||
|
||||
def _plt(self, name='SplEval', fig=111, show=True):
|
||||
import matplotlib.pyplot as plt
|
||||
x = np.linspace(-0.0, 1.0, 500)
|
||||
r = x[1] - x[0]
|
||||
plt.figure(fig)
|
||||
plt.title(name)
|
||||
plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--',
|
||||
self.knots[0],self.knots[1],'x')
|
||||
plt.xlim(0.0, 1.0)
|
||||
if show:
|
||||
plt.show()
|
||||
|
||||
def __str__(self):
|
||||
return '[%g:%g]' % (self(0), self(1))
|
||||
def __repr__(self):
|
||||
return '<interp [%g:%g]>' % (self(0), self(1))
|
||||
|
||||
@property
|
||||
def knotlist(self):
|
||||
# TODO: scale error constants proportional to RMS?
|
||||
# If everything is constant, return a constant
|
||||
if np.std(self.knots[1]) < 1e-6:
|
||||
return self.knots[1][0]
|
||||
# If constant slope, omit the end knots
|
||||
slopes = np.diff(self.knots[1]) / np.diff(self.knots[0])
|
||||
if np.std(slopes) < 1e-6:
|
||||
return list(self.knots.T.flat)[2:-2]
|
||||
return list(self.knots.T.flat)
|
||||
|
||||
def update(self, knots, overwrite=True):
|
||||
"""
|
||||
Update this spline's knotlist with ``knots``, a list of two-tuples
|
||||
(time, value) or a dictionary of the same, while preserving the zeroth
|
||||
and first derivatives at t=0 and t=1.
|
||||
|
||||
If `overwrite` is True (the default), any knot values with precisely
|
||||
the same float32 representation will be overwritten by the incoming
|
||||
values. If not, a KeyError will be raised. Counting on this is not
|
||||
recommended, due to the vagaries of floating-point representations,
|
||||
but it works fine in a pinch.
|
||||
|
||||
Endpoint-preservation is not guaranteed (or conversely, is guaranteed
|
||||
not to work) if any time is passed outside of the exclusive range
|
||||
(0,1).
|
||||
"""
|
||||
old = dict(self.knots.T[1:-1])
|
||||
new = dict(knots)
|
||||
if not overwrite and set(old).intersection(set(new)):
|
||||
raise KeyError("Conflicting spline times")
|
||||
old.update(new)
|
||||
knots = list(sum(sorted(old.items()), ()))
|
||||
self.knots = self.normalize(knots, self(0, 1), self(1, 1))
|
||||
|
||||
def insert_knot(self, t, v):
|
||||
self.update([(t, v)], True)
|
||||
|
||||
def palette_decode(datastrs):
|
||||
"""
|
||||
Decode a palette (stored as a list suitable for JSON packing) into a
|
||||
palette. Internal palette format is simply as a (256,4) array of [0,1]
|
||||
RGBA floats.
|
||||
"""
|
||||
if datastrs[0] != 'rgb8':
|
||||
raise NotImplementedError
|
||||
raw = base64.b64decode(''.join(datastrs[1:]))
|
||||
pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3))
|
||||
data = np.ones((256, 4), np.float32)
|
||||
data[:,:3] = pal / 255.0
|
||||
return data
|
||||
|
||||
def palette_encode(data, format='rgb8'):
|
||||
"""
|
||||
Encode an internal-format palette to an external representation.
|
||||
"""
|
||||
if format != 'rgb8':
|
||||
raise NotImplementedError
|
||||
clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0)))
|
||||
enc = base64.b64encode(np.uint8(clamp))
|
||||
return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)]
|
||||
|
||||
class _AttrDict(dict):
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
raise AttributeError('%s not a dict key' % name)
|
||||
|
||||
@classmethod
|
||||
def _wrap(cls, dct):
|
||||
for k, v in dct.items():
|
||||
if (isinstance(v, (float, int)) or
|
||||
(isinstance(v, list) and isinstance(v[1], (float, int)))):
|
||||
dct[k] = SplEval(v)
|
||||
elif isinstance(v, dict):
|
||||
dct[k] = cls._wrap(cls(v))
|
||||
return dct
|
||||
|
||||
class Genome(_AttrDict):
|
||||
"""
|
||||
Load a genome description, wrapping all data structures in _AttrDicts,
|
||||
converting lists of numbers to splines, and deriving some values. Derived
|
||||
values are stored as instance properties, rather than replacing the
|
||||
original values, such that JSON-encoding this structure should always
|
||||
print a valid genome.
|
||||
"""
|
||||
# For now, we base the Genome class on an _AttrDict, letting its structure
|
||||
# be defined implicitly by the way it is used in device code, except for
|
||||
# these derived properties.
|
||||
def __init__(self, gnm):
|
||||
super(Genome, self).__init__(gnm)
|
||||
for k, v in self.items():
|
||||
if not isinstance(v, dict):
|
||||
continue
|
||||
v = _AttrDict(v)
|
||||
# These two properties must be handled separately
|
||||
if k not in ('info', 'time'):
|
||||
_AttrDict._wrap(v)
|
||||
self[k] = v
|
||||
|
||||
self.decoded_palettes = map(palette_decode, self.palettes)
|
||||
pal = self.color.palette_times
|
||||
if isinstance(pal, basestring):
|
||||
self.palette_times = [(0.0, int(pal)), (1.0, int(pal))]
|
||||
else:
|
||||
self.palette_times = zip(pal[::2], map(int, pal[1::2]))
|
||||
|
||||
self.adj_frame_width, self.spp = None, None
|
||||
|
||||
def set_profile(self, prof, offset=0.0, err_spread=True):
|
||||
"""
|
||||
Sets the instance props which are dependent on a profile. Also
|
||||
calculates timing information, which is returned instead of being
|
||||
attached to the genome. May be called multiple times to set different
|
||||
options.
|
||||
|
||||
``prof`` is a profile dictionary. ``offset`` is the time in seconds
|
||||
that the first frame's effective presentation time should be offset
|
||||
from the natural presentation time. ``err_spread`` will spread the
|
||||
rounding error in this frame across all frames, such that PTS+(1/FPS)
|
||||
is exactly equal to the requested duration.
|
||||
|
||||
Returns ``(err, times)``, where ``err`` is the rounding error in
|
||||
seconds (taking ``offset`` into account), and ``times`` is a list of
|
||||
the central time of each frame in the animation in relative-time
|
||||
coordinates. Also sets the ``spp`` and ``adj_frame_width`` properties.
|
||||
"""
|
||||
self.spp = SplEval(self.camera.density.knotlist)
|
||||
self.spp.knots[1] *= prof['quality']
|
||||
fps, base_dur = prof['fps'], prof['duration']
|
||||
|
||||
# TODO: test!
|
||||
dur = self.time.duration
|
||||
if isinstance(dur, basestring):
|
||||
clock = float(dur[:-1]) + offset
|
||||
else:
|
||||
clock = dur * base_dur + offset
|
||||
if (not isinstance(self.get('link'), dict) or
|
||||
not self.link.get('right')):
|
||||
warnings.warn("Genomes with missing or string-valued 'link' "
|
||||
"properties are deprecated, and will be axed shortly.")
|
||||
nframes = int(np.ceil(clock * fps))
|
||||
elif self.link.right == 'reference':
|
||||
nframes = int(np.floor(clock * fps))
|
||||
else:
|
||||
nframes = int(np.ceil(clock * fps))
|
||||
err = (clock - nframes / fps) / clock
|
||||
|
||||
fw = self.time.frame_width
|
||||
if not isinstance(fw, list):
|
||||
fw = [0, fw, 1, fw]
|
||||
fw = [float(f[:-1]) * fps if isinstance(f, basestring)
|
||||
else float(f) / (clock * fps) for f in fw]
|
||||
self.adj_frame_width = SplEval(fw)
|
||||
|
||||
times = np.linspace(offset, 1 - err, nframes + 1)
|
||||
# Move each time to a center time, and discard the last value
|
||||
times = times[:-1] + 0.5 * (times[1] - times[0])
|
||||
if err_spread:
|
||||
epts = np.linspace(-2*np.pi, 2*np.pi, nframes)
|
||||
times = times + 0.5 * err * (np.tanh(epts) + 1)
|
||||
return err, times
|
||||
|
||||
def json_encode_genome(obj):
|
||||
"""
|
||||
Encode an object into JSON notation. This serializer only works on the
|
||||
subset of JSON used in genomes.
|
||||
"""
|
||||
result = _js_enc_obj(obj).lstrip()
|
||||
result = '\n'.join(l.rstrip() for l in result.split('\n'))
|
||||
return result + '\n'
|
||||
|
||||
def _js_enc_obj(obj, indent=0):
|
||||
isnum = lambda v: isinstance(v, (float, int, np.number))
|
||||
|
||||
def wrap(pairs, delims):
|
||||
do, dc = delims
|
||||
i = ' ' * indent
|
||||
out = ''.join([do, ', '.join(pairs), dc])
|
||||
if '\n' not in out and len(out) + indent < 70:
|
||||
return out
|
||||
return ''.join(['\n', i, do, ' ', ('\n'+i+', ').join(pairs),
|
||||
'\n', i, dc])
|
||||
|
||||
if isinstance(obj, dict):
|
||||
if not obj:
|
||||
return '{}'
|
||||
digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv
|
||||
ks, vs = zip(*sorted(obj.items(), key=digsort))
|
||||
if ks == ('b', 'g', 'r'):
|
||||
ks, vs = reversed(ks), reversed(vs)
|
||||
ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks]
|
||||
vs = [_js_enc_obj(v, indent+2) for v in vs]
|
||||
return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}')
|
||||
elif isinstance(obj, list):
|
||||
vs = [_js_enc_obj(v, indent+2) for v in obj]
|
||||
if vs and len(vs) % 2 == 0 and isnum(obj[0]):
|
||||
vs = map(', '.join, zip(vs[::2], vs[1::2]))
|
||||
return wrap(vs, '[]')
|
||||
elif isinstance(obj, SplEval):
|
||||
return _js_enc_obj(obj.knotlist, indent)
|
||||
elif isinstance(obj, basestring):
|
||||
return crep(obj)
|
||||
elif isnum(obj):
|
||||
return '%.6g' % obj
|
||||
raise TypeError("Don't know how to serialize %s of type %s" %
|
||||
(obj, type(obj)))
|
||||
|
||||
class XMLGenomeParser(object):
|
||||
"""
|
||||
Parse an XML genome into a list of dictionaries.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.flames = []
|
||||
self._flame = None
|
||||
self.parser = xml.parsers.expat.ParserCreate()
|
||||
self.parser.StartElementHandler = self.start_element
|
||||
self.parser.EndElementHandler = self.end_element
|
||||
|
||||
def start_element(self, name, attrs):
|
||||
if name == 'flame':
|
||||
assert self._flame is None
|
||||
self._flame = dict(attrs)
|
||||
self._flame['xforms'] = []
|
||||
self._flame['palette'] = np.ones((256, 4), dtype=np.float32)
|
||||
elif name == 'xform':
|
||||
self._flame['xforms'].append(dict(attrs))
|
||||
elif name == 'finalxform':
|
||||
self._flame['finalxform'] = dict(attrs)
|
||||
elif name == 'color':
|
||||
idx = int(attrs['index'])
|
||||
self._flame['palette'][idx][:3] = [float(v) / 255.0
|
||||
for v in attrs['rgb'].split()]
|
||||
elif name == 'symmetry':
|
||||
self._flame['symmetry'] = int(attrs['kind'])
|
||||
def end_element(self, name):
|
||||
if name == 'flame':
|
||||
self.flames.append(self._flame)
|
||||
self._flame = None
|
||||
|
||||
@classmethod
|
||||
def parse(cls, src):
|
||||
parser = cls()
|
||||
parser.parser.Parse(src, True)
|
||||
return parser.flames
|
||||
|
||||
def convert_flame(flame, arc=-360, offset=0):
|
||||
"""
|
||||
Convert an XML flame (as returned by XMLGenomeParser) into a plain dict
|
||||
in cuburn's JSON genome format representing a loop edge. Caller is
|
||||
responsible for correctly setting the 'link' dict.
|
||||
"""
|
||||
cvt = lambda ks: dict((k, float(flame[k])) for k in ks)
|
||||
camera = {
|
||||
'center': dict(zip('xy', map(float, flame['center'].split()))),
|
||||
'scale': float(flame['scale']) / float(flame['size'].split()[0]),
|
||||
'dither_width': float(flame['filter']),
|
||||
'rotation': float(flame.get('rotate', 0)),
|
||||
'density': 1.0
|
||||
}
|
||||
|
||||
info = {}
|
||||
if 'name' in flame:
|
||||
info['name'] = flame['name']
|
||||
if 'nick' in flame:
|
||||
info['authors'] = [flame['nick']]
|
||||
if flame.get('url'):
|
||||
info['authors'][0] = info['authors'][0] + ', http://' + flame['url']
|
||||
|
||||
time = dict(frame_width=float(flame.get('temporal_filter_width', 1)),
|
||||
duration=abs(arc)/360.)
|
||||
|
||||
color = cvt(['brightness', 'gamma'])
|
||||
color.update((k, float(flame.get(k, d))) for k, d in
|
||||
[('highlight_power', -1), ('gamma_threshold', 0.01)])
|
||||
color['vibrance'] = float(flame.get('vibrancy', 1))
|
||||
color['background'] = dict(zip('rgb',
|
||||
map(float, flame['background'].split())))
|
||||
color['palette_times'] = "0"
|
||||
pal = palette_encode(flame['palette'])
|
||||
|
||||
de = dict((k, float(flame.get(f, d))) for f, k, d in
|
||||
[('estimator', 'radius', 11),
|
||||
('estimator_minimum', 'minimum', 0),
|
||||
('estimator_curve', 'curve', 0.6)])
|
||||
|
||||
num_xf = len(flame['xforms'])
|
||||
xfs = dict([(str(k), convert_xform(v, num_xf, arc, offset))
|
||||
for k, v in enumerate(flame['xforms'])])
|
||||
if 'symmetry' in flame:
|
||||
xfs.update(make_symm_xforms(flame['symmetry'], len(xfs)))
|
||||
if 'finalxform' in flame:
|
||||
xfs['final'] = convert_xform(flame['finalxform'], num_xf,
|
||||
arc, offset, True)
|
||||
return dict(camera=camera, color=color, de=de, xforms=xfs,
|
||||
info=info, time=time, palettes=[pal], link='self')
|
||||
|
||||
def convert_xform(xf, num_xf, arc, offset, isfinal=False):
|
||||
# TODO: chaos
|
||||
xf = dict(xf)
|
||||
symm = float(xf.pop('symmetry', 0))
|
||||
anim = xf.pop('animate', symm <= 0)
|
||||
out = dict((k, float(xf.pop(k, v))) for k, v in
|
||||
dict(color=0, color_speed=(1-symm)/2, opacity=1).items())
|
||||
if not isfinal:
|
||||
out['density'] = float(xf.pop('weight'))
|
||||
out['affine'] = convert_affine(xf.pop('coefs'), arc, offset, anim)
|
||||
if 'post' in xf and map(float, xf['post'].split()) != [1, 0, 0, 1, 0, 0]:
|
||||
out['post'] = convert_affine(xf.pop('post'), arc, offset)
|
||||
if 'chaos' in xf:
|
||||
chaos = map(float, xf.pop('chaos').split())
|
||||
out['chaos'] = dict()
|
||||
for i in range(num_xf):
|
||||
if i < len(chaos):
|
||||
out['chaos'][str(i)] = chaos[i]
|
||||
else:
|
||||
out['chaos'][str(i)] = 1.0
|
||||
|
||||
out['variations'] = {}
|
||||
for k in var_code:
|
||||
if k in xf:
|
||||
var = dict(weight=float(xf.pop(k)))
|
||||
for param, default in var_params.get(k, {}).items():
|
||||
var[param] = float(xf.pop('%s_%s' % (k, param), default))
|
||||
out['variations'][k] = var
|
||||
assert not xf, 'Unrecognized parameters remain: ' + str(xf)
|
||||
return out
|
||||
|
||||
def convert_affine(aff, arc, offset, animate=False):
|
||||
xx, yx, xy, yy, xo, yo = map(float, aff.split())
|
||||
# Invert all instances of y (yy is inverted twice)
|
||||
yx, xy, yo = -yx, -xy, -yo
|
||||
|
||||
xa = np.degrees(np.arctan2(yx, xx))
|
||||
ya = np.degrees(np.arctan2(yy, xy))
|
||||
xm = np.hypot(xx, yx)
|
||||
ym = np.hypot(xy, yy)
|
||||
|
||||
angle_between = ya - xa
|
||||
if angle_between < 0:
|
||||
angle_between += 360
|
||||
|
||||
if angle_between < 180:
|
||||
spread = angle_between / 2.0
|
||||
else:
|
||||
spread = -(360-angle_between) / 2.0
|
||||
|
||||
angle = xa + spread
|
||||
if angle < 0:
|
||||
angle += 360.0
|
||||
|
||||
if animate:
|
||||
angle = [0, angle + offset, 1, angle + arc + offset]
|
||||
|
||||
return dict(spread=spread, magnitude={'x': xm, 'y': ym},
|
||||
angle=angle, offset={'x': xo, 'y': yo})
|
||||
|
||||
def make_symm_xforms(kind, offset):
|
||||
assert kind != 0, 'Pick your own damn symmetry.'
|
||||
out = []
|
||||
boring_xf = dict(color=1, color_speed=0, density=1, opacity=1,
|
||||
variations={'linear': {'weight': 1}})
|
||||
if kind < 0:
|
||||
out.append(boring_xf.copy())
|
||||
out[-1]['affine'] = dict(angle=135, magnitude={'x': 1, 'y': 1},
|
||||
spread=-45, offset={'x': 0, 'y': 0})
|
||||
kind = -kind
|
||||
for i in range(1, kind):
|
||||
out.append(boring_xf.copy())
|
||||
if kind >= 3:
|
||||
out[-1]['color'] = (i - 1) / (kind - 2.0)
|
||||
ang = (45 + 360 * i / float(kind)) % 360
|
||||
out[-1]['affine'] = dict(angle=ang, magnitude={'x': 1, 'y': 1},
|
||||
spread=-45, offset={'x': 0, 'y': 0})
|
||||
return dict((str(i+offset), v) for i, v in enumerate(out))
|
||||
|
||||
def convert_file(path):
|
||||
"""Quick one-shot conversion for an XML genome."""
|
||||
flames = XMLGenomeParser.parse(open(path).read())
|
||||
if len(flames) > 10:
|
||||
warnings.warn("Lot of flames in this file. Sure it's not a "
|
||||
"frame-based animation?")
|
||||
for flame in flames:
|
||||
yield convert_flame(flame)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
print '\n\n'.join(map(json_encode_genome, convert_file(sys.argv[1])))
|
0
cuburn/genome/__init__.py
Normal file
0
cuburn/genome/__init__.py
Normal file
288
cuburn/genome/blend.py
Normal file
288
cuburn/genome/blend.py
Normal file
@ -0,0 +1,288 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2011-2012 Erik Reckase <e.reckase@gmail.com>,
|
||||
# Steven Robertson <steven@strobe.cc>.
|
||||
|
||||
import numpy as np
|
||||
from copy import deepcopy
|
||||
from itertools import izip_longest
|
||||
from scipy.ndimage.filters import gaussian_filter1d
|
||||
|
||||
import spectypes
|
||||
import spec
|
||||
import util
|
||||
from util import get
|
||||
import variations
|
||||
|
||||
# TODO: move to better place before checkin
|
||||
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
|
||||
|
||||
|
||||
def blend(src, dst, edit={}):
|
||||
"""
|
||||
Blend two nodes to produce an animation.
|
||||
|
||||
``src`` and ``dst`` are the source and destination node specs for the
|
||||
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.
|
||||
|
||||
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)
|
||||
for d in src, dst, edit:
|
||||
opts.update(d.get('blend', {}))
|
||||
|
||||
blended = merge_nodes(spec.node, src, dst, edit, opts['nloops'])
|
||||
name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
|
||||
explicit=zip(*opts.get('xform_map', [])))
|
||||
|
||||
blended['xforms'] = {}
|
||||
for (sxf_key, dxf_key) in name_map:
|
||||
bxf_key = (sxf_key or 'pad') + '_' + (dxf_key or 'pad')
|
||||
xf_edits = merge_edits(spec.xform,
|
||||
get(edit, {}, 'xforms', 'src', sxf_key),
|
||||
get(edit, {}, 'xforms', 'dst', dxf_key))
|
||||
blended['xforms'][bxf_key] = blend_xform(
|
||||
src['xforms'].get(sxf_key),
|
||||
dst['xforms'].get(dxf_key),
|
||||
xf_edits, opts['nloops'])
|
||||
|
||||
if 'final_xform' in src or 'final_xform' in dst:
|
||||
blended['final_xform'] = blend_xform(src.get('final_xform'),
|
||||
dst.get('final_xform'), edit.get('final_xform'), 0, True)
|
||||
|
||||
# TODO: write 'info' section
|
||||
# TODO: palflip
|
||||
blended['type'] = 'animation'
|
||||
blended.setdefault('time', {})['duration'] = opts['duration']
|
||||
return blended
|
||||
|
||||
def merge_edits(sv, av, bv):
|
||||
"""
|
||||
Merge the values of ``av`` and ``bv`` according to the spec ``sv``.
|
||||
"""
|
||||
if isinstance(spec, (dict, spectypes.Map)):
|
||||
av, bv = av or {}, bv or {}
|
||||
getsv = lambda k: sv.type if isinstance(sv, spectypes.Map) else sv[k]
|
||||
return dict([(k, merge_edits(getsv(k), av.get(k), bv.get(k)))
|
||||
for k in set(av.keys() + bv.keys())])
|
||||
elif isinstance(sv, (spectypes.List, spectypes.Spline)):
|
||||
return (av or []) + (bv or [])
|
||||
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
|
||||
|
||||
sp, sv = split_node_val(src) # position, velocity
|
||||
dp, dv = split_node_val(dst)
|
||||
|
||||
# For variation parameters, copy missing values instead of using defaults
|
||||
if spl.var:
|
||||
if src is None:
|
||||
sp = dp
|
||||
if dst is None:
|
||||
dp = sp
|
||||
|
||||
edit = dict(zip(edit[::2], edit[1::2])) if edit else {}
|
||||
e0, e1 = edit.pop(0, None), edit.pop(1, None)
|
||||
edit = zip(*[(k, v) for k, v in edit.items() if v is not None])
|
||||
|
||||
if spl.period:
|
||||
# Periodic extension: compute an appropriate number of loops based on
|
||||
# the angular velocities at the endpoints, and extend the destination
|
||||
# position by the appropriate number of periods.
|
||||
avg_vel = round(float(sv + dv) * loops / spl.period)
|
||||
dp = dp % spl.period + avg_vel * spl.period
|
||||
|
||||
# Endpoint override: allow adjusting the number of loops as calculated
|
||||
# above by locking to the nearest value with the same mod (i.e. the
|
||||
# nearest value which will still line up with the node)
|
||||
if e0 is not None:
|
||||
sp += round(float(e0 - sp) / spl.period) * spl.period
|
||||
if e1 is not None:
|
||||
dp += round(float(e1 - dp) / spl.period) * spl.period
|
||||
if edit or sv or dv:
|
||||
return [sp, sv, dp, dv] + edit
|
||||
if sp != dp:
|
||||
return [sp, dp]
|
||||
return sp
|
||||
|
||||
def trace(k):
|
||||
print k,
|
||||
return k
|
||||
|
||||
def merge_nodes(sp, src, dst, edit, loops):
|
||||
if isinstance(sp, dict):
|
||||
src, dst, edit = [x or {} for x in src, dst, edit]
|
||||
return dict([(k, merge_nodes(sp[k], src.get(k),
|
||||
dst.get(k), edit.get(k), loops))
|
||||
for k in set(src.keys() + dst.keys() + edit.keys()) if k in sp])
|
||||
elif isinstance(sp, spectypes.Spline):
|
||||
return tospline(sp, src, dst, edit, loops)
|
||||
elif isinstance(sp, spectypes.List):
|
||||
if isinstance(sp.type, spectypes.Palette):
|
||||
if src is not None: src = [[0] + src]
|
||||
if dst is not None: dst = [[1] + dst]
|
||||
return (src or []) + (dst or []) + (edit or [])
|
||||
else:
|
||||
return edit if edit is not None else dst if dst is not None else src
|
||||
|
||||
def blend_xform(sxf, dxf, edits, loops, isfinal=False):
|
||||
if sxf is None:
|
||||
sxf = padding_xform(dxf, isfinal)
|
||||
if dxf is None:
|
||||
dxf = padding_xform(sxf, isfinal)
|
||||
return merge_nodes(spec.xform, sxf, dxf, edits, loops)
|
||||
|
||||
# If xin contains any of these, use the inverse identity
|
||||
hole_variations = ('spherical ngon julian juliascope polar '
|
||||
'wedge_sph wedge_julia bipolar').split()
|
||||
|
||||
# These variations are identity functions at their default values
|
||||
ident_variations = ('rectangles rings2 fan2 blob perspective curl '
|
||||
'super_shape').split()
|
||||
|
||||
def padding_xform(xf, isfinal):
|
||||
vars = {}
|
||||
xout = {'variations': vars, 'pre_affine': {'angle': 45}}
|
||||
if isfinal:
|
||||
xout.update(weight=0, color_speed=0)
|
||||
if get(xf, 45, 'pre_affine', 'spread') > 90:
|
||||
xout['pre_affine'] = {'angle': 135, 'spread': 135}
|
||||
if get(xf, 45, 'post_affine', 'spread') > 90:
|
||||
xout['post_affine'] = {'angle': 135, 'spread': 135}
|
||||
|
||||
for k in xf['variations']:
|
||||
if k in hole_variations:
|
||||
# Attempt to correct for some known-ugly variations.
|
||||
xout['pre_affine']['angle'] += 180
|
||||
vars['linear'] = dict(weight=-1)
|
||||
return xout
|
||||
if k in ident_variations:
|
||||
# Try to use non-linear variations whenever we can
|
||||
vars[k] = dict([(vk, vv.default)
|
||||
for vk, vv in variations.var_params[k].items()])
|
||||
|
||||
if vars:
|
||||
n = float(len(vars))
|
||||
for k in vars:
|
||||
vars[k]['weight'] /= n
|
||||
else:
|
||||
vars['linear'] = dict(weight=1)
|
||||
|
||||
return xout
|
||||
|
||||
def blend_genomes(left, right, nloops=2, align='weightflip', seed=None,
|
||||
stagger=False, blur=None, palflip=True):
|
||||
align_xforms(left, right, align)
|
||||
name = '%s=%s' % (left.info.get('name', ''), right.info.get('name', ''))
|
||||
if seed is None:
|
||||
seed = map(ord, name)
|
||||
rng = np.random.RandomState(seed)
|
||||
|
||||
blend = blend_splines(left, right, nloops, rng, stagger)
|
||||
# TODO: licenses; check license compatibility when merging
|
||||
# TODO: add URL and flockutil revision to authors
|
||||
blend['info'] = {
|
||||
'name': name,
|
||||
'authors': sum([g.info.get('authors', []) for g in left, right], [])
|
||||
}
|
||||
blend['info']['authors'].append('flockutil')
|
||||
blend['palettes'] = [get_palette(left, False), get_palette(right, True)]
|
||||
blend['color']['palette_times'] = [0, "0", 1, "1"]
|
||||
|
||||
if palflip:
|
||||
checkpalflip(blend)
|
||||
|
||||
if blur:
|
||||
blur_palettes(blend, blur)
|
||||
|
||||
return blend
|
||||
|
||||
|
||||
def halfhearted_human_sort_key(key):
|
||||
try:
|
||||
return int(key)
|
||||
except ValueError:
|
||||
return key
|
||||
|
||||
def sort_xforms(sxfs, dxfs, sortmethod, explicit=[]):
|
||||
# Walk through the explicit pairs, popping previous matches from the
|
||||
# forward (src=>dst) and reverse (dst=>src) maps
|
||||
fwd, rev = {}, {}
|
||||
for sx, dx in explicit:
|
||||
if sx in fwd:
|
||||
rev.pop(fwd.pop(sx, None), None)
|
||||
if dx in rev:
|
||||
fwd.pop(rev.pop(dx, None), None)
|
||||
fwd[sx] = dx
|
||||
rev[dx] = sx
|
||||
|
||||
for sd in sorted(fwd.items()):
|
||||
yield sd
|
||||
|
||||
# Classify the remaining xforms. Currently we classify based on whether
|
||||
# the pre- and post-affine transforms are flipped
|
||||
scl, dcl = {}, {}
|
||||
for (cl, xfs, exp) in [(scl, sxfs, fwd), (dcl, dxfs, rev)]:
|
||||
for k, v in xfs.items():
|
||||
if k in exp: continue
|
||||
xcl = (get(v, 45, 'pre_affine', 'spread') > 90,
|
||||
get(v, 45, 'post_affine', 'spread') > 90)
|
||||
cl.setdefault(xcl, []).append(k)
|
||||
|
||||
def sort(keys, dct, snd=False):
|
||||
if sortmethod in ('weight', 'weightflip'):
|
||||
sortf = lambda k: dct[k].get('weight', 0)
|
||||
elif sortmethod == 'color':
|
||||
sortf = lambda k: dct[k].get('color', 0)
|
||||
else:
|
||||
# 'natural' key-based sort
|
||||
sortf = halfhearted_human_sort_key
|
||||
return sorted(keys, key=sortf)
|
||||
|
||||
for cl in set(scl.keys() + dcl.keys()):
|
||||
ssort = sort(scl.get(cl, []), sxfs)
|
||||
dsort = sort(dcl.get(cl, []), dxfs)
|
||||
if sortmethod == 'weightflip':
|
||||
dsort = reversed(dsort)
|
||||
for sd in izip_longest(ssort, dsort):
|
||||
yield sd
|
||||
|
||||
def checkpalflip(gnm):
|
||||
if 'final' in gnm['xforms']:
|
||||
f = gnm['xforms']['final']
|
||||
fcv, fcsp = f['color'], f['color_speed']
|
||||
else:
|
||||
fcv, fcsp = SplEval(0), SplEval(0)
|
||||
sansfinal = [v for k, v in gnm['xforms'].items() if k != 'final']
|
||||
|
||||
lc, rc = [np.array([v['color'](t) * (1 - fcsp(t)) + fcv(t) * fcsp(t)
|
||||
for v in sansfinal]) for t in (0, 1)]
|
||||
rcrv = 1 - rc
|
||||
# TODO: use spline integration instead of L2
|
||||
dens = np.array([np.hypot(v['weight'](0), v['weight'](1))
|
||||
for v in sansfinal])
|
||||
return np.sum(np.abs(dens * (rc - lc))) > np.sum(np.abs(dens * (rcrv - lc)))
|
||||
|
||||
def palflip(gnm):
|
||||
for v in gnm['xforms'].values():
|
||||
c = v['color']
|
||||
v['color'] = SplEval([0, c(0), 1, 1 - c(1)], c(0, 1), -c(1, 1))
|
||||
pal = genome.palette_decode(gnm['palettes'][1])
|
||||
gnm['palettes'][1] = genome.palette_encode(np.flipud(pal))
|
||||
|
||||
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))
|
182
cuburn/genome/convert.py
Normal file
182
cuburn/genome/convert.py
Normal file
@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import base64
|
||||
import warnings
|
||||
import xml.parsers.expat
|
||||
import numpy as np
|
||||
|
||||
from variations import var_params
|
||||
import util
|
||||
|
||||
class XMLGenomeParser(object):
|
||||
"""
|
||||
Parse an XML genome into a list of dictionaries.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.flames = []
|
||||
self._flame = None
|
||||
self.parser = xml.parsers.expat.ParserCreate()
|
||||
self.parser.StartElementHandler = self.start_element
|
||||
self.parser.EndElementHandler = self.end_element
|
||||
|
||||
def start_element(self, name, attrs):
|
||||
if name == 'flame':
|
||||
assert self._flame is None
|
||||
self._flame = dict(attrs)
|
||||
self._flame['xforms'] = []
|
||||
self._flame['palette'] = np.ones((256, 4), dtype=np.float32)
|
||||
elif name == 'xform':
|
||||
self._flame['xforms'].append(dict(attrs))
|
||||
elif name == 'finalxform':
|
||||
self._flame['finalxform'] = dict(attrs)
|
||||
elif name == 'color':
|
||||
idx = int(attrs['index'])
|
||||
self._flame['palette'][idx][:3] = [float(v) / 255.0
|
||||
for v in attrs['rgb'].split()]
|
||||
elif name == 'symmetry':
|
||||
self._flame['symmetry'] = int(attrs['kind'])
|
||||
def end_element(self, name):
|
||||
if name == 'flame':
|
||||
self.flames.append(self._flame)
|
||||
self._flame = None
|
||||
|
||||
@classmethod
|
||||
def parse(cls, src):
|
||||
parser = cls()
|
||||
parser.parser.Parse(src, True)
|
||||
return parser.flames
|
||||
|
||||
def convert_affine(aff, animate=False):
|
||||
xx, yx, xy, yy, xo, yo = vals = map(float, aff.split())
|
||||
if vals == [1, 0, 0, 1, 0, 0]: return None
|
||||
|
||||
# Cuburn's IFS-space vertical direction is inverted with respect to flam3,
|
||||
# so we invert all instances of y. (``yy`` is effectively inverted twice.)
|
||||
yx, xy, yo = -yx, -xy, -yo
|
||||
|
||||
xa = np.degrees(np.arctan2(yx, xx))
|
||||
ya = np.degrees(np.arctan2(yy, xy))
|
||||
xm = np.hypot(xx, yx)
|
||||
ym = np.hypot(xy, yy)
|
||||
spread = ((ya - xa) % 360) / 2
|
||||
angle = (xa + spread) % 360
|
||||
return dict(spread=spread, magnitude={'x': xm, 'y': ym},
|
||||
angle=angle, offset={'x': xo, 'y': yo})
|
||||
|
||||
def convert_vars(xf):
|
||||
struct = lambda k, ps: ([('weight', k, float)] +
|
||||
[(p, k+'_'+p, float) for p in ps])
|
||||
return dict([(k, apply_structure(struct(k, ps), xf))
|
||||
for k, ps in var_params.items() if k in xf])
|
||||
|
||||
def convert_xform(xf):
|
||||
out = apply_structure(xform_structure, xf)
|
||||
|
||||
# Deprecated symmetry arg makes this too much of a bother to handle within
|
||||
# the structure framework
|
||||
symm = float(xf.get('symmetry', 0))
|
||||
anim = xf.get('animate', symm <= 0)
|
||||
if 'symmetry' in xf:
|
||||
out.setdefault('color_speed', (1-symm)/2)
|
||||
if anim and 'pre_affine' in out:
|
||||
out['pre_affine']['angle'] = [out['pre_affine']['angle'], -360]
|
||||
return out
|
||||
|
||||
def make_symm_xforms(kind, offset):
|
||||
assert kind != 0, 'Pick your own damn symmetry.'
|
||||
out = []
|
||||
boring_xf = dict(color=1, color_speed=0, density=1,
|
||||
variations={'linear': {'weight': 1}})
|
||||
if kind < 0:
|
||||
out.append(boring_xf.copy())
|
||||
out[-1]['affine'] = dict(angle=135, spread=-45)
|
||||
kind = -kind
|
||||
for i in range(1, kind):
|
||||
out.append(boring_xf.copy())
|
||||
if kind >= 3:
|
||||
out[-1]['color'] = (i - 1) / (kind - 2.0)
|
||||
ang = (45 + 360 * i / float(kind)) % 360
|
||||
out[-1]['affine'] = dict(angle=ang, spread=-45)
|
||||
return dict(enumerate(out, offset))
|
||||
|
||||
def convert_xforms(flame):
|
||||
xfs = dict(enumerate(map(convert_xform, flame['xforms'])))
|
||||
if 'symmetry' in flame:
|
||||
xfs.update(make_symm_xforms(float(flame['symmetry']), len(xfs)))
|
||||
return xfs
|
||||
|
||||
split_to_dict = lambda keys: lambda v: dict(zip(keys, map(float, v.split())))
|
||||
pair = split_to_dict('xy')
|
||||
rgb_triple = split_to_dict('rgb')
|
||||
|
||||
xform_structure = (
|
||||
('pre_affine', 'coefs', convert_affine),
|
||||
('post_affine', 'post', convert_affine),
|
||||
('color', 'color', float),
|
||||
('color_speed', 'color_speed', float),
|
||||
('opacity', 'opacity', float),
|
||||
('weight', 'weight', float),
|
||||
('chaos', 'chaos',
|
||||
lambda s: dict(enumerate(map(float, s.split())))),
|
||||
('variations', convert_vars)
|
||||
)
|
||||
|
||||
# A list of either three-tuples (dst, src, cvt_val), or two-tuples
|
||||
# (dst, cvt_dict) for properties that are built from multiple source keys.
|
||||
# If a function returns 'None', its key is dropped from the result.
|
||||
flame_structure = (
|
||||
('info.author', 'nick', str),
|
||||
('info.author_url', 'url', lambda s: 'http://' + str(s)),
|
||||
('info.name', 'name', str),
|
||||
|
||||
('camera.center', 'center', pair),
|
||||
('camera.rotation', 'rotate', float),
|
||||
('camera.dither_width', 'filter', float),
|
||||
('camera.scale',
|
||||
lambda d: float(d['scale']) / float(d['size'].split()[0])),
|
||||
|
||||
('filters.colorclip.gamma', 'filter', float),
|
||||
('filters.colorclip.gamma_threshold', 'gamma_threshold', float),
|
||||
('filters.colorclip.highlight_power', 'highlight_power', float),
|
||||
('filters.colorclip.vibrance', 'vibrancy', float),
|
||||
# Not sure about putting this one on colorclip
|
||||
('filters.colorclip.background', 'background', rgb_triple),
|
||||
|
||||
('filters.de.curve', 'estimator_curve', float),
|
||||
('filters.de.radius', 'estimator_radius', float),
|
||||
('filters.de.minimum',
|
||||
lambda d: (float(d['estimator_minimum']) /
|
||||
float(d.get('estimator_radius', 11)))
|
||||
if 'estimator_minimum' in d else None),
|
||||
|
||||
('palette', 'palette', util.palette_encode),
|
||||
('xforms', convert_xforms),
|
||||
('final_xform', 'finalxform', convert_xform),
|
||||
)
|
||||
|
||||
def apply_structure(struct, src):
|
||||
out = {}
|
||||
for l in struct:
|
||||
if len(l) == 2:
|
||||
v = l[1](src)
|
||||
else:
|
||||
v = l[2](src[l[1]]) if l[1] in src else None
|
||||
if v is not None:
|
||||
out[l[0]] = v
|
||||
return out
|
||||
|
||||
def convert_flame(flame):
|
||||
return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
|
||||
|
||||
def convert_file(path):
|
||||
"""Quick one-shot conversion for an XML genome."""
|
||||
flames = XMLGenomeParser.parse(open(path).read())
|
||||
if len(flames) > 10:
|
||||
warnings.warn("Lot of flames in this file. Sure it's not a "
|
||||
"frame-based animation?")
|
||||
for flame in flames:
|
||||
yield convert_flame(flame)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
print '\n\n'.join(map(util.json_encode, convert_file(sys.argv[1])))
|
82
cuburn/genome/schema.py
Normal file
82
cuburn/genome/schema.py
Normal file
@ -0,0 +1,82 @@
|
||||
from schematypes import *
|
||||
from variations import var_
|
||||
|
||||
affine = (
|
||||
{ angle: spline(45, period=360)
|
||||
, spread: spline(45, period=180)
|
||||
# TODO: should these scale relative to magnitude?
|
||||
, off_x: spline()
|
||||
, off_y: spline()
|
||||
# TODO: this is probably an inappropriate scaling domain? Should one be
|
||||
# constructed specifically for magnitudes?
|
||||
, mag_x: spline(1)
|
||||
, mag_y: spline(1)
|
||||
})
|
||||
|
||||
xform = (
|
||||
{ affine: affine
|
||||
, post: affine
|
||||
, color: spline(0, 0, 1)
|
||||
, color_speed: spline(0.5, 0, 1)
|
||||
, density: spline()
|
||||
, opacity: scalespline(max=1)
|
||||
, variations: cuburn.code.variations.params
|
||||
})
|
||||
|
||||
# Since the structure of the info element differs between anims, nodes and
|
||||
# edges, we pull out some of the common elements here
|
||||
author = String('Attribution in the form: "Name [<email>][, url]"')
|
||||
name = String('A human-readable name for this entity')
|
||||
src = String('The identifier of the source node')
|
||||
dst = String('The identifier of the destination node')
|
||||
|
||||
filters = (
|
||||
{ bilateral:
|
||||
{ spatial_std: scalespline(d='Scale of profile spatial standard deviation')
|
||||
, color_std: scalespline(d='Scale of profile color standard deviation')
|
||||
, density_std: scalespline(d='Scale of profile density standard deviation')
|
||||
, density_pow: scalespline(d='Scale of profile density pre-blur exponent')
|
||||
, gradient: spline(1, d='Scale of profile gradient filter intensity '
|
||||
'(can be negative)')
|
||||
}
|
||||
, colorclip:
|
||||
{ bg_r: spline(0, 0, 1)
|
||||
, bg_g: spline(0, 0, 1)
|
||||
, bg_b: spline(0, 0, 1)
|
||||
, gamma: scalespline()
|
||||
, gamma_threshold: spline(0.01, 0, 1)
|
||||
, highlight_power: spline(-1, -1, 1)
|
||||
, vibrance: spline(1, 0, 1)
|
||||
}
|
||||
, de:
|
||||
{ radius: scalespline(d='Scale of profile filter radius')
|
||||
, minimum: scalespline(0, d='Scale against adjusted DE radius of '
|
||||
'minimum radius')
|
||||
, curve: scalespline(0.6, d='Absolute (unscaled) value of DE curve')
|
||||
}
|
||||
# TODO: absolute or relative?
|
||||
, logscale: {brightness: scalespline(4, d='Absolute log brightness')}
|
||||
})
|
||||
|
||||
anim = (
|
||||
{ type: 'animation'
|
||||
, info: dict(authors=List(author), name=name, src=src, dst=dst)
|
||||
, camera:
|
||||
# Should center_{xy} be scaled relative to the 'scale' parameter, or is
|
||||
# that just too complicated for this representation?
|
||||
{ center_x: spline()
|
||||
, center_y: spline()
|
||||
, density: scalespline()
|
||||
, dither_width: scalespline()
|
||||
, rotation: spline(period=360)
|
||||
, scale: scalespline()
|
||||
}
|
||||
, filters: filters
|
||||
, time:
|
||||
{ duration:
|
||||
, frame_width: scalespline(d='Scale of profile temporal width per frame.')
|
||||
}
|
||||
, palettes: list_(Palette())
|
||||
, xforms: map(xform)
|
||||
, final_xform: xform
|
||||
})
|
112
cuburn/genome/spec.py
Normal file
112
cuburn/genome/spec.py
Normal file
@ -0,0 +1,112 @@
|
||||
from spectypes import *
|
||||
from variations import var_params
|
||||
|
||||
affine = (
|
||||
{ 'angle': spline(45, period=360)
|
||||
, 'spread': spline(45, period=180)
|
||||
, 'magnitude': XYPair(scalespline())
|
||||
, 'offset': XYPair(spline())
|
||||
})
|
||||
|
||||
xform = (
|
||||
{ 'pre_affine': affine
|
||||
, 'post_affine': affine
|
||||
, 'color': spline(0, 0, 1)
|
||||
, 'color_speed': spline(0.5, 0, 1)
|
||||
, 'weight': spline()
|
||||
, 'opacity': scalespline(max=1)
|
||||
, 'variations': var_params
|
||||
})
|
||||
|
||||
# Since the structure of the info element differs between anims, nodes and
|
||||
# edges, we pull out some of the common elements here
|
||||
author = String('Attribution in the form: "Name [<email>][, url]"')
|
||||
name = String('A human-readable name for this entity')
|
||||
src = String('The identifier of the source node')
|
||||
dst = String('The identifier of the destination node')
|
||||
|
||||
filters = (
|
||||
{ 'bilateral':
|
||||
{ 'spatial_std': scalespline(6,
|
||||
d='Spatial filter radius, normalized to 1080p pixels')
|
||||
, 'color_std': scalespline(0.05,
|
||||
d='Color filter radius, in YUV space, normalized to [0,1]')
|
||||
, 'density_std': scalespline(1.5, d='Density standard deviation')
|
||||
, 'density_pow': scalespline(0.8, d='Density pre-filter power')
|
||||
, 'gradient': scalespline(4.0, min=None,
|
||||
d='Intensity of gradient amplification (can be negative)')
|
||||
}
|
||||
, 'colorclip':
|
||||
{ 'gamma': scalespline(4)
|
||||
, 'gamma_threshold': spline(0.01, 0, 1)
|
||||
, 'highlight_power': spline(-1, -1, 1)
|
||||
, 'vibrance': scalespline()
|
||||
}
|
||||
, 'de':
|
||||
{ 'radius': scalespline(11, d='Spatial filter radius in flam3 units')
|
||||
, 'minimum': scalespline(0, max=1, d='Proportional min radius')
|
||||
, 'curve': scalespline(0.6, d='Power of filter radius with density')
|
||||
}
|
||||
, 'haloclip': {'gamma': scalespline(4)}
|
||||
, 'logscale': {'brightness': scalespline(4, d='Log-scale brightness')}
|
||||
})
|
||||
|
||||
camera = (
|
||||
{ 'center': XYPair(spline())
|
||||
, 'spp': scalespline(d='Samples per pixel multiplier')
|
||||
, 'dither_width': scalespline()
|
||||
, 'rotation': spline(period=360)
|
||||
, 'scale': scalespline()
|
||||
})
|
||||
|
||||
time = (
|
||||
{ 'duration': scalar(1)
|
||||
, 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
|
||||
})
|
||||
|
||||
base = (
|
||||
{ 'camera': camera
|
||||
, 'filters': filters
|
||||
, 'palette': list_(Palette())
|
||||
, 'xforms': map_(xform)
|
||||
, 'final_xform': xform
|
||||
})
|
||||
|
||||
anim = dict(base)
|
||||
anim.update(type='animation', time=time,
|
||||
info=dict(authors=list_(author), name=name, src=src, dst=dst,
|
||||
origin=string_()))
|
||||
|
||||
# TODO
|
||||
node = dict(base)
|
||||
node.update(type='node', info=dict(author=author, author_url=string_(),
|
||||
id=string_(), name=name))
|
||||
|
||||
# TODO
|
||||
edge = dict(anim)
|
||||
edge.update(type='edge',
|
||||
info=dict(author=author, id=string_(), src=src, dst=dst),
|
||||
xforms=dict(src=map_(xform), dst=map_(xform)))
|
||||
|
||||
# Yeah, now I'm just messing around.
|
||||
prof_filters = dict([(fk, dict([(k, refscalar(1, '.'.join(['filters', fk, k])))
|
||||
for k in fv])) for fk, fv in filters.items()])
|
||||
# And here's a completely stupid hack to drag scale into the logscale filter
|
||||
prof_filters['logscale']['scale'] = refscalar(1, 'camera.scale')
|
||||
|
||||
default_filters = [{'type': k} for k in ['bilateral', 'logscale', 'colorclip']]
|
||||
|
||||
profile = (
|
||||
{ 'duration': RefScalar(30, 'time.duration', 'Base duration in seconds')
|
||||
, 'fps': Scalar(24, 'Frames per second')
|
||||
, 'height': Scalar(1920, 'Output height in pixels')
|
||||
, 'width': Scalar(1080, 'Output width in pixels')
|
||||
, 'frame_width': refscalar(1, 'time.frame_width')
|
||||
, 'spp': RefScalar(2000, 'camera.spp', 'Base samples per pixel')
|
||||
, 'skip': Scalar(0, 'Skip this many frames between each rendered frame')
|
||||
, 'filters': TypedList(prof_filters, default_filters,
|
||||
'Ordered list of filters to apply')
|
||||
})
|
||||
|
||||
# Types recognized as independent units with a 'type' key
|
||||
toplevels = dict(animation=anim, node=node, edge=edge, profile=profile)
|
45
cuburn/genome/spectypes.py
Normal file
45
cuburn/genome/spectypes.py
Normal file
@ -0,0 +1,45 @@
|
||||
from collections import namedtuple
|
||||
|
||||
Map = namedtuple('Map', 'type doc')
|
||||
map_ = lambda type, d=None: Map(type, d)
|
||||
|
||||
List = namedtuple('List', 'type doc')
|
||||
list_ = lambda type, d=None: List(type, d)
|
||||
|
||||
# A list as above, but where each element is a dict with a 'type' parameter
|
||||
# corresponding to one of the specs listed in the 'types' dict on this spec.
|
||||
TypedList = namedtuple('TypedList', 'types defaults doc')
|
||||
typedlist = lambda types, defaults=[], d=None: TypedList(types, defaults, d)
|
||||
|
||||
Spline = namedtuple('Spline', 'default min max interp period doc var')
|
||||
def spline(default=0, min=None, max=None, interp='linear', period=None, d=None):
|
||||
return Spline(default, min, max, interp, period, d, False)
|
||||
def scalespline(default=1, min=0, max=None, d=None):
|
||||
"""Spline helper, with defaults appropriate for a scaling parameter."""
|
||||
return Spline(default, min, None, 'mag', None, d, False)
|
||||
|
||||
class XYPair(dict):
|
||||
"""
|
||||
Specialization of spline over two dimensions. Separate type is a hint to
|
||||
UIs and mutator, but this may be treated just like a normal dict.
|
||||
"""
|
||||
def __init__(self, type):
|
||||
self['x'] = self['y'] = self.type = type
|
||||
|
||||
Scalar = namedtuple('Scalar', 'default doc')
|
||||
scalar = lambda default, d=None: Scalar(default, d)
|
||||
|
||||
# These are scalars, as used in profiles, but which are scaled by some other
|
||||
# parameter (in the genome) given by name as ``ref``.
|
||||
RefScalar = namedtuple('RefScalar', 'default ref doc')
|
||||
refscalar = lambda default, ref, d=None: RefScalar(default, ref, d)
|
||||
|
||||
String = namedtuple('String', 'doc')
|
||||
def string_(d=None):
|
||||
return String(d)
|
||||
Enum = namedtuple('Enum', 'choices doc')
|
||||
def enum(choices, d=None):
|
||||
"""Enum helper. 'choices' is a space-separated string."""
|
||||
return Enum(choices.split(), d)
|
||||
|
||||
Palette = namedtuple('Palette', '')
|
188
cuburn/genome/use.py
Normal file
188
cuburn/genome/use.py
Normal file
@ -0,0 +1,188 @@
|
||||
import numpy as np
|
||||
|
||||
from spectypes import Spline, Scalar, RefScalar, Map, List, TypedList
|
||||
from spec import toplevels
|
||||
|
||||
class Wrapper(object):
|
||||
def __init__(self, val, spec=None, path=()):
|
||||
if spec is None:
|
||||
spec = toplevels[val['type']]
|
||||
# plain 'val' would conflict with some variation property names
|
||||
self._val, self.spec, self.path = val, spec, path
|
||||
|
||||
def wrap(self, name, spec, val):
|
||||
# Oh, a visitor. How... pedestrian.
|
||||
path = self.path + (name,)
|
||||
if isinstance(spec, Spline):
|
||||
return self.wrap_spline(path, spec, val)
|
||||
elif isinstance(spec, Scalar):
|
||||
return self.wrap_scalar(path, spec, val)
|
||||
elif isinstance(spec, RefScalar):
|
||||
return self.wrap_refscalar(path, spec, val)
|
||||
elif isinstance(spec, dict):
|
||||
return self.wrap_dict(path, spec, val)
|
||||
elif isinstance(spec, Map):
|
||||
return self.wrap_Map(path, spec, val)
|
||||
elif isinstance(spec, List):
|
||||
return self.wrap_List(path, spec, val)
|
||||
elif isinstance(spec, TypedList):
|
||||
return self.wrap_TypedList(path, spec, val)
|
||||
return self.wrap_default(path, spec, val)
|
||||
|
||||
def wrap_default(self, path, spec, val):
|
||||
return val
|
||||
|
||||
def wrap_spline(self, path, spec, val):
|
||||
return val
|
||||
|
||||
def wrap_scalar(self, path, spec, val):
|
||||
return val if val is not None else spec.default
|
||||
|
||||
def wrap_dict(self, path, spec, val):
|
||||
return type(self)(val or {}, spec, path)
|
||||
|
||||
def wrap_Map(self, path, spec, val):
|
||||
return self.wrap_dict(path, spec, val)
|
||||
|
||||
def wrap_List(self, path, spec, val):
|
||||
return [self.wrap(spec.type, v) for v in val]
|
||||
|
||||
def wrap_TypedList(self, path, spec, val):
|
||||
val = val if val is not None else spec.defaults
|
||||
return [self.wrap(path+(str(i),), spec.types[v['type']], v)
|
||||
for i, v in enumerate(val)]
|
||||
|
||||
def get_spec(self, name):
|
||||
if isinstance(self.spec, Map):
|
||||
return self.spec.type
|
||||
return self.spec[name]
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.wrap(name, self.get_spec(name), self._val.get(name))
|
||||
|
||||
# Container emulation
|
||||
def keys(self):
|
||||
return sorted(self._val.keys())
|
||||
def items(self):
|
||||
return sorted((k, self[k]) for k in self)
|
||||
def __contains__(self, name):
|
||||
self.get_spec(name) # raise IndexError if name is not typed
|
||||
return name in self._val
|
||||
def __iter__(self):
|
||||
return iter(sorted(self._val))
|
||||
def __getitem__(self, name):
|
||||
return getattr(self, str(name))
|
||||
|
||||
|
||||
class RefWrapper(Wrapper):
|
||||
"""
|
||||
Wrapper that handles RefScalars, as with profile objects.
|
||||
"""
|
||||
# Turns out (somewhat intentionally) that every spline parameter used on
|
||||
# the host has a matching parameter in the profile, so this
|
||||
def __init__(self, val, other, spec=None, path=()):
|
||||
super(RefWrapper, self).__init__(val, spec, path)
|
||||
self.other = other
|
||||
|
||||
def wrap_dict(self, path, spec, val):
|
||||
return type(self)(val or {}, self.other, spec, path)
|
||||
|
||||
def wrap_refscalar(self, path, spec, val):
|
||||
spev = self.other
|
||||
for part in spec.ref.split('.'):
|
||||
spev = spev[part]
|
||||
spev *= val if val is not None else spec.default
|
||||
return spev
|
||||
|
||||
class SplineWrapper(Wrapper):
|
||||
def wrap_spline(self, path, spec, val):
|
||||
return SplineEval(val if val is not None else spec.default,
|
||||
spec.interp)
|
||||
|
||||
class SplineEval(object):
|
||||
_mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1],
|
||||
[1,-1, 0, 0], [-2, 3, 0, 0]])
|
||||
_deriv = np.matrix(np.diag([3,2,1], 1))
|
||||
|
||||
def __init__(self, knots, interp='linear'):
|
||||
self.knots, self.interp = self.normalize(knots), interp
|
||||
|
||||
@staticmethod
|
||||
def normalize(knots):
|
||||
if isinstance(knots, (int, float)):
|
||||
v0, v1 = 0, 0
|
||||
knots = [(0, knots), (1, knots)]
|
||||
elif len(knots) % 2 != 0:
|
||||
raise ValueError("List with odd number of elements given")
|
||||
elif len(knots) == 2:
|
||||
v0, v1 = 0, 0
|
||||
knots = [(0, knots[0]), (1, knots[1])]
|
||||
else:
|
||||
p0, v0, p1, v1 = knots[:4]
|
||||
knots = [(0, p0), (1, p1)] + zip(knots[4::2], knots[5::2])
|
||||
|
||||
knots = sorted(knots)
|
||||
|
||||
# If stabilizing knots are missing before or after the edges of the
|
||||
# [0,1] interval, add them. In almost all cases, the precise timing of
|
||||
# the end knots has little affect on the shape of the curve.
|
||||
td = 2
|
||||
if knots[0][0] >= 0:
|
||||
knots = [(-td, knots[1][1] - (knots[1][0] - (-td)) * v0)] + knots
|
||||
if knots[-1][0] <= 1:
|
||||
knots.extend([(1+td, knots[-2][1] + (1+td - knots[-2][0]) * v1)])
|
||||
|
||||
knotarray = np.zeros((2, len(knots)))
|
||||
knotarray.T[:] = knots
|
||||
return knotarray
|
||||
|
||||
def find_knots(self, itime):
|
||||
idx = np.searchsorted(self.knots[0], itime) - 2
|
||||
idx = max(0, min(idx, len(self.knots[0]) - 4))
|
||||
|
||||
times = self.knots[0][idx:idx+4]
|
||||
vals = self.knots[1][idx:idx+4]
|
||||
# Normalize to [0,1]
|
||||
t = itime - times[1]
|
||||
times = times - times[1]
|
||||
scale = 1 / times[2]
|
||||
t = t * scale
|
||||
times = times * scale
|
||||
return times, vals, t, scale
|
||||
|
||||
def __call__(self, itime, deriv=0):
|
||||
times, vals, t, scale = self.find_knots(itime)
|
||||
|
||||
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
|
||||
m2 = (vals[3] - vals[1]) / times[3]
|
||||
|
||||
mat = self._mat
|
||||
if deriv:
|
||||
mat = mat * (scale * self._deriv) ** deriv
|
||||
val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T
|
||||
return val[0,0]
|
||||
|
||||
def __imul__(self, other):
|
||||
self.knots[1] *= other
|
||||
return self
|
||||
|
||||
def _plt(self, name='SplEval', fig=111, show=True):
|
||||
import matplotlib.pyplot as plt
|
||||
x = np.linspace(-0.0, 1.0, 500)
|
||||
r = x[1] - x[0]
|
||||
plt.figure(fig)
|
||||
plt.title(name)
|
||||
plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--',
|
||||
self.knots[0],self.knots[1],'x')
|
||||
plt.xlim(0.0, 1.0)
|
||||
if show:
|
||||
plt.show()
|
||||
|
||||
def wrap_genome(prof, gnm):
|
||||
# It's not obvious that this is what needs to happen, so we wrap. The
|
||||
# timing is simplistic, and may get expanded or moved later.
|
||||
gprof = RefWrapper(prof, SplineWrapper(gnm), toplevels['profile'])
|
||||
nframes = round(gprof.fps * gprof.duration)
|
||||
times = np.linspace(0, 1, nframes + 1)
|
||||
times = times[:-1] + 0.5 * (times[1] - times[0])
|
||||
return gprof, times
|
122
cuburn/genome/util.py
Normal file
122
cuburn/genome/util.py
Normal file
@ -0,0 +1,122 @@
|
||||
import base64
|
||||
import numpy as np
|
||||
|
||||
from cuburn.code.util import crep
|
||||
|
||||
def get(dct, default, *keys):
|
||||
if len(keys) == 1:
|
||||
keys = keys[0].split('.')
|
||||
for k in keys:
|
||||
if k in dct:
|
||||
dct = dct[k]
|
||||
else:
|
||||
return default
|
||||
return dct
|
||||
|
||||
def flatten(dct, ctx=()):
|
||||
"""
|
||||
Given a nested dict, return a flattened dict with dot-separated string
|
||||
keys. Keys that have dots in them already are treated the same.
|
||||
|
||||
>>> flatten({'ab': {'xy.zw': 1}, 4: 5}) == {'ab.xy.zw': 1, '4': 5}
|
||||
True
|
||||
"""
|
||||
|
||||
for k, v in dct.items():
|
||||
k = str(k)
|
||||
if isinstance(v, dict):
|
||||
for sk, sv in flatten(v, ctx + (k,)):
|
||||
yield sk, sv
|
||||
else:
|
||||
yield '.'.join(ctx + (k,)), v
|
||||
|
||||
def unflatten(kvlist):
|
||||
"""
|
||||
Given a flattened dict, return a nested dict, where every dot-separated
|
||||
key is converted into a sub-dict.
|
||||
|
||||
>>> (unflatten([('ab.xy.zw', 1), ('4', 5)) ==
|
||||
... {'ab': {'xy': {'zw': 1}}, '4': 5})
|
||||
True
|
||||
"""
|
||||
def go(d, k, v):
|
||||
if len(k) == 1:
|
||||
d[k[0]] = v
|
||||
else:
|
||||
go(d.setdefault(k[0], {}), k[1:], v)
|
||||
out = {}
|
||||
for k, v in kvlist:
|
||||
go(out, k.split('.'), v)
|
||||
return out
|
||||
|
||||
|
||||
def palette_decode(datastrs):
|
||||
"""
|
||||
Decode a palette (stored as a list suitable for JSON packing) into a
|
||||
palette. Internal palette format is simply as a (256,4) array of [0,1]
|
||||
RGBA floats.
|
||||
"""
|
||||
if datastrs[0] != 'rgb8':
|
||||
raise NotImplementedError
|
||||
raw = base64.b64decode(''.join(datastrs[1:]))
|
||||
pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3))
|
||||
data = np.ones((256, 4), np.float32)
|
||||
data[:,:3] = pal / 255.0
|
||||
return data
|
||||
|
||||
def palette_encode(data, format='rgb8'):
|
||||
"""
|
||||
Encode an internal-format palette to an external representation.
|
||||
"""
|
||||
if format != 'rgb8':
|
||||
raise NotImplementedError
|
||||
clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0)))
|
||||
enc = base64.b64encode(np.uint8(clamp))
|
||||
return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)]
|
||||
|
||||
def json_encode(obj):
|
||||
"""
|
||||
Encode an object into JSON notation, formatted to be more readable than
|
||||
the output of the standard 'json' package for genomes.
|
||||
|
||||
This serializer only works on the subset of JSON used in genomes.
|
||||
"""
|
||||
result = _js_enc_obj(obj).lstrip()
|
||||
result = '\n'.join(l.rstrip() for l in result.split('\n'))
|
||||
return result + '\n'
|
||||
|
||||
def _js_enc_obj(obj, indent=0):
|
||||
isnum = lambda v: isinstance(v, (float, int, np.number))
|
||||
|
||||
def wrap(pairs, delims):
|
||||
do, dc = delims
|
||||
i = ' ' * indent
|
||||
out = ''.join([do, ', '.join(pairs), dc])
|
||||
if '\n' not in out and len(out) + indent < 70:
|
||||
return out
|
||||
return ''.join(['\n', i, do, ' ', ('\n'+i+', ').join(pairs),
|
||||
'\n', i, dc])
|
||||
|
||||
if isinstance(obj, dict):
|
||||
if not obj:
|
||||
return '{}'
|
||||
digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv
|
||||
ks, vs = zip(*sorted(obj.items(), key=digsort))
|
||||
if ks == ('b', 'g', 'r'):
|
||||
ks, vs = reversed(ks), reversed(vs)
|
||||
ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks]
|
||||
vs = [_js_enc_obj(v, indent+2) for v in vs]
|
||||
return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}')
|
||||
elif isinstance(obj, list):
|
||||
vs = [_js_enc_obj(v, indent+2) for v in obj]
|
||||
if vs and len(vs) % 2 == 0 and isnum(obj[1]):
|
||||
vs = map(', '.join, zip(vs[::2], vs[1::2]))
|
||||
return wrap(vs, '[]')
|
||||
#elif isinstance(obj, SplEval):
|
||||
#return _js_enc_obj(obj.knotlist, indent)
|
||||
elif isinstance(obj, basestring):
|
||||
return crep(obj)
|
||||
elif isnum(obj):
|
||||
return '%.6g' % obj
|
||||
raise TypeError("Don't know how to serialize %s of type %s" %
|
||||
(obj, type(obj)))
|
127
cuburn/genome/variations.py
Normal file
127
cuburn/genome/variations.py
Normal file
@ -0,0 +1,127 @@
|
||||
from spectypes import spline, scalespline
|
||||
import numpy as np
|
||||
# Pre-instantiated default splines. Used a *lot*.
|
||||
s, ss, sz = spline(), scalespline(), scalespline(min=0)
|
||||
|
||||
__all__ = ["var_names", "var_params"]
|
||||
|
||||
# A map from flam3 variation numbers to variation names. Some variations may
|
||||
# not be included in this list if they don't yet exist in flam3.
|
||||
var_names = {}
|
||||
|
||||
# A map from variation names to a dict of parameter types, suitable for
|
||||
# inclusion in the genome schema.
|
||||
var_params = {}
|
||||
|
||||
def var(num, name, **params):
|
||||
if num is not None:
|
||||
var_names[num] = name
|
||||
# Mark as a variation parameter spline. This can be handled in various
|
||||
# ways by interpolation - usually by copying a value when missing, instead
|
||||
# of reading the default value.
|
||||
for k, v in params.items():
|
||||
params[k] = v._replace(var=True)
|
||||
params['weight'] = scalespline(0)
|
||||
var_params[name] = params
|
||||
|
||||
# TODO: review all parameter splines, possibly programmatically
|
||||
var(0, 'linear')
|
||||
var(1, 'sinusoidal')
|
||||
var(2, 'spherical')
|
||||
var(3, 'swirl')
|
||||
var(4, 'horseshoe')
|
||||
var(5, 'polar')
|
||||
var(6, 'handkerchief')
|
||||
var(7, 'heart')
|
||||
var(8, 'disc')
|
||||
var(9, 'spiral')
|
||||
var(10, 'hyperbolic')
|
||||
var(11, 'diamond')
|
||||
var(12, 'ex')
|
||||
var(13, 'julia')
|
||||
var(14, 'bent')
|
||||
var(15, 'waves')
|
||||
var(16, 'fisheye')
|
||||
var(17, 'popcorn')
|
||||
var(18, 'exponential')
|
||||
var(19, 'power')
|
||||
var(20, 'cosine')
|
||||
var(21, 'rings')
|
||||
var(22, 'fan')
|
||||
var(23, 'blob', low=ss, high=ss, waves=ss)
|
||||
var(24, 'pdj', a=s, b=s, c=s, d=s)
|
||||
var(25, 'fan2', x=s, y=s)
|
||||
var(26, 'rings2', val=s)
|
||||
var(27, 'eyefish')
|
||||
var(28, 'bubble')
|
||||
var(29, 'cylinder')
|
||||
var(30, 'perspective', angle=s, dist=s) # TODO: period
|
||||
var(31, 'noise')
|
||||
var(32, 'julian', power=ss, dist=ss)
|
||||
var(33, 'juliascope', power=ss, dist=ss)
|
||||
var(34, 'blur')
|
||||
var(35, 'gaussian_blur')
|
||||
var(36, 'radial_blur', angle=spline(period=4))
|
||||
var(37, 'pie', slices=spline(6, 1), rotation=s, thickness=spline(0.5, 0, 1))
|
||||
var(38, 'ngon', sides=spline(5), power=spline(3),
|
||||
circle=spline(1), corners=spline(2))
|
||||
var(39, 'curl', c1=spline(1), c2=s) # TODO: not identity?
|
||||
|
||||
var(40, 'rectangles', x=s, y=s)
|
||||
var(41, 'arch')
|
||||
var(42, 'tangent')
|
||||
var(43, 'square')
|
||||
var(44, 'rays')
|
||||
var(45, 'blade')
|
||||
var(46, 'secant2')
|
||||
var(48, 'cross')
|
||||
var(49, 'disc2', rot=s, twist=s)
|
||||
var(50, 'super_shape', rnd=s, m=s, n1=ss, n2=spline(1), n3=spline(1), holes=s)
|
||||
var(51, 'flower', holes=s, petals=s)
|
||||
var(52, 'conic', holes=s, eccentricity=spline(1))
|
||||
var(53, 'parabola', height=ss, width=ss)
|
||||
var(54, 'bent2', x=ss, y=ss)
|
||||
var(55, 'bipolar', shift=s)
|
||||
var(56, 'boarders')
|
||||
var(57, 'butterfly')
|
||||
var(58, 'cell', size=ss)
|
||||
var(59, 'cpow', r=ss, i=s, power=ss)
|
||||
var(60, 'curve', xamp=s, yamp=s, xlength=ss, ylength=ss)
|
||||
var(61, 'edisc')
|
||||
var(62, 'elliptic')
|
||||
var(63, 'escher', beta=spline(period=2*np.pi))
|
||||
var(64, 'foci')
|
||||
var(65, 'lazysusan', x=s, y=s, twist=s, space=s, spin=s)
|
||||
var(66, 'loonie')
|
||||
var(67, 'pre_blur')
|
||||
var(68, 'modulus', x=s, y=s)
|
||||
var(69, 'oscope', separation=spline(1), frequency=scalespline(np.pi),
|
||||
amplitude=ss, damping=s)
|
||||
var(70, 'polar2')
|
||||
var(71, 'popcorn2', x=s, y=s, c=s)
|
||||
var(72, 'scry')
|
||||
var(73, 'separation', x=s, xinside=s, y=s, yinside=s)
|
||||
var(74, 'split', xsize=s, ysize=s)
|
||||
var(75, 'splits', x=s, y=s)
|
||||
var(76, 'stripes', space=s, warp=s)
|
||||
var(77, 'wedge', angle=s, hole=s, count=ss, swirl=s)
|
||||
var(80, 'whorl', inside=s, outside=s)
|
||||
var(81, 'waves2', scalex=ss, scaley=ss,
|
||||
freqx=scalespline(np.pi), freqy=scalespline(np.pi))
|
||||
var(82, 'exp')
|
||||
var(83, 'log')
|
||||
var(84, 'sin')
|
||||
var(85, 'cos')
|
||||
var(86, 'tan')
|
||||
var(87, 'sec')
|
||||
var(88, 'csc')
|
||||
var(89, 'cot')
|
||||
var(90, 'sinh')
|
||||
var(91, 'cosh')
|
||||
var(92, 'tanh')
|
||||
var(93, 'sech')
|
||||
var(94, 'csch')
|
||||
var(95, 'coth')
|
||||
var(97, 'flux', spread=s)
|
||||
var(98, 'mobius', re_a=s, im_a=s, re_b=s, im_b=s,
|
||||
re_c=s, im_c=s, re_d=s, im_d=s)
|
@ -18,6 +18,7 @@ import filters
|
||||
import output
|
||||
from code import util, mwc, iter, interp, sort
|
||||
from code.util import ClsMod, devlib, filldptrlib, assemble_code, launch
|
||||
from cuburn.genome.util import palette_decode
|
||||
|
||||
RenderedImage = namedtuple('RenderedImage', 'buf idx gpu_time')
|
||||
Dimensions = namedtuple('Dimensions', 'w h aw ah astride')
|
||||
@ -200,7 +201,7 @@ class Renderer(object):
|
||||
MAX_MODREFS = 20
|
||||
_modrefs = []
|
||||
|
||||
def __init__(self, gnm):
|
||||
def __init__(self, gnm, gprof):
|
||||
self.packer, self.lib = iter.mkiterlib(gnm)
|
||||
cubin = util.compile('iter', assemble_code(self.lib))
|
||||
self.mod = cuda.module_from_buffer(cubin)
|
||||
@ -210,9 +211,7 @@ class Renderer(object):
|
||||
self._modrefs.append(self.mod)
|
||||
|
||||
# TODO: make these customizable
|
||||
self.filts = [ filters.Bilateral()
|
||||
, filters.Logscale()
|
||||
, filters.ColorClip() ]
|
||||
self.filts = filters.create(gprof)
|
||||
self.out = output.PILOutput()
|
||||
|
||||
class RenderManager(ClsMod):
|
||||
@ -236,13 +235,14 @@ class RenderManager(ClsMod):
|
||||
Note that for now, this is broken! It ignores ``gnm``, and only packs
|
||||
the genome that was used when creating the renderer.
|
||||
"""
|
||||
times, knots = rdr.packer.pack(self.pool)
|
||||
times, knots = rdr.packer.pack(gnm, self.pool)
|
||||
cuda.memcpy_htod_async(self.src_a.d_times, times, self.stream_a)
|
||||
cuda.memcpy_htod_async(self.src_a.d_knots, knots, self.stream_a)
|
||||
|
||||
ptimes, pidxs = zip(*gnm.palette_times)
|
||||
palettes = self.pool.allocate((len(ptimes), 256, 4), f32)
|
||||
palettes[:] = [gnm.decoded_palettes[i] for i in pidxs]
|
||||
palsrc = dict([(v[0], palette_decode(v[1:])) for v in gnm['palette']])
|
||||
ptimes, pvals = zip(*sorted(palsrc.items()))
|
||||
palettes = self.pool.allocate((len(palsrc), 256, 4), f32)
|
||||
palettes[:] = pvals
|
||||
palette_times = self.pool.allocate((self.src_a.max_knots,), f32)
|
||||
palette_times.fill(1e9)
|
||||
palette_times[:len(ptimes)] = ptimes
|
||||
@ -271,7 +271,7 @@ class RenderManager(ClsMod):
|
||||
256, np.ceil(nts / 256.),
|
||||
self.info_a.d_params, self.src_a.d_times, self.src_a.d_knots,
|
||||
f32(ts), f32(td / nts), i32(nts))
|
||||
#self._print_interp_knots(rdr)
|
||||
self._print_interp_knots(rdr)
|
||||
|
||||
def _print_interp_knots(self, rdr, tsidx=5):
|
||||
infos = cuda.from_device(self.info_a.d_params,
|
||||
@ -279,7 +279,7 @@ class RenderManager(ClsMod):
|
||||
for i, n in zip(infos[-1], rdr.packer.packed):
|
||||
print '%60s %g' % ('_'.join(n), i)
|
||||
|
||||
def _iter(self, rdr, gnm, dim, tc):
|
||||
def _iter(self, rdr, gnm, gprof, dim, tc):
|
||||
tref = rdr.mod.get_surfref('flatpal')
|
||||
tref.set_array(self.info_a.d_pal_array, 0)
|
||||
|
||||
@ -291,19 +291,29 @@ class RenderManager(ClsMod):
|
||||
fill(self.fb.d_points, self.fb._len_d_points / 4, f32(np.nan))
|
||||
|
||||
nts = self.info_a.ntemporal_samples
|
||||
nsamps = (gnm.spp(tc) * dim.w * dim.h)
|
||||
nsamps = (gprof.spp(tc) * dim.w * dim.h)
|
||||
nrounds = int(nsamps / (nts * 256. * 256)) + 1
|
||||
launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, nrounds),
|
||||
self.fb.d_front, self.fb.d_side,
|
||||
self.fb.d_rb, self.fb.d_seeds, self.fb.d_points,
|
||||
self.info_a.d_params)
|
||||
|
||||
def launch_iter(n):
|
||||
if n == 0: return
|
||||
launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, n),
|
||||
self.fb.d_front, self.fb.d_side,
|
||||
self.fb.d_rb, self.fb.d_seeds, self.fb.d_points,
|
||||
self.info_a.d_params)
|
||||
# Split the launch into multiple rounds, possibly (slightly) reducing
|
||||
# work overlap but avoiding stalls when working on a device with an
|
||||
# active X session. TODO: characterize performance impact, autodetect
|
||||
BLOCK_SIZE = 4
|
||||
for i in range(BLOCK_SIZE-1, nrounds, BLOCK_SIZE):
|
||||
launch_iter(BLOCK_SIZE)
|
||||
launch_iter(nrounds%BLOCK_SIZE)
|
||||
|
||||
nblocks = int(np.ceil(np.sqrt(dim.ah*dim.astride/256.)))
|
||||
launch('flush_atom', self.mod, self.stream_a,
|
||||
256, (nblocks, nblocks),
|
||||
u64(self.fb.d_front), u64(self.fb.d_side), i32(nbins))
|
||||
|
||||
def queue_frame(self, rdr, gnm, tc, w, h, copy=True):
|
||||
def queue_frame(self, rdr, gnm, gprof, tc, copy=True):
|
||||
"""
|
||||
Queue one frame for rendering.
|
||||
|
||||
@ -332,9 +342,10 @@ class RenderManager(ClsMod):
|
||||
"""
|
||||
# Note: we synchronize on the previous stream if buffers need to be
|
||||
# reallocated, which implicitly also syncs the current stream.
|
||||
dim = self.fb.set_dim(w, h, self.stream_b)
|
||||
dim = self.fb.set_dim(gprof.width, gprof.height, self.stream_b)
|
||||
|
||||
td = gnm.adj_frame_width(tc)
|
||||
# TODO: calculate this externally somewhere?
|
||||
td = gprof.frame_width(tc) / round(gprof.fps * gprof.duration)
|
||||
ts, te = tc - 0.5 * td, tc + 0.5 * td
|
||||
|
||||
# The stream interleaving here is nontrivial.
|
||||
@ -345,12 +356,12 @@ class RenderManager(ClsMod):
|
||||
self._interp(rdr, gnm, dim, ts, td)
|
||||
if self.filt_evt:
|
||||
self.stream_a.wait_for_event(self.filt_evt)
|
||||
self._iter(rdr, gnm, dim, tc)
|
||||
self._iter(rdr, gnm, gprof, dim, tc)
|
||||
if self.copy_evt:
|
||||
self.stream_a.wait_for_event(self.copy_evt)
|
||||
for filt in rdr.filts:
|
||||
filt.apply(self.fb, gnm, dim, tc, self.stream_a)
|
||||
rdr.out.convert(self.fb, gnm, dim, self.stream_a)
|
||||
for filt, params in zip(rdr.filts, gprof.filters):
|
||||
filt.apply(self.fb, gprof, params, dim, tc, self.stream_a)
|
||||
rdr.out.convert(self.fb, gprof, dim, self.stream_a)
|
||||
self.filt_evt = cuda.Event().record(self.stream_a)
|
||||
h_out = rdr.out.copy(self.fb, dim, self.pool, self.stream_a)
|
||||
self.copy_evt = cuda.Event().record(self.stream_a)
|
||||
@ -359,13 +370,13 @@ class RenderManager(ClsMod):
|
||||
self.stream_a, self.stream_b = self.stream_b, self.stream_a
|
||||
return self.copy_evt, h_out
|
||||
|
||||
def render(self, gnm, times, w, h):
|
||||
def render(self, gnm, gprof, times):
|
||||
"""
|
||||
A port of the old rendering function, retained for backwards
|
||||
compatibility. Some of this will be pulled into as-yet-undecided
|
||||
methods for more DRY.
|
||||
"""
|
||||
rdr = Renderer(gnm)
|
||||
rdr = Renderer(gnm, gprof)
|
||||
last_evt = cuda.Event().record(self.stream_a)
|
||||
last_idx = None
|
||||
def wait(): # Times like these where you wish for a macro
|
||||
@ -374,7 +385,7 @@ class RenderManager(ClsMod):
|
||||
gpu_time = last_evt.time_since(two_evts_ago)
|
||||
return RenderedImage(last_buf, last_idx, gpu_time)
|
||||
for idx, tc in times:
|
||||
evt, h_buf = self.queue_frame(rdr, gnm, tc, w, h, last_idx is None)
|
||||
evt, h_buf = self.queue_frame(rdr, gnm, gprof, tc, last_idx is None)
|
||||
if last_idx:
|
||||
yield wait()
|
||||
two_evts_ago, last_evt = last_evt, evt
|
||||
|
20
main.py
20
main.py
@ -22,13 +22,14 @@ from itertools import ifilter
|
||||
import numpy as np
|
||||
import pycuda.driver as cuda
|
||||
|
||||
from cuburn import genome, render, filters, output
|
||||
from cuburn import render, filters, output
|
||||
from cuburn.genome import convert, use
|
||||
|
||||
profiles = {
|
||||
'1080p': dict(fps=24, width=1920, height=1080, quality=3000, skip=0),
|
||||
'720p': dict(fps=24, width=1280, height=720, quality=2500, skip=0),
|
||||
'540p': dict(fps=24, width=960, height=540, quality=2500, skip=0),
|
||||
'preview': dict(fps=24, width=640, height=360, quality=800, skip=1)
|
||||
'1080p': dict(width=1920, height=1080),
|
||||
'720p': dict(width=1280, height=720),
|
||||
'540p': dict(width=960, height=540),
|
||||
'preview': dict(width=640, height=360, quality=800, skip=1)
|
||||
}
|
||||
|
||||
def save(out):
|
||||
@ -41,15 +42,14 @@ def main(args, prof):
|
||||
|
||||
gnm_str = args.flame.read()
|
||||
if '<' in gnm_str[:10]:
|
||||
flames = genome.XMLGenomeParser.parse(gnm_str)
|
||||
flames = convert.XMLGenomeParser.parse(gnm_str)
|
||||
if len(flames) != 1:
|
||||
warnings.warn('%d flames in file, only using one.' % len(flames))
|
||||
gnm = genome.convert_flame(flames[0])
|
||||
gnm = convert.convert_flame(flames[0])
|
||||
else:
|
||||
gnm = json.loads(gnm_str)
|
||||
gnm = genome.Genome(gnm)
|
||||
err, times = gnm.set_profile(prof)
|
||||
|
||||
gprof, times = use.wrap_genome(prof, gnm)
|
||||
rmgr = render.RenderManager()
|
||||
|
||||
basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_'
|
||||
@ -71,7 +71,7 @@ def main(args, prof):
|
||||
if not os.path.isfile(f[0]) or m > os.path.getmtime(f[0]))
|
||||
|
||||
w, h = prof['width'], prof['height']
|
||||
gen = rmgr.render(gnm, frames, w, h)
|
||||
gen = rmgr.render(gnm, gprof, frames)
|
||||
|
||||
if not args.gfx:
|
||||
for out in gen:
|
||||
|
Loading…
Reference in New Issue
Block a user