|
| 1 | +# %% [markdown] |
| 2 | +# # Create your own Xarray Backend |
| 3 | +# In this lesson, we will learn about and how to create your own custom xarray backend |
| 4 | +# |
| 5 | +# :::{admonition} Learning Goals |
| 6 | +# - Learn about xarray's support for custom backends |
| 7 | +# - Learn how to use a custom `imageio` backend to open and manipulate GIFs |
| 8 | +# - Learn how to extend the `imagegio` backend to write out a GIF |
| 9 | +# ::: |
| 10 | +# |
| 11 | +# ## Why should you create your own Xarray backend? |
| 12 | +# |
| 13 | +# - Allows you to use xarray's interface |
| 14 | +# - Attribute-like syntax, dict-like syntax and label based indexing |
| 15 | +# - You don’t need to integrate any code in Xarray |
| 16 | +# - Easy and fast! |
| 17 | +# |
| 18 | + |
| 19 | +# %% [markdown] |
| 20 | +# ## Setting up the BackendEntrypoint |
| 21 | +# |
| 22 | +# To set up a `BackendEntrypoint` we can implement a subclass of `BackendEntrypoint` and expose the `open_dataset` method to it. For this tutorial we have the `ImageIOBackend` already defined but we will extend the functionally to write GIFS. |
| 23 | + |
| 24 | +# %% |
| 25 | +image_path = "io.gif" |
| 26 | + |
| 27 | +# %% [markdown] |
| 28 | +# We can write a simple image reader function that we can then plug into our `MyBackendEntrypoint` class. For this example we are going to use `imageio` an image reader and writer library. With `imageio` you can read an image file with `iio.imopen` |
| 29 | +# |
| 30 | +# :::{note} |
| 31 | +# The `ImageIOBackend` also defines a `ImageIOBackendArray` with basic indexing. |
| 32 | +# : |
| 33 | + |
| 34 | +# %% |
| 35 | +import imageio as iio |
| 36 | +from xarray.backends import BackendEntrypoint |
| 37 | + |
| 38 | + |
| 39 | +def imageio_open( |
| 40 | + filename_or_obj, |
| 41 | +): |
| 42 | + img = iio.imopen(filename_or_obj, io_mode="r") |
| 43 | + return img.read() |
| 44 | + |
| 45 | + |
| 46 | +class MyImageReader(BackendEntrypoint): |
| 47 | + def open_dataset( |
| 48 | + self, |
| 49 | + filename_or_obj, |
| 50 | + *, |
| 51 | + drop_variables=None, |
| 52 | + ): |
| 53 | + return imageio_open(filename_or_obj) |
| 54 | + |
| 55 | + |
| 56 | +type(MyImageReader().open_dataset(image_path)) |
| 57 | + |
| 58 | +# %% [markdown] |
| 59 | +# ## Reading image data |
| 60 | +# |
| 61 | +# Lets use our `ImageIOBackend` to open a GIF. |
| 62 | +# |
| 63 | + |
| 64 | +# %% |
| 65 | +import xarray as xr |
| 66 | +from imageio_ import ImageIOBackend |
| 67 | + |
| 68 | +gif_ds = xr.open_dataset(image_path, engine=ImageIOBackend) |
| 69 | + |
| 70 | +# %% [markdown] |
| 71 | +# ### Examining our image dataset |
| 72 | +# |
| 73 | +# Since our image is a `Dataset` object we can use xarray's interface for `Dataset` objects. |
| 74 | +# |
| 75 | +# Let's try listing all of the variables, dimensions and selecting data |
| 76 | + |
| 77 | +# %% |
| 78 | + |
| 79 | +# %% [markdown] |
| 80 | +# We can list our dimensions |
| 81 | + |
| 82 | +# %% |
| 83 | + |
| 84 | +# %% [markdown] |
| 85 | +# Let's try getting our `DataArray` |
| 86 | + |
| 87 | +# %% |
| 88 | + |
| 89 | +# %% [markdown] |
| 90 | +# ## GIF metadata |
| 91 | +# We can examine, update and add metadata to our `Dataset` object. |
| 92 | +# |
| 93 | +# We can examine the attributes in our GIF "data" variable with the `.attrs` method |
| 94 | + |
| 95 | +# %% |
| 96 | + |
| 97 | +# %% [markdown] |
| 98 | +# ## Exercise |
| 99 | + |
| 100 | +# %% [markdown] |
| 101 | +# ::::{admonition} Exercise |
| 102 | +# :class: tip |
| 103 | +# |
| 104 | +# Can you add a new attribute to our GIF "data" variable called "fps". This is the frames per second we can write our GIF to. You can set it to any value. |
| 105 | +# |
| 106 | +# :::{admonition} Solution |
| 107 | +# :class: dropdown |
| 108 | +# |
| 109 | +# ```python |
| 110 | +# gif_ds.data.attrs['fps'] = 100 |
| 111 | +# ``` |
| 112 | +# ::: |
| 113 | +# :::: |
| 114 | + |
| 115 | +# %% [markdown] |
| 116 | +# ### GIF writer |
| 117 | +# We can extend our backend with an GIF writer. Here we use `matplotlib`'s animation functions and `PillowWriter` |
| 118 | + |
| 119 | +# %% |
| 120 | +import matplotlib.animation as animation |
| 121 | +import matplotlib.pyplot as plt |
| 122 | +import numpy as np |
| 123 | +from imageio_ import ImageIOBackend |
| 124 | +from matplotlib.animation import PillowWriter |
| 125 | + |
| 126 | + |
| 127 | +class SimpleImageWriter(ImageIOBackend): |
| 128 | + def to_giff( |
| 129 | + self, |
| 130 | + dataset, |
| 131 | + variable, |
| 132 | + time_dim, |
| 133 | + out_filename, |
| 134 | + **kwargs, |
| 135 | + ): |
| 136 | + fig, ax = plt.subplots() |
| 137 | + |
| 138 | + frames = [] |
| 139 | + variable_da = dataset[variable] |
| 140 | + X, Y = np.meshgrid(variable_da.height, variable_da.width) |
| 141 | + for time in variable_da[time_dim]: |
| 142 | + variable_da = variable_da.transpose("time", "width", "height", "color") |
| 143 | + to_plot = ax.pcolormesh( |
| 144 | + X, |
| 145 | + Y, |
| 146 | + variable_da.sel(time=time).isel(color=0), |
| 147 | + # animated=True, |
| 148 | + shading="auto", |
| 149 | + **kwargs, |
| 150 | + ) |
| 151 | + |
| 152 | + frames.append([to_plot]) |
| 153 | + |
| 154 | + try: |
| 155 | + writer = PillowWriter(fps=variable_da.attrs["fps"], **kwargs) |
| 156 | + except KeyError: |
| 157 | + writer = PillowWriter(fps=50, **kwargs) |
| 158 | + |
| 159 | + ani = animation.ArtistAnimation(fig, frames, blit=True, repeat=True) |
| 160 | + ani.save(filename=out_filename, writer=writer) |
| 161 | + |
| 162 | + |
| 163 | +# %% |
| 164 | +img_writer = SimpleImageWriter() |
| 165 | +img_writer.to_giff(gif_ds, "data", "time", "io_writer.gif") |
| 166 | + |
| 167 | +# %% |
0 commit comments