Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a planar screen as an optional assumption for handling off-disk helioprojective coordinates #7115

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/7115.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`~sunpy.coordinates.Helioprojective.assume_spherical_screen` has been deprecated in favor of `~sunpy.coordinates.SphericalScreen`.
1 change: 1 addition & 0 deletions changelog/7115.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `~sunpy.coordinates.PlanarScreen` for realizing 3D versions of coordinates in a Helioprojective frame.
13 changes: 13 additions & 0 deletions docs/whatsnew/6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,16 @@ Deprecate positional arguments in :meth:`sunpy.map.GenericMap.plot`

The arguments for :meth:`sunpy.map.GenericMap.plot` have been changed to being keyword only.
Pass them as keyword arguments (e.g., ``..., title=True, ...``) instead.

New `~sunpy.coordinates.PlanarScreen` context manager
=====================================================

`~sunpy.coordinates.PlanarScreen` provides a context manager for interpreting 2D coordinates as being on the inside of a planar screen.
The plane goes through Sun center (or some specified distance from Sun center) and is perpendicular to the vector between the specified vantage point and Sun center.
This replaces the default assumption where 2D coordinates are mapped onto the surface of the Sun and is an alternative to `~sunpy.coordinates.SphericalScreen`.

Deprecate `~sunpy.coordinates.Helioprojective.assume_spherical_screen`
======================================================================

:func:`~sunpy.coordinates.Helioprojective.assume_spherical_screen` is now deprecated.
Equivalent functionality is now provided by :class:`~sunpy.coordinates.SphericalScreen`.
2 changes: 1 addition & 1 deletion examples/map_transformations/autoalign_aia_hmi.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
# Note that off-disk HMI data are not retained by default because an
# additional assumption is required to define the location of the HMI
# emission in 3D space. We can use
# :meth:`~sunpy.coordinates.Helioprojective.assume_spherical_screen` to
# :meth:`~sunpy.coordinates.SphericalScreen` to
# retain the off-disk HMI data. See
# :ref:`sphx_glr_generated_gallery_map_transformations_reprojection_spherical_screen.py`
# for more reference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
# Note that off-disk HMI data are not retained by default because an
# additional assumption is required to define the location of the HMI
# emission in 3D space. We can use
# :meth:`~sunpy.coordinates.Helioprojective.assume_spherical_screen` to
# :meth:`~sunpy.coordinates.SphericalScreen` to
# retain the off-disk HMI data. See
# :ref:`sphx_glr_generated_gallery_map_transformations_reprojection_spherical_screen.py`
# for more reference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from astropy.coordinates import SkyCoord

import sunpy.map
from sunpy.coordinates import Helioprojective
from sunpy.coordinates import SphericalScreen
from sunpy.data.sample import AIA_171_IMAGE

######################################################################
Expand Down Expand Up @@ -79,7 +79,7 @@
# not obvious in this plot due to the relatively small field of view
# of AIA (compared to, say, a coronagraph).

with Helioprojective.assume_spherical_screen(aia_map.observer_coordinate):
with SphericalScreen(aia_map.observer_coordinate):
outmap_screen_all = aia_map.reproject_to(out_header)

fig = plt.figure()
Expand All @@ -92,8 +92,7 @@
# be used for only off-disk parts of the image, and continue to map
# on-disk parts of the image to the surface of the Sun.

with Helioprojective.assume_spherical_screen(aia_map.observer_coordinate,
only_off_disk=True):
with SphericalScreen(aia_map.observer_coordinate, only_off_disk=True):
outmap_screen_off_disk = aia_map.reproject_to(out_header)

fig = plt.figure()
Expand Down
8 changes: 4 additions & 4 deletions examples/plotting/lasco_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from astropy.coordinates import SkyCoord

import sunpy.data.sample
from sunpy.coordinates import Helioprojective
from sunpy.coordinates import SphericalScreen
from sunpy.map import Map
from sunpy.util.config import get_and_create_download_dir

Expand All @@ -36,7 +36,7 @@
# Note that off-disk AIA data are not retained by default because an
# additional assumption is required to define the location of the AIA
# emission in 3D space. We can use
# :meth:`~sunpy.coordinates.Helioprojective.assume_spherical_screen` to
# :meth:`~sunpy.coordinates.SphericalScreen` to
# retain the off-disk AIA data. See
# :ref:`sphx_glr_generated_gallery_map_transformations_reprojection_spherical_screen.py`
# for more reference.
Expand All @@ -51,9 +51,9 @@
scale=u.Quantity(aia_map.scale),
instrument=aia_map.instrument,
wavelength=aia_map.wavelength)
# We use `assume_spherical_screen` to ensure that the off limb AIA pixels are reprojected
# We use `SphericalScreen` to ensure that the off limb AIA pixels are reprojected
# otherwise it will only be the on disk pixels that are reprojected.
with Helioprojective.assume_spherical_screen(aia_map.observer_coordinate):
with SphericalScreen(aia_map.observer_coordinate):
aia_reprojected = aia_map.reproject_to(projected_header)

###############################################################################
Expand Down
6 changes: 3 additions & 3 deletions examples/plotting/mplcairo_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@

import sunpy.data.sample
import sunpy.map
from sunpy.coordinates import Helioprojective
from sunpy.coordinates import SphericalScreen

###############################################################################
# Let's load two maps for blending. We reproject the second map to the
# coordinate frame of the first map for proper compositing, taking care to use
# the :meth:`~sunpy.coordinates.Helioprojective.assume_spherical_screen`
# the :class:`~sunpy.coordinates.SphericalScreen`
# context manager in order to preserve off-disk data.

a171 = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)
a131 = sunpy.map.Map(sunpy.data.sample.AIA_131_IMAGE)
with Helioprojective.assume_spherical_screen(a171.observer_coordinate):
with SphericalScreen(a171.observer_coordinate):
a131 = a131.reproject_to(a171.wcs)

###############################################################################
Expand Down
4 changes: 2 additions & 2 deletions examples/plotting/three_map_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from astropy.coordinates import SkyCoord

import sunpy.data.sample
from sunpy.coordinates import Helioprojective
from sunpy.coordinates import SphericalScreen
from sunpy.map import Map

###############################################################################
Expand All @@ -32,7 +32,7 @@
# overlaid. Next, zoom in around the solar flare so the RHESSI contours are
# visible. Also, specify the RHESSI contour levels to be plotted.

with Helioprojective.assume_spherical_screen(eit.observer_coordinate):
with SphericalScreen(eit.observer_coordinate):
aia = aia.reproject_to(eit.wcs)

bottom_left = [200, -800] * u.arcsec
Expand Down
4 changes: 2 additions & 2 deletions examples/showcase/stereoscopic_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import sunpy.data.sample
import sunpy.map
from sunpy.coordinates import Helioprojective
from sunpy.coordinates import SphericalScreen

##############################################################################
# Download co-temporal SDO/AIA image STEREO/EUVI images. The EUVI map does
Expand Down Expand Up @@ -57,7 +57,7 @@ def reproject_to_1au(in_map):
),
scale=(2.2, 2.2)*u.arcsec/u.pixel
)
with Helioprojective.assume_spherical_screen(in_map.observer_coordinate):
with SphericalScreen(in_map.observer_coordinate):
return in_map.reproject_to(header)


Expand Down
1 change: 1 addition & 0 deletions sunpy/coordinates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .ephemeris import *
from .frames import *
from .metaframes import *
from .screens import *
from .wcs_utils import *

__doc__ += _make_sunpy_graph()
83 changes: 12 additions & 71 deletions sunpy/coordinates/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from sunpy import log
from sunpy.sun.constants import radius as _RSUN
from sunpy.time.time import _variables_for_parse_time_docstring
from sunpy.util.decorators import add_common_docstring, sunpycontextmanager
from sunpy.util.decorators import add_common_docstring, deprecated, sunpycontextmanager
from sunpy.util.exceptions import warn_user
from .frameattributes import ObserverCoordinateAttribute, TimeFrameAttributeSunPy

Expand Down Expand Up @@ -569,15 +569,9 @@ def make_3d(self):
with np.errstate(invalid='ignore'):
d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2 # use the "near" solution

if self._spherical_screen:
sphere_center = self._spherical_screen['center'].transform_to(self).cartesian
c = sphere_center.norm()**2 - self._spherical_screen['radius']**2
b = -2 * sphere_center.dot(rep)
# Ignore sqrt of NaNs
with np.errstate(invalid='ignore'):
dd = ((-1*b) + np.sqrt(b**2 - 4*c)) / 2 # use the "far" solution

d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd
if self._assumed_screen:
d_screen = self._assumed_screen.calculate_distance(self)
d = np.fmin(d, d_screen) if self._assumed_screen.only_off_disk else d_screen

# This warning can be triggered in specific draw calls when plt.show() is called
# we can not easily prevent this, so we check the specific function is being called
Expand All @@ -589,7 +583,7 @@ def make_3d(self):
warn_user("The conversion of these 2D helioprojective coordinates to 3D is all NaNs "
"because off-disk coordinates need an additional assumption to be mapped to "
"calculate distance from the observer. Consider using the context manager "
"`Helioprojective.assume_spherical_screen()`.")
"`SphericalScreen()`.")

return self.realize_frame(SphericalRepresentation(lon=lon,
lat=lat,
Expand Down Expand Up @@ -674,73 +668,20 @@ def is_visible(self, *, tolerance: u.m = 1*u.m):

return is_behind_observer | is_beyond_limb | (is_on_near_side & is_above_surface)

_spherical_screen = None
_assumed_screen = None

@classmethod
@sunpycontextmanager
@deprecated('6.0', alternative='sunpy.coordinates.screens.SphericalScreen')
def assume_spherical_screen(cls, center, only_off_disk=False):
"""
Context manager to interpret 2D coordinates as being on the inside of a spherical screen.

The radius of the screen is the distance between the specified ``center`` and Sun center.
This ``center`` does not have to be the same as the observer location for the coordinate
frame. If they are the same, then this context manager is equivalent to assuming that the
helioprojective "zeta" component is zero.

This replaces the default assumption where 2D coordinates are mapped onto the surface of the
Sun.

Parameters
----------
center : `~astropy.coordinates.SkyCoord`
The center of the spherical screen
only_off_disk : `bool`, optional
If `True`, apply this assumption only to off-disk coordinates, with on-disk coordinates
still mapped onto the surface of the Sun. Defaults to `False`.

Examples
--------
.. minigallery:: sunpy.coordinates.Helioprojective.assume_spherical_screen

>>> import astropy.units as u
>>> from sunpy.coordinates import Helioprojective
>>> h = Helioprojective(range(7)*u.arcsec*319, [0]*7*u.arcsec,
... observer='earth', obstime='2020-04-08')
>>> print(h.make_3d())
<Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
[( 0., 0., 0.99660825), ( 319., 0., 0.99687244),
( 638., 0., 0.99778472), ( 957., 0., 1.00103285),
(1276., 0., nan), (1595., 0., nan),
(1914., 0., nan)]>

>>> with Helioprojective.assume_spherical_screen(h.observer):
... print(h.make_3d())
<Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
[( 0., 0., 1.00125872), ( 319., 0., 1.00125872),
( 638., 0., 1.00125872), ( 957., 0., 1.00125872),
(1276., 0., 1.00125872), (1595., 0., 1.00125872),
(1914., 0., 1.00125872)]>

>>> with Helioprojective.assume_spherical_screen(h.observer, only_off_disk=True):
... print(h.make_3d())
<Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
[( 0., 0., 0.99660825), ( 319., 0., 0.99687244),
( 638., 0., 0.99778472), ( 957., 0., 1.00103285),
(1276., 0., 1.00125872), (1595., 0., 1.00125872),
(1914., 0., 1.00125872)]>
"""
try:
old_spherical_screen = cls._spherical_screen # nominally None

center_hgs = center.transform_to(HeliographicStonyhurst(obstime=center.obstime))
cls._spherical_screen = {
'center': center,
'radius': center_hgs.radius,
'only_off_disk': only_off_disk
}
old_assumed_screen = cls._assumed_screen # nominally None
from sunpy.coordinates import SphericalScreen
sph_screen = SphericalScreen(center, only_off_disk=only_off_disk)
cls._assumed_screen = sph_screen
yield
finally:
cls._spherical_screen = old_spherical_screen
cls._assumed_screen = old_assumed_screen


@add_common_docstring(**_frame_parameters())
Expand Down