@@ -252,15 +252,6 @@ Spherical Sky Regions
252252Spherical sky regions are defined using celestial coordinates,
253253and **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
265256Spherical 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
309300regions and to learn more about :class: `~regions.Region ` objects and
310301their 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