Repository: sunpy/sunpy

Issue:
Differential rotation of maps should pass in diff_rot kwargs
The keywords of _diff_rot_ should be passable from _diffrot_map_.


Discussion:

Resolving patch:
diff --git a/sunpy/physics/differential_rotation.py b/sunpy/physics/differential_rotation.py
--- a/sunpy/physics/differential_rotation.py
+++ b/sunpy/physics/differential_rotation.py
@@ -1,5 +1,5 @@
 from __future__ import division
-from datetime import timedelta
+import datetime
 from copy import deepcopy
 import warnings
 from itertools import product
@@ -171,7 +171,7 @@ def solar_rotate_coordinate(coordinate,
 
 
 @u.quantity_input(dt=u.s)
-def _warp_sun_coordinates(xy, smap, dt):
+def _warp_sun_coordinates(xy, smap, dt, **diffrot_kwargs):
     """
     Function that returns a new list of coordinates for each input coord.
     This is an inverse function needed by the scikit-image `transform.warp`
@@ -193,7 +193,7 @@ def _warp_sun_coordinates(xy, smap, dt):
     """
     # NOTE: The time is being subtracted - this is because this function
     # calculates the inverse of the transformation.
-    rotated_time = smap.date - timedelta(seconds=dt.to(u.s).value)
+    rotated_time = smap.date - datetime.timedelta(seconds=dt.to(u.s).value)
 
     # Calculate the hpc coords
     x = np.arange(0, smap.dimensions.x.value)
@@ -208,7 +208,7 @@ def _warp_sun_coordinates(xy, smap, dt):
         hpc_coords = smap.pixel_to_world(xx * u.pix, yy * u.pix)
 
         # then diff-rotate the hpc coordinates to the desired time
-        rotated_coord = solar_rotate_coordinate(hpc_coords, rotated_time)
+        rotated_coord = solar_rotate_coordinate(hpc_coords, rotated_time, **diffrot_kwargs)
 
         # To find the values that are behind the sun we need to convert them
         # to HeliographicStonyhurst
@@ -234,8 +234,8 @@ def _warp_sun_coordinates(xy, smap, dt):
     return xy2
 
 
-@u.quantity_input(dt=u.s)
-def diffrot_map(smap, dt, pad=False):
+@u.quantity_input(dt='time')
+def diffrot_map(smap, time=None, dt=None, pad=False, **diffrot_kwargs):
     """
     Function to apply solar differential rotation to a sunpy map.
 
@@ -243,7 +243,9 @@ def diffrot_map(smap, dt, pad=False):
     ----------
     smap : `~sunpy.map`
         Original map that we want to transform.
-    dt : `~astropy.units.Quantity`
+    time : sunpy-compatible time
+        date/time at which the input co-ordinate will be rotated to.
+    dt : `~astropy.units.Quantity` or `datetime`
         Desired interval between the input map and returned map.
     pad : `bool`
         Whether to create a padded map for submaps to don't loose data
@@ -254,7 +256,15 @@ def diffrot_map(smap, dt, pad=False):
         A map with the result of applying solar differential rotation to the
         input map.
     """
-    new_time = smap.date + timedelta(seconds=dt.to(u.s).value)
+    if (time is not None) and (dt is not None):
+        raise ValueError('Only a time or an interval is accepted')
+    elif not (time or dt):
+        raise ValueError('Either a time or an interval (`dt=`) needs to be provided')
+    elif time:
+        new_time = parse_time(time)
+        dt = (new_time - smap.date).total_seconds() * u.s
+    else:
+        new_time = smap.date + datetime.timedelta(seconds=dt.to(u.s).value)
 
     # Check for masked maps
     if smap.mask is not None:
@@ -273,7 +283,7 @@ def diffrot_map(smap, dt, pad=False):
             deltax = deltay = 0
             for corner in product(*product([0 * u.pix], smap.dimensions)):
                 corner_world = smap.pixel_to_world(*corner)
-                corner_world_rotated = solar_rotate_coordinate(corner_world, new_time)
+                corner_world_rotated = solar_rotate_coordinate(corner_world, new_time, **diffrot_kwargs)
                 corner_px_rotated = smap.world_to_pixel(corner_world_rotated)
                 dx = np.abs(corner_px_rotated.x - corner[0])
                 dy = np.abs(corner_px_rotated.y - corner[1])
@@ -291,9 +301,11 @@ def diffrot_map(smap, dt, pad=False):
             smap_meta['crpix2'] += deltay
             smap = sunpy.map.Map(smap_data, smap_meta)
 
+    warp_args = {'smap': smap, 'dt': dt}
+    warp_args.update(diffrot_kwargs)
     # Apply solar differential rotation as a scikit-image warp
     out = transform.warp(to_norm(smap_data), inverse_map=_warp_sun_coordinates,
-                         map_args={"smap": smap, "dt": dt})
+                         map_args=warp_args)
 
     # Recover the original intensity range.
     out = un_norm(out, smap.data)
@@ -305,7 +317,7 @@ def diffrot_map(smap, dt, pad=False):
     out_meta['date-obs'] = "{:%Y-%m-%dT%H:%M:%S}".format(new_time)
 
     if submap:
-        crval_rotated = solar_rotate_coordinate(smap.reference_coordinate, new_time)
+        crval_rotated = solar_rotate_coordinate(smap.reference_coordinate, new_time, **diffrot_kwargs)
         out_meta['crval1'] = crval_rotated.Tx.value
         out_meta['crval2'] = crval_rotated.Ty.value
 


Test patch:
diff --git a/sunpy/physics/tests/test_differential_rotation.py b/sunpy/physics/tests/test_differential_rotation.py
--- a/sunpy/physics/tests/test_differential_rotation.py
+++ b/sunpy/physics/tests/test_differential_rotation.py
@@ -144,21 +144,41 @@ def test_warp_sun():
 
 def test_diffrot_map(aia171_test_map):
     # Test a submap without padding
-    aia_srot = diffrot_map(aia171_test_map, -5 * u.day)
+    aia_srot = diffrot_map(aia171_test_map, dt=-5 * u.day)
     assert aia_srot.dimensions == aia171_test_map.dimensions
     assert (aia171_test_map.date - timedelta(days=5)) - aia_srot.date < timedelta(seconds=1)
 
 
 def test_diffrot_submap(aia171_test_submap):
     # Test a submap without padding
-    aia_srot = diffrot_map(aia171_test_submap, -0.5 * u.day)
+    aia_srot = diffrot_map(aia171_test_submap, '2011-02-14T12:00:00')
     assert aia_srot.dimensions == aia171_test_submap.dimensions
     assert (aia171_test_submap.date - timedelta(days=0.5)) - aia_srot.date < timedelta(seconds=1)
 
 
 def test_diffrot_submap_pad(aia171_test_submap):
-    aia_srot = diffrot_map(aia171_test_submap, -0.5 * u.day, pad=True)
+    aia_srot = diffrot_map(aia171_test_submap, dt=-0.5 * u.day, pad=True)
     assert aia_srot.dimensions >= aia171_test_submap.dimensions
     assert (aia171_test_submap.date - timedelta(days=0.5)) - aia_srot.date < timedelta(seconds=1)
     assert aia_srot.meta['naxis1'] == 35
     assert aia_srot.meta['naxis2'] == 18
+
+
+def test_diffrot_allen_submap_pad(aia171_test_submap):
+    aia_srot = diffrot_map(aia171_test_submap, dt=-0.5 * u.day, pad=True, rot_type='allen')
+    assert aia_srot.dimensions >= aia171_test_submap.dimensions
+    assert (aia171_test_submap.date - timedelta(days=0.5)) - aia_srot.date < timedelta(seconds=1)
+    assert aia_srot.meta['naxis1'] == 35
+    assert aia_srot.meta['naxis2'] == 18
+
+
+def test_diffrot_manyinputs(aia171_test_map):
+    with pytest.raises(ValueError) as exc_info:
+        diffrot_map(aia171_test_map, '2010-01-01', dt=3 * u.hour)
+    assert 'Only a time or an interval is accepted' in str(exc_info.value)
+
+
+def test_diffrot_noinputs(aia171_test_map):
+    with pytest.raises(ValueError) as exc_info:
+        diffrot_map(aia171_test_map)
+    assert 'Either a time or an interval (`dt=`) needs to be provided' in str(exc_info.value)