Repository: sunpy/sunpy

Issue:
Broken timerange parsing in rhessi.backprojection()
The following example shows a genuine bug. The timerange is set automatically from the event list file, but the code doesn't recognize the format:

```
ipdb> info_parameters.data.field('ABSOLUTE_TIME_RANGE')[0]
array([  7.30206360e+08,   7.30206403e+08])
```

As I understand this should be in seconds unit since a reference date, but it would make more sense if someone familiar with the data and the desired behaviour would fix it (I'm not such a person).

I'll skip the relevant example lines for the doctest, ones it's fixed it should be unskipped.

```
    >>> import sunpy.data                                                                           
    >>> import sunpy.data.sample                                                                    
    >>> import sunpy.instr.rhessi as rhessi                                                         
    >>> sunpy.data.download_sample_data(overwrite=False)                            
    >>> map = rhessi.backprojection(sunpy.data.sample.RHESSI_EVENT_LIST)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-35-2587d1ec7c8a> in <module>()
----> 1 map = rhessi.backprojection(sunpy.data.sample.RHESSI_EVENT_LIST)

/Users/bsipocz/munka/devel/sunpy/sunpy/instr/rhessi.py in backprojection(calibrated_event_list, pixel_size, image_dim)
    405         "CDELT1": pixel_size[0],
    406         "NAXIS1": image_dim[0],
--> 407         "CRVAL1": xyoffset[0],
    408         "CRPIX1": image_dim[0].value/2 + 0.5,
    409         "CUNIT1": "arcsec",

/Users/bsipocz/munka/devel/sunpy/sunpy/time/timerange.py in center(self)
    128         value : `datetime.datetime`
    129         """
--> 130         return self.start + self.dt / 2
    131 
    132     @property

/Users/bsipocz/munka/devel/sunpy/sunpy/time/timerange.py in dt(self)
    117         dt : `datetime.timedelta`
    118         """
--> 119         return self._t2 - self._t1
    120 
    121     @property

TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'

```



Discussion:

Resolving patch:
diff --git a/sunpy/instr/rhessi.py b/sunpy/instr/rhessi.py
--- a/sunpy/instr/rhessi.py
+++ b/sunpy/instr/rhessi.py
@@ -439,6 +439,7 @@ def _backproject(calibrated_event_list, detector=8, pixel_size=(1., 1.),
     return bproj_image
 
 
+@u.quantity_input(pixel_size=u.arcsec, image_dim=u.pix)
 def backprojection(calibrated_event_list, pixel_size=(1., 1.) * u.arcsec,
                    image_dim=(64, 64) * u.pix):
     """
@@ -471,21 +472,15 @@ def backprojection(calibrated_event_list, pixel_size=(1., 1.) * u.arcsec,
     >>> map.peek()   # doctest: +SKIP
 
     """
-    if not isinstance(pixel_size, u.Quantity):
-        raise ValueError("Must be astropy Quantity in arcseconds")
-    try:
-        pixel_size = pixel_size.to(u.arcsec)
-    except:
-        raise ValueError("'{0}' is not a valid pixel_size unit".format(pixel_size.unit))
-    if not (isinstance(image_dim, u.Quantity) and image_dim.unit == 'pix'):
-        raise ValueError("Must be astropy Quantity in pixels")
+    pixel_size = pixel_size.to(u.arcsec)
+    image_dim = np.array(image_dim.to(u.pix).value, dtype=int)
 
     afits = fits.open(calibrated_event_list)
     info_parameters = afits[2]
     xyoffset = info_parameters.data.field('USED_XYOFFSET')[0]
     time_range = TimeRange(info_parameters.data.field('ABSOLUTE_TIME_RANGE')[0])
 
-    image = np.zeros(image_dim.value)
+    image = np.zeros(image_dim)
 
     # find out what detectors were used
     det_index_mask = afits[1].data.field('det_index_mask')[0]
@@ -493,30 +488,29 @@ def backprojection(calibrated_event_list, pixel_size=(1., 1.) * u.arcsec,
     for detector in detector_list:
         if detector > 0:
             image = image + _backproject(calibrated_event_list, detector=detector, pixel_size=pixel_size.value,
-                                         image_dim=image_dim.value)
+                                         image_dim=image_dim)
 
     dict_header = {
-        "DATE-OBS": time_range.center().strftime("%Y-%m-%d %H:%M:%S"),
+        "DATE-OBS": time_range.center.strftime("%Y-%m-%d %H:%M:%S"),
         "CDELT1": pixel_size[0],
         "NAXIS1": image_dim[0],
         "CRVAL1": xyoffset[0],
-        "CRPIX1": image_dim[0].value/2 + 0.5,
+        "CRPIX1": image_dim[0]/2 + 0.5,
         "CUNIT1": "arcsec",
         "CTYPE1": "HPLN-TAN",
         "CDELT2": pixel_size[1],
         "NAXIS2": image_dim[1],
         "CRVAL2": xyoffset[1],
-        "CRPIX2": image_dim[0].value/2 + 0.5,
+        "CRPIX2": image_dim[0]/2 + 0.5,
         "CUNIT2": "arcsec",
         "CTYPE2": "HPLT-TAN",
         "HGLT_OBS": 0,
         "HGLN_OBS": 0,
-        "RSUN_OBS": solar_semidiameter_angular_size(time_range.center()).value,
+        "RSUN_OBS": solar_semidiameter_angular_size(time_range.center).value,
         "RSUN_REF": sunpy.sun.constants.radius.value,
-        "DSUN_OBS": sunearth_distance(time_range.center()) * sunpy.sun.constants.au.value
+        "DSUN_OBS": sunearth_distance(time_range.center) * sunpy.sun.constants.au.value
     }
 
-    header = sunpy.map.MetaDict(dict_header)
-    result_map = sunpy.map.Map(image, header)
+    result_map = sunpy.map.Map(image, dict_header)
 
     return result_map


Test patch:
diff --git a/sunpy/instr/tests/test_rhessi.py b/sunpy/instr/tests/test_rhessi.py
new file mode 100644
--- /dev/null
+++ b/sunpy/instr/tests/test_rhessi.py
@@ -0,0 +1,16 @@
+"""
+Because rhessi.py is code as well.
+"""
+import os
+
+import sunpy.map
+import sunpy.data.test
+import sunpy.instr.rhessi as rhessi
+
+
+testpath = sunpy.data.test.rootdir
+
+
+def test_backprojection():
+    amap = rhessi.backprojection(os.path.join(testpath, 'hsi_calib_ev_20020220_1106_20020220_1106_25_40.fits'))
+    assert isinstance(amap, sunpy.map.GenericMap)