From 29163f4682e981a34ed7b9d2680849fb280faa23 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 2 Jul 2024 18:33:43 +0200 Subject: [PATCH] feat: homogenize the complement argument in from_polygons methods --- CHANGELOG.md | 2 ++ docs/api.rst | 4 ++-- docs/examples/polygon_coverage.py | 5 +---- docs/examples/user_documentation.rst | 12 ++++++---- python/mocpy/moc/moc.py | 33 +++++++++++++++++++++------- python/mocpy/tests/test_moc.py | 3 +++ src/lib.rs | 7 ++++-- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e98f5907..82afc5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ and radii. This is multi-threaded. * new method `MOC.from_boxes` allows to create lists of MOCs from boxes. This is multi-threaded. * the `mocpy.WCS` class can now accept a sequence of angles as its fov argument rather than always representing square areas of the sky. +* `MOC.from_polygons` and `MOC.from_polygons` now accept a boolean `complement` that allows to chose +between the small MOC described by the polygon or the bigger one (its complement) ### Changed diff --git a/docs/api.rst b/docs/api.rst index 81749d31..c5cb0d03 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,5 +1,5 @@ -API -=== +Detailed list of all methods +============================ Auto-generated API documentation for MOCpy. diff --git a/docs/examples/polygon_coverage.py b/docs/examples/polygon_coverage.py index 03027416..45c2bbe0 100644 --- a/docs/examples/polygon_coverage.py +++ b/docs/examples/polygon_coverage.py @@ -30,10 +30,7 @@ ], ) skycoord = SkyCoord(vertices, unit="deg", frame="icrs") -# A point that we say it belongs to the inside of the MOC -inside = SkyCoord(ra=10, dec=5, unit="deg", frame="icrs") -moc = MOC.from_polygon_skycoord(skycoord, max_depth=9) -moc.save("polygon_moc.fits", format="fits", overwrite=True) +moc = MOC.from_polygon_skycoord(skycoord, complement=False, max_depth=9) # Plot the MOC using matplotlib fig = plt.figure(111, figsize=(10, 10)) diff --git a/docs/examples/user_documentation.rst b/docs/examples/user_documentation.rst index 750b84ac..a6fb9644 100644 --- a/docs/examples/user_documentation.rst +++ b/docs/examples/user_documentation.rst @@ -11,11 +11,11 @@ Gallery of notebooks examples using SMOCs .. nbgallery:: ../_collections/notebooks/00-MOCpy_introduction - ../_collections/notebooks/compute_moc_borders.ipynb + ../_collections/notebooks/compute_moc_borders ../_collections/notebooks/filtering_astropy_table ../_collections/notebooks/FITS-image-pixels-intersecting-MOC ../_collections/notebooks/from_astropy_table.ipynb - ../_collections/notebooks/01-Creating_MOCs_from_shapes.ipynb + ../_collections/notebooks/01-Creating_MOCs_from_shapes ../_collections/notebooks/02-Creating_MOCs_from_astropy_regions ../_collections/notebooks/from_fits_and_intersection ../_collections/notebooks/from_image_with_mask @@ -59,14 +59,18 @@ This example: .. plot:: examples/logical_operations.py :include-source: -Create a MOC from a concave polygon -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Create a MOC from a polygon +~~~~~~~~~~~~~~~~~~~~~~~~~~~ This example shows how to call :py:meth:`mocpy.moc.MOC.from_polygon` or :py:meth:`mocpy.moc.MOC.from_polygon_skycoord`. .. plot:: examples/polygon_coverage.py :include-source: +For a more extended description on how to create MOCs from shapes, you can check the example notebooks +:doc:`../_collections/notebooks/01-Creating_MOCs_from_shapes` and +:doc:`../_collections/notebooks/02-Creating_MOCs_from_astropy_regions`. + Get the border(s) of a MOC ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/python/mocpy/moc/moc.py b/python/mocpy/moc/moc.py index b1111b47..6102c199 100644 --- a/python/mocpy/moc/moc.py +++ b/python/mocpy/moc/moc.py @@ -815,8 +815,7 @@ def probability_in_multiordermap(self, multiordermap): See Also -------- - probabilities_in_multiordermap: makes this calculation for a list of MOCs in a - parallelized way. + probabilities_in_multiordermap: makes this calculation for a list of MOCs """ index = self.store_index @@ -1358,7 +1357,7 @@ def from_ring( return cls(index) @classmethod - def from_polygon_skycoord(cls, skycoord, max_depth=10): + def from_polygon_skycoord(cls, skycoord, complement=False, max_depth=10): """ Create a MOC from a polygon. @@ -1369,6 +1368,8 @@ def from_polygon_skycoord(cls, skycoord, max_depth=10): ---------- skycoord : `astropy.coordinates.SkyCoord` The sky coordinates defining the vertices of a polygon. + complement : return the complement of the polygon. Set to False by default. + The default polygon is the smallest one. max_depth : int, optional The resolution of the MOC. Set to 10 by default. @@ -1394,24 +1395,33 @@ def from_polygon_skycoord(cls, skycoord, max_depth=10): return cls.from_polygon( lon=skycoord.icrs.ra, lat=skycoord.icrs.dec, + complement=complement, max_depth=np.uint8(max_depth), ) @classmethod - def from_polygons(cls, list_vertices, max_depth=10, n_threads=None): + def from_polygons( + cls, + list_vertices, + complement=False, + max_depth=10, + n_threads=None, + ): """ Create a list of MOCs list from a list of polygons. Parameters ---------- list_vertices : list[`~astropy.coordinates.SkyCoord`] OR numpy.ndarray - - If list_vertices is a list of `~astropy.coordinates.SkyCoord` objects, each + If list_vertices is a list of `~astropy.coordinates.SkyCoord` objects, each SkyCoord object should contain more than three vertices and they should each describe a polygon. - - If list_vertices is a numpy.ndarray, it should be in the form + If list_vertices is a numpy.ndarray, it should be in the form [lon_array1, lat_array1, lon_array2, lat_array2, lon_array3, lat_array3, ...]. They should be valid longitudes and latitudes in degrees in ICRS. - max_depth : int, optional + complement : return the complement of the polygon. Set to False by default. + The default polygon is the smallest one. + 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. If this is set to None (default value), @@ -1449,12 +1459,18 @@ def from_polygons(cls, list_vertices, max_depth=10, n_threads=None): for x in list_vertices for f in (lambda x: x.icrs.ra.deg, lambda x: x.icrs.dec.deg) ] - indices = mocpy.from_polygons(lon_lat_list, np.uint8(max_depth), n_threads) + indices = mocpy.from_polygons( + lon_lat_list, + complement, + np.uint8(max_depth), + n_threads, + ) else: # This is the unsafe version where the users should provide correct coordinates # without checks on our side indices = mocpy.from_polygons( np.array(list_vertices, dtype=np.float64), + complement, np.uint8(max_depth), n_threads, ) @@ -1477,6 +1493,7 @@ def from_polygon(cls, lon, lat, complement=False, max_depth=10): The latitudes defining the polygon. Can describe convex and concave polygons but not self-intersecting ones. complement : return the complement of the polygon. Set to False by default. + The default polygon is the smallest one. max_depth : int, optional The resolution of the MOC. Set to 10 by default. diff --git a/python/mocpy/tests/test_moc.py b/python/mocpy/tests/test_moc.py index 5408aa1a..6d3b9bd0 100644 --- a/python/mocpy/tests/test_moc.py +++ b/python/mocpy/tests/test_moc.py @@ -507,7 +507,10 @@ def test_moc_contains_2d_parameters(): lat2 = Angle(np.array([[20, 25, 10, 22], [-60, 80, 0, 10]]), unit=u.deg) moc = MOC.from_polygon(lon=lon, lat=lat, max_depth=12) should_be_inside = moc.contains_lonlat(lon=lon, lat=lat) + complement_moc = MOC.from_polygon(lon=lon, lat=lat, max_depth=12, complement=True) + assert complement_moc.sky_fraction > moc.sky_fraction assert should_be_inside.all() + # test mismatched with pytest.raises( ValueError, diff --git a/src/lib.rs b/src/lib.rs index 7e8700c2..724b317f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -601,11 +601,14 @@ fn mocpy(_py: Python, m: &PyModule) -> PyResult<()> { /// # Params /// * `lon_lat_deg` list of polygons vertices coordinates, of the form /// `lon_array_1, lat_array_1, lon_array_2, lat_array_2, ..., lon_array_b, lat_array_n` + /// * `is_complement` should the method return the complement of the polygon (false returns + /// the smallest polygon) /// * `depth`: MOC depth /// * `n_threads`: number of threads to use (max number of threads if `n_threads=None`. #[pyfn(m)] pub fn from_polygons( lon_lat_deg: Vec>, + complement: bool, depth: u8, n_threads: Option, ) -> PyResult> { @@ -640,7 +643,7 @@ fn mocpy(_py: Python, m: &PyModule) -> PyResult<()> { .map(|(lon, lat)| { U64MocStore::get_global_store().from_polygon( lon.iter().cloned().zip(lat.iter().cloned()), - false, + complement, depth, CellSelection::All, ) @@ -656,7 +659,7 @@ fn mocpy(_py: Python, m: &PyModule) -> PyResult<()> { .map(|(lon, lat)| { U64MocStore::get_global_store().from_polygon( lon.iter().cloned().zip(lat.iter().cloned()), - false, + complement, depth, CellSelection::All, )