Bibliography
+Page under construction 🚧. For now, see bibliography.bib
.
diff --git a/404.html b/404.html index 7dede8b..a040bc5 100644 --- a/404.html +++ b/404.html @@ -331,6 +331,23 @@ + + + + +
Page under construction 🚧. For now, see bibliography.bib
.
Tölvera is a Python library designed for composing together and interacting with basal agencies. +
Tölvera is a Python library designed for composing together and interacting with basal agencies, inspired by fields such as artificial life (ALife) and self-organising systems. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, Tölvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.
The word Tölvera is an Icelandic kenning based on tölva meaning computer, from tala (number) and völva (prophetess), and vera (being), composed together as number being.
diff --git a/publications/index.html b/publications/index.html index 86bcf22..53a41b7 100644 --- a/publications/index.html +++ b/publications/index.html @@ -12,6 +12,8 @@ + + @@ -340,6 +342,23 @@ + + + + +T\u00f6lvera is a Python library designed for composing together and interacting with basal agencies. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, T\u00f6lvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.
The word T\u00f6lvera is an Icelandic kenning based on t\u00f6lva meaning computer, from tala (number) and v\u00f6lva (prophetess), and vera (being), composed together as number being.
We have employed T\u00f6lvera in various collaborative artistic works, including musical performances, compositions, and multimedia installations (see references.bib
for peer-reviewed publications). T\u00f6lvera's role in these pieces has mainly been \"mappable behaviour engine\", where interface inputs can control T\u00f6lvera programs, and T\u00f6lvera runtime data can control interface outputs, in practically any combination. In this way, and to controllable degrees, T\u00f6lvera can contribute to the underlying dynamics of a given interactive scenario. It can also add a visual component, and equally has been used without projection in other works.
T\u00f6lvera makes use of Taichi, a domain-specific language embedded in Python that enables parallelisation, and is experimental software subject to change.
We would be happy to have you join us on our Discord server!
"},{"location":"#showcase-examples","title":"Showcase & Examples","text":"Examples can be found at iil-examples/tolvera. See also the guide, reference and experiments pages.
Visit the YouTube Playlist (if you'd like to add a video, please get in touch).
"},{"location":"#install","title":"Install","text":"Taichi supports numerous operating systems and backends. If you plan on using Vulkan for graphics (recommended for macOS), you may need to install the Vulkan SDK first and restart your machine.
T\u00f6lvera is registered on PyPI and can be installed via a Python package manager such as pip
:
pip install tolvera\n
"},{"location":"#develop","title":"Develop","text":"For development, we use poetry
. Fork/clone this repository and install the package with `poetry:
git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)\ncd tolvera\npoetry install\n
"},{"location":"#known-issues-limitations","title":"Known Issues & Limitations","text":"conda create -n tolvera python=3.11\nconda activate tolvera\n
anguilla
's FAISS dependency, and Mediapipe not supporting Intel Macs).export KMP_DUPLICATE_LIB_OK=TRUE\n
We welcome Pull Requests across all areas of the project:
To discuss T\u00f6lvera with developers and other users:
Across the project, we follow the Berlin Code of Conduct. Please get in touch if you experience or witness any conduct issues.
"},{"location":"#roadmap","title":"Roadmap","text":"See Discussion.
"},{"location":"#citation","title":"Citation","text":"T\u00f6lvera is being written about and used in a number of contexts (see references.bib). The current canonical citation is our NIME 2024 paper:
@inproceedings{armitageTolveraComposingBasal2024,\n title = {T{\\\"o}lvera: {{Composing With Basal Agencies}}},\n booktitle = {Proc. {{New Interfaces}} for {{Musical Expression}}},\n author = {Armitage, Jack and Shepardson, Victor and Magnusson, Thor},\n year = {2024},\n address = {Utrecht, NL},\n}\n
"},{"location":"#inspiration","title":"Inspiration","text":"T\u00f6lvera is developed primarily by Jack Armitage and collaborators at the Intelligent Instruments Lab. Get in touch to collaborate:
\u25e6 iil.is \u25e6 Facebook \u25e6 Instagram \u25e6 X (Twitter) \u25e6 YouTube \u25e6 Discord \u25e6 GitHub \u25e6 LinkedIn \u25e6 Email \u25e6
"},{"location":"#acknowledgements","title":"Acknowledgements","text":"We thank the Taichi community for their wonderful project, that makes T\u00f6lvera possible.
The Intelligent Instruments project (INTENT) is funded by the European Research Council (ERC) under the European Union\u2019s Horizon 2020 research and innovation programme (Grant agreement No. 101001848).
"},{"location":"artworks/","title":"Artworks","text":""},{"location":"artworks/#artworks","title":"Artworks","text":"Page under construction \ud83d\udea7.
"},{"location":"examples/","title":"Examples","text":""},{"location":"examples/#examples","title":"Examples","text":"Page under construction \ud83d\udea7. For now, see iil-examples/tolvera.
"},{"location":"experiments/","title":"Experiments","text":""},{"location":"experiments/#experiments","title":"Experiments","text":"These are ongoing experiments with T\u00f6lvera, Taichi and third-party libraries. You can find more experiments in the examples folder. Most are working examples, but don't be surprised if there is the occasional broken one - please report issues here.
"},{"location":"experiments/#live-coding","title":"Live Coding","text":"T\u00f6lvera can be live coded via Sardine. Note that this only works so far using VSCode's inbuilt Python REPL, and not with the Sardine VSCode extension:
tv.s.params = {'state':{'evap': (ti.f32, 0., 0.99)}}\n\n@swim\ndef gui_loop(p=0.5, i=0):\n tv.px.diffuse(tv.s.params.field[0].evap)\n tv.px.particles(tv.p, tv.s.species())\n tv.show(tv.px)\n again(gui_loop, p=1/64, i=i+1)\n\n@swim\ndef control_loop(p=4, i=0):\n tv.s.params.field[0].evap = P('0.9 0.99 0.1', i)\n again(control_loop, p=1/2, i=i+1)\n
"},{"location":"experiments/#sonification","title":"Sonification","text":"Sonification of and with T\u00f6lvera can be achieved via SignalFlow. See examples here.
"},{"location":"experiments/#gpu-audio","title":"GPU Audio","text":"GPU audio rendering can be achieved using Taichi alone:
from iipyper import Audio, run\nimport sounddevice as sd\nimport taichi as ti\nfrom math import pi\n\n@ti.kernel\ndef generate_data(outdata: ti.ext_arr(), \n frames: ti.i32, \n start_idx: ti.template(), \n fs: ti.f32):\n for I in ti.grouped(ti.ndrange(frames)):\n i = I[0]\n t = (start_idx[None] + i) / fs\n s = ti.sin(2 * pi * 440 * t)\n start_idx[None] += 1\n outdata[i,0] = s\n\ndef main():\n ti.init(arch=ti.vulkan)\n device = sd.default.device\n fs = sd.query_devices(sd.default.device, 'output')['default_samplerate']\n buffer_size = 128\n start_idx = ti.field(ti.i32, ())\n def callback(indata, outdata, frames, time, status):\n if status: print(status, file=sys.stderr)\n generate_data(outdata, frames, start_idx, fs)\n Audio(device=device, channels=1, blocksize=buffer_size,\n samplerate=fs, callback=callback)\n\nif __name__=='__main__':\n run(main)\n
"},{"location":"experiments/#3d-printing","title":"3D Printing","text":"Exploration of 3D printing with T\u00f6lvera can be achieved via FullControl. See example here.
"},{"location":"experiments/#sketchbook","title":"Sketchbook","text":"T\u00f6lvera has an experimental sketchbook built-in. For example, if the following program exists in a folder called mysketch.py
:
from tolvera import Tolvera\n\ndef mysketch(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species)\n return tv.px\n
It can be run with:
python -m tolvera --sketch \"mysketch\"\n
Available commands:
--sketchbook Set a sketchbook folder\n--sketches List available sketches\n--sketch Run a particular sketch (can be a file path or index)\n--random Run a random sketch from the sketchbook\n
"},{"location":"guide/","title":"Guide","text":""},{"location":"guide/#tolvera-v010-api-guide","title":"T\u00f6lvera v0.1.0
API Guide","text":"This guide provides an overview of the T\u00f6lvera v0.1.0
API:
The v0.2.0
API will be different, so everything here is subject to change. Please share your feedback on our Discord.
tv.v
: a collection of \"vera\" (beings) including Move, Flock, Slime and Swarm, with more being continuously added. Vera can be combined and composed in various ways.tv.p
: extensible particle system. Particles are divided into multiple species, where each species has a unique relationship with every other species, including itselftv.s
: n-dimensional state structures that can be used by \"vera\", including built-in OSC and IML creation (see below).tv.px
: drawing library including various shapes and blend modes, styled similarly to p5.js etc.tv.osc
: Open Sound Control (OSC) via iipyper, including automated export of OSC schemas to JSON, XML, Pure Data (Pd), Max/MSP (SuperCollider TBC).tv.iml
: Interactive Machine Learning via anguilla.tv.ti
: Taichi-based simulation and rendering engine. Can be run \"headless\" (without graphics).tv.cv
: computer vision integration based on OpenCV and Mediapipe.This example demonstrates the basic usage of T\u00f6lvera when used as a standalone Python script. It will display a window with a black background:
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Tolvera
class and run()
function from the tolvera
Python package.def main()
function takes in keyword arguments (**kwargs
) from the command line.def main()
, we initialise a Tolvera
instance with the given **kwargs
.@tv.render()
decorator to create the scene and render the pixels.def _()
in the example), and is analagous to loop()
in Arduino/Processing/p5.js and render()
in Bela, in that it will run in a loop until the user exits the program.def _()
must return an instance of Pixels
. Pixels
instance belonging to the Tolvera
instance, accessed with tv.px
.run()
with def main()
as the argument.tv.p
) & Species (tv.s.species
)","text":"A central idea of T\u00f6lvera is the particle as a base unit of activity. The particle system is a field of type Particle
with these properties:
@ti.dataclass\nclass Particle:\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n
Particles are divided into species (represented as an integer), and species can have different relationships with each other, creating a matrix of species-species interactions. This idea was inspired by Particle Life, and provides a simple means to mimic ecological complexity, even when using a single behaviour or model. Species are implemented as multi-dimensional state (see below), which means all tv.v
behaviour models can make use of the multispecies matrix.
tv.s
)","text":"To enable composition of behaviours, T\u00f6lvera features a global state dictionary. State is n-dimensional and can be manipulated in parallel on the GPU. Typical state shapes might be: (species, species)
for multispecies rules, (particles)
for individual particle states, and \\pyi(particles, particles) for pairwise comparison of particles. Assigning a dictionary to a variable after tv.s
causes a new state to be instanced. Here are two examples of state from tv.v.flock
, showing the syntax of \"name\": (type, min, max)
for each attribute, and some of the additional options which includes built-in randomisation:
tv.s.flock_s = { \"state\": \n {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (species, species),\n \"osc\": (\"set\"),\n \"randomise\": True\n}\n\ntv.s.flock_dist = {\n \"state\": {\"dist\": (ti.f32, 0.0, tv.x*2)},\n \"shape\": (particles, particles),\n \"randomise\": False\n}\n
State is useful and interesting to visualise, for example drawing the particle distances that flock
uses reveals hidden structure. State can also be reused and combined with state from other models, to compose even more complex behaviour.
tv.v
)","text":"The image below shows some of the available behaviours and models in T\u00f6lvera. Some of these are inspired directly from open source code posted by the Taichi community - thank you!
Examples of behaviours and models available via `tv.v`. Top row: stateless `tv.v`. Bottom row: stateful `tv.v`.Models can be used by calling them and passing particles to them, for example:
@tv.render\ndef _():\n tv.px.clear()\n tv.v.flock(tv.p)\n tv.v.plife(tv.p)\n tv.px.particles(tv.p, tv.s.species())\n return tv.px\n
In this case, the flock
and plife
(particle life) models are composed together to create a compound behaviour. Models are also designed to be simple and concise internally to encourage users to understand them and make their own. Simple behaviours like move
can be stateless and implemented as single GPU kernels.
@ti.kernel\ndef move(p: ti.template()):\n for i in range(p.field.shape[0]):\n p1 = p.field[i]\n if p1.field[i].active == 0: continue\n p.field[i].pos += p1.vel * p1.speed\n
Models that use state are implemented as classes, that at minimum provide a step()
method, where particles can be compared and states updated:
@ti.data_oriented\nclass MyVera:\n def __init__(self, tolvera, **kwargs):\n self.tv, self.kwargs = tolvera, kwargs\n self.C = CONSTS({...})\n self.tv.s.vera_s = {...} #\u00a0state\n\n @ti.kernel\n def step(self, p):\n for i in range(p.shape[0]):\n for j in range(p.shape[1]):\n # compare p[i] & p[j]\n # update state, etc.\n p[i].pos += ... # update p[i]\n\n def __call__(self, p):\n self.step(p)\n
Step function inside tv.v
:
@ti.kernel\ndef step(self, p: ti.template(), W: ti.f32):\n for i in range(p.shape[0]):\n p1 = p[i]\n if p1[i].active == 0: continue\n for j in range(p.shape[0]):\n p2 = p[j]\n if i == j & p2[j].active == 0: continue\n s = self.tv.s.vera_s[p1.species, p2.species]\n d = p1.pos - p2.pos\n if d < s.radius:\n # p1 & p2 are neighbours\n p[i].vel += W * ...\n p[i].pos += W * ...\n
"},{"location":"guide/#pixels-tvpx","title":"Pixels (tv.px
)","text":"T\u00f6lvera has a drawing module that is similar in design to p5.js. This example draws a rectangle in the middle of the window:
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
It mainly features shape primitives and blend modes. Pixels can also be thought of as fields and used as part of a vera, as tv.v.slime
does to deposit particles onto a pheromone trail. Due to this flexibility, drawing and visualisation can impact behaviour and create more mappings and feedback loops between model states.
tv.ti
)","text":"T\u00f6lvera does most of its work on the GPU thanks to Taichi (tv.ti
). Taichi is a domain-specific language (DSL) embedded in Python that supports multiple backends (CUDA, OpenGL, Vulkan, Metal). Taichi programs are distinguished by three levels of scope: regular Python scope, kernel scope (@ti.kernel
) and function scope (@ti.func
). Python scope can call kernels, and kernels and functions can call functions. In Taichi scope, top-level for
loops are automatically parallelised. It can also run headless (without a window), and provides a C runtime and ahead-of-time (AOT) compilation for deployment in non-Python programs. It also interoperates with NumPy and PyTorch for ML integrations.
tv.osc
)","text":"OSC in T\u00f6lvera is handled by iipyper
, a package specifically designed for working with artificial intelligence. When creating state, OSC endpoints can be automatically added via the \"osc\"
option. For custom OSC endpoints, the tv.osc.map
decorators can be used to add senders and receivers of different varieties. Here are two examples, one of receiving two arguments x
and y
, and another of receiving a vector of length ten:
@tv.osc.map.receive_args(x=(0.,0.,1.), y=(0.,0.,1.), count=5)\ndef my_args(x: float, y: float):\n print(f\"Receiving args: {x} {y}\")\n\n@tv.osc.map.receive_list(vector=(0.,0.,1.), length=10, count=5)\ndef my_list(vector: list[float]):\n print(f\"Received list: {vector}\")\n
The count
decorator argument can be used to rate limit how often an endpoint's Python function runs, relative to the number of frames elapsing. T\u00f6lvera OSC mappings can be exported to JSON and XML, and they can also generate patches for Max/MSP and Pure Data, enabling rapid integration with other software.
tv.iml
)","text":"Interactive machine learning is achieved via the anguilla
Python package. T\u00f6lvera features a global dictionary of IML instances at tv.iml
, similarly to state (tv.s
). IML can be used for a wide range of purposes in T\u00f6lvera, including creating internal feedback loops, for example between tv.v.flock
's position and species rules states:
tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec)\n}\n
The above example uses fun2fun
, meaning the input and output methods specified in the io
option will be run in the background by T\u00f6lvera. These are to_vec
and from_vec
, built-in methods that serialise and deserialise state to/from 1D arrays making them suitable for use as IML input/output vectors. To facilitate automatic routing of IML inputs and outputs, there are nine types in which the input and output can be either a vector, function or OSC endpoint: vec2vec
, vec2fun
, vec2osc
, fun2vec
, fun2fun
, fun2osc
, osc2vec
, osc2fun
, and osc2osc
. Notably, these IML maps can be trained and updated on-the-fly, providing more variation in prolonged usage.
The image below demonstrates how T\u00f6lvera's state and drawing capabilities can be used to enable real-time visualisation of IML mappings.
Interactive machine learning (`tv.iml`) real-time map visualisation for input size two (XY axes) and output size three (pixel RGB). Input-output pairs are shown as white circles. This example demonstrates anguilla's ripple interpolator."},{"location":"guide/#computer-vision-tvcv","title":"Computer Vision (tv.cv
)","text":"T\u00f6lvera integrates with OpenCV and Mediapipe to enable exploration of computer vision and tracking of hands, face and full body pose. See examples.
"},{"location":"guide/#command-line-arguments","title":"Command-line arguments","text":"When T\u00f6lvera is instanced, a global kwargs
dictionary is passed down through the various modules that allows setting of flags and parameters.
python -m tolvera
)","text":"All arguments below can be applied, and in addition:
--demo Run a tv.v.flock() demo.\n--help Print a help message.\n
See also sketchbook
in experiments.
tv.ctx
)","text":"--x Width in pixels. Defaults to 1920.\n--y Height in pixels. Defaults to 1080.\n
"},{"location":"guide/#tolvera-instance-tv","title":"T\u00f6lvera Instance (tv
)","text":"--name Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n--speed Global speed scalar. Defaults to 1.\n--particles Number of particles. Defaults to 1024. Aliased to tv.pn.\n--species Number of species. Defaults to 4. Aliased to tv.sn.\n--substep Number of substeps of render function. Defaults to 1.\n
"},{"location":"guide/#taichi-tvti","title":"Taichi (tv.ti
)","text":"--gpu GPU architecture to run on. Defaults to \"vulkan\".\n--cpu Run on CPU instead of GPU. Defaults to False.\n--fps Frames per second. Defaults to 120.\n--seed Random seed. Defaults to int(time.time()).\n--headless False\n--name Instance name. Defaults to \"T\u00f6lvera\".\n
"},{"location":"guide/#pixels-tvpx_1","title":"Pixels (tv.px
)","text":"--polygon_mode Polygon drawing mode. Defaults to 'crossing'.\n--brightness Brightness scalar. Defaults to 1.\n
"},{"location":"guide/#open-sound-control-tvosc_1","title":"Open Sound Control (tv.osc
)","text":"--osc Enable OSC. Defaults to False.\n--host OSC Host IP. Defaults to \"127.0.0.1\".\n--client OSC Client IP. Defaults to \"127.0.0.1\".\n--client_name OSC client name. Defaults to self.ctx.name_clean.\n--receive_port OSC host port. Defaults to 5001.\n--send_port OSC client port. Defaults to 5000.\n--osc_trace Print all OSC messages. Defaults to False.\n--osc_verbose Verbose printing of iipyper. Defaults to False.\n--create_patch Create a Max or Pd patch based on tv.osc.map. Defaults to False.\n--patch_type Type of patch to create. Defaults to \"Pd\".\n--patch_filepath Filepath of patch. Defaults to self.client_name.\n--export_patch Export patch schema to XML, JSON or both. Defaults to None.\n
"},{"location":"guide/#interactive-machine-learning-tviml_1","title":"Interactive Machine Learning (tv.iml
)","text":"--update_rate Rate of IML updates relative to FPS. Default to 10.\n--config anguilla instance configuration. Default to {}.\n--map_kw anguilla.map kwargs. Default to {}.\n--infun_kw Input method kwargs. Default to {}.\n--outfun_kw Output method kwargs. Default to {}.\n--randomise IML randomisation. Default to False.\n--rand_pairs IML randomisation. Default to 32.\n--rand_input_weight IML input randomisation weight. Default to None.\n--rand_output_weight IML output randomisation weight. Default to None.\n--rand_method IML randomisation method. Default to \"rand\".\n--rand_kw IML randomisation kwargs. Default to {}.\n--lag Lag value updates. Default to False.\n--lag_coef Lag coefficient. Default to 0.5.\n--name Name of IML instance. Default to None.\n
"},{"location":"guide/#computer-vision-tvcv_1","title":"Computer Vision (tv.cv
)","text":"--camera Enable camera. Defaults to False.\n--device OpenCV device index. Defaults to 0.\n--substeps Number of substeps for reading camera frames. Defaults to 2.\n--colormode Color channels. Defaults to 'rgba'.\n--ggui_fps_limit FPS limit of Taichi GGUI. Defaults to 120fps.\n--hands Enable hand tracking. Defaults to False.\n--pose Enable pose tracking. Defaults to False.\n--face Enable face tracking. Defaults to False.\n--face_mesh Enable face mesh tracking. Defaults to False.\n
"},{"location":"publications/","title":"Publications","text":""},{"location":"publications/#publications","title":"Publications","text":"Page under construction \ud83d\udea7. For now, see references.bib
.
TolveraContext
is a shared context or environment for Tolvera
instances. It is created automatically when a Tolvera
instance is created, if one does not already exist. It manages the integration of packages for graphics, computer vision, communications protocols, and more. If multiple Tolvera
instances are created, they must share the same TolveraContext
.
TolveraContext
can be created manually, and shared with multiple Tolvera
instances. Note that only one render
function can be used at a time.
from tolvera import TolveraContext, Tolvera, run\n\ndef main(**kwargs):\n ctx = TolveraContext(**kwargs)\n\n tv1 = Tolvera(ctx=ctx, **kwargs)\n tv2 = Tolvera(ctx=ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
Example TolveraContext
can also be created automatically, and still shared.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv1 = Tolvera(**kwargs)\n tv2 = Tolvera(ctx=tv1.ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext","title":"TolveraContext
","text":"Context for sharing between multiple T\u00f6lvera instances. Context includes Taichi, OSC, IML and CV. All T\u00f6lvera instances share the same context and are managed as a dict.
Attributes:
Name Type Descriptionkwargs
dict
Keyword arguments for context.
name
str
Name of context.
name_clean
str
'Cleaned' name of context.
i
int
Frame counter.
x
int
Width of canvas.
y
int
Height of canvas.
ti
Taichi
Taichi instance.
canvas
Pixels
Pixels instance.
osc
OSC
OSC instance.
iml
IML
IML instance.
cv
CV
CV instance.
_cleanup_fns
list
List of cleanup functions.
tolveras
dict
Dict of T\u00f6lvera instances.
Source code insrc/tolvera/context.py
class TolveraContext:\n \"\"\"\n Context for sharing between multiple T\u00f6lvera instances.\n Context includes Taichi, OSC, IML and CV.\n All T\u00f6lvera instances share the same context and are managed as a dict.\n\n Attributes:\n kwargs (dict): Keyword arguments for context.\n name (str): Name of context.\n name_clean (str): 'Cleaned' name of context.\n i (int): Frame counter.\n x (int): Width of canvas.\n y (int): Height of canvas.\n ti (Taichi): Taichi instance.\n canvas (Pixels): Pixels instance.\n osc (OSC): OSC instance.\n iml (IML): IML instance.\n cv (CV): CV instance.\n _cleanup_fns (list): List of cleanup functions.\n tolveras (dict): Dict of T\u00f6lvera instances.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n\n def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.i = ti.field(ti.i32, ())\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n self.pose = kwargs.get(\"pose\", False)\n self.face = kwargs.get(\"face\", False)\n self.face_mesh = kwargs.get(\"face_mesh\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n if self.hands:\n self.hands = MPHands(self, **kwargs)\n if self.pose:\n self.pose = MPPose(self, **kwargs)\n if self.face:\n self.face = MPFace(self, **kwargs)\n if self.face_mesh:\n self.face_mesh = MPFaceMesh(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n\n def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n self.step(f, **kwargs)\n\n def step(self, f, **kwargs):\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i[None] += 1\n\n def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n\n def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n\n def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n\n def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n\n def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n\n def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n\n def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.__init__","title":"__init__(**kwargs)
","text":"Initialise T\u00f6lvera context with given keyword arguments.
Source code insrc/tolvera/context.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.add","title":"add(tolvera)
","text":"Add T\u00f6lvera to context.
Parameters:
Name Type Description Defaulttolvera
Tolvera
T\u00f6lvera to add.
required Source code insrc/tolvera/context.py
def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.cleanup","title":"cleanup(f=None)
","text":"Decorator for cleanup functions based on iipyper. Make functions run on KeyBoardInterrupt (before exit). Cleanup functions must be defined before render is called!
Parameters:
Name Type Description Defaultf
Function to cleanup.
None
Returns:
Type DescriptionDecorator function if f is None, else decorated function.
Source code insrc/tolvera/context.py
def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_by_name","title":"get_by_name(name)
","text":"Get T\u00f6lvera by name.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera to get.
requiredReturns:
Name Type DescriptionT\u00f6lvera
T\u00f6lvera with given name.
Source code insrc/tolvera/context.py
def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_names","title":"get_names()
","text":"Get names of all T\u00f6lveras in context.
Returns:
Name Type Descriptionlist
List of T\u00f6lvera names.
Source code insrc/tolvera/context.py
def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.init","title":"init(**kwargs)
","text":"Initialise wrapped external packages with given keyword arguments. This only happens once when T\u00f6lvera is first initialised.
Parameters:
Name Type Description Defaultx
int
Width of canvas. Default: 1920.
requiredy
int
Height of canvas. Default: 1080.
requiredosc
bool
Enable OSC. Default: False.
requirediml
bool
Enable IML. Default: False.
requiredcv
bool
Enable CV. Default: False.
required Source code insrc/tolvera/context.py
def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.i = ti.field(ti.i32, ())\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n self.pose = kwargs.get(\"pose\", False)\n self.face = kwargs.get(\"face\", False)\n self.face_mesh = kwargs.get(\"face_mesh\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n if self.hands:\n self.hands = MPHands(self, **kwargs)\n if self.pose:\n self.pose = MPPose(self, **kwargs)\n if self.face:\n self.face = MPFace(self, **kwargs)\n if self.face_mesh:\n self.face_mesh = MPFaceMesh(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.remove","title":"remove(name)
","text":"Remove T\u00f6lvera by name.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera to delete.
required Source code insrc/tolvera/context.py
def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.render","title":"render(f=None, **kwargs)
","text":"Render T\u00f6lvera with given function and keyword arguments.
Parameters:
Name Type Description Defaultf
function
Function to run. Defaults to None.
None
Source code in src/tolvera/context.py
def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.run","title":"run(f=None, **kwargs)
","text":"Run T\u00f6lvera with given render function and keyword arguments. This function will run inside a locked thread until KeyboardInterrupt/exit. It runs the render function, updates the OSC map (if enabled), and shows the pixels.
Parameters:
Name Type Description Defaultf
Function to run.
None
**kwargs
Keyword arguments for function.
{}
Source code in src/tolvera/context.py
def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n self.step(f, **kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.stop","title":"stop()
","text":"Run cleanup functions and exit.
Source code insrc/tolvera/context.py
def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n
"},{"location":"reference/tolvera/cv/","title":"Cv","text":""},{"location":"reference/tolvera/iml/","title":"Iml","text":"IML stands for Interactive Machine Learning. T\u00f6lvera wraps the anguilla package to provide convenient ways for quickly creating mappings between vectors, functions and OSC routes.
Every T\u00f6lvera instance has an IMLDict, which is a dictionary of IML instances. The IMLDict is accessible via the iml
attribute of a T\u00f6lvera instance, and can be used to create and access IML instances.
There are 9 IML types, which are listed below.
ExampleHere we create a mapping based on states created by tv.v.flock
, where the per-particle state flock_p
is mapped to the species rule matrix flock_s
. Since this is a fun2fun
mapping (see IML Types below), we provide input and output functions, and T\u00f6lvera updates the mapping automatically every render()
call.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec),\n 'randomise': True,\n }\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species, 'circle')\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
IML Types vec2vec
: Vector to vector mapping.vec2fun
: Vector to function mapping.vec2osc
: Vector to OSC mapping.fun2vec
: Function to vector mapping.fun2fun
: Function to function mapping.fun2osc
: Function to OSC mapping.osc2vec
: OSC to vector mapping.osc2fun
: OSC to function mapping.osc2osc
: OSC to OSC mapping.IMLBase
","text":" Bases: IML
This class inherits from anguilla and adds some functionality. It is not intended to be used directly, but rather to be inherited from.
The base class is initialised with a size tuple (input, output) and a config dict which is passed to anguilla.IML
.
It provides a randomise
method which adds random pairs to the mapping. It also provides methods to remove pairs (remove_oldest
, remove_newest
, remove_random
). It also provides a lag
method which lags the mapped data. Finally, it provides an update
method which is called by the updater
(see .osc.update
).
src/tolvera/iml.py
class IMLBase(iiIML):\n \"\"\"\n This class inherits from [anguilla](https://intelligent-instruments-lab.github.io/anguilla) \n and adds some functionality. It is not intended to be used directly, but rather \n to be inherited from.\n\n The base class is initialised with a size tuple (input, output) and a config dict\n which is passed to `anguilla.IML`.\n\n It provides a `randomise` method which adds random pairs to the mapping.\n It also provides methods to remove pairs (`remove_oldest`, `remove_newest`, `remove_random`).\n It also provides a `lag` method which lags the mapped data.\n Finally, it provides an `update` method which is called by the `updater` (see `.osc.update`).\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"embed_input\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n\n def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n\n def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n\n def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n ):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n\n def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n\n def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n\n def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n\n def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n\n def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n\n def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n\n def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n\n def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n\n def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n\n def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n\n def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__call__","title":"__call__(*args)
","text":"Call updater with args.
Parameters:
Name Type Description Default*args
Updater args.
()
Returns:
Name Type DescriptionAny
Any
Mapped data.
Source code insrc/tolvera/iml.py
def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLBase
kwargssize (tuple, required): (input, output) sizes. io (tuple, optional): (input, output) functions. config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}. updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...). update_rate (int, optional): Updater's update rate (defaults to 1). randomise (bool, optional): Randomise mapping on init (defaults to False). rand_pairs (int, optional): Number of random pairs to add (defaults to 32). rand_input_weight (Any, optional): Random input weight (defaults to None). rand_output_weight (Any, optional): Random output weight (defaults to None). rand_method (str, optional): rand_method type (see utils). rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils). map_kw (dict, optional): kwargs to use in IML.map(). infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2' only). outfun_kw (dict, optional): kwargs to use in outfun() (type '2Fun' only). lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types. lag_coef (float, optional): Lag coefficient (defaults to 0.5 if lag
is True).
src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"embed_input\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.add_random_pair","title":"add_random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Add random pair to mapping.
Parameters:
Name Type Description Defaultinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
see random_pair kwargs.
{}
Source code in src/tolvera/iml.py
def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_lag","title":"init_lag(**kwargs)
","text":"Initialise lag() method with kwargs
kwargs: see init kwargs.
Source code insrc/tolvera/iml.py
def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_randomise","title":"init_randomise(**kwargs)
","text":"Initialise randomise() method with kwargs
kwargs: see init kwargs.
Source code insrc/tolvera/iml.py
def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.lag_mapped_data","title":"lag_mapped_data(lag_coef=0.5)
","text":"Lag mapped data.
Parameters:
Name Type Description Defaultlag_coef
float
Lag coefficient. Defaults to 0.5.
0.5
Source code in src/tolvera/iml.py
def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_input","title":"random_input(**kwargs)
","text":"Random input vector.
Parameters:
Name Type Description Default**kwargs
self.rand kwargs.
{}
Returns:
Type DescriptionTensor
torch.Tensor: Random input vector.
Source code insrc/tolvera/iml.py
def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_output","title":"random_output(**kwargs)
","text":"Random output vector.
Parameters:
Name Type Description Default**kwargs
self.rand kwargs
{}
Returns:
Type DescriptionTensor
torch.Tensor: Random output vector.
Source code insrc/tolvera/iml.py
def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_pair","title":"random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Create random pair.
Parameters:
Name Type Description Defaultinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
rand_method (str, optional): Randomisation method. Defaults to \"rand\". rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).
{}
Raises:
Type DescriptionValueError
Invalid input_weight type.
ValueError
Invalid output_weight type.
Returns:
Name Type Descriptiontuple
(input, output) vectors.
Source code insrc/tolvera/iml.py
def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.randomise","title":"randomise(times, input_weight=None, output_weight=None, method='rand', **kwargs)
","text":"Randomise mapping.
Parameters:
Name Type Description Defaulttimes
int
Number of random pairs to add.
requiredinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_newest","title":"remove_newest(n=1)
","text":"Remove newest pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_oldest","title":"remove_oldest(n=1)
","text":"Remove oldest pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_random","title":"remove_random(n=1)
","text":"Remove random pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.set_random_method","title":"set_random_method(method='rand')
","text":"Set random method.
Parameters:
Name Type Description Defaultmethod
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultinvec
list | Tensor | ndarray
Input vector.
requiredReturns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update_rate","title":"update_rate(rate=None)
","text":"Update rate getter/setter.
Parameters:
Name Type Description Defaultrate
int
Update rate. Defaults to None.
None
Returns:
Name Type Descriptionint
Update rate.
Source code insrc/tolvera/iml.py
def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict","title":"IMLDict
","text":" Bases: dotdict
IML mapping dict
Similarly to StateDict
, this class inherits from dotdict
to enable instantiation via assignment.
src/tolvera/iml.py
class IMLDict(dotdict):\n \"\"\"IML mapping dict\n\n Similarly to `StateDict`, this class inherits from `dotdict` to enable instantiation\n via assignment.\n \"\"\"\n\n def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n\n def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n\n def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n\n def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__call__","title":"__call__(name=None, *args, **kwargs)
","text":"Call IML instance or all IML instances.
Parameters:
Name Type Description Defaultname
str
Name of IML instance to call. Defaults to None.
None
Raises:
Type DescriptionValueError
'name' not in dict.
Returns:
Name Type DescriptionAny
Any
IML output or dict of IML outputs.
Source code insrc/tolvera/iml.py
def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__init__","title":"__init__(context)
","text":"Initialise IMLDict
Parameters:
Name Type Description Defaultcontext
TolveraContext
TolveraContext instance.
required Source code insrc/tolvera/iml.py
def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set IML instance.
Parameters:
Name Type Description Default__name
str
Name of IML instance.
required__value
Any
IML instance kwargs.
required Source code insrc/tolvera/iml.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.add","title":"add(name, iml_type, **kwargs)
","text":"Add IML instance.
Parameters:
Name Type Description Defaultname
str
Name of IML instance.
requirediml_type
str
IML type.
requiredRaises:
Type DescriptionValueError
Invalid IML_TYPE.
Returns:
Name Type DescriptionAny
Any
IML instance.
Source code insrc/tolvera/iml.py
def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.set","title":"set(name, kwargs)
","text":"Set IML instance.
Parameters:
Name Type Description Defaultname
str
Name of IML instance.
requiredkwargs
dict
IML instance kwargs.
requiredRaises:
Type DescriptionValueError
Cannot replace 'tv' IML instance.
ValueError
Cannot replace 'i' IML instance.
ValueError
Cannot replace 'o' IML instance.
NotImplementedError
set() with tuple not implemented yet.
TypeError
set() requires dict|tuple, not type.
Exception
Other exceptions.
Returns:
Name Type DescriptionAny
Any
IML instance.
Source code insrc/tolvera/iml.py
def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun","title":"IMLFun2Fun
","text":" Bases: IMLBase
IML function to function mapping.
Exampledef infun():\n return [0,0,0,0]\n\ndef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2Fun(IMLBase):\n \"\"\"IML function to function mapping.\n\n Example:\n ```py\n def infun():\n return [0,0,0,0]\n\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Fun
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (callable, callable) input and output functions. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC","title":"IMLFun2OSC
","text":" Bases: IMLBase
IML function to OSC mapping
ExampleThis will send the output vector to '/out/vec'.
def infun():\n return [0,0,0,0]\n\ntv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2OSC(IMLBase):\n \"\"\"IML function to OSC mapping\n\n Example:\n This will send the output vector to '/out/vec'.\n\n ```py\n def infun():\n return [0,0,0,0]\n\n tv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLFun2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (callable, str) input function and output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec","title":"IMLFun2Vec
","text":" Bases: IMLBase
IML function to vector mapping.
Output vector is accessed via tv.iml.o['name']
.
tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLFun2Vec(IMLBase):\n \"\"\"IML function to vector mapping.\n\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Vec
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (callable, None) input function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun","title":"IMLOSC2Fun
","text":" Bases: IMLBase
IML OSC to function mapping
Exampledef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2Fun(IMLBase):\n \"\"\"IML OSC to function mapping\n\n Example:\n ```py\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n }\n ```\n \"\"\"\n def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLOSC2Fun
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (str, callable) input OSC route and output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC","title":"IMLOSC2OSC
","text":" Bases: IMLBase
IML OSC to OSC mapping
Example'/in/vec' is mapped and the output sent to '/out/vec'.
tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2OSC(IMLBase):\n \"\"\"IML OSC to OSC mapping\n\n Example:\n '/in/vec' is mapped and the output sent to '/out/vec'.\n\n ```py\n tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.__init__","title":"__init__(osc_map, osc, **kwargs)
","text":"Initialise IMLOSC2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredosc
OSC
iipyper OSC instance.
requiredkwargs
io (tuple, required): (str, str) input and output OSC routes. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec","title":"IMLOSC2Vec
","text":" Bases: IMLBase
IML OSC to vector mapping
ExampleThis will map the OSC input to the output vector and store it in tv.iml.o['name']
.
tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLOSC2Vec(IMLBase):\n \"\"\"IML OSC to vector mapping\n\n Example:\n This will map the OSC input to the output vector and store it in `tv.iml.o['name']`.\n\n ```py\n tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.__init__","title":"__init__(osc_map, outvecs, name, **kwargs)
","text":"Initialise IMLOSC2Vec
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredoutvecs
dict
Output vectors dict.
requiredname
str
Name of output vector.
requiredkwargs
io (tuple, required): (str, None) input OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun","title":"IMLVec2Fun
","text":" Bases: IMLBase
IML vector to function mapping
Exampledef update(outvec):\n print('outvec', outvec)\n\ntv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2Fun(IMLBase):\n \"\"\"IML vector to function mapping\n\n Example:\n ```py\n def update(outvec):\n print('outvec', outvec)\n\n tv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Fun
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (None, callable) output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultinvec
list | Tensor | ndarray
Input vector.
requiredReturns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC","title":"IMLVec2OSC
","text":" Bases: IMLBase
IML vector to OSC mapping.
ExampleSends the output vector to '/tolvera/flock'.
tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2OSC(IMLBase):\n \"\"\"IML vector to OSC mapping.\n\n Example:\n Sends the output vector to '/tolvera/flock'.\n\n ```py\n tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLVec2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (None, str) output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec","title":"IMLVec2Vec
","text":" Bases: IMLBase
IML vector to vector mapping.
Input vector is accessed via tv.iml.i['name']
. Output vector is accessed via tv.iml.o['name']
.
tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n}\n\ndef update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n
Parameters:
Name Type Description Defaultkwargs
see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
class IMLVec2Vec(IMLBase):\n \"\"\"IML vector to vector mapping.\n\n Input vector is accessed via `tv.iml.i['name']`.\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n }\n\n def update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n ```\n\n Args:\n kwargs:\n see IMLBase kwargs.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Vec
Source code insrc/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.rand_select","title":"rand_select(method='rand')
","text":"Select randomisation method.
Parameters:
Name Type Description Defaultmethod
str
Randomisation method. Defaults to \"rand\".
'rand'
Raises:
Type DescriptionValueError
Invalid method.
Returns:
Name Type Descriptioncallable
Randomisation method.
Source code insrc/tolvera/iml.py
def rand_select(method=\"rand\"):\n \"\"\"Select randomisation method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n\n Raises:\n ValueError: Invalid method.\n\n Returns:\n callable: Randomisation method.\n \"\"\"\n match method:\n case \"rand\":\n return rand_n\n case \"uniform\":\n return rand_uniform\n case \"normal\":\n return rand_normal\n case \"exponential\":\n return rand_exponential\n case \"cauchy\":\n return rand_cauchy\n case \"lognormal\":\n return rand_lognormal\n case \"sigmoid\":\n return rand_sigmoid\n case \"beta\":\n return rand_beta\n case _:\n raise ValueError(\n f\"[tolvera._iml.rand_select] Invalid method '{method}'. Valid methods: {RAND_METHODS}.\"\n )\n
"},{"location":"reference/tolvera/npndarray_dict/","title":"Npndarray dict","text":"Module for working with dictionary of NumPy ndarrays.
Primaril used by State.
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict","title":"NpNdarrayDict
","text":"A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range. It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.
Attributes:
Name Type Descriptiondata
Dict[str, Dict[str, Union[ndarray, Any]]]
A dictionary where each key represents an attribute,
shape
Tuple[int, int]
The shape of the ndarrays, which is consistent across all attributes.
Examplestate = NpNdarrayDict({ 'i': (np.int32, 2, 10), 'f': (np.float32, 0., 1.), 'v2': (np_vec2, 0., 1.), 'v3': (np_vec3, 0., 1.), 'v4': (np_vec4, 0., 1.), }, (2,2)) state.set_value('i', (0, 0), 5) print(state.get_value('i', (0, 0))) 5
Source code insrc/tolvera/npndarray_dict.py
class NpNdarrayDict:\n \"\"\"\n A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range.\n It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.\n\n Attributes:\n data (Dict[str, Dict[str, Union[np.ndarray, Any]]]): A dictionary where each key represents an attribute,\n and the value is another dictionary with keys 'array', 'min', and 'max', representing the ndarray,\n its minimum value, and its maximum value, respectively.\n shape (Tuple[int, int]): The shape of the ndarrays, which is consistent across all attributes.\n\n Example:\n state = NpNdarrayDict({\n 'i': (np.int32, 2, 10),\n 'f': (np.float32, 0., 1.),\n 'v2': (np_vec2, 0., 1.),\n 'v3': (np_vec3, 0., 1.),\n 'v4': (np_vec4, 0., 1.),\n }, (2,2))\n state.set_value('i', (0, 0), 5)\n print(state.get_value('i', (0, 0)))\n 5\n \"\"\"\n\n def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n\n def init(\n self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]\n ) -> None:\n self.dict = {}\n self.data = {}\n self.size = 0\n for key, (dtype, min_val, max_val) in data_dict.items():\n dshape = self.shape\n length = 1\n # handle np_vec2, np_vec3, np_vec4\n if isinstance(dtype, np.ndarray):\n dshape = dshape + dtype.shape\n length = dtype.shape[0]\n dtype = np.float32\n self.dict[key] = {\n \"dtype\": dtype,\n \"min\": min_val,\n \"max\": max_val,\n \"length\": length,\n \"shape\": dshape,\n \"ndims\": len(dshape),\n }\n self.data[key] = np.zeros(dshape, dtype=dtype)\n size = self.data[key].size\n self.dict[key][\"size\"] = size\n self.size += size\n\n \"\"\"\n to|from vec | list (iml)\n \"\"\"\n\n def from_vec(self, vec: list):\n vec_start = 0\n for key in self.data.keys():\n attr_vec_size = self.dict[key][\"size\"]\n attr_vec = vec[vec_start : vec_start + attr_vec_size]\n self.attr_from_vec(key, attr_vec)\n vec_start += attr_vec_size\n\n def to_vec(self) -> list:\n vec = []\n for key in self.data.keys():\n vec += self.attr_to_vec(key).tolist()\n return vec\n\n def attr_from_vec(self, attr: str, vec: list):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n if len(vec) != np.prod(attr_shape):\n raise ValueError(\n f\"Length of vec {len(vec)} does not match the shape of {attr} {attr_shape}\"\n )\n nparr = np.array(vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting {attr}: {e}\")\n raise\n\n def attr_to_vec(self, attr: str) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n vec = self.data[attr].flatten()\n return vec\n\n def slice_from_vec(\n self, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n # TODO: unique slice obj needed per key...\n # slice_obj = create_safe_slice(slice_args)\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def slice_to_vec(self, slice_args: Union[int, tuple[int, ...], slice]) -> list:\n # TODO: unique slice obj needed per key...\n # vec = []\n # for key in self.data.keys():\n # slice_obj = create_safe_slice(slice_args)\n # vec += self.attr_slice_to_vec(key, slice_obj)\n # return vec\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def attr_slice_from_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n nparr = np.array(slice_vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr][slice_obj] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting slice: {e}\")\n raise\n\n def attr_slice_to_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n vec = self.data[attr][slice_obj].flatten()\n return vec\n\n \"\"\"\n vec slice helpers\n \"\"\"\n\n def get_slice_size(self, slice_args: Union[int, tuple[int, ...], slice]) -> int:\n slice_obj = create_safe_slice(slice_args)\n return np.sum([self.data[key][slice_obj].size for key in self.data.keys()])\n\n def get_attr_slice_size(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> int:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n return self.data[attr][slice_obj].size\n\n \"\"\"\n to|from vec_args (simple osc)\n \"\"\"\n\n \"\"\"\n to|from ndarray | ndarraydict (serialised formats, complex osc)\n \"\"\"\n\n \"\"\"\n ...\n \"\"\"\n\n def set_slice_from_dict(self, slice_indices: tuple, slice_values: dict):\n for key, values in slice_values.items():\n if key not in self.data:\n raise KeyError(f\"Key {key} not found in data\")\n\n array_slice = self.data[key][slice_indices]\n if array_slice.shape != np.array(values).shape:\n raise ValueError(\n f\"Shape {array_slice.shape} of values for key {key} does not match the shape of the slice {np.array(values).shape}\"\n )\n\n self.data[key][slice_indices] = np.array(\n values, dtype=self.dict[key][\"dtype\"]\n )\n\n # def list_to_dict(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary.\n\n # :param _list: The flat list to convert.\n # :return: A dictionary that matches self.dict.\n # \"\"\"\n # pass\n\n # def list_len_to_dict_shape(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary of shapes.\n\n # :param _list: The flat list to convert.\n # :return: shape of the dictionary of _list based on self.shape.\n # \"\"\"\n # list_len = len(_list)\n # dict_shape = ()\n # for key in self.data.keys():\n # dict_shape += self.dict[key]['shape'][1:]\n # dict_len = np.prod(dict_shape)\n # if list_len != dict_len:\n # raise ValueError(f\"Length of list {_list} does not match the length of the dictionary {dict_len}\")\n # return dict_shape\n\n def set_slice_from_list(self, slice_indices: tuple, slice_values_list: list):\n list_index = 0\n\n for key in self.data.keys():\n # Determine the total number of elements required for the current key\n num_elements = np.prod(self.dict[key][\"shape\"][1:])\n print(f\"[{key}] num_elements: {num_elements}\")\n\n # Extract the slice from slice_values_list and reshape if necessary\n slice_shape = self.dict[key][\"shape\"][1:]\n slice = slice_values_list[list_index : list_index + num_elements]\n print(f\"[{key}] slice_shape: {slice_shape}, slice: {slice}\")\n\n # Check if the slice has the correct length\n if len(slice) != num_elements:\n raise ValueError(\n f\"Slice length {len(slice)} for key {key} does not match the number of elements {num_elements}\"\n )\n\n # Reshape the slice for ndarrays with more than 2 dimensions\n if len(slice_shape) > 1:\n slice = np.reshape(slice, slice_shape)\n print(f\"[{key}] (reshaping) slice_shape: {slice_shape}, slice: {slice}\")\n\n # Assign the slice to the corresponding key\n self.data[key][slice_indices] = slice\n\n list_index += num_elements\n print(f\"[{key}] list_index: {list_index}, num_elements: {num_elements}\")\n\n print(f\"data: {self.data}\")\n\n # Check if there are extra values in slice_values_list\n if list_index != len(slice_values_list):\n raise ValueError(\n f\"Extra values {slice_values_list[list_index:]} in slice_values_list {slice_values_list} that do not correspond to any array\"\n )\n\n def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n\n def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n\n def validate(self, new_state: dict[str, np.ndarray]) -> bool:\n raise NotImplementedError(\"validate() not implemented\")\n\n def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n # Add more conditions here if you have other data types\n\n def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n\n def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n ) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.__init__","title":"__init__(data_dict, shape)
","text":"Initialize the State class.
Parameters:
Name Type Description Defaultdata_dict
dict[str, tuple[Any, Any, Any]]
A dictionary where keys are attribute names and values are tuples of (dtype, min_value, max_value).
requiredshape
tuple[int]
The shape of the numpy arrays for each attribute.
required Source code insrc/tolvera/npndarray_dict.py
def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_apply","title":"attr_apply(key, func)
","text":"Apply a user-defined function to the array of a specified key.
Parameters:
Name Type Description Defaultkey
str
The attribute key.
requiredfunc
Callable[[ndarray], ndarray]
A function that takes a numpy array and returns a numpy array.
requiredRaises:
Type DescriptionKeyError
If the key is not found.
Source code insrc/tolvera/npndarray_dict.py
def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_broadcast","title":"attr_broadcast(key, other, op)
","text":"Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.
Parameters:
Name Type Description Defaultkey
str
The key of the array in the dictionary to operate on.
requiredother
Union[ndarray, NpNdarrayDict]
The other array or NpNdarrayDict to use in the operation.
requiredop
Callable[[ndarray, ndarray], ndarray]
A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).
requiredRaises:
Type DescriptionKeyError
If the key is not found in the dictionary.
ValueError
If the operation cannot be broadcasted or if it violates the min-max constraints.
Source code insrc/tolvera/npndarray_dict.py
def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.get_data","title":"get_data()
","text":"Get the entire current data as a dictionary.
Returns:
Type Descriptiondict[str, ndarray]
A dictionary where each key is an attribute and the value is a numpy array.
Source code insrc/tolvera/npndarray_dict.py
def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.randomise","title":"randomise()
","text":"Randomize the entire state dictionary based on the datatype, minimum, and maximum values for each attribute.
Source code insrc/tolvera/npndarray_dict.py
def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.set_data","title":"set_data(new_data)
","text":"Set the data with a new data dictionary.
Parameters:
Name Type Description Defaultnew_data
dict[str, ndarray]
A dictionary representing the new data, where each key is an attribute and the value is a numpy array.
requiredRaises:
Type DescriptionValueError
If the new data is invalid (e.g., wrong shape, type, or value range).
Source code insrc/tolvera/npndarray_dict.py
def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_from_vector_args","title":"dict_from_vector_args(a, scalars=None)
","text":"Convert a list of arguments to a dictionary.
Args: - a: A list of arguments. - scalars: A list of keys that should be unwrapped from lists.
Returns: - A dictionary of keyword arguments.
Source code insrc/tolvera/npndarray_dict.py
def dict_from_vector_args(a: list, scalars=None):\n \"\"\"Convert a list of arguments to a dictionary.\n\n Args:\n - a: A list of arguments.\n - scalars: A list of keys that should be unwrapped from lists.\n\n Returns:\n - A dictionary of keyword arguments.\n \"\"\"\n a = list(a)\n kw = defaultdict(list)\n k = None\n while len(a):\n item = a.pop(0)\n if isinstance(item, str):\n k = item\n else:\n if k is None:\n print(f\"ERROR: bad syntax in {a}\")\n kw[k].append(item)\n # unwrap scalars\n for item in scalars or []:\n if item in kw:\n kw[item] = kw[item][0]\n return kw\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_to_vector_args","title":"dict_to_vector_args(kw)
","text":"Convert a dictionary to a list of arguments.
This function takes a dictionary and returns a list of arguments.
Args: - kw: A dictionary of keyword arguments.
Returns: - A list of arguments.
Source code insrc/tolvera/npndarray_dict.py
def dict_to_vector_args(kw):\n \"\"\"Convert a dictionary to a list of arguments.\n\n This function takes a dictionary and returns a list of arguments.\n\n Args:\n - kw: A dictionary of keyword arguments.\n\n Returns:\n - A list of arguments.\n \"\"\"\n args = []\n for key, value in kw.items():\n args.append(key)\n if isinstance(value, (list, np.ndarray)):\n # If it's a numpy array (regardless of its shape), flatten it and extend the list\n if isinstance(value, np.ndarray):\n value = value.flatten()\n args.extend(value)\n else:\n # Append the scalar value associated with the key\n args.append(value)\n return args\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.ndarraydict_from_vector_args","title":"ndarraydict_from_vector_args(lst, shapes)
","text":"Convert a list to a dictionary where each list is turned into a numpy array.
This function takes a list in the format output by dict_from_vector_args
and converts it into a dictionary. Each key's list of values is converted into a numpy array with a specified shape.
Args: - lst: The list to be converted. - shapes: A dictionary where keys correspond to the keys in the original list and values are tuples representing the desired shape of the numpy array.
Returns: - A dictionary with keys mapped to numpy arrays.
Source code insrc/tolvera/npndarray_dict.py
def ndarraydict_from_vector_args(lst, shapes):\n \"\"\"Convert a list to a dictionary where each list is turned into a numpy array.\n\n This function takes a list in the format output by `dict_from_vector_args` and converts it\n into a dictionary. Each key's list of values is converted into a numpy array with a\n specified shape.\n\n Args:\n - lst: The list to be converted.\n - shapes: A dictionary where keys correspond to the keys in the original list and\n values are tuples representing the desired shape of the numpy array.\n\n Returns:\n - A dictionary with keys mapped to numpy arrays.\n \"\"\"\n\n def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n # Flatten only if all elements are lists\n return [item for sublist in lst for item in sublist]\n return lst\n\n kw = defaultdict(list)\n k = None\n for item in lst:\n if isinstance(item, str):\n k = item\n else:\n kw[k].append(item)\n\n for key, shape in shapes.items():\n if key in kw:\n values = flatten(kw[key])\n array_size = np.prod(shape)\n if len(values) != array_size:\n raise ValueError(\n f\"Shape mismatch for key '{key}': expected {array_size} elements, got {len(values)}.\"\n )\n kw[key] = np.array(values).reshape(shape)\n\n return dict(kw)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.shapes_from_ndarray_dict","title":"shapes_from_ndarray_dict(ndarray_dict)
","text":"Return a dictionary of shapes given a dictionary of numpy ndarrays.
This function takes a dictionary where values are numpy ndarrays and returns a new dictionary with the same keys, where each value is the shape of the ndarray.
Args: - ndarray_dict: A dictionary where values are numpy ndarrays.
Returns: - A dictionary where each key maps to the shape of the corresponding ndarray.
Source code insrc/tolvera/npndarray_dict.py
def shapes_from_ndarray_dict(ndarray_dict):\n \"\"\"Return a dictionary of shapes given a dictionary of numpy ndarrays.\n\n This function takes a dictionary where values are numpy ndarrays and returns\n a new dictionary with the same keys, where each value is the shape of the ndarray.\n\n Args:\n - ndarray_dict: A dictionary where values are numpy ndarrays.\n\n Returns:\n - A dictionary where each key maps to the shape of the corresponding ndarray.\n \"\"\"\n shapes = {}\n for key, array in ndarray_dict.items():\n shapes[key] = array.shape\n return shapes\n
"},{"location":"reference/tolvera/particles/","title":"Particles","text":"Particle system.
The Tolvera particle system consists of a Particle class and a Particles class. The Particle class is a Taichi dataclass for a single particle, and the Particles class is a Taichi data_oriented class containing a Particle field.
The Particles class also contains methods for processing the particle system, such as updating the particles, and getting and setting particle properties.
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle","title":"Particle
","text":"Particle data structure and methods.
Source code insrc/tolvera/particles.py
@ti.dataclass\nclass Particle:\n \"\"\"Particle data structure and methods.\"\"\"\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n ppos: ti.math.vec2\n pvel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n\n @ti.func\n def dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n\n @ti.func\n def dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n\n @ti.func\n def dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n\n @ti.func\n def dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n\n # @ti.func\n # def dist_wrap(self, other, x, y):\n # dx = self.pos[0] - other.pos[0]\n # dy = self.pos[1] - other.pos[1]\n # # Wrap around for the x-axis\n # if abs(dx) > x / 2:\n # dx = x - abs(dx)\n # if self.pos[0] < other.pos[0]:\n # dx = -dx\n # # Wrap around for the y-axis\n # if abs(dy) > y / 2:\n # dy = y - abs(dy)\n # if self.pos[1] < other.pos[1]:\n # dy = -dy\n # return ti.Vector([dx, dy])\n # @ti.func\n # def dist_wrap(self, other, width, height):\n # # Compute the element-wise absolute difference\n # self_abs = ti.abs(self.pos)\n # other_abs = ti.abs(other.pos)\n # delta = self_abs - other_abs\n # # Check if wrapping around is shorter for both the x and y components\n # if delta[0] > width / 2:\n # delta[0] = width - delta[0]\n # if delta[1] > height / 2:\n # delta[1] = height - delta[1]\n # # Correct the signs if necessary\n # if self.pos[0] > other.pos[0] and delta[0] > 0:\n # delta[0] = -delta[0]\n # if self.pos[1] > other.pos[1] and delta[1] > 0:\n # delta[1] = -delta[1]\n # return delta\n @ti.func\n def randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n\n @ti.func\n def randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n\n @ti.func\n def randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist","title":"dist(other)
","text":"Distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: Distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_norm","title":"dist_norm(other)
","text":"ti.math.norm() distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: ti.math.norm() distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_normalized","title":"dist_normalized(other)
","text":"ti.math.normalized() distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: ti.math.normalized() distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_wrap","title":"dist_wrap(other, x, y)
","text":"Wrap around distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredx
float
Width.
requiredy
float
Height.
requiredReturns:
Type Descriptionti.math.vec2: Wrap around distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise","title":"randomise(x, y)
","text":"Randomise the particle's position and velocity.
Parameters:
Name Type Description Defaultx
f32
Width.
requiredy
f32
Height.
required Source code insrc/tolvera/particles.py
@ti.func\ndef randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_pos","title":"randomise_pos(x, y)
","text":"Randomise the particle's position.
Parameters:
Name Type Description Defaultx
f32
Width.
requiredy
f32
Height.
required Source code insrc/tolvera/particles.py
@ti.func\ndef randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_vel","title":"randomise_vel()
","text":"Randomise the particle's velocity.
Source code insrc/tolvera/particles.py
@ti.func\ndef randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles","title":"Particles
","text":"Particle system.
Source code insrc/tolvera/particles.py
@ti.data_oriented\nclass Particles:\n \"\"\"Particle system.\"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n self.tv.s.collisions_p = {\n 'state': {\n 'collision': (ti.i32, 0, 1),\n 'dpos': (ti.math.vec2, 0., 1.),\n 'dvel': (ti.math.vec2, 0., 1.),\n },\n 'shape': self.n,\n }\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n\n def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n\n @ti.kernel\n def assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n\n def _randomise(self):\n \"\"\"Randomise the particle system (Python scope).\"\"\"\n self.randomise()\n\n @ti.kernel\n def randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n\n @ti.kernel\n def update(self):\n \"\"\"Update the particle system.\"\"\"\n j = 0\n for i in range(self.n):\n if self.field[i] == 0.0: continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n self.detect_collisions(i, self.C.COLL_RAD)\n self.update_prev(i)\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n\n @ti.func\n def toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n\n @ti.func\n def limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n\n @ti.func\n def detect_collisions(self, i: ti.i32, radius: ti.f32):\n \"\"\"Detect collisions between particles.\n\n TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n TODO: Multiple collision states? Collided, Colliding, etc.\n TODO: Detect collisions between external objects.\n\n Args:\n i (ti.i32): Particle index.\n radius (ti.f32): Collision radius.\n \"\"\"\n for j in range(self.n):\n p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n if p2.active == 0: continue\n dist = p1.pos - p2.pos\n if dist.norm() < radius:\n pdist = p1.ppos - p2.ppos\n dpos = ti.abs(pdist - dist)\n dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n self.tv.s.collisions_p[i].dpos = dpos\n self.tv.s.collisions_p[i].dvel = dvel\n if pdist.norm() > radius:\n self.tv.s.collisions_p[i].collision = 1\n else:\n self.tv.s.collisions_p[i].collision = 0\n\n @ti.func\n def update_prev(self, i: ti.i32):\n \"\"\"Update the previous position and velocity of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n self.field[i].ppos = self.field[i].pos\n self.field[i].pvel = self.field[i].vel\n\n @ti.kernel\n def activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n\n def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update()\n\n @ti.kernel\n def set_total_active(self, total: ti.i32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n\n @ti.kernel\n def set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n amount (ti.f32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = amount\n\n @ti.kernel\n def set_species_total_active(self, i: ti.i32, total: ti.i32):\n \"\"\"Set the total number of active particles for a species.\n\n Args:\n i (ti.i32): Species index.\n total (ti.i32): Total active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n\n @ti.kernel\n def set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n total: (ti.i32): Total number of active particles.\n amount (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = amount\n\n def set_pos(self, i, x, y):\n self.field[i].pos = [x, y]\n\n def set_vel(self, i, x, y):\n self.field[i].vel = [x, y]\n\n def set_speed(self, i, s):\n self.field[i].speed = s\n\n def set_size(self, i, s):\n self.field[i].size = s\n\n def get_pos(self, i):\n return self.field[i].pos.to_numpy().tolist()\n\n def get_vel(self, i):\n return self.field[i].vel.to_numpy().tolist()\n\n def get_pos_all_1d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().flatten().tolist()\n\n def get_pos_all_2d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().tolist()\n\n def get_vel_all_1d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().flatten().tolist()\n\n def get_vel_all_2d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_all(self):\n # for i in range(self.active_count[None]):\n # idx = self.active_indexes[i]\n # p = self.field[idx]\n # self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # TODO: Only send active particle positions...? Or inactive=-1?\n for i in range(self.n):\n p = self.field[i]\n # if p.active > 0.0: # causes IML shape assertion error\n self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # else:\n # self.tmp_pos[i] = [0.0,0.0] # ???\n\n @ti.kernel\n def _get_vel_all(self):\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.tmp_vel[i] = p.vel\n\n def get_pos_species_1d(self, species: int):\n self._get_pos_species()\n return self.tmp_pos_species.to_numpy().flatten().tolist()\n\n def get_pos_species_2d(self, species: int):\n if species > self.tv.species - 1:\n return\n self._get_pos_species(species)\n return self.tmp_pos_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n pos = p.pos / [self.tv.x, self.tv.y]\n self.tmp_pos_species[species_index] = pos\n\n def get_vel_species_1d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().flatten().tolist()\n\n def get_vel_species_2d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_vel_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n vel = p.vel / [self.tv.x, self.tv.y]\n self.tmp_vel_species[species_index] = vel\n\n def get_vel_stats_species_1d(self, species):\n self._species_velocity_statistics(species)\n return self.tmp_vel_stats.to_numpy().flatten().tolist()\n\n @ti.kernel\n def _species_velocity_statistics(self, i: ti.i32):\n \"\"\"\n Centre of Mass Velocity: This is the average velocity of all particles in the species.\n Relative Velocity: This is the average velocity of all particles in the species relative to the centre of mass velocity.\n Angular Momentum: This is the sum of the angular momentum of all particles, which is given by mass * velocity * radius for each particle.\n Kinetic Energy: This is the sum of the kinetic energy of all particles, which is given by 0.5 * mass * velocity^2 for each particle.\n Temperature: In statistical mechanics, the temperature of a system of particles is related to the average kinetic energy of the particles.\n \"\"\"\n centre_of_mass_velocity = ti.Vector([0.0, 0.0])\n relative_velocity = ti.Vector([0.0, 0.0])\n angular_momentum = ti.Vector([0.0])\n kinetic_energy = ti.Vector([0.0])\n for j in range(self.n):\n if self.field[j].species == i:\n v = self.field[j].vel\n p = self.field[j].pos\n m = self.field[j].mass\n centre_of_mass_velocity += v\n relative_velocity += v # - centre_of_mass_velocity\n angular_momentum += m * ti.math.cross(v, p)\n kinetic_energy += 0.5 * m * v.norm_sqr()\n centre_of_mass_velocity = centre_of_mass_velocity / self.n_per_species\n relative_velocity = (\n relative_velocity - centre_of_mass_velocity * self.n_per_species\n ) / self.n_per_species\n temperature = 2.0 * kinetic_energy / (self.particles_per_species * 1.380649e-23)\n self.tmp_vel_stats[0] = centre_of_mass_velocity[0]\n self.tmp_vel_stats[1] = centre_of_mass_velocity[1]\n self.tmp_vel_stats[2] = relative_velocity[0]\n self.tmp_vel_stats[3] = relative_velocity[1]\n self.tmp_vel_stats[4] = angular_momentum[0]\n self.tmp_vel_stats[5] = kinetic_energy[0]\n self.tmp_vel_stats[6] = temperature[0]\n\n def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n\n def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n\n def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__call__","title":"__call__()
","text":"Call will process the particle system.
Source code insrc/tolvera/particles.py
def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the particle system.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance.
required**kwargs
Keyword arguments (currently there are none).
{}
Source code in src/tolvera/particles.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n self.tv.s.collisions_p = {\n 'state': {\n 'collision': (ti.i32, 0, 1),\n 'dpos': (ti.math.vec2, 0., 1.),\n 'dvel': (ti.math.vec2, 0., 1.),\n },\n 'shape': self.n,\n }\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.activity_decay","title":"activity_decay()
","text":"Decay the activity of the particles.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.assign_species","title":"assign_species()
","text":"Assign species to particles.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.detect_collisions","title":"detect_collisions(i, radius)
","text":"Detect collisions between particles.
TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s? TODO: Multiple collision states? Collided, Colliding, etc. TODO: Detect collisions between external objects.
Parameters:
Name Type Description Defaulti
i32
Particle index.
requiredradius
f32
Collision radius.
required Source code insrc/tolvera/particles.py
@ti.func\ndef detect_collisions(self, i: ti.i32, radius: ti.f32):\n \"\"\"Detect collisions between particles.\n\n TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n TODO: Multiple collision states? Collided, Colliding, etc.\n TODO: Detect collisions between external objects.\n\n Args:\n i (ti.i32): Particle index.\n radius (ti.f32): Collision radius.\n \"\"\"\n for j in range(self.n):\n p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n if p2.active == 0: continue\n dist = p1.pos - p2.pos\n if dist.norm() < radius:\n pdist = p1.ppos - p2.ppos\n dpos = ti.abs(pdist - dist)\n dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n self.tv.s.collisions_p[i].dpos = dpos\n self.tv.s.collisions_p[i].dvel = dvel\n if pdist.norm() > radius:\n self.tv.s.collisions_p[i].collision = 1\n else:\n self.tv.s.collisions_p[i].collision = 0\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.init","title":"init()
","text":"Initialise the particle system.
Source code insrc/tolvera/particles.py
def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.limit_speed","title":"limit_speed(i)
","text":"Limit the speed of a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.process","title":"process()
","text":"Process the particle system.
Source code insrc/tolvera/particles.py
def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.randomise","title":"randomise()
","text":"Randomise the particle system (Taichi scope).
Source code insrc/tolvera/particles.py
@ti.kernel\ndef randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.reset","title":"reset()
","text":"Reset the particle system.
Source code insrc/tolvera/particles.py
def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active","title":"set_species_total_active(i, total)
","text":"Set the total number of active particles for a species.
Parameters:
Name Type Description Defaulti
i32
Species index.
requiredtotal
i32
Total active particles.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_species_total_active(self, i: ti.i32, total: ti.i32):\n \"\"\"Set the total number of active particles for a species.\n\n Args:\n i (ti.i32): Species index.\n total (ti.i32): Total active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active_amount","title":"set_species_total_active_amount(i, total, amount)
","text":"Set particle activity amount of a species.
Parameters:
Name Type Description Defaulti
i32
Species index.
requiredtotal
i32
(ti.i32): Total number of active particles.
requiredamount
i32
Amount of activity.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n total: (ti.i32): Total number of active particles.\n amount (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = amount\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active","title":"set_total_active(total)
","text":"Set the total number of active particles.
Parameters:
Name Type Description Defaulttotal
i32
Total active particles.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_total_active(self, total: ti.i32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active_amount","title":"set_total_active_amount(total, amount)
","text":"Set the total number of active particles.
Parameters:
Name Type Description Defaulttotal
i32
Total active particles.
requiredamount
f32
Amount of activity.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n amount (ti.f32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = amount\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.speed","title":"speed(speed=None)
","text":"Get or set the speed of the particle system.
Parameters:
Name Type Description Defaultspeed
float
Speed. Defaults to None.
None
Returns:
Name Type Descriptionfloat
Speed.
Source code insrc/tolvera/particles.py
def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.toroidal_wrap","title":"toroidal_wrap(i)
","text":"Toroidal wrap a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update","title":"update()
","text":"Update the particle system.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef update(self):\n \"\"\"Update the particle system.\"\"\"\n j = 0\n for i in range(self.n):\n if self.field[i] == 0.0: continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n self.detect_collisions(i, self.C.COLL_RAD)\n self.update_prev(i)\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_prev","title":"update_prev(i)
","text":"Update the previous position and velocity of a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef update_prev(self, i: ti.i32):\n \"\"\"Update the previous position and velocity of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n self.field[i].ppos = self.field[i].pos\n self.field[i].pvel = self.field[i].vel\n
"},{"location":"reference/tolvera/patches/","title":"Patches","text":"Patches for third-party libraries.
Current patches: - 'dill.source.findsource fails when in asyncio REPL' https://github.com/uqfoundation/dill/issues/627
"},{"location":"reference/tolvera/patches/#tolvera.patches.findsource","title":"findsource(object)
","text":"Return the entire source file and starting line number for an object. For interactively-defined objects, the 'file' is the interpreter's history.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a list of all the lines in the file and the line number indexes a line in that list. An IOError is raised if the source code cannot be retrieved, while a TypeError is raised for objects where the source code is unavailable (e.g. builtins).
Source code insrc/tolvera/patches.py
def findsource(object):\n # print(f\"[dill.source.findsource] PATCHED\")\n\n \"\"\"Return the entire source file and starting line number for an object.\n For interactively-defined objects, the 'file' is the interpreter's history.\n\n The argument may be a module, class, method, function, traceback, frame,\n or code object. The source code is returned as a list of all the lines\n in the file and the line number indexes a line in that list. An IOError\n is raised if the source code cannot be retrieved, while a TypeError is\n raised for objects where the source code is unavailable (e.g. builtins).\"\"\"\n\n def patched_getfile(module):\n # set file = None when module.__package__ == 'asyncio'\n # print(f\"[dill.source.patched_getfile] module={module}\\nmodule.__package__={module.__package__}\\nmodule.__name__={module.__name__}\")\n if module.__package__ == \"asyncio\":\n raise TypeError\n # if module.__package__ == 'sardine':\n # raise TypeError\n ret = getfile(module)\n return ret\n\n module = getmodule(object)\n # try: file = getfile(module)\n try:\n file = patched_getfile(module)\n except TypeError:\n file = None\n # correctly compute `is_module_main` when in asyncio\n is_module_main = module and module.__name__ == \"__main__\" and not file\n # is_module_main = (module and module.__name__ == '__main__' or module.__name__ == 'sardine' and not file)\n print(\n f\"[dill.source.findsource] module: {module}, file: {file}, is_module_main: {is_module_main}\"\n )\n if IS_IPYTHON and is_module_main:\n # FIXME: quick fix for functions and classes in IPython interpreter\n try:\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except TypeError:\n if isclass(object):\n for object_method in filter(isfunction, object.__dict__.values()):\n # look for a method of the class\n file_candidate = getfile(object_method)\n if not file_candidate.startswith(\"<ipython-input-\"):\n continue\n file = file_candidate\n sourcefile = getsourcefile(object_method)\n break\n if file:\n lines = linecache.getlines(file)\n else:\n # fallback to use history\n history = \"\\n\".join(get_ipython().history_manager.input_hist_parsed)\n lines = [line + \"\\n\" for line in history.splitlines()]\n # use readline when working in interpreter (i.e. __main__ and not file)\n elif is_module_main:\n try:\n import readline\n\n err = \"\"\n except ImportError:\n import sys\n\n err = sys.exc_info()[1].args[0]\n if sys.platform[:3] == \"win\":\n err += \", please install 'pyreadline'\"\n if err:\n raise IOError(err)\n lbuf = readline.get_current_history_length()\n lines = [readline.get_history_item(i) + \"\\n\" for i in range(1, lbuf)]\n else:\n try: # special handling for class instances\n if not isclass(object) and isclass(type(object)): # __class__\n file = getfile(module)\n sourcefile = getsourcefile(module)\n else: # builtins fail with a TypeError\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except (TypeError, AttributeError): # fail with better error\n file = getfile(object)\n sourcefile = getsourcefile(object)\n if not sourcefile and file[:1] + file[-1:] != \"<>\":\n raise IOError(\"source code not available\")\n file = sourcefile if sourcefile else file\n\n module = getmodule(object, file)\n if module:\n lines = linecache.getlines(file, module.__dict__)\n else:\n lines = linecache.getlines(file)\n\n if not lines:\n raise IOError(\"could not extract source code\")\n\n # FIXME: all below may fail if exec used (i.e. exec('f = lambda x:x') )\n if ismodule(object):\n return lines, 0\n\n # NOTE: beneficial if search goes from end to start of buffer history\n name = pat1 = obj = \"\"\n pat2 = r\"^(\\s*@)\"\n # pat1b = r'^(\\s*%s\\W*=)' % name #FIXME: finds 'f = decorate(f)', not exec\n if ismethod(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__func__\n if isfunction(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n obj = object # XXX: better a copy?\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__code__\n if istraceback(object):\n object = object.tb_frame\n if isframe(object):\n object = object.f_code\n if iscode(object):\n if not hasattr(object, \"co_firstlineno\"):\n raise IOError(\"could not find function definition\")\n # stdin = object.co_filename == '<stdin>'\n stdin = object.co_filename in (\"<console>\", \"<stdin>\")\n # print(f\"[dill.source.findsource] object.co_filename: {object.co_filename}, stdin: {stdin}\")\n if stdin:\n lnum = len(lines) - 1 # can't get lnum easily, so leverage pat\n if not pat1:\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n else:\n lnum = object.co_firstlineno - 1\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n pat1 = re.compile(pat1)\n pat2 = re.compile(pat2)\n # XXX: candidate_lnum = [n for n in range(lnum) if pat1.match(lines[n])]\n while lnum > 0: # XXX: won't find decorators in <stdin> ?\n line = lines[lnum]\n if pat1.match(line):\n if not stdin:\n break # co_firstlineno does the job\n if name == \"<lambda>\": # hackery needed to confirm a match\n if _matchlambda(obj, line):\n break\n else: # not a lambda, just look for the name\n if name in line: # need to check for decorator...\n hats = 0\n for _lnum in range(lnum - 1, -1, -1):\n if pat2.match(lines[_lnum]):\n hats += 1\n else:\n break\n lnum = lnum - hats\n break\n lnum = lnum - 1\n return lines, lnum\n\n try: # turn instances into classes\n if not isclass(object) and isclass(type(object)): # __class__\n object = object.__class__ # XXX: sometimes type(class) is better?\n # XXX: we don't find how the instance was built\n except AttributeError:\n pass\n if isclass(object):\n name = object.__name__\n pat = re.compile(r\"^(\\s*)class\\s*\" + name + r\"\\b\")\n # make some effort to find the best matching class definition:\n # use the one with the least indentation, which is the one\n # that's most probably not inside a function definition.\n candidates = []\n for i in range(len(lines) - 1, -1, -1):\n match = pat.match(lines[i])\n if match:\n # if it's at toplevel, it's already the best one\n if lines[i][0] == \"c\":\n return lines, i\n # else add whitespace to candidate list\n candidates.append((match.group(1), i))\n if candidates:\n # this will sort by whitespace, and by line number,\n # less whitespace first #XXX: should sort high lnum before low\n candidates.sort()\n return lines, candidates[0][1]\n else:\n raise IOError(\"could not find class definition\")\n raise IOError(\"could not find code object\")\n
"},{"location":"reference/tolvera/pixels/","title":"Pixels","text":"Pixels module.
ExampleDraw a red rectangle in the centre of the screen.
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n tv.p()\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels","title":"Pixels
","text":"Pixels class for drawing pixels to the screen.
This class is used to draw pixels to the screen. It contains methods for drawing points, lines, rectangles, circles, triangles, and polygons. It also contains methods for blending pixels together, flipping pixels, inverting pixels, and diffusing, decaying and clearing pixels.
It tries to follow a similar API to the Processing library.
Source code insrc/tolvera/pixels.py
@ti.data_oriented\nclass Pixels:\n \"\"\"Pixels class for drawing pixels to the screen.\n\n This class is used to draw pixels to the screen. It contains methods for drawing\n points, lines, rectangles, circles, triangles, and polygons. It also contains\n methods for blending pixels together, flipping pixels, inverting pixels, and\n diffusing, decaying and clearing pixels.\n\n It tries to follow a similar API to the Processing library.\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n\n\n def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n\n @ti.kernel\n def k_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n @ti.kernel\n def f_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n\n @ti.kernel\n def clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n\n @ti.kernel\n def diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n\n @ti.func\n def background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n\n @ti.func\n def point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n\n @ti.func\n def rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n\n @ti.func\n def line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n\n @ti.func\n def circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n\n @ti.func\n def triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n\n @ti.func\n def polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n\n @ti.func\n def _is_inside(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n is_inside = 0\n if self.polygon_mode == \"crossing\":\n is_inside = self._is_inside_crossing(p, x, y, l)\n elif self.polygon_mode == \"winding\":\n is_inside = self._is_inside_winding(p, x, y, l)\n return is_inside\n\n @ti.func\n def _is_inside_crossing(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using crossing number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if (v0[1] <= p[1] and v1[1] > p[1]) or (v0[1] > p[1] and v1[1] <= p[1]):\n vt = (p[1] - v0[1]) / (v1[1] - v0[1])\n if p[0] < v0[0] + vt * (v1[0] - v0[0]):\n n += 1\n return n % 2\n\n @ti.func\n def _is_inside_winding(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using winding number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if v0[1] <= p[1] and v1[1] > p[1] and (v0 - v1).cross(p - v1) > 0:\n n += 1\n elif v1[1] <= p[1] and (v0 - v1).cross(p - v1) < 0:\n n -= 1\n return n\n\n @ti.kernel\n def flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[self.x - 1 - i, j]\n\n @ti.kernel\n def flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[i, self.y - 1 - j]\n\n @ti.kernel\n def invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n\n @ti.kernel\n def decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n\n def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_add(self, rgba: ti.template()):\n \"\"\"Blend by adding pixels together (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] += rgba[i, j]\n\n def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_sub(self, rgba: ti.template()):\n \"\"\"Blend by subtracting pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] -= rgba[i, j]\n\n def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_mul(self, rgba: ti.template()):\n \"\"\"Blend by multiplying pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rgba[i, j]\n\n def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_div(self, rgba: ti.template()):\n \"\"\"Blend by dividing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] /= rgba[i, j]\n\n def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_min(self, rgba: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.min(self.px.rgba[i, j], rgba[i, j])\n\n def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_max(self, rgba: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.max(self.px.rgba[i, j], rgba[i, j])\n\n def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff(self, rgba: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(self.px.rgba[i, j] - rgba[i, j])\n\n def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff_inv(self, rgba: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(rgba[i, j] - self.px.rgba[i, j])\n\n def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n\n @ti.kernel\n def _blend_mix(self, rgba: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.math.mix(self.px.rgba[i, j], rgba[i, j], amount)\n\n @ti.kernel\n def blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n\n def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n ):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n\n @ti.kernel\n def _particles(self, particles: ti.template(), species: ti.template(), shape: int):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (int): Shape enum value.\n \"\"\"\n for i in range(self.tv.p.n):\n p = particles.field[i]\n s = species[p.species]\n if p.active == 0.0:\n continue\n px = ti.cast(p.pos[0], ti.i32)\n py = ti.cast(p.pos[1], ti.i32)\n vx = ti.cast(p.pos[0] + p.vel[0] * 20, ti.i32)\n vy = ti.cast(p.pos[1] + p.vel[1] * 20, ti.i32)\n rgba = s.rgba * self.CONSTS.BRIGHTNESS\n if shape == 0:\n self.point(px, py, rgba)\n elif shape == 1:\n self.line(px, py, vx, vy, rgba)\n elif shape == 2:\n side = int(s.size) * 2\n self.rect(px, py, side, side, rgba)\n elif shape == 3:\n self.circle(px, py, p.size, rgba)\n elif shape == 4:\n a = p.pos\n b = p.pos + 1\n c = a + b\n self.triangle(a, b, c, rgba)\n # elif shape == 5:\n # self.polygon(px, py, rgba)\n\n def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n\n def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__call__","title":"__call__()
","text":"Call returns pixels.
Source code insrc/tolvera/pixels.py
def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Pixels
Parameters:
Name Type Description Defaulttolvera
Tolvera
T\u00f6lvera instance.
required**kwargs
Keyword arguments. polygon_mode (str): Polygon mode. Defaults to \"crossing\". brightness (float): Brightness. Defaults to 1.0.
{}
Source code in src/tolvera/pixels.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.background","title":"background(r, g, b)
","text":"Set background colour.
Parameters:
Name Type Description Defaultr
f32
Red.
requiredg
f32
Green.
requiredb
f32
Blue.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_add","title":"blend_add(px)
","text":"Blend by adding pixels together (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff","title":"blend_diff(px)
","text":"Blend by taking the difference of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff_inv","title":"blend_diff_inv(px)
","text":"Blend by taking the inverse difference of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_div","title":"blend_div(px)
","text":"Blend by dividing pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_max","title":"blend_max(px)
","text":"Blend by taking the maximum of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_min","title":"blend_min(px)
","text":"Blend by taking the minimum of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mix","title":"blend_mix(px, amount)
","text":"Blend by mixing pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
requiredamount
f32
Amount to mix.
required Source code insrc/tolvera/pixels.py
def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mul","title":"blend_mul(px)
","text":"Blend by multiplying pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_sub","title":"blend_sub(px)
","text":"Blend by subtracting pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blur","title":"blur(radius)
","text":"Blur pixels.
Parameters:
Name Type Description Defaultradius
i32
Blur radius.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circle","title":"circle(x, y, r, rgba)
","text":"Draw a filled circle.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredr
i32
Radius.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circles","title":"circles(x, y, r, rgba)
","text":"Draw circles with the same colour.
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredr
template
Radii.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.clear","title":"clear()
","text":"Clear pixels.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.decay","title":"decay(rate)
","text":"Decay pixels.
Parameters:
Name Type Description Defaultrate
f32
decay rate.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.diffuse","title":"diffuse(evaporate)
","text":"Diffuse pixels.
Parameters:
Name Type Description Defaultevaporate
float
Evaporation rate.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_x","title":"flip_x()
","text":"Flip image in x-axis.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[self.x - 1 - i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_y","title":"flip_y()
","text":"Flip image in y-axis.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[i, self.y - 1 - j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.get","title":"get()
","text":"Get pixels.
Source code insrc/tolvera/pixels.py
def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.invert","title":"invert()
","text":"Invert image.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.line","title":"line(x0, y0, x1, y1, rgba)
","text":"Draw a line using Bresenham's algorithm.
Parameters:
Name Type Description Defaultx0
i32
X start position.
requiredy0
i32
Y start position.
requiredx1
i32
X end position.
requiredy1
i32
Y end position.
requiredrgba
vec4
Colour.
requiredTODO: thickness TODO: anti-aliasing TODO: should lines wrap around (as two lines)?
Source code insrc/tolvera/pixels.py
@ti.func\ndef line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.particles","title":"particles(particles, species, shape='circle')
","text":"Draw particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredspecies
template
Species.
requiredshape
str
Shape. Defaults to \"circle\".
'circle'
Source code in src/tolvera/pixels.py
def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.point","title":"point(x, y, rgba)
","text":"Draw point.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.points","title":"points(x, y, rgba)
","text":"Draw points with the same colour.
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.polygon","title":"polygon(x, y, rgba)
","text":"Draw a filled polygon.
Polygons are drawn according to the polygon mode, which can be \"crossing\" (default) or \"winding\". First, the bounding box of the polygon is calculated. Then, we check if each pixel in the bounding box is inside the polygon. If it is, we draw it (along with each neighbour pixel).
Reference for point in polygon inclusion testing: http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredrgba
vec4
Colour.
requiredTODO: fill arg
Source code insrc/tolvera/pixels.py
@ti.func\ndef polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rect","title":"rect(x, y, w, h, rgba)
","text":"Draw a filled rectangle.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredw
i32
Width.
requiredh
i32
Height.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rgba_from_px","title":"rgba_from_px(px)
","text":"Get rgba from pixels.
Parameters:
Name Type Description Defaultpx
Any
Pixels to get rgba from.
requiredRaises:
Type DescriptionTypeError
If pixel field cannot be found.
Returns:
Name Type DescriptionMatrixField
RGBA matrix field.
Source code insrc/tolvera/pixels.py
def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.set","title":"set(px)
","text":"Set pixels.
Parameters:
Name Type Description Defaultpx
Any
Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).
required Source code insrc/tolvera/pixels.py
def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.triangle","title":"triangle(a, b, c, rgba)
","text":"Draw a filled triangle.
Parameters:
Name Type Description Defaulta
vec2
Point A.
requiredb
vec2
Point B.
requiredc
vec2
Point C.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n
"},{"location":"reference/tolvera/sketchbook/","title":"Sketchbook","text":"Sketchbook module for listing and running sketches from the command line.
WIP.
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketch_info","title":"get_sketch_info(sketch_file, sketchbook_folder='./')
","text":"Gets information about a specific sketch file.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionDict[str, Any]
Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.
Source code insrc/tolvera/sketchbook.py
def get_sketch_info(sketch_file: str, sketchbook_folder: str = \"./\") -> Dict[str, Any]:\n \"\"\"\n Gets information about a specific sketch file.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.\n \"\"\"\n validate_sketch_file(sketch_file, sketchbook_folder)\n datetimefmt = \"%Y-%m-%d %H:%M:%S\"\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n file_info = os.stat(file_path)\n size = file_info.st_size\n modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(datetimefmt)\n created = datetime.datetime.fromtimestamp(file_info.st_ctime).strftime(datetimefmt)\n return {\n \"name\": module_name,\n \"path\": file_path,\n \"size\": size,\n \"modified\": modified,\n \"created\": created,\n }\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files","title":"get_sketchbook_files(sketchbook_folder='./')
","text":"Gets all sketch files from the sketchbook folder.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionList[str]
List[str]: List of sketch file names.
Source code insrc/tolvera/sketchbook.py
def get_sketchbook_files(sketchbook_folder: str = \"./\") -> List[str]:\n \"\"\"\n Gets all sketch files from the sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[str]: List of sketch file names.\n \"\"\"\n files = os.listdir(sketchbook_folder)\n exclude = [\"__init__.py\", \"__pycache__\", \".DS_Store\", \"imgui.ini\"]\n return [f for f in files if f not in exclude]\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files_info","title":"get_sketchbook_files_info(sketches, sketchbook_folder='./')
","text":"Gets information about all sketch files in the sketchbook folder.
Parameters:
Name Type Description Defaultsketches
List[str]
List of sketch file names.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionList[Dict[str, Any]]
List[Dict[str, Any]]: List of sketch information dictionaries.
Source code insrc/tolvera/sketchbook.py
def get_sketchbook_files_info(\n sketches: List[str], sketchbook_folder: str = \"./\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Gets information about all sketch files in the sketchbook folder.\n\n Args:\n sketches (List[str]): List of sketch file names.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[Dict[str, Any]]: List of sketch information dictionaries.\n \"\"\"\n sketch_infos = []\n for sketch in sketches:\n sketch_info = get_sketch_info(sketch, sketchbook_folder)\n sketch_infos.append(sketch_info)\n return sketch_infos\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.import_sketch","title":"import_sketch(module_name, file_path)
","text":"Imports a sketch from a given file.
Parameters:
Name Type Description Defaultmodule_name
str
Name of the module.
requiredfile_path
str
Path to the file containing the module.
requiredReturns:
Name Type DescriptionAny
Any
Imported module.
Source code insrc/tolvera/sketchbook.py
def import_sketch(module_name: str, file_path: str) -> Any:\n \"\"\"\n Imports a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n\n Returns:\n Any: Imported module.\n \"\"\"\n if not os.path.exists(file_path):\n print(f\"File does not exist: {file_path}\")\n return None\n\n if not module_name:\n print(\"Module name is empty or invalid.\")\n return None\n try:\n print(f\"Importing {module_name} from {file_path}...\")\n spec = importlib.util.spec_from_file_location(module_name, file_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n except Exception as e:\n error_type = type(e).__name__\n print(f\"Error importing {module_name} ({error_type}): {str(e)}\")\n return None\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.list_sketches","title":"list_sketches(sketchbook_folder='./', sort='name', direction='ascending')
","text":"Lists all sketches in the given sketchbook folder.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Source code in src/tolvera/sketchbook.py
def list_sketches(\n sketchbook_folder: str = \"./\", sort: str = \"name\", direction: str = \"ascending\"\n) -> None:\n \"\"\"\n Lists all sketches in the given sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n sketch_files_list = get_sketchbook_files(sketchbook_folder)\n sketches = get_sketchbook_files_info(sketch_files_list, sketchbook_folder)\n sorted_sketches = sort_sketch_files(sketches, sort, direction)\n pretty_print_sketchbook(sorted_sketches, sketchbook_folder)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.main","title":"main(*args, **kwargs)
","text":"Main function for running the sketchbook from the command line.
Source code insrc/tolvera/sketchbook.py
def main(*args, **kwargs):\n \"\"\"\n Main function for running the sketchbook from the command line.\n \"\"\"\n if \"sketchbook\" in kwargs:\n sketchbook = kwargs[\"sketchbook\"]\n else:\n sketchbook = \"./\"\n if \"sketch\" in kwargs:\n sketch = kwargs[\"sketch\"]\n if isinstance(sketch, str):\n run_sketch_by_name(sketch, sketchbook, *args, **kwargs)\n elif isinstance(sketch, int):\n print(f\"Running sketch by index: {sketch}\")\n run_sketch_by_index(sketch, sketchbook, *args, **kwargs)\n elif \"sketches\" in kwargs:\n sort = kwargs[\"sort\"] if \"sort\" in kwargs else \"name\"\n direction = kwargs[\"direction\"] if \"direction\" in kwargs else \"ascending\"\n list_sketches(sketchbook, sort, direction)\n exit()\n elif \"random\" in kwargs:\n run_random_sketch(sketchbook)\n else:\n list_sketches(sketchbook)\n exit()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.pretty_print_sketchbook","title":"pretty_print_sketchbook(sketches, sketchbook_folder='./')
","text":"Pretty prints the sketchbook information.
Parameters:
Name Type Description Defaultsketches
List[Dict[str, Any]]
List of sketch information dictionaries.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def pretty_print_sketchbook(\n sketches: List[Dict[str, Any]], sketchbook_folder: str = \"./\"\n) -> None:\n \"\"\"\n Pretty prints the sketchbook information.\n\n Args:\n sketches (List[Dict[str, Any]]): List of sketch information dictionaries.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n print(f\"\\nSketchbook '{sketchbook_folder}':\\n\")\n print(\n f\" {'Index':<5} {'Sketch':<25} {'Size':<10} {'Modified':<20} {'Created':<20}\"\n )\n print(f\" {'-'*5:<5} {'-'*25:<25} {'-'*10:<10} {'-'*20:<20} {'-'*20:<20}\")\n for i, sketch in enumerate(sketches):\n print(\n f\" {i:<5} {sketch['name']:<25} {sketch['size']:<10} {sketch['modified']:<20} {sketch['created']:<20}\"\n )\n print()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_random_sketch","title":"run_random_sketch(sketchbook='./')
","text":"Runs a random sketch from the sketchbook.
Parameters:
Name Type Description Defaultsketchbook
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_random_sketch(sketchbook=\"./\"):\n \"\"\"\n Runs a random sketch from the sketchbook.\n\n Args:\n sketchbook (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook)\n files = get_sketchbook_files(sketchbook)\n sketch_file = files[random.randint(0, len(files) - 1)]\n run_sketch_by_name(sketch_file, sketchbook)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_index","title":"run_sketch_by_index(index, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its index in the sketchbook.
Parameters:
Name Type Description Defaultindex
int
Index of the sketch to run.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_index(\n index: int, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its index in the sketchbook.\n\n Args:\n index (int): Index of the sketch to run.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n files = get_sketchbook_files(sketchbook_folder)\n for file in files:\n if file.endswith(\".py\"):\n module_name = os.path.splitext(file)[0]\n file_path = os.path.join(sketchbook_folder, file)\n if str(index) == module_name:\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_name","title":"run_sketch_by_name(sketch_file, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its file name.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_name(\n sketch_file: str, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its file name.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_function_from_module","title":"run_sketch_function_from_module(module, function_name, file_path, *args, **kwargs)
","text":"Runs a specific function from a given module.
Parameters:
Name Type Description Defaultmodule
Any
The imported module.
requiredfunction_name
str
Name of the function to run.
requiredfile_path
str
Path to the file containing the module.
required Source code insrc/tolvera/sketchbook.py
def run_sketch_function_from_module(\n module: Any, function_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a specific function from a given module.\n\n Args:\n module (Any): The imported module.\n function_name (str): Name of the function to run.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n if hasattr(module, function_name) and callable(getattr(module, function_name)):\n print(f\"Running {function_name} from {file_path}...\")\n getattr(module, function_name)(*args, **kwargs)\n else:\n print(f\"{module} does not have a '{function_name}' function.\")\n except Exception as e:\n print(f\"Error running {function_name} from {file_path}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.sort_sketch_files","title":"sort_sketch_files(sketch_files, sort='name', direction='ascending')
","text":"Sorts sketch files by name, size, modified or created.
Parameters:
Name Type Description Defaultsort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
sketch_files
List[Dict[str, Any]]
List of sketch information dictionaries.
requireddirection
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Returns:
Type DescriptionList[Dict[str, Any]]
List[Dict[str, Any]]: List of sorted sketch information dictionaries.
Source code insrc/tolvera/sketchbook.py
def sort_sketch_files(\n sketch_files: List[Dict[str, Any]], sort: str = \"name\", direction: str = \"ascending\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Sorts sketch files by name, size, modified or created.\n\n Args:\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n sketch_files (List[Dict[str, Any]]): List of sketch information dictionaries.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n\n Returns:\n List[Dict[str, Any]]: List of sorted sketch information dictionaries.\n \"\"\"\n reverse = True if direction == \"descending\" else False\n if sort == \"name\":\n return sorted(sketch_files, key=lambda k: k[\"name\"], reverse=reverse)\n elif sort == \"size\":\n return sorted(sketch_files, key=lambda k: k[\"size\"], reverse=reverse)\n elif sort == \"modified\":\n return sorted(sketch_files, key=lambda k: k[\"modified\"], reverse=reverse)\n elif sort == \"created\":\n return sorted(sketch_files, key=lambda k: k[\"created\"], reverse=reverse)\n else:\n return sketch_files\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.try_import_and_run_sketch","title":"try_import_and_run_sketch(module_name, file_path, *args, **kwargs)
","text":"Tries to import and run a sketch from a given file.
Parameters:
Name Type Description Defaultmodule_name
str
Name of the module.
requiredfile_path
str
Path to the file containing the module.
required Source code insrc/tolvera/sketchbook.py
def try_import_and_run_sketch(\n module_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Tries to import and run a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n module = import_sketch(module_name, file_path)\n run_sketch_function_from_module(module, \"sketch\", file_path, *args, **kwargs)\n except Exception as e:\n print(f\"Error running {module_name}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketch_file","title":"validate_sketch_file(sketch_file, sketchbook_folder='./')
","text":"Validates if the given sketch file exists in the sketchbook folder.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type DescriptionSystemExit
If the sketch file does not exist.
Source code insrc/tolvera/sketchbook.py
def validate_sketch_file(sketch_file: str, sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketch file exists in the sketchbook folder.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketch file does not exist.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n if not os.path.isfile(file_path):\n print(f\"Sketch file '{file_path}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketchbook_path","title":"validate_sketchbook_path(sketchbook_folder='./')
","text":"Validates if the given sketchbook folder exists.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type DescriptionSystemExit
If the sketchbook folder does not exist.
Source code insrc/tolvera/sketchbook.py
def validate_sketchbook_path(sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketchbook folder exists.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketchbook folder does not exist.\n \"\"\"\n if not os.path.isdir(sketchbook_folder):\n print(f\"Sketchbook folder '{sketchbook_folder}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/species/","title":"Species","text":"Species class.
"},{"location":"reference/tolvera/species/#tolvera.species.Species","title":"Species
","text":"Species in T\u00f6lvera.
Species are implemented as a State with attributes for size
, speed
, mass
and colour
(rgba), and with a length determined by the number of species in the T\u00f6lvera instance (tv.sn
). The attributes are normalised and scaled by species the species_consts
attribute. They are initialised with random values.
Rather than accessing this class directly, access is typically via the State attributes via the T\u00f6lvera instance, via e.g. tv.s.species.field[i].size
.
src/tolvera/species.py
class Species:\n \"\"\"Species in T\u00f6lvera.\n\n Species are implemented as a State with attributes for `size`, `speed`, `mass` and\n `colour` (rgba), and with a length determined by the number of species in the\n T\u00f6lvera instance (`tv.sn`). The attributes are normalised and scaled by species the \n `species_consts` attribute. They are initialised with random values.\n\n Rather than accessing this class directly, access is typically via the State\n attributes via the T\u00f6lvera instance, via e.g. `tv.s.species.field[i].size`.\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n\n def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Species
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance.
required**kwargs
Keyword arguments.
{}
Source code in src/tolvera/species.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.randomise","title":"randomise()
","text":"Randomise species.
Source code insrc/tolvera/species.py
def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/state/","title":"State","text":"State and StateDict classes for T\u00f6lvera.
Every T\u00f6lvera instance has a StateDict, which is a dictionary of State instances. The StateDict is accessible via the 's' attribute of a T\u00f6lvera instance, and can be used to create and access states.
Each State instance has a Taichi struct field and a corresponding NpNdarrayDict, which handles OSC accessors and endpoints.
"},{"location":"reference/tolvera/state/#tolvera.state.State","title":"State
","text":"State class for T\u00f6lvera.
This class takes a name, dictionary of state attributes, and a shape, and creates a Taichi struct field and a corresponding dictionary of NumPy arrays (NpNdarrayDict) for a state.
The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict can be used in Python scope, and the two are kept in sync by the from_nddict() and to_nddict() methods.
The State class also handles OSC accessors for the state, which use the NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required to initialise a State instance.
State attributes are defined as a dictionary of attribute names and tuples of (Taichi type, min value, max value). The domain of the attribute is used when randomising the data in the state, and by OSCMap endpoints and client patches.
The state is n-dimensional based on the shape argument, and the NpNdarrayDict provides methods for accessing the data in the state in n-dimensional slices.
Exampletv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n}\n
Source code in src/tolvera/state.py
@ti.data_oriented\nclass State:\n \"\"\"State class for T\u00f6lvera.\n\n This class takes a name, dictionary of state attributes, and a shape, and\n creates a Taichi struct field and a corresponding dictionary of NumPy arrays \n (NpNdarrayDict) for a state.\n\n The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict\n can be used in Python scope, and the two are kept in sync by the from_nddict()\n and to_nddict() methods.\n\n The State class also handles OSC accessors for the state, which use the\n NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required\n to initialise a State instance.\n\n State attributes are defined as a dictionary of attribute names and tuples of\n (Taichi type, min value, max value). The domain of the attribute is used when\n randomising the data in the state, and by OSCMap endpoints and client patches.\n\n The state is n-dimensional based on the shape argument, and the NpNdarrayDict\n provides methods for accessing the data in the state in n-dimensional slices.\n\n Example:\n ```py\n tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n ```\n \"\"\"\n def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n\n def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n\n def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n ):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n\n def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n\n def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n\n def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n # if self.osc_get: self.add_osc_getters()\n # if self.osc_stream: self.add_osc_streams()\n\n def add_osc_setters(self):\n name = self.setter_name\n self.osc.map.receive_args_inline(name + \"_randomise\", self.randomise)\n\n def add_osc_getters(self):\n name = self.getter_name\n for k, v in self.dict.items():\n ranges = (int(v[0]), int(v[0]), int(v[1]))\n kwargs = {\"i\": ranges, \"j\": ranges, \"attr\": (k, k, k)}\n self.osc.map.receive_args_inline(f\"{name}\", self.osc_getter, **kwargs)\n\n # def osc_getter(self, i: int, j: int, attribute: str):\n # ret = self.get((i, j), attribute)\n # if ret is not None:\n # route = self.osc.map.pascal_to_path(self.getter_name) # +'/'+attribute\n # self.osc.host.return_to_sender_by_name(\n # (route, attribute, ret), self.osc.client_name\n # )\n # return ret\n\n # def add_osc_streams(self):\n # # add send in broadcast mode\n # raise NotImplementedError(\"add_osc_streams not implemented\")\n\n def serialize(self) -> str:\n return ti_serialize(self.field)\n\n def deserialize(self, json_str: str):\n ti_deserialize(self.field, json_str)\n\n def save(self, path: str):\n # TODO: path validation, save to path, etc.\n json_str = self.serialize()\n raise NotImplementedError(\"save not implemented\")\n\n def load(self, path: str):\n # TODO: path validation, file ext., etc.\n # TODO: data validation (pydantic?)\n json_str = jsons.load(path)\n self.deserialize(json_str)\n raise NotImplementedError(\"load not implemented\")\n\n def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n\n def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n\n def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n\n \"\"\"\n npndarray_dict wrappers\n \"\"\"\n\n def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n\n def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n\n def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n\n def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n\n def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n\n def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n\n def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n\n def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n\n def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n\n \"\"\"\n misc\n \"\"\"\n\n def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n\n @ti.func\n def __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__call__","title":"__call__(*args, **kwds)
","text":"Return the Taichi field.
Source code insrc/tolvera/state.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__getitem__","title":"__getitem__(index)
","text":"Return the Taichi field attribute.
Parameters:
Name Type Description Defaultindex
i32
Attribute index.
required Source code insrc/tolvera/state.py
@ti.func\ndef __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__init__","title":"__init__(tolvera, name, state, shape=None, osc=None, randomise=True, methods=None)
","text":"Initialise a state for T\u00f6lvera.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance to which this state belongs.
requiredname
str
Name of this state.
requiredstate
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state. Defaults to 1.
None
methods
dict[str, Any]
Flag for OSC via iipyper. Defaults to False.
None
Source code in src/tolvera/state.py
def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_from_vec","title":"attr_from_vec(attr, vec)
","text":"Wrapper for NpNdarrayDict.attr_from_vec().
Source code insrc/tolvera/state.py
def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_size","title":"attr_size(attr)
","text":"Return the size of the attribute.
Source code insrc/tolvera/state.py
def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_from_vec","title":"attr_slice_from_vec(attr, slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.attr_slice_from_vec().
Source code insrc/tolvera/state.py
def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_to_vec","title":"attr_slice_to_vec(attr, slice_args)
","text":"Wrapper for NpNdarrayDict.attr_slice_to_vec().
Source code insrc/tolvera/state.py
def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_to_vec","title":"attr_to_vec(attr)
","text":"Wrapper for NpNdarrayDict.attr_to_vec().
Source code insrc/tolvera/state.py
def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_npndarray_dict","title":"create_npndarray_dict()
","text":"Create a NpNdarrayDict for this state.
Raises:
Type DescriptionNotImplementedError
If no Numpy type is found for a Taichi type.
Source code insrc/tolvera/state.py
def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_struct_field","title":"create_struct_field(dict, shape, methods=None)
","text":"Create a Taichi struct field for this state.
Parameters:
Name Type Description Defaultdict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state.
requiredmethods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.fill","title":"fill(value)
","text":"Fill the Taichi field with a value.
Source code insrc/tolvera/state.py
def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_nddict","title":"from_nddict()
","text":"Copy data from NpNdarrayDict to Taichi field.
Raises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_vec","title":"from_vec(vec)
","text":"Wrapper for NpNdarrayDict.from_vec().
Source code insrc/tolvera/state.py
def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.randomise","title":"randomise()
","text":"Randomise the data in this state.
Source code insrc/tolvera/state.py
def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.set_from_nddict","title":"set_from_nddict(data)
","text":"Copy data from NumPy array dict to Taichi field.
Parameters:
Name Type Description Defaultdata
dict
NumPy array dict to copy.
requiredRaises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_data","title":"setup_data(dict, shape, randomise=True, methods=None)
","text":"Setup data structures and data for this state.
Parameters:
Name Type Description Defaultdict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state.
requiredrandomise
bool
Flag to randomise the data on creation. Defaults to True.
True
methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_osc","title":"setup_osc(osc=None)
","text":"Setup OSC for this state.
Parameters:
Name Type Description Defaultosc
tuple | str
(\"get\", \"set\", \"stream\"). Defaults to None.
None
Source code in src/tolvera/state.py
def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_from_vec","title":"slice_from_vec(slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.slice_from_vec().
Source code insrc/tolvera/state.py
def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_to_vec","title":"slice_to_vec(slice_args)
","text":"Wrapper for NpNdarrayDict.slice_to_vec().
Source code insrc/tolvera/state.py
def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_nddict","title":"to_nddict()
","text":"Copy data from Taichi field to NpNdarrayDict.
Raises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_vec","title":"to_vec()
","text":"Wrapper for NpNdarrayDict.to_vec().
Source code insrc/tolvera/state.py
def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict","title":"StateDict
","text":" Bases: dotdict
StateDict class for T\u00f6lvera.
This class is a dictionary of State instances, and is accessible via the 's' attribute of a T\u00f6lvera instance.
States can be created by assigning a dictionary or a tuple to a StateDict key. and can be used in Taichi scope and Python scope respectively.
Exampletv = Tolvera(**kwargs)
tv.s.mystate = { \"state\": { \"id\": (ti.i32, 0, tv.pn - 1), \"pos\": (ti.math.vec2, -1.0, 1.0), \"vel\": (ti.math.vec2, -1.0, 1.0), }, \"shape\": (tv.pn, 1), \"osc\": \"get\", \"randomise\": True }
tv.s.mystate.field.pos[0] = 0.5
Source code insrc/tolvera/state.py
class StateDict(dotdict):\n \"\"\"StateDict class for T\u00f6lvera.\n\n This class is a dictionary of State instances, and is accessible via the 's'\n attribute of a T\u00f6lvera instance.\n\n States can be created by assigning a dictionary or a tuple to a StateDict key.\n and can be used in Taichi scope and Python scope respectively.\n\n Example:\n tv = Tolvera(**kwargs)\n\n tv.s.mystate = {\n \"state\": {\n \"id\": (ti.i32, 0, tv.pn - 1),\n \"pos\": (ti.math.vec2, -1.0, 1.0),\n \"vel\": (ti.math.vec2, -1.0, 1.0),\n }, \n \"shape\": (tv.pn, 1), \n \"osc\": \"get\", \n \"randomise\": True\n }\n\n tv.s.mystate.field.pos[0] = 0.5\n \"\"\"\n def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n\n def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n\n def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n\n def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n\n def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__init__","title":"__init__(tolvera)
","text":"Initialise a StateDict for T\u00f6lvera.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance to which this StateDict belongs.
required Source code insrc/tolvera/state.py
def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default__name
str
Name of the state.
required__value
Any
State attributes.
required Source code insrc/tolvera/state.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.add","title":"add(name, kwargs)
","text":"Add a state to the StateDict.
Parameters:
Name Type Description Defaultname
str
Name of the state.
requiredkwargs
Any
State attributes.
requiredRaises:
Type DescriptionTypeError
If kwargs is not a dict or tuple.
Source code insrc/tolvera/state.py
def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.from_vec","title":"from_vec(states, vector)
","text":"Copy data from a vector to states in the StateDict.
Parameters:
Name Type Description Defaultstates
list[str]
List of state names.
requiredvector
list[float]
Vector of data to copy.
requiredRaises:
Type DescriptionException
If the vector is not the correct size.
Source code insrc/tolvera/state.py
def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.get_size","title":"get_size(states)
","text":"Return the size of the states in the StateDict.
Parameters:
Name Type Description Defaultstates
str | list[str]
State name or list of state names.
requiredReturns:
Name Type Descriptionint
int
Size of the states.
Source code insrc/tolvera/state.py
def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.set","title":"set(name, kwargs)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Defaultname
str
Name of the state.
requiredkwargs
Any
State attributes.
requiredRaises:
Type DescriptionValueError
If the state is already in the StateDict.
Exception
If the state cannot be added.
Source code insrc/tolvera/state.py
def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n
"},{"location":"reference/tolvera/taichi_/","title":"Taichi","text":"Taichi class for initialising Taichi and UI.
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi","title":"Taichi
","text":"Taichi class for initialising Taichi and UI.
This class provides a show method for showing the Taichi canvas. It is used by the TolveraContext class to display a window.
Source code insrc/tolvera/taichi_.py
class Taichi:\n \"\"\"Taichi class for initialising Taichi and UI.\n\n This class provides a show method for showing the Taichi canvas.\n It is used by the TolveraContext class to display a window.\"\"\"\n def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n\n def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n\n def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n\n def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__call__","title":"__call__(*args, **kwds)
","text":"Call Taichi window show.
Source code insrc/tolvera/taichi_.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__init__","title":"__init__(context, **kwargs)
","text":"Initialise Taichi
Parameters:
Name Type Description Defaultcontext
TolveraContext
global TolveraContext instance.
required**kwargs
Keyword arguments: gpu (str): GPU architecture to run on. Defaults to \"vulkan\". cpu (bool): Run on CPU. Defaults to False. fps (int): FPS limit. Defaults to 120. seed (int): Random seed. Defaults to time.time(). headless (bool): Run headless. Defaults to False. name (str): Window name. Defaults to \"T\u00f6lvera\".
{}
Source code in src/tolvera/taichi_.py
def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ti","title":"init_ti()
","text":"Initialise Taichi backend on selected architecture.
Source code insrc/tolvera/taichi_.py
def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ui","title":"init_ui()
","text":"Initialise Taichi UI window and canvas.
Source code insrc/tolvera/taichi_.py
def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.show","title":"show(px)
","text":"Show Taichi canvas and show window.
Source code insrc/tolvera/taichi_.py
def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n
"},{"location":"reference/tolvera/tolvera_/","title":"Tolvera","text":"Example This example demonstrates the basic usage of T\u00f6lvera. It will display a window with a black background.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Example Here's an annotated version of the above example:
# First, we import Tolvera and run() from tolvera.\nfrom tolvera import Tolvera, run\n\n# Then, we define a main function which takes in keyword arguments \n# (kwargs) from the command line.\ndef main(**kwargs):\n # Inside the main function, we initialise a Tolvera instance \n # with the given keyword arguments.\n tv = Tolvera(**kwargs)\n\n # We use the render() decorator to render the pixels.\n # This function can be named anything. \n # It will run in a loop until the user exits the program.\n @tv.render\n def _():\n # render() must return Pixels. Often, these pixels will be \n # the pixels of the Tolvera instance, accessed with tv.px.\n return tv.px\n\n# Finally, we call run() with the main function as the argument.\nif __name__ == '__main__':\n run(main)\n
When Tolvera is run, messages will be printed to the console. These messages inform the user of the status of Tolvera, during initialisation, setup, and running.
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera","title":"Tolvera
","text":"Tolvera main class.
Attributes:
Name Type Description`name`
str
Name of T\u00f6lvera instance.
`ctx`
TolveraContext
Shared TolveraContext.
`speed`
float
Global timebase speed.
`pn`
int
Number of particles.
`sn`
int
Number of species.
`p_per_s`
int
Number of particles per species.
`substep`
int
Number of substeps per frame.
`iml`
int
Dict of IML instances via anguilla.
`cv`
int
computer vision integration via OpenCV.
`osc`
int
OSC via iipyper.
`ti`
int
Taichi (graphics backend).
Source code insrc/tolvera/tolvera_.py
class Tolvera:\n \"\"\"Tolvera main class.\n\n Attributes:\n `name` (str): Name of T\u00f6lvera instance. \n `ctx` (TolveraContext): Shared TolveraContext.\n `speed` (float): Global timebase speed.\n `pn` (int): Number of particles.\n `sn` (int): Number of species.\n `p_per_s` (int): Number of particles per species.\n `substep` (int): Number of substeps per frame.\n `iml`: Dict of IML instances via anguilla.\n `cv`: computer vision integration via OpenCV.\n `osc`: OSC via iipyper.\n `ti`: Taichi (graphics backend).\n \"\"\"\n\n def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n\n def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n\n def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n self.pose = context.pose\n self.face = context.face\n self.face_mesh = context.face_mesh\n\n def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n if self.hands:\n self.hands.px = self.px\n if self.pose:\n self.pose.px = self.px\n if self.face:\n self.face.px = self.px\n if self.face_mesh:\n self.face_mesh.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n\n def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n\n def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n\n def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n\n def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.__init__","title":"__init__(**kwargs)
","text":"Initialise and setup T\u00f6lvera with given keyword arguments.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".
requiredctx
TolveraContext
TolveraContext to share. Defaults to None.
required Source code insrc/tolvera/tolvera_.py
def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.add_to_osc_map","title":"add_to_osc_map()
","text":"Add top-level T\u00f6lvera functions to OSCMap.
Source code insrc/tolvera/tolvera_.py
def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.init_context","title":"init_context(**kwargs)
","text":"Initiliase T\u00f6lveraContext with given keyword arguments.
Parameters:
Name Type Description Default**kwargs
Keyword arguments for T\u00f6lveraContext.
{}
Source code in src/tolvera/tolvera_.py
def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.randomise","title":"randomise()
","text":"Randomise particles, species, and Vera.
Source code insrc/tolvera/tolvera_.py
def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.reset","title":"reset(**kwargs)
","text":"Reset T\u00f6lvera with given keyword arguments. This will call setup() with given keyword arguments, but not init().
Parameters:
Name Type Description Default**kwargs
Keyword arguments for reset.
{}
Source code in src/tolvera/tolvera_.py
def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.setup","title":"setup(**kwargs)
","text":"Setup T\u00f6lvera with given keyword arguments. This can be called multiple throughout the lifetime of a T\u00f6lvera instance.
Parameters:
Name Type Description Default**kwargs
Keyword arguments for setup. speed (float): Global timebase speed. Defaults to 1. particles (int): Number of particles. Defaults to 1024. species (int): Number of species. Defaults to 4. substep (int): Number of substeps per frame. Defaults to 1.
{}
Source code in src/tolvera/tolvera_.py
def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n if self.hands:\n self.hands.px = self.px\n if self.pose:\n self.pose.px = self.px\n if self.face:\n self.face.px = self.px\n if self.face_mesh:\n self.face_mesh.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.share_context","title":"share_context(context)
","text":"Share T\u00f6lveraContext with another T\u00f6lvera instance.
Parameters:
Name Type Description Defaultcontext
T\u00f6lveraContext to share.
required Source code insrc/tolvera/tolvera_.py
def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n self.pose = context.pose\n self.face = context.face\n self.face_mesh = context.face_mesh\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.speed","title":"speed(speed=None)
","text":"Set or get global timebase speed.
Source code insrc/tolvera/tolvera_.py
def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n
"},{"location":"reference/tolvera/utils/","title":"Utils","text":"Utility functions for Tolvera.
"},{"location":"reference/tolvera/utils/#tolvera.utils.CONSTS","title":"CONSTS
","text":"Dict of CONSTS that can be used in Taichi scope
Source code insrc/tolvera/utils.py
class CONSTS:\n \"\"\"\n Dict of CONSTS that can be used in Taichi scope\n \"\"\"\n\n def __init__(self, dict: dict[str, (DataType, Any)]):\n self.struct = ti.types.struct(**{k: v[0] for k, v in dict.items()})\n self.consts = self.struct(**{k: v[1] for k, v in dict.items()})\n\n def __getattr__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n\n def __getitem__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.dotdict","title":"dotdict
","text":" Bases: dict
dot.notation access to dictionary attributes
Source code insrc/tolvera/utils.py
class dotdict(dict):\n \"\"\"dot.notation access to dictionary attributes\"\"\"\n __getattr__ = dict.get\n __setattr__ = dict.__setitem__\n __delattr__ = dict.__delitem__\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_and_validate_slice","title":"create_and_validate_slice(arg, target_array)
","text":"Creates and validates a slice object based on the target array.
Source code insrc/tolvera/utils.py
def create_and_validate_slice(\n arg: Union[int, tuple[int, ...], slice], target_array: np.ndarray\n) -> slice:\n \"\"\"\n Creates and validates a slice object based on the target array.\n \"\"\"\n try:\n slice_obj = create_safe_slice(arg)\n if not validate_slice(slice_obj, target_array):\n raise ValueError(f\"Invalid slice: {slice_obj}\")\n return slice_obj\n except Exception as e:\n raise type(e)(f\"Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_ndslices","title":"create_ndslices(dims)
","text":"Create a multi-dimensional slice from a list of tuples.
Parameters:
Name Type Description Defaultdims
list[tuple]
A list of tuples containing the slice parameters for each dimension.
requiredReturns:
Type Descriptions_
np.s_: A multi-dimensional slice object.
Source code insrc/tolvera/utils.py
def create_ndslices(dims: list[tuple]) -> np.s_:\n \"\"\"\n Create a multi-dimensional slice from a list of tuples.\n\n Args:\n dims (list[tuple]): A list of tuples containing the slice parameters for each dimension.\n\n Returns:\n np.s_: A multi-dimensional slice object.\n \"\"\"\n return np.s_[tuple(slice(*dim) if isinstance(dim, tuple) else dim for dim in dims)]\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_safe_slice","title":"create_safe_slice(arg)
","text":"Creates a slice object based on the input argument.
Parameters:
Name Type Description Defaultarg
(int, tuple, slice)
The argument for creating the slice. It can be an integer, a tuple with slice parameters, or a slice object itself.
requiredReturns:
Name Type Descriptionslice
slice
A slice object created based on the provided argument.
Source code insrc/tolvera/utils.py
def create_safe_slice(arg: Union[int, tuple[int, ...], slice]) -> slice:\n \"\"\"\n Creates a slice object based on the input argument.\n\n Args:\n arg (int, tuple, slice): The argument for creating the slice. It can be an integer,\n a tuple with slice parameters, or a slice object itself.\n\n Returns:\n slice: A slice object created based on the provided argument.\n \"\"\"\n try:\n if isinstance(arg, slice):\n return arg\n elif isinstance(arg, tuple):\n return slice(*arg)\n elif isinstance(arg, int):\n return slice(arg, arg + 1)\n else:\n raise TypeError(f\"Invalid slice type: {type(arg)} {arg}\")\n except Exception as e:\n raise type(e)(f\"[create_safe_slice] Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.flatten","title":"flatten(lst)
","text":"Flatten a nested list or return a non-nested list as is.
Source code insrc/tolvera/utils.py
def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n return [item for sublist in lst for item in sublist]\n return lst\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.generic_slice","title":"generic_slice(array, slice_params)
","text":"Slices a NumPy array based on a tuple of slice parameters for each dimension.
Parameters:
Name Type Description Defaultarray
ndarray
The array to be sliced.
requiredslice_params
tuple
A tuple where each item is either an integer, a tuple with slice parameters, or a slice object.
requiredReturns:
Name Type Descriptionndarray
ndarray
The sliced array.
Source code insrc/tolvera/utils.py
def generic_slice(\n array: np.ndarray,\n slice_params: Union[\n tuple[Union[int, tuple[int, ...], slice], ...],\n Union[int, tuple[int, ...], slice],\n ],\n) -> np.ndarray:\n \"\"\"\n Slices a NumPy array based on a tuple of slice parameters for each dimension.\n\n Args:\n array (np.ndarray): The array to be sliced.\n slice_params (tuple): A tuple where each item is either an integer, a tuple with\n slice parameters, or a slice object.\n\n Returns:\n ndarray: The sliced array.\n \"\"\"\n if not isinstance(slice_params, tuple):\n slice_params = (slice_params,)\n slices = tuple(create_safe_slice(param) for param in slice_params)\n return array.__getitem__(slices)\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.time_function","title":"time_function(func, *args, **kwargs)
","text":"Time how long it takes to run a function and print the result
Source code insrc/tolvera/utils.py
def time_function(func, *args, **kwargs):\n \"\"\"Time how long it takes to run a function and print the result\"\"\"\n start = time.time()\n ret = func(*args, **kwargs)\n end = time.time()\n print(f\"[Tolvera.utils] {func.__name__}() ran in {end-start:.4f}s\")\n if ret is not None:\n return (ret, end - start)\n return end - start\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_json_path","title":"validate_json_path(path)
","text":"Validate a JSON file path. It uses validate_path for initial validation.
Parameters:
Name Type Description Defaultpath
str
The JSON file path to be validated.
requiredReturns:
Name Type Descriptionbool
bool
True if the path is a valid JSON file path, raises an exception otherwise.
Raises:
Type DescriptionValueError
If the path does not end with '.json'.
Source code insrc/tolvera/utils.py
def validate_json_path(path: str) -> bool:\n \"\"\"\n Validate a JSON file path. It uses validate_path for initial validation.\n\n Args:\n path (str): The JSON file path to be validated.\n\n Returns:\n bool: True if the path is a valid JSON file path, raises an exception otherwise.\n\n Raises:\n ValueError: If the path does not end with '.json'.\n \"\"\"\n # Using validate_path for basic path validation\n validate_path(path)\n\n if not path.endswith(\".json\"):\n raise ValueError(\"Path should end with '.json'\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_path","title":"validate_path(path)
","text":"Validate a path using os.path and pathlib.
Parameters:
Name Type Description Defaultpath
str
The path to be validated.
requiredReturns:
Name Type Descriptionbool
bool
True if the path is valid, raises an exception otherwise.
Raises:
Type DescriptionTypeError
If the input is not a string.
FileNotFoundError
If the path does not exist.
PermissionError
If the path is not accessible.
Source code insrc/tolvera/utils.py
def validate_path(path: str) -> bool:\n \"\"\"\n Validate a path using os.path and pathlib.\n\n Args:\n path (str): The path to be validated.\n\n Returns:\n bool: True if the path is valid, raises an exception otherwise.\n\n Raises:\n TypeError: If the input is not a string.\n FileNotFoundError: If the path does not exist.\n PermissionError: If the path is not accessible.\n \"\"\"\n if not isinstance(path, str):\n raise TypeError(f\"Expected a string for path, but received {type(path)}\")\n\n path_obj = Path(path)\n if not path_obj.is_file():\n raise FileNotFoundError(f\"The path {path} does not exist or is not a file\")\n\n if not os.access(path, os.R_OK):\n raise PermissionError(f\"The path {path} is not accessible\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_slice","title":"validate_slice(slice_obj, target_array)
","text":"Validates if the given slice object is applicable to the target ndarray.
Parameters:
Name Type Description Defaultslice_obj
tuple[slice]
A tuple containing slice objects for each dimension.
requiredtarget_array
ndarray
The array to be sliced.
requiredReturns:
Name Type Descriptionbool
bool
True if the slice is valid for the given array, False otherwise.
Source code insrc/tolvera/utils.py
def validate_slice(slice_obj: tuple[slice], target_array: np.ndarray) -> bool:\n \"\"\"\n Validates if the given slice object is applicable to the target ndarray.\n\n Args:\n slice_obj (tuple[slice]): A tuple containing slice objects for each dimension.\n target_array (np.ndarray): The array to be sliced.\n\n Returns:\n bool: True if the slice is valid for the given array, False otherwise.\n \"\"\"\n if len(slice_obj) != target_array.ndim:\n return False\n\n for sl, size in zip(slice_obj, target_array.shape):\n # Check if slice start and stop are within the dimension size\n start, stop, _ = sl.indices(size)\n if not (0 <= start < size and (0 <= stop <= size or stop == -1)):\n return False\n return True\n
"},{"location":"reference/tolvera/mp/face/","title":"Face","text":""},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.FaceKeyPoint","title":"FaceKeyPoint
","text":" Bases: IntEnum
The enum type of the six face detection key points.
Source code insrc/tolvera/mp/face.py
class FaceKeyPoint(enum.IntEnum):\n \"\"\"The enum type of the six face detection key points.\"\"\"\n RIGHT_EYE = 0\n LEFT_EYE = 1\n NOSE_TIP = 2\n MOUTH_CENTER = 3\n RIGHT_EAR_TRAGION = 4\n LEFT_EAR_TRAGION = 5\n
"},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace","title":"MPFace
","text":"Source code in src/tolvera/mp/face.py
@ti.data_oriented\nclass MPFace:\n def __init__(self, context, **kwargs) -> None:\n self.ctx = context\n self.kwargs = kwargs\n self.n_points = 6\n self.max_faces = kwargs.get('max_faces', 4)\n\n self.config = {\n 'min_detection_confidence': kwargs.get('detection_con', .5),\n 'model_selection': kwargs.get('model_selection', 0),\n }\n\n \"\"\"\n TODO: add bbox as separate tv.s.faces_bbox?\n format: RELATIVE_BOUNDING_BOX\n relative_bounding_box {\n xmin: 0.482601523\n ymin: 0.402242899\n width: 0.162447035\n height: 0.2887941\n }\n \"\"\"\n\n self.faces_np = {\n 'pxnorm':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n 'px':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n }\n self.ctx.s.faces = {\n 'state': {\n 'pxnorm': (ti.math.vec2, 0.0, 1.0),\n 'px': (ti.math.vec2, 0.0, 1.0),\n # 'metres': (ti.math.vec3, 0.0, 1.0), # face_world_landmarks\n },\n 'shape': (self.max_faces, self.n_points)\n }\n\n self.mpFace = mp.solutions.face_detection\n self.face = self.mpFace.FaceDetection(**self.config)\n self.detected = ti.field(ti.i32, shape=())\n\n self.updater = Updater(self.detect, kwargs.get('face_detect_rate', 10))\n\n def detect(self, frame=None):\n if frame is None: return\n self.results = self.face.process(frame)\n if self.results.detections is None:\n self.ctx.s.faces.fill(0.)\n self.detected[None] = -1\n return\n\n if self.results.detections:\n for i, face in enumerate(self.results.detections):\n for j, lm in enumerate(face.location_data.relative_keypoints):\n pxnorm = np.array([1-lm.x, 1-lm.y])\n px = np.array([self.ctx.x*(1-lm.x), self.ctx.y*(1-lm.y)])\n self.faces_np['pxnorm'][i, j] = pxnorm\n self.faces_np['px'][i, j] = px\n self.ctx.s.faces.set_from_nddict(self.faces_np)\n\n self.detected[None] = len(self.results.detections)\n\n @ti.kernel\n def draw(self):\n if self.detected[None] > 0:\n self.draw_face_lms(5, ti.Vector([1, 1, 1, 1]))\n\n @ti.func\n def draw_face_lms(self, r, rgba):\n for i, lm in ti.ndrange(self.detected[None], self.n_conns):\n self.draw_lm(i, lm, r, rgba)\n\n @ti.func\n def draw_lm(self, face: ti.i32, lm: ti.i32, r: ti.i32, rgba: ti.math.vec4):\n px = self.ctx.s.faces[face, lm].px\n cx = ti.cast(px.x, ti.i32)\n cy = ti.cast(px.y, ti.i32)\n self.px.circle(cx, cy, r, rgba)\n\n def landmark_name_from_index(self, index):\n return FaceKeyPoint(index).name\n\n def landmark_index_from_name(self, name):\n return FaceKeyPoint[name].value\n\n @ti.kernel\n def get_landmark(self, face: ti.i32, landmark: ti.i32) -> ti.math.vec2:\n return self.ctx.s.faces[landmark].px\n\n def __call__(self, frame):\n self.updater(frame)\n
"},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace.config","title":"config = {'min_detection_confidence': kwargs.get('detection_con', 0.5), 'model_selection': kwargs.get('model_selection', 0)}
instance-attribute
","text":"add bbox as separate tv.s.faces_bbox? format: RELATIVE_BOUNDING_BOX relative_bounding_box { xmin: 0.482601523 ymin: 0.402242899 width: 0.162447035 height: 0.2887941 }
"},{"location":"reference/tolvera/mp/face_mesh/","title":"Face mesh","text":""},{"location":"reference/tolvera/mp/face_mesh_connections/","title":"Face mesh connections","text":"MediaPipe FaceMesh connections.
"},{"location":"reference/tolvera/mp/hands/","title":"Hands","text":""},{"location":"reference/tolvera/mp/hands/#tolvera.mp.hands.HandLandmark","title":"HandLandmark
","text":" Bases: IntEnum
The 21 hand landmarks.
Source code insrc/tolvera/mp/hands.py
class HandLandmark(enum.IntEnum):\n \"\"\"The 21 hand landmarks.\"\"\"\n WRIST = 0\n THUMB_CMC = 1\n THUMB_MCP = 2\n THUMB_IP = 3\n THUMB_TIP = 4\n INDEX_FINGER_MCP = 5\n INDEX_FINGER_PIP = 6\n INDEX_FINGER_DIP = 7\n INDEX_FINGER_TIP = 8\n MIDDLE_FINGER_MCP = 9\n MIDDLE_FINGER_PIP = 10\n MIDDLE_FINGER_DIP = 11\n MIDDLE_FINGER_TIP = 12\n RING_FINGER_MCP = 13\n RING_FINGER_PIP = 14\n RING_FINGER_DIP = 15\n RING_FINGER_TIP = 16\n PINKY_MCP = 17\n PINKY_PIP = 18\n PINKY_DIP = 19\n PINKY_TIP = 20\n
"},{"location":"reference/tolvera/mp/pose/","title":"Pose","text":""},{"location":"reference/tolvera/mp/pose/#tolvera.mp.pose.PoseLandmark","title":"PoseLandmark
","text":" Bases: IntEnum
The 33 pose landmarks.
Source code insrc/tolvera/mp/pose.py
class PoseLandmark(enum.IntEnum):\n \"\"\"The 33 pose landmarks.\"\"\"\n NOSE = 0\n LEFT_EYE_INNER = 1\n LEFT_EYE = 2\n LEFT_EYE_OUTER = 3\n RIGHT_EYE_INNER = 4\n RIGHT_EYE = 5\n RIGHT_EYE_OUTER = 6\n LEFT_EAR = 7\n RIGHT_EAR = 8\n MOUTH_LEFT = 9\n MOUTH_RIGHT = 10\n LEFT_SHOULDER = 11\n RIGHT_SHOULDER = 12\n LEFT_ELBOW = 13\n RIGHT_ELBOW = 14\n LEFT_WRIST = 15\n RIGHT_WRIST = 16\n LEFT_PINKY = 17\n RIGHT_PINKY = 18\n LEFT_INDEX = 19\n RIGHT_INDEX = 20\n LEFT_THUMB = 21\n RIGHT_THUMB = 22\n LEFT_HIP = 23\n RIGHT_HIP = 24\n LEFT_KNEE = 25\n RIGHT_KNEE = 26\n LEFT_ANKLE = 27\n RIGHT_ANKLE = 28\n LEFT_HEEL = 29\n RIGHT_HEEL = 30\n LEFT_FOOT_INDEX = 31\n RIGHT_FOOT_INDEX = 32\n
"},{"location":"reference/tolvera/osc/maxmsp/","title":"Maxmsp","text":""},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher","title":"MaxPatcher
","text":"TODO: copy-paste using stdout TODO: add scale objects before send and after receive TODO: add default values via loadbangs TODO: move udpsend/udpreceive to the top left TODO: dict of object ids TODO: add abstraction i/o messages e.g. param names, state save/load/dumps
Source code insrc/tolvera/osc/maxmsp.py
class MaxPatcher:\n \"\"\"\n TODO: copy-paste using stdout\n TODO: add scale objects before send and after receive\n TODO: add default values via loadbangs\n TODO: move udpsend/udpreceive to the top left\n TODO: dict of object ids\n TODO: add abstraction i/o messages e.g. param names, state save/load/dumps\n \"\"\"\n\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n v=\"8.5.4\",\n ) -> None:\n self.patch = {\n \"patcher\": {\n \"fileversion\": 1,\n \"appversion\": {\n \"major\": v[0],\n \"minor\": v[2],\n \"revision\": v[4],\n \"architecture\": \"x64\",\n \"modernui\": 1,\n },\n \"classnamespace\": \"box\",\n \"rect\": [x, y, w, h],\n \"bglocked\": 0,\n \"openinpresentation\": 0,\n \"default_fontsize\": 12.0,\n \"default_fontface\": 0,\n \"default_fontname\": \"Arial\",\n \"gridonopen\": 1,\n \"gridsize\": [15.0, 15.0],\n \"gridsnaponopen\": 1,\n \"objectsnaponopen\": 1,\n \"statusbarvisible\": 2,\n \"toolbarvisible\": 1,\n \"lefttoolbarpinned\": 0,\n \"toptoolbarpinned\": 0,\n \"righttoolbarpinned\": 0,\n \"bottomtoolbarpinned\": 0,\n \"toolbars_unpinned_last_save\": 0,\n \"tallnewobj\": 0,\n \"boxanimatetime\": 200,\n \"enablehscroll\": 1,\n \"enablevscroll\": 1,\n \"devicewidth\": 0.0,\n \"description\": \"\",\n \"digest\": \"\",\n \"tags\": \"\",\n \"style\": \"\",\n \"subpatcher_template\": \"\",\n \"assistshowspatchername\": 0,\n \"boxes\": [],\n \"lines\": [],\n \"dependency_cache\": [],\n \"autosave\": 0,\n }\n }\n self.types = {\n \"print\": \"print\",\n \"message\": \"message\",\n \"object\": \"newobj\",\n \"comment\": \"comment\",\n \"slider\": \"slider\",\n \"float\": \"flonum\",\n \"int\": \"number\",\n \"bang\": \"button\",\n }\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.init()\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 22.0 # default height (pixels)\n self.s_x, self.s_y = 30, 125 # insertion point\n self.r_x, self.r_y = 30, 575 # insertion point\n self.patcher_ids = {}\n self.patcher_ids[\"send_id\"] = self.add_osc_send(\n self.osc.host, self.osc.port, self.s_x, 30, print_label=\"sent\"\n )\n self.patcher_ids[\"receive_id\"] = self.add_osc_receive(\n self.client_port, self.s_x + 150, 30, print_label=\"received\"\n )\n self.add_comment(\"Max \u2192 Python\", self.s_x, self.s_y, 24)\n self.add_comment(\"Python \u2192 Max\", self.r_x, self.r_y, 24)\n self.s_y += 50\n self.r_y += 50\n self.save(self.filepath)\n\n def add_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id, box = self.create_box(box_type, inlets, outlets, x, y, w, h)\n return self._add_box(box)\n\n def _add_box(self, box):\n self.patch[\"patcher\"][\"boxes\"].append(box)\n return self.id_from_str(box[\"box\"][\"id\"])\n\n def create_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id = len(self.patch[\"patcher\"][\"boxes\"]) + 1\n box = {\n \"box\": {\n \"id\": \"obj-\" + str(box_id),\n \"maxclass\": self.types[box_type],\n \"numinlets\": inlets,\n \"numoutlets\": outlets,\n \"patching_rect\": [x, y, w, h],\n }\n }\n if outlets > 0:\n if outlets == 1:\n box[\"box\"][\"outlettype\"] = [\"\"]\n match box_type:\n case \"int\" | \"float\" | \"bang\":\n box[\"box\"][\"outlettype\"] = [\"\", \"bang\"]\n return box_id, box\n\n def add_object(self, text, inlets, outlets, x, y):\n box_id, box = self.create_box(\n \"object\", inlets, outlets, x, y, len(text) * self.w\n )\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_message(self, text, x, y):\n box_id, box = self.create_box(\"message\", 2, 1, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_comment(self, text, x, y, fontsize=12):\n box_id, box = self.create_box(\"comment\", 0, 0, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n box[\"box\"][\"fontsize\"] = fontsize\n self._add_box(box)\n return box_id\n\n def add_bang(self, x, y):\n box_id, box = self.create_box(\"bang\", 1, 1, x, y, 20.0)\n self._add_box(box)\n return box_id\n\n def add_slider(self, x, y, min_val, size, float=False):\n box_id, box = self.create_box(\"slider\", 1, 1, x, y, 20.0, 140.0)\n if float:\n box[\"box\"][\"floatoutput\"] = 1\n box[\"box\"][\"min\"] = min_val\n box[\"box\"][\"size\"] = size\n self._add_box(box)\n return box_id\n\n def connect(self, src, src_outlet, dst, dst_inlet):\n patchline = {\n \"patchline\": {\n \"destination\": [\"obj-\" + str(dst), dst_inlet],\n \"source\": [\"obj-\" + str(src), src_outlet],\n }\n }\n self.patch[\"patcher\"][\"lines\"].append(patchline)\n return patchline\n\n def save(self, name):\n with open(name + \".maxpat\", \"w\") as f:\n f.write(json.dumps(self.patch, indent=2))\n\n def load(self, name):\n with open(name + \".maxpat\", \"r\") as f:\n self.patch = json.loads(f.read())\n\n def get_box_by_id(self, id):\n for box in self.patch[\"patcher\"][\"boxes\"]:\n if self.id_from_str(box[\"box\"][\"id\"]) == id:\n return box\n return None\n\n def str_from_id(self, id):\n return \"obj-\" + str(id)\n\n def id_from_str(self, obj_str):\n return int(obj_str[4:])\n\n def add_osc_send(self, ip, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"r send\", 0, 1, x, y)\n box_id = self.add_object(\"udpsend \" + ip + \" \" + str(port), 1, 0, x, y + 25)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 50, y)\n self.connect(box_id_0, 0, box_id, 0)\n self.connect(box_id_0, 0, print_id, 0)\n return box_id_0\n return box_id\n\n def add_osc_receive(self, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"s receive\", 0, 1, x, y + 25)\n box_id = self.add_object(\"udpreceive \" + str(port), 1, 1, x, y)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 60, y + 25)\n self.connect(box_id, 0, print_id, 0)\n self.connect(box_id, 0, box_id_0, 0)\n return box_id_0\n return box_id\n\n def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n\n def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n\n def add_param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * 52.0)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.add_comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.add_comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]}-{p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n def add_osc_send_msg(self, x, y, path):\n msg_id = self.add_message(path, x, y + 225 + self.h)\n send_id = self.add_object(\"s send\", 1, 0, x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n def add_osc_receive_msg(self, x, y, path):\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + 225 + self.h)\n msg_id = self.add_message(path, x, y + 250 + self.h)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def add_send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, p in enumerate(f_p):\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_send_list_func(self, f):\n raise NotImplementedError(\"add_send_list_func not implemented yet\")\n\n def add_receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for p in f_p:\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[p].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_receive_list_func(self, f):\n raise NotImplementedError(\"add_receive_list_func not implemented yet\")\n\n def _msg_args(self, args):\n return \" \".join([\"$\" + str(i + 1) for i in range(len(args))])\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"i\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_receive_with_controls","title":"add_osc_receive_with_controls(x, y, path, parameters)
","text":"[comment path] [r receive] | [route /path] | | [unpack f f f ...] [print /path] | [slider] ... | [number] ... | [s arg_name] [comment path_arg_name] [comment type min-max]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_route","title":"add_osc_route(port, x, y, print=True, print_label=None)
","text":"[route path] [s name] print ? [r name]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_send_with_controls","title":"add_osc_send_with_controls(x, y, path, parameters)
","text":"[comment path] [comment args] [r path_arg_name] sliders | | [pak $1 $2 $3 ...] | [msg /path $1 $2 $3 ...] | [s send]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_sliders","title":"add_sliders(x, y, sliders)
","text":"sliders = [ { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 }, ]
[slider] ... | [number] ...
Source code insrc/tolvera/osc/maxmsp.py
def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n
"},{"location":"reference/tolvera/osc/osc/","title":"Osc","text":""},{"location":"reference/tolvera/osc/oscmap/","title":"Oscmap","text":""},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap","title":"OSCMap
","text":"OSCMap maps OSC messages to functions It creates a Max/MSP patcher that can be used to control the OSCMap It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages
Source code insrc/tolvera/osc/oscmap.py
class OSCMap:\n \"\"\"\n OSCMap maps OSC messages to functions\n It creates a Max/MSP patcher that can be used to control the OSCMap\n It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages\n \"\"\"\n\n def __init__(\n self,\n osc: iiOSC,\n client_name=\"client\",\n patch_type=\"Max\", # | \"Pd\"\n patch_filepath=\"osc_controls\",\n create_patch=True,\n pd_net_or_udp=\"udp\",\n pd_bela=False,\n export=None, # 'JSON' | 'XML' | True\n ) -> None:\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.dict = {\"send\": {}, \"receive\": {}}\n self.create_patch = create_patch\n self.patch_filepath = patch_filepath\n self.patch_type = patch_type\n if create_patch is True:\n self.init_patcher(patch_type, patch_filepath, pd_net_or_udp, pd_bela)\n if export is not None:\n assert (\n export == \"JSON\" or export == \"XML\" or export == True\n ), \"export must be 'JSON', 'XML' or True\"\n self.export = export\n\n def init_patcher(self, patch_type, patch_filepath, pd_net_or_udp, pd_bela):\n # create self.patch_dir if it doesn't exist\n self.patch_dir = \"pd\" if patch_type == \"Pd\" else \"max\"\n if not os.path.exists(self.patch_dir):\n print(f\"Creating {self.patch_dir} directory...\")\n os.makedirs(self.patch_dir)\n self.patch_appendix = \"_local\" if self.osc.host == \"127.0.0.1\" else \"_remote\"\n self.patch_filepath = (\n self.patch_dir + \"/\" + patch_filepath + self.patch_appendix\n )\n if patch_type == \"Max\":\n self.patcher = MaxPatcher(self.osc, self.client_name, self.patch_filepath)\n elif patch_type == \"Pd\":\n if pd_bela is True:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n bela=True,\n )\n else:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n )\n else:\n assert False, \"`patch_type` must be 'Max' or 'Pd'\"\n\n def add(self, **kwargs):\n print(\n \"DeprecationError: OSCMap.add() has been split into separate functions: use `send_args`, `send_list`, `receive_args` or `receive_list` instead!\"\n )\n exit()\n\n def map_func_to_dict(self, func, kwargs):\n if \"name\" not in kwargs:\n n = func.__name__\n address = \"/\" + n.replace(\"_\", \"/\")\n else:\n if isinstance(kwargs[\"name\"], str):\n n = kwargs[\"name\"]\n address = \"/\" + kwargs[\"name\"].replace(\"_\", \"/\")\n else:\n raise TypeError(\n f\"OSC func name must be string, found {str(type(kwargs['name']))}\"\n )\n # TODO: Move this into specific send/receive functions\n params = {\n k: v\n for k, v in kwargs.items()\n if k != \"count\" and k != \"send_mode\" and k != \"length\" and k != \"name\"\n }\n # TODO: turn params into dict with type hints (see export_dict)\n hints = get_type_hints(func)\n f = {\"f\": func, \"name\": n, \"address\": address, \"params\": params, \"hints\": hints}\n return f\n\n \"\"\"\n send args\n \"\"\"\n\n def send_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_args(func, kwargs)\n return func()\n\n default_args = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_send_args(self, func, kwargs):\n self.add_send_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_args_to_patcher(func)\n\n def add_send_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n # count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"args\"\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_args_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_args_func(f)\n\n \"\"\"\n send list\n \"\"\"\n\n def send_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLFun2OSC.update\n # return func()\n\n default_arg = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"length\" and a != \"name\"\n ]\n wrapper(default_arg)\n return wrapper\n\n return decorator\n\n def add_send_list(self, func, kwargs):\n self.add_send_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_list_to_patcher(func)\n\n def add_send_list_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n # TODO: Hack for send_list_inline which doesn't have a return type hint\n if \"return\" in f[\"hints\"]:\n hint = f[\"hints\"][\"return\"]\n else:\n hint = list[float]\n assert hint == list[float], \"send_list can only send list[float], found \" + str(\n hint\n )\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_list_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_list_func(f)\n\n def send_list_inline(self, name: str, sender_func, length: int, send_mode=\"broadcast\", count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"send_mode\": send_mode, \"count\": count}}\n self.send_list(**kwargs)(sender_func)\n\n \"\"\"\n send kwargs\n \"\"\"\n\n def send_kwargs(self, **kwargs):\n raise NotImplementedError(\"send_kwargs not implemented yet\")\n\n \"\"\"\n receive args\n \"\"\"\n\n def receive_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_args(func, kwargs)\n return func(*args)\n\n default_args = [\n kwargs[a][0] for a in kwargs if a != \"count\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_receive_args(self, func, kwargs):\n f = self.add_receive_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_args_to_patcher(f)\n\n def add_receive_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n f[\"updater\"] = OSCReceiveUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"args\"\n self.dict[\"receive\"][f[\"name\"]] = f\n return f\n\n def add_receive_args_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_args_func(f)\n\n def receive_args_inline(self, name: str, receiver_func, **kwargs):\n kwargs = {**kwargs, **{\"count\": 1, \"name\": name}}\n self.receive_args(**kwargs)(receiver_func)\n\n \"\"\"\n receive list\n \"\"\"\n\n def receive_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLOSC2Vec.init\n # return func(*args)\n\n # TODO: This probably shouldn't be here...\n randomised_list = self.randomise_list(\n kwargs[\"length\"], kwargs[\"vector\"][1], kwargs[\"vector\"][2]\n )\n wrapper(randomised_list)\n return wrapper\n\n return decorator\n\n def randomise_list(self, length, min, max):\n return min + (np.random.rand(length).astype(np.float32) * (max - min))\n\n def add_receive_list(self, func, kwargs):\n f = self.add_receive_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_list_to_patcher(f)\n\n def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n\n def add_receive_list_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_list_func(f)\n\n def receive_list_inline(self, name: str, receiver_func, length: int, count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"count\": count, \"vector\": (0, 0, 1)}}\n self.receive_list(**kwargs)(receiver_func)\n\n def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n ):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n\n \"\"\"\n receive kwargs\n \"\"\"\n\n def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n\n \"\"\"\n xml / json export\n \"\"\"\n\n def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n\n def xml_add_args_params(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Params\": str(len(params)),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n for i, p in enumerate(params):\n # TODO: This should already be defined by this point\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[i].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"Param\", **kw)\n\n def xml_add_list_param(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n length = f[\"length\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Length\": str(length),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n p = list(params.keys())[0]\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[0].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"ParamList\", **kw)\n\n def export_update(self, root):\n tree = ET.ElementTree(root)\n ET.indent(tree, space=\"\\t\", level=0)\n if self.export == \"XML\":\n self.save_xml(tree, root)\n elif self.export == \"JSON\":\n self.save_json(root)\n elif self.export == True:\n self.save_xml(tree, root)\n self.save_json(root)\n\n def save_xml(self, tree, root):\n tree.write(self.patch_filepath + \".xml\")\n print(f\"Exported OSCMap to {self.patch_filepath}.xml\")\n\n def save_json(self, xml_root):\n # TODO: params should be `params: []` and not `param: {}, param: {}, ...`\n json_dict = self.xml_to_json(\n ET.tostring(xml_root, encoding=\"utf8\", method=\"xml\")\n )\n with open(self.patch_filepath + \".json\", \"w\") as f:\n f.write(json_dict)\n print(f\"Exported OSCMap to {self.patch_filepath}.json\")\n\n def etree_to_dict(self, t):\n tag = self.pascal_to_camel(t.tag)\n d = {tag: {} if t.attrib else None}\n children = list(t)\n if children:\n dd = {}\n for dc in map(self.etree_to_dict, children):\n for k, v in dc.items():\n try:\n dd[k].append(v)\n except KeyError:\n dd[k] = [v]\n d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}\n if t.attrib:\n d[tag].update((self.pascal_to_camel(k), v) for k, v in t.attrib.items())\n if t.text:\n text = t.text.strip()\n if children or t.attrib:\n if text:\n d[tag][\"#text\"] = text\n else:\n d[tag] = text\n return d\n\n def pascal_to_camel(self, s):\n return s[0].lower() + s[1:]\n\n def xml_to_json(self, xml_str):\n e = ET.ElementTree(ET.fromstring(xml_str))\n return json.dumps(self.etree_to_dict(e.getroot()), indent=4)\n\n def update(self):\n for k, v in self.dict[\"send\"].items():\n if \"updater\" in v:\n ret = v[\"updater\"]()\n # v['updater']()\n for k, v in self.dict[\"receive\"].items():\n v[\"updater\"]()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n self.update()\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.add_receive_list_to_osc_map","title":"add_receive_list_to_osc_map(func, kwargs)
","text":"TODO: Should this support list[float] only, or list[int] list[str] etc?
Source code insrc/tolvera/osc/oscmap.py
def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.export_dict","title":"export_dict()
","text":"Save the OSCMap dict as XML
Source code insrc/tolvera/osc/oscmap.py
def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_kwargs","title":"receive_kwargs(**kwargs)
","text":"Same as receive_args but with named params
Source code insrc/tolvera/osc/oscmap.py
def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_list_with_idx","title":"receive_list_with_idx(name, receiver, idx_len, vec_len, attr=None)
","text":"Create an OSC list handler that assumes that the first idx_len
values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e. /name idx0 idx1 ... idxN arg0 arg1 ... argM ... receiver((idx0 idx1 ... idxN), args) Intended as a utility function to be used by external classes where it's not possible to use a decorator like receive_list
.
src/tolvera/osc/oscmap.py
def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n
"},{"location":"reference/tolvera/osc/pd/","title":"Pd","text":""},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher","title":"PdPatcher
","text":"Source code in src/tolvera/osc/pd.py
class PdPatcher:\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n net_or_udp=\"udp\",\n bela=False,\n ) -> None:\n self.x, self.y, self.w, self.h = x, y, w, h\n self.patch_objects = [f\"#N canvas {x} {y} {w} {h} 12;\\n\"]\n self.patch_connections = []\n self.types = {\n \"object\": \"obj\",\n \"message\": \"msg\",\n \"number\": \"floatatom\",\n \"symbol\": \"symbolatom\",\n \"toggle\": \"toggle\",\n \"slider\": \"vslider\",\n \"bang\": \"bng\",\n \"comment\": \"text\",\n }\n self.patch_ids = {}\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.net_or_udp = net_or_udp\n self.bela = bela\n self.init()\n\n \"\"\"\n init\n \"\"\"\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 27.0 # default height (pixels)\n self.line = 300 # default [line] (timed ramp generator) time in milliseconds\n self.param_width = 70\n self.s_x, self.s_y = 30, 30 # sends insertion point\n self.r_x, self.r_y = 30, 530 # receives\u00a0insertion point\n self.comment(\"Pd \u2192 Python\", self.s_x, self.s_y)\n self.comment(\"===========\", self.s_x, self.s_y + self.h / 2)\n self.patch_ids[\"send\"] = self.osc_send(\n self.osc.host, self.osc.port, self.s_x, self.s_y + self.h * 2\n )\n self.comment(\"Python \u2192 Pd\", self.r_x, self.r_y)\n self.comment(\"===========\", self.r_x, self.r_y + self.h / 2)\n self.patch_ids[\"receive\"] = self.osc_receive(\n self.client_port, self.r_x, self.r_y + self.h * 2\n )\n self.s_x += 300\n self.r_x += 300\n if self.bela:\n self.create_bela_main()\n self.save(self.filepath)\n\n def create_bela_main(self):\n if self.filepath.startswith(\"pd/\"):\n abstraction = self.filepath[3:]\n with open(\"pd/_main.pd\", \"w\") as f:\n f.write(f\"#N canvas {self.x} {self.y} {self.w} {self.h} 12;\\n\")\n f.write(f\"#X obj {30} {30} {abstraction};\\n\")\n\n \"\"\"\n basic objects\n \"\"\"\n\n def box(self, box_type, x, y, box_text):\n self.patch_objects.append(f\"#X {box_type} {x} {y} {box_text};\\n\")\n return self.get_last_id()\n\n def object(self, obj, x, y):\n return self.box(\"obj\", x, y, obj)\n\n def msg(self, msg, x, y):\n return self.box(\"msg\", x, y, msg)\n\n def comment(self, text, x, y):\n return self.box(\"text\", x, y, text)\n\n def number(self, x, y):\n return self.box(\"floatatom\", x, y, f\"5 0 0 0 - - - 0\")\n\n \"\"\"\n connections\n \"\"\"\n\n def connect(self, a_id, a_outlet, b_id, b_inlet):\n self.patch_connections.append(\n f\"#X connect {a_id} {a_outlet} {b_id} {b_inlet};\\n\"\n )\n\n \"\"\"\n osc send/receive\n \"\"\"\n\n def osc_send(self, host, port, x, y, send_rate_limit=100):\n loadbang_id = self.object(\"loadbang\", x, y)\n y += self.h\n connect_id = self.msg(f\"connect {host} {port}\", x, y)\n y += self.h\n disconnect_id = self.msg(\"disconnect\", x + 10, y)\n metro_id = self.object(f\"metro {send_rate_limit}\", x + 100, y)\n y += self.h\n send_rate_id = self.object(\"s rate\", x + 100, y)\n y += self.h\n receive_id = self.object(\"r send.to.iipyper\", x + 10, y)\n y += self.h\n packOSC_id = self.object(\"packOSC\", x + 10, y)\n y += self.h\n send_type = \"netsend -u\" if self.net_or_udp == \"net\" else \"udpsend\"\n send_id = self.object(send_type, x, y)\n y += self.h\n status_id = self.number(x, y)\n print_id = self.object(\"print reply.from.netreceive\", x + 40, y)\n # loadbang->connect->send->print\n self.connect(loadbang_id, 0, connect_id, 0)\n self.connect(connect_id, 0, send_id, 0)\n self.connect(send_id, 0, status_id, 0)\n self.connect(send_id, 1, print_id, 0)\n # loadbang->metro->send_rate\n self.connect(loadbang_id, 0, metro_id, 0)\n self.connect(metro_id, 0, send_rate_id, 0)\n # disconnect->send\n self.connect(disconnect_id, 0, send_id, 0)\n # receive->packOSC->send\n self.connect(receive_id, 0, packOSC_id, 0)\n self.connect(packOSC_id, 0, send_id, 0)\n return send_id\n\n def osc_receive(self, port, x, y): \n receive_type = (\n f\"netreceive -u {port}\"\n if self.net_or_udp == \"net\"\n else f\"udpreceive {port}\"\n )\n receive_id = self.object(receive_type, x, y)\n y += self.h\n unpackOSC_id = self.object(\"unpackOSC\", x, y)\n y += self.h\n print_id = self.object(\"print receive.from.iipyper\", x + 20, y)\n y += self.h\n s_receive_id = self.object(\"s receive.from.iipyper\", x, y)\n self.connect(receive_id, 0, unpackOSC_id, 0)\n self.connect(unpackOSC_id, 0, s_receive_id, 0)\n self.connect(unpackOSC_id, 0, print_id, 0)\n return self.get_last_id()\n\n \"\"\"\n osc send/receive args/list\n \"\"\"\n\n def send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n print('send_args_func',hints, f_p.items())\n params = []\n if len(f_p) == 0:\n self.osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, (k, p) in enumerate(f_p.items()):\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n print(i, k, p, p_def, p_min, p_max)\n params.append(\n {\n \"label\": k,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def send_list_func(self, f):\n self.osc_receive_list(self.r_x, self.r_y, f[\"address\"], f[\"params\"])\n self.r_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n def receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n # TODO: handle strings\n if isinstance(p, str):\n continue\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def receive_list_func(self, f):\n self.osc_send_list(self.s_x, self.s_y, f[\"address\"], f[\"params\"])\n self.s_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n \"\"\"\n osc send/receive no args/list (msg)\n \"\"\"\n\n def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def osc_send_msg(self, x, y, path):\n msg_id = self.msg(path, x, y + 225 + self.h)\n send_id = self.object(\"s send.to.iipyper\", x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n \"\"\"\n osc send/receive args with line, slider, rate-limiting, and change detection\n \"\"\"\n\n def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def osc_send_with_controls(self, x, y, path, parameters):\n y_off = 0\n # [comment path]\n path_comment_id = self.comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.object(\n \"r \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"send\"\n )\n y_off += self.h * 3 # line\n y_off += _y_off + 25\n y_off += 225\n\n pack_id = -1\n out_id = -1\n # [pack $1 $2 $3 ...]\n if len(parameters) > 1:\n pack_id = self.object(\"pack \" + self._pack_args(parameters), x, y + y_off)\n out_id = pack_id\n\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_args = self._msg_args(parameters)\n msg_id = self.msg(path + \" \" + msg_args, x, y + y_off)\n out_id = msg_id if len(parameters) == 1 else out_id\n # [s send]\n y_off += 25\n send_id = self.object(\"s send.to.iipyper\", x, y + y_off)\n\n # connections\n for i in range(len(parameters)):\n rcv = receive_ids[i]\n slider = slider_ids[i]\n slider_float = slider_float_ids[i]\n int_id = int_ids[i]\n tbf_id = tbf_ids[i]\n\n self.connect(rcv, 0, slider[0], 0)\n self.connect(rcv, 0, slider[1], 0)\n if int_id == -1 and tbf_id == -1: # if no int or tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id != -1 and tbf_id == -1: # if int but no tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id == -1 and tbf_id != -1: # if tbf but no int\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n elif int_id != -1 and tbf_id != -1: # if both int and tbf\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n\n self.connect(pack_id, 0, msg_id, 0) if pack_id != -1 else None\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n \"\"\"\n sliders\n \"\"\"\n\n def sliders(self, x, y, sliders, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n \"\"\"\n slider_ids = []\n float_ids = []\n int_ids = []\n tbf_ids = []\n y_off = 0\n send_rate_id = self.object(\"r rate\", x - 50, y + 155 + self.h * 3)\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * self.param_width)\n y_off += self.h\n slider_id, int_id, float_id, tbf_id = self.slider(\n send_rate_id,\n x_i,\n y + y_off,\n s[\"min_val\"],\n s[\"size\"],\n float=s[\"data\"] == \"float\",\n io=io if i > 0 else \"skip\",\n )\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n int_ids.append(int_id)\n tbf_ids.append(tbf_id)\n return slider_ids, float_ids, int_ids, tbf_ids, y_off\n\n def slider(self, send_rate_id, x, y, min_val, size, float=False, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n bang_id = self.object(\"bng\", x, y)\n y += self.h\n msg_id = self.msg(f\"{self.line}\", x, y)\n y += self.h\n line_id = self.object(f\"line 0 {self.line}\", x, y)\n y += self.h\n slider_id = self.box(\n \"obj\",\n x,\n y,\n f\"vsl 20 120 {min_val} {min_val+size} 0 0 empty empty empty 0 -9 0 12 #fcfcfc #000000 #000000 0 1\",\n )\n self.connect(bang_id, 0, msg_id, 0)\n self.connect(msg_id, 0, line_id, 1)\n self.connect(line_id, 0, slider_id, 0)\n y += 120 + 8\n int_id = -1\n tbf_id = -1\n float_id = -1\n if float == False and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_int(\n slider_id, send_rate_id, x, y\n )\n elif float == False and io != \"send\":\n y, change_id = self.receive_rate_limit_int(slider_id, send_rate_id, x, y)\n elif float == True and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_float(\n slider_id, send_rate_id, x, y\n )\n elif float == True and io != \"send\":\n y, change_id = self.recieve_rate_limit_float(slider_id, send_rate_id, x, y)\n return (line_id, bang_id), int_id, change_id, tbf_id\n\n def send_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number -> t b f\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def receive_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n def send_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number -> t b f\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def recieve_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n \"\"\"\n comments\n \"\"\"\n\n def param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * self.param_width)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]} {p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n \"\"\"\n lists\n \"\"\"\n\n def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n\n def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n\n \"\"\"\n utils\n \"\"\"\n\n def get_last_id(self):\n return len(self.patch_objects) - 2\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"f\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n\n def _msg_args(self, args):\n return \" \".join([\"\\$\" + str(i + 1) for i in range(len(args))])\n\n def path_to_snakecase(self, path):\n return path.replace(\"/\", \"_\")[1:] # +'_'+label[0:3]\n\n \"\"\"\n save/load\n \"\"\"\n\n def save(self, name):\n with open(name + \".pd\", \"w\") as f:\n [f.write(o) for o in self.patch_objects]\n [f.write(c) for c in self.patch_connections]\n\n def load(self, name):\n with open(name + \".pd\", \"r\") as f:\n for line in f:\n if f.startswith(\"#X connect\"):\n self.patch_connections.append(f)\n else:\n self.patch_objects.append(f)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_list","title":"osc_receive_list(x, y, path, params)
","text":"[comment] path [r receive.from.iipyper] [routeOSC path] s path params
Source code insrc/tolvera/osc/pd.py
def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_msg","title":"osc_receive_msg(x, y, path)
","text":"does this even make sense?
Source code insrc/tolvera/osc/pd.py
def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_with_controls","title":"osc_receive_with_controls(x, y, path, parameters)
","text":"TODO: Does [route] need to be broken down into individual subpaths?
Source code insrc/tolvera/osc/pd.py
def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_send_list","title":"osc_send_list(x, y, path, params)
","text":"[comment] path, list name, params [r] path [list prepend path] [list trim] [s send.to.iipyper]
Source code insrc/tolvera/osc/pd.py
def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/update/","title":"Update","text":""},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveListUpdater","title":"OSCReceiveListUpdater
","text":" Bases: ReceiveListUpdater
ReceiveListUpdater with an OSC handler
Source code insrc/tolvera/osc/update.py
class OSCReceiveListUpdater(ReceiveListUpdater):\n \"\"\"\n ReceiveListUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n self.set(list(args[1:]))\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater","title":"OSCReceiveUpdater
","text":" Bases: ReceiveUpdater
ReceiveUpdater with an OSC handler
Source code insrc/tolvera/osc/update.py
class OSCReceiveUpdater(ReceiveUpdater):\n \"\"\"\n ReceiveUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater.receive","title":"receive(address, *args)
","text":"v: first argument to the handler is the IP:port of the sender v: or you can use dispatcher.map directly and not set needs_reply_address=True j: can I get ip:port from osc itself? v: if you know the sender ahead of time yeah, but that lets you respond to different senders dynamically
Source code insrc/tolvera/osc/update.py
def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdaters","title":"OSCReceiveUpdaters
","text":"o = OSCReceiveUpdaters(osc, {\"/tolvera/particles/pos\": s.osc_set_pos, \"/tolvera/particles/vel\": s.osc_set_vel})
Source code insrc/tolvera/osc/update.py
class OSCReceiveUpdaters:\n \"\"\"\n o = OSCReceiveUpdaters(osc,\n {\"/tolvera/particles/pos\": s.osc_set_pos,\n \"/tolvera/particles/vel\": s.osc_set_vel})\n \"\"\"\n\n def __init__(self, osc, receives=None, count=10):\n self.osc = osc\n self.receives = []\n self.count = count\n if receives is not None:\n self.add_dict(receives, count=self.count)\n\n def add_dict(self, receives, count=None):\n if count is None:\n count = self.count\n {a: self.add(a, f, count=count) for a, f in receives.items()}\n\n def add(self, address, function, state=None, count=None, update=False):\n if count is None:\n count = self.count\n self.receives.append(\n OSCReceiveUpdater(self.osc, address, function, state, count, update)\n )\n\n def __call__(self):\n [r() for r in self.receives]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSend","title":"OSCSend
","text":"Non rate-limited OSC send
Source code insrc/tolvera/osc/update.py
class OSCSend:\n \"\"\"\n Non rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.client = client\n\n def __call__(self, *args):\n self.osc.send(self.address, *self.f(*args), client=self.client)\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdater","title":"OSCSendUpdater
","text":"Rate-limited OSC send
Source code insrc/tolvera/osc/update.py
class OSCSendUpdater:\n \"\"\"\n Rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, count=30, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.count = count\n self.counter = 0\n self.client = client\n\n def __call__(self):\n self.counter += 1\n if self.counter >= self.count:\n self.osc.send(self.address, *self.f(), client=self.client)\n self.counter = 0\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdaters","title":"OSCSendUpdaters
","text":"o = OSCSendUpdaters(osc, client=\"particles\", count=10, sends={ \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all })
Source code insrc/tolvera/osc/update.py
class OSCSendUpdaters:\n \"\"\"\n o = OSCSendUpdaters(osc, client=\"particles\", count=10,\n sends={\n \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all\n })\n \"\"\"\n\n def __init__(self, osc, sends=None, count=10, client=None):\n self.osc = osc\n self.sends = []\n self.count = count\n self.client = client\n if sends is not None:\n self.add_dict(sends, self.count, self.client)\n\n def add_dict(self, sends, count=None, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n {a: self.add(a, f, count=count, client=client) for a, f in sends.items()}\n\n def add(self, address, function, state=None, count=None, update=False, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n self.sends.append(OSCSendUpdater(self.osc, address, function, count, client))\n\n def __call__(self):\n [s() for s in self.sends]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCUpdaters","title":"OSCUpdaters
","text":"o = OSCUpdaters(osc, client=\"boids\", count=10, receives={ \"/tolvera/boids/pos\": b.osc_set_pos, \"/tolvera/boids/vel\": b.osc_set_vel }, sends={ \"/tolvera/boids/pos/all\": b.osc_get_all_pos } )
Source code insrc/tolvera/osc/update.py
class OSCUpdaters:\n \"\"\"\n o = OSCUpdaters(osc, client=\"boids\", count=10,\n receives={\n \"/tolvera/boids/pos\": b.osc_set_pos,\n \"/tolvera/boids/vel\": b.osc_set_vel\n },\n sends={\n \"/tolvera/boids/pos/all\": b.osc_get_all_pos\n }\n )\n \"\"\"\n\n def __init__(\n self,\n osc,\n sends=None,\n receives=None,\n send_count=60,\n receive_count=10,\n client=None,\n ):\n self.osc = osc\n self.client = client\n self.send_count = send_count\n self.receive_count = receive_count\n self.sends = OSCSendUpdaters(\n self.osc, count=self.send_count, client=self.client\n )\n self.receives = OSCReceiveUpdaters(self.osc, count=self.receive_count)\n if sends is not None:\n self.add_sends(sends)\n if receives is not None:\n self.add_receives(receives)\n\n def add_sends(self, sends, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add_dict(sends, count, client)\n\n def add_send(self, send, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add(send, client=client, count=count)\n\n def add_receives(self, receives, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add_dict(receives, count=count)\n\n def add_receive(self, receive, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add(receive, count=count)\n\n def __call__(self):\n self.sends()\n self.receives()\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater","title":"ReceiveListUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter Assumes a list[float] instead of *args
Source code insrc/tolvera/osc/update.py
class ReceiveListUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n Assumes a list[float] instead of *args\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code insrc/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code insrc/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater","title":"ReceiveUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter TODO: Rename to ReceiveArgsUpdater?
Source code insrc/tolvera/osc/update.py
class ReceiveUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n TODO: Rename to ReceiveArgsUpdater?\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code insrc/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code insrc/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.Updater","title":"Updater
","text":"Rate-limited function call
Source code insrc/tolvera/osc/update.py
class Updater:\n \"\"\"\n Rate-limited function call\n \"\"\"\n\n def __init__(self, f, count: int = 1):\n self.f = f\n self.count = int(count)\n self.counter = 0\n\n def __call__(self, *args, **kwargs):\n self.counter += 1\n if self.counter >= self.count:\n self.counter = 0\n return self.f(*args, **kwargs)\n return None\n
"},{"location":"reference/tolvera/vera/attractors/","title":"Attractors","text":"Inspired by https://github.com/williamgilpin/dysts
"},{"location":"reference/tolvera/vera/diffline/","title":"Diffline","text":"Differential line
"},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine","title":"DifferentialLine
","text":"Source code in src/tolvera/vera/diffline.py
@ti.data_oriented\nclass DifferentialLine:\n def __init__(self, tolvera, **kwargs) -> None:\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"dt\": (ti.f32, 0.1)})\n \"\"\"\n state\n max segments\n tv.s.segments\n init\n starting shape? circle etc.\n step\n for s in segments\n calculate the direction of growth\n apply angle variation and growth rate\n maintain minimum distance between points\n composition\n want to be able to e.g. flock the segments\n \"\"\"\n self.tv.s.diffline_s = {\n \"state\": {\n \"sigma\": (ti.f32, 0.0, 100.0),\n },\n \"shape\": self.tv.sn,\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n for i in particles:\n pass\n @ti.func\n def proc(self, x, y, z) -> ti.Vector:\n pass\n def randomise(self):\n self.tv.s.diffline_s.randomise()\n def __call__(self, particles: ti.template(), weight: ti.f32=1.0):\n self.step(particles, weight)\n
"},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine.CONSTS","title":"CONSTS = CONSTS({'dt': (ti.f32, 0.1)})
instance-attribute
","text":"state max segments tv.s.segments init starting shape? circle etc. step for s in segments calculate the direction of growth apply angle variation and growth rate maintain minimum distance between points composition want to be able to e.g. flock the segments
"},{"location":"reference/tolvera/vera/flock/","title":"Flock","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock","title":"Flock
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code insrc/tolvera/vera/flock.py
@ti.data_oriented\nclass Flock:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight * p1.speed * p1.active \n particles[i].pos += particles[i].vel\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code insrc/tolvera/vera/flock.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.step","title":"step(particles, weight)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Defaultparticles
template
A template for the particles.
requiredweight
f32
The weight of the Flock behaviour.
required Source code insrc/tolvera/vera/flock.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight * p1.speed * p1.active \n particles[i].pos += particles[i].vel\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/flock2/","title":"Flock2","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2","title":"Flock2
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code insrc/tolvera/vera/flock2.py
@ti.data_oriented\nclass Flock2:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock2_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock2_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock2_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock2_s.randomise()\n\n @ti.func\n def set_mag(self, v: ti.template(), mag: ti.f32):\n return (v / v.norm()) * mag\n\n @ti.func\n def set_mag2(self, v: ti.template(), mag: ti.f32):\n return (v / v.norm())# * mag\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n p1 = particles[i]\n if p1.active == 0:\n continue\n if single_species > -1:\n if p1.species != single_species:\n continue\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock2_s.struct()\n for j in range(n):\n p2 = particles[j]\n if i == j and p2.active == 0:\n continue\n # if single_species > -1:\n # if p2.species != single_species:\n # continue\n species = self.tv.s.flock2_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock2_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n vel = ti.Vector([0.0, 0.0])\n if algotype == 0:\n align = (align / nearby) * p1.active * species.align\n separate = (\n (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n )\n cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n # vel = separate + align + cohere\n elif algotype == 1:\n align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n elif algotype == 2:\n align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight, algotype, single_species)\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__call__","title":"__call__(particles, weight=1.0, algotype=0, single_species=-1)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock2.py
def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight, algotype, single_species)\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock2.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock2_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock2_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock2_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code insrc/tolvera/vera/flock2.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock2_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.step","title":"step(particles, weight, algotype, single_species)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Defaultparticles
template
A template for the particles.
requiredweight
f32
The weight of the Flock behaviour.
required Source code insrc/tolvera/vera/flock2.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n p1 = particles[i]\n if p1.active == 0:\n continue\n if single_species > -1:\n if p1.species != single_species:\n continue\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock2_s.struct()\n for j in range(n):\n p2 = particles[j]\n if i == j and p2.active == 0:\n continue\n # if single_species > -1:\n # if p2.species != single_species:\n # continue\n species = self.tv.s.flock2_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock2_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n vel = ti.Vector([0.0, 0.0])\n if algotype == 0:\n align = (align / nearby) * p1.active * species.align\n separate = (\n (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n )\n cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n # vel = separate + align + cohere\n elif algotype == 1:\n align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n elif algotype == 2:\n align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/flock_shiffman/","title":"Flock shiffman","text":"taichi flocking simulation credits: https://www.youtube.com/watch?v=mhjuuHl6qHM
"},{"location":"reference/tolvera/vera/forces/","title":"Forces","text":"Force functions for particles.
This module contains functions for applying forces to particles. It includes functions for moving, attracting, repelling and gravitating particles. It also includes variations of these functions for specific species of particles.
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract","title":"attract(particles, pos, mass, radius)
","text":"Attract the particles to a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef attract(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Attract the particles to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_particle","title":"attract_particle(p, pos, mass, radius)
","text":"Attract a particle to a position.
Parameters:
Name Type Description Defaultparticles
Particle
Individual particle.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Attraction velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef attract_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Attract a particle to a position.\n\n Args:\n particles (Particle): Individual particle.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n\n Returns:\n ti.math.vec2: Attraction velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (radius - target_distance) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_species","title":"attract_species(particles, pos, mass, radius, species)
","text":"Attract the particles of a given species to a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef attract_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Attract the particles of a given species to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal","title":"centripetal(particles, centre, direction, weight)
","text":"Apply a centripetal force to the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredcentre
vec2
Centripetal centre.
requireddirection
i32
Centripetal direction.
requiredweight
f32
Centripetal weight.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef centripetal(particles: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32):\n \"\"\"Apply a centripetal force to the particles.\n\n Args:\n particles (ti.template): Particles.\n centre (ti.math.vec2): Centripetal centre.\n direction (ti.i32): Centripetal direction.\n weight (ti.f32): Centripetal weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += centripetal_particle(p, centre, direction, weight)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal_particle","title":"centripetal_particle(p, centre, direction, weight)
","text":"Apply a centripetal force to a particle.
Parameters:
Name Type Description Defaultp
Particle
Individual particle.
requiredcentre
vec2
Centripetal centre.
requireddirection
i32
Centripetal direction.
requiredweight
f32
Centripetal weight.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Centripetal velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef centripetal_particle(p: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32) -> ti.math.vec2:\n \"\"\"Apply a centripetal force to a particle.\n\n Args:\n p (Particle): Individual particle.\n centre (ti.math.vec2): Centripetal centre.\n direction (ti.i32): Centripetal direction.\n weight (ti.f32): Centripetal weight.\n\n Returns:\n ti.math.vec2: Centripetal velocity.\n \"\"\"\n r = p.pos - centre\n if direction == 0:\n r = -r\n v_perp = ti.Vector([-r[1], r[0]])\n norm = v_perp.norm() + 1e-5\n v_perp_normalized = v_perp / norm\n speed = p.vel.norm()\n new_vel = v_perp_normalized * speed * weight\n return new_vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate","title":"gravitate(particles, G, radius)
","text":"Gravitate the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredG
f32
Gravitational constant.
requiredradius
f32
Gravitational radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef gravitate(particles: ti.template(), G: ti.f32, radius: ti.f32):\n \"\"\"Gravitate the particles.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate_species","title":"gravitate_species(particles, G, radius, species)
","text":"Gravitate the particles of a given species.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredG
f32
Gravitational constant.
requiredradius
f32
Gravitational radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef gravitate_species(\n particles: ti.template(), G: ti.f32, radius: ti.f32, species: ti.i32\n):\n \"\"\"Gravitate the particles of a given species.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n species (ti.i32): Species index.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if p1.species != species or p2.species != species:\n continue\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitation","title":"gravitation(p1, p2, G)
","text":"Calculate the gravitational force between two particles.
Parameters:
Name Type Description Defaultp1
Particle
Particle 1.
requiredp2
Particle
Particle 2.
requiredG
f32
Gravitational constant.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Gravitational force.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef gravitation(p1: Particle, p2: Particle, G: ti.f32) -> ti.math.vec2:\n \"\"\"Calculate the gravitational force between two particles.\n\n Args:\n p1 (Particle): Particle 1.\n p2 (Particle): Particle 2.\n G (ti.f32): Gravitational constant.\n\n Returns:\n ti.math.vec2: Gravitational force.\n \"\"\"\n r = p2.pos - p1.pos\n distance = r.norm() + 1e-5\n force_direction = r.normalized()\n force_magnitude = G * p1.mass * p2.mass / (distance**2)\n force = force_direction * force_magnitude\n return force / p1.mass\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.move","title":"move(particles)
","text":"Move the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef move(particles: ti.template()):\n \"\"\"Move the particles.\n\n Args:\n particles (ti.template): Particles.\n \"\"\"\n for i in range(particles.field.shape[0]):\n if particles.field[i].active == 0:\n continue\n p1 = particles.field[i]\n particles.field[i].pos += p1.vel * p1.speed * p1.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.noise","title":"noise(particles, weight)
","text":"Add noise to the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredweight
f32
Noise weight.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef noise(particles: ti.template(), weight: ti.f32):\n \"\"\"Add noise to the particles.\n\n Args:\n particles (ti.template): Particles.\n weight (ti.f32): Noise weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += (ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * weight)\n particles.field[i].pos += p.vel * p.speed * p.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel","title":"repel(particles, pos, mass, radius)
","text":"Repel the particles from a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef repel(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Repel the particles from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_particle","title":"repel_particle(p, pos, mass, radius)
","text":"Repel a particle from a position.
Parameters:
Name Type Description Defaultp
Particle
Individual particle.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Repulsion velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef repel_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Repel a particle from a position.\n\n Args:\n p (Particle): Individual particle.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n\n Returns:\n ti.math.vec2: Repulsion velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (target_distance - radius) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_species","title":"repel_species(particles, pos, mass, radius, species)
","text":"Repel the particles of a given species from a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef repel_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Repel the particles of a given species from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/geom/","title":"Geom","text":""},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.cos","title":"cos(particles, frequency, magnitude, phase, axis)
","text":"Apply a cosine wave force to particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredfrequency
f32
Frequency of the cosine wave.
requiredmagnitude
f32
Magnitude of the cosine wave.
requiredphase
f32
Phase of the cosine wave.
requiredaxis
i32
Axis to apply the cosine wave to.
required Source code insrc/tolvera/vera/geom.py
@ti.kernel\ndef cos(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n \"\"\"Apply a cosine wave force to particles.\n\n Args:\n particles (ti.template): Particles.\n frequency (ti.f32): Frequency of the cosine wave.\n magnitude (ti.f32): Magnitude of the cosine wave.\n phase (ti.f32): Phase of the cosine wave.\n axis (ti.i32): Axis to apply the cosine wave to.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n inc = magnitude * ti.cos(1/frequency * p.pos[axis] + phase)\n particles.field[i].vel[axis] += inc\n
"},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.sin","title":"sin(particles, frequency, magnitude, phase, axis)
","text":"Apply a sine wave force to particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredfrequency
f32
Frequency of the sine wave.
requiredmagnitude
f32
Magnitude of the sine wave.
requiredphase
f32
Phase of the sine wave.
requiredaxis
i32
Axis to apply the sine wave to.
required Source code insrc/tolvera/vera/geom.py
@ti.kernel\ndef sin(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n \"\"\"Apply a sine wave force to particles.\n\n Args:\n particles (ti.template): Particles.\n frequency (ti.f32): Frequency of the sine wave.\n magnitude (ti.f32): Magnitude of the sine wave.\n phase (ti.f32): Phase of the sine wave.\n axis (ti.i32): Axis to apply the sine wave to.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n inc = magnitude * ti.sin(1/frequency * p.pos[axis] + phase)\n particles.field[i].vel[axis] += inc\n
"},{"location":"reference/tolvera/vera/nca/","title":"Nca","text":"https://github.com/google/swissgl/blob/main/demo/NeuralCA.js
"},{"location":"reference/tolvera/vera/particle_life/","title":"Particle life","text":"Particle Life model.
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife","title":"ParticleLife
","text":"Particle Life model.
The Particle Life model is a simple model of particle behaviour, where particles are either attracted or repelled by other particles, depending on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr and others:
https://www.ventrella.com/Clusters/ https://github.com/tom-mohr/particle-life-app
Source code insrc/tolvera/vera/particle_life.py
@ti.data_oriented\nclass ParticleLife():\n \"\"\"Particle Life model.\n\n The Particle Life model is a simple model of particle behaviour, where\n particles are either attracted or repelled by other particles, depending\n on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr\n and others:\n\n https://www.ventrella.com/Clusters/\n https://github.com/tom-mohr/particle-life-app\n\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n # particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n # particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n particles[i].pos += particles[i].vel\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Particle Life model.
Parameters:
Name Type Description Defaultparticles
Particles
The particles to step.
required Source code insrc/tolvera/vera/particle_life.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Particle Life model.
'plife' stores the species rule matrix.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/particle_life.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.step","title":"step(particles, weight)
","text":"Step the Particle Life model.
Parameters:
Name Type Description Defaultparticles
field
The particles to step.
requiredweight
f32
The weight of the step.
required Source code insrc/tolvera/vera/particle_life.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n # particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n # particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n particles[i].pos += particles[i].vel\n
"},{"location":"reference/tolvera/vera/reaction_diffusion/","title":"Reaction diffusion","text":"Inspired by https://github.com/taichi-dev/faster-python-with-taichi/blob/main/reaction_diffusion_taichi.py
"},{"location":"reference/tolvera/vera/slime/","title":"Slime","text":"Slime behaviour based on the Physarum polycephalum slime mould.
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime","title":"Slime
","text":"Slime behaviour based on the Physarum polycephalum slime mould.
The slime mould is a single-celled organism that exhibits complex behaviour such as foraging, migration, and decision-making. It is a popular model for emergent behaviour in nature-inspired computing.
The slime mould is simulated by a set of particles that move around the simulation space. The particles sense their environment and move in response to the sensed information. The particles leave a \"pheromone trail\" behind them, which evaporates over time. The particles can be of different species, which have different sensing and moving parameters.
Taichi Physarum implementation inspired by: https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py
Source code insrc/tolvera/vera/slime.py
@ti.data_oriented\nclass Slime:\n \"\"\"Slime behaviour based on the Physarum polycephalum slime mould.\n\n The slime mould is a single-celled organism that exhibits complex behaviour\n such as foraging, migration, and decision-making. It is a popular model for\n emergent behaviour in nature-inspired computing.\n\n The slime mould is simulated by a set of particles that move around the\n simulation space. The particles sense their environment and move in response\n to the sensed information. The particles leave a \"pheromone trail\" behind them,\n which evaporates over time. The particles can be of different species, which \n have different sensing and moving parameters.\n\n Taichi Physarum implementation inspired by:\n https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n\n def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n\n @ti.kernel\n def move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n\n @ti.func\n def sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n\n @ti.func\n def sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n\n @ti.kernel\n def deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n\n def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n\n def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__call__","title":"__call__(particles, species, weight=1.0)
","text":"Call the Slime behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
A Particles instance.
requiredspecies
Species
A Species instance.
requiredweight
f32
Weight parameter. Defaults to 1.0.
1.0
Returns:
Name Type DescriptionPixels
A Pixels instance containing the pheromone trail.
Source code insrc/tolvera/vera/slime.py
def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Slime behaviour.
slime_p
stores the particle state. slime_s
stores the species state. trail
is a Pixels instance that stores the pheromone trail.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
requiredevaporate
f32
Evaporation rate. Defaults to 0.99.
required**kwargs
Keyword arguments. brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.
{}
Source code in src/tolvera/vera/slime.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.deposit_particles","title":"deposit_particles(particles, species)
","text":"Deposit particles onto the trail.
Parameters:
Name Type Description Defaultparticles
template
Particle field.
requiredspecies
template
Species field.
required Source code insrc/tolvera/vera/slime.py
@ti.kernel\ndef deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.move","title":"move(field, weight)
","text":"Move the particles based on the sensed environment.
Each particle senses the trail to its left, centre and right. Depending on the strength of the sensed trail in each direction, and the species parameters, a movement angle is calculated. The particle moves in this direction by a distance proportional to its active state and the weight parameter.
Parameters:
Name Type Description Defaultfield
template
Particle field.
requiredweight
f32
Weight of the movement.
required Source code insrc/tolvera/vera/slime.py
@ti.kernel\ndef move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.randomise","title":"randomise()
","text":"Randomise the Slime behaviour.
Source code insrc/tolvera/vera/slime.py
def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense","title":"sense(pos, ang, dist)
","text":"Sense the trail at a given position and angle.
Parameters:
Name Type Description Defaultpos
vec2
Position.
requiredang
f32
Angle.
requireddist
f32
Distance.
requiredReturns:
Type Descriptionvec4
ti.math.vec4: RGBA value of the sensed trail point.
Source code insrc/tolvera/vera/slime.py
@ti.func\ndef sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense_rgba","title":"sense_rgba(pos, ang, dist, rgba)
","text":"Sense the trail at a given position and angle and return a weighted RGBA value.
Parameters:
Name Type Description Defaultpos
vec2
Position.
requiredang
f32
Angle.
requireddist
f32
Distance.
requiredrgba
vec4
RGBA value.
requiredReturns:
Type Descriptionvec4
ti.math.vec4: Weighted RGBA value.
Source code insrc/tolvera/vera/slime.py
@ti.func\ndef sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.step","title":"step(particles, species, weight=1.0)
","text":"Step the Slime behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
A Particles instance.
requiredspecies
Species
A Species instance.
requiredweight
f32
Weight parameter. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/slime.py
def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n
"},{"location":"reference/tolvera/vera/swarmalators/","title":"Swarmalators","text":"Based on https://www.complexity-explorables.org/explorables/swarmalators/
"},{"location":"reference/tolvera/vera/viscek/","title":"Viscek","text":"Flocking behaviour based on the Viscek model.
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek","title":"Viscek
","text":"Viscek flocking model
Source code insrc/tolvera/vera/viscek.py
@ti.data_oriented\nclass Viscek:\n \"\"\"Viscek flocking model\"\"\"\n def __init__(self, tolvera, **kwargs):\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.viscek_s = {\n \"state\": {\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.viscek_p = {\n \"state\": {\n \"theta\": (ti.f32, -np.pi, np.pi),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.viscek_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise\"\"\"\n self.tv.s.viscek_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), noise_weight: ti.f32, weight: ti.f32):\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n species = self.tv.s.viscek_s.struct()\n n = 0\n n_dir = ti.Vector([0.0, 0.0])\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n n_dir += self.tv.s.viscek_p[j].theta\n n += 1\n avg_dir = n_dir / n\n\n\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/viscek.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek.randomise","title":"randomise()
","text":"Randomise
Source code insrc/tolvera/vera/viscek.py
def randomise(self):\n \"\"\"Randomise\"\"\"\n self.tv.s.viscek_s.randomise()\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Intro","text":""},{"location":"#tolvera","title":"T\u00f6lvera","text":"T\u00f6lvera is a Python library designed for composing together and interacting with basal agencies, inspired by fields such as artificial life (ALife) and self-organising systems. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, T\u00f6lvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.
The word T\u00f6lvera is an Icelandic kenning based on t\u00f6lva meaning computer, from tala (number) and v\u00f6lva (prophetess), and vera (being), composed together as number being.
We have employed T\u00f6lvera in various collaborative artistic works, including musical performances, compositions, and multimedia installations (see references.bib
for peer-reviewed publications). T\u00f6lvera's role in these pieces has mainly been \"mappable behaviour engine\", where interface inputs can control T\u00f6lvera programs, and T\u00f6lvera runtime data can control interface outputs, in practically any combination. In this way, and to controllable degrees, T\u00f6lvera can contribute to the underlying dynamics of a given interactive scenario. It can also add a visual component, and equally has been used without projection in other works.
T\u00f6lvera makes use of Taichi, a domain-specific language embedded in Python that enables parallelisation, and is experimental software subject to change.
We would be happy to have you join us on our Discord server!
"},{"location":"#showcase-examples","title":"Showcase & Examples","text":"Examples can be found at iil-examples/tolvera. See also the guide, reference and experiments pages.
Visit the YouTube Playlist (if you'd like to add a video, please get in touch).
"},{"location":"#install","title":"Install","text":"Taichi supports numerous operating systems and backends. If you plan on using Vulkan for graphics (recommended for macOS), you may need to install the Vulkan SDK first and restart your machine.
T\u00f6lvera is registered on PyPI and can be installed via a Python package manager such as pip
:
pip install tolvera\n
"},{"location":"#develop","title":"Develop","text":"For development, we use poetry
. Fork/clone this repository and install the package with `poetry:
git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)\ncd tolvera\npoetry install\n
"},{"location":"#known-issues-limitations","title":"Known Issues & Limitations","text":"conda create -n tolvera python=3.11\nconda activate tolvera\n
anguilla
's FAISS dependency, and Mediapipe not supporting Intel Macs).export KMP_DUPLICATE_LIB_OK=TRUE\n
We welcome Pull Requests across all areas of the project:
To discuss T\u00f6lvera with developers and other users:
Across the project, we follow the Berlin Code of Conduct. Please get in touch if you experience or witness any conduct issues.
"},{"location":"#roadmap","title":"Roadmap","text":"See Discussion.
"},{"location":"#citation","title":"Citation","text":"T\u00f6lvera is being written about and used in a number of contexts (see references.bib). The current canonical citation is our NIME 2024 paper:
@inproceedings{armitageTolveraComposingBasal2024,\n title = {T{\\\"o}lvera: {{Composing With Basal Agencies}}},\n booktitle = {Proc. {{New Interfaces}} for {{Musical Expression}}},\n author = {Armitage, Jack and Shepardson, Victor and Magnusson, Thor},\n year = {2024},\n address = {Utrecht, NL},\n}\n
"},{"location":"#inspiration","title":"Inspiration","text":"T\u00f6lvera is developed primarily by Jack Armitage and collaborators at the Intelligent Instruments Lab. Get in touch to collaborate:
\u25e6 iil.is \u25e6 Facebook \u25e6 Instagram \u25e6 X (Twitter) \u25e6 YouTube \u25e6 Discord \u25e6 GitHub \u25e6 LinkedIn \u25e6 Email \u25e6
"},{"location":"#acknowledgements","title":"Acknowledgements","text":"We thank the Taichi community for their wonderful project, that makes T\u00f6lvera possible.
The Intelligent Instruments project (INTENT) is funded by the European Research Council (ERC) under the European Union\u2019s Horizon 2020 research and innovation programme (Grant agreement No. 101001848).
"},{"location":"artworks/","title":"Artworks","text":""},{"location":"artworks/#artworks","title":"Artworks","text":"Page under construction \ud83d\udea7.
"},{"location":"bibliography/","title":"Bibliography","text":""},{"location":"bibliography/#bibliography","title":"Bibliography","text":"Page under construction \ud83d\udea7. For now, see bibliography.bib
.
Page under construction \ud83d\udea7. For now, see iil-examples/tolvera.
"},{"location":"experiments/","title":"Experiments","text":""},{"location":"experiments/#experiments","title":"Experiments","text":"These are ongoing experiments with T\u00f6lvera, Taichi and third-party libraries. You can find more experiments in the examples folder. Most are working examples, but don't be surprised if there is the occasional broken one - please report issues here.
"},{"location":"experiments/#live-coding","title":"Live Coding","text":"T\u00f6lvera can be live coded via Sardine. Note that this only works so far using VSCode's inbuilt Python REPL, and not with the Sardine VSCode extension:
tv.s.params = {'state':{'evap': (ti.f32, 0., 0.99)}}\n\n@swim\ndef gui_loop(p=0.5, i=0):\n tv.px.diffuse(tv.s.params.field[0].evap)\n tv.px.particles(tv.p, tv.s.species())\n tv.show(tv.px)\n again(gui_loop, p=1/64, i=i+1)\n\n@swim\ndef control_loop(p=4, i=0):\n tv.s.params.field[0].evap = P('0.9 0.99 0.1', i)\n again(control_loop, p=1/2, i=i+1)\n
"},{"location":"experiments/#sonification","title":"Sonification","text":"Sonification of and with T\u00f6lvera can be achieved via SignalFlow. See examples here.
"},{"location":"experiments/#gpu-audio","title":"GPU Audio","text":"GPU audio rendering can be achieved using Taichi alone:
from iipyper import Audio, run\nimport sounddevice as sd\nimport taichi as ti\nfrom math import pi\n\n@ti.kernel\ndef generate_data(outdata: ti.ext_arr(), \n frames: ti.i32, \n start_idx: ti.template(), \n fs: ti.f32):\n for I in ti.grouped(ti.ndrange(frames)):\n i = I[0]\n t = (start_idx[None] + i) / fs\n s = ti.sin(2 * pi * 440 * t)\n start_idx[None] += 1\n outdata[i,0] = s\n\ndef main():\n ti.init(arch=ti.vulkan)\n device = sd.default.device\n fs = sd.query_devices(sd.default.device, 'output')['default_samplerate']\n buffer_size = 128\n start_idx = ti.field(ti.i32, ())\n def callback(indata, outdata, frames, time, status):\n if status: print(status, file=sys.stderr)\n generate_data(outdata, frames, start_idx, fs)\n Audio(device=device, channels=1, blocksize=buffer_size,\n samplerate=fs, callback=callback)\n\nif __name__=='__main__':\n run(main)\n
"},{"location":"experiments/#3d-printing","title":"3D Printing","text":"Exploration of 3D printing with T\u00f6lvera can be achieved via FullControl. See example here.
"},{"location":"experiments/#sketchbook","title":"Sketchbook","text":"T\u00f6lvera has an experimental sketchbook built-in. For example, if the following program exists in a folder called mysketch.py
:
from tolvera import Tolvera\n\ndef mysketch(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species)\n return tv.px\n
It can be run with:
python -m tolvera --sketch \"mysketch\"\n
Available commands:
--sketchbook Set a sketchbook folder\n--sketches List available sketches\n--sketch Run a particular sketch (can be a file path or index)\n--random Run a random sketch from the sketchbook\n
"},{"location":"guide/","title":"Guide","text":""},{"location":"guide/#tolvera-v010-api-guide","title":"T\u00f6lvera v0.1.0
API Guide","text":"This guide provides an overview of the T\u00f6lvera v0.1.0
API:
The v0.2.0
API will be different, so everything here is subject to change. Please share your feedback on our Discord.
tv.v
: a collection of \"vera\" (beings) including Move, Flock, Slime and Swarm, with more being continuously added. Vera can be combined and composed in various ways.tv.p
: extensible particle system. Particles are divided into multiple species, where each species has a unique relationship with every other species, including itselftv.s
: n-dimensional state structures that can be used by \"vera\", including built-in OSC and IML creation (see below).tv.px
: drawing library including various shapes and blend modes, styled similarly to p5.js etc.tv.osc
: Open Sound Control (OSC) via iipyper, including automated export of OSC schemas to JSON, XML, Pure Data (Pd), Max/MSP (SuperCollider TBC).tv.iml
: Interactive Machine Learning via anguilla.tv.ti
: Taichi-based simulation and rendering engine. Can be run \"headless\" (without graphics).tv.cv
: computer vision integration based on OpenCV and Mediapipe.This example demonstrates the basic usage of T\u00f6lvera when used as a standalone Python script. It will display a window with a black background:
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Tolvera
class and run()
function from the tolvera
Python package.def main()
function takes in keyword arguments (**kwargs
) from the command line.def main()
, we initialise a Tolvera
instance with the given **kwargs
.@tv.render()
decorator to create the scene and render the pixels.def _()
in the example), and is analagous to loop()
in Arduino/Processing/p5.js and render()
in Bela, in that it will run in a loop until the user exits the program.def _()
must return an instance of Pixels
. Pixels
instance belonging to the Tolvera
instance, accessed with tv.px
.run()
with def main()
as the argument.tv.p
) & Species (tv.s.species
)","text":"A central idea of T\u00f6lvera is the particle as a base unit of activity. The particle system is a field of type Particle
with these properties:
@ti.dataclass\nclass Particle:\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n
Particles are divided into species (represented as an integer), and species can have different relationships with each other, creating a matrix of species-species interactions. This idea was inspired by Particle Life, and provides a simple means to mimic ecological complexity, even when using a single behaviour or model. Species are implemented as multi-dimensional state (see below), which means all tv.v
behaviour models can make use of the multispecies matrix.
tv.s
)","text":"To enable composition of behaviours, T\u00f6lvera features a global state dictionary. State is n-dimensional and can be manipulated in parallel on the GPU. Typical state shapes might be: (species, species)
for multispecies rules, (particles)
for individual particle states, and \\pyi(particles, particles) for pairwise comparison of particles. Assigning a dictionary to a variable after tv.s
causes a new state to be instanced. Here are two examples of state from tv.v.flock
, showing the syntax of \"name\": (type, min, max)
for each attribute, and some of the additional options which includes built-in randomisation:
tv.s.flock_s = { \"state\": \n {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (species, species),\n \"osc\": (\"set\"),\n \"randomise\": True\n}\n\ntv.s.flock_dist = {\n \"state\": {\"dist\": (ti.f32, 0.0, tv.x*2)},\n \"shape\": (particles, particles),\n \"randomise\": False\n}\n
State is useful and interesting to visualise, for example drawing the particle distances that flock
uses reveals hidden structure. State can also be reused and combined with state from other models, to compose even more complex behaviour.
tv.v
)","text":"The image below shows some of the available behaviours and models in T\u00f6lvera. Some of these are inspired directly from open source code posted by the Taichi community - thank you!
Examples of behaviours and models available via `tv.v`. Top row: stateless `tv.v`. Bottom row: stateful `tv.v`.Models can be used by calling them and passing particles to them, for example:
@tv.render\ndef _():\n tv.px.clear()\n tv.v.flock(tv.p)\n tv.v.plife(tv.p)\n tv.px.particles(tv.p, tv.s.species())\n return tv.px\n
In this case, the flock
and plife
(particle life) models are composed together to create a compound behaviour. Models are also designed to be simple and concise internally to encourage users to understand them and make their own. Simple behaviours like move
can be stateless and implemented as single GPU kernels.
@ti.kernel\ndef move(p: ti.template()):\n for i in range(p.field.shape[0]):\n p1 = p.field[i]\n if p1.field[i].active == 0: continue\n p.field[i].pos += p1.vel * p1.speed\n
Models that use state are implemented as classes, that at minimum provide a step()
method, where particles can be compared and states updated:
@ti.data_oriented\nclass MyVera:\n def __init__(self, tolvera, **kwargs):\n self.tv, self.kwargs = tolvera, kwargs\n self.C = CONSTS({...})\n self.tv.s.vera_s = {...} #\u00a0state\n\n @ti.kernel\n def step(self, p):\n for i in range(p.shape[0]):\n for j in range(p.shape[1]):\n # compare p[i] & p[j]\n # update state, etc.\n p[i].pos += ... # update p[i]\n\n def __call__(self, p):\n self.step(p)\n
Step function inside tv.v
:
@ti.kernel\ndef step(self, p: ti.template(), W: ti.f32):\n for i in range(p.shape[0]):\n p1 = p[i]\n if p1[i].active == 0: continue\n for j in range(p.shape[0]):\n p2 = p[j]\n if i == j & p2[j].active == 0: continue\n s = self.tv.s.vera_s[p1.species, p2.species]\n d = p1.pos - p2.pos\n if d < s.radius:\n # p1 & p2 are neighbours\n p[i].vel += W * ...\n p[i].pos += W * ...\n
"},{"location":"guide/#pixels-tvpx","title":"Pixels (tv.px
)","text":"T\u00f6lvera has a drawing module that is similar in design to p5.js. This example draws a rectangle in the middle of the window:
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
It mainly features shape primitives and blend modes. Pixels can also be thought of as fields and used as part of a vera, as tv.v.slime
does to deposit particles onto a pheromone trail. Due to this flexibility, drawing and visualisation can impact behaviour and create more mappings and feedback loops between model states.
tv.ti
)","text":"T\u00f6lvera does most of its work on the GPU thanks to Taichi (tv.ti
). Taichi is a domain-specific language (DSL) embedded in Python that supports multiple backends (CUDA, OpenGL, Vulkan, Metal). Taichi programs are distinguished by three levels of scope: regular Python scope, kernel scope (@ti.kernel
) and function scope (@ti.func
). Python scope can call kernels, and kernels and functions can call functions. In Taichi scope, top-level for
loops are automatically parallelised. It can also run headless (without a window), and provides a C runtime and ahead-of-time (AOT) compilation for deployment in non-Python programs. It also interoperates with NumPy and PyTorch for ML integrations.
tv.osc
)","text":"OSC in T\u00f6lvera is handled by iipyper
, a package specifically designed for working with artificial intelligence. When creating state, OSC endpoints can be automatically added via the \"osc\"
option. For custom OSC endpoints, the tv.osc.map
decorators can be used to add senders and receivers of different varieties. Here are two examples, one of receiving two arguments x
and y
, and another of receiving a vector of length ten:
@tv.osc.map.receive_args(x=(0.,0.,1.), y=(0.,0.,1.), count=5)\ndef my_args(x: float, y: float):\n print(f\"Receiving args: {x} {y}\")\n\n@tv.osc.map.receive_list(vector=(0.,0.,1.), length=10, count=5)\ndef my_list(vector: list[float]):\n print(f\"Received list: {vector}\")\n
The count
decorator argument can be used to rate limit how often an endpoint's Python function runs, relative to the number of frames elapsing. T\u00f6lvera OSC mappings can be exported to JSON and XML, and they can also generate patches for Max/MSP and Pure Data, enabling rapid integration with other software.
tv.iml
)","text":"Interactive machine learning is achieved via the anguilla
Python package. T\u00f6lvera features a global dictionary of IML instances at tv.iml
, similarly to state (tv.s
). IML can be used for a wide range of purposes in T\u00f6lvera, including creating internal feedback loops, for example between tv.v.flock
's position and species rules states:
tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec)\n}\n
The above example uses fun2fun
, meaning the input and output methods specified in the io
option will be run in the background by T\u00f6lvera. These are to_vec
and from_vec
, built-in methods that serialise and deserialise state to/from 1D arrays making them suitable for use as IML input/output vectors. To facilitate automatic routing of IML inputs and outputs, there are nine types in which the input and output can be either a vector, function or OSC endpoint: vec2vec
, vec2fun
, vec2osc
, fun2vec
, fun2fun
, fun2osc
, osc2vec
, osc2fun
, and osc2osc
. Notably, these IML maps can be trained and updated on-the-fly, providing more variation in prolonged usage.
The image below demonstrates how T\u00f6lvera's state and drawing capabilities can be used to enable real-time visualisation of IML mappings.
Interactive machine learning (`tv.iml`) real-time map visualisation for input size two (XY axes) and output size three (pixel RGB). Input-output pairs are shown as white circles. This example demonstrates anguilla's ripple interpolator."},{"location":"guide/#computer-vision-tvcv","title":"Computer Vision (tv.cv
)","text":"T\u00f6lvera integrates with OpenCV and Mediapipe to enable exploration of computer vision and tracking of hands, face and full body pose. See examples.
"},{"location":"guide/#command-line-arguments","title":"Command-line arguments","text":"When T\u00f6lvera is instanced, a global kwargs
dictionary is passed down through the various modules that allows setting of flags and parameters.
python -m tolvera
)","text":"All arguments below can be applied, and in addition:
--demo Run a tv.v.flock() demo.\n--help Print a help message.\n
See also sketchbook
in experiments.
tv.ctx
)","text":"--x Width in pixels. Defaults to 1920.\n--y Height in pixels. Defaults to 1080.\n
"},{"location":"guide/#tolvera-instance-tv","title":"T\u00f6lvera Instance (tv
)","text":"--name Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n--speed Global speed scalar. Defaults to 1.\n--particles Number of particles. Defaults to 1024. Aliased to tv.pn.\n--species Number of species. Defaults to 4. Aliased to tv.sn.\n--substep Number of substeps of render function. Defaults to 1.\n
"},{"location":"guide/#taichi-tvti","title":"Taichi (tv.ti
)","text":"--gpu GPU architecture to run on. Defaults to \"vulkan\".\n--cpu Run on CPU instead of GPU. Defaults to False.\n--fps Frames per second. Defaults to 120.\n--seed Random seed. Defaults to int(time.time()).\n--headless False\n--name Instance name. Defaults to \"T\u00f6lvera\".\n
"},{"location":"guide/#pixels-tvpx_1","title":"Pixels (tv.px
)","text":"--polygon_mode Polygon drawing mode. Defaults to 'crossing'.\n--brightness Brightness scalar. Defaults to 1.\n
"},{"location":"guide/#open-sound-control-tvosc_1","title":"Open Sound Control (tv.osc
)","text":"--osc Enable OSC. Defaults to False.\n--host OSC Host IP. Defaults to \"127.0.0.1\".\n--client OSC Client IP. Defaults to \"127.0.0.1\".\n--client_name OSC client name. Defaults to self.ctx.name_clean.\n--receive_port OSC host port. Defaults to 5001.\n--send_port OSC client port. Defaults to 5000.\n--osc_trace Print all OSC messages. Defaults to False.\n--osc_verbose Verbose printing of iipyper. Defaults to False.\n--create_patch Create a Max or Pd patch based on tv.osc.map. Defaults to False.\n--patch_type Type of patch to create. Defaults to \"Pd\".\n--patch_filepath Filepath of patch. Defaults to self.client_name.\n--export_patch Export patch schema to XML, JSON or both. Defaults to None.\n
"},{"location":"guide/#interactive-machine-learning-tviml_1","title":"Interactive Machine Learning (tv.iml
)","text":"--update_rate Rate of IML updates relative to FPS. Default to 10.\n--config anguilla instance configuration. Default to {}.\n--map_kw anguilla.map kwargs. Default to {}.\n--infun_kw Input method kwargs. Default to {}.\n--outfun_kw Output method kwargs. Default to {}.\n--randomise IML randomisation. Default to False.\n--rand_pairs IML randomisation. Default to 32.\n--rand_input_weight IML input randomisation weight. Default to None.\n--rand_output_weight IML output randomisation weight. Default to None.\n--rand_method IML randomisation method. Default to \"rand\".\n--rand_kw IML randomisation kwargs. Default to {}.\n--lag Lag value updates. Default to False.\n--lag_coef Lag coefficient. Default to 0.5.\n--name Name of IML instance. Default to None.\n
"},{"location":"guide/#computer-vision-tvcv_1","title":"Computer Vision (tv.cv
)","text":"--camera Enable camera. Defaults to False.\n--device OpenCV device index. Defaults to 0.\n--substeps Number of substeps for reading camera frames. Defaults to 2.\n--colormode Color channels. Defaults to 'rgba'.\n--ggui_fps_limit FPS limit of Taichi GGUI. Defaults to 120fps.\n--hands Enable hand tracking. Defaults to False.\n--pose Enable pose tracking. Defaults to False.\n--face Enable face tracking. Defaults to False.\n--face_mesh Enable face mesh tracking. Defaults to False.\n
"},{"location":"publications/","title":"Publications","text":""},{"location":"publications/#publications","title":"Publications","text":"Page under construction \ud83d\udea7. For now, see references.bib
.
TolveraContext
is a shared context or environment for Tolvera
instances. It is created automatically when a Tolvera
instance is created, if one does not already exist. It manages the integration of packages for graphics, computer vision, communications protocols, and more. If multiple Tolvera
instances are created, they must share the same TolveraContext
.
TolveraContext
can be created manually, and shared with multiple Tolvera
instances. Note that only one render
function can be used at a time.
from tolvera import TolveraContext, Tolvera, run\n\ndef main(**kwargs):\n ctx = TolveraContext(**kwargs)\n\n tv1 = Tolvera(ctx=ctx, **kwargs)\n tv2 = Tolvera(ctx=ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
Example TolveraContext
can also be created automatically, and still shared.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv1 = Tolvera(**kwargs)\n tv2 = Tolvera(ctx=tv1.ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext","title":"TolveraContext
","text":"Context for sharing between multiple T\u00f6lvera instances. Context includes Taichi, OSC, IML and CV. All T\u00f6lvera instances share the same context and are managed as a dict.
Attributes:
Name Type Descriptionkwargs
dict
Keyword arguments for context.
name
str
Name of context.
name_clean
str
'Cleaned' name of context.
i
int
Frame counter.
x
int
Width of canvas.
y
int
Height of canvas.
ti
Taichi
Taichi instance.
canvas
Pixels
Pixels instance.
osc
OSC
OSC instance.
iml
IML
IML instance.
cv
CV
CV instance.
_cleanup_fns
list
List of cleanup functions.
tolveras
dict
Dict of T\u00f6lvera instances.
Source code insrc/tolvera/context.py
class TolveraContext:\n \"\"\"\n Context for sharing between multiple T\u00f6lvera instances.\n Context includes Taichi, OSC, IML and CV.\n All T\u00f6lvera instances share the same context and are managed as a dict.\n\n Attributes:\n kwargs (dict): Keyword arguments for context.\n name (str): Name of context.\n name_clean (str): 'Cleaned' name of context.\n i (int): Frame counter.\n x (int): Width of canvas.\n y (int): Height of canvas.\n ti (Taichi): Taichi instance.\n canvas (Pixels): Pixels instance.\n osc (OSC): OSC instance.\n iml (IML): IML instance.\n cv (CV): CV instance.\n _cleanup_fns (list): List of cleanup functions.\n tolveras (dict): Dict of T\u00f6lvera instances.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n\n def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.i = ti.field(ti.i32, ())\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n self.pose = kwargs.get(\"pose\", False)\n self.face = kwargs.get(\"face\", False)\n self.face_mesh = kwargs.get(\"face_mesh\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n if self.hands:\n self.hands = MPHands(self, **kwargs)\n if self.pose:\n self.pose = MPPose(self, **kwargs)\n if self.face:\n self.face = MPFace(self, **kwargs)\n if self.face_mesh:\n self.face_mesh = MPFaceMesh(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n\n def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n self.step(f, **kwargs)\n\n def step(self, f, **kwargs):\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i[None] += 1\n\n def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n\n def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n\n def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n\n def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n\n def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n\n def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n\n def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.__init__","title":"__init__(**kwargs)
","text":"Initialise T\u00f6lvera context with given keyword arguments.
Source code insrc/tolvera/context.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.add","title":"add(tolvera)
","text":"Add T\u00f6lvera to context.
Parameters:
Name Type Description Defaulttolvera
Tolvera
T\u00f6lvera to add.
required Source code insrc/tolvera/context.py
def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.cleanup","title":"cleanup(f=None)
","text":"Decorator for cleanup functions based on iipyper. Make functions run on KeyBoardInterrupt (before exit). Cleanup functions must be defined before render is called!
Parameters:
Name Type Description Defaultf
Function to cleanup.
None
Returns:
Type DescriptionDecorator function if f is None, else decorated function.
Source code insrc/tolvera/context.py
def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_by_name","title":"get_by_name(name)
","text":"Get T\u00f6lvera by name.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera to get.
requiredReturns:
Name Type DescriptionT\u00f6lvera
T\u00f6lvera with given name.
Source code insrc/tolvera/context.py
def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_names","title":"get_names()
","text":"Get names of all T\u00f6lveras in context.
Returns:
Name Type Descriptionlist
List of T\u00f6lvera names.
Source code insrc/tolvera/context.py
def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.init","title":"init(**kwargs)
","text":"Initialise wrapped external packages with given keyword arguments. This only happens once when T\u00f6lvera is first initialised.
Parameters:
Name Type Description Defaultx
int
Width of canvas. Default: 1920.
requiredy
int
Height of canvas. Default: 1080.
requiredosc
bool
Enable OSC. Default: False.
requirediml
bool
Enable IML. Default: False.
requiredcv
bool
Enable CV. Default: False.
required Source code insrc/tolvera/context.py
def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.i = ti.field(ti.i32, ())\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n self.pose = kwargs.get(\"pose\", False)\n self.face = kwargs.get(\"face\", False)\n self.face_mesh = kwargs.get(\"face_mesh\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n if self.hands:\n self.hands = MPHands(self, **kwargs)\n if self.pose:\n self.pose = MPPose(self, **kwargs)\n if self.face:\n self.face = MPFace(self, **kwargs)\n if self.face_mesh:\n self.face_mesh = MPFaceMesh(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.remove","title":"remove(name)
","text":"Remove T\u00f6lvera by name.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera to delete.
required Source code insrc/tolvera/context.py
def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.render","title":"render(f=None, **kwargs)
","text":"Render T\u00f6lvera with given function and keyword arguments.
Parameters:
Name Type Description Defaultf
function
Function to run. Defaults to None.
None
Source code in src/tolvera/context.py
def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.run","title":"run(f=None, **kwargs)
","text":"Run T\u00f6lvera with given render function and keyword arguments. This function will run inside a locked thread until KeyboardInterrupt/exit. It runs the render function, updates the OSC map (if enabled), and shows the pixels.
Parameters:
Name Type Description Defaultf
Function to run.
None
**kwargs
Keyword arguments for function.
{}
Source code in src/tolvera/context.py
def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n self.step(f, **kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.stop","title":"stop()
","text":"Run cleanup functions and exit.
Source code insrc/tolvera/context.py
def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n
"},{"location":"reference/tolvera/cv/","title":"Cv","text":""},{"location":"reference/tolvera/iml/","title":"Iml","text":"IML stands for Interactive Machine Learning. T\u00f6lvera wraps the anguilla package to provide convenient ways for quickly creating mappings between vectors, functions and OSC routes.
Every T\u00f6lvera instance has an IMLDict, which is a dictionary of IML instances. The IMLDict is accessible via the iml
attribute of a T\u00f6lvera instance, and can be used to create and access IML instances.
There are 9 IML types, which are listed below.
ExampleHere we create a mapping based on states created by tv.v.flock
, where the per-particle state flock_p
is mapped to the species rule matrix flock_s
. Since this is a fun2fun
mapping (see IML Types below), we provide input and output functions, and T\u00f6lvera updates the mapping automatically every render()
call.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec),\n 'randomise': True,\n }\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species, 'circle')\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
IML Types vec2vec
: Vector to vector mapping.vec2fun
: Vector to function mapping.vec2osc
: Vector to OSC mapping.fun2vec
: Function to vector mapping.fun2fun
: Function to function mapping.fun2osc
: Function to OSC mapping.osc2vec
: OSC to vector mapping.osc2fun
: OSC to function mapping.osc2osc
: OSC to OSC mapping.IMLBase
","text":" Bases: IML
This class inherits from anguilla and adds some functionality. It is not intended to be used directly, but rather to be inherited from.
The base class is initialised with a size tuple (input, output) and a config dict which is passed to anguilla.IML
.
It provides a randomise
method which adds random pairs to the mapping. It also provides methods to remove pairs (remove_oldest
, remove_newest
, remove_random
). It also provides a lag
method which lags the mapped data. Finally, it provides an update
method which is called by the updater
(see .osc.update
).
src/tolvera/iml.py
class IMLBase(iiIML):\n \"\"\"\n This class inherits from [anguilla](https://intelligent-instruments-lab.github.io/anguilla) \n and adds some functionality. It is not intended to be used directly, but rather \n to be inherited from.\n\n The base class is initialised with a size tuple (input, output) and a config dict\n which is passed to `anguilla.IML`.\n\n It provides a `randomise` method which adds random pairs to the mapping.\n It also provides methods to remove pairs (`remove_oldest`, `remove_newest`, `remove_random`).\n It also provides a `lag` method which lags the mapped data.\n Finally, it provides an `update` method which is called by the `updater` (see `.osc.update`).\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"embed_input\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n\n def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n\n def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n\n def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n ):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n\n def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n\n def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n\n def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n\n def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n\n def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n\n def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n\n def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n\n def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n\n def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n\n def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n\n def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__call__","title":"__call__(*args)
","text":"Call updater with args.
Parameters:
Name Type Description Default*args
Updater args.
()
Returns:
Name Type DescriptionAny
Any
Mapped data.
Source code insrc/tolvera/iml.py
def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLBase
kwargssize (tuple, required): (input, output) sizes. io (tuple, optional): (input, output) functions. config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}. updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...). update_rate (int, optional): Updater's update rate (defaults to 1). randomise (bool, optional): Randomise mapping on init (defaults to False). rand_pairs (int, optional): Number of random pairs to add (defaults to 32). rand_input_weight (Any, optional): Random input weight (defaults to None). rand_output_weight (Any, optional): Random output weight (defaults to None). rand_method (str, optional): rand_method type (see utils). rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils). map_kw (dict, optional): kwargs to use in IML.map(). infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2' only). outfun_kw (dict, optional): kwargs to use in outfun() (type '2Fun' only). lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types. lag_coef (float, optional): Lag coefficient (defaults to 0.5 if lag
is True).
src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"embed_input\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.add_random_pair","title":"add_random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Add random pair to mapping.
Parameters:
Name Type Description Defaultinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
see random_pair kwargs.
{}
Source code in src/tolvera/iml.py
def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_lag","title":"init_lag(**kwargs)
","text":"Initialise lag() method with kwargs
kwargs: see init kwargs.
Source code insrc/tolvera/iml.py
def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_randomise","title":"init_randomise(**kwargs)
","text":"Initialise randomise() method with kwargs
kwargs: see init kwargs.
Source code insrc/tolvera/iml.py
def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.lag_mapped_data","title":"lag_mapped_data(lag_coef=0.5)
","text":"Lag mapped data.
Parameters:
Name Type Description Defaultlag_coef
float
Lag coefficient. Defaults to 0.5.
0.5
Source code in src/tolvera/iml.py
def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_input","title":"random_input(**kwargs)
","text":"Random input vector.
Parameters:
Name Type Description Default**kwargs
self.rand kwargs.
{}
Returns:
Type DescriptionTensor
torch.Tensor: Random input vector.
Source code insrc/tolvera/iml.py
def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_output","title":"random_output(**kwargs)
","text":"Random output vector.
Parameters:
Name Type Description Default**kwargs
self.rand kwargs
{}
Returns:
Type DescriptionTensor
torch.Tensor: Random output vector.
Source code insrc/tolvera/iml.py
def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_pair","title":"random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Create random pair.
Parameters:
Name Type Description Defaultinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
rand_method (str, optional): Randomisation method. Defaults to \"rand\". rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).
{}
Raises:
Type DescriptionValueError
Invalid input_weight type.
ValueError
Invalid output_weight type.
Returns:
Name Type Descriptiontuple
(input, output) vectors.
Source code insrc/tolvera/iml.py
def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.randomise","title":"randomise(times, input_weight=None, output_weight=None, method='rand', **kwargs)
","text":"Randomise mapping.
Parameters:
Name Type Description Defaulttimes
int
Number of random pairs to add.
requiredinput_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_newest","title":"remove_newest(n=1)
","text":"Remove newest pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_oldest","title":"remove_oldest(n=1)
","text":"Remove oldest pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_random","title":"remove_random(n=1)
","text":"Remove random pair(s) from mapping.
Parameters:
Name Type Description Defaultn
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.set_random_method","title":"set_random_method(method='rand')
","text":"Set random method.
Parameters:
Name Type Description Defaultmethod
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultinvec
list | Tensor | ndarray
Input vector.
requiredReturns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update_rate","title":"update_rate(rate=None)
","text":"Update rate getter/setter.
Parameters:
Name Type Description Defaultrate
int
Update rate. Defaults to None.
None
Returns:
Name Type Descriptionint
Update rate.
Source code insrc/tolvera/iml.py
def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict","title":"IMLDict
","text":" Bases: dotdict
IML mapping dict
Similarly to StateDict
, this class inherits from dotdict
to enable instantiation via assignment.
src/tolvera/iml.py
class IMLDict(dotdict):\n \"\"\"IML mapping dict\n\n Similarly to `StateDict`, this class inherits from `dotdict` to enable instantiation\n via assignment.\n \"\"\"\n\n def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n\n def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n\n def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n\n def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__call__","title":"__call__(name=None, *args, **kwargs)
","text":"Call IML instance or all IML instances.
Parameters:
Name Type Description Defaultname
str
Name of IML instance to call. Defaults to None.
None
Raises:
Type DescriptionValueError
'name' not in dict.
Returns:
Name Type DescriptionAny
Any
IML output or dict of IML outputs.
Source code insrc/tolvera/iml.py
def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__init__","title":"__init__(context)
","text":"Initialise IMLDict
Parameters:
Name Type Description Defaultcontext
TolveraContext
TolveraContext instance.
required Source code insrc/tolvera/iml.py
def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set IML instance.
Parameters:
Name Type Description Default__name
str
Name of IML instance.
required__value
Any
IML instance kwargs.
required Source code insrc/tolvera/iml.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.add","title":"add(name, iml_type, **kwargs)
","text":"Add IML instance.
Parameters:
Name Type Description Defaultname
str
Name of IML instance.
requirediml_type
str
IML type.
requiredRaises:
Type DescriptionValueError
Invalid IML_TYPE.
Returns:
Name Type DescriptionAny
Any
IML instance.
Source code insrc/tolvera/iml.py
def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.set","title":"set(name, kwargs)
","text":"Set IML instance.
Parameters:
Name Type Description Defaultname
str
Name of IML instance.
requiredkwargs
dict
IML instance kwargs.
requiredRaises:
Type DescriptionValueError
Cannot replace 'tv' IML instance.
ValueError
Cannot replace 'i' IML instance.
ValueError
Cannot replace 'o' IML instance.
NotImplementedError
set() with tuple not implemented yet.
TypeError
set() requires dict|tuple, not type.
Exception
Other exceptions.
Returns:
Name Type DescriptionAny
Any
IML instance.
Source code insrc/tolvera/iml.py
def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun","title":"IMLFun2Fun
","text":" Bases: IMLBase
IML function to function mapping.
Exampledef infun():\n return [0,0,0,0]\n\ndef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2Fun(IMLBase):\n \"\"\"IML function to function mapping.\n\n Example:\n ```py\n def infun():\n return [0,0,0,0]\n\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Fun
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (callable, callable) input and output functions. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC","title":"IMLFun2OSC
","text":" Bases: IMLBase
IML function to OSC mapping
ExampleThis will send the output vector to '/out/vec'.
def infun():\n return [0,0,0,0]\n\ntv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2OSC(IMLBase):\n \"\"\"IML function to OSC mapping\n\n Example:\n This will send the output vector to '/out/vec'.\n\n ```py\n def infun():\n return [0,0,0,0]\n\n tv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLFun2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (callable, str) input function and output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec","title":"IMLFun2Vec
","text":" Bases: IMLBase
IML function to vector mapping.
Output vector is accessed via tv.iml.o['name']
.
tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLFun2Vec(IMLBase):\n \"\"\"IML function to vector mapping.\n\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Vec
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (callable, None) input function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun","title":"IMLOSC2Fun
","text":" Bases: IMLBase
IML OSC to function mapping
Exampledef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2Fun(IMLBase):\n \"\"\"IML OSC to function mapping\n\n Example:\n ```py\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n }\n ```\n \"\"\"\n def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLOSC2Fun
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (str, callable) input OSC route and output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC","title":"IMLOSC2OSC
","text":" Bases: IMLBase
IML OSC to OSC mapping
Example'/in/vec' is mapped and the output sent to '/out/vec'.
tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2OSC(IMLBase):\n \"\"\"IML OSC to OSC mapping\n\n Example:\n '/in/vec' is mapped and the output sent to '/out/vec'.\n\n ```py\n tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.__init__","title":"__init__(osc_map, osc, **kwargs)
","text":"Initialise IMLOSC2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredosc
OSC
iipyper OSC instance.
requiredkwargs
io (tuple, required): (str, str) input and output OSC routes. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec","title":"IMLOSC2Vec
","text":" Bases: IMLBase
IML OSC to vector mapping
ExampleThis will map the OSC input to the output vector and store it in tv.iml.o['name']
.
tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLOSC2Vec(IMLBase):\n \"\"\"IML OSC to vector mapping\n\n Example:\n This will map the OSC input to the output vector and store it in `tv.iml.o['name']`.\n\n ```py\n tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.__init__","title":"__init__(osc_map, outvecs, name, **kwargs)
","text":"Initialise IMLOSC2Vec
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredoutvecs
dict
Output vectors dict.
requiredname
str
Name of output vector.
requiredkwargs
io (tuple, required): (str, None) input OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultvector
list[float]
Input vector.
requiredReturns:
Type Descriptionlist[float]
list[float]: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun","title":"IMLVec2Fun
","text":" Bases: IMLBase
IML vector to function mapping
Exampledef update(outvec):\n print('outvec', outvec)\n\ntv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2Fun(IMLBase):\n \"\"\"IML vector to function mapping\n\n Example:\n ```py\n def update(outvec):\n print('outvec', outvec)\n\n tv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Fun
Parameters:
Name Type Description Defaultkwargs
io (tuple, required): (None, callable) output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Defaultinvec
list | Tensor | ndarray
Input vector.
requiredReturns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC","title":"IMLVec2OSC
","text":" Bases: IMLBase
IML vector to OSC mapping.
ExampleSends the output vector to '/tolvera/flock'.
tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2OSC(IMLBase):\n \"\"\"IML vector to OSC mapping.\n\n Example:\n Sends the output vector to '/tolvera/flock'.\n\n ```py\n tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLVec2OSC
Parameters:
Name Type Description Defaultosc_map
(OSCMap, required)
OSCMap instance.
requiredkwargs
io (tuple, required): (None, str) output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Descriptionlist | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code insrc/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec","title":"IMLVec2Vec
","text":" Bases: IMLBase
IML vector to vector mapping.
Input vector is accessed via tv.iml.i['name']
. Output vector is accessed via tv.iml.o['name']
.
tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n}\n\ndef update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n
Parameters:
Name Type Description Defaultkwargs
see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
class IMLVec2Vec(IMLBase):\n \"\"\"IML vector to vector mapping.\n\n Input vector is accessed via `tv.iml.i['name']`.\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n }\n\n def update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n ```\n\n Args:\n kwargs:\n see IMLBase kwargs.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Vec
Source code insrc/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.rand_select","title":"rand_select(method='rand')
","text":"Select randomisation method.
Parameters:
Name Type Description Defaultmethod
str
Randomisation method. Defaults to \"rand\".
'rand'
Raises:
Type DescriptionValueError
Invalid method.
Returns:
Name Type Descriptioncallable
Randomisation method.
Source code insrc/tolvera/iml.py
def rand_select(method=\"rand\"):\n \"\"\"Select randomisation method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n\n Raises:\n ValueError: Invalid method.\n\n Returns:\n callable: Randomisation method.\n \"\"\"\n match method:\n case \"rand\":\n return rand_n\n case \"uniform\":\n return rand_uniform\n case \"normal\":\n return rand_normal\n case \"exponential\":\n return rand_exponential\n case \"cauchy\":\n return rand_cauchy\n case \"lognormal\":\n return rand_lognormal\n case \"sigmoid\":\n return rand_sigmoid\n case \"beta\":\n return rand_beta\n case _:\n raise ValueError(\n f\"[tolvera._iml.rand_select] Invalid method '{method}'. Valid methods: {RAND_METHODS}.\"\n )\n
"},{"location":"reference/tolvera/npndarray_dict/","title":"Npndarray dict","text":"Module for working with dictionary of NumPy ndarrays.
Primaril used by State.
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict","title":"NpNdarrayDict
","text":"A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range. It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.
Attributes:
Name Type Descriptiondata
Dict[str, Dict[str, Union[ndarray, Any]]]
A dictionary where each key represents an attribute,
shape
Tuple[int, int]
The shape of the ndarrays, which is consistent across all attributes.
Examplestate = NpNdarrayDict({ 'i': (np.int32, 2, 10), 'f': (np.float32, 0., 1.), 'v2': (np_vec2, 0., 1.), 'v3': (np_vec3, 0., 1.), 'v4': (np_vec4, 0., 1.), }, (2,2)) state.set_value('i', (0, 0), 5) print(state.get_value('i', (0, 0))) 5
Source code insrc/tolvera/npndarray_dict.py
class NpNdarrayDict:\n \"\"\"\n A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range.\n It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.\n\n Attributes:\n data (Dict[str, Dict[str, Union[np.ndarray, Any]]]): A dictionary where each key represents an attribute,\n and the value is another dictionary with keys 'array', 'min', and 'max', representing the ndarray,\n its minimum value, and its maximum value, respectively.\n shape (Tuple[int, int]): The shape of the ndarrays, which is consistent across all attributes.\n\n Example:\n state = NpNdarrayDict({\n 'i': (np.int32, 2, 10),\n 'f': (np.float32, 0., 1.),\n 'v2': (np_vec2, 0., 1.),\n 'v3': (np_vec3, 0., 1.),\n 'v4': (np_vec4, 0., 1.),\n }, (2,2))\n state.set_value('i', (0, 0), 5)\n print(state.get_value('i', (0, 0)))\n 5\n \"\"\"\n\n def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n\n def init(\n self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]\n ) -> None:\n self.dict = {}\n self.data = {}\n self.size = 0\n for key, (dtype, min_val, max_val) in data_dict.items():\n dshape = self.shape\n length = 1\n # handle np_vec2, np_vec3, np_vec4\n if isinstance(dtype, np.ndarray):\n dshape = dshape + dtype.shape\n length = dtype.shape[0]\n dtype = np.float32\n self.dict[key] = {\n \"dtype\": dtype,\n \"min\": min_val,\n \"max\": max_val,\n \"length\": length,\n \"shape\": dshape,\n \"ndims\": len(dshape),\n }\n self.data[key] = np.zeros(dshape, dtype=dtype)\n size = self.data[key].size\n self.dict[key][\"size\"] = size\n self.size += size\n\n \"\"\"\n to|from vec | list (iml)\n \"\"\"\n\n def from_vec(self, vec: list):\n vec_start = 0\n for key in self.data.keys():\n attr_vec_size = self.dict[key][\"size\"]\n attr_vec = vec[vec_start : vec_start + attr_vec_size]\n self.attr_from_vec(key, attr_vec)\n vec_start += attr_vec_size\n\n def to_vec(self) -> list:\n vec = []\n for key in self.data.keys():\n vec += self.attr_to_vec(key).tolist()\n return vec\n\n def attr_from_vec(self, attr: str, vec: list):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n if len(vec) != np.prod(attr_shape):\n raise ValueError(\n f\"Length of vec {len(vec)} does not match the shape of {attr} {attr_shape}\"\n )\n nparr = np.array(vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting {attr}: {e}\")\n raise\n\n def attr_to_vec(self, attr: str) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n vec = self.data[attr].flatten()\n return vec\n\n def slice_from_vec(\n self, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n # TODO: unique slice obj needed per key...\n # slice_obj = create_safe_slice(slice_args)\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def slice_to_vec(self, slice_args: Union[int, tuple[int, ...], slice]) -> list:\n # TODO: unique slice obj needed per key...\n # vec = []\n # for key in self.data.keys():\n # slice_obj = create_safe_slice(slice_args)\n # vec += self.attr_slice_to_vec(key, slice_obj)\n # return vec\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def attr_slice_from_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n nparr = np.array(slice_vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr][slice_obj] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting slice: {e}\")\n raise\n\n def attr_slice_to_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n vec = self.data[attr][slice_obj].flatten()\n return vec\n\n \"\"\"\n vec slice helpers\n \"\"\"\n\n def get_slice_size(self, slice_args: Union[int, tuple[int, ...], slice]) -> int:\n slice_obj = create_safe_slice(slice_args)\n return np.sum([self.data[key][slice_obj].size for key in self.data.keys()])\n\n def get_attr_slice_size(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> int:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n return self.data[attr][slice_obj].size\n\n \"\"\"\n to|from vec_args (simple osc)\n \"\"\"\n\n \"\"\"\n to|from ndarray | ndarraydict (serialised formats, complex osc)\n \"\"\"\n\n \"\"\"\n ...\n \"\"\"\n\n def set_slice_from_dict(self, slice_indices: tuple, slice_values: dict):\n for key, values in slice_values.items():\n if key not in self.data:\n raise KeyError(f\"Key {key} not found in data\")\n\n array_slice = self.data[key][slice_indices]\n if array_slice.shape != np.array(values).shape:\n raise ValueError(\n f\"Shape {array_slice.shape} of values for key {key} does not match the shape of the slice {np.array(values).shape}\"\n )\n\n self.data[key][slice_indices] = np.array(\n values, dtype=self.dict[key][\"dtype\"]\n )\n\n # def list_to_dict(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary.\n\n # :param _list: The flat list to convert.\n # :return: A dictionary that matches self.dict.\n # \"\"\"\n # pass\n\n # def list_len_to_dict_shape(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary of shapes.\n\n # :param _list: The flat list to convert.\n # :return: shape of the dictionary of _list based on self.shape.\n # \"\"\"\n # list_len = len(_list)\n # dict_shape = ()\n # for key in self.data.keys():\n # dict_shape += self.dict[key]['shape'][1:]\n # dict_len = np.prod(dict_shape)\n # if list_len != dict_len:\n # raise ValueError(f\"Length of list {_list} does not match the length of the dictionary {dict_len}\")\n # return dict_shape\n\n def set_slice_from_list(self, slice_indices: tuple, slice_values_list: list):\n list_index = 0\n\n for key in self.data.keys():\n # Determine the total number of elements required for the current key\n num_elements = np.prod(self.dict[key][\"shape\"][1:])\n print(f\"[{key}] num_elements: {num_elements}\")\n\n # Extract the slice from slice_values_list and reshape if necessary\n slice_shape = self.dict[key][\"shape\"][1:]\n slice = slice_values_list[list_index : list_index + num_elements]\n print(f\"[{key}] slice_shape: {slice_shape}, slice: {slice}\")\n\n # Check if the slice has the correct length\n if len(slice) != num_elements:\n raise ValueError(\n f\"Slice length {len(slice)} for key {key} does not match the number of elements {num_elements}\"\n )\n\n # Reshape the slice for ndarrays with more than 2 dimensions\n if len(slice_shape) > 1:\n slice = np.reshape(slice, slice_shape)\n print(f\"[{key}] (reshaping) slice_shape: {slice_shape}, slice: {slice}\")\n\n # Assign the slice to the corresponding key\n self.data[key][slice_indices] = slice\n\n list_index += num_elements\n print(f\"[{key}] list_index: {list_index}, num_elements: {num_elements}\")\n\n print(f\"data: {self.data}\")\n\n # Check if there are extra values in slice_values_list\n if list_index != len(slice_values_list):\n raise ValueError(\n f\"Extra values {slice_values_list[list_index:]} in slice_values_list {slice_values_list} that do not correspond to any array\"\n )\n\n def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n\n def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n\n def validate(self, new_state: dict[str, np.ndarray]) -> bool:\n raise NotImplementedError(\"validate() not implemented\")\n\n def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n # Add more conditions here if you have other data types\n\n def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n\n def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n ) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.__init__","title":"__init__(data_dict, shape)
","text":"Initialize the State class.
Parameters:
Name Type Description Defaultdata_dict
dict[str, tuple[Any, Any, Any]]
A dictionary where keys are attribute names and values are tuples of (dtype, min_value, max_value).
requiredshape
tuple[int]
The shape of the numpy arrays for each attribute.
required Source code insrc/tolvera/npndarray_dict.py
def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_apply","title":"attr_apply(key, func)
","text":"Apply a user-defined function to the array of a specified key.
Parameters:
Name Type Description Defaultkey
str
The attribute key.
requiredfunc
Callable[[ndarray], ndarray]
A function that takes a numpy array and returns a numpy array.
requiredRaises:
Type DescriptionKeyError
If the key is not found.
Source code insrc/tolvera/npndarray_dict.py
def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_broadcast","title":"attr_broadcast(key, other, op)
","text":"Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.
Parameters:
Name Type Description Defaultkey
str
The key of the array in the dictionary to operate on.
requiredother
Union[ndarray, NpNdarrayDict]
The other array or NpNdarrayDict to use in the operation.
requiredop
Callable[[ndarray, ndarray], ndarray]
A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).
requiredRaises:
Type DescriptionKeyError
If the key is not found in the dictionary.
ValueError
If the operation cannot be broadcasted or if it violates the min-max constraints.
Source code insrc/tolvera/npndarray_dict.py
def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.get_data","title":"get_data()
","text":"Get the entire current data as a dictionary.
Returns:
Type Descriptiondict[str, ndarray]
A dictionary where each key is an attribute and the value is a numpy array.
Source code insrc/tolvera/npndarray_dict.py
def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.randomise","title":"randomise()
","text":"Randomize the entire state dictionary based on the datatype, minimum, and maximum values for each attribute.
Source code insrc/tolvera/npndarray_dict.py
def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.set_data","title":"set_data(new_data)
","text":"Set the data with a new data dictionary.
Parameters:
Name Type Description Defaultnew_data
dict[str, ndarray]
A dictionary representing the new data, where each key is an attribute and the value is a numpy array.
requiredRaises:
Type DescriptionValueError
If the new data is invalid (e.g., wrong shape, type, or value range).
Source code insrc/tolvera/npndarray_dict.py
def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_from_vector_args","title":"dict_from_vector_args(a, scalars=None)
","text":"Convert a list of arguments to a dictionary.
Args: - a: A list of arguments. - scalars: A list of keys that should be unwrapped from lists.
Returns: - A dictionary of keyword arguments.
Source code insrc/tolvera/npndarray_dict.py
def dict_from_vector_args(a: list, scalars=None):\n \"\"\"Convert a list of arguments to a dictionary.\n\n Args:\n - a: A list of arguments.\n - scalars: A list of keys that should be unwrapped from lists.\n\n Returns:\n - A dictionary of keyword arguments.\n \"\"\"\n a = list(a)\n kw = defaultdict(list)\n k = None\n while len(a):\n item = a.pop(0)\n if isinstance(item, str):\n k = item\n else:\n if k is None:\n print(f\"ERROR: bad syntax in {a}\")\n kw[k].append(item)\n # unwrap scalars\n for item in scalars or []:\n if item in kw:\n kw[item] = kw[item][0]\n return kw\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_to_vector_args","title":"dict_to_vector_args(kw)
","text":"Convert a dictionary to a list of arguments.
This function takes a dictionary and returns a list of arguments.
Args: - kw: A dictionary of keyword arguments.
Returns: - A list of arguments.
Source code insrc/tolvera/npndarray_dict.py
def dict_to_vector_args(kw):\n \"\"\"Convert a dictionary to a list of arguments.\n\n This function takes a dictionary and returns a list of arguments.\n\n Args:\n - kw: A dictionary of keyword arguments.\n\n Returns:\n - A list of arguments.\n \"\"\"\n args = []\n for key, value in kw.items():\n args.append(key)\n if isinstance(value, (list, np.ndarray)):\n # If it's a numpy array (regardless of its shape), flatten it and extend the list\n if isinstance(value, np.ndarray):\n value = value.flatten()\n args.extend(value)\n else:\n # Append the scalar value associated with the key\n args.append(value)\n return args\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.ndarraydict_from_vector_args","title":"ndarraydict_from_vector_args(lst, shapes)
","text":"Convert a list to a dictionary where each list is turned into a numpy array.
This function takes a list in the format output by dict_from_vector_args
and converts it into a dictionary. Each key's list of values is converted into a numpy array with a specified shape.
Args: - lst: The list to be converted. - shapes: A dictionary where keys correspond to the keys in the original list and values are tuples representing the desired shape of the numpy array.
Returns: - A dictionary with keys mapped to numpy arrays.
Source code insrc/tolvera/npndarray_dict.py
def ndarraydict_from_vector_args(lst, shapes):\n \"\"\"Convert a list to a dictionary where each list is turned into a numpy array.\n\n This function takes a list in the format output by `dict_from_vector_args` and converts it\n into a dictionary. Each key's list of values is converted into a numpy array with a\n specified shape.\n\n Args:\n - lst: The list to be converted.\n - shapes: A dictionary where keys correspond to the keys in the original list and\n values are tuples representing the desired shape of the numpy array.\n\n Returns:\n - A dictionary with keys mapped to numpy arrays.\n \"\"\"\n\n def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n # Flatten only if all elements are lists\n return [item for sublist in lst for item in sublist]\n return lst\n\n kw = defaultdict(list)\n k = None\n for item in lst:\n if isinstance(item, str):\n k = item\n else:\n kw[k].append(item)\n\n for key, shape in shapes.items():\n if key in kw:\n values = flatten(kw[key])\n array_size = np.prod(shape)\n if len(values) != array_size:\n raise ValueError(\n f\"Shape mismatch for key '{key}': expected {array_size} elements, got {len(values)}.\"\n )\n kw[key] = np.array(values).reshape(shape)\n\n return dict(kw)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.shapes_from_ndarray_dict","title":"shapes_from_ndarray_dict(ndarray_dict)
","text":"Return a dictionary of shapes given a dictionary of numpy ndarrays.
This function takes a dictionary where values are numpy ndarrays and returns a new dictionary with the same keys, where each value is the shape of the ndarray.
Args: - ndarray_dict: A dictionary where values are numpy ndarrays.
Returns: - A dictionary where each key maps to the shape of the corresponding ndarray.
Source code insrc/tolvera/npndarray_dict.py
def shapes_from_ndarray_dict(ndarray_dict):\n \"\"\"Return a dictionary of shapes given a dictionary of numpy ndarrays.\n\n This function takes a dictionary where values are numpy ndarrays and returns\n a new dictionary with the same keys, where each value is the shape of the ndarray.\n\n Args:\n - ndarray_dict: A dictionary where values are numpy ndarrays.\n\n Returns:\n - A dictionary where each key maps to the shape of the corresponding ndarray.\n \"\"\"\n shapes = {}\n for key, array in ndarray_dict.items():\n shapes[key] = array.shape\n return shapes\n
"},{"location":"reference/tolvera/particles/","title":"Particles","text":"Particle system.
The Tolvera particle system consists of a Particle class and a Particles class. The Particle class is a Taichi dataclass for a single particle, and the Particles class is a Taichi data_oriented class containing a Particle field.
The Particles class also contains methods for processing the particle system, such as updating the particles, and getting and setting particle properties.
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle","title":"Particle
","text":"Particle data structure and methods.
Source code insrc/tolvera/particles.py
@ti.dataclass\nclass Particle:\n \"\"\"Particle data structure and methods.\"\"\"\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n ppos: ti.math.vec2\n pvel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n\n @ti.func\n def dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n\n @ti.func\n def dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n\n @ti.func\n def dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n\n @ti.func\n def dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n\n # @ti.func\n # def dist_wrap(self, other, x, y):\n # dx = self.pos[0] - other.pos[0]\n # dy = self.pos[1] - other.pos[1]\n # # Wrap around for the x-axis\n # if abs(dx) > x / 2:\n # dx = x - abs(dx)\n # if self.pos[0] < other.pos[0]:\n # dx = -dx\n # # Wrap around for the y-axis\n # if abs(dy) > y / 2:\n # dy = y - abs(dy)\n # if self.pos[1] < other.pos[1]:\n # dy = -dy\n # return ti.Vector([dx, dy])\n # @ti.func\n # def dist_wrap(self, other, width, height):\n # # Compute the element-wise absolute difference\n # self_abs = ti.abs(self.pos)\n # other_abs = ti.abs(other.pos)\n # delta = self_abs - other_abs\n # # Check if wrapping around is shorter for both the x and y components\n # if delta[0] > width / 2:\n # delta[0] = width - delta[0]\n # if delta[1] > height / 2:\n # delta[1] = height - delta[1]\n # # Correct the signs if necessary\n # if self.pos[0] > other.pos[0] and delta[0] > 0:\n # delta[0] = -delta[0]\n # if self.pos[1] > other.pos[1] and delta[1] > 0:\n # delta[1] = -delta[1]\n # return delta\n @ti.func\n def randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n\n @ti.func\n def randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n\n @ti.func\n def randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist","title":"dist(other)
","text":"Distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: Distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_norm","title":"dist_norm(other)
","text":"ti.math.norm() distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: ti.math.norm() distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_normalized","title":"dist_normalized(other)
","text":"ti.math.normalized() distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredReturns:
Type Descriptionti.math.vec2: ti.math.normalized() distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_wrap","title":"dist_wrap(other, x, y)
","text":"Wrap around distance between two particles.
Parameters:
Name Type Description Defaultother
Particle
Other particle.
requiredx
float
Width.
requiredy
float
Height.
requiredReturns:
Type Descriptionti.math.vec2: Wrap around distance between the two particles.
Source code insrc/tolvera/particles.py
@ti.func\ndef dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise","title":"randomise(x, y)
","text":"Randomise the particle's position and velocity.
Parameters:
Name Type Description Defaultx
f32
Width.
requiredy
f32
Height.
required Source code insrc/tolvera/particles.py
@ti.func\ndef randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_pos","title":"randomise_pos(x, y)
","text":"Randomise the particle's position.
Parameters:
Name Type Description Defaultx
f32
Width.
requiredy
f32
Height.
required Source code insrc/tolvera/particles.py
@ti.func\ndef randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_vel","title":"randomise_vel()
","text":"Randomise the particle's velocity.
Source code insrc/tolvera/particles.py
@ti.func\ndef randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles","title":"Particles
","text":"Particle system.
Source code insrc/tolvera/particles.py
@ti.data_oriented\nclass Particles:\n \"\"\"Particle system.\"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n self.tv.s.collisions_p = {\n 'state': {\n 'collision': (ti.i32, 0, 1),\n 'dpos': (ti.math.vec2, 0., 1.),\n 'dvel': (ti.math.vec2, 0., 1.),\n },\n 'shape': self.n,\n }\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n\n def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n\n @ti.kernel\n def assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n\n def _randomise(self):\n \"\"\"Randomise the particle system (Python scope).\"\"\"\n self.randomise()\n\n @ti.kernel\n def randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n\n @ti.kernel\n def update(self):\n \"\"\"Update the particle system.\"\"\"\n j = 0\n for i in range(self.n):\n if self.field[i] == 0.0: continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n self.detect_collisions(i, self.C.COLL_RAD)\n self.update_prev(i)\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n\n @ti.func\n def toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n\n @ti.func\n def limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n\n @ti.func\n def detect_collisions(self, i: ti.i32, radius: ti.f32):\n \"\"\"Detect collisions between particles.\n\n TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n TODO: Multiple collision states? Collided, Colliding, etc.\n TODO: Detect collisions between external objects.\n\n Args:\n i (ti.i32): Particle index.\n radius (ti.f32): Collision radius.\n \"\"\"\n for j in range(self.n):\n p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n if p2.active == 0: continue\n dist = p1.pos - p2.pos\n if dist.norm() < radius:\n pdist = p1.ppos - p2.ppos\n dpos = ti.abs(pdist - dist)\n dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n self.tv.s.collisions_p[i].dpos = dpos\n self.tv.s.collisions_p[i].dvel = dvel\n if pdist.norm() > radius:\n self.tv.s.collisions_p[i].collision = 1\n else:\n self.tv.s.collisions_p[i].collision = 0\n\n @ti.func\n def update_prev(self, i: ti.i32):\n \"\"\"Update the previous position and velocity of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n self.field[i].ppos = self.field[i].pos\n self.field[i].pvel = self.field[i].vel\n\n @ti.kernel\n def activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n\n def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update()\n\n @ti.kernel\n def set_total_active(self, total: ti.i32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n\n @ti.kernel\n def set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n amount (ti.f32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = amount\n\n @ti.kernel\n def set_species_total_active(self, i: ti.i32, total: ti.i32):\n \"\"\"Set the total number of active particles for a species.\n\n Args:\n i (ti.i32): Species index.\n total (ti.i32): Total active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n\n @ti.kernel\n def set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n total: (ti.i32): Total number of active particles.\n amount (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = amount\n\n def set_pos(self, i, x, y):\n self.field[i].pos = [x, y]\n\n def set_vel(self, i, x, y):\n self.field[i].vel = [x, y]\n\n def set_speed(self, i, s):\n self.field[i].speed = s\n\n def set_size(self, i, s):\n self.field[i].size = s\n\n def get_pos(self, i):\n return self.field[i].pos.to_numpy().tolist()\n\n def get_vel(self, i):\n return self.field[i].vel.to_numpy().tolist()\n\n def get_pos_all_1d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().flatten().tolist()\n\n def get_pos_all_2d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().tolist()\n\n def get_vel_all_1d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().flatten().tolist()\n\n def get_vel_all_2d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_all(self):\n # for i in range(self.active_count[None]):\n # idx = self.active_indexes[i]\n # p = self.field[idx]\n # self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # TODO: Only send active particle positions...? Or inactive=-1?\n for i in range(self.n):\n p = self.field[i]\n # if p.active > 0.0: # causes IML shape assertion error\n self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # else:\n # self.tmp_pos[i] = [0.0,0.0] # ???\n\n @ti.kernel\n def _get_vel_all(self):\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.tmp_vel[i] = p.vel\n\n def get_pos_species_1d(self, species: int):\n self._get_pos_species()\n return self.tmp_pos_species.to_numpy().flatten().tolist()\n\n def get_pos_species_2d(self, species: int):\n if species > self.tv.species - 1:\n return\n self._get_pos_species(species)\n return self.tmp_pos_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n pos = p.pos / [self.tv.x, self.tv.y]\n self.tmp_pos_species[species_index] = pos\n\n def get_vel_species_1d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().flatten().tolist()\n\n def get_vel_species_2d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_vel_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n vel = p.vel / [self.tv.x, self.tv.y]\n self.tmp_vel_species[species_index] = vel\n\n def get_vel_stats_species_1d(self, species):\n self._species_velocity_statistics(species)\n return self.tmp_vel_stats.to_numpy().flatten().tolist()\n\n @ti.kernel\n def _species_velocity_statistics(self, i: ti.i32):\n \"\"\"\n Centre of Mass Velocity: This is the average velocity of all particles in the species.\n Relative Velocity: This is the average velocity of all particles in the species relative to the centre of mass velocity.\n Angular Momentum: This is the sum of the angular momentum of all particles, which is given by mass * velocity * radius for each particle.\n Kinetic Energy: This is the sum of the kinetic energy of all particles, which is given by 0.5 * mass * velocity^2 for each particle.\n Temperature: In statistical mechanics, the temperature of a system of particles is related to the average kinetic energy of the particles.\n \"\"\"\n centre_of_mass_velocity = ti.Vector([0.0, 0.0])\n relative_velocity = ti.Vector([0.0, 0.0])\n angular_momentum = ti.Vector([0.0])\n kinetic_energy = ti.Vector([0.0])\n for j in range(self.n):\n if self.field[j].species == i:\n v = self.field[j].vel\n p = self.field[j].pos\n m = self.field[j].mass\n centre_of_mass_velocity += v\n relative_velocity += v # - centre_of_mass_velocity\n angular_momentum += m * ti.math.cross(v, p)\n kinetic_energy += 0.5 * m * v.norm_sqr()\n centre_of_mass_velocity = centre_of_mass_velocity / self.n_per_species\n relative_velocity = (\n relative_velocity - centre_of_mass_velocity * self.n_per_species\n ) / self.n_per_species\n temperature = 2.0 * kinetic_energy / (self.particles_per_species * 1.380649e-23)\n self.tmp_vel_stats[0] = centre_of_mass_velocity[0]\n self.tmp_vel_stats[1] = centre_of_mass_velocity[1]\n self.tmp_vel_stats[2] = relative_velocity[0]\n self.tmp_vel_stats[3] = relative_velocity[1]\n self.tmp_vel_stats[4] = angular_momentum[0]\n self.tmp_vel_stats[5] = kinetic_energy[0]\n self.tmp_vel_stats[6] = temperature[0]\n\n def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n\n def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n\n def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__call__","title":"__call__()
","text":"Call will process the particle system.
Source code insrc/tolvera/particles.py
def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the particle system.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance.
required**kwargs
Keyword arguments (currently there are none).
{}
Source code in src/tolvera/particles.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n self.tv.s.collisions_p = {\n 'state': {\n 'collision': (ti.i32, 0, 1),\n 'dpos': (ti.math.vec2, 0., 1.),\n 'dvel': (ti.math.vec2, 0., 1.),\n },\n 'shape': self.n,\n }\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.activity_decay","title":"activity_decay()
","text":"Decay the activity of the particles.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.assign_species","title":"assign_species()
","text":"Assign species to particles.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.detect_collisions","title":"detect_collisions(i, radius)
","text":"Detect collisions between particles.
TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s? TODO: Multiple collision states? Collided, Colliding, etc. TODO: Detect collisions between external objects.
Parameters:
Name Type Description Defaulti
i32
Particle index.
requiredradius
f32
Collision radius.
required Source code insrc/tolvera/particles.py
@ti.func\ndef detect_collisions(self, i: ti.i32, radius: ti.f32):\n \"\"\"Detect collisions between particles.\n\n TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n TODO: Multiple collision states? Collided, Colliding, etc.\n TODO: Detect collisions between external objects.\n\n Args:\n i (ti.i32): Particle index.\n radius (ti.f32): Collision radius.\n \"\"\"\n for j in range(self.n):\n p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n if p2.active == 0: continue\n dist = p1.pos - p2.pos\n if dist.norm() < radius:\n pdist = p1.ppos - p2.ppos\n dpos = ti.abs(pdist - dist)\n dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n self.tv.s.collisions_p[i].dpos = dpos\n self.tv.s.collisions_p[i].dvel = dvel\n if pdist.norm() > radius:\n self.tv.s.collisions_p[i].collision = 1\n else:\n self.tv.s.collisions_p[i].collision = 0\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.init","title":"init()
","text":"Initialise the particle system.
Source code insrc/tolvera/particles.py
def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.limit_speed","title":"limit_speed(i)
","text":"Limit the speed of a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.process","title":"process()
","text":"Process the particle system.
Source code insrc/tolvera/particles.py
def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.randomise","title":"randomise()
","text":"Randomise the particle system (Taichi scope).
Source code insrc/tolvera/particles.py
@ti.kernel\ndef randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.reset","title":"reset()
","text":"Reset the particle system.
Source code insrc/tolvera/particles.py
def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active","title":"set_species_total_active(i, total)
","text":"Set the total number of active particles for a species.
Parameters:
Name Type Description Defaulti
i32
Species index.
requiredtotal
i32
Total active particles.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_species_total_active(self, i: ti.i32, total: ti.i32):\n \"\"\"Set the total number of active particles for a species.\n\n Args:\n i (ti.i32): Species index.\n total (ti.i32): Total active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active_amount","title":"set_species_total_active_amount(i, total, amount)
","text":"Set particle activity amount of a species.
Parameters:
Name Type Description Defaulti
i32
Species index.
requiredtotal
i32
(ti.i32): Total number of active particles.
requiredamount
i32
Amount of activity.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n total: (ti.i32): Total number of active particles.\n amount (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j >= total:\n self.field[j].active = 0\n else:\n self.field[j].active = amount\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active","title":"set_total_active(total)
","text":"Set the total number of active particles.
Parameters:
Name Type Description Defaulttotal
i32
Total active particles.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_total_active(self, total: ti.i32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active_amount","title":"set_total_active_amount(total, amount)
","text":"Set the total number of active particles.
Parameters:
Name Type Description Defaulttotal
i32
Total active particles.
requiredamount
f32
Amount of activity.
required Source code insrc/tolvera/particles.py
@ti.kernel\ndef set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n \"\"\"Set the total number of active particles.\n\n Args:\n total (ti.i32): Total active particles.\n amount (ti.f32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i >= total:\n self.field[i].active = 0\n else:\n self.field[i].active = amount\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.speed","title":"speed(speed=None)
","text":"Get or set the speed of the particle system.
Parameters:
Name Type Description Defaultspeed
float
Speed. Defaults to None.
None
Returns:
Name Type Descriptionfloat
Speed.
Source code insrc/tolvera/particles.py
def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.toroidal_wrap","title":"toroidal_wrap(i)
","text":"Toroidal wrap a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update","title":"update()
","text":"Update the particle system.
Source code insrc/tolvera/particles.py
@ti.kernel\ndef update(self):\n \"\"\"Update the particle system.\"\"\"\n j = 0\n for i in range(self.n):\n if self.field[i] == 0.0: continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n self.detect_collisions(i, self.C.COLL_RAD)\n self.update_prev(i)\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_prev","title":"update_prev(i)
","text":"Update the previous position and velocity of a particle.
Parameters:
Name Type Description Defaulti
i32
Particle index.
required Source code insrc/tolvera/particles.py
@ti.func\ndef update_prev(self, i: ti.i32):\n \"\"\"Update the previous position and velocity of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n self.field[i].ppos = self.field[i].pos\n self.field[i].pvel = self.field[i].vel\n
"},{"location":"reference/tolvera/patches/","title":"Patches","text":"Patches for third-party libraries.
Current patches: - 'dill.source.findsource fails when in asyncio REPL' https://github.com/uqfoundation/dill/issues/627
"},{"location":"reference/tolvera/patches/#tolvera.patches.findsource","title":"findsource(object)
","text":"Return the entire source file and starting line number for an object. For interactively-defined objects, the 'file' is the interpreter's history.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a list of all the lines in the file and the line number indexes a line in that list. An IOError is raised if the source code cannot be retrieved, while a TypeError is raised for objects where the source code is unavailable (e.g. builtins).
Source code insrc/tolvera/patches.py
def findsource(object):\n # print(f\"[dill.source.findsource] PATCHED\")\n\n \"\"\"Return the entire source file and starting line number for an object.\n For interactively-defined objects, the 'file' is the interpreter's history.\n\n The argument may be a module, class, method, function, traceback, frame,\n or code object. The source code is returned as a list of all the lines\n in the file and the line number indexes a line in that list. An IOError\n is raised if the source code cannot be retrieved, while a TypeError is\n raised for objects where the source code is unavailable (e.g. builtins).\"\"\"\n\n def patched_getfile(module):\n # set file = None when module.__package__ == 'asyncio'\n # print(f\"[dill.source.patched_getfile] module={module}\\nmodule.__package__={module.__package__}\\nmodule.__name__={module.__name__}\")\n if module.__package__ == \"asyncio\":\n raise TypeError\n # if module.__package__ == 'sardine':\n # raise TypeError\n ret = getfile(module)\n return ret\n\n module = getmodule(object)\n # try: file = getfile(module)\n try:\n file = patched_getfile(module)\n except TypeError:\n file = None\n # correctly compute `is_module_main` when in asyncio\n is_module_main = module and module.__name__ == \"__main__\" and not file\n # is_module_main = (module and module.__name__ == '__main__' or module.__name__ == 'sardine' and not file)\n print(\n f\"[dill.source.findsource] module: {module}, file: {file}, is_module_main: {is_module_main}\"\n )\n if IS_IPYTHON and is_module_main:\n # FIXME: quick fix for functions and classes in IPython interpreter\n try:\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except TypeError:\n if isclass(object):\n for object_method in filter(isfunction, object.__dict__.values()):\n # look for a method of the class\n file_candidate = getfile(object_method)\n if not file_candidate.startswith(\"<ipython-input-\"):\n continue\n file = file_candidate\n sourcefile = getsourcefile(object_method)\n break\n if file:\n lines = linecache.getlines(file)\n else:\n # fallback to use history\n history = \"\\n\".join(get_ipython().history_manager.input_hist_parsed)\n lines = [line + \"\\n\" for line in history.splitlines()]\n # use readline when working in interpreter (i.e. __main__ and not file)\n elif is_module_main:\n try:\n import readline\n\n err = \"\"\n except ImportError:\n import sys\n\n err = sys.exc_info()[1].args[0]\n if sys.platform[:3] == \"win\":\n err += \", please install 'pyreadline'\"\n if err:\n raise IOError(err)\n lbuf = readline.get_current_history_length()\n lines = [readline.get_history_item(i) + \"\\n\" for i in range(1, lbuf)]\n else:\n try: # special handling for class instances\n if not isclass(object) and isclass(type(object)): # __class__\n file = getfile(module)\n sourcefile = getsourcefile(module)\n else: # builtins fail with a TypeError\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except (TypeError, AttributeError): # fail with better error\n file = getfile(object)\n sourcefile = getsourcefile(object)\n if not sourcefile and file[:1] + file[-1:] != \"<>\":\n raise IOError(\"source code not available\")\n file = sourcefile if sourcefile else file\n\n module = getmodule(object, file)\n if module:\n lines = linecache.getlines(file, module.__dict__)\n else:\n lines = linecache.getlines(file)\n\n if not lines:\n raise IOError(\"could not extract source code\")\n\n # FIXME: all below may fail if exec used (i.e. exec('f = lambda x:x') )\n if ismodule(object):\n return lines, 0\n\n # NOTE: beneficial if search goes from end to start of buffer history\n name = pat1 = obj = \"\"\n pat2 = r\"^(\\s*@)\"\n # pat1b = r'^(\\s*%s\\W*=)' % name #FIXME: finds 'f = decorate(f)', not exec\n if ismethod(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__func__\n if isfunction(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n obj = object # XXX: better a copy?\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__code__\n if istraceback(object):\n object = object.tb_frame\n if isframe(object):\n object = object.f_code\n if iscode(object):\n if not hasattr(object, \"co_firstlineno\"):\n raise IOError(\"could not find function definition\")\n # stdin = object.co_filename == '<stdin>'\n stdin = object.co_filename in (\"<console>\", \"<stdin>\")\n # print(f\"[dill.source.findsource] object.co_filename: {object.co_filename}, stdin: {stdin}\")\n if stdin:\n lnum = len(lines) - 1 # can't get lnum easily, so leverage pat\n if not pat1:\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n else:\n lnum = object.co_firstlineno - 1\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n pat1 = re.compile(pat1)\n pat2 = re.compile(pat2)\n # XXX: candidate_lnum = [n for n in range(lnum) if pat1.match(lines[n])]\n while lnum > 0: # XXX: won't find decorators in <stdin> ?\n line = lines[lnum]\n if pat1.match(line):\n if not stdin:\n break # co_firstlineno does the job\n if name == \"<lambda>\": # hackery needed to confirm a match\n if _matchlambda(obj, line):\n break\n else: # not a lambda, just look for the name\n if name in line: # need to check for decorator...\n hats = 0\n for _lnum in range(lnum - 1, -1, -1):\n if pat2.match(lines[_lnum]):\n hats += 1\n else:\n break\n lnum = lnum - hats\n break\n lnum = lnum - 1\n return lines, lnum\n\n try: # turn instances into classes\n if not isclass(object) and isclass(type(object)): # __class__\n object = object.__class__ # XXX: sometimes type(class) is better?\n # XXX: we don't find how the instance was built\n except AttributeError:\n pass\n if isclass(object):\n name = object.__name__\n pat = re.compile(r\"^(\\s*)class\\s*\" + name + r\"\\b\")\n # make some effort to find the best matching class definition:\n # use the one with the least indentation, which is the one\n # that's most probably not inside a function definition.\n candidates = []\n for i in range(len(lines) - 1, -1, -1):\n match = pat.match(lines[i])\n if match:\n # if it's at toplevel, it's already the best one\n if lines[i][0] == \"c\":\n return lines, i\n # else add whitespace to candidate list\n candidates.append((match.group(1), i))\n if candidates:\n # this will sort by whitespace, and by line number,\n # less whitespace first #XXX: should sort high lnum before low\n candidates.sort()\n return lines, candidates[0][1]\n else:\n raise IOError(\"could not find class definition\")\n raise IOError(\"could not find code object\")\n
"},{"location":"reference/tolvera/pixels/","title":"Pixels","text":"Pixels module.
ExampleDraw a red rectangle in the centre of the screen.
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n tv.p()\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels","title":"Pixels
","text":"Pixels class for drawing pixels to the screen.
This class is used to draw pixels to the screen. It contains methods for drawing points, lines, rectangles, circles, triangles, and polygons. It also contains methods for blending pixels together, flipping pixels, inverting pixels, and diffusing, decaying and clearing pixels.
It tries to follow a similar API to the Processing library.
Source code insrc/tolvera/pixels.py
@ti.data_oriented\nclass Pixels:\n \"\"\"Pixels class for drawing pixels to the screen.\n\n This class is used to draw pixels to the screen. It contains methods for drawing\n points, lines, rectangles, circles, triangles, and polygons. It also contains\n methods for blending pixels together, flipping pixels, inverting pixels, and\n diffusing, decaying and clearing pixels.\n\n It tries to follow a similar API to the Processing library.\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n\n\n def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n\n @ti.kernel\n def k_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n @ti.kernel\n def f_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n\n @ti.kernel\n def clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n\n @ti.kernel\n def diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n\n @ti.func\n def background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n\n @ti.func\n def point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n\n @ti.func\n def rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n\n @ti.func\n def line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n\n @ti.func\n def circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n\n @ti.func\n def triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n\n @ti.func\n def polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n\n @ti.func\n def _is_inside(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n is_inside = 0\n if self.polygon_mode == \"crossing\":\n is_inside = self._is_inside_crossing(p, x, y, l)\n elif self.polygon_mode == \"winding\":\n is_inside = self._is_inside_winding(p, x, y, l)\n return is_inside\n\n @ti.func\n def _is_inside_crossing(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using crossing number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if (v0[1] <= p[1] and v1[1] > p[1]) or (v0[1] > p[1] and v1[1] <= p[1]):\n vt = (p[1] - v0[1]) / (v1[1] - v0[1])\n if p[0] < v0[0] + vt * (v1[0] - v0[0]):\n n += 1\n return n % 2\n\n @ti.func\n def _is_inside_winding(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using winding number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if v0[1] <= p[1] and v1[1] > p[1] and (v0 - v1).cross(p - v1) > 0:\n n += 1\n elif v1[1] <= p[1] and (v0 - v1).cross(p - v1) < 0:\n n -= 1\n return n\n\n @ti.kernel\n def flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[self.x - 1 - i, j]\n\n @ti.kernel\n def flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[i, self.y - 1 - j]\n\n @ti.kernel\n def invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n\n @ti.kernel\n def decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n\n def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_add(self, rgba: ti.template()):\n \"\"\"Blend by adding pixels together (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] += rgba[i, j]\n\n def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_sub(self, rgba: ti.template()):\n \"\"\"Blend by subtracting pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] -= rgba[i, j]\n\n def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_mul(self, rgba: ti.template()):\n \"\"\"Blend by multiplying pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rgba[i, j]\n\n def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_div(self, rgba: ti.template()):\n \"\"\"Blend by dividing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] /= rgba[i, j]\n\n def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_min(self, rgba: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.min(self.px.rgba[i, j], rgba[i, j])\n\n def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_max(self, rgba: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.max(self.px.rgba[i, j], rgba[i, j])\n\n def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff(self, rgba: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(self.px.rgba[i, j] - rgba[i, j])\n\n def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff_inv(self, rgba: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(rgba[i, j] - self.px.rgba[i, j])\n\n def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n\n @ti.kernel\n def _blend_mix(self, rgba: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.math.mix(self.px.rgba[i, j], rgba[i, j], amount)\n\n @ti.kernel\n def blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n\n def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n ):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n\n @ti.kernel\n def _particles(self, particles: ti.template(), species: ti.template(), shape: int):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (int): Shape enum value.\n \"\"\"\n for i in range(self.tv.p.n):\n p = particles.field[i]\n s = species[p.species]\n if p.active == 0.0:\n continue\n px = ti.cast(p.pos[0], ti.i32)\n py = ti.cast(p.pos[1], ti.i32)\n vx = ti.cast(p.pos[0] + p.vel[0] * 20, ti.i32)\n vy = ti.cast(p.pos[1] + p.vel[1] * 20, ti.i32)\n rgba = s.rgba * self.CONSTS.BRIGHTNESS\n if shape == 0:\n self.point(px, py, rgba)\n elif shape == 1:\n self.line(px, py, vx, vy, rgba)\n elif shape == 2:\n side = int(s.size) * 2\n self.rect(px, py, side, side, rgba)\n elif shape == 3:\n self.circle(px, py, p.size, rgba)\n elif shape == 4:\n a = p.pos\n b = p.pos + 1\n c = a + b\n self.triangle(a, b, c, rgba)\n # elif shape == 5:\n # self.polygon(px, py, rgba)\n\n def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n\n def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__call__","title":"__call__()
","text":"Call returns pixels.
Source code insrc/tolvera/pixels.py
def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Pixels
Parameters:
Name Type Description Defaulttolvera
Tolvera
T\u00f6lvera instance.
required**kwargs
Keyword arguments. polygon_mode (str): Polygon mode. Defaults to \"crossing\". brightness (float): Brightness. Defaults to 1.0.
{}
Source code in src/tolvera/pixels.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.background","title":"background(r, g, b)
","text":"Set background colour.
Parameters:
Name Type Description Defaultr
f32
Red.
requiredg
f32
Green.
requiredb
f32
Blue.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_add","title":"blend_add(px)
","text":"Blend by adding pixels together (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff","title":"blend_diff(px)
","text":"Blend by taking the difference of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff_inv","title":"blend_diff_inv(px)
","text":"Blend by taking the inverse difference of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_div","title":"blend_div(px)
","text":"Blend by dividing pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_max","title":"blend_max(px)
","text":"Blend by taking the maximum of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_min","title":"blend_min(px)
","text":"Blend by taking the minimum of each pixel (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mix","title":"blend_mix(px, amount)
","text":"Blend by mixing pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
requiredamount
f32
Amount to mix.
required Source code insrc/tolvera/pixels.py
def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mul","title":"blend_mul(px)
","text":"Blend by multiplying pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_sub","title":"blend_sub(px)
","text":"Blend by subtracting pixels (Python scope).
Parameters:
Name Type Description Defaultpx
template
Pixels to blend with.
required Source code insrc/tolvera/pixels.py
def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blur","title":"blur(radius)
","text":"Blur pixels.
Parameters:
Name Type Description Defaultradius
i32
Blur radius.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circle","title":"circle(x, y, r, rgba)
","text":"Draw a filled circle.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredr
i32
Radius.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circles","title":"circles(x, y, r, rgba)
","text":"Draw circles with the same colour.
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredr
template
Radii.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.clear","title":"clear()
","text":"Clear pixels.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.decay","title":"decay(rate)
","text":"Decay pixels.
Parameters:
Name Type Description Defaultrate
f32
decay rate.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.diffuse","title":"diffuse(evaporate)
","text":"Diffuse pixels.
Parameters:
Name Type Description Defaultevaporate
float
Evaporation rate.
required Source code insrc/tolvera/pixels.py
@ti.kernel\ndef diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_x","title":"flip_x()
","text":"Flip image in x-axis.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[self.x - 1 - i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_y","title":"flip_y()
","text":"Flip image in y-axis.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = self.px.rgba[i, self.y - 1 - j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.get","title":"get()
","text":"Get pixels.
Source code insrc/tolvera/pixels.py
def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.invert","title":"invert()
","text":"Invert image.
Source code insrc/tolvera/pixels.py
@ti.kernel\ndef invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.line","title":"line(x0, y0, x1, y1, rgba)
","text":"Draw a line using Bresenham's algorithm.
Parameters:
Name Type Description Defaultx0
i32
X start position.
requiredy0
i32
Y start position.
requiredx1
i32
X end position.
requiredy1
i32
Y end position.
requiredrgba
vec4
Colour.
requiredTODO: thickness TODO: anti-aliasing TODO: should lines wrap around (as two lines)?
Source code insrc/tolvera/pixels.py
@ti.func\ndef line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.particles","title":"particles(particles, species, shape='circle')
","text":"Draw particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredspecies
template
Species.
requiredshape
str
Shape. Defaults to \"circle\".
'circle'
Source code in src/tolvera/pixels.py
def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.point","title":"point(x, y, rgba)
","text":"Draw point.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.points","title":"points(x, y, rgba)
","text":"Draw points with the same colour.
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.polygon","title":"polygon(x, y, rgba)
","text":"Draw a filled polygon.
Polygons are drawn according to the polygon mode, which can be \"crossing\" (default) or \"winding\". First, the bounding box of the polygon is calculated. Then, we check if each pixel in the bounding box is inside the polygon. If it is, we draw it (along with each neighbour pixel).
Reference for point in polygon inclusion testing: http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py
Parameters:
Name Type Description Defaultx
template
X positions.
requiredy
template
Y positions.
requiredrgba
vec4
Colour.
requiredTODO: fill arg
Source code insrc/tolvera/pixels.py
@ti.func\ndef polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rect","title":"rect(x, y, w, h, rgba)
","text":"Draw a filled rectangle.
Parameters:
Name Type Description Defaultx
i32
X position.
requiredy
i32
Y position.
requiredw
i32
Width.
requiredh
i32
Height.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rgba_from_px","title":"rgba_from_px(px)
","text":"Get rgba from pixels.
Parameters:
Name Type Description Defaultpx
Any
Pixels to get rgba from.
requiredRaises:
Type DescriptionTypeError
If pixel field cannot be found.
Returns:
Name Type DescriptionMatrixField
RGBA matrix field.
Source code insrc/tolvera/pixels.py
def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.set","title":"set(px)
","text":"Set pixels.
Parameters:
Name Type Description Defaultpx
Any
Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).
required Source code insrc/tolvera/pixels.py
def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.triangle","title":"triangle(a, b, c, rgba)
","text":"Draw a filled triangle.
Parameters:
Name Type Description Defaulta
vec2
Point A.
requiredb
vec2
Point B.
requiredc
vec2
Point C.
requiredrgba
vec4
Colour.
required Source code insrc/tolvera/pixels.py
@ti.func\ndef triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n
"},{"location":"reference/tolvera/sketchbook/","title":"Sketchbook","text":"Sketchbook module for listing and running sketches from the command line.
WIP.
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketch_info","title":"get_sketch_info(sketch_file, sketchbook_folder='./')
","text":"Gets information about a specific sketch file.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionDict[str, Any]
Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.
Source code insrc/tolvera/sketchbook.py
def get_sketch_info(sketch_file: str, sketchbook_folder: str = \"./\") -> Dict[str, Any]:\n \"\"\"\n Gets information about a specific sketch file.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.\n \"\"\"\n validate_sketch_file(sketch_file, sketchbook_folder)\n datetimefmt = \"%Y-%m-%d %H:%M:%S\"\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n file_info = os.stat(file_path)\n size = file_info.st_size\n modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(datetimefmt)\n created = datetime.datetime.fromtimestamp(file_info.st_ctime).strftime(datetimefmt)\n return {\n \"name\": module_name,\n \"path\": file_path,\n \"size\": size,\n \"modified\": modified,\n \"created\": created,\n }\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files","title":"get_sketchbook_files(sketchbook_folder='./')
","text":"Gets all sketch files from the sketchbook folder.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionList[str]
List[str]: List of sketch file names.
Source code insrc/tolvera/sketchbook.py
def get_sketchbook_files(sketchbook_folder: str = \"./\") -> List[str]:\n \"\"\"\n Gets all sketch files from the sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[str]: List of sketch file names.\n \"\"\"\n files = os.listdir(sketchbook_folder)\n exclude = [\"__init__.py\", \"__pycache__\", \".DS_Store\", \"imgui.ini\"]\n return [f for f in files if f not in exclude]\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files_info","title":"get_sketchbook_files_info(sketches, sketchbook_folder='./')
","text":"Gets information about all sketch files in the sketchbook folder.
Parameters:
Name Type Description Defaultsketches
List[str]
List of sketch file names.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type DescriptionList[Dict[str, Any]]
List[Dict[str, Any]]: List of sketch information dictionaries.
Source code insrc/tolvera/sketchbook.py
def get_sketchbook_files_info(\n sketches: List[str], sketchbook_folder: str = \"./\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Gets information about all sketch files in the sketchbook folder.\n\n Args:\n sketches (List[str]): List of sketch file names.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[Dict[str, Any]]: List of sketch information dictionaries.\n \"\"\"\n sketch_infos = []\n for sketch in sketches:\n sketch_info = get_sketch_info(sketch, sketchbook_folder)\n sketch_infos.append(sketch_info)\n return sketch_infos\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.import_sketch","title":"import_sketch(module_name, file_path)
","text":"Imports a sketch from a given file.
Parameters:
Name Type Description Defaultmodule_name
str
Name of the module.
requiredfile_path
str
Path to the file containing the module.
requiredReturns:
Name Type DescriptionAny
Any
Imported module.
Source code insrc/tolvera/sketchbook.py
def import_sketch(module_name: str, file_path: str) -> Any:\n \"\"\"\n Imports a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n\n Returns:\n Any: Imported module.\n \"\"\"\n if not os.path.exists(file_path):\n print(f\"File does not exist: {file_path}\")\n return None\n\n if not module_name:\n print(\"Module name is empty or invalid.\")\n return None\n try:\n print(f\"Importing {module_name} from {file_path}...\")\n spec = importlib.util.spec_from_file_location(module_name, file_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n except Exception as e:\n error_type = type(e).__name__\n print(f\"Error importing {module_name} ({error_type}): {str(e)}\")\n return None\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.list_sketches","title":"list_sketches(sketchbook_folder='./', sort='name', direction='ascending')
","text":"Lists all sketches in the given sketchbook folder.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Source code in src/tolvera/sketchbook.py
def list_sketches(\n sketchbook_folder: str = \"./\", sort: str = \"name\", direction: str = \"ascending\"\n) -> None:\n \"\"\"\n Lists all sketches in the given sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n sketch_files_list = get_sketchbook_files(sketchbook_folder)\n sketches = get_sketchbook_files_info(sketch_files_list, sketchbook_folder)\n sorted_sketches = sort_sketch_files(sketches, sort, direction)\n pretty_print_sketchbook(sorted_sketches, sketchbook_folder)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.main","title":"main(*args, **kwargs)
","text":"Main function for running the sketchbook from the command line.
Source code insrc/tolvera/sketchbook.py
def main(*args, **kwargs):\n \"\"\"\n Main function for running the sketchbook from the command line.\n \"\"\"\n if \"sketchbook\" in kwargs:\n sketchbook = kwargs[\"sketchbook\"]\n else:\n sketchbook = \"./\"\n if \"sketch\" in kwargs:\n sketch = kwargs[\"sketch\"]\n if isinstance(sketch, str):\n run_sketch_by_name(sketch, sketchbook, *args, **kwargs)\n elif isinstance(sketch, int):\n print(f\"Running sketch by index: {sketch}\")\n run_sketch_by_index(sketch, sketchbook, *args, **kwargs)\n elif \"sketches\" in kwargs:\n sort = kwargs[\"sort\"] if \"sort\" in kwargs else \"name\"\n direction = kwargs[\"direction\"] if \"direction\" in kwargs else \"ascending\"\n list_sketches(sketchbook, sort, direction)\n exit()\n elif \"random\" in kwargs:\n run_random_sketch(sketchbook)\n else:\n list_sketches(sketchbook)\n exit()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.pretty_print_sketchbook","title":"pretty_print_sketchbook(sketches, sketchbook_folder='./')
","text":"Pretty prints the sketchbook information.
Parameters:
Name Type Description Defaultsketches
List[Dict[str, Any]]
List of sketch information dictionaries.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def pretty_print_sketchbook(\n sketches: List[Dict[str, Any]], sketchbook_folder: str = \"./\"\n) -> None:\n \"\"\"\n Pretty prints the sketchbook information.\n\n Args:\n sketches (List[Dict[str, Any]]): List of sketch information dictionaries.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n print(f\"\\nSketchbook '{sketchbook_folder}':\\n\")\n print(\n f\" {'Index':<5} {'Sketch':<25} {'Size':<10} {'Modified':<20} {'Created':<20}\"\n )\n print(f\" {'-'*5:<5} {'-'*25:<25} {'-'*10:<10} {'-'*20:<20} {'-'*20:<20}\")\n for i, sketch in enumerate(sketches):\n print(\n f\" {i:<5} {sketch['name']:<25} {sketch['size']:<10} {sketch['modified']:<20} {sketch['created']:<20}\"\n )\n print()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_random_sketch","title":"run_random_sketch(sketchbook='./')
","text":"Runs a random sketch from the sketchbook.
Parameters:
Name Type Description Defaultsketchbook
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_random_sketch(sketchbook=\"./\"):\n \"\"\"\n Runs a random sketch from the sketchbook.\n\n Args:\n sketchbook (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook)\n files = get_sketchbook_files(sketchbook)\n sketch_file = files[random.randint(0, len(files) - 1)]\n run_sketch_by_name(sketch_file, sketchbook)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_index","title":"run_sketch_by_index(index, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its index in the sketchbook.
Parameters:
Name Type Description Defaultindex
int
Index of the sketch to run.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_index(\n index: int, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its index in the sketchbook.\n\n Args:\n index (int): Index of the sketch to run.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n files = get_sketchbook_files(sketchbook_folder)\n for file in files:\n if file.endswith(\".py\"):\n module_name = os.path.splitext(file)[0]\n file_path = os.path.join(sketchbook_folder, file)\n if str(index) == module_name:\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_name","title":"run_sketch_by_name(sketch_file, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its file name.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_name(\n sketch_file: str, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its file name.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_function_from_module","title":"run_sketch_function_from_module(module, function_name, file_path, *args, **kwargs)
","text":"Runs a specific function from a given module.
Parameters:
Name Type Description Defaultmodule
Any
The imported module.
requiredfunction_name
str
Name of the function to run.
requiredfile_path
str
Path to the file containing the module.
required Source code insrc/tolvera/sketchbook.py
def run_sketch_function_from_module(\n module: Any, function_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a specific function from a given module.\n\n Args:\n module (Any): The imported module.\n function_name (str): Name of the function to run.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n if hasattr(module, function_name) and callable(getattr(module, function_name)):\n print(f\"Running {function_name} from {file_path}...\")\n getattr(module, function_name)(*args, **kwargs)\n else:\n print(f\"{module} does not have a '{function_name}' function.\")\n except Exception as e:\n print(f\"Error running {function_name} from {file_path}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.sort_sketch_files","title":"sort_sketch_files(sketch_files, sort='name', direction='ascending')
","text":"Sorts sketch files by name, size, modified or created.
Parameters:
Name Type Description Defaultsort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
sketch_files
List[Dict[str, Any]]
List of sketch information dictionaries.
requireddirection
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Returns:
Type DescriptionList[Dict[str, Any]]
List[Dict[str, Any]]: List of sorted sketch information dictionaries.
Source code insrc/tolvera/sketchbook.py
def sort_sketch_files(\n sketch_files: List[Dict[str, Any]], sort: str = \"name\", direction: str = \"ascending\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Sorts sketch files by name, size, modified or created.\n\n Args:\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n sketch_files (List[Dict[str, Any]]): List of sketch information dictionaries.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n\n Returns:\n List[Dict[str, Any]]: List of sorted sketch information dictionaries.\n \"\"\"\n reverse = True if direction == \"descending\" else False\n if sort == \"name\":\n return sorted(sketch_files, key=lambda k: k[\"name\"], reverse=reverse)\n elif sort == \"size\":\n return sorted(sketch_files, key=lambda k: k[\"size\"], reverse=reverse)\n elif sort == \"modified\":\n return sorted(sketch_files, key=lambda k: k[\"modified\"], reverse=reverse)\n elif sort == \"created\":\n return sorted(sketch_files, key=lambda k: k[\"created\"], reverse=reverse)\n else:\n return sketch_files\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.try_import_and_run_sketch","title":"try_import_and_run_sketch(module_name, file_path, *args, **kwargs)
","text":"Tries to import and run a sketch from a given file.
Parameters:
Name Type Description Defaultmodule_name
str
Name of the module.
requiredfile_path
str
Path to the file containing the module.
required Source code insrc/tolvera/sketchbook.py
def try_import_and_run_sketch(\n module_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Tries to import and run a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n module = import_sketch(module_name, file_path)\n run_sketch_function_from_module(module, \"sketch\", file_path, *args, **kwargs)\n except Exception as e:\n print(f\"Error running {module_name}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketch_file","title":"validate_sketch_file(sketch_file, sketchbook_folder='./')
","text":"Validates if the given sketch file exists in the sketchbook folder.
Parameters:
Name Type Description Defaultsketch_file
str
Name of the sketch file.
requiredsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type DescriptionSystemExit
If the sketch file does not exist.
Source code insrc/tolvera/sketchbook.py
def validate_sketch_file(sketch_file: str, sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketch file exists in the sketchbook folder.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketch file does not exist.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n if not os.path.isfile(file_path):\n print(f\"Sketch file '{file_path}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketchbook_path","title":"validate_sketchbook_path(sketchbook_folder='./')
","text":"Validates if the given sketchbook folder exists.
Parameters:
Name Type Description Defaultsketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type DescriptionSystemExit
If the sketchbook folder does not exist.
Source code insrc/tolvera/sketchbook.py
def validate_sketchbook_path(sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketchbook folder exists.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketchbook folder does not exist.\n \"\"\"\n if not os.path.isdir(sketchbook_folder):\n print(f\"Sketchbook folder '{sketchbook_folder}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/species/","title":"Species","text":"Species class.
"},{"location":"reference/tolvera/species/#tolvera.species.Species","title":"Species
","text":"Species in T\u00f6lvera.
Species are implemented as a State with attributes for size
, speed
, mass
and colour
(rgba), and with a length determined by the number of species in the T\u00f6lvera instance (tv.sn
). The attributes are normalised and scaled by species the species_consts
attribute. They are initialised with random values.
Rather than accessing this class directly, access is typically via the State attributes via the T\u00f6lvera instance, via e.g. tv.s.species.field[i].size
.
src/tolvera/species.py
class Species:\n \"\"\"Species in T\u00f6lvera.\n\n Species are implemented as a State with attributes for `size`, `speed`, `mass` and\n `colour` (rgba), and with a length determined by the number of species in the\n T\u00f6lvera instance (`tv.sn`). The attributes are normalised and scaled by species the \n `species_consts` attribute. They are initialised with random values.\n\n Rather than accessing this class directly, access is typically via the State\n attributes via the T\u00f6lvera instance, via e.g. `tv.s.species.field[i].size`.\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n\n def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Species
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance.
required**kwargs
Keyword arguments.
{}
Source code in src/tolvera/species.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.randomise","title":"randomise()
","text":"Randomise species.
Source code insrc/tolvera/species.py
def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/state/","title":"State","text":"State and StateDict classes for T\u00f6lvera.
Every T\u00f6lvera instance has a StateDict, which is a dictionary of State instances. The StateDict is accessible via the 's' attribute of a T\u00f6lvera instance, and can be used to create and access states.
Each State instance has a Taichi struct field and a corresponding NpNdarrayDict, which handles OSC accessors and endpoints.
"},{"location":"reference/tolvera/state/#tolvera.state.State","title":"State
","text":"State class for T\u00f6lvera.
This class takes a name, dictionary of state attributes, and a shape, and creates a Taichi struct field and a corresponding dictionary of NumPy arrays (NpNdarrayDict) for a state.
The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict can be used in Python scope, and the two are kept in sync by the from_nddict() and to_nddict() methods.
The State class also handles OSC accessors for the state, which use the NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required to initialise a State instance.
State attributes are defined as a dictionary of attribute names and tuples of (Taichi type, min value, max value). The domain of the attribute is used when randomising the data in the state, and by OSCMap endpoints and client patches.
The state is n-dimensional based on the shape argument, and the NpNdarrayDict provides methods for accessing the data in the state in n-dimensional slices.
Exampletv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n}\n
Source code in src/tolvera/state.py
@ti.data_oriented\nclass State:\n \"\"\"State class for T\u00f6lvera.\n\n This class takes a name, dictionary of state attributes, and a shape, and\n creates a Taichi struct field and a corresponding dictionary of NumPy arrays \n (NpNdarrayDict) for a state.\n\n The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict\n can be used in Python scope, and the two are kept in sync by the from_nddict()\n and to_nddict() methods.\n\n The State class also handles OSC accessors for the state, which use the\n NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required\n to initialise a State instance.\n\n State attributes are defined as a dictionary of attribute names and tuples of\n (Taichi type, min value, max value). The domain of the attribute is used when\n randomising the data in the state, and by OSCMap endpoints and client patches.\n\n The state is n-dimensional based on the shape argument, and the NpNdarrayDict\n provides methods for accessing the data in the state in n-dimensional slices.\n\n Example:\n ```py\n tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n ```\n \"\"\"\n def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n\n def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n\n def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n ):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n\n def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n\n def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n\n def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n # if self.osc_get: self.add_osc_getters()\n # if self.osc_stream: self.add_osc_streams()\n\n def add_osc_setters(self):\n name = self.setter_name\n self.osc.map.receive_args_inline(name + \"_randomise\", self.randomise)\n\n def add_osc_getters(self):\n name = self.getter_name\n for k, v in self.dict.items():\n ranges = (int(v[0]), int(v[0]), int(v[1]))\n kwargs = {\"i\": ranges, \"j\": ranges, \"attr\": (k, k, k)}\n self.osc.map.receive_args_inline(f\"{name}\", self.osc_getter, **kwargs)\n\n # def osc_getter(self, i: int, j: int, attribute: str):\n # ret = self.get((i, j), attribute)\n # if ret is not None:\n # route = self.osc.map.pascal_to_path(self.getter_name) # +'/'+attribute\n # self.osc.host.return_to_sender_by_name(\n # (route, attribute, ret), self.osc.client_name\n # )\n # return ret\n\n # def add_osc_streams(self):\n # # add send in broadcast mode\n # raise NotImplementedError(\"add_osc_streams not implemented\")\n\n def serialize(self) -> str:\n return ti_serialize(self.field)\n\n def deserialize(self, json_str: str):\n ti_deserialize(self.field, json_str)\n\n def save(self, path: str):\n # TODO: path validation, save to path, etc.\n json_str = self.serialize()\n raise NotImplementedError(\"save not implemented\")\n\n def load(self, path: str):\n # TODO: path validation, file ext., etc.\n # TODO: data validation (pydantic?)\n json_str = jsons.load(path)\n self.deserialize(json_str)\n raise NotImplementedError(\"load not implemented\")\n\n def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n\n def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n\n def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n\n \"\"\"\n npndarray_dict wrappers\n \"\"\"\n\n def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n\n def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n\n def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n\n def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n\n def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n\n def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n\n def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n\n def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n\n def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n\n \"\"\"\n misc\n \"\"\"\n\n def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n\n @ti.func\n def __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__call__","title":"__call__(*args, **kwds)
","text":"Return the Taichi field.
Source code insrc/tolvera/state.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__getitem__","title":"__getitem__(index)
","text":"Return the Taichi field attribute.
Parameters:
Name Type Description Defaultindex
i32
Attribute index.
required Source code insrc/tolvera/state.py
@ti.func\ndef __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__init__","title":"__init__(tolvera, name, state, shape=None, osc=None, randomise=True, methods=None)
","text":"Initialise a state for T\u00f6lvera.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance to which this state belongs.
requiredname
str
Name of this state.
requiredstate
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state. Defaults to 1.
None
methods
dict[str, Any]
Flag for OSC via iipyper. Defaults to False.
None
Source code in src/tolvera/state.py
def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_from_vec","title":"attr_from_vec(attr, vec)
","text":"Wrapper for NpNdarrayDict.attr_from_vec().
Source code insrc/tolvera/state.py
def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_size","title":"attr_size(attr)
","text":"Return the size of the attribute.
Source code insrc/tolvera/state.py
def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_from_vec","title":"attr_slice_from_vec(attr, slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.attr_slice_from_vec().
Source code insrc/tolvera/state.py
def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_to_vec","title":"attr_slice_to_vec(attr, slice_args)
","text":"Wrapper for NpNdarrayDict.attr_slice_to_vec().
Source code insrc/tolvera/state.py
def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_to_vec","title":"attr_to_vec(attr)
","text":"Wrapper for NpNdarrayDict.attr_to_vec().
Source code insrc/tolvera/state.py
def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_npndarray_dict","title":"create_npndarray_dict()
","text":"Create a NpNdarrayDict for this state.
Raises:
Type DescriptionNotImplementedError
If no Numpy type is found for a Taichi type.
Source code insrc/tolvera/state.py
def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_struct_field","title":"create_struct_field(dict, shape, methods=None)
","text":"Create a Taichi struct field for this state.
Parameters:
Name Type Description Defaultdict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state.
requiredmethods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.fill","title":"fill(value)
","text":"Fill the Taichi field with a value.
Source code insrc/tolvera/state.py
def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_nddict","title":"from_nddict()
","text":"Copy data from NpNdarrayDict to Taichi field.
Raises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_vec","title":"from_vec(vec)
","text":"Wrapper for NpNdarrayDict.from_vec().
Source code insrc/tolvera/state.py
def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.randomise","title":"randomise()
","text":"Randomise the data in this state.
Source code insrc/tolvera/state.py
def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.set_from_nddict","title":"set_from_nddict(data)
","text":"Copy data from NumPy array dict to Taichi field.
Parameters:
Name Type Description Defaultdata
dict
NumPy array dict to copy.
requiredRaises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_data","title":"setup_data(dict, shape, randomise=True, methods=None)
","text":"Setup data structures and data for this state.
Parameters:
Name Type Description Defaultdict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
requiredshape
int | tuple[int]
Shape of the state.
requiredrandomise
bool
Flag to randomise the data on creation. Defaults to True.
True
methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_osc","title":"setup_osc(osc=None)
","text":"Setup OSC for this state.
Parameters:
Name Type Description Defaultosc
tuple | str
(\"get\", \"set\", \"stream\"). Defaults to None.
None
Source code in src/tolvera/state.py
def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_from_vec","title":"slice_from_vec(slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.slice_from_vec().
Source code insrc/tolvera/state.py
def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_to_vec","title":"slice_to_vec(slice_args)
","text":"Wrapper for NpNdarrayDict.slice_to_vec().
Source code insrc/tolvera/state.py
def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_nddict","title":"to_nddict()
","text":"Copy data from Taichi field to NpNdarrayDict.
Raises:
Type DescriptionException
If data cannot be copied.
Source code insrc/tolvera/state.py
def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_vec","title":"to_vec()
","text":"Wrapper for NpNdarrayDict.to_vec().
Source code insrc/tolvera/state.py
def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict","title":"StateDict
","text":" Bases: dotdict
StateDict class for T\u00f6lvera.
This class is a dictionary of State instances, and is accessible via the 's' attribute of a T\u00f6lvera instance.
States can be created by assigning a dictionary or a tuple to a StateDict key. and can be used in Taichi scope and Python scope respectively.
Exampletv = Tolvera(**kwargs)
tv.s.mystate = { \"state\": { \"id\": (ti.i32, 0, tv.pn - 1), \"pos\": (ti.math.vec2, -1.0, 1.0), \"vel\": (ti.math.vec2, -1.0, 1.0), }, \"shape\": (tv.pn, 1), \"osc\": \"get\", \"randomise\": True }
tv.s.mystate.field.pos[0] = 0.5
Source code insrc/tolvera/state.py
class StateDict(dotdict):\n \"\"\"StateDict class for T\u00f6lvera.\n\n This class is a dictionary of State instances, and is accessible via the 's'\n attribute of a T\u00f6lvera instance.\n\n States can be created by assigning a dictionary or a tuple to a StateDict key.\n and can be used in Taichi scope and Python scope respectively.\n\n Example:\n tv = Tolvera(**kwargs)\n\n tv.s.mystate = {\n \"state\": {\n \"id\": (ti.i32, 0, tv.pn - 1),\n \"pos\": (ti.math.vec2, -1.0, 1.0),\n \"vel\": (ti.math.vec2, -1.0, 1.0),\n }, \n \"shape\": (tv.pn, 1), \n \"osc\": \"get\", \n \"randomise\": True\n }\n\n tv.s.mystate.field.pos[0] = 0.5\n \"\"\"\n def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n\n def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n\n def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n\n def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n\n def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__init__","title":"__init__(tolvera)
","text":"Initialise a StateDict for T\u00f6lvera.
Parameters:
Name Type Description Defaulttolvera
Tolvera
Tolvera instance to which this StateDict belongs.
required Source code insrc/tolvera/state.py
def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default__name
str
Name of the state.
required__value
Any
State attributes.
required Source code insrc/tolvera/state.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.add","title":"add(name, kwargs)
","text":"Add a state to the StateDict.
Parameters:
Name Type Description Defaultname
str
Name of the state.
requiredkwargs
Any
State attributes.
requiredRaises:
Type DescriptionTypeError
If kwargs is not a dict or tuple.
Source code insrc/tolvera/state.py
def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.from_vec","title":"from_vec(states, vector)
","text":"Copy data from a vector to states in the StateDict.
Parameters:
Name Type Description Defaultstates
list[str]
List of state names.
requiredvector
list[float]
Vector of data to copy.
requiredRaises:
Type DescriptionException
If the vector is not the correct size.
Source code insrc/tolvera/state.py
def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.get_size","title":"get_size(states)
","text":"Return the size of the states in the StateDict.
Parameters:
Name Type Description Defaultstates
str | list[str]
State name or list of state names.
requiredReturns:
Name Type Descriptionint
int
Size of the states.
Source code insrc/tolvera/state.py
def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.set","title":"set(name, kwargs)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Defaultname
str
Name of the state.
requiredkwargs
Any
State attributes.
requiredRaises:
Type DescriptionValueError
If the state is already in the StateDict.
Exception
If the state cannot be added.
Source code insrc/tolvera/state.py
def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n
"},{"location":"reference/tolvera/taichi_/","title":"Taichi","text":"Taichi class for initialising Taichi and UI.
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi","title":"Taichi
","text":"Taichi class for initialising Taichi and UI.
This class provides a show method for showing the Taichi canvas. It is used by the TolveraContext class to display a window.
Source code insrc/tolvera/taichi_.py
class Taichi:\n \"\"\"Taichi class for initialising Taichi and UI.\n\n This class provides a show method for showing the Taichi canvas.\n It is used by the TolveraContext class to display a window.\"\"\"\n def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n\n def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n\n def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n\n def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__call__","title":"__call__(*args, **kwds)
","text":"Call Taichi window show.
Source code insrc/tolvera/taichi_.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__init__","title":"__init__(context, **kwargs)
","text":"Initialise Taichi
Parameters:
Name Type Description Defaultcontext
TolveraContext
global TolveraContext instance.
required**kwargs
Keyword arguments: gpu (str): GPU architecture to run on. Defaults to \"vulkan\". cpu (bool): Run on CPU. Defaults to False. fps (int): FPS limit. Defaults to 120. seed (int): Random seed. Defaults to time.time(). headless (bool): Run headless. Defaults to False. name (str): Window name. Defaults to \"T\u00f6lvera\".
{}
Source code in src/tolvera/taichi_.py
def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ti","title":"init_ti()
","text":"Initialise Taichi backend on selected architecture.
Source code insrc/tolvera/taichi_.py
def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ui","title":"init_ui()
","text":"Initialise Taichi UI window and canvas.
Source code insrc/tolvera/taichi_.py
def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.show","title":"show(px)
","text":"Show Taichi canvas and show window.
Source code insrc/tolvera/taichi_.py
def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n
"},{"location":"reference/tolvera/tolvera_/","title":"Tolvera","text":"Example This example demonstrates the basic usage of T\u00f6lvera. It will display a window with a black background.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Example Here's an annotated version of the above example:
# First, we import Tolvera and run() from tolvera.\nfrom tolvera import Tolvera, run\n\n# Then, we define a main function which takes in keyword arguments \n# (kwargs) from the command line.\ndef main(**kwargs):\n # Inside the main function, we initialise a Tolvera instance \n # with the given keyword arguments.\n tv = Tolvera(**kwargs)\n\n # We use the render() decorator to render the pixels.\n # This function can be named anything. \n # It will run in a loop until the user exits the program.\n @tv.render\n def _():\n # render() must return Pixels. Often, these pixels will be \n # the pixels of the Tolvera instance, accessed with tv.px.\n return tv.px\n\n# Finally, we call run() with the main function as the argument.\nif __name__ == '__main__':\n run(main)\n
When Tolvera is run, messages will be printed to the console. These messages inform the user of the status of Tolvera, during initialisation, setup, and running.
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera","title":"Tolvera
","text":"Tolvera main class.
Attributes:
Name Type Description`name`
str
Name of T\u00f6lvera instance.
`ctx`
TolveraContext
Shared TolveraContext.
`speed`
float
Global timebase speed.
`pn`
int
Number of particles.
`sn`
int
Number of species.
`p_per_s`
int
Number of particles per species.
`substep`
int
Number of substeps per frame.
`iml`
int
Dict of IML instances via anguilla.
`cv`
int
computer vision integration via OpenCV.
`osc`
int
OSC via iipyper.
`ti`
int
Taichi (graphics backend).
Source code insrc/tolvera/tolvera_.py
class Tolvera:\n \"\"\"Tolvera main class.\n\n Attributes:\n `name` (str): Name of T\u00f6lvera instance. \n `ctx` (TolveraContext): Shared TolveraContext.\n `speed` (float): Global timebase speed.\n `pn` (int): Number of particles.\n `sn` (int): Number of species.\n `p_per_s` (int): Number of particles per species.\n `substep` (int): Number of substeps per frame.\n `iml`: Dict of IML instances via anguilla.\n `cv`: computer vision integration via OpenCV.\n `osc`: OSC via iipyper.\n `ti`: Taichi (graphics backend).\n \"\"\"\n\n def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n\n def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n\n def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n self.pose = context.pose\n self.face = context.face\n self.face_mesh = context.face_mesh\n\n def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n if self.hands:\n self.hands.px = self.px\n if self.pose:\n self.pose.px = self.px\n if self.face:\n self.face.px = self.px\n if self.face_mesh:\n self.face_mesh.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n\n def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n\n def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n\n def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n\n def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.__init__","title":"__init__(**kwargs)
","text":"Initialise and setup T\u00f6lvera with given keyword arguments.
Parameters:
Name Type Description Defaultname
str
Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".
requiredctx
TolveraContext
TolveraContext to share. Defaults to None.
required Source code insrc/tolvera/tolvera_.py
def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.add_to_osc_map","title":"add_to_osc_map()
","text":"Add top-level T\u00f6lvera functions to OSCMap.
Source code insrc/tolvera/tolvera_.py
def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.init_context","title":"init_context(**kwargs)
","text":"Initiliase T\u00f6lveraContext with given keyword arguments.
Parameters:
Name Type Description Default**kwargs
Keyword arguments for T\u00f6lveraContext.
{}
Source code in src/tolvera/tolvera_.py
def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.randomise","title":"randomise()
","text":"Randomise particles, species, and Vera.
Source code insrc/tolvera/tolvera_.py
def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.reset","title":"reset(**kwargs)
","text":"Reset T\u00f6lvera with given keyword arguments. This will call setup() with given keyword arguments, but not init().
Parameters:
Name Type Description Default**kwargs
Keyword arguments for reset.
{}
Source code in src/tolvera/tolvera_.py
def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.setup","title":"setup(**kwargs)
","text":"Setup T\u00f6lvera with given keyword arguments. This can be called multiple throughout the lifetime of a T\u00f6lvera instance.
Parameters:
Name Type Description Default**kwargs
Keyword arguments for setup. speed (float): Global timebase speed. Defaults to 1. particles (int): Number of particles. Defaults to 1024. species (int): Number of species. Defaults to 4. substep (int): Number of substeps per frame. Defaults to 1.
{}
Source code in src/tolvera/tolvera_.py
def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n if self.hands:\n self.hands.px = self.px\n if self.pose:\n self.pose.px = self.px\n if self.face:\n self.face.px = self.px\n if self.face_mesh:\n self.face_mesh.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.share_context","title":"share_context(context)
","text":"Share T\u00f6lveraContext with another T\u00f6lvera instance.
Parameters:
Name Type Description Defaultcontext
T\u00f6lveraContext to share.
required Source code insrc/tolvera/tolvera_.py
def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n self.pose = context.pose\n self.face = context.face\n self.face_mesh = context.face_mesh\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.speed","title":"speed(speed=None)
","text":"Set or get global timebase speed.
Source code insrc/tolvera/tolvera_.py
def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n
"},{"location":"reference/tolvera/utils/","title":"Utils","text":"Utility functions for Tolvera.
"},{"location":"reference/tolvera/utils/#tolvera.utils.CONSTS","title":"CONSTS
","text":"Dict of CONSTS that can be used in Taichi scope
Source code insrc/tolvera/utils.py
class CONSTS:\n \"\"\"\n Dict of CONSTS that can be used in Taichi scope\n \"\"\"\n\n def __init__(self, dict: dict[str, (DataType, Any)]):\n self.struct = ti.types.struct(**{k: v[0] for k, v in dict.items()})\n self.consts = self.struct(**{k: v[1] for k, v in dict.items()})\n\n def __getattr__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n\n def __getitem__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.dotdict","title":"dotdict
","text":" Bases: dict
dot.notation access to dictionary attributes
Source code insrc/tolvera/utils.py
class dotdict(dict):\n \"\"\"dot.notation access to dictionary attributes\"\"\"\n __getattr__ = dict.get\n __setattr__ = dict.__setitem__\n __delattr__ = dict.__delitem__\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_and_validate_slice","title":"create_and_validate_slice(arg, target_array)
","text":"Creates and validates a slice object based on the target array.
Source code insrc/tolvera/utils.py
def create_and_validate_slice(\n arg: Union[int, tuple[int, ...], slice], target_array: np.ndarray\n) -> slice:\n \"\"\"\n Creates and validates a slice object based on the target array.\n \"\"\"\n try:\n slice_obj = create_safe_slice(arg)\n if not validate_slice(slice_obj, target_array):\n raise ValueError(f\"Invalid slice: {slice_obj}\")\n return slice_obj\n except Exception as e:\n raise type(e)(f\"Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_ndslices","title":"create_ndslices(dims)
","text":"Create a multi-dimensional slice from a list of tuples.
Parameters:
Name Type Description Defaultdims
list[tuple]
A list of tuples containing the slice parameters for each dimension.
requiredReturns:
Type Descriptions_
np.s_: A multi-dimensional slice object.
Source code insrc/tolvera/utils.py
def create_ndslices(dims: list[tuple]) -> np.s_:\n \"\"\"\n Create a multi-dimensional slice from a list of tuples.\n\n Args:\n dims (list[tuple]): A list of tuples containing the slice parameters for each dimension.\n\n Returns:\n np.s_: A multi-dimensional slice object.\n \"\"\"\n return np.s_[tuple(slice(*dim) if isinstance(dim, tuple) else dim for dim in dims)]\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_safe_slice","title":"create_safe_slice(arg)
","text":"Creates a slice object based on the input argument.
Parameters:
Name Type Description Defaultarg
(int, tuple, slice)
The argument for creating the slice. It can be an integer, a tuple with slice parameters, or a slice object itself.
requiredReturns:
Name Type Descriptionslice
slice
A slice object created based on the provided argument.
Source code insrc/tolvera/utils.py
def create_safe_slice(arg: Union[int, tuple[int, ...], slice]) -> slice:\n \"\"\"\n Creates a slice object based on the input argument.\n\n Args:\n arg (int, tuple, slice): The argument for creating the slice. It can be an integer,\n a tuple with slice parameters, or a slice object itself.\n\n Returns:\n slice: A slice object created based on the provided argument.\n \"\"\"\n try:\n if isinstance(arg, slice):\n return arg\n elif isinstance(arg, tuple):\n return slice(*arg)\n elif isinstance(arg, int):\n return slice(arg, arg + 1)\n else:\n raise TypeError(f\"Invalid slice type: {type(arg)} {arg}\")\n except Exception as e:\n raise type(e)(f\"[create_safe_slice] Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.flatten","title":"flatten(lst)
","text":"Flatten a nested list or return a non-nested list as is.
Source code insrc/tolvera/utils.py
def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n return [item for sublist in lst for item in sublist]\n return lst\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.generic_slice","title":"generic_slice(array, slice_params)
","text":"Slices a NumPy array based on a tuple of slice parameters for each dimension.
Parameters:
Name Type Description Defaultarray
ndarray
The array to be sliced.
requiredslice_params
tuple
A tuple where each item is either an integer, a tuple with slice parameters, or a slice object.
requiredReturns:
Name Type Descriptionndarray
ndarray
The sliced array.
Source code insrc/tolvera/utils.py
def generic_slice(\n array: np.ndarray,\n slice_params: Union[\n tuple[Union[int, tuple[int, ...], slice], ...],\n Union[int, tuple[int, ...], slice],\n ],\n) -> np.ndarray:\n \"\"\"\n Slices a NumPy array based on a tuple of slice parameters for each dimension.\n\n Args:\n array (np.ndarray): The array to be sliced.\n slice_params (tuple): A tuple where each item is either an integer, a tuple with\n slice parameters, or a slice object.\n\n Returns:\n ndarray: The sliced array.\n \"\"\"\n if not isinstance(slice_params, tuple):\n slice_params = (slice_params,)\n slices = tuple(create_safe_slice(param) for param in slice_params)\n return array.__getitem__(slices)\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.time_function","title":"time_function(func, *args, **kwargs)
","text":"Time how long it takes to run a function and print the result
Source code insrc/tolvera/utils.py
def time_function(func, *args, **kwargs):\n \"\"\"Time how long it takes to run a function and print the result\"\"\"\n start = time.time()\n ret = func(*args, **kwargs)\n end = time.time()\n print(f\"[Tolvera.utils] {func.__name__}() ran in {end-start:.4f}s\")\n if ret is not None:\n return (ret, end - start)\n return end - start\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_json_path","title":"validate_json_path(path)
","text":"Validate a JSON file path. It uses validate_path for initial validation.
Parameters:
Name Type Description Defaultpath
str
The JSON file path to be validated.
requiredReturns:
Name Type Descriptionbool
bool
True if the path is a valid JSON file path, raises an exception otherwise.
Raises:
Type DescriptionValueError
If the path does not end with '.json'.
Source code insrc/tolvera/utils.py
def validate_json_path(path: str) -> bool:\n \"\"\"\n Validate a JSON file path. It uses validate_path for initial validation.\n\n Args:\n path (str): The JSON file path to be validated.\n\n Returns:\n bool: True if the path is a valid JSON file path, raises an exception otherwise.\n\n Raises:\n ValueError: If the path does not end with '.json'.\n \"\"\"\n # Using validate_path for basic path validation\n validate_path(path)\n\n if not path.endswith(\".json\"):\n raise ValueError(\"Path should end with '.json'\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_path","title":"validate_path(path)
","text":"Validate a path using os.path and pathlib.
Parameters:
Name Type Description Defaultpath
str
The path to be validated.
requiredReturns:
Name Type Descriptionbool
bool
True if the path is valid, raises an exception otherwise.
Raises:
Type DescriptionTypeError
If the input is not a string.
FileNotFoundError
If the path does not exist.
PermissionError
If the path is not accessible.
Source code insrc/tolvera/utils.py
def validate_path(path: str) -> bool:\n \"\"\"\n Validate a path using os.path and pathlib.\n\n Args:\n path (str): The path to be validated.\n\n Returns:\n bool: True if the path is valid, raises an exception otherwise.\n\n Raises:\n TypeError: If the input is not a string.\n FileNotFoundError: If the path does not exist.\n PermissionError: If the path is not accessible.\n \"\"\"\n if not isinstance(path, str):\n raise TypeError(f\"Expected a string for path, but received {type(path)}\")\n\n path_obj = Path(path)\n if not path_obj.is_file():\n raise FileNotFoundError(f\"The path {path} does not exist or is not a file\")\n\n if not os.access(path, os.R_OK):\n raise PermissionError(f\"The path {path} is not accessible\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_slice","title":"validate_slice(slice_obj, target_array)
","text":"Validates if the given slice object is applicable to the target ndarray.
Parameters:
Name Type Description Defaultslice_obj
tuple[slice]
A tuple containing slice objects for each dimension.
requiredtarget_array
ndarray
The array to be sliced.
requiredReturns:
Name Type Descriptionbool
bool
True if the slice is valid for the given array, False otherwise.
Source code insrc/tolvera/utils.py
def validate_slice(slice_obj: tuple[slice], target_array: np.ndarray) -> bool:\n \"\"\"\n Validates if the given slice object is applicable to the target ndarray.\n\n Args:\n slice_obj (tuple[slice]): A tuple containing slice objects for each dimension.\n target_array (np.ndarray): The array to be sliced.\n\n Returns:\n bool: True if the slice is valid for the given array, False otherwise.\n \"\"\"\n if len(slice_obj) != target_array.ndim:\n return False\n\n for sl, size in zip(slice_obj, target_array.shape):\n # Check if slice start and stop are within the dimension size\n start, stop, _ = sl.indices(size)\n if not (0 <= start < size and (0 <= stop <= size or stop == -1)):\n return False\n return True\n
"},{"location":"reference/tolvera/mp/face/","title":"Face","text":""},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.FaceKeyPoint","title":"FaceKeyPoint
","text":" Bases: IntEnum
The enum type of the six face detection key points.
Source code insrc/tolvera/mp/face.py
class FaceKeyPoint(enum.IntEnum):\n \"\"\"The enum type of the six face detection key points.\"\"\"\n RIGHT_EYE = 0\n LEFT_EYE = 1\n NOSE_TIP = 2\n MOUTH_CENTER = 3\n RIGHT_EAR_TRAGION = 4\n LEFT_EAR_TRAGION = 5\n
"},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace","title":"MPFace
","text":"Source code in src/tolvera/mp/face.py
@ti.data_oriented\nclass MPFace:\n def __init__(self, context, **kwargs) -> None:\n self.ctx = context\n self.kwargs = kwargs\n self.n_points = 6\n self.max_faces = kwargs.get('max_faces', 4)\n\n self.config = {\n 'min_detection_confidence': kwargs.get('detection_con', .5),\n 'model_selection': kwargs.get('model_selection', 0),\n }\n\n \"\"\"\n TODO: add bbox as separate tv.s.faces_bbox?\n format: RELATIVE_BOUNDING_BOX\n relative_bounding_box {\n xmin: 0.482601523\n ymin: 0.402242899\n width: 0.162447035\n height: 0.2887941\n }\n \"\"\"\n\n self.faces_np = {\n 'pxnorm':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n 'px':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n }\n self.ctx.s.faces = {\n 'state': {\n 'pxnorm': (ti.math.vec2, 0.0, 1.0),\n 'px': (ti.math.vec2, 0.0, 1.0),\n # 'metres': (ti.math.vec3, 0.0, 1.0), # face_world_landmarks\n },\n 'shape': (self.max_faces, self.n_points)\n }\n\n self.mpFace = mp.solutions.face_detection\n self.face = self.mpFace.FaceDetection(**self.config)\n self.detected = ti.field(ti.i32, shape=())\n\n self.updater = Updater(self.detect, kwargs.get('face_detect_rate', 10))\n\n def detect(self, frame=None):\n if frame is None: return\n self.results = self.face.process(frame)\n if self.results.detections is None:\n self.ctx.s.faces.fill(0.)\n self.detected[None] = -1\n return\n\n if self.results.detections:\n for i, face in enumerate(self.results.detections):\n for j, lm in enumerate(face.location_data.relative_keypoints):\n pxnorm = np.array([1-lm.x, 1-lm.y])\n px = np.array([self.ctx.x*(1-lm.x), self.ctx.y*(1-lm.y)])\n self.faces_np['pxnorm'][i, j] = pxnorm\n self.faces_np['px'][i, j] = px\n self.ctx.s.faces.set_from_nddict(self.faces_np)\n\n self.detected[None] = len(self.results.detections)\n\n @ti.kernel\n def draw(self):\n if self.detected[None] > 0:\n self.draw_face_lms(5, ti.Vector([1, 1, 1, 1]))\n\n @ti.func\n def draw_face_lms(self, r, rgba):\n for i, lm in ti.ndrange(self.detected[None], self.n_conns):\n self.draw_lm(i, lm, r, rgba)\n\n @ti.func\n def draw_lm(self, face: ti.i32, lm: ti.i32, r: ti.i32, rgba: ti.math.vec4):\n px = self.ctx.s.faces[face, lm].px\n cx = ti.cast(px.x, ti.i32)\n cy = ti.cast(px.y, ti.i32)\n self.px.circle(cx, cy, r, rgba)\n\n def landmark_name_from_index(self, index):\n return FaceKeyPoint(index).name\n\n def landmark_index_from_name(self, name):\n return FaceKeyPoint[name].value\n\n @ti.kernel\n def get_landmark(self, face: ti.i32, landmark: ti.i32) -> ti.math.vec2:\n return self.ctx.s.faces[landmark].px\n\n def __call__(self, frame):\n self.updater(frame)\n
"},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace.config","title":"config = {'min_detection_confidence': kwargs.get('detection_con', 0.5), 'model_selection': kwargs.get('model_selection', 0)}
instance-attribute
","text":"add bbox as separate tv.s.faces_bbox? format: RELATIVE_BOUNDING_BOX relative_bounding_box { xmin: 0.482601523 ymin: 0.402242899 width: 0.162447035 height: 0.2887941 }
"},{"location":"reference/tolvera/mp/face_mesh/","title":"Face mesh","text":""},{"location":"reference/tolvera/mp/face_mesh_connections/","title":"Face mesh connections","text":"MediaPipe FaceMesh connections.
"},{"location":"reference/tolvera/mp/hands/","title":"Hands","text":""},{"location":"reference/tolvera/mp/hands/#tolvera.mp.hands.HandLandmark","title":"HandLandmark
","text":" Bases: IntEnum
The 21 hand landmarks.
Source code insrc/tolvera/mp/hands.py
class HandLandmark(enum.IntEnum):\n \"\"\"The 21 hand landmarks.\"\"\"\n WRIST = 0\n THUMB_CMC = 1\n THUMB_MCP = 2\n THUMB_IP = 3\n THUMB_TIP = 4\n INDEX_FINGER_MCP = 5\n INDEX_FINGER_PIP = 6\n INDEX_FINGER_DIP = 7\n INDEX_FINGER_TIP = 8\n MIDDLE_FINGER_MCP = 9\n MIDDLE_FINGER_PIP = 10\n MIDDLE_FINGER_DIP = 11\n MIDDLE_FINGER_TIP = 12\n RING_FINGER_MCP = 13\n RING_FINGER_PIP = 14\n RING_FINGER_DIP = 15\n RING_FINGER_TIP = 16\n PINKY_MCP = 17\n PINKY_PIP = 18\n PINKY_DIP = 19\n PINKY_TIP = 20\n
"},{"location":"reference/tolvera/mp/pose/","title":"Pose","text":""},{"location":"reference/tolvera/mp/pose/#tolvera.mp.pose.PoseLandmark","title":"PoseLandmark
","text":" Bases: IntEnum
The 33 pose landmarks.
Source code insrc/tolvera/mp/pose.py
class PoseLandmark(enum.IntEnum):\n \"\"\"The 33 pose landmarks.\"\"\"\n NOSE = 0\n LEFT_EYE_INNER = 1\n LEFT_EYE = 2\n LEFT_EYE_OUTER = 3\n RIGHT_EYE_INNER = 4\n RIGHT_EYE = 5\n RIGHT_EYE_OUTER = 6\n LEFT_EAR = 7\n RIGHT_EAR = 8\n MOUTH_LEFT = 9\n MOUTH_RIGHT = 10\n LEFT_SHOULDER = 11\n RIGHT_SHOULDER = 12\n LEFT_ELBOW = 13\n RIGHT_ELBOW = 14\n LEFT_WRIST = 15\n RIGHT_WRIST = 16\n LEFT_PINKY = 17\n RIGHT_PINKY = 18\n LEFT_INDEX = 19\n RIGHT_INDEX = 20\n LEFT_THUMB = 21\n RIGHT_THUMB = 22\n LEFT_HIP = 23\n RIGHT_HIP = 24\n LEFT_KNEE = 25\n RIGHT_KNEE = 26\n LEFT_ANKLE = 27\n RIGHT_ANKLE = 28\n LEFT_HEEL = 29\n RIGHT_HEEL = 30\n LEFT_FOOT_INDEX = 31\n RIGHT_FOOT_INDEX = 32\n
"},{"location":"reference/tolvera/osc/maxmsp/","title":"Maxmsp","text":""},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher","title":"MaxPatcher
","text":"TODO: copy-paste using stdout TODO: add scale objects before send and after receive TODO: add default values via loadbangs TODO: move udpsend/udpreceive to the top left TODO: dict of object ids TODO: add abstraction i/o messages e.g. param names, state save/load/dumps
Source code insrc/tolvera/osc/maxmsp.py
class MaxPatcher:\n \"\"\"\n TODO: copy-paste using stdout\n TODO: add scale objects before send and after receive\n TODO: add default values via loadbangs\n TODO: move udpsend/udpreceive to the top left\n TODO: dict of object ids\n TODO: add abstraction i/o messages e.g. param names, state save/load/dumps\n \"\"\"\n\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n v=\"8.5.4\",\n ) -> None:\n self.patch = {\n \"patcher\": {\n \"fileversion\": 1,\n \"appversion\": {\n \"major\": v[0],\n \"minor\": v[2],\n \"revision\": v[4],\n \"architecture\": \"x64\",\n \"modernui\": 1,\n },\n \"classnamespace\": \"box\",\n \"rect\": [x, y, w, h],\n \"bglocked\": 0,\n \"openinpresentation\": 0,\n \"default_fontsize\": 12.0,\n \"default_fontface\": 0,\n \"default_fontname\": \"Arial\",\n \"gridonopen\": 1,\n \"gridsize\": [15.0, 15.0],\n \"gridsnaponopen\": 1,\n \"objectsnaponopen\": 1,\n \"statusbarvisible\": 2,\n \"toolbarvisible\": 1,\n \"lefttoolbarpinned\": 0,\n \"toptoolbarpinned\": 0,\n \"righttoolbarpinned\": 0,\n \"bottomtoolbarpinned\": 0,\n \"toolbars_unpinned_last_save\": 0,\n \"tallnewobj\": 0,\n \"boxanimatetime\": 200,\n \"enablehscroll\": 1,\n \"enablevscroll\": 1,\n \"devicewidth\": 0.0,\n \"description\": \"\",\n \"digest\": \"\",\n \"tags\": \"\",\n \"style\": \"\",\n \"subpatcher_template\": \"\",\n \"assistshowspatchername\": 0,\n \"boxes\": [],\n \"lines\": [],\n \"dependency_cache\": [],\n \"autosave\": 0,\n }\n }\n self.types = {\n \"print\": \"print\",\n \"message\": \"message\",\n \"object\": \"newobj\",\n \"comment\": \"comment\",\n \"slider\": \"slider\",\n \"float\": \"flonum\",\n \"int\": \"number\",\n \"bang\": \"button\",\n }\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.init()\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 22.0 # default height (pixels)\n self.s_x, self.s_y = 30, 125 # insertion point\n self.r_x, self.r_y = 30, 575 # insertion point\n self.patcher_ids = {}\n self.patcher_ids[\"send_id\"] = self.add_osc_send(\n self.osc.host, self.osc.port, self.s_x, 30, print_label=\"sent\"\n )\n self.patcher_ids[\"receive_id\"] = self.add_osc_receive(\n self.client_port, self.s_x + 150, 30, print_label=\"received\"\n )\n self.add_comment(\"Max \u2192 Python\", self.s_x, self.s_y, 24)\n self.add_comment(\"Python \u2192 Max\", self.r_x, self.r_y, 24)\n self.s_y += 50\n self.r_y += 50\n self.save(self.filepath)\n\n def add_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id, box = self.create_box(box_type, inlets, outlets, x, y, w, h)\n return self._add_box(box)\n\n def _add_box(self, box):\n self.patch[\"patcher\"][\"boxes\"].append(box)\n return self.id_from_str(box[\"box\"][\"id\"])\n\n def create_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id = len(self.patch[\"patcher\"][\"boxes\"]) + 1\n box = {\n \"box\": {\n \"id\": \"obj-\" + str(box_id),\n \"maxclass\": self.types[box_type],\n \"numinlets\": inlets,\n \"numoutlets\": outlets,\n \"patching_rect\": [x, y, w, h],\n }\n }\n if outlets > 0:\n if outlets == 1:\n box[\"box\"][\"outlettype\"] = [\"\"]\n match box_type:\n case \"int\" | \"float\" | \"bang\":\n box[\"box\"][\"outlettype\"] = [\"\", \"bang\"]\n return box_id, box\n\n def add_object(self, text, inlets, outlets, x, y):\n box_id, box = self.create_box(\n \"object\", inlets, outlets, x, y, len(text) * self.w\n )\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_message(self, text, x, y):\n box_id, box = self.create_box(\"message\", 2, 1, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_comment(self, text, x, y, fontsize=12):\n box_id, box = self.create_box(\"comment\", 0, 0, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n box[\"box\"][\"fontsize\"] = fontsize\n self._add_box(box)\n return box_id\n\n def add_bang(self, x, y):\n box_id, box = self.create_box(\"bang\", 1, 1, x, y, 20.0)\n self._add_box(box)\n return box_id\n\n def add_slider(self, x, y, min_val, size, float=False):\n box_id, box = self.create_box(\"slider\", 1, 1, x, y, 20.0, 140.0)\n if float:\n box[\"box\"][\"floatoutput\"] = 1\n box[\"box\"][\"min\"] = min_val\n box[\"box\"][\"size\"] = size\n self._add_box(box)\n return box_id\n\n def connect(self, src, src_outlet, dst, dst_inlet):\n patchline = {\n \"patchline\": {\n \"destination\": [\"obj-\" + str(dst), dst_inlet],\n \"source\": [\"obj-\" + str(src), src_outlet],\n }\n }\n self.patch[\"patcher\"][\"lines\"].append(patchline)\n return patchline\n\n def save(self, name):\n with open(name + \".maxpat\", \"w\") as f:\n f.write(json.dumps(self.patch, indent=2))\n\n def load(self, name):\n with open(name + \".maxpat\", \"r\") as f:\n self.patch = json.loads(f.read())\n\n def get_box_by_id(self, id):\n for box in self.patch[\"patcher\"][\"boxes\"]:\n if self.id_from_str(box[\"box\"][\"id\"]) == id:\n return box\n return None\n\n def str_from_id(self, id):\n return \"obj-\" + str(id)\n\n def id_from_str(self, obj_str):\n return int(obj_str[4:])\n\n def add_osc_send(self, ip, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"r send\", 0, 1, x, y)\n box_id = self.add_object(\"udpsend \" + ip + \" \" + str(port), 1, 0, x, y + 25)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 50, y)\n self.connect(box_id_0, 0, box_id, 0)\n self.connect(box_id_0, 0, print_id, 0)\n return box_id_0\n return box_id\n\n def add_osc_receive(self, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"s receive\", 0, 1, x, y + 25)\n box_id = self.add_object(\"udpreceive \" + str(port), 1, 1, x, y)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 60, y + 25)\n self.connect(box_id, 0, print_id, 0)\n self.connect(box_id, 0, box_id_0, 0)\n return box_id_0\n return box_id\n\n def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n\n def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n\n def add_param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * 52.0)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.add_comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.add_comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]}-{p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n def add_osc_send_msg(self, x, y, path):\n msg_id = self.add_message(path, x, y + 225 + self.h)\n send_id = self.add_object(\"s send\", 1, 0, x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n def add_osc_receive_msg(self, x, y, path):\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + 225 + self.h)\n msg_id = self.add_message(path, x, y + 250 + self.h)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def add_send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, p in enumerate(f_p):\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_send_list_func(self, f):\n raise NotImplementedError(\"add_send_list_func not implemented yet\")\n\n def add_receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for p in f_p:\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[p].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_receive_list_func(self, f):\n raise NotImplementedError(\"add_receive_list_func not implemented yet\")\n\n def _msg_args(self, args):\n return \" \".join([\"$\" + str(i + 1) for i in range(len(args))])\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"i\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_receive_with_controls","title":"add_osc_receive_with_controls(x, y, path, parameters)
","text":"[comment path] [r receive] | [route /path] | | [unpack f f f ...] [print /path] | [slider] ... | [number] ... | [s arg_name] [comment path_arg_name] [comment type min-max]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_route","title":"add_osc_route(port, x, y, print=True, print_label=None)
","text":"[route path] [s name] print ? [r name]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_send_with_controls","title":"add_osc_send_with_controls(x, y, path, parameters)
","text":"[comment path] [comment args] [r path_arg_name] sliders | | [pak $1 $2 $3 ...] | [msg /path $1 $2 $3 ...] | [s send]
Source code insrc/tolvera/osc/maxmsp.py
def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_sliders","title":"add_sliders(x, y, sliders)
","text":"sliders = [ { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 }, ]
[slider] ... | [number] ...
Source code insrc/tolvera/osc/maxmsp.py
def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n
"},{"location":"reference/tolvera/osc/osc/","title":"Osc","text":""},{"location":"reference/tolvera/osc/oscmap/","title":"Oscmap","text":""},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap","title":"OSCMap
","text":"OSCMap maps OSC messages to functions It creates a Max/MSP patcher that can be used to control the OSCMap It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages
Source code insrc/tolvera/osc/oscmap.py
class OSCMap:\n \"\"\"\n OSCMap maps OSC messages to functions\n It creates a Max/MSP patcher that can be used to control the OSCMap\n It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages\n \"\"\"\n\n def __init__(\n self,\n osc: iiOSC,\n client_name=\"client\",\n patch_type=\"Max\", # | \"Pd\"\n patch_filepath=\"osc_controls\",\n create_patch=True,\n pd_net_or_udp=\"udp\",\n pd_bela=False,\n export=None, # 'JSON' | 'XML' | True\n ) -> None:\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.dict = {\"send\": {}, \"receive\": {}}\n self.create_patch = create_patch\n self.patch_filepath = patch_filepath\n self.patch_type = patch_type\n if create_patch is True:\n self.init_patcher(patch_type, patch_filepath, pd_net_or_udp, pd_bela)\n if export is not None:\n assert (\n export == \"JSON\" or export == \"XML\" or export == True\n ), \"export must be 'JSON', 'XML' or True\"\n self.export = export\n\n def init_patcher(self, patch_type, patch_filepath, pd_net_or_udp, pd_bela):\n # create self.patch_dir if it doesn't exist\n self.patch_dir = \"pd\" if patch_type == \"Pd\" else \"max\"\n if not os.path.exists(self.patch_dir):\n print(f\"Creating {self.patch_dir} directory...\")\n os.makedirs(self.patch_dir)\n self.patch_appendix = \"_local\" if self.osc.host == \"127.0.0.1\" else \"_remote\"\n self.patch_filepath = (\n self.patch_dir + \"/\" + patch_filepath + self.patch_appendix\n )\n if patch_type == \"Max\":\n self.patcher = MaxPatcher(self.osc, self.client_name, self.patch_filepath)\n elif patch_type == \"Pd\":\n if pd_bela is True:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n bela=True,\n )\n else:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n )\n else:\n assert False, \"`patch_type` must be 'Max' or 'Pd'\"\n\n def add(self, **kwargs):\n print(\n \"DeprecationError: OSCMap.add() has been split into separate functions: use `send_args`, `send_list`, `receive_args` or `receive_list` instead!\"\n )\n exit()\n\n def map_func_to_dict(self, func, kwargs):\n if \"name\" not in kwargs:\n n = func.__name__\n address = \"/\" + n.replace(\"_\", \"/\")\n else:\n if isinstance(kwargs[\"name\"], str):\n n = kwargs[\"name\"]\n address = \"/\" + kwargs[\"name\"].replace(\"_\", \"/\")\n else:\n raise TypeError(\n f\"OSC func name must be string, found {str(type(kwargs['name']))}\"\n )\n # TODO: Move this into specific send/receive functions\n params = {\n k: v\n for k, v in kwargs.items()\n if k != \"count\" and k != \"send_mode\" and k != \"length\" and k != \"name\"\n }\n # TODO: turn params into dict with type hints (see export_dict)\n hints = get_type_hints(func)\n f = {\"f\": func, \"name\": n, \"address\": address, \"params\": params, \"hints\": hints}\n return f\n\n \"\"\"\n send args\n \"\"\"\n\n def send_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_args(func, kwargs)\n return func()\n\n default_args = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_send_args(self, func, kwargs):\n self.add_send_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_args_to_patcher(func)\n\n def add_send_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n # count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"args\"\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_args_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_args_func(f)\n\n \"\"\"\n send list\n \"\"\"\n\n def send_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLFun2OSC.update\n # return func()\n\n default_arg = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"length\" and a != \"name\"\n ]\n wrapper(default_arg)\n return wrapper\n\n return decorator\n\n def add_send_list(self, func, kwargs):\n self.add_send_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_list_to_patcher(func)\n\n def add_send_list_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n # TODO: Hack for send_list_inline which doesn't have a return type hint\n if \"return\" in f[\"hints\"]:\n hint = f[\"hints\"][\"return\"]\n else:\n hint = list[float]\n assert hint == list[float], \"send_list can only send list[float], found \" + str(\n hint\n )\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_list_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_list_func(f)\n\n def send_list_inline(self, name: str, sender_func, length: int, send_mode=\"broadcast\", count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"send_mode\": send_mode, \"count\": count}}\n self.send_list(**kwargs)(sender_func)\n\n \"\"\"\n send kwargs\n \"\"\"\n\n def send_kwargs(self, **kwargs):\n raise NotImplementedError(\"send_kwargs not implemented yet\")\n\n \"\"\"\n receive args\n \"\"\"\n\n def receive_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_args(func, kwargs)\n return func(*args)\n\n default_args = [\n kwargs[a][0] for a in kwargs if a != \"count\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_receive_args(self, func, kwargs):\n f = self.add_receive_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_args_to_patcher(f)\n\n def add_receive_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n f[\"updater\"] = OSCReceiveUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"args\"\n self.dict[\"receive\"][f[\"name\"]] = f\n return f\n\n def add_receive_args_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_args_func(f)\n\n def receive_args_inline(self, name: str, receiver_func, **kwargs):\n kwargs = {**kwargs, **{\"count\": 1, \"name\": name}}\n self.receive_args(**kwargs)(receiver_func)\n\n \"\"\"\n receive list\n \"\"\"\n\n def receive_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLOSC2Vec.init\n # return func(*args)\n\n # TODO: This probably shouldn't be here...\n randomised_list = self.randomise_list(\n kwargs[\"length\"], kwargs[\"vector\"][1], kwargs[\"vector\"][2]\n )\n wrapper(randomised_list)\n return wrapper\n\n return decorator\n\n def randomise_list(self, length, min, max):\n return min + (np.random.rand(length).astype(np.float32) * (max - min))\n\n def add_receive_list(self, func, kwargs):\n f = self.add_receive_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_list_to_patcher(f)\n\n def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n\n def add_receive_list_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_list_func(f)\n\n def receive_list_inline(self, name: str, receiver_func, length: int, count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"count\": count, \"vector\": (0, 0, 1)}}\n self.receive_list(**kwargs)(receiver_func)\n\n def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n ):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n\n \"\"\"\n receive kwargs\n \"\"\"\n\n def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n\n \"\"\"\n xml / json export\n \"\"\"\n\n def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n\n def xml_add_args_params(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Params\": str(len(params)),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n for i, p in enumerate(params):\n # TODO: This should already be defined by this point\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[i].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"Param\", **kw)\n\n def xml_add_list_param(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n length = f[\"length\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Length\": str(length),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n p = list(params.keys())[0]\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[0].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"ParamList\", **kw)\n\n def export_update(self, root):\n tree = ET.ElementTree(root)\n ET.indent(tree, space=\"\\t\", level=0)\n if self.export == \"XML\":\n self.save_xml(tree, root)\n elif self.export == \"JSON\":\n self.save_json(root)\n elif self.export == True:\n self.save_xml(tree, root)\n self.save_json(root)\n\n def save_xml(self, tree, root):\n tree.write(self.patch_filepath + \".xml\")\n print(f\"Exported OSCMap to {self.patch_filepath}.xml\")\n\n def save_json(self, xml_root):\n # TODO: params should be `params: []` and not `param: {}, param: {}, ...`\n json_dict = self.xml_to_json(\n ET.tostring(xml_root, encoding=\"utf8\", method=\"xml\")\n )\n with open(self.patch_filepath + \".json\", \"w\") as f:\n f.write(json_dict)\n print(f\"Exported OSCMap to {self.patch_filepath}.json\")\n\n def etree_to_dict(self, t):\n tag = self.pascal_to_camel(t.tag)\n d = {tag: {} if t.attrib else None}\n children = list(t)\n if children:\n dd = {}\n for dc in map(self.etree_to_dict, children):\n for k, v in dc.items():\n try:\n dd[k].append(v)\n except KeyError:\n dd[k] = [v]\n d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}\n if t.attrib:\n d[tag].update((self.pascal_to_camel(k), v) for k, v in t.attrib.items())\n if t.text:\n text = t.text.strip()\n if children or t.attrib:\n if text:\n d[tag][\"#text\"] = text\n else:\n d[tag] = text\n return d\n\n def pascal_to_camel(self, s):\n return s[0].lower() + s[1:]\n\n def xml_to_json(self, xml_str):\n e = ET.ElementTree(ET.fromstring(xml_str))\n return json.dumps(self.etree_to_dict(e.getroot()), indent=4)\n\n def update(self):\n for k, v in self.dict[\"send\"].items():\n if \"updater\" in v:\n ret = v[\"updater\"]()\n # v['updater']()\n for k, v in self.dict[\"receive\"].items():\n v[\"updater\"]()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n self.update()\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.add_receive_list_to_osc_map","title":"add_receive_list_to_osc_map(func, kwargs)
","text":"TODO: Should this support list[float] only, or list[int] list[str] etc?
Source code insrc/tolvera/osc/oscmap.py
def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.export_dict","title":"export_dict()
","text":"Save the OSCMap dict as XML
Source code insrc/tolvera/osc/oscmap.py
def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_kwargs","title":"receive_kwargs(**kwargs)
","text":"Same as receive_args but with named params
Source code insrc/tolvera/osc/oscmap.py
def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_list_with_idx","title":"receive_list_with_idx(name, receiver, idx_len, vec_len, attr=None)
","text":"Create an OSC list handler that assumes that the first idx_len
values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e. /name idx0 idx1 ... idxN arg0 arg1 ... argM ... receiver((idx0 idx1 ... idxN), args) Intended as a utility function to be used by external classes where it's not possible to use a decorator like receive_list
.
src/tolvera/osc/oscmap.py
def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n
"},{"location":"reference/tolvera/osc/pd/","title":"Pd","text":""},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher","title":"PdPatcher
","text":"Source code in src/tolvera/osc/pd.py
class PdPatcher:\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n net_or_udp=\"udp\",\n bela=False,\n ) -> None:\n self.x, self.y, self.w, self.h = x, y, w, h\n self.patch_objects = [f\"#N canvas {x} {y} {w} {h} 12;\\n\"]\n self.patch_connections = []\n self.types = {\n \"object\": \"obj\",\n \"message\": \"msg\",\n \"number\": \"floatatom\",\n \"symbol\": \"symbolatom\",\n \"toggle\": \"toggle\",\n \"slider\": \"vslider\",\n \"bang\": \"bng\",\n \"comment\": \"text\",\n }\n self.patch_ids = {}\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.net_or_udp = net_or_udp\n self.bela = bela\n self.init()\n\n \"\"\"\n init\n \"\"\"\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 27.0 # default height (pixels)\n self.line = 300 # default [line] (timed ramp generator) time in milliseconds\n self.param_width = 70\n self.s_x, self.s_y = 30, 30 # sends insertion point\n self.r_x, self.r_y = 30, 530 # receives\u00a0insertion point\n self.comment(\"Pd \u2192 Python\", self.s_x, self.s_y)\n self.comment(\"===========\", self.s_x, self.s_y + self.h / 2)\n self.patch_ids[\"send\"] = self.osc_send(\n self.osc.host, self.osc.port, self.s_x, self.s_y + self.h * 2\n )\n self.comment(\"Python \u2192 Pd\", self.r_x, self.r_y)\n self.comment(\"===========\", self.r_x, self.r_y + self.h / 2)\n self.patch_ids[\"receive\"] = self.osc_receive(\n self.client_port, self.r_x, self.r_y + self.h * 2\n )\n self.s_x += 300\n self.r_x += 300\n if self.bela:\n self.create_bela_main()\n self.save(self.filepath)\n\n def create_bela_main(self):\n if self.filepath.startswith(\"pd/\"):\n abstraction = self.filepath[3:]\n with open(\"pd/_main.pd\", \"w\") as f:\n f.write(f\"#N canvas {self.x} {self.y} {self.w} {self.h} 12;\\n\")\n f.write(f\"#X obj {30} {30} {abstraction};\\n\")\n\n \"\"\"\n basic objects\n \"\"\"\n\n def box(self, box_type, x, y, box_text):\n self.patch_objects.append(f\"#X {box_type} {x} {y} {box_text};\\n\")\n return self.get_last_id()\n\n def object(self, obj, x, y):\n return self.box(\"obj\", x, y, obj)\n\n def msg(self, msg, x, y):\n return self.box(\"msg\", x, y, msg)\n\n def comment(self, text, x, y):\n return self.box(\"text\", x, y, text)\n\n def number(self, x, y):\n return self.box(\"floatatom\", x, y, f\"5 0 0 0 - - - 0\")\n\n \"\"\"\n connections\n \"\"\"\n\n def connect(self, a_id, a_outlet, b_id, b_inlet):\n self.patch_connections.append(\n f\"#X connect {a_id} {a_outlet} {b_id} {b_inlet};\\n\"\n )\n\n \"\"\"\n osc send/receive\n \"\"\"\n\n def osc_send(self, host, port, x, y, send_rate_limit=100):\n loadbang_id = self.object(\"loadbang\", x, y)\n y += self.h\n connect_id = self.msg(f\"connect {host} {port}\", x, y)\n y += self.h\n disconnect_id = self.msg(\"disconnect\", x + 10, y)\n metro_id = self.object(f\"metro {send_rate_limit}\", x + 100, y)\n y += self.h\n send_rate_id = self.object(\"s rate\", x + 100, y)\n y += self.h\n receive_id = self.object(\"r send.to.iipyper\", x + 10, y)\n y += self.h\n packOSC_id = self.object(\"packOSC\", x + 10, y)\n y += self.h\n send_type = \"netsend -u\" if self.net_or_udp == \"net\" else \"udpsend\"\n send_id = self.object(send_type, x, y)\n y += self.h\n status_id = self.number(x, y)\n print_id = self.object(\"print reply.from.netreceive\", x + 40, y)\n # loadbang->connect->send->print\n self.connect(loadbang_id, 0, connect_id, 0)\n self.connect(connect_id, 0, send_id, 0)\n self.connect(send_id, 0, status_id, 0)\n self.connect(send_id, 1, print_id, 0)\n # loadbang->metro->send_rate\n self.connect(loadbang_id, 0, metro_id, 0)\n self.connect(metro_id, 0, send_rate_id, 0)\n # disconnect->send\n self.connect(disconnect_id, 0, send_id, 0)\n # receive->packOSC->send\n self.connect(receive_id, 0, packOSC_id, 0)\n self.connect(packOSC_id, 0, send_id, 0)\n return send_id\n\n def osc_receive(self, port, x, y): \n receive_type = (\n f\"netreceive -u {port}\"\n if self.net_or_udp == \"net\"\n else f\"udpreceive {port}\"\n )\n receive_id = self.object(receive_type, x, y)\n y += self.h\n unpackOSC_id = self.object(\"unpackOSC\", x, y)\n y += self.h\n print_id = self.object(\"print receive.from.iipyper\", x + 20, y)\n y += self.h\n s_receive_id = self.object(\"s receive.from.iipyper\", x, y)\n self.connect(receive_id, 0, unpackOSC_id, 0)\n self.connect(unpackOSC_id, 0, s_receive_id, 0)\n self.connect(unpackOSC_id, 0, print_id, 0)\n return self.get_last_id()\n\n \"\"\"\n osc send/receive args/list\n \"\"\"\n\n def send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n print('send_args_func',hints, f_p.items())\n params = []\n if len(f_p) == 0:\n self.osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, (k, p) in enumerate(f_p.items()):\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n print(i, k, p, p_def, p_min, p_max)\n params.append(\n {\n \"label\": k,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def send_list_func(self, f):\n self.osc_receive_list(self.r_x, self.r_y, f[\"address\"], f[\"params\"])\n self.r_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n def receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n # TODO: handle strings\n if isinstance(p, str):\n continue\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def receive_list_func(self, f):\n self.osc_send_list(self.s_x, self.s_y, f[\"address\"], f[\"params\"])\n self.s_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n \"\"\"\n osc send/receive no args/list (msg)\n \"\"\"\n\n def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def osc_send_msg(self, x, y, path):\n msg_id = self.msg(path, x, y + 225 + self.h)\n send_id = self.object(\"s send.to.iipyper\", x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n \"\"\"\n osc send/receive args with line, slider, rate-limiting, and change detection\n \"\"\"\n\n def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def osc_send_with_controls(self, x, y, path, parameters):\n y_off = 0\n # [comment path]\n path_comment_id = self.comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.object(\n \"r \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"send\"\n )\n y_off += self.h * 3 # line\n y_off += _y_off + 25\n y_off += 225\n\n pack_id = -1\n out_id = -1\n # [pack $1 $2 $3 ...]\n if len(parameters) > 1:\n pack_id = self.object(\"pack \" + self._pack_args(parameters), x, y + y_off)\n out_id = pack_id\n\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_args = self._msg_args(parameters)\n msg_id = self.msg(path + \" \" + msg_args, x, y + y_off)\n out_id = msg_id if len(parameters) == 1 else out_id\n # [s send]\n y_off += 25\n send_id = self.object(\"s send.to.iipyper\", x, y + y_off)\n\n # connections\n for i in range(len(parameters)):\n rcv = receive_ids[i]\n slider = slider_ids[i]\n slider_float = slider_float_ids[i]\n int_id = int_ids[i]\n tbf_id = tbf_ids[i]\n\n self.connect(rcv, 0, slider[0], 0)\n self.connect(rcv, 0, slider[1], 0)\n if int_id == -1 and tbf_id == -1: # if no int or tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id != -1 and tbf_id == -1: # if int but no tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id == -1 and tbf_id != -1: # if tbf but no int\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n elif int_id != -1 and tbf_id != -1: # if both int and tbf\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n\n self.connect(pack_id, 0, msg_id, 0) if pack_id != -1 else None\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n \"\"\"\n sliders\n \"\"\"\n\n def sliders(self, x, y, sliders, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n \"\"\"\n slider_ids = []\n float_ids = []\n int_ids = []\n tbf_ids = []\n y_off = 0\n send_rate_id = self.object(\"r rate\", x - 50, y + 155 + self.h * 3)\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * self.param_width)\n y_off += self.h\n slider_id, int_id, float_id, tbf_id = self.slider(\n send_rate_id,\n x_i,\n y + y_off,\n s[\"min_val\"],\n s[\"size\"],\n float=s[\"data\"] == \"float\",\n io=io if i > 0 else \"skip\",\n )\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n int_ids.append(int_id)\n tbf_ids.append(tbf_id)\n return slider_ids, float_ids, int_ids, tbf_ids, y_off\n\n def slider(self, send_rate_id, x, y, min_val, size, float=False, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n bang_id = self.object(\"bng\", x, y)\n y += self.h\n msg_id = self.msg(f\"{self.line}\", x, y)\n y += self.h\n line_id = self.object(f\"line 0 {self.line}\", x, y)\n y += self.h\n slider_id = self.box(\n \"obj\",\n x,\n y,\n f\"vsl 20 120 {min_val} {min_val+size} 0 0 empty empty empty 0 -9 0 12 #fcfcfc #000000 #000000 0 1\",\n )\n self.connect(bang_id, 0, msg_id, 0)\n self.connect(msg_id, 0, line_id, 1)\n self.connect(line_id, 0, slider_id, 0)\n y += 120 + 8\n int_id = -1\n tbf_id = -1\n float_id = -1\n if float == False and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_int(\n slider_id, send_rate_id, x, y\n )\n elif float == False and io != \"send\":\n y, change_id = self.receive_rate_limit_int(slider_id, send_rate_id, x, y)\n elif float == True and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_float(\n slider_id, send_rate_id, x, y\n )\n elif float == True and io != \"send\":\n y, change_id = self.recieve_rate_limit_float(slider_id, send_rate_id, x, y)\n return (line_id, bang_id), int_id, change_id, tbf_id\n\n def send_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number -> t b f\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def receive_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n def send_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number -> t b f\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def recieve_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n \"\"\"\n comments\n \"\"\"\n\n def param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * self.param_width)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]} {p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n \"\"\"\n lists\n \"\"\"\n\n def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n\n def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n\n \"\"\"\n utils\n \"\"\"\n\n def get_last_id(self):\n return len(self.patch_objects) - 2\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"f\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n\n def _msg_args(self, args):\n return \" \".join([\"\\$\" + str(i + 1) for i in range(len(args))])\n\n def path_to_snakecase(self, path):\n return path.replace(\"/\", \"_\")[1:] # +'_'+label[0:3]\n\n \"\"\"\n save/load\n \"\"\"\n\n def save(self, name):\n with open(name + \".pd\", \"w\") as f:\n [f.write(o) for o in self.patch_objects]\n [f.write(c) for c in self.patch_connections]\n\n def load(self, name):\n with open(name + \".pd\", \"r\") as f:\n for line in f:\n if f.startswith(\"#X connect\"):\n self.patch_connections.append(f)\n else:\n self.patch_objects.append(f)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_list","title":"osc_receive_list(x, y, path, params)
","text":"[comment] path [r receive.from.iipyper] [routeOSC path] s path params
Source code insrc/tolvera/osc/pd.py
def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_msg","title":"osc_receive_msg(x, y, path)
","text":"does this even make sense?
Source code insrc/tolvera/osc/pd.py
def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_with_controls","title":"osc_receive_with_controls(x, y, path, parameters)
","text":"TODO: Does [route] need to be broken down into individual subpaths?
Source code insrc/tolvera/osc/pd.py
def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_send_list","title":"osc_send_list(x, y, path, params)
","text":"[comment] path, list name, params [r] path [list prepend path] [list trim] [s send.to.iipyper]
Source code insrc/tolvera/osc/pd.py
def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/update/","title":"Update","text":""},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveListUpdater","title":"OSCReceiveListUpdater
","text":" Bases: ReceiveListUpdater
ReceiveListUpdater with an OSC handler
Source code insrc/tolvera/osc/update.py
class OSCReceiveListUpdater(ReceiveListUpdater):\n \"\"\"\n ReceiveListUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n self.set(list(args[1:]))\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater","title":"OSCReceiveUpdater
","text":" Bases: ReceiveUpdater
ReceiveUpdater with an OSC handler
Source code insrc/tolvera/osc/update.py
class OSCReceiveUpdater(ReceiveUpdater):\n \"\"\"\n ReceiveUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater.receive","title":"receive(address, *args)
","text":"v: first argument to the handler is the IP:port of the sender v: or you can use dispatcher.map directly and not set needs_reply_address=True j: can I get ip:port from osc itself? v: if you know the sender ahead of time yeah, but that lets you respond to different senders dynamically
Source code insrc/tolvera/osc/update.py
def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdaters","title":"OSCReceiveUpdaters
","text":"o = OSCReceiveUpdaters(osc, {\"/tolvera/particles/pos\": s.osc_set_pos, \"/tolvera/particles/vel\": s.osc_set_vel})
Source code insrc/tolvera/osc/update.py
class OSCReceiveUpdaters:\n \"\"\"\n o = OSCReceiveUpdaters(osc,\n {\"/tolvera/particles/pos\": s.osc_set_pos,\n \"/tolvera/particles/vel\": s.osc_set_vel})\n \"\"\"\n\n def __init__(self, osc, receives=None, count=10):\n self.osc = osc\n self.receives = []\n self.count = count\n if receives is not None:\n self.add_dict(receives, count=self.count)\n\n def add_dict(self, receives, count=None):\n if count is None:\n count = self.count\n {a: self.add(a, f, count=count) for a, f in receives.items()}\n\n def add(self, address, function, state=None, count=None, update=False):\n if count is None:\n count = self.count\n self.receives.append(\n OSCReceiveUpdater(self.osc, address, function, state, count, update)\n )\n\n def __call__(self):\n [r() for r in self.receives]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSend","title":"OSCSend
","text":"Non rate-limited OSC send
Source code insrc/tolvera/osc/update.py
class OSCSend:\n \"\"\"\n Non rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.client = client\n\n def __call__(self, *args):\n self.osc.send(self.address, *self.f(*args), client=self.client)\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdater","title":"OSCSendUpdater
","text":"Rate-limited OSC send
Source code insrc/tolvera/osc/update.py
class OSCSendUpdater:\n \"\"\"\n Rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, count=30, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.count = count\n self.counter = 0\n self.client = client\n\n def __call__(self):\n self.counter += 1\n if self.counter >= self.count:\n self.osc.send(self.address, *self.f(), client=self.client)\n self.counter = 0\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdaters","title":"OSCSendUpdaters
","text":"o = OSCSendUpdaters(osc, client=\"particles\", count=10, sends={ \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all })
Source code insrc/tolvera/osc/update.py
class OSCSendUpdaters:\n \"\"\"\n o = OSCSendUpdaters(osc, client=\"particles\", count=10,\n sends={\n \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all\n })\n \"\"\"\n\n def __init__(self, osc, sends=None, count=10, client=None):\n self.osc = osc\n self.sends = []\n self.count = count\n self.client = client\n if sends is not None:\n self.add_dict(sends, self.count, self.client)\n\n def add_dict(self, sends, count=None, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n {a: self.add(a, f, count=count, client=client) for a, f in sends.items()}\n\n def add(self, address, function, state=None, count=None, update=False, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n self.sends.append(OSCSendUpdater(self.osc, address, function, count, client))\n\n def __call__(self):\n [s() for s in self.sends]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCUpdaters","title":"OSCUpdaters
","text":"o = OSCUpdaters(osc, client=\"boids\", count=10, receives={ \"/tolvera/boids/pos\": b.osc_set_pos, \"/tolvera/boids/vel\": b.osc_set_vel }, sends={ \"/tolvera/boids/pos/all\": b.osc_get_all_pos } )
Source code insrc/tolvera/osc/update.py
class OSCUpdaters:\n \"\"\"\n o = OSCUpdaters(osc, client=\"boids\", count=10,\n receives={\n \"/tolvera/boids/pos\": b.osc_set_pos,\n \"/tolvera/boids/vel\": b.osc_set_vel\n },\n sends={\n \"/tolvera/boids/pos/all\": b.osc_get_all_pos\n }\n )\n \"\"\"\n\n def __init__(\n self,\n osc,\n sends=None,\n receives=None,\n send_count=60,\n receive_count=10,\n client=None,\n ):\n self.osc = osc\n self.client = client\n self.send_count = send_count\n self.receive_count = receive_count\n self.sends = OSCSendUpdaters(\n self.osc, count=self.send_count, client=self.client\n )\n self.receives = OSCReceiveUpdaters(self.osc, count=self.receive_count)\n if sends is not None:\n self.add_sends(sends)\n if receives is not None:\n self.add_receives(receives)\n\n def add_sends(self, sends, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add_dict(sends, count, client)\n\n def add_send(self, send, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add(send, client=client, count=count)\n\n def add_receives(self, receives, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add_dict(receives, count=count)\n\n def add_receive(self, receive, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add(receive, count=count)\n\n def __call__(self):\n self.sends()\n self.receives()\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater","title":"ReceiveListUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter Assumes a list[float] instead of *args
Source code insrc/tolvera/osc/update.py
class ReceiveListUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n Assumes a list[float] instead of *args\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code insrc/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code insrc/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater","title":"ReceiveUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter TODO: Rename to ReceiveArgsUpdater?
Source code insrc/tolvera/osc/update.py
class ReceiveUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n TODO: Rename to ReceiveArgsUpdater?\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code insrc/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code insrc/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.Updater","title":"Updater
","text":"Rate-limited function call
Source code insrc/tolvera/osc/update.py
class Updater:\n \"\"\"\n Rate-limited function call\n \"\"\"\n\n def __init__(self, f, count: int = 1):\n self.f = f\n self.count = int(count)\n self.counter = 0\n\n def __call__(self, *args, **kwargs):\n self.counter += 1\n if self.counter >= self.count:\n self.counter = 0\n return self.f(*args, **kwargs)\n return None\n
"},{"location":"reference/tolvera/vera/attractors/","title":"Attractors","text":"Inspired by https://github.com/williamgilpin/dysts
"},{"location":"reference/tolvera/vera/diffline/","title":"Diffline","text":"Differential line
"},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine","title":"DifferentialLine
","text":"Source code in src/tolvera/vera/diffline.py
@ti.data_oriented\nclass DifferentialLine:\n def __init__(self, tolvera, **kwargs) -> None:\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"dt\": (ti.f32, 0.1)})\n \"\"\"\n state\n max segments\n tv.s.segments\n init\n starting shape? circle etc.\n step\n for s in segments\n calculate the direction of growth\n apply angle variation and growth rate\n maintain minimum distance between points\n composition\n want to be able to e.g. flock the segments\n \"\"\"\n self.tv.s.diffline_s = {\n \"state\": {\n \"sigma\": (ti.f32, 0.0, 100.0),\n },\n \"shape\": self.tv.sn,\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n for i in particles:\n pass\n @ti.func\n def proc(self, x, y, z) -> ti.Vector:\n pass\n def randomise(self):\n self.tv.s.diffline_s.randomise()\n def __call__(self, particles: ti.template(), weight: ti.f32=1.0):\n self.step(particles, weight)\n
"},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine.CONSTS","title":"CONSTS = CONSTS({'dt': (ti.f32, 0.1)})
instance-attribute
","text":"state max segments tv.s.segments init starting shape? circle etc. step for s in segments calculate the direction of growth apply angle variation and growth rate maintain minimum distance between points composition want to be able to e.g. flock the segments
"},{"location":"reference/tolvera/vera/flock/","title":"Flock","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock","title":"Flock
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code insrc/tolvera/vera/flock.py
@ti.data_oriented\nclass Flock:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight * p1.speed * p1.active \n particles[i].pos += particles[i].vel\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code insrc/tolvera/vera/flock.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.step","title":"step(particles, weight)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Defaultparticles
template
A template for the particles.
requiredweight
f32
The weight of the Flock behaviour.
required Source code insrc/tolvera/vera/flock.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight * p1.speed * p1.active \n particles[i].pos += particles[i].vel\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/flock2/","title":"Flock2","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2","title":"Flock2
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code insrc/tolvera/vera/flock2.py
@ti.data_oriented\nclass Flock2:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock2_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock2_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock2_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock2_s.randomise()\n\n @ti.func\n def set_mag(self, v: ti.template(), mag: ti.f32):\n return (v / v.norm()) * mag\n\n @ti.func\n def set_mag2(self, v: ti.template(), mag: ti.f32):\n return (v / v.norm())# * mag\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n p1 = particles[i]\n if p1.active == 0:\n continue\n if single_species > -1:\n if p1.species != single_species:\n continue\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock2_s.struct()\n for j in range(n):\n p2 = particles[j]\n if i == j and p2.active == 0:\n continue\n # if single_species > -1:\n # if p2.species != single_species:\n # continue\n species = self.tv.s.flock2_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock2_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n vel = ti.Vector([0.0, 0.0])\n if algotype == 0:\n align = (align / nearby) * p1.active * species.align\n separate = (\n (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n )\n cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n # vel = separate + align + cohere\n elif algotype == 1:\n align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n elif algotype == 2:\n align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight, algotype, single_species)\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__call__","title":"__call__(particles, weight=1.0, algotype=0, single_species=-1)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock2.py
def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight, algotype, single_species)\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock2.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock2_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock2_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0.0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock2_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code insrc/tolvera/vera/flock2.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock2_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.step","title":"step(particles, weight, algotype, single_species)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Defaultparticles
template
A template for the particles.
requiredweight
f32
The weight of the Flock behaviour.
required Source code insrc/tolvera/vera/flock2.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n p1 = particles[i]\n if p1.active == 0:\n continue\n if single_species > -1:\n if p1.species != single_species:\n continue\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock2_s.struct()\n for j in range(n):\n p2 = particles[j]\n if i == j and p2.active == 0:\n continue\n # if single_species > -1:\n # if p2.species != single_species:\n # continue\n species = self.tv.s.flock2_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock2_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n vel = ti.Vector([0.0, 0.0])\n if algotype == 0:\n align = (align / nearby) * p1.active * species.align\n separate = (\n (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n )\n cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n # vel = separate + align + cohere\n elif algotype == 1:\n align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n elif algotype == 2:\n align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n vel = separate + align + cohere\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/flock_shiffman/","title":"Flock shiffman","text":"taichi flocking simulation credits: https://www.youtube.com/watch?v=mhjuuHl6qHM
"},{"location":"reference/tolvera/vera/forces/","title":"Forces","text":"Force functions for particles.
This module contains functions for applying forces to particles. It includes functions for moving, attracting, repelling and gravitating particles. It also includes variations of these functions for specific species of particles.
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract","title":"attract(particles, pos, mass, radius)
","text":"Attract the particles to a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef attract(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Attract the particles to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_particle","title":"attract_particle(p, pos, mass, radius)
","text":"Attract a particle to a position.
Parameters:
Name Type Description Defaultparticles
Particle
Individual particle.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Attraction velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef attract_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Attract a particle to a position.\n\n Args:\n particles (Particle): Individual particle.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n\n Returns:\n ti.math.vec2: Attraction velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (radius - target_distance) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_species","title":"attract_species(particles, pos, mass, radius, species)
","text":"Attract the particles of a given species to a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Attraction position.
requiredmass
f32
Attraction mass.
requiredradius
f32
Attraction radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef attract_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Attract the particles of a given species to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal","title":"centripetal(particles, centre, direction, weight)
","text":"Apply a centripetal force to the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredcentre
vec2
Centripetal centre.
requireddirection
i32
Centripetal direction.
requiredweight
f32
Centripetal weight.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef centripetal(particles: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32):\n \"\"\"Apply a centripetal force to the particles.\n\n Args:\n particles (ti.template): Particles.\n centre (ti.math.vec2): Centripetal centre.\n direction (ti.i32): Centripetal direction.\n weight (ti.f32): Centripetal weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += centripetal_particle(p, centre, direction, weight)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal_particle","title":"centripetal_particle(p, centre, direction, weight)
","text":"Apply a centripetal force to a particle.
Parameters:
Name Type Description Defaultp
Particle
Individual particle.
requiredcentre
vec2
Centripetal centre.
requireddirection
i32
Centripetal direction.
requiredweight
f32
Centripetal weight.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Centripetal velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef centripetal_particle(p: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32) -> ti.math.vec2:\n \"\"\"Apply a centripetal force to a particle.\n\n Args:\n p (Particle): Individual particle.\n centre (ti.math.vec2): Centripetal centre.\n direction (ti.i32): Centripetal direction.\n weight (ti.f32): Centripetal weight.\n\n Returns:\n ti.math.vec2: Centripetal velocity.\n \"\"\"\n r = p.pos - centre\n if direction == 0:\n r = -r\n v_perp = ti.Vector([-r[1], r[0]])\n norm = v_perp.norm() + 1e-5\n v_perp_normalized = v_perp / norm\n speed = p.vel.norm()\n new_vel = v_perp_normalized * speed * weight\n return new_vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate","title":"gravitate(particles, G, radius)
","text":"Gravitate the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredG
f32
Gravitational constant.
requiredradius
f32
Gravitational radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef gravitate(particles: ti.template(), G: ti.f32, radius: ti.f32):\n \"\"\"Gravitate the particles.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate_species","title":"gravitate_species(particles, G, radius, species)
","text":"Gravitate the particles of a given species.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredG
f32
Gravitational constant.
requiredradius
f32
Gravitational radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef gravitate_species(\n particles: ti.template(), G: ti.f32, radius: ti.f32, species: ti.i32\n):\n \"\"\"Gravitate the particles of a given species.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n species (ti.i32): Species index.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if p1.species != species or p2.species != species:\n continue\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitation","title":"gravitation(p1, p2, G)
","text":"Calculate the gravitational force between two particles.
Parameters:
Name Type Description Defaultp1
Particle
Particle 1.
requiredp2
Particle
Particle 2.
requiredG
f32
Gravitational constant.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Gravitational force.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef gravitation(p1: Particle, p2: Particle, G: ti.f32) -> ti.math.vec2:\n \"\"\"Calculate the gravitational force between two particles.\n\n Args:\n p1 (Particle): Particle 1.\n p2 (Particle): Particle 2.\n G (ti.f32): Gravitational constant.\n\n Returns:\n ti.math.vec2: Gravitational force.\n \"\"\"\n r = p2.pos - p1.pos\n distance = r.norm() + 1e-5\n force_direction = r.normalized()\n force_magnitude = G * p1.mass * p2.mass / (distance**2)\n force = force_direction * force_magnitude\n return force / p1.mass\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.move","title":"move(particles)
","text":"Move the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef move(particles: ti.template()):\n \"\"\"Move the particles.\n\n Args:\n particles (ti.template): Particles.\n \"\"\"\n for i in range(particles.field.shape[0]):\n if particles.field[i].active == 0:\n continue\n p1 = particles.field[i]\n particles.field[i].pos += p1.vel * p1.speed * p1.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.noise","title":"noise(particles, weight)
","text":"Add noise to the particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredweight
f32
Noise weight.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef noise(particles: ti.template(), weight: ti.f32):\n \"\"\"Add noise to the particles.\n\n Args:\n particles (ti.template): Particles.\n weight (ti.f32): Noise weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += (ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * weight)\n particles.field[i].pos += p.vel * p.speed * p.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel","title":"repel(particles, pos, mass, radius)
","text":"Repel the particles from a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef repel(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Repel the particles from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_particle","title":"repel_particle(p, pos, mass, radius)
","text":"Repel a particle from a position.
Parameters:
Name Type Description Defaultp
Particle
Individual particle.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
requiredReturns:
Type Descriptionvec2
ti.math.vec2: Repulsion velocity.
Source code insrc/tolvera/vera/forces.py
@ti.func\ndef repel_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Repel a particle from a position.\n\n Args:\n p (Particle): Individual particle.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n\n Returns:\n ti.math.vec2: Repulsion velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (target_distance - radius) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_species","title":"repel_species(particles, pos, mass, radius, species)
","text":"Repel the particles of a given species from a position.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredpos
vec2
Repulsion position.
requiredmass
f32
Repulsion mass.
requiredradius
f32
Repulsion radius.
requiredspecies
i32
Species index.
required Source code insrc/tolvera/vera/forces.py
@ti.kernel\ndef repel_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Repel the particles of a given species from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/geom/","title":"Geom","text":""},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.cos","title":"cos(particles, frequency, magnitude, phase, axis)
","text":"Apply a cosine wave force to particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredfrequency
f32
Frequency of the cosine wave.
requiredmagnitude
f32
Magnitude of the cosine wave.
requiredphase
f32
Phase of the cosine wave.
requiredaxis
i32
Axis to apply the cosine wave to.
required Source code insrc/tolvera/vera/geom.py
@ti.kernel\ndef cos(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n \"\"\"Apply a cosine wave force to particles.\n\n Args:\n particles (ti.template): Particles.\n frequency (ti.f32): Frequency of the cosine wave.\n magnitude (ti.f32): Magnitude of the cosine wave.\n phase (ti.f32): Phase of the cosine wave.\n axis (ti.i32): Axis to apply the cosine wave to.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n inc = magnitude * ti.cos(1/frequency * p.pos[axis] + phase)\n particles.field[i].vel[axis] += inc\n
"},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.sin","title":"sin(particles, frequency, magnitude, phase, axis)
","text":"Apply a sine wave force to particles.
Parameters:
Name Type Description Defaultparticles
template
Particles.
requiredfrequency
f32
Frequency of the sine wave.
requiredmagnitude
f32
Magnitude of the sine wave.
requiredphase
f32
Phase of the sine wave.
requiredaxis
i32
Axis to apply the sine wave to.
required Source code insrc/tolvera/vera/geom.py
@ti.kernel\ndef sin(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n \"\"\"Apply a sine wave force to particles.\n\n Args:\n particles (ti.template): Particles.\n frequency (ti.f32): Frequency of the sine wave.\n magnitude (ti.f32): Magnitude of the sine wave.\n phase (ti.f32): Phase of the sine wave.\n axis (ti.i32): Axis to apply the sine wave to.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n inc = magnitude * ti.sin(1/frequency * p.pos[axis] + phase)\n particles.field[i].vel[axis] += inc\n
"},{"location":"reference/tolvera/vera/nca/","title":"Nca","text":"https://github.com/google/swissgl/blob/main/demo/NeuralCA.js
"},{"location":"reference/tolvera/vera/particle_life/","title":"Particle life","text":"Particle Life model.
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife","title":"ParticleLife
","text":"Particle Life model.
The Particle Life model is a simple model of particle behaviour, where particles are either attracted or repelled by other particles, depending on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr and others:
https://www.ventrella.com/Clusters/ https://github.com/tom-mohr/particle-life-app
Source code insrc/tolvera/vera/particle_life.py
@ti.data_oriented\nclass ParticleLife():\n \"\"\"Particle Life model.\n\n The Particle Life model is a simple model of particle behaviour, where\n particles are either attracted or repelled by other particles, depending\n on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr\n and others:\n\n https://www.ventrella.com/Clusters/\n https://github.com/tom-mohr/particle-life-app\n\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n # particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n # particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n particles[i].pos += particles[i].vel\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Particle Life model.
Parameters:
Name Type Description Defaultparticles
Particles
The particles to step.
required Source code insrc/tolvera/vera/particle_life.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Particle Life model.
'plife' stores the species rule matrix.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
required**kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/particle_life.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.step","title":"step(particles, weight)
","text":"Step the Particle Life model.
Parameters:
Name Type Description Defaultparticles
field
The particles to step.
requiredweight
f32
The weight of the step.
required Source code insrc/tolvera/vera/particle_life.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n # particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n # particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n particles[i].pos += particles[i].vel\n
"},{"location":"reference/tolvera/vera/reaction_diffusion/","title":"Reaction diffusion","text":"Inspired by https://github.com/taichi-dev/faster-python-with-taichi/blob/main/reaction_diffusion_taichi.py
"},{"location":"reference/tolvera/vera/slime/","title":"Slime","text":"Slime behaviour based on the Physarum polycephalum slime mould.
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime","title":"Slime
","text":"Slime behaviour based on the Physarum polycephalum slime mould.
The slime mould is a single-celled organism that exhibits complex behaviour such as foraging, migration, and decision-making. It is a popular model for emergent behaviour in nature-inspired computing.
The slime mould is simulated by a set of particles that move around the simulation space. The particles sense their environment and move in response to the sensed information. The particles leave a \"pheromone trail\" behind them, which evaporates over time. The particles can be of different species, which have different sensing and moving parameters.
Taichi Physarum implementation inspired by: https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py
Source code insrc/tolvera/vera/slime.py
@ti.data_oriented\nclass Slime:\n \"\"\"Slime behaviour based on the Physarum polycephalum slime mould.\n\n The slime mould is a single-celled organism that exhibits complex behaviour\n such as foraging, migration, and decision-making. It is a popular model for\n emergent behaviour in nature-inspired computing.\n\n The slime mould is simulated by a set of particles that move around the\n simulation space. The particles sense their environment and move in response\n to the sensed information. The particles leave a \"pheromone trail\" behind them,\n which evaporates over time. The particles can be of different species, which \n have different sensing and moving parameters.\n\n Taichi Physarum implementation inspired by:\n https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n\n def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n\n @ti.kernel\n def move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n\n @ti.func\n def sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n\n @ti.func\n def sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n\n @ti.kernel\n def deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n\n def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n\n def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__call__","title":"__call__(particles, species, weight=1.0)
","text":"Call the Slime behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
A Particles instance.
requiredspecies
Species
A Species instance.
requiredweight
f32
Weight parameter. Defaults to 1.0.
1.0
Returns:
Name Type DescriptionPixels
A Pixels instance containing the pheromone trail.
Source code insrc/tolvera/vera/slime.py
def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Slime behaviour.
slime_p
stores the particle state. slime_s
stores the species state. trail
is a Pixels instance that stores the pheromone trail.
Parameters:
Name Type Description Defaulttolvera
Tolvera
A Tolvera instance.
requiredevaporate
f32
Evaporation rate. Defaults to 0.99.
required**kwargs
Keyword arguments. brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.
{}
Source code in src/tolvera/vera/slime.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.deposit_particles","title":"deposit_particles(particles, species)
","text":"Deposit particles onto the trail.
Parameters:
Name Type Description Defaultparticles
template
Particle field.
requiredspecies
template
Species field.
required Source code insrc/tolvera/vera/slime.py
@ti.kernel\ndef deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.move","title":"move(field, weight)
","text":"Move the particles based on the sensed environment.
Each particle senses the trail to its left, centre and right. Depending on the strength of the sensed trail in each direction, and the species parameters, a movement angle is calculated. The particle moves in this direction by a distance proportional to its active state and the weight parameter.
Parameters:
Name Type Description Defaultfield
template
Particle field.
requiredweight
f32
Weight of the movement.
required Source code insrc/tolvera/vera/slime.py
@ti.kernel\ndef move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.randomise","title":"randomise()
","text":"Randomise the Slime behaviour.
Source code insrc/tolvera/vera/slime.py
def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense","title":"sense(pos, ang, dist)
","text":"Sense the trail at a given position and angle.
Parameters:
Name Type Description Defaultpos
vec2
Position.
requiredang
f32
Angle.
requireddist
f32
Distance.
requiredReturns:
Type Descriptionvec4
ti.math.vec4: RGBA value of the sensed trail point.
Source code insrc/tolvera/vera/slime.py
@ti.func\ndef sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense_rgba","title":"sense_rgba(pos, ang, dist, rgba)
","text":"Sense the trail at a given position and angle and return a weighted RGBA value.
Parameters:
Name Type Description Defaultpos
vec2
Position.
requiredang
f32
Angle.
requireddist
f32
Distance.
requiredrgba
vec4
RGBA value.
requiredReturns:
Type Descriptionvec4
ti.math.vec4: Weighted RGBA value.
Source code insrc/tolvera/vera/slime.py
@ti.func\ndef sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.step","title":"step(particles, species, weight=1.0)
","text":"Step the Slime behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
A Particles instance.
requiredspecies
Species
A Species instance.
requiredweight
f32
Weight parameter. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/slime.py
def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n
"},{"location":"reference/tolvera/vera/swarmalators/","title":"Swarmalators","text":"Based on https://www.complexity-explorables.org/explorables/swarmalators/
"},{"location":"reference/tolvera/vera/viscek/","title":"Viscek","text":"Flocking behaviour based on the Viscek model.
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek","title":"Viscek
","text":"Viscek flocking model
Source code insrc/tolvera/vera/viscek.py
@ti.data_oriented\nclass Viscek:\n \"\"\"Viscek flocking model\"\"\"\n def __init__(self, tolvera, **kwargs):\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.viscek_s = {\n \"state\": {\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.viscek_p = {\n \"state\": {\n \"theta\": (ti.f32, -np.pi, np.pi),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.viscek_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise\"\"\"\n self.tv.s.viscek_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), noise_weight: ti.f32, weight: ti.f32):\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n species = self.tv.s.viscek_s.struct()\n n = 0\n n_dir = ti.Vector([0.0, 0.0])\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n n_dir += self.tv.s.viscek_p[j].theta\n n += 1\n avg_dir = n_dir / n\n\n\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Defaultparticles
Particles
Particles to step.
requiredweight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/viscek.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek.randomise","title":"randomise()
","text":"Randomise
Source code insrc/tolvera/vera/viscek.py
def randomise(self):\n \"\"\"Randomise\"\"\"\n self.tv.s.viscek_s.randomise()\n
"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index a6531a2..67a18de 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ