Repository: sunpy/sunpy

Issue:
Add a dynamic z axis option to the animators
So if your frames have a wide dynamic range the z scale changes


Discussion:
I want to start this issue please can anyone provide me some more information regarding this issue. Like which file to be modified and what to be checked?
Hi @SnehVachhani, this is not an issue I would suggest new people work on. The animators are very opaque and not great to work on. Please look at https://github.com/sunpy/sunpy/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22Good+First+Issue%22 instead
Any resources on this issue? Would like to work on this if it's open.
😆 this is a classic @Cadair one-liner issue. To add a bit more detail:

If you have a 3D dataset, the animator animates one of those dimensions. Currently the `vmin`/`vmax` for the colorbar is set to be the minimum and maximum of the whole 3D dataset, but if there is a wide range of values across the dimension being animated, it can be helpful to update the `vmin`/`vmax` to the vmin/max of the 2D slice currently being shown, and updating it again each time a new slice is shown.
Alright, I'll get started reading up on this!
> laughing this is a classic @Cadair one-liner issue. To add a bit more detail:
> 
> If you have a 3D dataset, the animator animates one of those dimensions. Currently the `vmin`/`vmax` for the colorbar is set to be the minimum and maximum of the whole 3D dataset, but if there is a wide range of values across the dimension being animated, it can be helpful to update the `vmin`/`vmax` to the vmin/max of the 2D slice currently being shown, and updating it again each time a new slice is shown.

Is there code for creating an animation from a 3D dataset? I can't seem to find an example that lists this out.
I believe this is an example: https://docs.sunpy.org/en/latest/generated/gallery/plotting/arrayanimatorwcs.html#sphx-glr-generated-gallery-plotting-arrayanimatorwcs-py 
> laughing this is a classic @Cadair one-liner issue. To add a bit more detail:
> 
> If you have a 3D dataset, the animator animates one of those dimensions. Currently the `vmin`/`vmax` for the colorbar is set to be the minimum and maximum of the whole 3D dataset, but if there is a wide range of values across the dimension being animated, it can be helpful to update the `vmin`/`vmax` to the vmin/max of the 2D slice currently being shown, and updating it again each time a new slice is shown.

The ```ArrayAnimatorWCS``` allows the user to specify a vmax, without using this property the images have a wide range of values, is this what you are referring to?

Yes, if you specify a `vmax` it is the same `vmax` for all the frames of the animation. I think a good API to fix this feature request would be allow `vmax='auto'` to be specified, which would update the `vmax` value frame by frame based on the maximum value of the data being shown in that frame.
> Yes, if you specify a `vmax` it is the same `vmax` for all the frames of the animation. I think a good API to fix this feature request would be allow `vmax='auto'` to be specified, which would update the `vmax` value frame by frame based on the maximum value of the data being shown in that frame.

Yeah that makes sense, I shall get working on this and make a PR soon
> Yes, if you specify a `vmax` it is the same `vmax` for all the frames of the animation. I think a good API to fix this feature request would be allow `vmax='auto'` to be specified, which would update the `vmax` value frame by frame based on the maximum value of the data being shown in that frame.

While implementing this, I was thinking if I were to set the ```vmin```/```vmax``` to a ```clip_interval=(1, 90)*u.percent``` or should the ```vmax``` be greater than 90? These seem to produce optimal results. such as 
![image](https://user-images.githubusercontent.com/50923538/108553275-74262b80-7318-11eb-8bba-dda12081c2d4.png)
From [this example](https://docs.sunpy.org/en/latest/generated/gallery/plotting/arrayanimatorwcs.html#sphx-glr-generated-gallery-plotting-arrayanimatorwcs-py)

Resolving patch:
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 @@ class ArrayAnimatorWCS(ArrayAnimator):
     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 @@ def __init__(self, data, wcs, slices, coord_params=None, ylim='dynamic', ylabel=
         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 @@ def plot_start_image_2d(self, ax):
                        '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 @@ def plot_start_image_2d(self, ax):
 
         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


Test patch:
diff --git a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_403.json b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_403.json
--- a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_403.json
+++ b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_403.json
@@ -45,6 +45,7 @@
   "sunpy.timeseries.tests.test_timeseriesbase.test_generic_ts_peek": "73a71e717b7dd0dd345f0f8a8bfa7f06387607417b3ecd123adfd8f630f93d51",
   "sunpy.visualization.animator.tests.test_basefuncanimator.test_lineanimator_figure": "dbf47ab983a844621f912b2f5a2d9ee657d3f661678c16a07afc7217f50e3fd4",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_simple_plot": "03ab823148c6071a81c9edad2bc58e0c0b401a3dcf5108d749a80ac0f24a07b7",
+  "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_clip_interval": "18e3b9433d7115d3f05d5fbc771774e6908d00e20b6d390bcc3d525b5af48632",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_celestial_sliders": "460980d5e2d2b06e1b7ce07613f2169078edc759515ca1c1cff242f72cca1158",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_update_plot": "377a48ea45516d91bd483e0044868d2a2da0bb45569ed15ead305987ad18dc00",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_transpose_update_plot": "1676a6fd6ba95e3ac725a9ef37f4dd1e1acb09886b8781f32d3d29e7c50794c4",
diff --git a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
--- a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
+++ b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
@@ -45,6 +45,7 @@
   "sunpy.timeseries.tests.test_timeseriesbase.test_generic_ts_peek": "73a71e717b7dd0dd345f0f8a8bfa7f06387607417b3ecd123adfd8f630f93d51",
   "sunpy.visualization.animator.tests.test_basefuncanimator.test_lineanimator_figure": "dbf47ab983a844621f912b2f5a2d9ee657d3f661678c16a07afc7217f50e3fd4",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_simple_plot": "03ab823148c6071a81c9edad2bc58e0c0b401a3dcf5108d749a80ac0f24a07b7",
+  "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_clip_interval": "18e3b9433d7115d3f05d5fbc771774e6908d00e20b6d390bcc3d525b5af48632",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_celestial_sliders": "460980d5e2d2b06e1b7ce07613f2169078edc759515ca1c1cff242f72cca1158",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_update_plot": "377a48ea45516d91bd483e0044868d2a2da0bb45569ed15ead305987ad18dc00",
   "sunpy.visualization.animator.tests.test_wcs.test_array_animator_wcs_2d_transpose_update_plot": "52af2bd727302c27526e5c4c459ae1a5e7e452c193164a77095febb0d0412dfd",
diff --git a/sunpy/visualization/animator/tests/test_wcs.py b/sunpy/visualization/animator/tests/test_wcs.py
--- a/sunpy/visualization/animator/tests/test_wcs.py
+++ b/sunpy/visualization/animator/tests/test_wcs.py
@@ -134,6 +134,13 @@ def test_array_animator_wcs_2d_simple_plot(wcs_4d):
     return a.fig
 
 
+@figure_test
+def test_array_animator_wcs_2d_clip_interval(wcs_4d):
+    data = np.arange(120).reshape((5, 4, 3, 2))
+    a = ArrayAnimatorWCS(data, wcs_4d, [0, 0, 'x', 'y'], clip_interval=(1, 99)*u.percent)
+    return a.fig
+
+
 @figure_test
 def test_array_animator_wcs_2d_celestial_sliders(wcs_4d):
     data = np.arange(120).reshape((5, 4, 3, 2))