Repository: sunpy/sunpy

Issue:
Map.rotate is broken
It's dead jim. http://docs.sunpy.org/en/latest/generated/gallery/gallery/map_rotation.html

It seems that the reference pixel post-rotation is wrong.

ping @SolarDrew @ayshih 


Discussion:
`<whimper>`
Can you please elaborate on the issue a little bit? The example code works fine, so what is broken?

 @Cadair 


I think the output's coming like this, instead of the one given in the guidelines [http://docs.sunpy.org/en/latest/generated/gallery/gallery/map_rotation.html](url)

![rotatesun](https://cloud.githubusercontent.com/assets/16035442/23329204/783fe976-fb59-11e6-964a-2731e3675c3f.png)


Similarly for 90 degree rotation.
![90](https://cloud.githubusercontent.com/assets/16035442/23329236/a877fd6c-fb5a-11e6-919d-914c4904f2d6.png)

@Cadair, @ayshih  can I correct this in the documentation part?  I have already got the image. 
The image is auto-generated with the documentation. Interesting that it's not reproducible.
So I have traced this to the padding set where crpix1 and crpix2 is modified.
I traced the issue back to this patch: https://github.com/sunpy/sunpy/pull/1929/files#diff-9eadd681e99c7419c431865d29b5ca1dR1003

Fix incoming.

Resolving patch:
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -942,7 +942,7 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
                     error_msg = "no 'unit' attribute"
                 raise TypeError("Argument '{0}' to function '{1}' has {2}. "
                                 "You may want to pass in an astropy Quantity instead."
-                                 .format('angle', 'rotate', error_msg))
+                                .format('angle', 'rotate', error_msg))
 
         # Interpolation parameter sanity
         if order not in range(6):
@@ -964,6 +964,7 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
         # Calculate the shape in pixels to contain all of the image data
         extent = np.max(np.abs(np.vstack((self.data.shape * rmatrix,
                                           self.data.shape * rmatrix.T))), axis=0)
+
         # Calculate the needed padding or unpadding
         diff = np.asarray(np.ceil((extent - self.data.shape) / 2), dtype=int).ravel()
         # Pad the image array
@@ -971,9 +972,9 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
         pad_y = int(np.max((diff[0], 0)))
 
         new_data = np.pad(self.data,
-                              ((pad_y, pad_y), (pad_x, pad_x)),
-                              mode='constant',
-                              constant_values=(missing, missing))
+                          ((pad_y, pad_y), (pad_x, pad_x)),
+                          mode='constant',
+                          constant_values=(missing, missing))
         new_meta['crpix1'] += pad_x
         new_meta['crpix2'] += pad_y
 
@@ -981,9 +982,13 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
 
         pixel_array_center = (np.flipud(new_data.shape) - 1) / 2.0
 
+        # Create a temporary map so we can use it for the data to pixel calculation.
+        temp_map = self._new_instance(new_data, new_meta, self.plot_settings)
+
         # Convert the axis of rotation from data coordinates to pixel coordinates
-        pixel_rotation_center = u.Quantity(self.data_to_pixel(*rotation_center,
-                                                               origin=0)).value
+        pixel_rotation_center = u.Quantity(temp_map.data_to_pixel(*rotation_center,
+                                                                  origin=0)).value
+
         if recenter:
             pixel_center = pixel_rotation_center
         else:
@@ -1008,8 +1013,8 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
         # Define the new reference_pixel
         new_meta['crval1'] = rotation_center[0].value
         new_meta['crval2'] = rotation_center[1].value
-        new_meta['crpix1'] = new_reference_pixel[0] + 1 # FITS pixel origin is 1
-        new_meta['crpix2'] = new_reference_pixel[1] + 1 # FITS pixel origin is 1
+        new_meta['crpix1'] = new_reference_pixel[0] + 1  # FITS pixel origin is 1
+        new_meta['crpix2'] = new_reference_pixel[1] + 1  # FITS pixel origin is 1
 
         # Unpad the array if necessary
         unpad_x = -np.min((diff[1], 0))
@@ -1026,10 +1031,10 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
         # That being calculate the dot product of the old header data with the
         # inverse of the rotation matrix.
         pc_C = np.dot(self.rotation_matrix, rmatrix.I)
-        new_meta['PC1_1'] = pc_C[0,0]
-        new_meta['PC1_2'] = pc_C[0,1]
-        new_meta['PC2_1'] = pc_C[1,0]
-        new_meta['PC2_2'] = pc_C[1,1]
+        new_meta['PC1_1'] = pc_C[0, 0]
+        new_meta['PC1_2'] = pc_C[0, 1]
+        new_meta['PC2_1'] = pc_C[1, 0]
+        new_meta['PC2_2'] = pc_C[1, 1]
 
         # Update pixel size if image has been scaled.
         if scale != 1.0:
@@ -1045,8 +1050,9 @@ def rotate(self, angle=None, rmatrix=None, order=4, scale=1.0,
         new_meta.pop('CD2_1', None)
         new_meta.pop('CD2_2', None)
 
-        #Create new map with the modification
+        # Create new map with the modification
         new_map = self._new_instance(new_data, new_meta, self.plot_settings)
+
         return new_map
 
     def submap(self, range_a, range_b):


Test patch:
diff --git a/sunpy/map/tests/test_mapbase.py b/sunpy/map/tests/test_mapbase.py
--- a/sunpy/map/tests/test_mapbase.py
+++ b/sunpy/map/tests/test_mapbase.py
@@ -455,6 +455,14 @@ def test_rotate(aia171_test_map):
     assert aia171_test_map_crop_rot.data.shape[0] < aia171_test_map_crop_rot.data.shape[1]
 
 
+def test_rotate_pad_crpix(generic_map):
+    rotated_map = generic_map.rotate(30*u.deg)
+    # This tests that the reference pixel of the map is in the expected place.
+    assert rotated_map.data.shape != generic_map.data.shape
+    assert_quantity_allclose(u.Quantity(rotated_map.reference_pixel),
+                             u.Quantity((6.049038105675565, 7.5490381056760265), u.pix))
+
+
 def test_rotate_recenter(generic_map):
     rotated_map = generic_map.rotate(20*u.deg, recenter=True)
     pixel_array_center = (np.flipud(rotated_map.data.shape) - 1) / 2.0