Stage

A CeedStage combines ceed.shapes on the screen with a FuncBase or sequence of FuncBase which determines the intensity of the shapes as time progresses in the experiment. This module defines the CeedStage and associated classes used to store and compute the intensity values during the experiment.

Stage factory and plugins

The StageFactoryBase is a store of the defined CeedStage sub-classes and customized stage instances. Stage classes/instances registered with the StageFactoryBase instance used by the the GUI are available to the user in the GUI. During analysis, stages are similarly registered with the StageFactoryBase instance used in the analysis and can then be used to get these stages. E.g.:

>>> # get a function and shape factory
>>> function_factory = FunctionFactoryBase(...)
>>> shape_factory = CeedPaintCanvasBehavior(...)
>>> # create the stage store, linking to the other factories
>>> stage_factory = StageFactoryBase(
        function_factory=function_factory, shape_factory=shape_factory)
>>> register_all_stages(stage_factory)  # register plugins
>>> StageCls = stage_factory.get('CeedStage')  # get the class
>>> StageCls
<class 'ceed.stage.CeedStage'>

Classes can be registered manually with StageFactoryBase.register() or they can be registered automatically with register_all_stages() if they are an internal plugin or register_external_stages() for an external plugin. The GUI calls register_all_stages() when started as well as register_external_stages() if the external_stage_plugin_package configuration variable contains a package name.

See ceed.stage.plugin for details on writing plugins. For details on how to customize stages, see below for important steps and methods during a stage’s lifecycle.

To get a stage class registered with StageFactoryBase, e.g. the connonical CeedStage base class:

>>> StageCls = stage_factory.get('CeedStage')  # get the class
>>> StageCls
<class 'ceed.stage.CeedStage'>

Stage basics

All stages are instances of CeedStage or any plugin-defined sub-classes.

A stage is composed of one or more CeedStage.shapes, a series of ceed.function.FuncBase functions that govern the intensity these shapes take across time, and sub-CeedStage.stages, that are simultanously evaluated during this stage.

All stages have a CeedStage.name, and stages that are available globally through the StageFactoryBase and shown as root stages in the GUI have unique names. To run an experiment you select a named stage to run.

Stage duration

Ignoring sub-stages, a stage will sample trhough its CeedStage.functions sequentally until they are all done, CeedStage.loop times. After CeedStage.loop times, the stage is done. For example, a stage with function f1 and f2 that CeedStage.loop`s 3 times, will tick through the functions as follows: ``f1, f2, f1, f2, f1, f2`. At each sample, all the CeedStage.shapes of the stage (and possibly sub-stages) are set to the value in the [0, 1] range returned by the functions for that time step.

If there are child CeedStage.stages that have their own CeedStage.shapes, we also sample these stages simultanously during each root stage loop iteration. That means that while shapes associated with the root stage are updated from the root stage’s functions, the shapes of the sub-stages currently active are updated from their functions.

E.g. if we have a root stage which contains 4 children CeedStage.stages A, B, C, D:

root
    A
    B
    C
    D

If root’s CeedStage.order is 'serial', then for each root CeedStage.loop iteration, each sub-stage A - D is evaluated sequentially after the previous sub-stage has finished in the order A -> B -> C -> D. If it’s 'parallel', then each sub-stage is evaluated simultaneously rather than sequentially.

While the sub-stages are executed in parallel or serially, the root’s CeedStage.functions are evaluated and the root’s shapes set to those values.

If CeedStage.complete_on is 'any' then a loop iteration for the root stage will be considered completed after all the root’s CeedStage.functions are done and any of the CeedStage.stages have completed. The sub-stages that have not yet completed will just end early.

Otherwise, CeedStage.complete_on is 'all' and a loop iteration for the root will complete after all the root’s CeedStage.functions and all the CeedStage.stages have completed.

In all cases, shapes that are not associated with a stage that is currently being evaluated will be set to transparent. E.g. in the serial case, shapes that are associated with stage C will be transparent in all other stages, except if they also appear in those stages.

If a shape occurs in multiple stages (e.g. in both the root and A), then if the root and A set different color channels (e.g. root sets blue, A sets green), the shape will will set each channel from each active stage simultaneously because they don’t conflict. If they do set the same channel, the sub-stages win over parent stages in the stage tree.

Color channels

Each stage contains CeedStage.shapes that are all set to the value returned by the currently active function in CeedStage.functions for the current time step. That is the function yields a single floating point value between 0-1, inclusive and all the shapes get set to that value for that timestep.

However, there are 3 channels to be set; red, green, and blue. You can select which of these channels the stage sets using CeedStage.color_r, CeedStage.color_g, and CeedStage.color_b and those channels are set to the same function value. The remaining channels are not set by this stage and default to zero if no other parent or sub-stage sets them.

Shapes belonging to sub-stages are not controlled by parent stages, only by the direct stage(s) that contains them.

If the projector video_mode is set to "QUAD12X", then the function value is used for all three channels so it’s grayscale. E.g. even if only CeedStage.color_r and CeedStage.color_b are true, all three red, green, and blue channels will be set to the same value . If the channels are set to different values in parallel stages, the channels are first averaged.

Stage lifecycle

The ultimate use of a stage is to sample it for all timepoints that the stage is active. Before the stage is ready, however, it needs to be initialized. Folowing are the overall steps performed by StageFactoryBase.tick_stage() that initialize a stage before running it as an experiment.

Starting with a stage, first the stage is coped and all CeedStage.functions resampled using CeedStage.copy_and_resample(). Then the stage is renamed to last_experiment_stage_name. This displays the copied stage to be run in the GUI under the name last_experiment_stage_name.

Next, we call CeedStage.init_stage_tree() on the root stage. This calls init_func_tree() on all the stage’s CeedStage.functions and recursively calls CeedStage.init_stage_tree() on all the sub-CeedStage.stages as well as some initialization.

Next, using CeedStage.apply_pre_compute() it pre-computes all the stages and functions for which it is enabled. See below for details. Finally, the stage is sampled, a sample at a time using the CeedStage.tick_stage() generator. This generator either returns the pre-comuted values, computed before the experiment started if enabled, or it computes the sample values and yields them until the root stage is done.

When the stage is done, it internally raises a StageDoneException. This gets caught and Ceed knows that the stage is done. At the end of each loop iteration and at the end of the overall stage it calls CeedStage.finalize_loop_iteration(), and CeedStage.finalize_stage(), respectively.

Customizing stages

A CeedStage has multiple methods that can be overwritten by a plugin. Following are some relevant methods - see their docs for more details.

In all cases, CeedStage.get_state() may also need to be augmented to return any parameters that are part of the instance, otherwise they won’t be copied when the stage is internally copied and the stage will use incorrect values when run e.g. in the second process that runs the experiment.

If stages are pre-computed, it may not have any side-effects, because they would occur before the experiment during pre-computing. So if these side-effects are needed (e.g. drug delivery), either turn off pre-computing for the stage or set its function’s duration to negative to disable it for the function and consequently the stage containing it.

Running a stage

Once we have a stage, shapes, and a function, the stage is ready to be run with StageFactoryBase.tick_stage()

The following is a worked through example showing the steps the Ceed GUI goes through automatically to run a stage and what you should do to manually run a stage for testing purposes.

First create the stage, shape, and function:

from ceed.stage import CeedStage, StageFactoryBase, register_all_stages, \
    StageDoneException
from ceed.function import FunctionFactoryBase, register_all_functions
from ceed.shape import CeedPaintCanvasBehavior
from fractions import Fraction

# create function/shape/stage factories to house functions/shapes/stages
function_factory = FunctionFactoryBase()
# register built-in plugins
register_all_functions(function_factory)
shape_factory = CeedPaintCanvasBehavior()
stage_factory = StageFactoryBase(
    function_factory=function_factory, shape_factory=shape_factory)
# register built-in plugins
register_all_stages(stage_factory)

# create a 3 second duration function, that loops twice
LinearFuncCls = function_factory.get('LinearFunc')
function = LinearFuncCls(
    function_factory=function_factory, m=.33, b=0, duration=3, loop=2)
print(f'Function name: "{function.name}"')
# and a circle shape
circle = shape_factory.create_shape('circle', center=(100, 100))
# and add it
shape_factory.add_shape(circle)
print(f'Shape name: "{circle.name}"')
# and finally the stage
stage = CeedStage(
    stage_factory=stage_factory, function_factory=function_factory,
    shape_factory=shape_factory)
# and add it to factory
stage_factory.add_stage(stage)
print(f'Stage name: "{stage.name}"')

# now add the function and shape to stage
stage.add_func(function)
stage.add_shape(circle)
# stage will only set red and blue channels
stage.color_r = stage.color_b = True
stage.color_g = False

Once ready, we can run through the stage manually like Ceed does during an experiment:

# now create the generator that will iterate through all the stage shape
# values at a frame rate of 2Hz
tick = stage_factory.tick_stage(
    t_start=0, frame_rate=Fraction(2, 1), stage_name=stage.name)
# start it
next(tick)
# start with time zero, the same as t_start
i = 0

while True:
    # send the next global time value as multiples of the period (1 / 2)
    t = Fraction(i, 2)
    try:
        # this gets the raw intensity values
        shapes_intensity_raw = tick.send(t)
    except StageDoneException:
        # when the stage is done, it raises this exception
        break

    # convert raw intensity to final color intensity. This function can
    # also e.g. convert the colors for quad mode where it's gray-scale
    shapes_intensity = stage_factory.set_shape_gl_color_values(
        shape_views=None, shape_values=shapes_intensity_raw)
    print(f'time={t}s,      intensity="{shapes_intensity}"')

    i += 1

The above will print the following when run:

Function name: "Linear"
Shape name: "Shape"
Stage name: "Stage-2"
time=0s,    intensity="[('Shape', 0, 0, 0, 1)]"
time=1/2s,  intensity="[('Shape', 0.165, 0, 0.165, 1)]"
time=1s,    intensity="[('Shape', 0.33, 0, 0.33, 1)]"
time=3/2s,  intensity="[('Shape', 0.495, 0, 0.495, 1)]"
time=2s,    intensity="[('Shape', 0.66, 0, 0.66, 1)]"
time=5/2s,  intensity="[('Shape', 0.825, 0, 0.825, 1)]"
time=3s,    intensity="[('Shape', 0, 0, 0, 1)]"
time=7/2s,  intensity="[('Shape', 0.165, 0, 0.165, 1)]"
time=4s,    intensity="[('Shape', 0.33, 0, 0.33, 1)]"
time=9/2s,  intensity="[('Shape', 0.495, 0, 0.495, 1)]"
time=5s,    intensity="[('Shape', 0.66, 0, 0.66, 1)]"
time=11/2s, intensity="[('Shape', 0.825, 0, 0.825, 1)]"

Some important points to notice above: The global clock is run with multiples of the period. This period is the projector frame rate, and when ticking we must only increment the time by single period increments. This is required for stage pre-computing to work because once pre-computed, we have a list of intensity values with the expectation that each value corresponds to single period increment because that’s how they are pre-computed.

To skip a frame, you must still tick that frame time, but you can discard the yielded value. This is how Ceed drops frames when the GPU takes too long to render a frame and we must compensate by dropping a frame.

One can also use the stage factory to compute all the intensity values without having to iterate as follows. Once we have the stage ready:

from pprint import pprint
# now create the generator that will iterate through all the stage shape
# values at a frame rate of 2Hz
intensity = stage_factory.get_all_shape_values(
    frame_rate=Fraction(2, 1), stage_name=stage.name)
pprint(intensity)

This prints:

defaultdict(<class 'list'>,
            {'Shape': [(0, 0, 0, 1),
                       (0.165, 0, 0.165, 1),
                       (0.33, 0, 0.33, 1),
                       (0.495, 0, 0.495, 1),
                       (0.66, 0, 0.66, 1),
                       (0.825, 0, 0.825, 1),
                       (0, 0, 0, 1),
                       (0.165, 0, 0.165, 1),
                       (0.33, 0, 0.33, 1),
                       (0.495, 0, 0.495, 1),
                       (0.66, 0, 0.66, 1),
                       (0.825, 0, 0.825, 1)]})

Pre-computing

By default, during an experiment as the global clock ticks in multiple of the period, the root stage is given the current time and it compute the intensity values for all the shapes from its CeedStage.functions and sub-CeedStage.stages. If the intensity computation is slow, the CPU may miss updating the GPU with a new frame before the next frame deadline and consequently we will need to drop a frame.

Ceed can pre-compute the intensity values for all the shapes for all time points by virtually running through the whole experiment and recording the intensities yielded by the stage into a flat list. Then during the real experiment it simply looks up the intensity from the list by sequentially iterating through the list.

This works because the global time is sequential consecutive multiples of the period both during the virtual computation and during the replay, so we can simply count frames to locate the desired intensity.

Pre-computing can be enabled in the GUI through the pre_compute_stages property.

Not all stages can be pre-computed. A stage could have functions that are infinite in duration (i.e. negarive duration, e.g. when it’s waiting for a switch to go ON) or a stage can be manually opted out from pre-computing by setting CeedStage.disable_pre_compute to True. In that case, all other functions and stages that are not infinite and not opted would will still be pre-computed as much as possible.

See CeedStage.disable_pre_compute, CeedStage.runtime_functions, CeedStage.runtime_stage, and CeedStage.can_pre_compute for more details.

Saving and restoring stages

Functions can be saved as a data dictionary and later restored into a function instance. E.g.:

>>> function = LinearFunc(
...     function_factory=function_factory, m=.33, b=0, duration=3, loop=2)
>>> circle = shape_factory.create_shape('circle', center=(100, 100))
>>> shape_factory.add_shape(circle)
>>> stage = CeedStage(
...     stage_factory=stage_factory, function_factory=function_factory,
...     shape_factory=shape_factory)
>>> stage.add_func(function)
>>> stage.add_shape(circle)
>>> stage_factory.add_stage(stage)
>>> state = stage.get_state()
>>> state
{'cls': 'CeedStage',
 'color_b': True,
 'color_g': False,
 'color_r': False,
 'complete_on': 'all',
 'disable_pre_compute': False,
 'functions': [{'b': 0.0,
                'cls': 'LinearFunc',
                'duration': 3,
                'loop': 2,
                'm': 0.33,
                'name': 'Linear',
                'noisy_parameter_samples': {},
                'noisy_parameters': {},
                't_offset': 0.0,
                'timebase_denominator': 1,
                'timebase_numerator': 0}],
 'name': 'Stage-2',
 'order': 'serial',
 'shapes': [{'keep_dark': False, 'name': 'Shape'}],
 'stages': []}
>>> # this is how we create a stage from state
>>> new_stage = stage_factory.make_stage(state)
>>> new_stage
<ceed.stage.CeedStage: "Stage" children=(1, 0), at 0x22c2f3b7898>
>>> new_stage.get_state()
{'cls': 'CeedStage',
 'color_b': True,
 'color_g': False,
 'color_r': False,
 ...
 'name': 'Stage',
 'order': 'serial',
 'shapes': [{'keep_dark': False, 'name': 'Shape'}],
 'stages': []}

If you notice, name was not restored to the new stage. That’s because name is only restored if we pass clone=True as name is considered an internal property and not always user-customizable. Because we ensure each stage’s name in the GUI is unique. E.g. with clone:

>>> new_stage = stage_factory.make_stage(state, clone=True)
>>> new_stage.get_state()
{'cls': 'CeedStage',
 ...
 'name': 'Stage-2',
 'stages': []}

A fundumental part of Ceed is copying and reconstructing stage objects. E.g. this is required to recover functions from a template file, from old data, or even to be able to run the experiment because it is run from a second process. Consequently, anything required for the stage to be reconstructed must be returned by CeedStage.get_state().

Reference stages

Stages can contain other CeedStage.stages as children. Instead of copying stages around we want to be able to reference another stage and add that reference as a child of a stage. This is useful so that these sub-stages update when the original stage’s parameters are updated. This is mostly meant to be used from the GUI, although work fine otherwise.

CeedStageRef allows one to reference stages in such a manner. Instead of copying a stage, just get a reference to it with StageFactoryBase.get_stage_ref() and add it to another stage.

When removed and destroyed, such stage references must be explicitly released with StageFactoryBase.return_stage_ref(), otherwise the original stage cannot be deleted in the GUI.

Methods that accept stages (such as CeedStage.add_stage()) should also typically accept CeedStageRef stages.

CeedStageRef cannot be used directly during an experiment, unlike normal stages. Therefore, they or any stages that contain them must first copy them and expand them to refer to the orignal stages being referenced before using them, with CeedStage.copy_expand_ref() or CeedStageRef.copy_expand_ref().

E.g.

>>> stage = CeedStage(...)
>>> stage
<ceed.stage.CeedStage: "Stage-2" children=(1, 0), at 0x258b6de3c18>
>>> ref_stage = stage_factory.get_stage_ref(stage=stage)
>>> ref_stage
<ceed.stage.CeedStageRef object at 0x00000258B6DE8128>
>>> ref_stage.stage
<ceed.stage.CeedStage: "Stage-2" children=(1, 0), at 0x258b6de3c18>
>>> stage_factory.return_stage_ref(ref_stage)

Before an experiment using a stage is run, the stage and all its sub-stages and stage functions that are such references are expanded and copied.

Copying stages

Stages can be copied automatically using deepcopy or CeedStage.copy_expand_ref(). The former makes a full copy of all the stages, but any CeedStageRef stages will only copy the CeedStageRef, not the original stage being refered to. The latter, instead of copying the CeedStageRef, will replace any CeedStageRef with copies of the class it refers to.

Stages can be manually copied with CeedStage.get_state() and CeedStage.set_state() (although StageFactoryBase.make_stage() is more appropriate for end-user creation).

Create stage in script

A stage complete with functions and 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 save_config_to_yaml() for an example.

Custom plugin stage example

As explained above, plugins can register customized CeedStage sub-classes to be included in the GUI. Following is an example of how CeedStage.evaluate_stage() can be overwritten.

By default the CeedStage.evaluate_stage() generator cycles through CeedStage.loop times and for each loop iteration it ticks through all the stage’s CeedStage.functions, setting the stage’s shapes to their values in addition to ticking through the sub-stages simultaniously and then yielding.

CeedStage.evaluate_stage() can be safely overwritten to yield directly whatever values you wish ignoring any functions or sub-stages.

E.g. if you have a shape in the stage named "circle" (in the GUI this shape will have to be added to the stage) and you want its RGB value to to be (0.5, 0, 0) for 2 frames, (0, 0.6, 0) for 3 frames, and finally (0, 0.2, 0.1) for 4 frames for a total experiment duration of 9 frames you would write the following sub-class in the stage plugin:

class SlowStage(CeedStage):

    def evaluate_stage(self, shapes, last_end_t):
        # always get the first time
        self.t_start = t = yield
        for _ in range(2):
            # r, g, b, a values. a (alpha) should be None
            shapes['circle'].append((0.5, 0, 0, None))
            # this yields so GUI can use the change shape colors
            t = yield
        for _ in range(3):
            shapes['circle'].append((0, 0.6, 0, None))
            t = yield
        for _ in range(4):
            shapes['circle'].append((0, 0.2, 0.1, None))
            t = yield

        # this time value was not used and this effectively makes the
        # stage 9 samples long, and it ends on the last sample so
        # that last time will be used as start of next stage
        self.t_end = t
        # raising this exception is how we indicate we're done
        raise StageDoneException

The above class will behave correctly whether the stage is pre-computed or not because either way it’s called to get the values. See CeedStage.evaluate_stage() for further details.

To add stage settings to the GUI, see CeedStage.get_settings_display() and the CSV stage plugin implamentation.

Other methods could potentially also be overwritten to hook into the stage lifecycle, but they generally require more care. See all CeedStage methods and below for further details.

Custom graphics

Besides the shapes drawn in the Ceed GUI or script generated, stages could add arbitrary Kivy GL graphics to the experiment screen and update them during an experiment. This e.g. allows the display of a circle whose intensity falls off as it’s farther from the center of the circle.

CeedStage provides the following methods to add, update, and remove these graphics for an experiment: add_gl_to_canvas(), set_gl_colors(), and remove_gl_from_canvas().

Additionally, like GUI drawn shapes that automatically log the shape intensity for each frame to be accessible from shapes_intensity, stages can overwrite get_stage_shape_names() to add names and use those names to log arbitrary 4-byte (nominally RGBA for shapes) values for each frame. These values are also displayed in the graph preview window for all shapes in the GUI. However, you have to ensure to compute and log the rgba data during each tick.

See the example plugins in the examples directory.

TODO: if a function or stage has zero duration, any data events logged during

intitialization is not logged if pre-computing. Log these as well. Similarly, logs created after the last frame of a stage/function is not logged when pre-computing.

exception ceed.stage.StageDoneException

Bases: Exception

Raised as a signal by a CeedStage when it is done.

ceed.stage.StageType

The type-hint type for CeedStage.

alias of TypeVar(‘StageType’, bound=CeedStage)

ceed.stage.CeedStageOrRefInstance

Instance of either CeedStage or CeedStageRef.

alias of Union[CeedStage, CeedStageRef]

ceed.stage.last_experiment_stage_name = 'experiment_sampled'

The stage name used for the last experiment run. This name cannot be used by a stage and this stage is overwritten after each experiment run.

class ceed.stage.StageFactoryBase(function_factory, shape_factory, **kwargs)

Bases: kivy._event.EventDispatcher

A global store of the defined CeedStage classes and customized stage instances.

Stages in the factory are displayed to the user in the GUI. Similarly, when a user creates a custom stage in the GUI, it’s added here automatically.

See ceed.stage for details.

Events
on_changed:

Triggered whenever a stage is added or removed from the factory.

on_data_event:

The event is dispatched by stages whenever the wish to log something. During an experiment, this data is captured and recorded in the file.

To dispatch, you must pass the function’s ceed_id and a string indicating the event type. You can also pass arbitrary arguments that gets recorded as well. E.g. stage_factory.dispatch('on_data_event', stage.ceed_id, 'drug', .4, 'h2o').

See event_data for details on default events as well as data type and argument requirements.

stage_names: Dict[str, ceed.stage.CeedStage]

A dict of all the stages whose keys are the stage CeedStage.name and whose values are the corresponding CeedStage instances.

Contains stages added with add_stage() as well as those automatically created and added when register() is called on a class.

>>> stage_factory.stage_names
{'Stage': <ceed.stage.CeedStage at 0x1da866f00b8>}
shape_factory: ceed.shape.CeedPaintCanvasBehavior = None

The CeedPaintCanvasBehavior instance that contains or is associated with all the shapes used in the stages.

function_factory: ceed.function.FunctionFactoryBase = None

The FunctionFactoryBase instance that contains or is associated with all the functions used in the stages.

stages: List[ceed.stage.CeedStage]

The list of the currently available CeedStage instances added with add_stage().

These stages are listed in the GUI and can be used by name to start a stage to run.

It does not include the instances automatically created and stored in stages_inst_default when you register() a stage class.

stages_cls: Dict[str, Type[ceed.stage.StageType]] = {}

Dict whose keys is the name of the stage classes registered with register() and whose values is the corresponding classes.:

>>> stage_factory.stages_cls
{'CeedStage': ceed.stage.CeedStage}
stages_inst_default: Dict[str, ceed.stage.CeedStage] = {}

Dict whose keys is the function CeedStage.name and whose values are the corresponding stage instances.

Contains only the stages that are automatically created and added when register() is called on a class.

>>> stage_factory.stages_inst_default
{'Stage': <ceed.stage.CeedStage at 0x1da866f00b8>}
unique_names: ceed.utils.UniqueNames = None

A set that tracks existing stage names to help us ensure all global stages have unique names.

plugin_sources: Dict[str, List[Tuple[Tuple[str], bytes]]] = {}

A dictionary of the names of all the plugin packages imported, mapped to the python file contents of the directories in the package. Each value is a list of tuples.

The first item of each tuple is also a tuple containing the names of the directories leading to and including the python filename relative to the package. The second item in the tuple is the bytes content of the file.

get_stage_ref(name: Optional[str] = None, stage: Optional[Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]] = None) ceed.stage.CeedStageRef

Returns a CeedStageRef instance that refers to the original stage. See ceed.stage for details.

One of name or stage must be specified. The stage being referenced by stage should have been added to this instance, although it is not explicitly enforced currently.

If used, return_stae_ref() must be called when the reference is not used anymore.

Parameters
  • name – The name of the stage to lookup in stage_names.

  • stage – Or the actual stage to use.

Returns

A CeedStageRef to the original stage.

return_stage_ref(stage_ref: ceed.stage.CeedStageRef) None

Releases the stage ref created by get_stage_ref().

Parameters

stage_ref – Instance returned by get_stage_ref().

register(cls: Type[ceed.stage.StageType], instance: Optional[ceed.stage.CeedStage] = None)

Registers the class and adds it to stages_cls. It also creates an instance (unless instance is provided, in which case that is used) of the class that is added to stage_names and stages_inst_default.

See ceed.stage for details.

Params
cls: subclass of CeedStage

The class to register.

instance: instance of cls

The instance of cls to use. If None, a default class instance, using the default CeedStage.name is stored. Defaults to None.

add_plugin_source(package: str, contents: List[Tuple[Tuple[str], bytes]])

Adds the package contents to plugin_sources if it hasn’t already been added. Otherwise raises an error.

get(name: str) Optional[Type[ceed.stage.StageType]]

Returns the class with name name that was registered with register().

See ceed.stage for details.

Params
name: str

The name of the class (e.g. 'CosStage').

Returns

The class if found, otherwise None.

get_names() List[str]

Returns the class names of all classes registered with register().

get_classes() List[Type[ceed.stage.StageType]]

Returns the classes registered with register().

make_stage(state: dict, instance: Optional[Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]] = None, clone: bool = False, func_name_map: Dict[str, str] = {}, old_name_to_shape_map: Optional[Dict[str, Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]]] = None) Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]

Instantiates the stage from the state and returns it.

This method must be used to instantiate a stage from state. See ceed.stage for details and an example.

Parameters
  • state – The state dict representing the stage as returned by FuncBase.get_state().

  • instance – If None, a stage instance will be created and state will applied to it. Otherwise, it is applied to the given instance, which must be of the correct class.

  • clone – See CeedStage.apply_state().

  • func_name_map – See CeedStage.apply_state().

  • old_name_to_shape_map – See CeedStage.apply_state().

Returns

The stage instance created or used.

add_stage(stage: ceed.stage.CeedStage, allow_last_experiment=True) None

Adds the CeedStage to the stage factory (stages) and makes it available in the GUI.

See ceed.stage for details.

Remember to check can_other_stage_be_added() before adding if there’s potential for it to return False.

If the CeedStage.name already exists in stages, CeedStage.name will be set to a unique name based on its original name. Once added until removed, anytime the stage’s CeedStage.name changes, if it clashes with an existing stage’s name, it is automatically renamed.

Parameters
  • stage – The CeedStage to add.

  • allow_last_experiment – Whether to allow the stage to have the same name is ceed.stage.last_experiment_stage_name. If False and a stage with that name is added, it is renamed. Otherwise, it’s original name is kept.

remove_stage(stage: ceed.stage.CeedStage, force=False) bool

Removes a stage previously added with add_stage().

Params
stage: CeedStage

The stage to remove.

force: bool

If True, it’ll remove the stage even if there are references to it created by get_stage_ref().

Returns

Whether the stage was removed successfully. E.g. if force is False and it has a ref, it won’t be removed.

clear_stages(force=False) None

Removes all the stages registered with add_stage().

Params
force: bool

If True, it’ll remove all stages even if there are references to them created by get_stage_ref().

find_shape_in_all_stages(_, shape, process_shape_callback) None

Searches for the ceed.shape.CeedShape instance in all the known stages and calls process_shape_callback on each found.

Params
_: anything

This parameter is ignored and can be anything.

shape: ceed.shape.CeedShape

The shape to search in all stages.

process_shape_callback: callback function

It is called with two parameters; the CeedStage and ceed.shape.CeedShape instance for each found.

save_stages(use_cache=False) List[dict]

Returns a dict representation of all the stages added with add_stage().

Parameters

use_cache – If True, it’ll get the state using the cache from previous times the state was read and cached, if the cache exists.

It is a list of dicts where each item is the CeedStage.get_state() of the corresponding stage in stages.

recover_stages(stage_states: List[dict], func_name_map: dict, old_name_to_shape_map: Dict[str, Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]]) Tuple[List[ceed.stage.CeedStage], Dict[str, str]]

Takes a list of stages states such as returned by save_stages() and instantiates the stages represented by the states and adds (add_stage()) the stages to the factory.

Parameters
Returns

A tuple stages, name_map, stages is the list of stages, name_map is a map from the original stage’s name to the new name given to the stage (in case a stage with that name already existed).

tick_stage(t_start: Union[float, int, fractions.Fraction], frame_rate: fractions.Fraction, stage_name: str = '', stage: Optional[ceed.stage.CeedStage] = None, pre_compute: bool = False) Generator[List[Tuple[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]]], Union[float, int, fractions.Fraction], None]

A generator which starts a CeedStage and can be time-ticked to generate the shape intensity values for each time point in the experiment.

A CeedStage represents a collection of shapes with functions applied to them. Each of these shapes has a defined intensity for every time point. This generator walks through time computing the intensity for each shape for every time point and yielding it.

See get_all_shape_values() for example usage. Ceed GUI uses this to generate the shape intensity values shown during an experiment.

See the example in stage showing how to run a stage with this method.

Params
t_start: a number

The global time at which the stage starts.

frame_rate: fraction

The frame rate to sample at, as a fraction.

stage_name: str

The CeedStage.name of the stage to start.

stage: str

The CeedStage to start.

pre_compute: bool

Whether to pre-compute the stages, for those stages that support it.

Yields

A list of the intensity values for each shape.

Each item in the list is a 2-tuple of (name, values). name is the kivy_garden.painter.PaintShape.name of the shape and is listed only once in the list. values is a list of color values and each item in that list is a 4-tuple of (r, g, b, a). Any of these values can be None, in which case that color remains the same (see set_shape_gl_color_values()). This way a shape can be updated from multiple sub-stages, where only e.g. the r value is changed.

Raises
StageDoneException:

When done with the stage (time is out of bounds). The time at which this is raised was not used by the stage so there’s no shape values for that last time point.

add_shapes_gl_to_canvas(canvas: kivy.graphics.instructions.Canvas, name: str, quad: Optional[int] = None, shapes: Optional[Container[str]] = None) Dict[str, kivy.graphics.context_instructions.Color]

Adds all the kivy OpenGL instructions required to display the intensity-varying shapes to the kivy canvas and returns the color classes that control the color of each shape.

This is called by Ceed when it creates a new experiment to get all the graphics for the shapes that it will control during the experiment.

Params
canvas: Kivy canvas instance

The canvas to which the gl instructions are added. It add e.g. the polygon and its color.

name: str

The name to associate with these OpenGL instructions. The name is used later to remove the instructions as it allows to clear all the instructions with that name.

quad: int or None

When in quad mode, we add the instructions 4 times, one for each quad (top-left, top-right, bottom-left, bottom-right) so it is called 4 times sequentially. This counts up from 0-3 in that case. Otherwise, it’s None. From a user POV it should not matter whether we’re in quad mode because Ceed handles scaling and placing the graphics in the right area.

Returns

a dict whose keys is the kivy_garden.painter.PaintShape.name of the ceed.shape.CeedShape and whose value is the Kivy Color instruction instance that controls the color of the shape.

set_shape_gl_color_values(shape_views: Optional[Dict[str, kivy.graphics.context_instructions.Color]], shape_values: List[Tuple[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]]], quad: Optional[int] = None, grayscale: Optional[str] = None) List[Tuple[str, float, float, float, float]]

Takes the dict of the Color instances that control the color of each shape as well as the list of the color values for the current time point for each shape and sets the shape’s color to those values.

This is called by Ceed for every time step to set the current shape color values. In QUAD4X it’s called 4 times per frame, in QUAD121X it’s called 12 times per frame.

The shape color values is a list of 4-tuples, each a r, g, b, a value. In each tuple, any of them can be None, in which case that color channel is skipped for that tuple. The list is flattened and the last value for each channel across all tuples is used (after being forced to the [0, 1] range). If any are None across all tuples, it’s left unchanged and not set. If all r, g, b, and a is None, that shape becomes transparent.

Params
shape_views: dict

The dict of shape names and shapes colors as returned by add_shapes_gl_to_canvas().

If it is None, the color will not be updated but the return result will be identical to when not None.

shape_values: list

The list of color intensity values to use for each shape as yielded by tick_stage().

quad: int or None

When in quad mode, we added the instructions 4 times, one for each quad. This indicates which quad is being updated, counting up from 0-3 in that case. Otherwise, it’s None.

grayscale: str

The colors to operate on. Can be any subset of the string ‘rgb’. Specifically, although we get intensity values for some subset of r, g, b values for each stage from the stage settings, this computes the average intensity for the active RGB channels selected in the stage and assigns the mean to all of the colors listed in grayscale.

E.g. if a stage selects the r and g colors in its config, and grayscale is "gb", then both the g and b channels are set to the mean of the r and g values provided by the stage for this timestep (b is excluded since the stage provides no value for it). The b channel is not set so it’s left unchanged (i.e. it’ll keep the last value).

This is how we turn the color into a gray-scale value when e.g. in QUAD12X mode. Specifically, in that mode, this method is called 12 times, 4 for the 4 quads, and 3 for the r, g, and b color channel for each quad. It gets called 4 times for the red channel with grayscale set to 'r', followed by 4 times for the green channel with grayscale set to 'g', followed by 4 times for the blue channel with grayscale set to 'b'. This sets the value for 12 frames.

Returns

A list of the colors each shape was set to. Each item in the list is (name, r, g, b, a), where name is the shape’s name and r, g, b, a is the color value it was set to. Each name occurs at most once in the list.

E.g. from the worked example in stage, by default we called set_shape_gl_color_values() with no grayscale parameter value, which printed:

time=0s,    intensity="[('Shape', 0, 0, 0, 1)]"
time=1/2s,  intensity="[('Shape', 0.165, 0, 0.165, 1)]"
time=1s,    intensity="[('Shape', 0.33, 0, 0.33, 1)]"
...

If we provide "r" for grayscale, it prints:

time=0s,    intensity="[('Shape', 0.0, 0.0, 0.0, 1)]"
time=1/2s,  intensity="[('Shape', 0.165, 0.165, 0.165, 1)]"
time=1s,    intensity="[('Shape', 0.33, 0.33, 0.33, 1)]"
...

That is the stage only sets red and blue, so it averages those two values, which happen to be the same because there’s only one stage setting the color value for both red/blue channels. This mean value is assigned to r, g, and b in the result. However, if shape_views was provided to the function, only the red channel’s color would be set to this value because grayscale was "r". If it was "rg", the printed value would be the same, but only red and green of the Color graphics instructions would be set to the mean value and the others (green/blue or blue, respectively) remain unchanged.

remove_shapes_gl_from_canvas(canvas: kivy.graphics.instructions.Canvas, name: str) None

Removes all the shape and color instructions that was added with add_shapes_gl_to_canvas().

This is called by Ceed after an experiment and it removes all the instructions added with this group name.

Params
canvas: Kivy canvas instance

The canvas to which the gl instructions were added.

name: str

The name used when adding the instructions with add_shapes_gl_to_canvas().

get_all_shape_values(frame_rate: fractions.Fraction, stage_name: str = '', stage: Optional[ceed.stage.CeedStage] = None, pre_compute: bool = False) Dict[str, List[Tuple[float, float, float, float]]]

Uses tick_stage() for every shape in the stage stage_name or given stage, it samples all the shape intensity values at the given frame rate for the full stage duration and returns a list of intensity values for each shape corresponding to each time point.

frame_rate is not frame_rate (although it can be) bur rather the rate at which we sample the functions.

TODO: skip functions that are infinite duration. Add option to indicate

stage is also infinite. Currently it would just hang for infinite stage.

add_manual_gl_to_canvas(screen_width: int, screen_height: int, stage: ceed.stage.CeedStage, canvas: kivy.graphics.instructions.Canvas, name: str, quad_mode: str, quad: Optional[int] = None) List[ceed.stage.CeedStage]

Adds all the kivy OpenGL instructions that a stage may manually set. It internally calls add_gl_to_canvas() for the root stage and all its substages.

This is called by Ceed when it creates a new experiment to get all the graphics for the stage used during an experiment.

Parameters
  • screen_width – The width of the projector in pixels.

  • screen_height – The height of the projector in pixels.

  • stage – The root CeedStage that will be run.

  • canvas – The Kivy canvas instance to which the gl instructions must be added.

  • name – The name to associate with these OpenGL instructions. The name is used later to remove the instructions in remove_shapes_gl_from_canvas() as it allows to clear all the instructions with that name.

  • quad_mode – Whether we’re in quad mode. This is the specific quad mode used. It can be one of ‘RGB’ (normal mode), ‘QUAD4X’, or ‘QUAD12X’.

  • quad

    When in quad mode, we have to add the instructions 4 times, one for each quad (top-left, top-right, bottom-left, bottom-right) so it is called 4 times sequentially. This counts up from 0-3 in that case. Otherwise, it’s None.

    From a user POV it should not matter whether we’re in quad mode because Ceed handles scaling and placing the graphics in the right area. So the user must always create their graphics at full screen size and relative to bottom left corner. Ceed will automatically scale and translate them to the appropriate quad.

Returns

The list of stages who added graphics instructions (their add_gl_to_canvas() returned True).

set_manual_gl_colors(stages: List[ceed.stage.CeedStage], quad: Optional[int] = None, grayscale: Optional[str] = None, clear: bool = False) None

Calls set_gl_colors() for all the stages with manual graphics.

Called by Ceed for every time step to allow the stages to update their manually added gl instructions (in add_manual_gl_to_canvas()) for this frame. In QUAD4X it’s called 4 times per frame, in QUAD121X it’s called 12 times per frame.

Parameters
  • stages – The list of stages returned by add_manual_gl_to_canvas().

  • quad – Same as in set_shape_gl_color_values().

  • grayscale – Same as in set_shape_gl_color_values().

  • clear – Unlike for the shape graphics that Ceed controls directly Ceed does not control the manual graphics. If a stage ends in the middle of a frame in quad mode, then the rest of the graphics or color channels for that frame must be cleared to black. Therefore, This will be called for those quads/channels with this parameter True and you must clear it.

remove_manual_gl_from_canvas(stage: ceed.stage.CeedStage, canvas: kivy.graphics.instructions.Canvas, name: str) None

Removes all the gl instructions that was added with add_manual_gl_to_canvas(). It internally calls remove_gl_from_canvas() for the root stage and all its substages.

This is called by Ceed after an experiment and it should remove all the instructions added. Instructions added with this name will be automatically removed by remove_shapes_gl_from_canvas() so they don’t have to be removed manually.

Parameters
  • stage – The root CeedStage that was run.

  • canvas – The Kivy canvas instance to which the gl instructions was added.

  • name – The name associated with these OpenGL instructions.

class ceed.stage.CeedStage(stage_factory: ceed.stage.StageFactoryBase, function_factory: ceed.function.FunctionFactoryBase, shape_factory: ceed.shape.CeedPaintCanvasBehavior, **kwargs)

Bases: kivy._event.EventDispatcher, ceed.utils.CeedWithID

The stage that controls a time period of an experiment.

See ceed.stage for details.

Events
on_changed:

Triggered whenever a stage’s configuration option changes or if one of the functions or shapes of the stage is added/removed.

name: str

The name of this stage.

order: str

The order in which the sub-stages, stages, are evaluated. Can be one of 'serial' (one after the other), 'parallel' (all in parallel).

See CeedStage description for details.

complete_on: str

When to consider the stage’s children stages to be complete if we contain sub-stages - stages. Can be one of 'all', 'any'.

If 'any', this stage is done when any of the children stages is done, and when all of this stage’s functions are done. If 'all', this stage is done when all children stages are done, and when all of this stage’s functions are done.

See CeedStage description for details.

disable_pre_compute: bool

Whether to disable pre-computing for this stage.

When pre-computing, either the stage is completely pre-computed from its functions and all sub-stages and then the stage’s intensity values for all time-points becomes essentially a flat list stored in runtime_stage. Or, if e.g. some of the sub-stages or functions cannot be pre-computed, then only the stage’s functions (or those among the functions that can be pre-computed) are pre-computed and all are stored in runtime_functions.

When disable_pre_compute is True, neither is pre-computed, even if the overall pre_compute_stages is True.

loop: int

The number of loop iterations the stage should do.

If more than one, the stage will go through all its functions and stages loop times.

parent_stage: ceed.stage.CeedStage

The parent stage when this stage is a sub-stage of another.

has_ref: bool

Whether there’s a CeedStageRef pointing to this stage.

color_r: bool

Whether the shapes red channel should be set to the functions value or if it should remain at zero intensity (False).

color_g: bool

Whether the shapes green channel should be set to the functions value or if it should remain at zero intensity (False).

color_b: bool

Whether the shapes blue channel should be set to the functions value or if it should remain at zero intensity (False).

color_a: bool

Whether the shapes alpha channel should be set. Currently this is ignored.

randomize_child_order: bool

Whether the sub-stages order should be randomly re-shuffled before each experiment.

If True, stages order stays the same, but the stage executes them in random order. The order is pre-sampled before the stage is executed and the given order is then used during the stage.

The order is stored in shuffled_order.

See also randomize_order_each_loop, lock_after_forked, loop_count, and loop_tree_count.

randomize_order_each_loop

When randomize_child_order is True, whether the child order should be re-shuffled for each loop iteration including loops of parent and parent of parents etc. (True) or whether we shuffle once before each experiment and use that order for all loop iterations.

See also randomize_child_order, lock_after_forked, loop_count, and loop_tree_count.

lock_after_forked: bool

Stages can reference other stages. After the reference stages are expanded and copied before running the stage as an experiment, if lock_after_forked is False then shuffled_order is re-sampled again for each copied stage. If it’s True, then it is not re-sampled again and all the stages referencing the original stage will share the same randomized re-ordering as the referenced stage.

See also copy_and_resample.

shuffled_order: List[List[int]] = []

When randomize_child_order is True, it is a list of the stages ordering that we should use for each loop.

It is a list of lists, and each internal list corresponds to a single loop iteration as indexed by loop_tree_count. If we don’t randomize_order_each_loop, then it contains a single list used for all the loops, otherwise there’s one for each loop.

If not randomize_child_order, then it’s empty and they run in stages order.

If there are more loops than number of items in the outer list, we use the last sub-list for the remaining loops. If not all stages indices are included in the internal lists, those stages are skipped.

display = None

A widget used by the GUI to display the stage.

pad_stage_ticks: int = 0

If the duration of the stage as represented by the number of clock tick is less than pad_stage_ticks, the stage will be padded to pad_stage_ticks clock cycles at the end.

During those cycles, the stage’s shapes will be unchanged by this stage (i.e. if another stage is simultaneously active and set their values, that value will be used, otherwise it’ll be transparent), except for the shapes with StageShape.keep_dark that will still be kept black.

See pad_to_stage_handshake for usage details.

Warning

This is for internal use and is not saved with the stage state.

t_start: Union[float, int, fractions.Fraction] = 0

The global time with which the stage or loop iteration was initialized. The value is in seconds.

Don’t set directly, it is set in init_stage() and init_loop_iteration(). If the stage is can_pre_compute, this is not used after pre-computing the stage.

t_end: Union[float, int, fractions.Fraction] = 0

The time at which the loop or stage ended in global timebase.

Set by the stage after each loop is done and is only valid once loop/stage is done. It is used by the next stage in stages after this stage to know when to start, or for this stage to know when the the next loop started. If the stage is can_pre_compute, after pre-computing the stage it is only set to indicate when the stage ends, not for each loop.

If overwriting evaluate_stage(), this must be set with the last time value passed in that was not used, indicating the time the stage ended (i.e. the stage spanned from the stage start time until t_end, not including the end). The next stage in the sequence would start from this time. Similarly, if manually setting runtime_stage, the total stage duration is included and this value is automatically set from it in evaluate_pre_computed_stage().

It is updated before finalize_loop_iteration(), and finalize_stage() are called.

loop_count: int = 0

The current loop iteration.

This goes from zero (set by init_stage() / init_loop_iteration()) to loop - 1. The stage is done when it is exactly loop - 1, having looped times. When finalize_loop_iteration() and finalize_stage() are called, it is the value for the loop iteration that just ended.

If the stage is can_pre_compute, this is not used after pre-computing the stage.

See also loop_tree_count.

loop_tree_count: int = 0

The current iteration, starting from zero, and incremented for each loop of the stage, including outside loops that loop over the stage.

E.g.:

Stage:
    name: root
    loop: 2
    Stage:
        name: child
        loop: 3

Then the root and child stage’s loop_count will be 0 - 1, and 0 - 2, respectively, while loop_tree_count will be 0 - 1 and 0 - 5, respectively.

runtime_functions: List[Tuple[Optional[ceed.function.FuncBase], Optional[List[float]], Optional[list], Optional[float]]] = []

Stores the pre-computed function values for those can be pre-computed and the original function for the rest.

Similar to runtime_stage, but if can_pre_compute is False yet disable_pre_compute is also False, then we pre-compute all the functions who are not infinite in duration ( duration is non-negative) and store them here interleaved with those that are infinite.

It is a list of 4-tuples of the same length as functions. Each item is (function, intensity, logs, duration). It is generated by pre_compute_functions().

If the corresponding function is pre-computable, the function is None and intensity, logs, and duration is similar to runtime_stage with the same constraints about each intensity and logs value corresponds to a time point the function is sampled and the ending time-point must be larger or equal to duration, relative to the function start time. logs may be one value larger than intensity, if there’s some logs to be emitted on the frame after the last sample.

If the function is not pre-computable, the function is the original function and intensity, logs, and duration are None.

If set manually, ensure that apply_pre_compute() is overwritten to do nothing, otherwise it may overwrite your value. Similarly, runtime_stage must be None, otherwise that will be used instead.

runtime_stage: Optional[Tuple[Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], list, int, Union[float, int, fractions.Fraction]]] = None

A 4-tuple of stage (intensity, logs, count, duration).

If can_pre_compute, then this stage’s intensity values are pre-computed into a list for each shape and stored here. Otherwise, if it’s not pre-computed it is None. intensity is a dict whose keys are shape names and values are a list of r, g, b, a values, one for each time point indicating the r,g,b,a value for that shape for that time point.

Each time-point value corresponds exactly to the time passed to the stage that generated the value. E.g. with a linear function, the stage may be called with times (relative to the stage start time) such as 0 / rate, 1 / rate, … n / rate and the values correspond to the function values at those times. Then during the experiment, as we get time values, we instead count the number of tick stage calls and that number is the index into the values list that we return for all the shapes.

Similarly, logs is a list of any data event logs captured during pre-computing. These logs are replayed during the real experiment for each corresponding frame.

count is the number of frames in intensity and logs. However, logs may be one value larger than intensity (count), if there’s some logs to be emitted on the frame after the last sample.

After the last value in the list is used, the next time point past will raise a StageDoneException indicating the stage is done and the time value will have to be larger or equal to t_end, which is the same saying the time relative to the stage start time must be larger or equal to the duration of the tuple.

By default it is generated by pre_compute_stage(). If set manually by the user, the above constraints must be followed and additionally, apply_pre_compute() should be overwritten to do nothing, otherwise it may overwrite your value.

can_pre_compute: bool = False

Whether we can pre-compute the full stage.

It is read only and is automatically computed during init_stage_tree().

If True it means that all the functions have finite duration (i.e. non-negative), for all stages their can_pre_compute is True, and disable_pre_compute is False.

apply_pre_compute() does the pre-computation. If can_pre_compute is True, then it precomputes the stage’s intensity values for all time-points that the stage is active from its functions and all sub-stages and then essentially stores it as a flat list in runtime_stage.

If it is False, then if disable_pre_compute is also False, then all the functions that can be pre-computed are pre-computed, otherwise nothing is pre-computed. In both case we still call apply_pre_compute() on all sub-stages.

stage_factory: ceed.stage.StageFactoryBase = None

The StageFactoryBase this stage is associated with.

function_factory: ceed.function.FunctionFactoryBase = None

The FunctionFactoryBase the functions are associated with.

shape_factory: ceed.shape.CeedPaintCanvasBehavior = None

The CeedPaintCanvasBehavior the shapes are associated with.

functions: List[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef]] = []

A list of ceed.function.FuncBase instances which the stage iterates through sequentially to compute the intensity of all the shapes at each time point.

stages: List[Union[CeedStage, CeedStageRef]] = []

A list of CeedStage instances that are sub-stages of this stage.

See CeedStage description for details.

shapes: List[ceed.stage.StageShape] = []

The list of StageShape instances that are associated with this stage.

All the shapes are set to the same intensity value at every time point according to the functions value at that time point.

get_cached_state(use_cache=False) Dict

Like get_state(), but it caches the result. And next time it is called, if use_cache is True, the cached value will be returned, unless the config changed in between. Helpful for backup so we don’t recompute the full state.

Parameters

use_cache – If True, it’ll get the state using the cache from previous times the state was read and cached, if the cache exists.

Returns

The state dict.

get_state(expand_ref: bool = False) dict

Returns a dict representation of the stage so that it can be reconstructed later with apply_state().

Params
state: dict

A dict with the state, to which configuration items and their values are added. If None, the default, a dict is created and returned.

Returns

A dict with all the configuration data.

apply_state(state: dict = {}, clone: bool = False, func_name_map: Dict[str, str] = {}, old_name_to_shape_map: Optional[Dict[str, Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]]] = None) None

Takes the state of the stage saved with get_state() and applies it to this stage. it also creates any children functions and stages and creates the references to the shapes.

It is called internally and should not be used directly. Use StageFactoryBase.make_stage() instead.

Parameters
  • state – The state dict representing the stage as returned by get_state().

  • clone – If False, only user customizable properties of the stage will be set (i.e those not listed in _clone_props), otherwise, all properties from state are applied to the stage. Clone is meant to be a complete re-instantiation of stage function.

  • func_name_map – a mapping that maps old function names to their new names, in case they were re-named when imported.

  • old_name_to_shape_map – Mapping from shape names to the shapes.

copy_expand_ref() ceed.stage.CeedStage

Returns a copy of the stage. Any sub-stages, recursively, that are ref-stages are expanded to normal stages.

replace_ref_stage_with_source(stage_ref: ceed.stage.CeedStageRef) Tuple[ceed.stage.CeedStage, int]

Replaces the stage ref in stages with a copy of the referenced stage.

replace_ref_func_with_source(func_ref: ceed.function.CeedFuncRef) Tuple[ceed.function.FuncBase, int]

Replaces the func ref in functions with a copy of the referenced function.

can_other_stage_be_added(other_stage: Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]) bool

Checks whether the other stage may be added to us.

If the stage is already a child of this stage or sub-stages, it returns False to prevent recursion loops.

add_stage(stage: Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef], after: Optional[Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]] = None, index: Optional[int] = None) None

Adds a sub-stage instance CeedStage to stages.

Parameters
  • stage – The CeedStage or ref to add.

  • after – The CeedStage in stages after which to add this stage, if not None, the default.

  • index – The index in stages at which to add this stage, if not None, the default.

remove_stage(stage: Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef]) bool

Removes a sub-stage instance CeedStage from stages.

add_func(func: Union[ceed.function.FuncBase, ceed.function.CeedFuncRef], after: Optional[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef]] = None, index: Optional[int] = None) None

Adds the function instance ceed.function.FuncBase to functions.

Parameters
remove_func(func: Union[ceed.function.FuncBase, ceed.function.CeedFuncRef]) bool

Removes the function instance ceed.function.FuncBase from functions.

add_shape(shape: Union[ceed.shape.CeedShapeGroup, ceed.shape.CeedShape]) Optional[ceed.stage.StageShape]

Creates and adds a StageShape instance wrapping the ceed.shape.CeedShape shape to the shapes. If the shape was already added it doesn’t add it again.

Params
shape: ceed.shape.CeedShape

The shape instance to add.

Returns

The StageShape created if the shape wasn’t already added, otherwise None.

remove_shape(stage_shape: ceed.stage.StageShape) None

Removes a StageShape that was previously added to shapes.

get_settings_display(stage_widget) Dict[str, Any]

Returns widgets that will be displayed to the user in the stage settings.

These widgets can be used to allow users to further configure custom stages. This is called by the Ceed GUI when the settings are first displayed to the user.

Parameters

stage_widget – The root settings widget to which the settings will be added as grandchildren by the caller.

Returns

It should return a dict of the name of each setting mapped to the widget controlling the setting. It will be displayed in two columns: the name followed by the widget on the same row.

get_stages(step_into_ref: bool = True) Generator[Union[ceed.stage.CeedStage, ceed.stage.CeedStageRef], None, None]

Generator that iterates depth-first through all the stages and children stages and yields these stages.

Parameters

step_into_ref – bool If True, when it encounters a CeedStageRef instance it’ll step into it and return that stage and its children. Otherwise, it’ll just return the CeedStageRef and not step into it.

get_funcs() Generator[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef], None, None]

Generator that iterates depth-first through all the stages and children stages and for each stage yields each function from functions as well as their children functions, recursively.

If it encounters a CeedStageRef or CeedFuncRef it’ll enter the original stage/function and yield its children.

init_stage(t_start: Union[float, int, fractions.Fraction]) None

Initializes the stage so it is ready to be used to get the stage values. See also init_stage_tree() and init_loop_iteration(). If overriding, super must be called.

If the stage is can_pre_compute, this is not used after pre-computing the stage.

Parameters

t_start – The time in seconds in global time. t_start will be set to this value.

init_loop_iteration(t_start: Union[float, int, fractions.Fraction]) None

Initializes the stage at the beginning of each loop.

It’s called internally at the start of every loop iteration, except the first. See also init_stage_tree() and init_stage(). If overriding, super must be called.

If the stage is can_pre_compute, this is not used after pre-computing the stage.

Parameters

t_start – The time in seconds in global time. t_start will be set to this value.

finalize_stage() None

Finalizes the stage at the end of all its loops, when the stage is done. See also finalize_loop_iteration(). If overriding, super must be called.

finalize_loop_iteration() None

Finalizes the stage at the end of each loop, including the first and last. See also finalize_stage(). If overriding, super must be called.

tick_stage(shapes: Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], last_end_t: Union[float, int, fractions.Fraction]) Generator[None, Union[float, int, fractions.Fraction], None]

Similar to StageFactoryBase.tick_stage() but for this stage. This calls internally either evaluate_pre_computed_stage() if the stage was pre-computed (runtime_stage is not None) or evaluate_pre_computed_stage`evaluate_stage() when it is not pre-computed.

It is an generator that ticks through time and updates the intensity values for the shapes associated with this stage and its sub-stages.

Specifically, at every iteration, a time value is sent to the iterator by Ceed which then updates the shapes dict with the intensity values of the shape for the that time-point.

The method is sent time step values and it yields at every time step after the shapes dict is updated. The final time that was sent on which it raises StageDoneException means that the given time was not used and the stage is done for that time value.

Parameters
  • shapes – A dict whose keys is the name of all the shapes of this stage and its sub-stages. The corresponding values are empty lists. At every iteration the list should be filled in with the desired color values.

  • last_end_t – the start time of the stage in globbal time.

Raises
StageDoneException:

When done with the stage (time is out of bounds). The time value that raised this was not used.

evaluate_pre_computed_stage(shapes: Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], last_end_t: Union[float, int, fractions.Fraction]) Generator[None, Union[float, int, fractions.Fraction], None]

Generator called by tick_stage() if the stage was pre-computed. See that method for details.

This should not be overwritten, rather one can set runtime_stage to desired values and this method will iterate through it.

If setting runtime_stage manually, apply_pre_compute() should be overwritten, otherwise it may overwrite runtime_stage as it attempts to pre-compute again. But it’s generally safer and simpler to customize evaluate_stage() instead and have Ceed generated the pre-compute values from it.

evaluate_stage(shapes: Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], last_end_t: Union[float, int, fractions.Fraction]) Generator[None, Union[float, int, fractions.Fraction], None]

Generator called by tick_stage() in real-time if the stage won’t be pre-computed or before the stage is run if we’re pre-computing the stage. See that method for details.

This method can safely be overwritten to set stage-shape values. And if the stage will be pre-computed, this method will still be internally called to get the shape values so pre-computing does not have to be considered at all when overwriting this method.

However, t_end must be set with the final stage time before the method ends, otherwise it’ll break the timing. Similarly, t_start should be set the first time value of the stage. Following is an appropriate customization (assuming those named shapes exist in the GUI):

def evaluate_stage(self, shapes, last_end_t):
    # always get the first time
    self.t_start = t = yield
    # we ignore any looping and just use 10 time points.
    for i in range(10):
        # r, g, b, a values
        shapes['my shape'].append(
            (float(.1 * (t - last_end_t)), .2,  (i % 2) * .3, None))
        shapes['other shape'].append((.1, .2,  (i % 2) * .5, None))
        # this yields so GUI can use the change shape colors
        t = yield

    # this time value was not used and this effectively makes the
    # stage 10 samples long, and it ends on the last sample so
    # that last time will be used as start of next stage
    self.t_end = t
    raise StageDoneException
tick_stage_loop(shapes: Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], last_end_t: Union[float, int, fractions.Fraction]) Generator[None, Union[float, int, fractions.Fraction], None]

If the stage was not pre-computed, ticks through one loop iteration of the stage yielding the shape values for each time-point.

pre_compute_functions(frame_rate: fractions.Fraction) List[Tuple[Optional[ceed.function.FuncBase], Optional[List[float]], Optional[list], Optional[float]]]

Goes through all the stage’s functions and pre-computes those that are finite and can be pre-computed.

Returns a list of pre-computed values/original functions, one for each function of the stage. Each item is a 4-tuple of either (func, None, None, None) when the function is not finite, otherwise (None, pre_computed_values, logs, end_time).

This allows only some functions to be pre-computed.

pre_compute_stage(frame_rate: fractions.Fraction, t_start: Union[float, int, fractions.Fraction], shapes: Set[str]) Tuple[Dict[str, List[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]], list, int, Union[float, int, fractions.Fraction]]

If the stage is to be pre-computed, apply_pre_compute() uses this to pre-compute the stage intensity values for all the shapes for all time points when the stage would be active.

It returns the shape intensity values and data logs for all time points as well as the end time when the stage ended relative to zero time (not t_start).

tick_funcs(last_end_t: Union[float, int, fractions.Fraction]) Generator[Optional[float], Union[float, int, fractions.Fraction], None]

Iterates through the functions of this stage sequentially and returns the function’s value associated with the given time passed in for each tick.

If the functions were pre-computed, it goes through the pre-computed values, assuming the passed in time is exactly those values used when pre-computing relative to last_end_t.

init_stage_tree(root: Optional[ceed.stage.CeedStage] = None) None

Before the stage is apply_pre_compute() and started, the stage and all sub-stages are recursively initialized.

Initializes the stage as part of the stage tree so it is ready to be called to get the stage values as part of the tree. It is called once for each stage of the entire stage tree.

Parameters

root – The root of the stage tree. If None, it’s self.

apply_pre_compute(pre_compute: bool, frame_rate: fractions.Fraction, t_start: Union[float, int, fractions.Fraction], shapes: Set[str]) None

Pre-computes the intensity values for all the shapes of this stage and/or sub-stages, if enabled.

Depending on pre_compute, can_pre_compute, and disable_pre_compute, it pre-computes the full stage and sub-stages, just the stage’s functions or only the sub-stages recursively.

resample_parameters(parent_tree: Optional[List[ceed.stage.CeedStage]] = None, is_forked: bool = False) None

Resamples all parameters of all functions of the stage that have randomness associated with it as well as shuffled_order if randomize_child_order.

parent_tree is not inclusive. It is a list of stages starting from the root stage leading up to this stage in the stage tree.

See FuncBase.resample_parameters() and copy_and_resample() for the meaning of is_forked.

copy_and_resample() ceed.stage.CeedStage

Resamples all the functions of the stage and shuffled_order, copies and expands the stage, possibly resamples parameters, and returns the duplicated stage.

It copies a stage so that the stage is ready to run in a experiment. First it resamples all the parameters of all the functions that have randomness associated with it and it resamples shuffled_order. Then it copies and expends the stage and any sub-stages and functions that refer to other functions/stages. Then, it resamples again those randomized function parameters that are not marked as lock_after_forked as well as shuffled_order if not lock_after_forked. Those will maintain their original re-sampled values so that all the expanded copies of reference functions/stages have the same random values.

See resample_parameters() and FuncBase.resample_parameters() as well.

get_stage_shape_names() Set[str]

Gets all the names of the shapes controlled by the stage or substages. If adding names, you must call super to get the builtin shapes.

It calls get_stage_shape_names() on its children, recursively, so any of them can be overwritten to return arbitrary names (that must be unique among all the shapes).

This is used to create the shape logs in the HDF5 data file. Because for each shape we create a Nx4 array, where N is the number of Ceed time frames, and 4 is for the RGBA value for that frame (shapes_intensity).

By default, it gets the name of the shapes from shapes from itself and its children stages. If you’re directly updating the graphics, you can log rgba values by returning additional names here and then setting their values when the stage is ticked, as if it was a real shape. The values will then be accessible in shapes_intensity.

If you set the values of the additional shapes when the stage is ticked, then the values will also show in the stage preview graph so you can use it to display arbitrary rgb data for your stage in the graph. But it must be logged during stage tick (e.g. evaluate_stage()), not during the actual graphing in set_gl_colors() that follows each tick.

See the stage example plugins for example usage.

set_ceed_id(min_available: int) int

Sets the ID of this and any sub objects, each to a number equal or greater than min_available and returns the next minimum number available to be used.

See event_data for more details.

Parameters

min_available – The minimum number available to be used for the ID so it is unique.

Returns

The next minimum available number that can be used. Any number larger or equal to it is free to be used.

get_ref_src() ceed.stage.CeedStage

Returns the stage referenced by this object if we are a CeedStageRef, otherwise it just returns this stage.

Useful to get the referenced stage without having to check whether we are a CeedStageRef. I.e. stage.get_ref_src().name.

add_gl_to_canvas(screen_width: int, screen_height: int, canvas: kivy.graphics.instructions.Canvas, name: str, quad_mode: str, quad: Optional[int] = None, **kwargs) bool

Should add any stage specific kivy OpenGL instructions that a stage may manually set.

This is called by Ceed when it creates a new experiment to get all the graphics for the stage and substages used during an experiment.

Parameters
Returns

Whether the stage added custom graphics. If True, set_gl_colors() will be called for this stage for every Ceed frame. Otherwise, it is not called. Defaults to returning None.

set_gl_colors(quad: Optional[int] = None, grayscale: Optional[str] = None, clear: bool = False, **kwargs) None

If add_gl_to_canvas() returned True, it is called by Ceed for every time step to allow the stage to update the manually added gl instructions for this frame. In QUAD4X it’s called 4 times per frame, in QUAD121X it’s called 12 times per frame.

Parameters

Warning

For every clock tick, Ceed “ticks” the stage and then updates the graphics based on the values computed during the tick. If the frame is not dropped, the value is then applied to the graphics.

For shapes, Ceed does this automatically, only updating the shapes when the frame is not dropped, for each tick. For these manual gl graphics, the stage should only update the graphics in this method. Normally, every tick is followed by a call to this method to draw. If the frame is dropped, the draw call does not follow the tick. Hence, only draw in this method.

If the stage is pre-computed, then Ceed ticks through the stage before the stage runs. During the stage, tick won’t be called, but this method will still be called so you have to work out the timing prior and apply it during this method.

remove_gl_from_canvas(canvas: kivy.graphics.instructions.Canvas, name: str, **kwargs) None

Should remove all the gl instructions that was added with add_gl_to_canvas().

It is called by Ceed for the root stage and all substages after the experiment is done. It should remove all the instructions added. Instructions added with this name will be automatically removed by remove_shapes_gl_from_canvas() so they don’t have to be removed manually.

Parameters
  • canvas – The Kivy canvas instance to which the gl instructions was added.

  • name – The name associated with these OpenGL instructions.

class ceed.stage.StageShape(stage: Optional[ceed.stage.CeedStage] = None, shape: Optional[Union[ceed.shape.CeedShape, ceed.shape.CeedShapeGroup]] = None, **kwargs)

Bases: kivy._event.EventDispatcher

A wrapper for ceed.shape.CeedShape instances used in CeedStage.add_shape() to wrap a shape or shape group to be used by the stage.

name: str

The ceed.shape.CeedShapeGroup.name or kivy_garden.painter.PaintShape.name of the instance wrapped.

keep_dark: bool

Whether this shape will be black during the whole stage. Instead of it taking the color of the stage, it’ll be kept black.

This is useful when the inside of some shape must be black, e.g. a donut. By setting keep_dark of the inner shape to True, it’ll be black.

stage: ceed.stage.CeedStage = None

The CeedStage this is associated with.

shape: Union[ceed.shape.CeedShape, ceed.shape.CeedShapeGroup] = None

The ceed.shape.CeedShape or ceed.shape.CeedShapeGroup instance being wrapped.

get_config() Dict

(internal) used by the config system to get the config data of the shape.

apply_config(**kwargs) None

(internal) used by the config system to set the config data of the shape.

class ceed.stage.CeedStageRef(stage_factory: ceed.stage.StageFactoryBase, function_factory: ceed.function.FunctionFactoryBase, shape_factory: ceed.shape.CeedPaintCanvasBehavior, stage: Optional[ceed.stage.CeedStage] = None)

Bases: object

A stage that refers to another stage.

This is never manually created, but rather returned by StageFactoryBase.get_stage_ref(). See StageFactoryBase.get_stage_ref() and ceed.stage for details.

display = None

Same as CeedStage.display.

parent_stage: ceed.stage.CeedStage = None

Same as CeedStage.parent_stage.

stage: ceed.stage.CeedStage = None

The reffered to stage.

stage_factory: ceed.stage.StageFactoryBase = None

Same as CeedStage.stage_factory.

shape_factory: ceed.shape.CeedPaintCanvasBehavior = None

Same as CeedStage.shape_factory.

function_factory: ceed.function.FunctionFactoryBase = None

Same as CeedStage.function_factory.

get_ref_src() ceed.stage.CeedStage

See CeedStage.get_ref_src().

ceed.stage.remove_shapes_upon_deletion(stage_factory: ceed.stage.StageFactoryBase, shape_factory: ceed.shape.CeedPaintCanvasBehavior, process_shape_callback) None

Once called, whenever a shape or group of shapes is deleted in the shape_factory, it’ll also remove the shape or shape group from all stages that reference it.

This is automatically called by the Ceed GUI, but should be manually called if manually creating these factories.

Parameters
  • stage_factory – The StageFactoryBase that lists all the stages.

  • shape_factory – The CeedPaintCanvasBehavior that lists all the shapes.

  • process_shape_callback – For each stage in stage_factory that contains the shape, process_shape_callback will be called with 2 parameters: the CeedStage and StageShape instances. The callback may e.g. then hide the shape in the GUI or whatever else it needs.

ceed.stage.register_all_stages(stage_factory: ceed.stage.StageFactoryBase)

Registers all the internal plugins and built-in stages with the StageFactoryBase instance.

It gets and registers all the plugins stages under ceed/stage/plugin using get_plugin_stages(). See that function for how to make your plugin stages discoverable.

Parameters

stage_factory – a StageFactoryBase instance.

ceed.stage.register_external_stages(stage_factory: ceed.stage.StageFactoryBase, package: str)

Registers all the plugin stages in the package with the StageFactoryBase instance.

See get_plugin_stages() for how to make your plugin stages discoverable.

Plugin source code files are copied to the data file when a a data file is created. However, it doesn’t copy all files (i.e. it ignores non-python files) so it should be independently tracked for each experiment.

Parameters
  • stage_factory – A StageFactoryBase instance.

  • package – The name of the package containing the plugins.