Skip to content

Commit

Permalink
feat: add support for astropy regions
Browse files Browse the repository at this point in the history
  • Loading branch information
ManonMarchand committed May 29, 2024
1 parent d1be81f commit 373711c
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ MOC and the Multi-order-map. `MOC.probability_in_multiordermap` has a similar
behavior but also converts a probability-density into a probability.
* `STMOC.new_empty()` allows to create a new empty Space-Time MOC.
* `MOC.from_box` to create rectangular MOCs
* `MOC.from_astropy_regions` to create MOCs from astropy regions.

## [0.13.1]

Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"matplotlib": ("https://matplotlib.org/", None),
"networkx": ("https://networkx.github.io/documentation/stable/", None),
"numpy": ("https://numpy.org/doc/stable/", None),
"regions": ("https://astropy-regions.readthedocs.io/en/stable", None),
}


Expand Down
1 change: 1 addition & 0 deletions docs/examples/user_documentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Gallery of notebooks examples using SMOCs
../_collections/notebooks/filtering_astropy_table
../_collections/notebooks/FITS-image-pixels-intersecting-MOC
../_collections/notebooks/from_astropy_table.ipynb
../_collections/notebooks/from-astropy-regions
../_collections/notebooks/from_polygon
../_collections/notebooks/from_fits_and_intersection
../_collections/notebooks/from_image_with_mask
Expand Down
290 changes: 290 additions & 0 deletions notebooks/from-astropy-regions.ipynb

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ classifiers = [
[project.optional-dependencies]
# optional to load FITS from URLs
query_fits = ["requests"]
# optional to support astropy regions
astropy_regions = ["regions"]
# for the documentation
docs = [
"astropy-sphinx-theme",
Expand All @@ -43,7 +45,8 @@ dev = [
"pytest > 6.0",
"pytest-mock",
"pytest-cov",
"requests"
"requests",
"regions"
]
# to run the notebooks
notebooks = [
Expand Down
91 changes: 89 additions & 2 deletions python/mocpy/moc/moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from astropy.utils.data import download_file

with contextlib.suppress(ImportError):
import regions
from astropy_healpix import HEALPix

from .. import mocpy
Expand Down Expand Up @@ -1031,7 +1032,7 @@ def from_cone(cls, lon, lat, radius, max_depth, delta_depth=2):
np.uint8(delta_depth),
)
return cls(index)

@classmethod
@validate_lonlat
def from_box(cls, lon, lat, a, b, angle, max_depth):
Expand Down Expand Up @@ -1080,7 +1081,7 @@ def from_box(cls, lon, lat, a, b, angle, max_depth):
np.float64(a.to_value(u.deg)),
np.float64(b.to_value(u.deg)),
np.float64(angle.to_value(u.deg)),
np.uint8(max_depth)
np.uint8(max_depth),
)
return cls(index)

Expand Down Expand Up @@ -1254,6 +1255,92 @@ def from_stcs(cls, stcs, max_depth, delta_depth=2):
index = mocpy.from_stcs(stcs, np.uint8(max_depth), np.uint8(delta_depth))
return cls(index)

@classmethod
def from_astropy_regions(cls, region, max_depth: int):
"""Create a SMOC from an astropy regions.
This creates the MOC of the requested order that contains entirely the astropy
region. See https://github.com/astropy/regions.
Parameters
----------
region : `~regions.SkyRegion`
The supported sky regions are `~regions.CircleSkyRegion`,
`~regions.CircleAnnulusSkyRegion`, `~regions.EllipseSkyRegion`,
`~regions.RectangleSkyRegion`, `~regions.PolygonSkyRegion`,
`~regions.PointSkyRegion`.
max_depth : int
The maximum HEALPix cell resolution of the MOC. Should be comprised between
0 and 29.
Returns
-------
`~mocpy.moc.MOC`
Examples
--------
>>> from astropy.coordinates import SkyCoord
>>> point = SkyCoord("+23h20m48.3s +61d12m06s")
>>> point_region = regions.PointSkyRegion(point)
>>> moc_point = MOC.from_astropy_regions(point_region, max_depth=10)
>>> moc_point
10/3663728
"""
supported_regions_types = (
regions.CircleSkyRegion,
regions.CircleAnnulusSkyRegion,
regions.EllipseSkyRegion,
regions.RectangleSkyRegion,
regions.PolygonSkyRegion,
regions.PointSkyRegion,
)
if isinstance(region, regions.CircleSkyRegion):
center = region.center.icrs
return cls.from_cone(
center.ra,
center.dec,
radius=region.radius,
max_depth=max_depth,
)
if isinstance(region, regions.CircleAnnulusSkyRegion):
center = region.center.icrs
return cls.from_ring(
center.ra,
center.dec,
internal_radius=region.inner_radius,
external_radius=region.outer_radius,
max_depth=max_depth,
)
if isinstance(region, regions.EllipseSkyRegion):
center = region.center.icrs
return cls.from_elliptical_cone(
center.ra,
center.dec,
a=region.height / 2,
b=region.width / 2,
pa=region.angle,
max_depth=max_depth,
)
if isinstance(region, regions.RectangleSkyRegion):
center = region.center.icrs
return cls.from_box(
center.ra,
center.dec,
a=region.width / 2,
b=region.height / 2,
angle=region.angle + Angle("90d"),
max_depth=max_depth,
)
if isinstance(region, regions.PolygonSkyRegion):
return cls.from_polygon_skycoord(region.vertices, max_depth=max_depth)
if isinstance(region, regions.PointSkyRegion):
return cls.from_skycoords(region.center, max_norder=max_depth)

raise ValueError(
"'from_astropy_regions' not not support this region type."
f"The supported regions are: {supported_regions_types}",
)

@classmethod
def new_empty(cls, max_depth):
"""
Expand Down
50 changes: 47 additions & 3 deletions python/mocpy/tests/test_moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import cdshealpix
import numpy as np
import pytest
from astropy.coordinates import Angle, SkyCoord
import regions
from astropy.coordinates import Angle, Latitude, Longitude, SkyCoord
from astropy.io import fits
from astropy.io.votable import parse_single_table
from astropy.table import QTable
Expand Down Expand Up @@ -526,18 +527,61 @@ def test_from_ring():
max_depth=10,
)


def test_from_box():
a = Angle("10d")
b = Angle("2d")
moc = MOC.from_box(
lon=Longitude("0d"), lat=Latitude("0d"),
a=a, b=b, angle=Angle("30d"),max_depth=10)
lon=Longitude("0d"),
lat=Latitude("0d"),
a=a,
b=b,
angle=Angle("30d"),
max_depth=10,
)
area = moc.sky_fraction * 4 * math.pi * u.steradian
# the moc covers a slightly bigger area than the region defined by the
# parameters
assert area.to(u.deg**2).value > 80
assert area.to(u.deg**2).value < 90


def test_from_astropy_regions():
center = SkyCoord(42, 43, unit="deg", frame="fk5")
# circle
circle = regions.CircleSkyRegion(center, radius=3 * u.deg)
moc = MOC.from_astropy_regions(circle, max_depth=10)
assert round(moc.barycenter().ra.value) == 42
assert round(moc.barycenter().dec.value) == 43
assert round(moc.largest_distance_from_coo_to_vertices(center).to("deg").value) == 3
# ring
ring = regions.CircleAnnulusSkyRegion(center, 3 * u.deg, 4 * u.deg)
moc = MOC.from_astropy_regions(ring, max_depth=9)
assert not moc.contains_skycoords(center)
# ellipse
ellipse = regions.EllipseSkyRegion(center, 3 * u.deg, 6 * u.deg, 10 * u.deg)
moc = MOC.from_astropy_regions(ellipse, max_depth=9)
assert moc.contains_skycoords(center)
assert round(moc.largest_distance_from_coo_to_vertices(center).to("deg").value) == 3
# rectangle
box = regions.RectangleSkyRegion(center, 12 * u.deg, 6 * u.deg, 10 * u.deg)
moc = MOC.from_astropy_regions(box, max_depth=8)
assert all(
moc.contains_skycoords(SkyCoord([42, 45], [44, 44], unit="deg", frame="icrs")),
)
# polygons
vertices = SkyCoord([1, 2, 2], [1, 1, 2], unit="deg", frame="fk5")
polygon = regions.PolygonSkyRegion(vertices)
moc = MOC.from_astropy_regions(polygon, max_depth=10)
assert all(moc.contains_skycoords(vertices))
# points
point = SkyCoord("+23h20m48.3s +61d12m06s")
region_point = regions.PointSkyRegion(point)
moc = MOC.from_astropy_regions(region_point, max_depth=10)
assert moc.max_order == 10
assert moc.contains_skycoords(point)


# TODO: IMPROVE THE ALGO
"""
def test_boundaries():
Expand Down

0 comments on commit 373711c

Please sign in to comment.