Finally, a working main.py again.

This commit is contained in:
Steven Robertson 2011-12-17 21:25:15 -05:00
parent 303accad16
commit 00890dd0d1

275
main.py
View File

@ -1,9 +1,10 @@
#!/usr/bin/python #!/usr/bin/python
# #
# flam3cuda, one of a surprisingly large number of ports of the fractal flame # cuburn, one of a surprisingly large number of ports of the fractal flame
# algorithm to NVIDIA GPUs. # algorithm to NVIDIA GPUs.
# #
# This one is copyright 2010 Steven Robertson <steven@strobe.cc> # This one is copyright 2010-2011, Steven Robertson <steven@strobe.cc>
# and Eric Reckase <e.reckase@gmail.com>.
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later # it under the terms of the GNU General Public License version 2 or later
@ -12,6 +13,8 @@
import os import os
import sys import sys
import time import time
import json
import warnings
import argparse import argparse
import multiprocessing import multiprocessing
from subprocess import Popen from subprocess import Popen
@ -21,137 +24,57 @@ from itertools import ifilter
import numpy as np import numpy as np
import Image import Image
import scipy import scipy
import pycuda.autoinit import pycuda.driver as cuda
import cuburn._pyflam3_hacks from cuburn import genome, render
from fr0stlib import pyflam3
from cuburn import render
from cuburn.code.mwc import MWCTest
np.set_printoptions(precision=5, edgeitems=20) 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),
'preview': dict(fps=24, width=640, height=360, quality=800, skip=1)
}
real_stdout = sys.stdout def save(rframe):
noalpha = rframe.buf[:,:,:3]
def fmt_time(time):
# Format time in a lexically-ordered way that doesn't interfere with the
# typical case of ascending natural numbers
atime = abs(time)
dcml = ('-' if time < 0 else '') + ('%05d' % np.floor(atime))
frac = np.round((atime - np.floor(atime)) * 1000)
if frac:
return '%s_%03d' % (dcml, frac)
return dcml
def fmt_filename(args, time):
return os.path.join(args.dir, '%s_%s' % (args.name, fmt_time(time)))
def save(args, time, raw):
noalpha = raw[:,:,:3]
if args.raw:
real_stdout.write(buffer(np.uint8(noalpha * 255.0)))
sys.stderr.write('.')
return
name = fmt_filename(args, time)
img = scipy.misc.toimage(noalpha, cmin=0, cmax=1) img = scipy.misc.toimage(noalpha, cmin=0, cmax=1)
img.save(name+'.png') img.save(rframe.idx, quality=95)
print rframe.idx, rframe.gpu_time
if args.jpg is not None: def main(args, prof):
img.save(name+'.jpg', quality=args.jpg) import pycuda.autoinit
print 'saved', name
def error(msg): gnm_str = args.flame.read()
print "Error:", msg if '<' in gnm_str[:10]:
sys.exit(1) flames = genome.XMLGenomeParser.parse(gnm_str)
if len(flames) != 1:
def main(args): warnings.warn('%d flames in file, only using one.' % len(flames))
if args.test: gnm = genome.convert_flame(flames[0])
MWCTest.test_mwc()
return
if args.raw:
sys.stdout = sys.stderr
genome_ptr, ngenomes = pyflam3.Genome.from_string(args.flame.read())
genomes = cast(genome_ptr, POINTER(pyflam3.Genome*ngenomes)).contents
if args.qs:
for g in genomes:
g.sample_density *= args.qs
g.ntemporal_samples = max(1, int(g.ntemporal_samples * args.qs))
if args.width:
if not args.scale:
args.scale = float(args.width) / genomes[0].width
for g in genomes:
g.width = args.width
elif not np.all([g.width == genomes[0].width for g in genomes]):
error("Inconsistent width. Force with --width.")
if args.height:
for g in genomes:
g.height = args.height
elif not np.all([g.height == genomes[0].height for g in genomes]):
error("Inconsistent height. Force with --height.")
if args.scale:
for g in genomes:
g.pixels_per_unit *= args.scale
if args.skip and not args.tempscale:
args.tempscale = float(args.skip)
if args.tempscale:
for g in genomes:
g.temporal_filter_width *= args.tempscale
if not args.name:
if args.flame.name == '<stdin>':
args.name = genomes[0].name
else:
args.name = os.path.splitext(os.path.basename(args.flame.name))[0]
cp_times = [cp.time for cp in genomes]
if args.renumber is not None:
for t, g in enumerate(genomes):
g.time = t - args.renumber
cp_times = [cp.time for cp in genomes]
elif np.any(np.diff(cp_times) <= 0):
error("Genome times are non-monotonic. Try using --renumber.")
if len(cp_times) > 2:
if args.start is None:
# [1], not [0]; want to be inclusive on --start but exclude
# the first genome when --start is not passed
args.start = cp_times[1]
if args.end is None:
args.end = cp_times[-1]
if args.skip:
times = np.arange(args.start, args.end, args.skip)
else:
times = [t for t in cp_times if args.start <= t < args.end]
else: else:
times = cp_times gnm = json.loads(gnm_str)
gnm = genome.Genome(gnm)
err, times = gnm.set_profile(prof)
anim = render.Renderer()
anim.compile(gnm, keep=args.keep)
anim.load(gnm)
basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_'
if args.flame.name == '-':
basename = ''
prefix = os.path.join(args.dir, args.name or basename)
frames = [(prefix + '%05d.jpg' % (i+1), t) for i, t in enumerate(times)]
if args.end:
frames = frames[:args.end]
frames = frames[args.start::prof['skip']+1]
if args.resume: if args.resume:
times = [t for t in times frames = ifilter(lambda r: not os.path.isfile(r[0]), frames)
if not os.path.isfile(fmt_filename(args, t)+'.png')] w, h = prof['width'], prof['height']
gen = anim.render(gnm, frames, w, h)
if times == []:
print 'No genomes to be rendered.'
return
anim = render.Animation(genomes)
if args.debug:
anim.cmp_options.append('-G')
anim.keep = args.keep or args.debug
anim.compile()
anim.load()
if args.gfx: if args.gfx:
import pyglet import pyglet
window = pyglet.window.Window(anim.features.width, anim.features.height) window = pyglet.window.Window(w, h)
image = pyglet.image.CheckerImagePattern().create_image( image = pyglet.image.CheckerImagePattern().create_image(w, h)
anim.features.width, anim.features.height)
label = pyglet.text.Label('Rendering first frame', x=5, y=5, label = pyglet.text.Label('Rendering first frame', x=5, y=5,
font_size=24, bold=True) font_size=24, bold=True)
@ -172,9 +95,8 @@ def main(args):
last_time = [time.time()] last_time = [time.time()]
frames = anim.render_frames(times, sync=args.sync)
def poll(dt): def poll(dt):
out = next(frames, False) out = next(gen, False)
if out is False: if out is False:
if args.nopause: if args.nopause:
pyglet.app.exit() pyglet.app.exit()
@ -184,25 +106,23 @@ def main(args):
elif out is not None: elif out is not None:
real_dt = time.time() - last_time[0] real_dt = time.time() - last_time[0]
last_time[0] = time.time() last_time[0] = time.time()
ftime, buf = out save(out)
save(args, ftime, buf) imgbuf = np.uint8(out.buf.flatten() * 255)
imgbuf = np.uint8(buf.flatten() * 255) image.set_data('RGBA', -w*4, imgbuf.tostring())
image.set_data('RGBA', -anim.features.width*4, imgbuf.tostring()) label.text = '%s (%g fps)' % (out.idx, 1./real_dt)
label.text = '%s %4g (%g fps)' % (args.name, ftime, 1./real_dt)
else: else:
label.text += '.' label.text += '.'
if args.sleep: if args.sync:
time.sleep(args.sleep / 1000.) cuda.Context.synchronize()
pyglet.clock.set_fps_limit(30) pyglet.clock.set_fps_limit(30)
pyglet.clock.schedule_interval(poll, 1/30.) pyglet.clock.schedule_interval(poll, 1/30.)
pyglet.app.run() pyglet.app.run()
else: else:
for ftime, out in ifilter(None, anim.render_frames(times, sync=args.sync)): for out in gen:
save(args, ftime, out) save(out)
if args.sleep: if args.sync:
time.sleep(args.sleep / 1000.) cuda.Context.synchronize()
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Render fractal flames.') parser = argparse.ArgumentParser(description='Render fractal flames.')
@ -211,68 +131,45 @@ if __name__ == "__main__":
help="Path to genome file ('-' for stdin)") help="Path to genome file ('-' for stdin)")
parser.add_argument('-g', action='store_true', dest='gfx', parser.add_argument('-g', action='store_true', dest='gfx',
help="Show output in OpenGL window") help="Show output in OpenGL window")
parser.add_argument('-j', metavar='QUALITY', nargs='?',
action='store', type=int, dest='jpg', const=90,
help="Write .jpg in addition to .png (default quality 90)")
parser.add_argument('-n', metavar='NAME', type=str, dest='name', parser.add_argument('-n', metavar='NAME', type=str, dest='name',
help="Prefix to use when saving files (default is basename of input)") help="Prefix to use when saving files (default is basename of input)")
parser.add_argument('-o', metavar='DIR', type=str, dest='dir', parser.add_argument('-o', metavar='DIR', type=str, dest='dir',
help="Output directory", default='.') help="Output directory", default='.')
parser.add_argument('--resume', action='store_true', dest='resume', parser.add_argument('--resume', action='store_true', dest='resume',
help="Do not render any frame for which a .png already exists.") help="Do not render any frame for which a .jpg already exists.")
parser.add_argument('--raw', action='store_true', dest='raw',
help="Do not write files; instead, send raw RGBA data to stdout.")
parser.add_argument('--nopause', action='store_true', parser.add_argument('--nopause', action='store_true',
help="Don't pause after rendering when preview is up") help="Don't pause after rendering the last frame when previewing")
seq = parser.add_argument_group('Sequence options', description=""" parser.add_argument('--keep', action='store_true', dest='keep',
Control which frames are rendered from a genome sequence. If '-k' is
not given, '-s' and '-e' act as limits, and any control point with a
time in bounds is rendered at its central time. If '-k' is given,
a list of times to render is given according to the semantics of
Python's range operator, as in range(start, end, skip).
If no options are given, all control points except the first and last
are rendered. If only one or two control points are passed, everything
gets rendered.""")
seq.add_argument('-s', dest='start', metavar='TIME', type=float,
help="Start time of image sequence (inclusive)")
seq.add_argument('-e', dest='end', metavar='TIME', type=float,
help="End time of image sequence (exclusive)")
seq.add_argument('-k', dest='skip', metavar='TIME', type=float,
help="Skip time between frames in image sequence. Auto-sets "
"--tempscale, use '--tempscale 1' to override.")
seq.add_argument('--renumber', metavar="TIME", type=float,
dest='renumber', nargs='?', const=0,
help="Renumber frame times, counting up from the supplied start time "
"(default is 0).")
genome = parser.add_argument_group('Genome options')
genome.add_argument('--qs', type=float, metavar='SCALE',
help="Scale quality and number of temporal samples")
genome.add_argument('--scale', type=float, metavar='SCALE',
help="Scale pixels per unit (camera zoom)")
genome.add_argument('--tempscale', type=float, metavar='SCALE',
help="Scale temporal filter width")
genome.add_argument('--width', type=int, metavar='PIXELS',
help="Use this width. Auto-sets scale, use '--scale 1' to override.")
genome.add_argument('--height', type=int, metavar='PIXELS',
help="Use this height (does *not* auto-set scale)")
debug = parser.add_argument_group('Debug options')
debug.add_argument('--test', action='store_true', dest='test',
help='Run some internal tests')
debug.add_argument('--keep', action='store_true', dest='keep',
help='Keep compilation directory (disables kernel caching)') help='Keep compilation directory (disables kernel caching)')
debug.add_argument('--debug', action='store_true', dest='debug', parser.add_argument('--sync', action='store_true', dest='sync',
help='Compile kernel with debugging enabled (implies --keep)')
debug.add_argument('--sync', action='store_true', dest='sync',
help='Use synchronous launches whenever possible') help='Use synchronous launches whenever possible')
debug.add_argument('--sleep', metavar='MSEC', type=int, dest='sleep',
nargs='?', const='5', parser.add_argument('--duration', type=float, metavar='TIME',
help='Sleep between invocations. Keeps a single-card system ' help="Set base duration in seconds (30)", default=30)
'usable. Implies --sync.') parser.add_argument('--start', metavar='FRAME_NO', type=int,
default=0, help="First frame to render (inclusive)")
parser.add_argument('--end', metavar='FRAME_NO', type=int,
help="Last frame to render (exclusive, negative OK)")
prof = parser.add_argument_group('Profile options')
prof.add_argument('-p', dest='prof', choices=profiles.keys(),
default='preview', help='Set profile, specifying defaults for all '
'options below. (default: "preview")')
prof.add_argument('--skip', dest='skip', metavar='N', type=int,
help="Skip N frames between each rendered frame")
prof.add_argument('--quality', type=int, metavar='SPP',
help="Set base samples per pixel")
prof.add_argument('--fps', type=float, dest='fps',
help="Set frames per second")
prof.add_argument('--width', type=int, metavar='PX')
prof.add_argument('--height', type=int, metavar='PX')
args = parser.parse_args() args = parser.parse_args()
prof = dict(profiles[args.prof])
for k in prof:
if getattr(args, k) is not None:
prof[k] = getattr(args, k)
prof['duration'] = args.duration
main(args) main(args, prof)