Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] artists.stitch_to_animation: better gifs #1181

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
69ad6ea
soft deprecate plot_colorbar
ddkohler Mar 22, 2024
0b3f566
Merge branch 'master' into plan-plot_colorbar-deprecation
ddkohler Mar 22, 2024
daa1e46
Update CHANGELOG.md
ddkohler Mar 22, 2024
5c9eaa0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 22, 2024
8122951
fix 2 warnings
ddkohler Mar 22, 2024
7ab3ab7
test build
ddkohler Mar 23, 2024
1509e85
Update fringes_transform.py
ddkohler Mar 23, 2024
8242466
test node20
ddkohler Mar 23, 2024
1834767
Update fringes_transform.py
ddkohler Mar 23, 2024
13386fd
try more for sphinx
ddkohler Mar 23, 2024
06e32b7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 23, 2024
31fa0bd
Update split.py
ddkohler Mar 23, 2024
fdf358c
re-implement module reset (#1175)
ddkohler Mar 24, 2024
ddf2db8
new example for norms, cbar
ddkohler Mar 31, 2024
c75a0d6
Merge branch 'master' into plan-plot_colorbar-deprecation
ddkohler Mar 31, 2024
39c36bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 31, 2024
1622cd9
abandon using datasets as module
ddkohler Mar 31, 2024
5cd0cf5
try new load method
ddkohler Apr 1, 2024
0be6b2e
datasets us SimpleNamespace
ddkohler Apr 1, 2024
6dc130e
Merge branch 'master' into plan-plot_colorbar-deprecation
ddkohler Apr 1, 2024
813228a
Update norms.py
ddkohler Apr 1, 2024
274f789
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 1, 2024
35fc3a2
codeql
ddkohler Apr 1, 2024
6b4832d
Merge branch 'master' into plan-plot_colorbar-deprecation
ddkohler Apr 29, 2024
fa536ed
axis_label_from_data, deprecation warnings
ddkohler May 3, 2024
537c93f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 3, 2024
1398223
direct PIL option, remove bad kwargs, RGB conversion
ddkohler May 29, 2024
413fa9d
remove ImageIO implementation, new kwargs, use pathlib
ddkohler May 29, 2024
2536b15
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 29, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- refactor of artists.quick1D and artists.quick2D
- quick2D and quick1D will not force `autosave=True` if the number of figures is large. Instead, interactive plotting will be truncated if the number of figures is large.
- artists now gets turbo colormap straight from matplotlib
- deprecating `artists.plot_colorbar`: instead use matplotlib's `colorbar` implementations directly
- artists.interact2D now returns a `types.SimpleNamespace` object (rather than a tuple)

## [3.5.2]
Expand Down
3 changes: 2 additions & 1 deletion WrightTools/artists/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ def _parse_plot_args(self, *args, **kwargs):
data=data, channel_index=channel_index, dynamic_range=dynamic_range, **kwargs
)
if plot_type == "contourf":
if "levels" not in kwargs.keys():
if ("levels" not in kwargs.keys()) and ("norm" not in kwargs):
# because of _parse_limits, we ensur we always have vmin and vmax to use
kwargs["levels"] = np.linspace(kwargs["vmin"], kwargs["vmax"], 256)
elif plot_type == "contour":
if "levels" not in kwargs.keys():
Expand Down
127 changes: 100 additions & 27 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 @@ -30,18 +30,19 @@

__all__ = [
"_title",
"axis_label_from_data",
"add_sideplot",
"corner_text",
"create_figure",
"diagonal_line",
"get_scaled_bounds",
"norm_from_channel",
"pcolor_helper",
"plot_colorbar",
"pcolor_helper", # deprecation imminent
"plot_colorbar", # to be deprecated (use mpl methods)
"plot_margins",
"plot_gridlines",
"plot_gridlines", # to be deprecated (use mpl methods, styles)
"savefig",
"set_ax_labels",
"set_ax_labels", # to be deprecated (use mpl methods, styles)
"set_ax_spines",
"set_fig_labels",
"subplots_adjust",
Expand All @@ -53,6 +54,34 @@
# --- functions -----------------------------------------------------------------------------------


def axis_label_from_data(data, ax=None, cax=None, which="both", channel_index=0):
"""Apply x and/or y labels to axes.
Parameters
----------
data : WrightTools.Data
data from which to extract the label(s)
ax : Axis object (optional)
Default is current axis
autolabel : {'none', 'both', 'x', 'y'} (optional)
Label(s) to apply from data. Default is none.
channel_index : integer (optional)
Channel index. Default is 0. Only important for 2D data
"""
if ax is None:
ax = plt.gca()
if which in ["both", "x"]:
xlabel = data.axes[0].label
ax.set_xlabel(xlabel)
if which in ["both", "y"]:
if data.ndim == 1:
ylabel = data.channels[channel_index].label
elif data.ndim == 2:
ylabel = data.axes[1].label
else:
raise wt_exceptions.DimensionalityError("<=3", data.ndim)
ax.set_ylabel(ylabel)


def _title(fig, title, subtitle="", *, margin=1, fontsize=20, subfontsize=18):
"""Add a title to a figure.

Expand Down Expand Up @@ -657,6 +686,11 @@ def plot_colorbar(
matplotlib.colorbar.ColorbarBase object
The created colorbar.
"""
warnings.warn(
"`plot_colorbar` is planned for deprecation. "
+ "Use `matplotlib.pyplot.colorbar` instead.",
wt_exceptions.VisibleDeprecationWarning,
)
# parse cax
if cax is None:
cax = plt.gca()
Expand Down Expand Up @@ -807,6 +841,12 @@ def plot_gridlines(ax=None, c="grey", lw=1, diagonal=False, zorder=2, makegrid=T
zorder : number (optional)
zorder of plotted grid. Default is 2.
"""
warnings.warn(
"``plot_gridlines`` is deprecated and will be removed in a future version. "
+ "Use matplotlib's ``grid`` methods instead",
wt_exceptions.VisibleDeprecationWarning,
)

# get ax
if ax is None:
ax = plt.gca()
Expand Down Expand Up @@ -877,7 +917,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 @@ -913,6 +953,11 @@ def set_ax_labels(ax=None, xlabel=None, ylabel=None, xticks=None, yticks=None, l
--------
set_fig_labels
"""
warnings.warn(
"``set_ax_labels`` is deprecated and will be removed in a future version. "
+ "Use matplotlib's ``set_xlabel`` and ``set_ylabel`` methods instead",
wt_exceptions.VisibleDeprecationWarning,
)
# get ax
if ax is None:
ax = plt.gca()
Expand Down Expand Up @@ -1024,14 +1069,8 @@ def set_fig_labels(
for ax in fig.axes:
if ax.is_sideplot:
continue
try:
# [row|col]span were introduced in matplotlib 3.2
# this try/except can be removed when supprot for mpl < 3.2 is dropped
rowNum = ax.get_subplotspec().rowspan.start
colNum = ax.get_subplotspec().colspan.start
except AttributeError:
rowNum = ax.rowNum
colNum = ax.colNum
rowNum = ax.get_subplotspec().rowspan.start
colNum = ax.get_subplotspec().colspan.start
if row_start <= rowNum <= row_stop and col_start <= colNum <= col_stop:
if colNum == col_start:
set_ax_labels(ax=ax, ylabel=ylabel, yticks=yticks, label_fontsize=label_fontsize)
Expand All @@ -1047,8 +1086,11 @@ def set_fig_labels(


def subplots_adjust(fig=None, inches=1):
"""Enforce margins for generated figure, starting at subplots.
.. note::
"""
Enforce margins for generated figure, starting at subplots.

Note
----
You probably should be using wt.artists.create_figure instead.

Parameters
Expand Down Expand Up @@ -1085,7 +1127,9 @@ 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):
def stitch_to_animation(
paths, outpath=None, *, duration=0.5, ignore_alpha=True, reduce=None, verbose=True, **kwargs
):
"""Stitch a series of images into an animation.

Currently supports animated gifs, other formats coming as needed.
Expand All @@ -1099,23 +1143,52 @@ def stitch_to_animation(paths, outpath=None, *, duration=0.5, palettesize=256, v
of first path in `images`. 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_transparency : bool (optional)
When True, transparency is excluded from the gif and color palette may be higher res. ignoring alpha takes longer and produces larger gifs
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 : pathlib.Path
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):
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
4 changes: 3 additions & 1 deletion WrightTools/artists/_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,9 @@ def interact2D(
# colorbar
ticks = current_state.norm.ticks
ticklabels = gen_ticklabels(ticks, channel.signed)
colorbar = plot_colorbar(cax, cmap=cmap, label=channel.natural_name, ticks=ticks)
colorbar = fig.colorbar(
mappable=obj2D, cax=cax, cmap=cmap, label=channel.natural_name, ticks=ticks
)
colorbar.set_ticklabels(ticklabels)
fig.canvas.draw_idle()

Expand Down
3 changes: 2 additions & 1 deletion WrightTools/data/_join.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
def join(
datas, *, atol=None, rtol=None, name="join", parent=None, method="first", verbose=True
) -> Data:
"""Join a list of data objects into one data object.
"""
Join a list of data objects into one data object.
The underlying dataset arrays are merged.

Joined datas must have the same axes and axes order.
Expand Down
84 changes: 46 additions & 38 deletions WrightTools/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


import pathlib
from types import SimpleNamespace

from .. import kit as wt_kit

Expand All @@ -17,8 +18,27 @@
# --- container class -----------------------------------------------------------------------------


class DatasetContainer(object):
def _from_files(self, dirname, prefix=""):
BrunoldrRaman = SimpleNamespace()
Cary = SimpleNamespace()
COLORS = SimpleNamespace()
JASCO = SimpleNamespace()
KENT = SimpleNamespace()
LabRAM = SimpleNamespace()
ocean_optics = SimpleNamespace()
PyCMDS = SimpleNamespace()
Shimadzu = SimpleNamespace()
Solis = SimpleNamespace()
spcm = SimpleNamespace()
Tensor27 = SimpleNamespace()
wt5 = SimpleNamespace()


# --- fill ----------------------------------------------------------------------------------------


def _populate_containers():

def _from_files(obj, dirname, prefix=""):
"""Add datasets from files in a directory.

Parameters
Expand All @@ -30,9 +50,9 @@ def _from_files(self, dirname, prefix=""):
"""
for p in (here / dirname).iterdir():
n = prefix + wt_kit.string2identifier(p.name.split(".")[0])
setattr(self, n, p)
setattr(obj, n, p)

def _from_directory(self, dirname, prefix=""):
def _from_directory(obj, dirname, prefix=""):
"""Add dataset from files in a directory.

Parameters
Expand All @@ -44,55 +64,40 @@ def _from_directory(self, dirname, prefix=""):
"""
ps = list((here / dirname).iterdir())
n = prefix + wt_kit.string2identifier(dirname.name)
setattr(self, n, ps)


# --- fill ----------------------------------------------------------------------------------------
setattr(obj, n, ps)

_from_files(BrunoldrRaman, here / "BrunoldrRaman")

BrunoldrRaman = DatasetContainer()
BrunoldrRaman._from_files(here / "BrunoldrRaman")
_from_files(Cary, "Cary")

Cary = DatasetContainer()
Cary._from_files("Cary")
_from_files(COLORS, here / "COLORS" / "v0.2", prefix="v0p2_")
_from_files(COLORS, here / "COLORS" / "v2.2", prefix="v2p2_")

COLORS = DatasetContainer()
COLORS._from_files(here / "COLORS" / "v0.2", prefix="v0p2_")
COLORS._from_files(here / "COLORS" / "v2.2", prefix="v2p2_")
_from_files(JASCO, "JASCO")

JASCO = DatasetContainer()
JASCO._from_files("JASCO")
_from_directory(KENT, here / "KENT" / "LDS821 TRSF")
_from_directory(KENT, here / "KENT" / "LDS821 DOVE")
_from_directory(KENT, here / "KENT" / "PbSe 2D delay B")

KENT = DatasetContainer()
KENT._from_directory(here / "KENT" / "LDS821 TRSF")
KENT._from_directory(here / "KENT" / "LDS821 DOVE")
KENT._from_directory(here / "KENT" / "PbSe 2D delay B")
_from_files(LabRAM, here / "LabRAM")

LabRAM = DatasetContainer()
LabRAM._from_files(here / "LabRAM")
_from_files(ocean_optics, "ocean_optics")

ocean_optics = DatasetContainer()
ocean_optics._from_files("ocean_optics")
_from_files(PyCMDS, "PyCMDS")

PyCMDS = DatasetContainer()
PyCMDS._from_files("PyCMDS")
_from_files(Shimadzu, "Shimadzu")

Shimadzu = DatasetContainer()
Shimadzu._from_files("Shimadzu")
_from_files(Solis, "Solis")

Solis = DatasetContainer()
Solis._from_files("Solis")
_from_files(spcm, "spcm")

spcm = DatasetContainer()
spcm._from_files("spcm")
_from_files(Tensor27, "Tensor27")

Tensor27 = DatasetContainer()
Tensor27._from_files("Tensor27")
_from_files(wt5, here / "wt5" / "v1.0.0", prefix="v1p0p0_")
_from_files(wt5, here / "wt5" / "v1.0.1", prefix="v1p0p1_")

wt5 = DatasetContainer()
wt5._from_files(here / "wt5" / "v1.0.0", prefix="v1p0p0_")
wt5._from_files(here / "wt5" / "v1.0.1", prefix="v1p0p1_")

_populate_containers()

# --- pretty namespace ----------------------------------------------------------------------------

Expand All @@ -103,9 +108,12 @@ def _from_directory(self, dirname, prefix=""):
"COLORS",
"JASCO",
"KENT",
"LabRAM",
"ocean_optics",
"PyCMDS",
"Shimadzu",
"Solis",
"spcm",
"Tensor27",
"wt5",
]
Loading
Loading