Repository: sunpy/sunpy

Issue:
Map should error nicely if not passed a 2D array
Hello, I am getting the following errors when attempting to create a map from the attached fits file:

``` python
In [6]: map.Map('1130643840_vv_c076-077_f8-14_t034345_t034444_XX_d002.fits')
Out[6]: ---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/Users/kamen/anaconda/lib/python2.7/site-packages/IPython/core/formatters.pyc in __call__(self, obj)
    697                 type_pprinters=self.type_printers,
    698                 deferred_pprinters=self.deferred_printers)
--> 699             printer.pretty(obj)
    700             printer.flush()
    701             return stream.getvalue()

/Users/kamen/anaconda/lib/python2.7/site-packages/IPython/lib/pretty.pyc in pretty(self, obj)
    381                             if callable(meth):
    382                                 return meth(obj, self, cycle)
--> 383             return _default_pprint(obj, self, cycle)
    384         finally:
    385             self.end_group()

/Users/kamen/anaconda/lib/python2.7/site-packages/IPython/lib/pretty.pyc in _default_pprint(obj, p, cycle)
    501     if _safe_getattr(klass, '__repr__', None) not in _baseclass_reprs:
    502         # A user-provided repr. Find newlines and replace them with p.break_()
--> 503         _repr_pprint(obj, p, cycle)
    504         return
    505     p.begin_group(1, '<')

/Users/kamen/anaconda/lib/python2.7/site-packages/IPython/lib/pretty.pyc in _repr_pprint(obj, p, cycle)
    692     """A pprint that just redirects to the normal repr function."""
    693     # Find newlines and replace them with p.break_()
--> 694     output = repr(obj)
    695     for idx,output_line in enumerate(output.splitlines()):
    696         if idx:

/Users/kamen/anaconda/lib/python2.7/site-packages/sunpy/map/mapbase.py in __repr__(self)
    221            obs=self.observatory, inst=self.instrument, det=self.detector,
    222            meas=self.measurement, wave=self.wavelength, date=self.date, dt=self.exposure_time,
--> 223            dim=u.Quantity(self.dimensions),
    224            scale=u.Quantity(self.scale),
    225            tmf=TIME_FORMAT)

/Users/kamen/anaconda/lib/python2.7/site-packages/sunpy/map/mapbase.py in dimensions(self)
    301         The dimensions of the array (x axis first, y axis second).
    302         """
--> 303         return Pair(*u.Quantity(np.flipud(self.data.shape), 'pixel'))
    304 
    305     @property

TypeError: __new__() takes exactly 3 arguments (5 given)
```

Any thoughts? The fits file contains radio observations of the Sun by the Murchison Widefield Array telescope in Australia. Thanks!

[1130643840_c076-077_f8-14_t034345_t034444_XX_d002.fits.zip](https://github.com/sunpy/sunpy/files/527362/1130643840_c076-077_f8-14_t034345_t034444_XX_d002.fits.zip)



Discussion:
From a quick look over your traceback the reason is that your FITS file is 4D and Map only officially supports 2D files.

I am still going to classify this as a bug because up to now Map has been able to load multidim files (all be it probably break at somepoint), and at the very least should throw an error somewhat nicer than this.

What is the data in this file? You might want to do something like this: https://github.com/sunpy/sunpy/blob/master/sunpy/instr/iris/iris.py#L12 to convert slices into a map

Thanks. The data is 4D as you mentioned - X, Y coordinates, frequency, and Stokes parameters. In this case, it is a single frequency and the I Stokes parameter, so it is [1,1,1024,1024] in dimensions. I can easily get rid of the redundant dimensions and make a Map in this way, but I suspect I'd have to modify the header as well.

As long as the HPC (X,Y) coordinates are CYTPE1 and CTYPE2 Map will ignore other dimensions in the header data.

Hi, I'm happy to take a look at this! Would the correct treatment be to raise an error if the file has more than two dimensions, or is that too heavy handed if you unofficially support 2+ dimension files?

I've been looking into the code and docs and have figured out why this occurs - as pointed out above **repr**() depends on self.dimensions which depends on the named tuple Pair, which only has two attributes (for 2 dimensions).

The easiest solution would be to raise an error if it was not 2D, but that would exclude the file of the original report here, in an ideal world a file with dimensions `[1, 1, y, x]` would read.

Maybe the best approach would be to just slice the data array to 2D in the `__init__`, then it would always take the last two dimensions (which if I am not mistaken should correspond to '1' and '2' keys in the header) and it would happily ignore everything else. In this situation it would be correct to issue a warning to the user so that they know we are silently ignoring a hunk of the input data.

To clarify, the ordering of the arrays FITS files provide are reversed (they are column major) so the last two axes of the array would be what we would need, unless I am mistaken. (ping @dpshelio ?)


Resolving patch:
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -158,10 +158,29 @@ class GenericMap(NDData):
         this class will not process the WCS.
         Also the rotation_matrix does not work if the CDELT1 and CDELT2
         keywords are exactly equal.
+        Also, if a file with more than two dimensions is feed into the class,
+        only the first two dimensions (NAXIS1, NAXIS2) will be loaded and the
+        rest will be discarded.
     """
 
     def __init__(self, data, header, plot_settings=None, **kwargs):
 
+        # If the data has more than two dimensions, the first dimensions
+        # (NAXIS1, NAXIS2) are used and the rest are discarded.
+        ndim = data.ndim
+        if ndim > 2:
+            # We create a slice that removes all but the 'last' two
+            # dimensions. (Note dimensions in ndarray are in reverse order)
+
+            new_2d_slice = [0]*(ndim-2)
+            new_2d_slice.extend([slice(None), slice(None)])
+            data = data[new_2d_slice]
+            # Warn the user that the data has been truncated
+            warnings.warn_explicit("This file contains more than 2 dimensions. "
+                                   "Only the first two dimensions will be used."
+                                   " The truncated data will not be saved in a new file.",
+                                   Warning, __file__, inspect.currentframe().f_back.f_lineno)
+
         super(GenericMap, self).__init__(data, meta=header, **kwargs)
 
         # Correct possibly missing meta keywords


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
@@ -30,7 +30,6 @@
 
 testpath = sunpy.data.test.rootdir
 
-
 @pytest.fixture
 def aia171_test_map():
     return sunpy.map.Map(os.path.join(testpath, 'aia_171_level1.fits'))
@@ -639,3 +638,19 @@ def test_hc_warn():
 
     with pytest.warns(UserWarning):
         sunpy.map.Map((data, header))
+
+# Dimension testing
+
+
+def test_more_than_two_dimensions():
+    """Checks to see if an appropriate error is raised when a FITs with more than two dimensions is loaded.
+    We need to load a >2-dim dataset with a TELESCOP header"""
+
+    # Data crudely represnts 4 stokes, 4 wavelengths with Y,X of 3 and 5.
+    bad_data = np.random.rand(4, 4, 3, 5)
+    hdr = fits.Header()
+    hdr['TELESCOP'] = 'XXX'
+    bad_map = sunpy.map.Map(bad_data, hdr)
+    # Test fails if map.ndim > 2 and if the dimensions of the array are wrong.
+    assert bad_map.ndim is 2
+    assert_quantity_allclose(bad_map.dimensions, (5, 3) * u.pix)