Repository: sunpy/sunpy

Issue:
Add a submap() test that uses a mask
I noticed in https://github.com/sunpy/sunpy/pull/4727/files that we don't currently test `submap` on a `GenericMap` that has a mask set; we should add a simple test to the test suite to make sure it works as expected (ie. the pixels masked in the original map and submap should be the same).


Discussion:

Resolving patch:
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -28,7 +28,7 @@
 import sunpy.coordinates
 import sunpy.io as io
 import sunpy.visualization.colormaps
-from sunpy import config, log
+from sunpy import config
 from sunpy.coordinates import HeliographicCarrington, HeliographicStonyhurst, get_earth, sun
 from sunpy.coordinates.utils import get_rectangle_coordinates
 from sunpy.image.resample import resample as sunpy_image_resample
@@ -1446,7 +1446,9 @@ def submap(self, bottom_left, *, top_right=None, width: (u.deg, u.pix) = None, h
         Returns a submap defined by a rectangle.
 
         Any pixels which have at least part of their area inside the rectangle
-        are returned.
+        are returned. If the rectangle is defined in world coordinates, the
+        smallest array which contains all four corners of the rectangle as
+        defined in world coordinates is returned.
 
         Parameters
         ----------
@@ -1471,12 +1473,9 @@ def submap(self, bottom_left, *, top_right=None, width: (u.deg, u.pix) = None, h
 
         Notes
         -----
-        The rectangle is defined in pixel space. If coordinate input is given,
-        it is first transformed into pixel space, and the pixel indices of
-        top_right and bottom_left are used as the corners of the rectangle.
-
-        If top_right is below or to the left of bottom_left, a message is logged
-        at the debug level to the sunpy logger.
+        When specifying pixel coordinates, they are specified in Cartesian
+        order not in numpy order. So, for example, the ``bottom_left=``
+        argument should be ``[left, bottom]``.
 
         Examples
         --------
@@ -1491,60 +1490,37 @@ def submap(self, bottom_left, *, top_right=None, width: (u.deg, u.pix) = None, h
         <sunpy.map.sources.sdo.AIAMap object at ...>
         SunPy Map
         ---------
-        Observatory:                 SDO
+        Observatory:         SDO
         Instrument:          AIA 3
         Detector:            AIA
-        Measurement:                 171.0 Angstrom
+        Measurement:         171.0 Angstrom
         Wavelength:          171.0 Angstrom
         Observation Date:    2011-06-07 06:33:02
-        Exposure Time:               0.234256 s
-        Dimension:           [333. 335.] pix
+        Exposure Time:       0.234256 s
+        Dimension:           [335. 335.] pix
         Coordinate System:   helioprojective
-        Scale:                       [2.402792 2.402792] arcsec / pix
-        Reference Pixel:     [125.5 125.5] pix
+        Scale:               [2.402792 2.402792] arcsec / pix
+        Reference Pixel:     [126.5 125.5] pix
         Reference Coord:     [3.22309951 1.38578135] arcsec
-        array([[ 565.81494,  585.0416 ,  656.4552 , ..., 1178.3234 , 1005.28284,
-                977.8161 ],
-            [ 516.1865 ,  555.7032 ,  634.7365 , ..., 1024.9636 , 1010.1449 ,
-                1010.1449 ],
-            [ 620.9256 ,  620.9256 ,  654.8825 , ...,  933.8139 , 1074.4924 ,
-                1108.4492 ],
-            ...,
-            [ 212.1806 ,  232.78065,  228.66064, ...,  650.96185,  622.12177,
-                537.6615 ],
-            [ 236.07002,  222.5803 ,  218.08372, ...,  517.1058 ,  586.8026 ,
-                591.2992 ],
-            [ 187.92569,  225.1387 ,  236.3026 , ...,  619.59656,  649.367  ,
-                686.58   ]], dtype=float32)
+        ...
 
         >>> aia.submap([0,0]*u.pixel, top_right=[5,5]*u.pixel)   # doctest: +REMOTE_DATA
         <sunpy.map.sources.sdo.AIAMap object at ...>
         SunPy Map
         ---------
-        Observatory:                 SDO
+        Observatory:         SDO
         Instrument:          AIA 3
         Detector:            AIA
-        Measurement:                 171.0 Angstrom
+        Measurement:         171.0 Angstrom
         Wavelength:          171.0 Angstrom
         Observation Date:    2011-06-07 06:33:02
-        Exposure Time:               0.234256 s
+        Exposure Time:       0.234256 s
         Dimension:           [6. 6.] pix
         Coordinate System:   helioprojective
-        Scale:                       [2.402792 2.402792] arcsec / pix
+        Scale:               [2.402792 2.402792] arcsec / pix
         Reference Pixel:     [511.5 511.5] pix
         Reference Coord:     [3.22309951 1.38578135] arcsec
-        array([[-95.92475   ,   7.076416  ,  -1.9656711 ,  -2.9485066 ,
-                -0.98283553,  -6.0935802 ],
-            [-96.97533   ,  -5.1167884 ,   0.        ,   0.        ,
-                0.9746264 ,   3.8985057 ],
-            [-93.99607   ,   1.0189276 ,  -4.0757103 ,   2.0378551 ,
-                -2.0378551 ,  -7.896689  ],
-            [-96.97533   ,  -8.040668  ,  -2.9238791 ,  -5.1167884 ,
-                -0.9746264 ,  -8.040668  ],
-            [-95.92475   ,   6.028058  ,  -4.9797    ,  -1.0483578 ,
-                -3.9313421 ,  -1.0483578 ],
-            [-95.103004  ,   0.        ,  -4.993475  ,   0.        ,
-                -4.0855703 ,  -7.03626   ]], dtype=float32)
+        ...
 
         >>> width = 10 * u.arcsec
         >>> height = 10 * u.arcsec
@@ -1552,43 +1528,38 @@ def submap(self, bottom_left, *, top_right=None, width: (u.deg, u.pix) = None, h
         <sunpy.map.sources.sdo.AIAMap object at ...>
         SunPy Map
         ---------
-        Observatory:                 SDO
+        Observatory:         SDO
         Instrument:          AIA 3
         Detector:            AIA
-        Measurement:                 171.0 Angstrom
+        Measurement:         171.0 Angstrom
         Wavelength:          171.0 Angstrom
         Observation Date:    2011-06-07 06:33:02
-        Exposure Time:               0.234256 s
+        Exposure Time:       0.234256 s
         Dimension:           [5. 5.] pix
         Coordinate System:   helioprojective
-        Scale:                       [2.402792 2.402792] arcsec / pix
+        Scale:               [2.402792 2.402792] arcsec / pix
         Reference Pixel:     [125.5 125.5] pix
         Reference Coord:     [3.22309951 1.38578135] arcsec
-        array([[565.81494, 585.0416 , 656.4552 , 670.18854, 678.4286 ],
-            [516.1865 , 555.7032 , 634.7365 , 661.90424, 587.8105 ],
-            [620.9256 , 620.9256 , 654.8825 , 596.6707 , 531.18243],
-            [667.5083 , 560.52094, 651.22766, 530.28534, 495.39816],
-            [570.15643, 694.5542 , 653.0883 , 699.7374 , 583.11456]],
-            dtype=float32)
-
-        >>> bottom_left_vector = SkyCoord([0, 10]  * u.arcsec, [0, 10] * u.arcsec, frame='heliographic_stonyhurst')
+        ...
+
+        >>> bottom_left_vector = SkyCoord([0, 10]  * u.deg, [0, 10] * u.deg, frame='heliographic_stonyhurst')
         >>> aia.submap(bottom_left_vector)   # doctest: +REMOTE_DATA
         <sunpy.map.sources.sdo.AIAMap object at ...>
         SunPy Map
         ---------
-        Observatory:                 SDO
+        Observatory:         SDO
         Instrument:          AIA 3
         Detector:            AIA
-        Measurement:                 171.0 Angstrom
+        Measurement:         171.0 Angstrom
         Wavelength:          171.0 Angstrom
         Observation Date:    2011-06-07 06:33:02
-        Exposure Time:               0.234256 s
-        Dimension:           [1. 1.] pix
+        Exposure Time:       0.234256 s
+        Dimension:           [70. 69.] pix
         Coordinate System:   helioprojective
-        Scale:                       [2.402792 2.402792] arcsec / pix
+        Scale:               [2.402792 2.402792] arcsec / pix
         Reference Pixel:     [1.5 0.5] pix
         Reference Coord:     [3.22309951 1.38578135] arcsec
-        array([[209.89908]], dtype=float32)
+        ...
         """
         # Check that we have been given a valid combination of inputs
         # [False, False, False] is valid if bottom_left contains the two corner coords
@@ -1596,52 +1567,47 @@ def submap(self, bottom_left, *, top_right=None, width: (u.deg, u.pix) = None, h
                 not in [[True, False, False], [False, False, False], [False, True, True]]):
             raise ValueError("Either top_right alone or both width and height must be specified.")
         # parse input arguments
-        bottom_left, top_right = self._parse_submap_input(bottom_left, top_right, width, height)
-
-        x_pixels = u.Quantity([bottom_left[0], top_right[0]]).to_value(u.pix)
-        y_pixels = u.Quantity([bottom_left[1], top_right[1]]).to_value(u.pix)
-        if x_pixels[0] > x_pixels[1]:
-            log.debug("The rectangle is inverted in the left/right direction.")
+        pixel_corners = u.Quantity(self._parse_submap_input(bottom_left, top_right, width, height)).T
 
-        if y_pixels[0] > y_pixels[1]:
-            log.debug("The rectangle is inverted in the bottom/top direction.")
-        # Sort the pixel values so we always slice in the correct direction
-        x_pixels.sort()
-        y_pixels.sort()
+        # The pixel corners result is in Cartesian order, so the first index is
+        # columns and the second is rows.
+        bottom = np.min(pixel_corners[1]).to_value(u.pix)
+        top = np.max(pixel_corners[1]).to_value(u.pix)
+        left = np.min(pixel_corners[0]).to_value(u.pix)
+        right = np.max(pixel_corners[0]).to_value(u.pix)
 
         # Round the lower left pixel to the nearest integer
         # We want 0.5 to be rounded up to 1, so use floor(x + 0.5)
-        x_pixels[0] = np.floor(x_pixels[0] + 0.5)
-        y_pixels[0] = np.floor(y_pixels[0] + 0.5)
+        bottom = np.floor(bottom + 0.5)
+        left = np.floor(left + 0.5)
+
         # Round the top right pixel to the nearest integer, then add 1 for array indexing
         # We want e.g. 2.5 to be rounded down to 2, so use ceil(x - 0.5)
-        x_pixels[1] = np.ceil(x_pixels[1] - 0.5) + 1
-        y_pixels[1] = np.ceil(y_pixels[1] - 0.5) + 1
-
-        x_pixels = np.array(x_pixels)
-        y_pixels = np.array(y_pixels)
+        top = np.ceil(top - 0.5) + 1
+        right = np.ceil(right - 0.5) + 1
 
         # Clip pixel values to max of array, prevents negative
         # indexing
-        x_pixels = np.clip(x_pixels, 0, self.data.shape[1])
-        y_pixels = np.clip(y_pixels, 0, self.data.shape[0])
+        bottom = int(np.clip(bottom, 0, self.data.shape[0]))
+        top = int(np.clip(top, 0, self.data.shape[0]))
+        left = int(np.clip(left, 0, self.data.shape[1]))
+        right = int(np.clip(right, 0, self.data.shape[1]))
 
+        arr_slice = np.s_[bottom:top, left:right]
         # Get ndarray representation of submap
-        xslice = slice(int(x_pixels[0]), int(x_pixels[1]))
-        yslice = slice(int(y_pixels[0]), int(y_pixels[1]))
-        new_data = self.data[yslice, xslice].copy()
+        new_data = self.data[arr_slice].copy()
 
         # Make a copy of the header with updated centering information
         new_meta = self.meta.copy()
         # Add one to go from zero-based to one-based indexing
-        new_meta['crpix1'] = self.reference_pixel.x.to_value(u.pix) + 1 - x_pixels[0]
-        new_meta['crpix2'] = self.reference_pixel.y.to_value(u.pix) + 1 - y_pixels[0]
+        new_meta['crpix1'] = self.reference_pixel.x.to_value(u.pix) + 1 - left
+        new_meta['crpix2'] = self.reference_pixel.y.to_value(u.pix) + 1 - bottom
         new_meta['naxis1'] = new_data.shape[1]
         new_meta['naxis2'] = new_data.shape[0]
 
         # Create new map instance
         if self.mask is not None:
-            new_mask = self.mask[yslice, xslice].copy()
+            new_mask = self.mask[arr_slice].copy()
             # Create new map with the modification
             new_map = self._new_instance(new_data, new_meta, self.plot_settings, mask=new_mask)
             return new_map
@@ -1678,7 +1644,10 @@ def _parse_submap_quantity_input(self, bottom_left, top_right, width, height):
                                 "must be a Quantity in units of pixels.")
             # Add width and height to get top_right
             top_right = u.Quantity([bottom_left[0] + width, bottom_left[1] + height])
-        return bottom_left, top_right
+
+        top_left = u.Quantity([top_right[0], bottom_left[1]])
+        bottom_right = u.Quantity([bottom_left[0], top_right[1]])
+        return bottom_left, top_left, top_right, bottom_right
 
     @_parse_submap_input.register(SkyCoord)
     def _parse_submap_coord_input(self, bottom_left, top_right, width, height):
@@ -1687,10 +1656,14 @@ def _parse_submap_coord_input(self, bottom_left, top_right, width, height):
                                                            top_right=top_right,
                                                            width=width,
                                                            height=height)
-        # Convert cordinates to pixels
-        bottom_left = self.world_to_pixel(bottom_left)
-        top_right = self.world_to_pixel(top_right)
-        return bottom_left, top_right
+        frame = bottom_left.frame
+        left_lon, bottom_lat = self._get_lon_lat(bottom_left)
+        right_lon, top_lat = self._get_lon_lat(top_right)
+        corners = SkyCoord([left_lon, left_lon, right_lon, right_lon],
+                           [bottom_lat, top_lat, top_lat, bottom_lat],
+                           frame=frame)
+
+        return tuple(u.Quantity(self.wcs.world_to_pixel(corners), u.pix).T)
 
     @u.quantity_input
     def superpixel(self, dimensions: u.pixel, offset: u.pixel = (0, 0)*u.pixel, func=np.sum):


Test patch:
diff --git a/sunpy/map/tests/conftest.py b/sunpy/map/tests/conftest.py
new file mode 100644
--- /dev/null
+++ b/sunpy/map/tests/conftest.py
@@ -0,0 +1,128 @@
+import os
+
+import numpy as np
+import pytest
+
+import astropy.units as u
+from astropy.coordinates import SkyCoord
+from astropy.time import Time
+
+import sunpy.coordinates
+import sunpy.data.test
+import sunpy.io
+import sunpy.map
+import sunpy.sun
+from sunpy.coordinates import sun
+
+testpath = sunpy.data.test.rootdir
+
+
+@pytest.fixture
+def test_map(request):
+    return request.getfixturevalue(request.param)
+
+
+@pytest.fixture
+def hmi_test_map():
+    (data, header), = sunpy.io.read_file(os.path.join(testpath, 'resampled_hmi.fits'))
+
+    # Get rid of the blank keyword to prevent some astropy fits fixing warnings
+    header.pop('CRDER2')
+    header.pop('CRDER1')
+    return sunpy.map.Map((data, header))
+
+
+@pytest.fixture
+def aia171_test_map():
+    (data, header), = sunpy.io.read_file(os.path.join(testpath, 'aia_171_level1.fits'))
+
+    # Get rid of the blank keyword to prevent some astropy fits fixing warnings
+    header.pop('BLANK')
+    return sunpy.map.Map((data, header))
+
+
+@pytest.fixture
+def aia171_roll_map(aia171_test_map):
+    return aia171_test_map.rotate(-45*u.deg)
+
+
+@pytest.fixture
+def heliographic_test_map():
+    (data, header), = sunpy.io.read_file(os.path.join(testpath, 'heliographic_phase_map.fits.gz'))
+
+    # Fix unit strings to prevent some astropy fits fixing warnings
+    header['CUNIT1'] = 'deg'
+    header['CUNIT2'] = 'deg'
+    # Set observer location to avoid warnings later
+    header['HGLN_OBS'] = 0.0
+    return sunpy.map.Map((data, header))
+
+
+@pytest.fixture
+def aia171_test_map_with_mask(aia171_test_map):
+    shape = aia171_test_map.data.shape
+    mask = np.zeros_like(aia171_test_map.data, dtype=bool)
+    mask[0:shape[0] // 2, 0:shape[1] // 2] = True
+    return sunpy.map.Map(np.ma.array(aia171_test_map.data, mask=mask), aia171_test_map.meta)
+
+
+@pytest.fixture
+def generic_map():
+    data = np.ones([6, 6], dtype=np.float64)
+    dobs = Time('1970-01-01T00:00:00')
+    l0 = sun.L0(dobs).to_value(u.deg)
+    b0 = sun.B0(dobs).to_value(u.deg)
+    dsun = sun.earth_distance(dobs).to_value(u.m)
+    header = {
+        'CRVAL1': 0,
+        'CRVAL2': 0,
+        'CRPIX1': 5,
+        'CRPIX2': 5,
+        'CDELT1': 10,
+        'CDELT2': 10,
+        'CUNIT1': 'arcsec',
+        'CUNIT2': 'arcsec',
+        'CTYPE1': 'HPLN-TAN',
+        'CTYPE2': 'HPLT-TAN',
+        'PC1_1': 0,
+        'PC1_2': -1,
+        'PC2_1': 1,
+        'PC2_2': 0,
+        'NAXIS1': 6,
+        'NAXIS2': 6,
+        'date-obs': dobs.isot,
+        'crln_obs': l0,
+        'crlt_obs': b0,
+        "dsun_obs": dsun,
+        'mjd-obs': 40587.0,
+        'obsrvtry': 'Foo',
+        'detector': 'bar',
+        'wavelnth': 10,
+        'waveunit': 'm',
+        'bunit': 'ct/s',
+    }
+    return sunpy.map.Map((data, header))
+
+
+@pytest.fixture
+def simple_map():
+    # A 3x3 map, with it's center at (0, 0), and scaled differently in
+    # each direction
+    data = np.arange(9).reshape((3, 3))
+    ref_coord = SkyCoord(0.0, 0.0, frame='helioprojective', obstime='now', unit='deg',
+                         observer=SkyCoord(0 * u.deg, 0 * u.deg, 1 * u.AU,
+                                           frame='heliographic_stonyhurst'))
+    ref_pix = [1, 1] * u.pix
+    scale = [2, 1] * u.arcsec / u.pix
+    header = sunpy.map.make_fitswcs_header(data, ref_coord, reference_pixel=ref_pix, scale=scale)
+    return sunpy.map.Map(data, header)
+
+
+@pytest.fixture
+def eit_test_map():
+    """
+    Load SunPy's test EIT image.
+    """
+    testpath = sunpy.data.test.rootdir
+    eit_file = os.path.join(testpath, "EIT", "efz20040301.020010_s.fits")
+    return sunpy.map.Map(eit_file)
diff --git a/sunpy/map/tests/test_compositemap.py b/sunpy/map/tests/test_compositemap.py
--- a/sunpy/map/tests/test_compositemap.py
+++ b/sunpy/map/tests/test_compositemap.py
@@ -1,7 +1,6 @@
 """
 Test Composite Map
 """
-import os
 
 import pytest
 
@@ -16,16 +15,6 @@
               pytest.mark.filterwarnings(r'ignore:Unable to treat `\.meta` as a FITS header')]
 
 
-@pytest.fixture
-def aia171_test_map():
-    return sunpy.map.Map(os.path.join(testpath, 'aia_171_level1.fits'))
-
-
-@pytest.fixture
-def hmi_test_map():
-    return sunpy.map.Map(os.path.join(testpath, 'resampled_hmi.fits'))
-
-
 @pytest.fixture
 def composite_test_map(aia171_test_map, hmi_test_map):
     return sunpy.map.Map(aia171_test_map, hmi_test_map, composite=True)
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
@@ -31,94 +31,6 @@
 testpath = sunpy.data.test.rootdir
 
 
-@pytest.fixture
-def hmi_test_map():
-    return sunpy.map.Map(os.path.join(testpath, "resampled_hmi.fits"))
-
-
-@pytest.fixture
-def aia171_test_map():
-    map = sunpy.map.Map(os.path.join(testpath, 'aia_171_level1.fits'))
-
-    # Get rid of the blank keyword to prevent some astropy fits fixing warnings
-    header = dict(map.meta)
-    header.pop('blank')
-    return sunpy.map.Map((map.data, header))
-
-
-@pytest.fixture
-def heliographic_test_map():
-    map = sunpy.map.Map(os.path.join(testpath, 'heliographic_phase_map.fits.gz'))
-
-    # Fix unit strings to prevent some astropy fits fixing warnings
-    header = dict(map.meta)
-    header['cunit1'] = 'deg'
-    header['cunit2'] = 'deg'
-    # Set observer location to avoid warnings later
-    header['hgln_obs'] = 0.0
-    return sunpy.map.Map((map.data, header))
-
-
-@pytest.fixture
-def aia171_test_map_with_mask(aia171_test_map):
-    shape = aia171_test_map.data.shape
-    mask = np.zeros_like(aia171_test_map.data, dtype=bool)
-    mask[0:shape[0] // 2, 0:shape[1] // 2] = True
-    return sunpy.map.Map(np.ma.array(aia171_test_map.data, mask=mask), aia171_test_map.meta)
-
-
-@pytest.fixture
-def generic_map():
-    data = np.ones([6, 6], dtype=np.float64)
-    dobs = Time('1970-01-01T00:00:00')
-    l0 = sun.L0(dobs).to_value(u.deg)
-    b0 = sun.B0(dobs).to_value(u.deg)
-    dsun = sun.earth_distance(dobs).to_value(u.m)
-    header = {
-        'CRVAL1': 0,
-        'CRVAL2': 0,
-        'CRPIX1': 5,
-        'CRPIX2': 5,
-        'CDELT1': 10,
-        'CDELT2': 10,
-        'CUNIT1': 'arcsec',
-        'CUNIT2': 'arcsec',
-        'CTYPE1': 'HPLN-TAN',
-        'CTYPE2': 'HPLT-TAN',
-        'PC1_1': 0,
-        'PC1_2': -1,
-        'PC2_1': 1,
-        'PC2_2': 0,
-        'NAXIS1': 6,
-        'NAXIS2': 6,
-        'date-obs': dobs.isot,
-        'crln_obs': l0,
-        'crlt_obs': b0,
-        "dsun_obs": dsun,
-        'mjd-obs': 40587.0,
-        'obsrvtry': 'Foo',
-        'detector': 'bar',
-        'wavelnth': 10,
-        'waveunit': 'm',
-        'bunit': 'ct/s',
-    }
-    return sunpy.map.Map((data, header))
-
-
-@pytest.fixture
-def simple_map():
-    # A 3x3 map, with it's center at (0, 0), and scaled differently in
-    # each direction
-    data = np.arange(9).reshape((3, 3))
-    ref_coord = SkyCoord(0.0, 0.0, frame='helioprojective', obstime='now', unit='deg',
-                         observer=SkyCoord(0 * u.deg, 0 * u.deg, 1 * u.AU,
-                                           frame='heliographic_stonyhurst'))
-    ref_pix = [1, 1] * u.pix
-    scale = [2, 1] * u.arcsec / u.pix
-    header = sunpy.map.make_fitswcs_header(data, ref_coord, reference_pixel=ref_pix, scale=scale)
-    return sunpy.map.Map(data, header)
-
-
 def test_fits_data_comparison(aia171_test_map):
     """Make sure the data is the same in pyfits and SunPy"""
     with pytest.warns(VerifyWarning, match="Invalid 'BLANK' keyword in header."):
@@ -564,7 +476,50 @@ def test_submap_world(simple_map, rect, submap_out):
         np.testing.assert_equal(submap.data, submap_out)
 
 
-# Check that submap works with units convertable to pix but that aren't pix
+@pytest.mark.parametrize('test_map', ("aia171_roll_map", "aia171_test_map",
+                                      "hmi_test_map", "aia171_test_map_with_mask"),
+                         indirect=['test_map'])
+def test_submap_world_corners(test_map):
+    """
+    This test checks that when an unaligned map is cropped with submap that the
+    resulting map contains all four corners of the input world coordinate
+    bounding box.
+    """
+    corners = SkyCoord(Tx=[300, 300, 800, 800], Ty=[0, 500, 500, 0],
+                       unit=u.arcsec, frame=test_map.coordinate_frame)
+
+    submap = test_map.submap(corners[0], top_right=corners[2])
+
+    pix_corners = np.array(submap.wcs.world_to_pixel(corners)).T
+    for pix_corner in pix_corners:
+        assert ((-0.5, -0.5) <= pix_corner).all()
+        assert (pix_corner <= submap.data.shape[::-1]).all()
+
+    if test_map.mask is not None:
+        assert submap.mask.shape == submap.data.shape
+
+
+@pytest.mark.parametrize('test_map', ("aia171_test_map", "heliographic_test_map"),
+                         indirect=['test_map'])
+def test_submap_hgs_corners(test_map):
+    """
+    This test checks that when an unaligned map is cropped with submap that the
+    resulting map contains all four corners of the input world coordinate
+    bounding box.
+    """
+    corners = SkyCoord([10, 10, 40, 40], [-10, 30, 30, -10],
+                       unit=u.deg, frame="heliographic_stonyhurst",
+                       obstime=test_map.date)
+
+    submap = test_map.submap(corners[0], top_right=corners[2])
+
+    pix_corners = np.array(submap.wcs.world_to_pixel(corners)).T
+    for pix_corner in pix_corners:
+        assert ((-0.5, -0.5) <= pix_corner).all()
+        assert (pix_corner <= submap.data.shape[::-1]).all()
+
+
+# Check that submap works with units convertible to pix but that aren't pix
 @pytest.mark.parametrize('unit', [u.pix, u.mpix * 1e3])
 def test_submap_data_header(generic_map, unit):
     """Check data and header information for a submap"""
diff --git a/sunpy/map/tests/test_mapsequence.py b/sunpy/map/tests/test_mapsequence.py
--- a/sunpy/map/tests/test_mapsequence.py
+++ b/sunpy/map/tests/test_mapsequence.py
@@ -1,7 +1,6 @@
 """
 Test mapsequence functionality
 """
-import os
 from unittest import mock
 
 import numpy as np
@@ -16,72 +15,38 @@
 
 
 @pytest.fixture
-def aia_map():
-    """
-    Load SunPy's test AIA image.
-    """
-    testpath = sunpy.data.test.rootdir
-    aia_file = os.path.join(testpath, "aia_171_level1.fits")
-    return sunpy.map.Map(aia_file)
-
-
-@pytest.fixture
-def eit_map():
-    """
-    Load SunPy's test EIT image.
-    """
-    testpath = sunpy.data.test.rootdir
-    eit_file = os.path.join(testpath, "EIT", "efz20040301.020010_s.fits")
-    return sunpy.map.Map(eit_file)
-
-
-@pytest.fixture
-def masked_aia_map(aia_map):
-    """
-    Put a simple mask in the test AIA image.  A rectangular (not square) block
-    of True values are included to test that operations on the mask respect how
-    the mask is stored.
-    """
-    aia_map_data = aia_map.data
-    aia_map_mask = np.zeros_like(aia_map_data)
-    aia_map_mask[0:2, 0:3] = True
-    return sunpy.map.Map(np.ma.masked_array(aia_map_data, mask=aia_map_mask),
-                         aia_map.meta)
-
-
-@pytest.fixture
-def mapsequence_all_the_same(aia_map):
+def mapsequence_all_the_same(aia171_test_map):
     """ Simple `sunpy.map.mapsequence` for testing."""
-    return sunpy.map.Map([aia_map, aia_map], sequence=True)
+    return sunpy.map.Map([aia171_test_map, aia171_test_map], sequence=True)
 
 
 @pytest.fixture
-def mapsequence_different_maps(aia_map, eit_map):
+def mapsequence_different_maps(aia171_test_map, eit_test_map):
     """
     Simple `sunpy.map.mapsequence` for testing, in which there are different maps
     """
-    return sunpy.map.Map([aia_map, eit_map], sequence=True)
+    return sunpy.map.Map([aia171_test_map, eit_test_map], sequence=True)
 
 
 @pytest.fixture
-def mapsequence_all_the_same_all_have_masks(masked_aia_map):
+def mapsequence_all_the_same_all_have_masks(aia171_test_map_with_mask):
     """ Simple `sunpy.map.mapsequence` for testing, in which all the maps have
     masks."""
-    return sunpy.map.Map([masked_aia_map, masked_aia_map], sequence=True)
+    return sunpy.map.Map([aia171_test_map_with_mask, aia171_test_map_with_mask], sequence=True)
 
 
 @pytest.fixture
-def mapsequence_all_the_same_some_have_masks(aia_map, masked_aia_map):
+def mapsequence_all_the_same_some_have_masks(aia171_test_map, aia171_test_map_with_mask):
     """ Simple `sunpy.map.mapsequence` for testing, in which at least some of the
     maps have masks."""
-    return sunpy.map.Map([masked_aia_map, masked_aia_map, aia_map], sequence=True)
+    return sunpy.map.Map([aia171_test_map_with_mask, aia171_test_map_with_mask, aia171_test_map], sequence=True)
 
 
 @pytest.fixture()
-def mapsequence_different(aia_map):
+def mapsequence_different(aia171_test_map):
     """ Mapsequence allows that the size of the image data in each map be
     different.  This mapsequence contains such maps."""
-    return sunpy.map.Map([aia_map, aia_map.superpixel((4, 4) * u.pix)], sequence=True)
+    return sunpy.map.Map([aia171_test_map, aia171_test_map.superpixel((4, 4) * u.pix)], sequence=True)
 
 
 def test_all_maps_same_shape(mapsequence_all_the_same, mapsequence_different):
diff --git a/sunpy/map/tests/test_maputils.py b/sunpy/map/tests/test_maputils.py
--- a/sunpy/map/tests/test_maputils.py
+++ b/sunpy/map/tests/test_maputils.py
@@ -1,12 +1,9 @@
-import os
-
 import numpy as np
 import pytest
 
 import astropy.units as u
 from astropy.coordinates import BaseCoordinateFrame, SkyCoord
 
-import sunpy.data.test
 import sunpy.map
 from sunpy.coordinates import HeliographicStonyhurst
 from sunpy.coordinates.frames import HeliographicCarrington
@@ -27,13 +24,6 @@
     solar_angular_radius,
 )
 
-testpath = sunpy.data.test.rootdir
-
-
-@pytest.fixture
-def aia171_test_map():
-    return sunpy.map.Map(os.path.join(testpath, 'aia_171_level1.fits'))
-
 
 @pytest.fixture
 def all_off_disk_map(aia171_test_map):