Skip to content

Commit

Permalink
feat: add method from boxes
Browse files Browse the repository at this point in the history
  • Loading branch information
ManonMarchand committed Jun 21, 2024
1 parent 6e738e2 commit 2c87535
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 21 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

* a new method `MOC.from_cones` allows to create a list of MOCs from a list of centers
and radii.
and radii. This is multi-threaded.
* new method `MOC.from_boxes` allows to create lists of MOCs from boxes. This is multi-threaded.

### Changed

Expand Down
147 changes: 134 additions & 13 deletions notebooks/01-Creating_MOCs_from_shapes.ipynb

Large diffs are not rendered by default.

98 changes: 92 additions & 6 deletions python/mocpy/moc/moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,8 +1120,8 @@ def from_cones(cls, lon, lat, radius, max_depth, *, delta_depth=2, n_threads=Non
The depth at which the computations will be made will therefore be equal to
`max_depth` + `depth_delta`.
n_threads : int, optional
The number of threads to be used if it is not set, the maximum number of
available threads will be used.
The number of threads to be used. If this is set to None (default value),
all available threads will be used.
Returns
-------
Expand Down Expand Up @@ -1210,6 +1210,90 @@ def from_box(cls, lon, lat, a, b, angle, max_depth):
)
return cls(index)

@classmethod
@validate_lonlat
def from_boxes(cls, lon, lat, a, b, angle, max_depth, *, n_threads=None):
"""
Create a MOC from a box/rectangle.
The box is centered around the (`lon`, `lat`) position.
Parameters
----------
lon : `~astropy.coordinates.Longitude` or its supertype `~astropy.units.Quantity`
The longitude of the center of the cone.
lat : `~astropy.coordinates.Latitude` or its supertype `~astropy.units.Quantity`
The latitude of the center of the cone.
a : `~astropy.coordinates.Angle` or its supertype `~astropy.units.Quantity`
The semi-major axis of the box, i.e. half of the box's width.
b : `~astropy.coordinates.Angle` or its supertype `~astropy.units.Quantity`
The semi-minor axis of the box, i.e. half of the box's height.
angle : `astropy.coordinates.Angle` or its supertype `~astropy.units.Quantity`
The tilt angle between the north and the semi-major axis, east of north.
max_depth : int
Maximum HEALPix cell resolution.
n_threads : int, optional
The number of threads to be used. If this is set to None (default value),
all available threads will be used.
Returns
-------
result : list[`~mocpy.moc.MOC`]
The resulting list of MOCs.
Examples
--------
>>> from mocpy import MOC
>>> import astropy.units as u
>>> # similar boxes, same size and orientation
>>> moc_list = MOC.from_boxes(
... lon=[1, 2]*u.deg,
... lat=[1, 2]*u.deg,
... a=10*u.deg,
... b=5*u.deg,
... angle=30*u.deg,
... max_depth=10
... )
>>> # different boxes
>>> moc_list = MOC.from_boxes(
... lon=[1, 2]*u.deg,
... lat=[1, 2]*u.deg,
... a=[10, 20]*u.deg,
... b=[5, 10]*u.deg,
... angle=[30, 10]*u.deg,
... max_depth=10
... )
"""
params = [a, b, angle]
if any(isinstance(param, u.Quantity) and param.isscalar for param in params):
if not all(isinstance(param, u.Quantity) for param in params):
raise ValueError(
"'a', 'b' and 'angle' should either be all astropy angle-equivalent"
"scalar values or they should all be iterable angle-equivalent. "
"They cannot be a mix of both.",
)
indices = mocpy.from_same_boxes(
lon,
lat,
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),
n_threads=n_threads,
)
return [cls(index) for index in indices]
# different boxes
indices = mocpy.from_boxes(
lon,
lat,
np.array(Angle(a).to_value(u.deg), dtype=np.float64),
np.array(Angle(b).to_value(u.deg), dtype=np.float64),
np.array(Angle(angle).to_value(u.deg), dtype=np.float64),
np.uint8(max_depth),
n_threads=n_threads,
)
return [cls(index) for index in indices]

@classmethod
@validate_lonlat
def from_ring(
Expand Down Expand Up @@ -1317,7 +1401,8 @@ def from_polygons(cls, list_vertices, max_depth=10, n_threads=None):
max_depth : int, optional
The resolution of the MOC. Set to 10 by default.
n_threads : int, optional
The number of threads to be used
The number of threads to be used. If this is set to None (default value),
all available threads will be used.
Returns
-------
Expand All @@ -1335,8 +1420,8 @@ def from_polygons(cls, list_vertices, max_depth=10, n_threads=None):
>>> # without the SkyCoord object, we need to adapt the coordinates
>>> list_vertices = [[356, 4, 4, 356], [4, 4, -4, -4],
... [0, 6, 0, 354], [6, 0, -6, 0]]
>>> list_mocs_no_check = MOC.from_polygons(list_vertices)
>>> list_mocs == list_mocs_no_check
>>> list_mocs_no_check_no_wrap = MOC.from_polygons(list_vertices)
>>> list_mocs == list_mocs_no_check_no_wrap
True
"""
Expand Down Expand Up @@ -1432,7 +1517,8 @@ def from_stcs(cls, stcs, max_depth, delta_depth=2):
>>> from mocpy import MOC
>>> moc1 = MOC.from_stcs("Circle ICRS 147.6 69.9 0.4", max_depth=14)
>>> moc2 = MOC.from_cone(lon=147.6 * u.deg, lat=69.9 * u.deg, radius=Angle(0.4, u.deg), max_depth=14)
>>> assert (moc1 == moc2)
>>> moc1 == moc2
True
"""
index = mocpy.from_stcs(stcs, np.uint8(max_depth), np.uint8(delta_depth))
return cls(index)
Expand Down
36 changes: 35 additions & 1 deletion python/mocpy/tests/test_moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,14 +547,48 @@ def test_from_box():
lat=Latitude("0d"),
a=a,
b=b,
angle=Angle("30d"),
angle=Angle("30deg"),
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
# test from_boxes
list_boxes_same = MOC.from_boxes(
lon=[0, 0] * u.deg,
lat=[0, 0] * u.deg,
a=a,
b=b,
angle=30 * u.deg,
max_depth=10,
)
assert len(list_boxes_same) == 2
assert list_boxes_same[0] == moc
a = [Angle("10d"), Angle("20d")]
b = [Angle("2d"), Angle("4d")]
list_boxes_different = MOC.from_boxes(
lon=[0, 0] * u.deg,
lat=[0, 0] * u.deg,
a=a,
b=b,
angle=[30, 45] * u.deg,
max_depth=10,
)
assert len(list_boxes_different) == 2
assert list_boxes_different[0] == moc

# mixed iterables and scalars raise an error
with pytest.raises(ValueError, match="'a', 'b' and 'angle' should*"):
MOC.from_boxes(
lon=[0, 0] * u.deg,
lat=[0, 0] * u.deg,
a=a,
b=b,
angle=30 * u.deg,
max_depth=10,
)


def test_from_astropy_regions():
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ fn mocpy(_py: Python, m: &PyModule) -> PyResult<()> {
/// * `b` the semi-minor axis of the boxes (half the box height), in degrees
/// * `angle` the position angle of the boxes (i.e. the angle between the north and the semi-major axis, east-of-north), in degrees
/// * `depth`: the MOCs depth
/// * `n_threads`: number of threads to use (max number of threads if `n_threads=None`.
///
/// # Output
/// - The MOC indices in the storage
Expand Down Expand Up @@ -433,6 +434,7 @@ fn mocpy(_py: Python, m: &PyModule) -> PyResult<()> {
/// * `b` the semi-minor axis of the boxes (half the box height), in degrees
/// * `angle` the position angle of the boxes (i.e. the angle between the north and the semi-major axis, east-of-north), in degrees
/// * `depth`: the MOCs depth
/// * `n_threads`: number of threads to use (max number of threads if `n_threads=None`.
///
/// # Output
/// - The MOC indices in the storage
Expand Down

0 comments on commit 2c87535

Please sign in to comment.