Repository: sunpy/sunpy

Issue:
Allow for pixel input to `GenericMap.draw_quadrangle()`
`GenericMap.draw_quadrangle()` currently accepts only angular input.  We can have it accept pixel input, which would (by default) plot a rectangle on the pixel grid.


Discussion:
If the units were in pixel the `vertex_unit` kwarg to `Quadrangle` should be `u.pix` and the `transform` argument should by default be empty.
Wouldn't `transform` still need to be a "pixel"->"data" transform?
WCSAxes is in pixel space by default, so I think that's the default if you don't specify one?
@ayshih Skycoord can't accept `pix` as a unit right,  would the `bottom_left` coordinate have to be defined in pixels as well?
Yes, rather than using a SkyCoord the pixel coordinates would be passed as Quantity objects with units of pix. There is prior art for this in the submap method if you want to use that.
> Yes, rather than using a SkyCoord the pixel coordinates would be passed as Quantity objects with units of pix. There is prior art for this in the submap method if you want to use that.

Can I work on this if nobody else is?


Sure.

Resolving patch:
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -2059,7 +2059,7 @@ def draw_limb(self, axes=None, *, resolution=1000, **kwargs):
         return visible, hidden
 
     @u.quantity_input
-    def draw_quadrangle(self, bottom_left, *, width: u.deg = None, height: u.deg = None,
+    def draw_quadrangle(self, bottom_left, *, width: (u.deg, u.pix) = None, height: (u.deg, u.pix) = None,
                         axes=None, top_right=None, **kwargs):
         """
         Draw a quadrangle defined in world coordinates on the plot using Astropy's
@@ -2075,11 +2075,14 @@ def draw_quadrangle(self, bottom_left, *, width: u.deg = None, height: u.deg = N
 
         Parameters
         ----------
-        bottom_left : `~astropy.coordinates.SkyCoord`
-            The bottom-left coordinate of the quadrangle. It can
-            have shape ``(2,)`` to simultaneously define ``top_right``.
-        top_right : `~astropy.coordinates.SkyCoord`
-            The top-right coordinate of the quadrangle.
+        bottom_left : `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity`
+            The bottom-left coordinate of the rectangle. If a `~astropy.coordinates.SkyCoord` it can
+            have shape ``(2,)`` and simultaneously define ``top_right``. If specifying
+            pixel coordinates it must be given as an `~astropy.units.Quantity`
+            object with pixel units (e.g., ``pix``).
+        top_right : `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity`, optional
+            The top-right coordinate of the quadrangle. If ``top_right`` is
+            specified ``width`` and ``height`` must be omitted.
         width : `astropy.units.Quantity`, optional
             The width of the quadrangle. Required if ``top_right`` is omitted.
         height : `astropy.units.Quantity`
@@ -2104,19 +2107,28 @@ def draw_quadrangle(self, bottom_left, *, width: u.deg = None, height: u.deg = N
         """
         axes = self._check_axes(axes)
 
-        bottom_left, top_right = get_rectangle_coordinates(
-            bottom_left, top_right=top_right, width=width, height=height)
+        if isinstance(bottom_left, u.Quantity):
+            anchor, _, top_right, _ = self._parse_submap_quantity_input(bottom_left, top_right, width, height)
+            width, height = top_right - anchor
+            transform = axes.get_transform(self.wcs if self.wcs is not axes.wcs else 'pixel')
+            kwargs.update({"vertex_unit": u.pix})
+
+        else:
+            bottom_left, top_right = get_rectangle_coordinates(
+                bottom_left, top_right=top_right, width=width, height=height)
 
-        width = Longitude(top_right.spherical.lon - bottom_left.spherical.lon)
-        height = top_right.spherical.lat - bottom_left.spherical.lat
+            width = Longitude(top_right.spherical.lon - bottom_left.spherical.lon)
+            height = top_right.spherical.lat - bottom_left.spherical.lat
+            anchor = self._get_lon_lat(bottom_left)
+            transform = axes.get_transform(bottom_left.frame.replicate_without_data())
 
         kwergs = {
-            "transform": axes.get_transform(bottom_left.frame.replicate_without_data()),
+            "transform": transform,
             "edgecolor": "white",
             "fill": False,
         }
         kwergs.update(kwargs)
-        quad = Quadrangle(self._get_lon_lat(bottom_left), width, height, **kwergs)
+        quad = Quadrangle(anchor, width, height, **kwergs)
         axes.add_patch(quad)
         return quad
 


Test patch:
diff --git a/sunpy/map/tests/test_plotting.py b/sunpy/map/tests/test_plotting.py
--- a/sunpy/map/tests/test_plotting.py
+++ b/sunpy/map/tests/test_plotting.py
@@ -135,6 +135,13 @@ def test_quadrangle_aia17_width_height(aia171_test_map):
     aia171_test_map.draw_quadrangle(bottom_left=bottom_left, width=w, height=h)
 
 
+@figure_test
+def test_quadrangle_aia17_pix_width_height(aia171_test_map):
+    aia171_test_map.plot()
+    aia171_test_map.draw_quadrangle(bottom_left=(50, 50)*u.pix, width=30*u.pix,
+                                    height=50*u.pix, edgecolor="cyan")
+
+
 @figure_test
 def test_quadrangle_aia17_top_right(aia171_test_map):
     aia171_test_map.plot()
@@ -146,6 +153,29 @@ def test_quadrangle_aia17_top_right(aia171_test_map):
     plt.legend()  # Check that the 'Quadrangle' label shows up in the legend
 
 
+@figure_test
+def test_quadrangle_aia17_pix_top_right(aia171_test_map):
+    aia171_test_map.plot()
+    aia171_test_map.draw_quadrangle(bottom_left=(50, 50)*u.pix,
+                                    top_right=(80, 90)*u.pix, edgecolor='cyan')
+
+
+@figure_test
+def test_quadrangle_aia17_pix_top_right_different_axes(aia171_test_map):
+    # Plot the map rotated by 30 degrees
+    aia171_test_map.rotate(30*u.deg).plot()
+    # Plot a rectangle in the pixel space of the original map
+    aia171_test_map.draw_quadrangle(bottom_left=(50, 50)*u.pix,
+                                    top_right=(80, 90)*u.pix, edgecolor='cyan')
+
+
+@figure_test
+def test_quadrangle_aia17_pix_top_right_different_axes(aia171_test_map):
+    aia171_test_map.rotate(30*u.deg).plot()
+    aia171_test_map.draw_quadrangle(bottom_left=(50, 50)*u.pix,
+                                    top_right=(80, 90)*u.pix, edgecolor='cyan')
+
+
 @figure_test
 def test_plot_masked_aia171(aia171_test_map_with_mask):
     aia171_test_map_with_mask.plot()
diff --git a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_42.json b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_42.json
--- a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_42.json
+++ b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_42.json
@@ -18,7 +18,10 @@
   "sunpy.map.tests.test_plotting.test_rectangle_aia171_width_height": "eec5efc2ad0fe01d68f70c920ae3bb05758c6c6dc40c439debc9d7a05b7ef9b5",
   "sunpy.map.tests.test_plotting.test_rectangle_aia171_top_right": "2afb9cb55d924b1d743c95a6aac29cea5d9eea561c822bed4f5dad2af3359af3",
   "sunpy.map.tests.test_plotting.test_quadrangle_aia17_width_height": "f7df3ec501d9dfc7d4ad1e29d8e6f774385e0a4328e04895de45099a52c19452",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_width_height": "d4772f0eb989355f5606c4b33d4c36443de26550369ee21cfefaf9828a2345f3",
   "sunpy.map.tests.test_plotting.test_quadrangle_aia17_top_right": "6e92963950a4b0ef044dc0f30746a75bcaacd598defbeafe333ffa9fb5e3f77e",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_top_right": "1ccdad13885f88aa731daa402a19b3854703d04f1d3aa55d75f650c158bc7987",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_top_right_different_axes": "9fcda926876b231844a8eda6fe83e8125b52e3660d4438b64a6cac1b22571b47",
   "sunpy.map.tests.test_plotting.test_plot_masked_aia171": "2dff076c0eaaf5cf5849ad541b434bfbda00d99491db51eb35d2ab0ee729bbb2",
   "sunpy.map.tests.test_plotting.test_plot_aia171_superpixel": "0fa642815341e022dbf856b42e919cc25d9311290f68c4850ff65877a40cbe1b",
   "sunpy.map.tests.test_plotting.test_plot_resample": "fe43b20dfb373599ba4086f02da8c227e6a82d3d5de0d3c924a553532a9548c8",
diff --git a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
--- a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
+++ b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json
@@ -18,7 +18,10 @@
   "sunpy.map.tests.test_plotting.test_rectangle_aia171_width_height": "3707d9e19f98287552dea7905f88282281a80805d74ee125e28a6d4df428c8d9",
   "sunpy.map.tests.test_plotting.test_rectangle_aia171_top_right": "ef84baf265a97e9664fae2c2524359a3a48c0027621d0e5ebcabbfa3845f8f7d",
   "sunpy.map.tests.test_plotting.test_quadrangle_aia17_width_height": "b33badf3da283d51fe33dcbb84fc7383d4ec2b1dd325a8f52c03fb996e1e337d",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_width_height": "a376503f14693ddc54620165f9d41a727e538403c951569d5f5677945591f27e",
   "sunpy.map.tests.test_plotting.test_quadrangle_aia17_top_right": "b288a0531fd43c43a1a922d427e2af7bf31f026bfc7b43094f5ee7be7fa5b9bd",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_top_right": "407ac9ccd25e263444a42f53dc8faafaf86c171bc4a9e682c373f53e22f1670f",
+  "sunpy.map.tests.test_plotting.test_quadrangle_aia17_pix_top_right_different_axes": "67afb8ac96f8501f252aad93f0f52c85e96de538974003c7f2525c42a4e8166b",
   "sunpy.map.tests.test_plotting.test_plot_masked_aia171": "93ac3daeff182c7d95d51080f23ce65e7fbb6b0d94757a18e50230bece5c60a3",
   "sunpy.map.tests.test_plotting.test_plot_aia171_superpixel": "3acf916ce12ccb42c4b4b3a67b9fb0d9f9f4473717fcb0c959fe851df5b1db61",
   "sunpy.map.tests.test_plotting.test_plot_resample": "a4b7129362fb9a9ba2312181e910ed24ba306c317a10d47e01fc0e68413747a4",