Repository: sunpy/sunpy

Issue:
Shifted MapCubes do not have the correct co-ordinate information
The function apply_shifts in

https://github.com/sunpy/sunpy/blob/master/sunpy/image/coalignment.py

incorrectly sets the co-ordinate information in shifted output maps that form the output mapcube.  This function is called by other functions implementing the solar derotation of mapcubes and the coalignment of mapcubes.

A fix is being worked on in https://github.com/wafels/sunpy/tree/fix_apply_shift .



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
@@ -35,7 +35,8 @@
            'match_template_to_layer', 'find_best_match_location',
            'get_correlation_shifts', 'parabolic_turning_point',
            'repair_image_nonfinite', 'apply_shifts',
-           'mapcube_coalign_by_match_template']
+           'mapcube_coalign_by_match_template',
+           'calculate_match_template_shift']
 
 
 def _default_fmap_function(data):
@@ -370,29 +371,32 @@ def apply_shifts(mc, yshift, xshift, clip=True):
         A `~sunpy.map.MapCube` of the same shape as the input.  All layers in
         the `~sunpy.map.MapCube` have been shifted according the input shifts.
     """
+    # New mapcube will be constructed from this list
+    new_mc = []
 
-    # new mapcube will be constructed from this list
-    newmc_list = []
+    # Calculate the clipping
+    if clip:
+        yclips, xclips = calculate_clipping(-yshift, -xshift)
 
     # Shift the data and construct the mapcube
-    for i, m in enumerate(mc.maps):
+    for i, m in enumerate(mc):
         shifted_data = shift(m.data, [yshift[i].value, xshift[i].value])
-        # Clip if required
+        new_meta = deepcopy(m.meta)
+        # Clip if required.  Use the submap function to return the appropriate
+        # portion of the data.
         if clip:
-            yclips, xclips = calculate_clipping(-yshift, -xshift)
             shifted_data = clip_edges(shifted_data, yclips, xclips)
+            new_meta['naxis1'] = shifted_data.shape[1]
+            new_meta['naxis2'] = shifted_data.shape[0]
+            new_meta['crpix1'] = m.reference_pixel.x.value + xshift[i].value - xshift[0].value
+            new_meta['crpix2'] = m.reference_pixel.y.value + yshift[i].value - yshift[0].value
 
-        # New header
-        new_meta = deepcopy(m.meta)
-
-        # Adjust the positioning information accordingly.
-        new_meta['crval1'] = new_meta['crval1'] - xshift[i].value * m.scale.x.value
-        new_meta['crval2'] = new_meta['crval2'] - yshift[i].value * m.scale.y.value
+        new_map = sunpy.map.Map(shifted_data, new_meta)
 
         # Append to the list
-        newmc_list.append(sunpy.map.Map(shifted_data, new_meta))
+        new_mc.append(new_map)
 
-    return sunpy.map.Map(newmc_list, cube=True)
+    return sunpy.map.Map(new_mc, cube=True)
 
 
 def calculate_match_template_shift(mc, template=None, layer_index=0,
diff --git a/sunpy/physics/transforms/solar_rotation.py b/sunpy/physics/transforms/solar_rotation.py
--- a/sunpy/physics/transforms/solar_rotation.py
+++ b/sunpy/physics/transforms/solar_rotation.py
@@ -131,12 +131,9 @@ def mapcube_solar_derotate(mc, layer_index=0, clip=True, shift=None, **kwargs):
     # If no shifts are passed in, calculate them.  Otherwise,
     # use the shifts passed in.
     if shift is None:
-        shifts = calculate_solar_rotate_shift(mc, layer_index=layer_index, **kwargs)
-        xshift_arcseconds = shifts['x']
-        yshift_arcseconds = shifts['y']
-    else:
-        xshift_arcseconds = shift[0]
-        yshift_arcseconds = shift[1]
+        shift = calculate_solar_rotate_shift(mc, layer_index=layer_index, **kwargs)
+    xshift_arcseconds = shift['x']
+    yshift_arcseconds = shift['y']
 
     # Calculate the pixel shifts
     for i, m in enumerate(mc):
@@ -144,4 +141,4 @@ def mapcube_solar_derotate(mc, layer_index=0, clip=True, shift=None, **kwargs):
         yshift_keep[i] = yshift_arcseconds[i] / m.scale.y
 
     # Apply the pixel shifts and return the mapcube
-    return apply_shifts(mc, yshift_keep, xshift_keep , clip=clip)
+    return apply_shifts(mc, yshift_keep, xshift_keep, clip=clip)


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
@@ -110,12 +110,12 @@ def test_get_correlation_shifts():
     # Input array is too big in either direction
     test_array = np.zeros((4, 3))
     y_test, x_test = get_correlation_shifts(test_array)
-    assert(y_test == None)
-    assert(x_test == None)
+    assert(y_test is None)
+    assert(x_test is None)
     test_array = np.zeros((3, 4))
     y_test, x_test = get_correlation_shifts(test_array)
-    assert(y_test == None)
-    assert(x_test == None)
+    assert(y_test is None)
+    assert(x_test is None)
 
 
 def test_find_best_match_location(aia171_test_map_layer, aia171_test_template,
@@ -201,13 +201,13 @@ def test_calculate_match_template_shift(aia171_test_mc,
     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)
     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 )
+    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, 3 * nx / 4]*u.pix, [ny / 4, 3 * ny / 4]*u.pix)
     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 )
+    assert_allclose(test_displacements['y'], aia171_mc_arcsec_displacements['y'], rtol=5e-2, atol=0)
 
     # Test setting the template as something other than a ndarray and a
     # GenericMap.  This should throw a ValueError.
@@ -221,7 +221,7 @@ def test_mapcube_coalign_by_match_template(aia171_test_mc,
     ny = aia171_test_map_layer_shape[0]
     nx = aia171_test_map_layer_shape[1]
 
-    # Get the test displacements
+    # Get the calculated test displacements
     test_displacements = calculate_match_template_shift(aia171_test_mc)
 
     # Test passing in displacements
@@ -260,6 +260,13 @@ def test_mapcube_coalign_by_match_template(aia171_test_mc,
     assert(test_mc[0].data.shape == (ny - number_of_pixels_clipped[0].value, nx - number_of_pixels_clipped[1].value))
     assert(test_mc[1].data.shape == (ny - number_of_pixels_clipped[0].value, nx - number_of_pixels_clipped[1].value))
 
+    # Test that the reference pixel of each map in the coaligned mapcube is
+    # correct.
+    for im, m in enumerate(aia171_test_mc):
+        for i_s, s in enumerate(['x', 'y']):
+            assert_allclose(aia171_test_mc[im].reference_pixel[i_s] - test_mc[im].reference_pixel[i_s],
+                            test_displacements[s][im] / m.scale[i_s],
+                            rtol=5e-2, atol=0)
 
 def test_apply_shifts(aia171_test_map):
     # take two copies of the AIA image and create a test mapcube.
diff --git a/sunpy/physics/transforms/tests/test_solar_rotation.py b/sunpy/physics/transforms/tests/test_solar_rotation.py
--- a/sunpy/physics/transforms/tests/test_solar_rotation.py
+++ b/sunpy/physics/transforms/tests/test_solar_rotation.py
@@ -74,12 +74,16 @@ def test_mapcube_solar_derotate(aia171_test_mapcube, aia171_test_submap):
     for m in tmc:
         assert(m.data.shape == aia171_test_submap.data.shape)
 
-    # Test that the returned centers are correctly displaced.
-    tshift = calculate_solar_rotate_shift(aia171_test_mapcube)
+    # Test that the returned reference pixels are correctly displaced.
+    tmc = mapcube_solar_derotate(aia171_test_mapcube, clip=True)
+    tshift = calculate_solar_rotate_shift(aia171_test_mapcube, layer_index=1)
     for im, m in enumerate(tmc):
         for i_s, s in enumerate(['x', 'y']):
-            assert_allclose(m.center[i_s], aia171_test_submap.center[i_s] -
-                            tshift[s][im], rtol=5e-2, atol=0)
+            assert_allclose(m.reference_pixel[i_s],
+                            aia171_test_submap.reference_pixel[i_s] +
+                            tshift[s][im] / m.scale[i_s] -
+                            tshift[s][0] / m.scale[i_s],
+                            rtol=5e-2, atol=0)
 
     # Test that a mapcube is returned on default clipping (clipping is True)
     tmc = mapcube_solar_derotate(aia171_test_mapcube)
@@ -89,9 +93,3 @@ def test_mapcube_solar_derotate(aia171_test_mapcube, aia171_test_submap):
     clipped_shape = (24, 20)
     for m in tmc:
         assert(m.data.shape == clipped_shape)
-
-
-
-
-
-