Skip to content

Commit b8d8f14

Browse files
committed
Add docs demo of spherical vs planar circle
Add a short visual demonstration of the difference between spherical and planar circles to "Getting started". Modifies the "Spherical Sky Regions" section to have a new "Spherical to planar region transformations" subsection. The text discussing spherical-to-planar transformations is moved to that subsection, and added a short discussion of the plot demo.
1 parent 92fa60b commit b8d8f14

1 file changed

Lines changed: 107 additions & 9 deletions

File tree

docs/getting_started.rst

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,6 @@ Spherical Sky Regions
252252
Spherical sky regions are defined using celestial coordinates,
253253
and **are** defined as regions on the celestial sphere
254254
("regions-on-celestial-sphere", in contrast to the planar Sky Regions).
255-
In order to transform between spherical and planar ("region-on-image") regions,
256-
the planar projection (encoded in a `world coordinate system
257-
<https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_ object; e.g.,
258-
`~astropy.wcs.WCS`) must be specified, along with a specification of
259-
whether or not boundary distortions should be included.
260-
These distortions (implemented through discrete boundary sampling)
261-
capture the impact of the spherical-to-planar (or vice versa) projection
262-
described by the WCS. However, it is possible to ignore these
263-
distortions (e.g., transforming a spherical circle to a planar circle).
264255

265256
Spherical sky regions are created using celestial coordinates (as
266257
`~astropy.coordinates.SkyCoord`) and angular distances,
@@ -308,3 +299,110 @@ You can access its properties via attributes:
308299
See the :ref:`shapes` documentation for the complete list of pixel-based
309300
regions and to learn more about :class:`~regions.Region` objects and
310301
their capabilities.
302+
303+
Spherical to planar region transformations
304+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305+
In order to transform between spherical and planar ("region-on-image") regions,
306+
the planar projection (encoded in a `world coordinate system
307+
<https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_ object; e.g.,
308+
`~astropy.wcs.WCS`) must be specified, along with a specification of
309+
whether or not boundary distortions should be included.
310+
These distortions (implemented through discrete boundary sampling)
311+
capture the impact of the spherical-to-planar (or vice versa) projection
312+
described by the WCS. However, it is possible to ignore these
313+
distortions (e.g., transforming a spherical circle to a planar circle).
314+
315+
The example below demonstrates the difference between a spherical and
316+
a planar circle with the same center and radius. Boundary distortions
317+
are included when projection the spherical circle onto the plot.
318+
In this full-sky Aitoff projection, the distortions result in
319+
points inside the spherical circle falling outside of the planar circle
320+
and vice versa.
321+
322+
.. plot::
323+
:include-source: false
324+
325+
import matplotlib.pyplot as plt
326+
import numpy as np
327+
from astropy.coordinates import Angle, SkyCoord
328+
329+
from regions import (CircleSkyRegion, CircleSphericalSkyRegion,
330+
make_example_dataset, PixCoord)
331+
332+
# load example dataset to get skymap
333+
config = dict(crval=(0, 0),
334+
crpix=(180, 90),
335+
cdelt=(-1, 1),
336+
shape=(180, 360))
337+
338+
dataset = make_example_dataset(data='simulated', config=config)
339+
wcs = dataset.wcs
340+
341+
# remove sources
342+
dataset.image.data = np.zeros_like(dataset.image.data)
343+
344+
#----------------------------------------
345+
# define skycoords, pixcoords grids
346+
lon = np.arange(-180, 181, 10)
347+
lat = np.arange(-90, 91, 10)
348+
coords = np.array(np.meshgrid(lon, lat)).T.reshape(-1, 2)
349+
skycoords = SkyCoord(coords, unit='deg', frame='galactic')
350+
pixcoords = PixCoord.from_sky(skycoords, wcs)
351+
352+
353+
#----------------------------------------
354+
# define spherical & planar sky circles
355+
sph_circle = CircleSphericalSkyRegion(
356+
center=SkyCoord(50, 45, unit='deg', frame='galactic'),
357+
radius=Angle('30 deg'))
358+
circle = CircleSkyRegion(
359+
center=SkyCoord(50, 45, unit='deg', frame='galactic'),
360+
radius=Angle('30 deg'))
361+
# Note: circle is equivalent to transforming from sph_circle
362+
# with sph_circle.to_sky(wcs, include_boundary_distortions=False)
363+
364+
365+
#----------------------------------------
366+
# define transformed-to pixel regions
367+
pix_circ_distort = sph_circle.to_pixel(wcs=wcs,
368+
include_boundary_distortions=True,
369+
n_points=1000)
370+
pix_circ_nodistort = circle.to_pixel(wcs=wcs)
371+
372+
373+
#----------------------------------------
374+
# get contained points:
375+
distort_mask = sph_circle.contains(skycoords)
376+
nodistort_mask = pix_circ_nodistort.contains(pixcoords)
377+
378+
both_skycoords = skycoords[distort_mask & nodistort_mask]
379+
distort_only_skycoords = skycoords[distort_mask & ~nodistort_mask]
380+
nodistort_only_skycoords = skycoords[~distort_mask & nodistort_mask]
381+
382+
# plot
383+
fig = plt.figure()
384+
fig.set_size_inches(7,3.5)
385+
ax = fig.add_axes([0.15, 0.1, 0.8, 0.8], projection=wcs, aspect='equal')
386+
387+
ax.scatter(skycoords.l.value, skycoords.b.value, label='All',
388+
transform=ax.get_transform('galactic'), color='lightgrey')
389+
ax.scatter(distort_only_skycoords.l.value, distort_only_skycoords.b.value,
390+
color='magenta', label='Only within spherical circle',
391+
transform=ax.get_transform('galactic'))
392+
ax.scatter(nodistort_only_skycoords.l.value, nodistort_only_skycoords.b.value,
393+
color='lime', label='Only within planar circle',
394+
transform=ax.get_transform('galactic'))
395+
ax.scatter(both_skycoords.l.value, both_skycoords.b.value, color='orange',
396+
label='Within both', transform=ax.get_transform('galactic'))
397+
398+
pix_circ_distort.plot(ax=ax, edgecolor='red', facecolor='none',
399+
alpha=0.8, lw=3)
400+
401+
pix_circ_nodistort.plot(ax=ax, edgecolor='green', facecolor='none',
402+
alpha=0.8, lw=3)
403+
404+
ax.legend(loc='lower right')
405+
406+
ax.set_xlim(-0.5, dataset.config['shape'][1] - 0.5)
407+
ax.set_ylim(-0.5, dataset.config['shape'][0] - 0.5)
408+
ax.set_title("Spherical vs. Planar circle: Center=(50deg,45deg), radius=30deg")

0 commit comments

Comments
 (0)