Seamless fireworks demo

Example demonstrating simulation of OpenGL-rendered fireworks. Adapted from the Vispy gallery.

See the seamless examples directory:

  • examples/fireworks/fireworks.py: script that generates the fireworks.seamless context file, used in this notebook
  • examples/fireworks/tutorial/steps.ipy: step-by-step tutorial to set up OpenGL fireworks

The seamless YouTube playlist contains the following videos:

  • Demo video of this notebook
  • Step-by-step tutorial (three parts)

In [ ]:
#  Download fireworks example context
import urllib.request
url = "https://raw.githubusercontent.com/sjdv1982/seamless/master/examples/fireworks/fireworks.seamless"
urllib.request.urlretrieve(url, filename = "fireworks.seamless")
url = "https://raw.githubusercontent.com/sjdv1982/seamless/master/examples/fireworks/orca.png"
urllib.request.urlretrieve(url, filename = "orca.png")

In [ ]:
#  Boilerplate
import seamless
from seamless import cell, pythoncell, context, reactor, transformer
from seamless.lib.gui.basic_editor import edit
from seamless.lib.gui.basic_display import display
from seamless.lib import link

In [ ]:
ctx = seamless.fromfile("fireworks.seamless")

In [ ]:
#  Piece of code to link a seamless cell and an ipywidget

import traitlets
from collections import namedtuple
import traceback

def widgetlink(c, w):
    assert isinstance(c, seamless.core.Cell)
    assert isinstance(w, traitlets.HasTraits)
    assert w.has_trait("value")
    handler = lambda d: c.set(d["new"])
    value = c.value
    if value is not None:
        w.value = value
    else:
        c.set(w.value)
    def set_traitlet(value):
        try:
            w.value = value
        except:
            traceback.print_exc()
    w.observe(handler, names=["value"])
    obs = seamless.observer(c, set_traitlet )
    result = namedtuple('Widgetlink', ["unobserve"])
    def unobserve():
        nonlocal obs
        t[0].unobserve(handler)
        del obs  
    result.unobserve = unobserve
    return result

In [ ]:
# Build ipywidgets and link them to seamless cells

# Clean up any old widgetlinks, created by repeated execution of this cell
try:
    for w in widgetlinks:
        widget.unobserve()
except NameError:
    pass

from ipywidgets import Layout, Box, FloatSlider, IntSlider, Checkbox, Label

layout = Layout(
    display='flex',
    flex_flow='row',
    justify_content='space-between',
)

from ipywidgets import FloatSlider, IntSlider, Checkbox, HBox
from collections import OrderedDict

# Build widgets
widgets = OrderedDict()
widgets["N"] = IntSlider(min=1, max=20000, description = "N (number of points)", layout=layout)
widgets["gravity"] = FloatSlider(min=0, max=5, description = "Gravity", layout=layout)
widgets["pointsize"] = IntSlider(min=1, max=100, description = "Pointsize", layout=layout)
widgets["period"] = FloatSlider(min=0.01, max=5, description = "Period (time between explosions)", layout=layout)
widgets["shrink_with_age"] = Checkbox(description = "Shrink points with age", layout=layout)

widgetlinks = [] # You need to hang on to the object returned by traitlink
form_items = []
for k,w in widgets.items():
    c = getattr(ctx, k)
    widgetlinks.append(widgetlink(c, w))
    #  Replace the description with a label-widget pair
    row = Box([Label(value=w.description), w], layout=layout)
    form_items.append(row)
    w.description = ""

form = Box(form_items, layout=Layout(
    display='flex',
    flex_flow='column',
    border='solid 2px',
    align_items='stretch',
    width='70%'
))
form

In [ ]:
#  Replace the randomly generated texture with an image
ctx.tex_filename.set("orca.png")
ctx.tex_radius.set(100)

In [ ]:
# Define a mask, drawing a text onto it
import numpy as np
from PyQt5.QtGui import QImage, QFont, QColor
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
imsize = 1000
img = QImage(imsize, imsize, QImage.Format_Grayscale8)
img.fill(Qt.white)

text = "Hello world!"

qp = QPainter()
try:
    qp.begin(img)
    qp.setPen(Qt.black)
    font = QFont("Arial", 100)
    qp.setFont(font)
    mx = img.width()
    my = img.height()
    qp.drawText(0, 0, mx, my, Qt.AlignCenter,text)
finally:
    qp.end()
mask = np.array(img.bits().asarray(img.byteCount())).reshape(img.width(),img.height())
ctx.mask = cell("array").set(mask)

In [ ]:
#  Connect mask to pin
pin = ctx.display_texture.display_numpy.array
pin.cell().disconnect(pin)
ctx.mask.connect(pin)

In [ ]:
#  Connect texture to pin (undoing the last cell)
pin = ctx.display_texture.display_numpy.array
pin.cell().disconnect(pin)
ctx.texture.connect(pin)

Change the fireworks into exploding letters (step 1)


In [ ]:
params = ctx.params_gen_vertexdata.value
params["mask"] = {"pin": "input", "dtype": "array"}
ctx.params_gen_vertexdata.set(params)
ctx.mask.connect(ctx.gen_vertexdata.mask)

Change the fireworks into exploding letters (step 2)

  • In the GUI above, set the point size to 5
  • In cell-gen-vertexdata.py, line 8, change 0.7 to 0.05
  • Finally, replace line 7 with the following code:

rotmask = np.rot90(mask, 3) #in (x,y) form start_values0 = np.random.random((1000000, 3)) p = (start_values0*len(mask)).astype(np.int)[:,:2] mask_values = rotmask[p[:,0], p[:,1]] start_values0 = start_values0[mask_values==0] start_values = 2*start_values0[:N]-1

To undo the exploding letters, change back cell-gen-vertexdata.py, and then:


In [ ]:
params = ctx.params_gen_vertexdata.value
params.pop("mask", None)
ctx.params_gen_vertexdata.set(params)