Repository: wright-group/WrightTools

Issue:
Artists 'stitch_to_animation' using incorrect durations; looping is lost
`wt.artists.stitch_to_animation` no longer processes as before (e.g. May 2023). The duration (length of each frame) is now read in ms (as opposed to s) and any gif made from this command no longer loops (i.e. runs through once and stops). 

It appears image.io has undergone multiple updates over the past six months, which has relegated our implementation of its gif making tool to its legacy VPI.  Probably just need to figure out how to make gifs in newest VPI and change accordingly. 


Discussion:
in this timeframe mentioned, the imagio v2 api (what we use) got new plugins:
https://github.com/imageio/imageio/pull/824
It looks like the default plugin we previously used ("GIF-PIL") was replaced (with "pillow"):
```
>>> imageio.config.known_extensions[".gif"][0].priority
['pillow', 'GIF-PIL', 'pyav']
```

Nothing seems wrong with using pillow (perhaps it is the better choice now); [the kwargs and their defaults are just slightly different](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif-saving).  


Resolving patch:
diff --git a/WrightTools/artists/_helpers.py b/WrightTools/artists/_helpers.py
--- a/WrightTools/artists/_helpers.py
+++ b/WrightTools/artists/_helpers.py
@@ -14,7 +14,7 @@
 import matplotlib.patheffects as PathEffects
 from mpl_toolkits.axes_grid1 import make_axes_locatable
 
-import imageio
+import imageio.v3 as iio
 import warnings
 
 from .. import exceptions as wt_exceptions
@@ -1081,14 +1081,14 @@ def subplots_adjust(fig=None, inches=1):
         fig.subplots_adjust(top=top, right=right, bottom=bottom, left=left)
 
 
-def stitch_to_animation(images, outpath=None, *, duration=0.5, palettesize=256, verbose=True):
+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.
 
     Parameters
     ----------
-    images : list of strings
+    paths : list of strings
         Filepaths to the images to stitch together, in order of apperence.
     outpath : string (optional)
         Path of output, including extension. If None, bases output path on path
@@ -1097,22 +1097,21 @@ def stitch_to_animation(images, outpath=None, *, duration=0.5, palettesize=256,
         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 1024.
+        the nearest power of 2. Default is 256.
     verbose : bool (optional)
         Toggle talkback. Default is True.
     """
     # parse filename
     if outpath is None:
-        outpath = os.path.splitext(images[0])[0] + ".gif"
+        outpath = os.path.splitext(paths[0])[0] + ".gif"
     # write
     t = wt_kit.Timer(verbose=False)
-    with t, imageio.get_writer(
-        outpath, mode="I", duration=duration, palettesize=palettesize
-    ) as writer:
-        for p in images:
-            image = imageio.imread(p)
-            writer.append_data(image)
-    # finish
+    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
+            )
     if verbose:
         interval = np.round(t.interval, 2)
         print("gif generated in {0} seconds - saved at {1}".format(interval, outpath))
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,7 @@ def read(fname):
     python_requires=">=3.7",
     install_requires=[
         "h5py",
-        "imageio",
+        "imageio>=2.28.0",
         "matplotlib>=3.4.0",
         "numexpr",
         "numpy>=1.15.0",


Test patch: