We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add a dynamic z axis option to the animators
So if your frames have a wide dynamic range the z scale changes

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sunpy/visualization/animator/wcs.py`
Content:
```
1 from functools import partial
2 
3 import numpy as np
4 
5 import astropy.units as u
6 from astropy.wcs.wcsapi import BaseLowLevelWCS
7 
8 from sunpy.extern import modest_image
9 from sunpy.visualization.animator.base import ArrayAnimator
10 
11 __all__ = ['ArrayAnimatorWCS']
12 
13 
14 class ArrayAnimatorWCS(ArrayAnimator):
15     """
16     Animate an array with associated `~astropy.wcs.wcsapi.BaseLowLevelWCS` object.
17 
18     The following keyboard shortcuts are defined in the viewer:
19 
20     * 'left': previous step on active slider.
21     * 'right': next step on active slider.
22     * 'top': change the active slider up one.
23     * 'bottom': change the active slider down one.
24     * 'p': play/pause active slider.
25 
26     Parameters
27     ----------
28     data: `numpy.ndarray`
29         The data to be visualized.
30     wcs: `astropy.wcs.wcsapi.BaseLowLevelWCS`
31         The world coordinate object associated with the array.
32     slices: `tuple` or `list`
33         A list specifying which axes of the array should be plotted on which
34         axes. The list should be the same length as the number of pixel
35         dimensions with ``'x'`` and (optionally) ``'y'`` in the elements
36         corresponding to the axes to be plotted. If only ``'x'`` is present a
37         line plot will be drawn. All other elements should be ``0``.
38     coord_params: `dict`, optional
39         This dict allows you to override
40         `~astropy.visualization.wcsaxes.WCSAxes` parameters for each world
41         coordinate. The keys of this dictionary should be a value which can be
42         looked up in ``WCSAxes.coords`` (i.e. ``em.wl`` or ``hpln``) and the
43         values should be a dict which supports the following keys, and passes
44         their values to the associated `~astropy.visualization.wcsaxes.WCSAxes`
45         methods.
46 
47         * ``format_unit``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_format_unit`
48         * ``major_formatter``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_major_formatter`
49         * ``axislabel``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_axislabel`
50         * ``grid``: `~astropy.visualization.wcsaxes.CoordinateHelper.grid` (The value should be a dict of keyword arguments to ``grid()`` or `True`).
51         * ``ticks``: `dict` or `bool` the keyword arguments to the
52           `~astropy.visualization.wcsaxes.CoordinateHelper.set_ticks` method,
53           or `False` to display no ticks for this coord.
54 
55     ylim: `tuple` or `str`, optional
56        The yaxis limits to use when drawing a line plot, if 'fixed' then use
57        the global data limits, if 'dynamic' then set the y limit for each frame
58        individually (meaning the y limits change as you animate).
59     ylabel: `string`, optional
60        The yaxis label to use when drawing a line plot. Setting the label on
61        the y-axis on an image plot should be done via ``coord_params``.
62 
63     """
64 
65     def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None, **kwargs):
66         if not isinstance(wcs, BaseLowLevelWCS):
67             raise ValueError("A WCS object should be provided that implements the astropy WCS API.")
68         if wcs.pixel_n_dim != data.ndim:
69             raise ValueError("Dimensionality of the data and WCS object do not match.")
70         if len(slices) != wcs.pixel_n_dim:
71             raise ValueError("slices should be the same length as the number of pixel dimensions.")
72         if "x" not in slices:
73             raise ValueError(
74                 "slices should contain at least 'x' to indicate the axis to plot on the x axis.")
75 
76         self.plot_dimensionality = 1
77 
78         image_axes = [slices[::-1].index("x")]
79         if "y" in slices:
80             image_axes.append(slices[::-1].index("y"))
81             self.plot_dimensionality = 2
82 
83         self.naxis = data.ndim
84         self.num_sliders = self.naxis - self.plot_dimensionality
85         self.slices_wcsaxes = list(slices)
86         self.wcs = wcs
87         self.coord_params = coord_params
88         self.ylim = ylim
89         self.ylabel = ylabel
90 
91         extra_slider_labels = []
92         if "slider_functions" in kwargs and "slider_labels" not in kwargs:
93             extra_slider_labels = [a.__name__ for a in kwargs['slider_functions']]
94 
95         slider_labels = self._compute_slider_labels_from_wcs(slices) + extra_slider_labels
96 
97         super().__init__(data, image_axes=image_axes, axis_ranges=None,
98                          slider_labels=slider_labels,
99                          **kwargs)
100 
101     def _get_wcs_labels(self):
102         """
103         Read first the axes names property of the wcs and fall back to physical types.
104         """
105         # Return the name if it is set, or the physical type if it is not.
106         return [l or t for l, t in zip(self.wcs.world_axis_names, self.wcs.world_axis_physical_types)]
107 
108     def _compute_slider_labels_from_wcs(self, slices):
109         """
110         For each pixel dimension, not used in the plot, calculate the world
111         names which are correlated with that pixel dimension. This can return
112         more than one world name per pixel dimension (i.e. lat & lon) so join
113         them if there are.
114         """
115         labels = []
116         wal = np.array(self._get_wcs_labels())
117         pixel_indicies = np.array([a not in ['x', 'y'] for a in slices])
118         for sliced_axis in self.wcs.axis_correlation_matrix[:, pixel_indicies].T:
119             labels.append(" / ".join(list(map(str, wal[sliced_axis]))))
120 
121         return labels[::-1]
122 
123     def _partial_pixel_to_world(self, pixel_dimension, pixel_coord):
124         """
125         Return the world coordinate along one axis, if it is only
126         correlated to that axis.
127         """
128         wcs_dimension = self.wcs.pixel_n_dim - pixel_dimension - 1
129         corr = self.wcs.axis_correlation_matrix[:, wcs_dimension]
130 
131         # If more than one world axis is linked to this dimension we can't
132         # display the world coordinate because we have no way of picking,
133         # so we just display pixel index.
134         if len(np.nonzero(corr)[0]) != 1:
135             return pixel_coord * u.pix
136 
137         # We know that the coordinate we care about is independent of the
138         # other axes, so we can set the pixel coordinates to 0.
139         coords = [0] * self.wcs.pixel_n_dim
140         coords[wcs_dimension] = pixel_coord
141         wc = self.wcs.pixel_to_world_values(*coords)[wcs_dimension]
142         return u.Quantity(wc, unit=self.wcs.world_axis_units[wcs_dimension])
143 
144     def _sanitize_axis_ranges(self, *args):
145         """
146         This overrides the behaviour of ArrayAnimator to generate axis_ranges
147         based on the WCS.
148         """
149 
150         axis_ranges = [None] * self.wcs.pixel_n_dim
151         for i in self.slider_axes:
152             axis_ranges[i] = partial(self._partial_pixel_to_world, i)
153 
154         return axis_ranges, None
155 
156     def _apply_coord_params(self, axes):
157         if self.coord_params is None:
158             return
159 
160         for coord_name in self.coord_params:
161             coord = axes.coords[coord_name]
162             params = self.coord_params[coord_name]
163 
164             format_unit = params.get("format_unit", None)
165             if format_unit:
166                 coord.set_format_unit(format_unit)
167 
168             major_formatter = params.get("major_formatter", None)
169             if major_formatter:
170                 coord.set_major_formatter(major_formatter)
171 
172             axislabel = params.get("axislabel", None)
173             if axislabel:
174                 coord.set_axislabel(axislabel)
175 
176             grid = params.get("grid", None)
177             if grid is not None:
178                 if not isinstance(grid, dict):
179                     grid = {}
180                 coord.grid(**grid)
181 
182             ticks = params.get("ticks", None)
183             if ticks is not None:
184                 if isinstance(ticks, bool):
185                     coord.set_ticks_visible(ticks)
186                     coord.set_ticklabel_visible(ticks)
187                 elif isinstance(ticks, dict):
188                     coord.set_ticks(**ticks)
189                 else:
190                     raise TypeError(
191                         "The 'ticks' value in the coord_params dictionary must be a dict or a boolean."
192                     )
193 
194     def _setup_main_axes(self):
195         self.axes = self.fig.add_axes([0.1, 0.1, 0.8, 0.8], projection=self.wcs,
196                                       slices=self.slices_wcsaxes)
197         self._apply_coord_params(self.axes)
198 
199     def plot_start_image(self, ax):
200         if self.plot_dimensionality == 1:
201             artist = self.plot_start_image_1d(ax)
202 
203         elif self.plot_dimensionality == 2:
204             artist = self.plot_start_image_2d(ax)
205 
206         return artist
207 
208     def update_plot(self, val, artist, slider):
209         """
210         Update the plot when a slider changes.
211 
212         This method both updates the state of the Animator and also re-draws
213         the matplotlib artist.
214         """
215         ind = int(val)
216         if ind == int(slider.cval):
217             return
218         ax_ind = self.slider_axes[slider.slider_ind]
219         self.frame_slice[ax_ind] = ind
220         self.slices_wcsaxes[self.wcs.pixel_n_dim - ax_ind - 1] = ind
221 
222         if self.plot_dimensionality == 1:
223             self.update_plot_1d(val, artist, slider)
224         elif self.plot_dimensionality == 2:
225             self.update_plot_2d(val, artist, slider)
226 
227         self._apply_coord_params(self.axes)
228         return super().update_plot(val, artist, slider)
229 
230     def plot_start_image_1d(self, ax):
231         """
232         Set up a line plot.
233 
234         When plotting with WCSAxes, we always plot against pixel coordinate.
235         """
236         if self.ylim != 'dynamic':
237             ylim = self.ylim
238             if ylim == 'fixed':
239                 ylim = (self.data.min(), self.data.max())
240             ax.set_ylim(ylim)
241 
242         if self.ylabel:
243             ax.set_ylabel(self.ylabel)
244 
245         ydata = self.data[self.frame_index]
246         line, = ax.plot(ydata, **self.imshow_kwargs)
247 
248         if isinstance(self.data, np.ma.MaskedArray):
249             ax.set_xlim((0, ydata.shape[0]))
250 
251         return line
252 
253     @property
254     def data_transposed(self):
255         """
256         Return data for 2D plotting, transposed if needed.
257         """
258         if self.slices_wcsaxes.index('y') < self.slices_wcsaxes.index("x"):
259             return self.data[self.frame_index].transpose()
260         else:
261             return self.data[self.frame_index]
262 
263     def update_plot_1d(self, val, line, slider):
264         """
265         Update the line plot.
266         """
267         self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)
268         line.set_ydata(self.data[self.frame_index])
269 
270         # If we are not setting ylim globally then we set it per frame.
271         if self.ylim == 'dynamic':
272             self.axes.set_ylim(self.data[self.frame_index].min(),
273                                self.data[self.frame_index].max())
274         slider.cval = val
275 
276     def plot_start_image_2d(self, ax):
277         """
278         Setup an image plot.
279         """
280         imshow_args = {'interpolation': 'nearest',
281                        'origin': 'lower'}
282         imshow_args.update(self.imshow_kwargs)
283 
284         im = modest_image.imshow(ax, self.data_transposed, **imshow_args)
285 
286         if 'extent' in imshow_args:
287             ax.set_xlim(imshow_args['extent'][:2])
288             ax.set_ylim(imshow_args['extent'][2:])
289         else:
290             ny, nx = self.data_transposed.shape
291             ax.set_xlim(-0.5, nx - 0.5)
292             ax.set_ylim(-0.5, ny - 0.5)
293 
294         ax.dataLim.intervalx = ax.get_xlim()
295         ax.dataLim.intervaly = ax.get_ylim()
296 
297         if self.if_colorbar:
298             self._add_colorbar(im)
299 
300         return im
301 
302     def update_plot_2d(self, val, im, slider):
303         """
304         Update the image plot.
305         """
306         self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)
307         im.set_array(self.data_transposed)
308         slider.cval = val
309
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sunpy/visualization/animator/wcs.py b/sunpy/visualization/animator/wcs.py
--- a/sunpy/visualization/animator/wcs.py
+++ b/sunpy/visualization/animator/wcs.py
@@ -3,6 +3,7 @@
 import numpy as np
 
 import astropy.units as u
+from astropy.visualization import AsymmetricPercentileInterval
 from astropy.wcs.wcsapi import BaseLowLevelWCS
 
 from sunpy.extern import modest_image
@@ -59,10 +60,12 @@
     ylabel: `string`, optional
        The yaxis label to use when drawing a line plot. Setting the label on
        the y-axis on an image plot should be done via ``coord_params``.
-
+    clip_interval : two-element `~astropy.units.Quantity`, optional
+        If provided, the data for each step will be clipped to the percentile interval bounded by the two numbers.
     """
 
-    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None, **kwargs):
+    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None,
+                 clip_interval: u.percent = None, **kwargs):
         if not isinstance(wcs, BaseLowLevelWCS):
             raise ValueError("A WCS object should be provided that implements the astropy WCS API.")
         if wcs.pixel_n_dim != data.ndim:
@@ -88,6 +91,11 @@
         self.ylim = ylim
         self.ylabel = ylabel
 
+        if clip_interval is not None and len(clip_interval) != 2:
+            raise ValueError('A range of 2 values must be specified for clip_interval.')
+
+        self.clip_interval = clip_interval
+
         extra_slider_labels = []
         if "slider_functions" in kwargs and "slider_labels" not in kwargs:
             extra_slider_labels = [a.__name__ for a in kwargs['slider_functions']]
@@ -281,6 +289,9 @@
                        'origin': 'lower'}
         imshow_args.update(self.imshow_kwargs)
 
+        if self.clip_interval is not None:
+            imshow_args['vmin'], imshow_args['vmax'] = self._get_2d_plot_limits()
+
         im = modest_image.imshow(ax, self.data_transposed, **imshow_args)
 
         if 'extent' in imshow_args:
@@ -299,10 +310,23 @@
 
         return im
 
+    def _get_2d_plot_limits(self):
+        """
+        Get vmin, vmax of a data slice when clip_interval is specified.
+        """
+        percent_limits = self.clip_interval.to('%').value
+        vmin, vmax = AsymmetricPercentileInterval(*percent_limits).get_limits(self.data_transposed)
+        return vmin, vmax
+
     def update_plot_2d(self, val, im, slider):
         """
         Update the image plot.
         """
         self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)
         im.set_array(self.data_transposed)
+
+        if self.clip_interval is not None:
+            vmin, vmax = self._get_2d_plot_limits()
+            im.set_clim(vmin, vmax)
+
         slider.cval = val

{"golden_diff": "diff --git a/sunpy/visualization/animator/wcs.py b/sunpy/visualization/animator/wcs.py\n--- a/sunpy/visualization/animator/wcs.py\n+++ b/sunpy/visualization/animator/wcs.py\n@@ -3,6 +3,7 @@\n import numpy as np\n \n import astropy.units as u\n+from astropy.visualization import AsymmetricPercentileInterval\n from astropy.wcs.wcsapi import BaseLowLevelWCS\n \n from sunpy.extern import modest_image\n@@ -59,10 +60,12 @@\n     ylabel: `string`, optional\n        The yaxis label to use when drawing a line plot. Setting the label on\n        the y-axis on an image plot should be done via ``coord_params``.\n-\n+    clip_interval : two-element `~astropy.units.Quantity`, optional\n+        If provided, the data for each step will be clipped to the percentile interval bounded by the two numbers.\n     \"\"\"\n \n-    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None, **kwargs):\n+    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None,\n+                 clip_interval: u.percent = None, **kwargs):\n         if not isinstance(wcs, BaseLowLevelWCS):\n             raise ValueError(\"A WCS object should be provided that implements the astropy WCS API.\")\n         if wcs.pixel_n_dim != data.ndim:\n@@ -88,6 +91,11 @@\n         self.ylim = ylim\n         self.ylabel = ylabel\n \n+        if clip_interval is not None and len(clip_interval) != 2:\n+            raise ValueError('A range of 2 values must be specified for clip_interval.')\n+\n+        self.clip_interval = clip_interval\n+\n         extra_slider_labels = []\n         if \"slider_functions\" in kwargs and \"slider_labels\" not in kwargs:\n             extra_slider_labels = [a.__name__ for a in kwargs['slider_functions']]\n@@ -281,6 +289,9 @@\n                        'origin': 'lower'}\n         imshow_args.update(self.imshow_kwargs)\n \n+        if self.clip_interval is not None:\n+            imshow_args['vmin'], imshow_args['vmax'] = self._get_2d_plot_limits()\n+\n         im = modest_image.imshow(ax, self.data_transposed, **imshow_args)\n \n         if 'extent' in imshow_args:\n@@ -299,10 +310,23 @@\n \n         return im\n \n+    def _get_2d_plot_limits(self):\n+        \"\"\"\n+        Get vmin, vmax of a data slice when clip_interval is specified.\n+        \"\"\"\n+        percent_limits = self.clip_interval.to('%').value\n+        vmin, vmax = AsymmetricPercentileInterval(*percent_limits).get_limits(self.data_transposed)\n+        return vmin, vmax\n+\n     def update_plot_2d(self, val, im, slider):\n         \"\"\"\n         Update the image plot.\n         \"\"\"\n         self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)\n         im.set_array(self.data_transposed)\n+\n+        if self.clip_interval is not None:\n+            vmin, vmax = self._get_2d_plot_limits()\n+            im.set_clim(vmin, vmax)\n+\n         slider.cval = val\n", "issue": "Add a dynamic z axis option to the animators\nSo if your frames have a wide dynamic range the z scale changes\n", "before_files": [{"content": "from functools import partial\n\nimport numpy as np\n\nimport astropy.units as u\nfrom astropy.wcs.wcsapi import BaseLowLevelWCS\n\nfrom sunpy.extern import modest_image\nfrom sunpy.visualization.animator.base import ArrayAnimator\n\n__all__ = ['ArrayAnimatorWCS']\n\n\nclass ArrayAnimatorWCS(ArrayAnimator):\n    \"\"\"\n    Animate an array with associated `~astropy.wcs.wcsapi.BaseLowLevelWCS` object.\n\n    The following keyboard shortcuts are defined in the viewer:\n\n    * 'left': previous step on active slider.\n    * 'right': next step on active slider.\n    * 'top': change the active slider up one.\n    * 'bottom': change the active slider down one.\n    * 'p': play/pause active slider.\n\n    Parameters\n    ----------\n    data: `numpy.ndarray`\n        The data to be visualized.\n    wcs: `astropy.wcs.wcsapi.BaseLowLevelWCS`\n        The world coordinate object associated with the array.\n    slices: `tuple` or `list`\n        A list specifying which axes of the array should be plotted on which\n        axes. The list should be the same length as the number of pixel\n        dimensions with ``'x'`` and (optionally) ``'y'`` in the elements\n        corresponding to the axes to be plotted. If only ``'x'`` is present a\n        line plot will be drawn. All other elements should be ``0``.\n    coord_params: `dict`, optional\n        This dict allows you to override\n        `~astropy.visualization.wcsaxes.WCSAxes` parameters for each world\n        coordinate. The keys of this dictionary should be a value which can be\n        looked up in ``WCSAxes.coords`` (i.e. ``em.wl`` or ``hpln``) and the\n        values should be a dict which supports the following keys, and passes\n        their values to the associated `~astropy.visualization.wcsaxes.WCSAxes`\n        methods.\n\n        * ``format_unit``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_format_unit`\n        * ``major_formatter``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_major_formatter`\n        * ``axislabel``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_axislabel`\n        * ``grid``: `~astropy.visualization.wcsaxes.CoordinateHelper.grid` (The value should be a dict of keyword arguments to ``grid()`` or `True`).\n        * ``ticks``: `dict` or `bool` the keyword arguments to the\n          `~astropy.visualization.wcsaxes.CoordinateHelper.set_ticks` method,\n          or `False` to display no ticks for this coord.\n\n    ylim: `tuple` or `str`, optional\n       The yaxis limits to use when drawing a line plot, if 'fixed' then use\n       the global data limits, if 'dynamic' then set the y limit for each frame\n       individually (meaning the y limits change as you animate).\n    ylabel: `string`, optional\n       The yaxis label to use when drawing a line plot. Setting the label on\n       the y-axis on an image plot should be done via ``coord_params``.\n\n    \"\"\"\n\n    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None, **kwargs):\n        if not isinstance(wcs, BaseLowLevelWCS):\n            raise ValueError(\"A WCS object should be provided that implements the astropy WCS API.\")\n        if wcs.pixel_n_dim != data.ndim:\n            raise ValueError(\"Dimensionality of the data and WCS object do not match.\")\n        if len(slices) != wcs.pixel_n_dim:\n            raise ValueError(\"slices should be the same length as the number of pixel dimensions.\")\n        if \"x\" not in slices:\n            raise ValueError(\n                \"slices should contain at least 'x' to indicate the axis to plot on the x axis.\")\n\n        self.plot_dimensionality = 1\n\n        image_axes = [slices[::-1].index(\"x\")]\n        if \"y\" in slices:\n            image_axes.append(slices[::-1].index(\"y\"))\n            self.plot_dimensionality = 2\n\n        self.naxis = data.ndim\n        self.num_sliders = self.naxis - self.plot_dimensionality\n        self.slices_wcsaxes = list(slices)\n        self.wcs = wcs\n        self.coord_params = coord_params\n        self.ylim = ylim\n        self.ylabel = ylabel\n\n        extra_slider_labels = []\n        if \"slider_functions\" in kwargs and \"slider_labels\" not in kwargs:\n            extra_slider_labels = [a.__name__ for a in kwargs['slider_functions']]\n\n        slider_labels = self._compute_slider_labels_from_wcs(slices) + extra_slider_labels\n\n        super().__init__(data, image_axes=image_axes, axis_ranges=None,\n                         slider_labels=slider_labels,\n                         **kwargs)\n\n    def _get_wcs_labels(self):\n        \"\"\"\n        Read first the axes names property of the wcs and fall back to physical types.\n        \"\"\"\n        # Return the name if it is set, or the physical type if it is not.\n        return [l or t for l, t in zip(self.wcs.world_axis_names, self.wcs.world_axis_physical_types)]\n\n    def _compute_slider_labels_from_wcs(self, slices):\n        \"\"\"\n        For each pixel dimension, not used in the plot, calculate the world\n        names which are correlated with that pixel dimension. This can return\n        more than one world name per pixel dimension (i.e. lat & lon) so join\n        them if there are.\n        \"\"\"\n        labels = []\n        wal = np.array(self._get_wcs_labels())\n        pixel_indicies = np.array([a not in ['x', 'y'] for a in slices])\n        for sliced_axis in self.wcs.axis_correlation_matrix[:, pixel_indicies].T:\n            labels.append(\" / \".join(list(map(str, wal[sliced_axis]))))\n\n        return labels[::-1]\n\n    def _partial_pixel_to_world(self, pixel_dimension, pixel_coord):\n        \"\"\"\n        Return the world coordinate along one axis, if it is only\n        correlated to that axis.\n        \"\"\"\n        wcs_dimension = self.wcs.pixel_n_dim - pixel_dimension - 1\n        corr = self.wcs.axis_correlation_matrix[:, wcs_dimension]\n\n        # If more than one world axis is linked to this dimension we can't\n        # display the world coordinate because we have no way of picking,\n        # so we just display pixel index.\n        if len(np.nonzero(corr)[0]) != 1:\n            return pixel_coord * u.pix\n\n        # We know that the coordinate we care about is independent of the\n        # other axes, so we can set the pixel coordinates to 0.\n        coords = [0] * self.wcs.pixel_n_dim\n        coords[wcs_dimension] = pixel_coord\n        wc = self.wcs.pixel_to_world_values(*coords)[wcs_dimension]\n        return u.Quantity(wc, unit=self.wcs.world_axis_units[wcs_dimension])\n\n    def _sanitize_axis_ranges(self, *args):\n        \"\"\"\n        This overrides the behaviour of ArrayAnimator to generate axis_ranges\n        based on the WCS.\n        \"\"\"\n\n        axis_ranges = [None] * self.wcs.pixel_n_dim\n        for i in self.slider_axes:\n            axis_ranges[i] = partial(self._partial_pixel_to_world, i)\n\n        return axis_ranges, None\n\n    def _apply_coord_params(self, axes):\n        if self.coord_params is None:\n            return\n\n        for coord_name in self.coord_params:\n            coord = axes.coords[coord_name]\n            params = self.coord_params[coord_name]\n\n            format_unit = params.get(\"format_unit\", None)\n            if format_unit:\n                coord.set_format_unit(format_unit)\n\n            major_formatter = params.get(\"major_formatter\", None)\n            if major_formatter:\n                coord.set_major_formatter(major_formatter)\n\n            axislabel = params.get(\"axislabel\", None)\n            if axislabel:\n                coord.set_axislabel(axislabel)\n\n            grid = params.get(\"grid\", None)\n            if grid is not None:\n                if not isinstance(grid, dict):\n                    grid = {}\n                coord.grid(**grid)\n\n            ticks = params.get(\"ticks\", None)\n            if ticks is not None:\n                if isinstance(ticks, bool):\n                    coord.set_ticks_visible(ticks)\n                    coord.set_ticklabel_visible(ticks)\n                elif isinstance(ticks, dict):\n                    coord.set_ticks(**ticks)\n                else:\n                    raise TypeError(\n                        \"The 'ticks' value in the coord_params dictionary must be a dict or a boolean.\"\n                    )\n\n    def _setup_main_axes(self):\n        self.axes = self.fig.add_axes([0.1, 0.1, 0.8, 0.8], projection=self.wcs,\n                                      slices=self.slices_wcsaxes)\n        self._apply_coord_params(self.axes)\n\n    def plot_start_image(self, ax):\n        if self.plot_dimensionality == 1:\n            artist = self.plot_start_image_1d(ax)\n\n        elif self.plot_dimensionality == 2:\n            artist = self.plot_start_image_2d(ax)\n\n        return artist\n\n    def update_plot(self, val, artist, slider):\n        \"\"\"\n        Update the plot when a slider changes.\n\n        This method both updates the state of the Animator and also re-draws\n        the matplotlib artist.\n        \"\"\"\n        ind = int(val)\n        if ind == int(slider.cval):\n            return\n        ax_ind = self.slider_axes[slider.slider_ind]\n        self.frame_slice[ax_ind] = ind\n        self.slices_wcsaxes[self.wcs.pixel_n_dim - ax_ind - 1] = ind\n\n        if self.plot_dimensionality == 1:\n            self.update_plot_1d(val, artist, slider)\n        elif self.plot_dimensionality == 2:\n            self.update_plot_2d(val, artist, slider)\n\n        self._apply_coord_params(self.axes)\n        return super().update_plot(val, artist, slider)\n\n    def plot_start_image_1d(self, ax):\n        \"\"\"\n        Set up a line plot.\n\n        When plotting with WCSAxes, we always plot against pixel coordinate.\n        \"\"\"\n        if self.ylim != 'dynamic':\n            ylim = self.ylim\n            if ylim == 'fixed':\n                ylim = (self.data.min(), self.data.max())\n            ax.set_ylim(ylim)\n\n        if self.ylabel:\n            ax.set_ylabel(self.ylabel)\n\n        ydata = self.data[self.frame_index]\n        line, = ax.plot(ydata, **self.imshow_kwargs)\n\n        if isinstance(self.data, np.ma.MaskedArray):\n            ax.set_xlim((0, ydata.shape[0]))\n\n        return line\n\n    @property\n    def data_transposed(self):\n        \"\"\"\n        Return data for 2D plotting, transposed if needed.\n        \"\"\"\n        if self.slices_wcsaxes.index('y') < self.slices_wcsaxes.index(\"x\"):\n            return self.data[self.frame_index].transpose()\n        else:\n            return self.data[self.frame_index]\n\n    def update_plot_1d(self, val, line, slider):\n        \"\"\"\n        Update the line plot.\n        \"\"\"\n        self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)\n        line.set_ydata(self.data[self.frame_index])\n\n        # If we are not setting ylim globally then we set it per frame.\n        if self.ylim == 'dynamic':\n            self.axes.set_ylim(self.data[self.frame_index].min(),\n                               self.data[self.frame_index].max())\n        slider.cval = val\n\n    def plot_start_image_2d(self, ax):\n        \"\"\"\n        Setup an image plot.\n        \"\"\"\n        imshow_args = {'interpolation': 'nearest',\n                       'origin': 'lower'}\n        imshow_args.update(self.imshow_kwargs)\n\n        im = modest_image.imshow(ax, self.data_transposed, **imshow_args)\n\n        if 'extent' in imshow_args:\n            ax.set_xlim(imshow_args['extent'][:2])\n            ax.set_ylim(imshow_args['extent'][2:])\n        else:\n            ny, nx = self.data_transposed.shape\n            ax.set_xlim(-0.5, nx - 0.5)\n            ax.set_ylim(-0.5, ny - 0.5)\n\n        ax.dataLim.intervalx = ax.get_xlim()\n        ax.dataLim.intervaly = ax.get_ylim()\n\n        if self.if_colorbar:\n            self._add_colorbar(im)\n\n        return im\n\n    def update_plot_2d(self, val, im, slider):\n        \"\"\"\n        Update the image plot.\n        \"\"\"\n        self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)\n        im.set_array(self.data_transposed)\n        slider.cval = val\n", "path": "sunpy/visualization/animator/wcs.py"}], "after_files": [{"content": "from functools import partial\n\nimport numpy as np\n\nimport astropy.units as u\nfrom astropy.visualization import AsymmetricPercentileInterval\nfrom astropy.wcs.wcsapi import BaseLowLevelWCS\n\nfrom sunpy.extern import modest_image\nfrom sunpy.visualization.animator.base import ArrayAnimator\n\n__all__ = ['ArrayAnimatorWCS']\n\n\nclass ArrayAnimatorWCS(ArrayAnimator):\n    \"\"\"\n    Animate an array with associated `~astropy.wcs.wcsapi.BaseLowLevelWCS` object.\n\n    The following keyboard shortcuts are defined in the viewer:\n\n    * 'left': previous step on active slider.\n    * 'right': next step on active slider.\n    * 'top': change the active slider up one.\n    * 'bottom': change the active slider down one.\n    * 'p': play/pause active slider.\n\n    Parameters\n    ----------\n    data: `numpy.ndarray`\n        The data to be visualized.\n    wcs: `astropy.wcs.wcsapi.BaseLowLevelWCS`\n        The world coordinate object associated with the array.\n    slices: `tuple` or `list`\n        A list specifying which axes of the array should be plotted on which\n        axes. The list should be the same length as the number of pixel\n        dimensions with ``'x'`` and (optionally) ``'y'`` in the elements\n        corresponding to the axes to be plotted. If only ``'x'`` is present a\n        line plot will be drawn. All other elements should be ``0``.\n    coord_params: `dict`, optional\n        This dict allows you to override\n        `~astropy.visualization.wcsaxes.WCSAxes` parameters for each world\n        coordinate. The keys of this dictionary should be a value which can be\n        looked up in ``WCSAxes.coords`` (i.e. ``em.wl`` or ``hpln``) and the\n        values should be a dict which supports the following keys, and passes\n        their values to the associated `~astropy.visualization.wcsaxes.WCSAxes`\n        methods.\n\n        * ``format_unit``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_format_unit`\n        * ``major_formatter``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_major_formatter`\n        * ``axislabel``: `~astropy.visualization.wcsaxes.CoordinateHelper.set_axislabel`\n        * ``grid``: `~astropy.visualization.wcsaxes.CoordinateHelper.grid` (The value should be a dict of keyword arguments to ``grid()`` or `True`).\n        * ``ticks``: `dict` or `bool` the keyword arguments to the\n          `~astropy.visualization.wcsaxes.CoordinateHelper.set_ticks` method,\n          or `False` to display no ticks for this coord.\n\n    ylim: `tuple` or `str`, optional\n       The yaxis limits to use when drawing a line plot, if 'fixed' then use\n       the global data limits, if 'dynamic' then set the y limit for each frame\n       individually (meaning the y limits change as you animate).\n    ylabel: `string`, optional\n       The yaxis label to use when drawing a line plot. Setting the label on\n       the y-axis on an image plot should be done via ``coord_params``.\n    clip_interval : two-element `~astropy.units.Quantity`, optional\n        If provided, the data for each step will be clipped to the percentile interval bounded by the two numbers.\n    \"\"\"\n\n    def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=None,\n                 clip_interval: u.percent = None, **kwargs):\n        if not isinstance(wcs, BaseLowLevelWCS):\n            raise ValueError(\"A WCS object should be provided that implements the astropy WCS API.\")\n        if wcs.pixel_n_dim != data.ndim:\n            raise ValueError(\"Dimensionality of the data and WCS object do not match.\")\n        if len(slices) != wcs.pixel_n_dim:\n            raise ValueError(\"slices should be the same length as the number of pixel dimensions.\")\n        if \"x\" not in slices:\n            raise ValueError(\n                \"slices should contain at least 'x' to indicate the axis to plot on the x axis.\")\n\n        self.plot_dimensionality = 1\n\n        image_axes = [slices[::-1].index(\"x\")]\n        if \"y\" in slices:\n            image_axes.append(slices[::-1].index(\"y\"))\n            self.plot_dimensionality = 2\n\n        self.naxis = data.ndim\n        self.num_sliders = self.naxis - self.plot_dimensionality\n        self.slices_wcsaxes = list(slices)\n        self.wcs = wcs\n        self.coord_params = coord_params\n        self.ylim = ylim\n        self.ylabel = ylabel\n\n        if clip_interval is not None and len(clip_interval) != 2:\n            raise ValueError('A range of 2 values must be specified for clip_interval.')\n\n        self.clip_interval = clip_interval\n\n        extra_slider_labels = []\n        if \"slider_functions\" in kwargs and \"slider_labels\" not in kwargs:\n            extra_slider_labels = [a.__name__ for a in kwargs['slider_functions']]\n\n        slider_labels = self._compute_slider_labels_from_wcs(slices) + extra_slider_labels\n\n        super().__init__(data, image_axes=image_axes, axis_ranges=None,\n                         slider_labels=slider_labels,\n                         **kwargs)\n\n    def _get_wcs_labels(self):\n        \"\"\"\n        Read first the axes names property of the wcs and fall back to physical types.\n        \"\"\"\n        # Return the name if it is set, or the physical type if it is not.\n        return [l or t for l, t in zip(self.wcs.world_axis_names, self.wcs.world_axis_physical_types)]\n\n    def _compute_slider_labels_from_wcs(self, slices):\n        \"\"\"\n        For each pixel dimension, not used in the plot, calculate the world\n        names which are correlated with that pixel dimension. This can return\n        more than one world name per pixel dimension (i.e. lat & lon) so join\n        them if there are.\n        \"\"\"\n        labels = []\n        wal = np.array(self._get_wcs_labels())\n        pixel_indicies = np.array([a not in ['x', 'y'] for a in slices])\n        for sliced_axis in self.wcs.axis_correlation_matrix[:, pixel_indicies].T:\n            labels.append(\" / \".join(list(map(str, wal[sliced_axis]))))\n\n        return labels[::-1]\n\n    def _partial_pixel_to_world(self, pixel_dimension, pixel_coord):\n        \"\"\"\n        Return the world coordinate along one axis, if it is only\n        correlated to that axis.\n        \"\"\"\n        wcs_dimension = self.wcs.pixel_n_dim - pixel_dimension - 1\n        corr = self.wcs.axis_correlation_matrix[:, wcs_dimension]\n\n        # If more than one world axis is linked to this dimension we can't\n        # display the world coordinate because we have no way of picking,\n        # so we just display pixel index.\n        if len(np.nonzero(corr)[0]) != 1:\n            return pixel_coord * u.pix\n\n        # We know that the coordinate we care about is independent of the\n        # other axes, so we can set the pixel coordinates to 0.\n        coords = [0] * self.wcs.pixel_n_dim\n        coords[wcs_dimension] = pixel_coord\n        wc = self.wcs.pixel_to_world_values(*coords)[wcs_dimension]\n        return u.Quantity(wc, unit=self.wcs.world_axis_units[wcs_dimension])\n\n    def _sanitize_axis_ranges(self, *args):\n        \"\"\"\n        This overrides the behaviour of ArrayAnimator to generate axis_ranges\n        based on the WCS.\n        \"\"\"\n\n        axis_ranges = [None] * self.wcs.pixel_n_dim\n        for i in self.slider_axes:\n            axis_ranges[i] = partial(self._partial_pixel_to_world, i)\n\n        return axis_ranges, None\n\n    def _apply_coord_params(self, axes):\n        if self.coord_params is None:\n            return\n\n        for coord_name in self.coord_params:\n            coord = axes.coords[coord_name]\n            params = self.coord_params[coord_name]\n\n            format_unit = params.get(\"format_unit\", None)\n            if format_unit:\n                coord.set_format_unit(format_unit)\n\n            major_formatter = params.get(\"major_formatter\", None)\n            if major_formatter:\n                coord.set_major_formatter(major_formatter)\n\n            axislabel = params.get(\"axislabel\", None)\n            if axislabel:\n                coord.set_axislabel(axislabel)\n\n            grid = params.get(\"grid\", None)\n            if grid is not None:\n                if not isinstance(grid, dict):\n                    grid = {}\n                coord.grid(**grid)\n\n            ticks = params.get(\"ticks\", None)\n            if ticks is not None:\n                if isinstance(ticks, bool):\n                    coord.set_ticks_visible(ticks)\n                    coord.set_ticklabel_visible(ticks)\n                elif isinstance(ticks, dict):\n                    coord.set_ticks(**ticks)\n                else:\n                    raise TypeError(\n                        \"The 'ticks' value in the coord_params dictionary must be a dict or a boolean.\"\n                    )\n\n    def _setup_main_axes(self):\n        self.axes = self.fig.add_axes([0.1, 0.1, 0.8, 0.8], projection=self.wcs,\n                                      slices=self.slices_wcsaxes)\n        self._apply_coord_params(self.axes)\n\n    def plot_start_image(self, ax):\n        if self.plot_dimensionality == 1:\n            artist = self.plot_start_image_1d(ax)\n\n        elif self.plot_dimensionality == 2:\n            artist = self.plot_start_image_2d(ax)\n\n        return artist\n\n    def update_plot(self, val, artist, slider):\n        \"\"\"\n        Update the plot when a slider changes.\n\n        This method both updates the state of the Animator and also re-draws\n        the matplotlib artist.\n        \"\"\"\n        ind = int(val)\n        if ind == int(slider.cval):\n            return\n        ax_ind = self.slider_axes[slider.slider_ind]\n        self.frame_slice[ax_ind] = ind\n        self.slices_wcsaxes[self.wcs.pixel_n_dim - ax_ind - 1] = ind\n\n        if self.plot_dimensionality == 1:\n            self.update_plot_1d(val, artist, slider)\n        elif self.plot_dimensionality == 2:\n            self.update_plot_2d(val, artist, slider)\n\n        self._apply_coord_params(self.axes)\n        return super().update_plot(val, artist, slider)\n\n    def plot_start_image_1d(self, ax):\n        \"\"\"\n        Set up a line plot.\n\n        When plotting with WCSAxes, we always plot against pixel coordinate.\n        \"\"\"\n        if self.ylim != 'dynamic':\n            ylim = self.ylim\n            if ylim == 'fixed':\n                ylim = (self.data.min(), self.data.max())\n            ax.set_ylim(ylim)\n\n        if self.ylabel:\n            ax.set_ylabel(self.ylabel)\n\n        ydata = self.data[self.frame_index]\n        line, = ax.plot(ydata, **self.imshow_kwargs)\n\n        if isinstance(self.data, np.ma.MaskedArray):\n            ax.set_xlim((0, ydata.shape[0]))\n\n        return line\n\n    @property\n    def data_transposed(self):\n        \"\"\"\n        Return data for 2D plotting, transposed if needed.\n        \"\"\"\n        if self.slices_wcsaxes.index('y') < self.slices_wcsaxes.index(\"x\"):\n            return self.data[self.frame_index].transpose()\n        else:\n            return self.data[self.frame_index]\n\n    def update_plot_1d(self, val, line, slider):\n        \"\"\"\n        Update the line plot.\n        \"\"\"\n        self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)\n        line.set_ydata(self.data[self.frame_index])\n\n        # If we are not setting ylim globally then we set it per frame.\n        if self.ylim == 'dynamic':\n            self.axes.set_ylim(self.data[self.frame_index].min(),\n                               self.data[self.frame_index].max())\n        slider.cval = val\n\n    def plot_start_image_2d(self, ax):\n        \"\"\"\n        Setup an image plot.\n        \"\"\"\n        imshow_args = {'interpolation': 'nearest',\n                       'origin': 'lower'}\n        imshow_args.update(self.imshow_kwargs)\n\n        if self.clip_interval is not None:\n            imshow_args['vmin'], imshow_args['vmax'] = self._get_2d_plot_limits()\n\n        im = modest_image.imshow(ax, self.data_transposed, **imshow_args)\n\n        if 'extent' in imshow_args:\n            ax.set_xlim(imshow_args['extent'][:2])\n            ax.set_ylim(imshow_args['extent'][2:])\n        else:\n            ny, nx = self.data_transposed.shape\n            ax.set_xlim(-0.5, nx - 0.5)\n            ax.set_ylim(-0.5, ny - 0.5)\n\n        ax.dataLim.intervalx = ax.get_xlim()\n        ax.dataLim.intervaly = ax.get_ylim()\n\n        if self.if_colorbar:\n            self._add_colorbar(im)\n\n        return im\n\n    def _get_2d_plot_limits(self):\n        \"\"\"\n        Get vmin, vmax of a data slice when clip_interval is specified.\n        \"\"\"\n        percent_limits = self.clip_interval.to('%').value\n        vmin, vmax = AsymmetricPercentileInterval(*percent_limits).get_limits(self.data_transposed)\n        return vmin, vmax\n\n    def update_plot_2d(self, val, im, slider):\n        \"\"\"\n        Update the image plot.\n        \"\"\"\n        self.axes.reset_wcs(wcs=self.wcs, slices=self.slices_wcsaxes)\n        im.set_array(self.data_transposed)\n\n        if self.clip_interval is not None:\n            vmin, vmax = self._get_2d_plot_limits()\n            im.set_clim(vmin, vmax)\n\n        slider.cval = val\n", "path": "sunpy/visualization/animator/wcs.py"}]}