overview
Contents
overview#
This is a short overview of enjoyn
, geared towards new users.
why enjoyn
#
The primary goal of enjoyn
is to facilitate joining images to form animations.
Indubitably, there are plenty of other Python libraries that already accomplish this.
However, many of these Python libraries either:
only utilize a single thread for rendering.
requires the images for the animation ready on the file system.
In contrast, enjoyn
features:
utilizing multiple threads and/or processes for rendering.
generating input images for the animation on the fly.
short demo#
To demonstrate, let’s first generate a large number of images to animate.
[1]:
from enjoyn.example import RandomWalkExample
example = RandomWalkExample(length=1000)
with example.time_run():
outputs = example.output_images()
print(f"Length: {len(outputs)}, Example: {outputs[0]}")
Runtime: 45.973551041 seconds
Length: 1000, Example: /var/folders/l8/rxc2dv157_9dmx0sjlq7nwc00000gn/T/enjoyn_bardrjv5/f07433b50ba14ee1abc1688e52a25d71.png
using imageio#
To animate those images, let’s use imageio
to:
serialize the output into
numpy
arrays usingiio.imread
.join the arrays to form a GIF using
iio.imwrite
.
[2]:
import imageio.v3 as iio
with example.time_run():
imageio_uri = "assets/imageio_random_walk.gif"
iio.imwrite(imageio_uri, [iio.imread(output) for output in outputs], loop=0)
Runtime: 9.815273082999994 seconds
using enjoyn#
Now let’s use enjoyn
to do the same.
[3]:
from enjoyn import GifAnimator
with example.time_run():
enjoyn_uri = "assets/enjoyn_random_walk.gif"
GifAnimator(items=outputs, output_path=enjoyn_uri).compute()
[########################################] | 100% Completed | 4.2s
Runtime: 4.240391915999993 seconds
both outputs#
Here are the rendered GIFs; imageio
is shown above and enjoyn
is shown below.
Here are the file sizes.
[4]:
example.size_of("assets/imageio_random_walk.gif")
example.size_of("assets/enjoyn_random_walk.gif")
File size of imageio_random_walk.gif: 3.73 MBs
File size of enjoyn_random_walk.gif: 1.27 MBs
Notice, although the renders look identical, both runtime and file size are halved with enjoyn
!
inner workings#
Internally, enjoyn
uses imageio
as described above.
However, on top of that, enjoyn
leverages dask
to scale and gifsicle
to optimize:
dask
partitions the items across workers, returning partitioned animations.gifsicle
concatenates the partitioned animations and applies compression.
For a deeper dive on the implementation, see design notes.
Here’s what the dask
dashboard would have looked like if a distributed.Client
was provided!
using preprocessor#
Before cleaning up, let’s see how enjoyn
can generate input images on the fly.
To accomplish this:
serialize a
Preprocessor
with the desired plotting function,func
, and keywords,kwds
.update
items
so that when it’s mapped, each item becomes the first positional argument offunc
.
Note enjoyn
can accept both files and file-objects, as exemplified by setting to_bytes_io = True
here.
[5]:
from enjoyn import GifAnimator, Preprocessor
with example.time_run():
example.to_bytes_io = True
preprocessor = Preprocessor(func=example.plot_image)
data = example.load_data()
items = [data[:i] for i in range(1, len(data))]
output_path = "assets/enjoyn_random_walk_on_the_fly.gif"
GifAnimator(
preprocessor=preprocessor, items=items, output_path=output_path
).compute()
[########################################] | 100% Completed | 24.8s
Runtime: 25.475539207999987 seconds
The runtime is now way over 10 seconds.
However, that’s because it’s now performing two jobs:
generating the images.
rendering the animation.
Overall, enjoyn
is still able to halve the total runtime because these jobs are executed in parallel.
[6]:
example.cleanup_images()
next steps#
If this guide intrigued you, why not install enjoyn or star the repo?
It’s inspiring to see others enjoy enjoyn
!