Repository: sunpy/sunpy

Issue:
Do not try to remove non-finite entries before image coalignment
Currently, before coaligning two images, [`repair_image_nonfinite`](https://github.com/sunpy/sunpy/blob/master/sunpy/image/coalignment.py#L290) is run on the image to remove non-finite entries and replace them with the local mean.

This step should be removed and the user should be expected to this themselves before coaligning images. If NaNs/non-finite entries are present, a warning should be thrown.


Discussion:

Resolving patch:
diff --git a/sunpy/image/coalignment.py b/sunpy/image/coalignment.py
--- a/sunpy/image/coalignment.py
+++ b/sunpy/image/coalignment.py
@@ -17,6 +17,7 @@
    1995, p. 120-123 http://www.scribblethink.org/Work/nvisionInterface/vi95_lewis.pdf.
 """
 from copy import deepcopy
+import warnings
 
 import numpy as np
 from scipy.ndimage.interpolation import shift
@@ -26,12 +27,13 @@
 
 import sunpy.map
 from sunpy.map.mapbase import GenericMap
+from sunpy.util import (SunpyUserWarning, SunpyDeprecationWarning, deprecated)
 
 __all__ = ['calculate_shift', 'clip_edges', 'calculate_clipping',
            'match_template_to_layer', 'find_best_match_location',
            'get_correlation_shifts', 'parabolic_turning_point',
-           'repair_image_nonfinite', 'apply_shifts',
-           'mapsequence_coalign_by_match_template',
+           'repair_image_nonfinite', 'check_for_nonfinite_entries',
+           'apply_shifts', 'mapsequence_coalign_by_match_template',
            'calculate_match_template_shift']
 
 
@@ -45,7 +47,7 @@ def _default_fmap_function(data):
     return np.float64(data)
 
 
-def calculate_shift(this_layer, template):
+def calculate_shift(this_layer, template, repair_nonfinite=True):
     """
     Calculates the pixel shift required to put the template in the "best"
     position on a layer.
@@ -64,9 +66,18 @@ def calculate_shift(this_layer, template):
         Pixel shifts ``(yshift, xshift)`` relative to the offset of the template
         to the input array.
     """
-    # Repair any NANs, Infs, etc in the layer and the template
-    this_layer = repair_image_nonfinite(this_layer)
-    template = repair_image_nonfinite(template)
+    if repair_nonfinite:
+        # Repair any NANs, Infs, etc in the layer and the template
+        # This behaviour is deprecated
+        warnings.warn('The repairing of nonfinite values is deprecated '
+                      'and will be removed in future versions. '
+                      'You can disable this behaviour by setting the '
+                      'repair_nonfinite kwarg to False.', SunpyDeprecationWarning)
+        this_layer = repair_image_nonfinite(this_layer)
+        template = repair_image_nonfinite(template)
+    else:
+        # Warn user if any NANs, Infs, etc are present in the layer or the template
+        check_for_nonfinite_entries(this_layer, template)
 
     # Calculate the correlation array matching the template to this layer
     corr = match_template_to_layer(this_layer, template)
@@ -287,6 +298,33 @@ def parabolic_turning_point(y):
     return numerator / denominator
 
 
+def check_for_nonfinite_entries(layer_image, template_image):
+    """
+    Issue a warning if there is any nonfinite entry in the layer or template images.
+
+    Parameters
+    ----------
+    layer_image : `numpy.ndarray`
+        A two-dimensional `numpy.ndarray`.
+    template_image : `numpy.ndarray`
+        A two-dimensional `numpy.ndarray`.
+    """
+    if not np.all(np.isfinite(layer_image)):
+        warnings.warn('The layer image has nonfinite entries. '
+                      'This could cause errors when calculating shift between two '
+                      'images. Please make sure there are no infinity or '
+                      'Not a Number values. For instance, replacing them with a '
+                      'local mean.', SunpyUserWarning)
+
+    if not np.all(np.isfinite(template_image)):
+        warnings.warn('The template image has nonfinite entries. '
+                      'This could cause errors when calculating shift between two '
+                      'images. Please make sure there are no infinity or '
+                      'Not a Number values. For instance, replacing them with a '
+                      'local mean.', SunpyUserWarning)
+
+
+@deprecated("1.1")
 def repair_image_nonfinite(image):
     """
     Return a new image in which all the nonfinite entries of the original image
@@ -395,7 +433,8 @@ def apply_shifts(mc, yshift: u.pix, xshift: u.pix, clip=True, **kwargs):
 
 
 def calculate_match_template_shift(mc, template=None, layer_index=0,
-                                   func=_default_fmap_function):
+                                   func=_default_fmap_function,
+                                   repair_nonfinite=True):
     """
     Calculate the arcsecond shifts necessary to co-register the layers in a
     `~sunpy.map.MapSequence` according to a template taken from that
@@ -465,7 +504,8 @@ def calculate_match_template_shift(mc, template=None, layer_index=0,
         this_layer = func(m.data)
 
         # Calculate the y and x shifts in pixels
-        yshift, xshift = calculate_shift(this_layer, tplate)
+        yshift, xshift = calculate_shift(this_layer, tplate,
+                                         repair_nonfinite=repair_nonfinite)
 
         # Keep shifts in pixels
         yshift_keep[i] = yshift
@@ -487,7 +527,7 @@ def calculate_match_template_shift(mc, template=None, layer_index=0,
 # Coalignment by matching a template
 def mapsequence_coalign_by_match_template(mc, template=None, layer_index=0,
                                           func=_default_fmap_function, clip=True,
-                                          shift=None, **kwargs):
+                                          shift=None, repair_nonfinite=True, **kwargs):
     """
     Co-register the layers in a `~sunpy.map.MapSequence` according to a
     template taken from that `~sunpy.map.MapSequence`. This method REQUIRES
@@ -566,7 +606,8 @@ def mapsequence_coalign_by_match_template(mc, template=None, layer_index=0,
     if shift is None:
         shifts = calculate_match_template_shift(mc, template=template,
                                                 layer_index=layer_index,
-                                                func=func)
+                                                func=func,
+                                                repair_nonfinite=repair_nonfinite)
         xshift_arcseconds = shifts['x']
         yshift_arcseconds = shifts['y']
     else:


Test patch:
diff --git a/sunpy/image/tests/test_coalignment.py b/sunpy/image/tests/test_coalignment.py
--- a/sunpy/image/tests/test_coalignment.py
+++ b/sunpy/image/tests/test_coalignment.py
@@ -1,4 +1,5 @@
 import os
+import warnings
 
 import numpy as np
 import pytest
@@ -12,8 +13,10 @@
                                      calculate_clipping, calculate_match_template_shift, clip_edges,
                                      find_best_match_location, get_correlation_shifts,
                                      mapsequence_coalign_by_match_template, match_template_to_layer,
-                                     parabolic_turning_point, repair_image_nonfinite)
+                                     parabolic_turning_point, check_for_nonfinite_entries,
+                                     repair_image_nonfinite)
 from sunpy.map import Map, MapSequence
+from sunpy.util import (SunpyUserWarning, SunpyDeprecationWarning)
 
 
 @pytest.fixture
@@ -69,10 +72,44 @@ def test_repair_image_nonfinite():
             a = np.ones(9)
             a[i] = non_number
             b = a.reshape(3, 3)
-            c = repair_image_nonfinite(b)
+            with pytest.warns(SunpyDeprecationWarning):
+                c = repair_image_nonfinite(b)
+
             assert(np.isfinite(c).all())
 
 
+def test_check_for_nonfinite_entries():
+    with pytest.warns(None) as warning_list:
+        a = np.zeros((3, 3))
+        b = np.ones((3, 3))
+        check_for_nonfinite_entries(a, b)
+
+    assert len(warning_list) == 0
+
+    for i in range(0, 9):
+        for non_number in [np.nan, np.inf]:
+            a = np.ones(9)
+            a[i] = non_number
+            b = a.reshape(3, 3)
+
+            with pytest.warns(SunpyUserWarning,
+                              match='The layer image has nonfinite entries.') as warning_list:
+                check_for_nonfinite_entries(b, np.ones((3, 3)))
+
+            assert len(warning_list) == 1
+
+            with pytest.warns(SunpyUserWarning,
+                              match='The template image has nonfinite entries.') as warning_list:
+                check_for_nonfinite_entries(np.ones((3, 3)), b)
+
+            assert len(warning_list) == 1
+
+            with pytest.warns(None) as warning_list:
+                check_for_nonfinite_entries(b, b)
+
+            assert len(warning_list) == 2
+
+
 def test_match_template_to_layer(aia171_test_map_layer,
                                  aia171_test_template,
                                  aia171_test_map_layer_shape,
@@ -189,19 +226,22 @@ def test_calculate_match_template_shift(aia171_test_mc,
     nx = aia171_test_map_layer_shape[1]
 
     # Test to see if the code can recover the displacements.
-    test_displacements = calculate_match_template_shift(aia171_test_mc)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_displacements = calculate_match_template_shift(aia171_test_mc)
     assert_allclose(test_displacements['x'], aia171_mc_arcsec_displacements['x'], rtol=5e-2, atol=0)
     assert_allclose(test_displacements['y'], aia171_mc_arcsec_displacements['y'], rtol=5e-2, atol=0)
 
     # Test setting the template as a ndarray
     template_ndarray = aia171_test_map_layer[ny // 4: 3 * ny // 4, nx // 4: 3 * nx // 4]
-    test_displacements = calculate_match_template_shift(aia171_test_mc, template=template_ndarray)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_displacements = calculate_match_template_shift(aia171_test_mc, template=template_ndarray)
     assert_allclose(test_displacements['x'], aia171_mc_arcsec_displacements['x'], rtol=5e-2, atol=0)
     assert_allclose(test_displacements['y'], aia171_mc_arcsec_displacements['y'], rtol=5e-2, atol=0)
 
     # Test setting the template as GenericMap
     submap = aia171_test_map.submap([nx / 4, ny / 4]*u.pix, [3 * nx / 4, 3 * ny / 4]*u.pix)
-    test_displacements = calculate_match_template_shift(aia171_test_mc, template=submap)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_displacements = calculate_match_template_shift(aia171_test_mc, template=submap)
     assert_allclose(test_displacements['x'], aia171_mc_arcsec_displacements['x'], rtol=5e-2, atol=0)
     assert_allclose(test_displacements['y'], aia171_mc_arcsec_displacements['y'], rtol=5e-2, atol=0)
 
@@ -218,7 +258,8 @@ def test_mapsequence_coalign_by_match_template(aia171_test_mc,
     nx = aia171_test_map_layer_shape[1]
 
     # Get the calculated test displacements
-    test_displacements = calculate_match_template_shift(aia171_test_mc)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_displacements = calculate_match_template_shift(aia171_test_mc)
 
     # Test passing in displacements
     test_mc = mapsequence_coalign_by_match_template(aia171_test_mc, shift=test_displacements)
@@ -228,14 +269,16 @@ def test_mapsequence_coalign_by_match_template(aia171_test_mc,
 
     # Test returning with no clipping.  Output layers should have the same size
     # as the original input layer.
-    test_mc = mapsequence_coalign_by_match_template(aia171_test_mc, clip=False)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_mc = mapsequence_coalign_by_match_template(aia171_test_mc, clip=False)
     assert(test_mc[0].data.shape == aia171_test_map_layer_shape)
     assert(test_mc[1].data.shape == aia171_test_map_layer_shape)
 
     # Test the returned mapsequence using the default - clipping on.
     # All output layers should have the same size
     # which is smaller than the input by a known amount
-    test_mc = mapsequence_coalign_by_match_template(aia171_test_mc)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_mc = mapsequence_coalign_by_match_template(aia171_test_mc)
     x_displacement_pixels = test_displacements['x'] / test_mc[0].scale[0]
     y_displacement_pixels = test_displacements['y'] / test_mc[0].scale[1]
     expected_clipping = calculate_clipping(y_displacement_pixels, x_displacement_pixels)
@@ -247,7 +290,8 @@ def test_mapsequence_coalign_by_match_template(aia171_test_mc,
     # Test the returned mapsequence explicitly using clip=True.
     # All output layers should have the same size
     # which is smaller than the input by a known amount
-    test_mc = mapsequence_coalign_by_match_template(aia171_test_mc, clip=True)
+    with pytest.warns(SunpyDeprecationWarning):
+        test_mc = mapsequence_coalign_by_match_template(aia171_test_mc, clip=True)
     x_displacement_pixels = test_displacements['x'] / test_mc[0].scale[0]
     y_displacement_pixels = test_displacements['y'] / test_mc[0].scale[1]
     expected_clipping = calculate_clipping(y_displacement_pixels, x_displacement_pixels)