Ceed functions

A Ceed function is how a stage attaches a time-varying intensity to its shapes.

The stage calls the function or sequence of functions for every video frame with the current global time and the function returns a scalar value between 0-1, inclusive. This intensity is used for that stage’s shapes for that frame.

See the function API for in-depth function details.

Creating functions

Ceed comes with pre-defined functions, but a plugin can add to the available functions. Additionally, you can customize functions in the function pane to make it available for re-use in other function groups or stages.

This video shows multiple ways to create a customized global cosine function from existing cosine functions.

Customizing functions

Each function has parameters that describe its output as a function of time. All functions have these basic parameters:

  1. Duration (duration) - the time domain where it’s valid.. See also Function timebase.

  2. Loop (loop) - the number of times to loop. The function restarts as many times over its domain until done.

  3. TB num/denom (timebase) see Function timebase.

  4. Offset (t_offset) - an offset to the time parameter. E.g. if the function is y = m * t + b, specifying a non-zero value for offset will compute y = m * (t + t_offset) + b.

Function groups

Functions groups enable iterating through a sequence of functions as if it was a single function whose duration is the sum of its children functions, recursively.

Initially, when adding or dragging a global function into a group, only a reference to the function is added. So changing the original function will change the reference function as well, and the original function cannot be deleted. A reference function is indicated with the T-junction icon in the video. Clicking it will break the reference and convert it to a independent function.

Ceed guards from adding a group function to itself or its children, to prevent infinite recursion as shown at the end of video.

Randomizing functions

Function parameters can be randomized so it is resampled before each experiment. As seen in the video, there are multiple distributions to choose from and more can be added by plugins.

Looping

When a function or stage is looped, randomized parameters can be resampled once and the value used for all loop iterations, or it can be resampled once for each loop iteration.

In Ceed, the “Resample each loop” (sample_each_loop) option controls this behavior. For example, this protocol contains a function of constant intensity of 5 sec duration. Additionally, the function loops twice and the stage loops 3 times for a total of 6 loops. The intensity parameter is randomized from a Gaussian and is not resampled for each loop. So its graph is constant.

../_images/function_random.png

Ceed config

This protocol however does resample each loop iteration, which is reflected in its graph.

../_images/function_random_per_loop.png

Ceed config

Lock after forking

Before an experiment, functions with random parameters are re-sampled and then copied; that’s so the original prototype functions are not directly used by an experiment. After the copy, Ceed again re-samples the functions because of the following.

As shown above, Ceed supports reference functions that can be re-used in multiple places, allowing them to share parameters. These references are individually cloned from the prototype at the copying step into unique functions. The randomized parameters of the cloned functions support two options with regards to re-sampling, controlled by “Lock after fork” (lock_after_forked).

If False, Ceed will resample the parameter again after cloning. This ensures that each cloned copy will not share the same random values as the original and sibling functions. If True, the cloned functions will not be re-sampled so they will share the original re-sampled parameters.

The following protocol contains 3 functions; a reference to a constant function that loops 5 times and its intensity is randomized, it’s followed by a short cosine, that is followed again by a reference to the same constant function.

Here we set “Lock after fork” to False. You can see how both references to the constant function have different values.

../_images/function_random_fork.png

Ceed config

Here we set “Lock after fork” to True. You can see how both reference functions repeat the same pattern because they share the same random values for each loop iteration.

../_images/function_random_fork_lock.png

Ceed config

Function timebase

Functions are sampled by the stage at integer multiples of the video frame rate period. As function duration approaches the period, specifying duration using time in decimal leads to inaccuracy and rounding. Functions therefore support specifying duration as integer multiple of video frames. E.g. it can be set to be exactly one frame long.

When the function timebase is specified and non-zero, the duration is multiplied by the timebase to get the duration in seconds. So when the timebase is exactly one over the frame rate - the period, a duration of one means one frame.

E.g. in the video, the frame rate is 59.94, or 2997 / 50. Setting the timebase fraction to 50 / 2997 will allow use to set the duration of each constant function in the group to one frame. Then, setting the intensity (a) of the first child function to zero, the second one to one, and the group to loop over them 500 times will create a function that alternates between zero and one intensity for each frame.

The stages and preview graph are explained in the stage guide.

Ceed config of the video

Children of function groups (recursively) inherit their timebase from their parents, if it’s not explicitly overwritten by the child.

Create function in script

Functions and stages/shapes can be created in a script, saved to a yaml file, and then imported from the GUI ready to be used in an experiment. See the CeedDataWriterBase.save_config_to_yaml method for an example.

Function plugin

Ceed comes with a few functions, however using plugins Ceed can support any custom function.

It is fully explained in Function plugins, but you can make your plugin available to Ceed by copying your Python file to the ceed/function/plugin directory under where Ceed is installed, or register your external plugin package using external_function_plugin_package.

Ceed gets your function classes using a get_ceed_functions function in the plugin and similarly it uses get_ceed_distributions to get the probability distribution classes.

To write a plugin, it helps to become familiar with the function API and the FuncBase class that all functions inherit from.

A very simple function plugin file that computes 1 / (max(t, 1) ^ 2), where t is the elapsed time is:

from kivy.properties import NumericProperty
from ceed.function import CeedFunc


class DecayFunc(CeedFunc):

    # The parameter must be a Kivy property, so the GUI can update from it
    c = NumericProperty(1.)

    def __init__(self, **kwargs):
        self.name = 'Decay'
        self.description = 'y(t) = m / (t + t_offset) ^ 2'
        super().__init__(**kwargs)

    def __call__(self, t):
        # super handles checking whether function is finished
        super().__call__(t)
        t = self.get_relative_time(t)
        return self.c / max(t, 1) ** 2

    def get_gui_props(self):
        # makes sure c is editable from the GUI
        d = super().get_gui_props()
        d['c'] = None
        return d

    def get_state(self, *largs, **kwargs):
        # makes sure c is included in config
        d = super().get_state(*largs, **kwargs)
        d['c'] = self.c
        return d

    def get_noise_supported_parameters(self):
        # enables c to be randomized
        val = super().get_noise_supported_parameters()
        val.add('c')
        return val


def get_ceed_functions(function_factory):
    return [DecayFunc]

Simply copy the above code into your python file and place it in your external package or in Ceed’s plugin directory, e.g. ceed/function/plugin/my_plugin.py and DecayFunc will be listed in the GUI.

Similarly, you can add a simple random distribution that returns one of two numbers and make it available to randomize parameters, by copying the following code into your function plugin file:

from random import random
from kivy.properties import NumericProperty
from ceed.function.param_noise import NoiseBase


class BinaryNoise(NoiseBase):

    # The parameters must be a Kivy property, so the GUI can update from it
    # The type of the default value (0.0 - float here) is the type the user
    # can enter in the GUI. E.g. if it was an int, only ints could be entered
    a = NumericProperty(0.)

    b = NumericProperty(0.)

    def sample(self):
        return self.a if random() < .5 else self.b

    def get_config(self):
        # makes sure c is included in config and editable from the GUI
        config = super().get_config()
        for attr in ('a', 'b'):
            config[attr] = getattr(self, attr)
        return config

    def get_prop_pretty_name(self):
        # the user friendly name to display in the GUI
        names = super().get_prop_pretty_name()
        names['a'] = 'First value'
        names['b'] = 'Second value'
        return names


def get_ceed_distributions(function_factory):
    return [BinaryNoise]