Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
### Fixed
- `Data.squeeze`: axes of output object now inherit units from axes of the input object

### Changed
- use `pillow` directly and remove `ImageIO` dependency

## [3.6.3]

### Changed
Expand Down Expand Up @@ -74,6 +77,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- new artist helper function: `norm_from_channel`
- new artist helper function: `ticks_from_norm`
- new artist iterator `ChopHandler`
- `artists.stitch_to_animation`: new kwargs for more gif customization

### Fixed
- fixed Quick2D/Quick1D issues where collapsing unused dims did not work
Expand Down
73 changes: 54 additions & 19 deletions WrightTools/artists/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# --- import --------------------------------------------------------------------------------------


import os
import pathlib

import numpy as np

Expand All @@ -15,6 +15,7 @@

from matplotlib.colors import Normalize, CenteredNorm, TwoSlopeNorm
from mpl_toolkits.axes_grid1 import make_axes_locatable
from typing import List

import imageio.v3 as iio
import warnings
Expand Down Expand Up @@ -876,7 +877,7 @@ def savefig(path, fig=None, close=True, **kwargs):
if fig is None:
fig = plt.gcf()

path = os.path.abspath(path)
path = pathlib.Path(path).resolve()

kwargs["dpi"] = kwargs.get("dpi", 300)
kwargs["transparent"] = kwargs.get("transparent", False)
Expand Down Expand Up @@ -1084,37 +1085,71 @@ def subplots_adjust(fig=None, inches=1):
fig.subplots_adjust(top=top, right=right, bottom=bottom, left=left)


def stitch_to_animation(paths, outpath=None, *, duration=0.5, palettesize=256, verbose=True):
"""Stitch a series of images into an animation.

Currently supports animated gifs, other formats coming as needed.
def stitch_to_animation(
paths,
outpath=None,
duration=0.5,
ignore_alpha: bool = True,
reduce: int = None,
verbose=True,
**kwargs,
):
"""Stitch a series of images into a gif.

Parameters
----------
paths : list of strings
Filepaths to the images to stitch together, in order of apperence.
Filepaths to the images to stitch together, in order of appearance.
outpath : string (optional)
Path of output, including extension. If None, bases output path on path
of first path in `images`. Default is None.
Path of output, including extension. If None, bases output path on `paths[0]`. Default is None.
duration : number or list of numbers (optional)
Duration of (each) frame in seconds. Default is 0.5.
palettesize : int (optional)
The number of colors in the resulting animation. Input is rounded to
the nearest power of 2. Default is 256.
ignore_alpha : bool (optional)
When True, transparency is excluded from the gif and color palette may be higher res.
reduce : int (optional)
Reduces the resolution along both image dimensions by a factor of `reduce`.
verbose : bool (optional)
Toggle talkback. Default is True.

Returns:
--------
outpath : path-like
path to generated gif
"""
import contextlib
from PIL import Image

# parse filename
if outpath is None:
outpath = os.path.splitext(paths[0])[0] + ".gif"
outpath = pathlib.Path(paths[0]).with_suffix(".gif")
# write
t = wt_kit.Timer(verbose=False)
with t, iio.imopen(outpath, "w") as gif:
for p in paths:
frame = iio.imread(p)
gif.write(
frame, plugin="pillow", duration=duration * 1e3, loop=0, palettesize=palettesize
)

def process_imgs(imgs: List[Image.Image]):
count = 0
for img in imgs:
if verbose:
print(f"processing {count+1} / {len(paths)}...")
if ignore_alpha:
img = img.convert("RGB")
if reduce is not None:
img = img.reduce(reduce)
count += 1
yield img

with t, contextlib.ExitStack() as stack:
imgs = process_imgs(stack.enter_context(Image.open(p)) for p in paths)
img = next(imgs)
img.save(
fp=outpath,
format="GIF",
append_images=imgs,
save_all=True,
duration=duration * 1e3,
loop=0,
**kwargs,
)

if verbose:
interval = np.round(t.interval, 2)
print("gif generated in {0} seconds - saved at {1}".format(interval, outpath))
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ requires-python = ">=3.7"
dynamic = ["version"]
dependencies = [
"h5py",
"imageio>=2.28.0",
"pillow",
"matplotlib>=3.4.0",
"numexpr",
"numpy>=1.15.0",
Expand Down
Loading