# Licensed under a 3-clause BSD style license - see LICENSE.rst"""Regions helper functions.Throughout Gammapy, we use `regions` to represent and work with regions.https://astropy-regions.readthedocs.ioWe might add in other conveniences and features here, e.g. sky coord containswithout a WCS (see "sky and pixel regions" in PIG 10), or some HEALPix integration.TODO: before Gammapy v1.0, discuss what to do about ``gammapy.utils.regions``.Options: keep as-is, hide from the docs, or to remove it completely(if the functionality is available in ``astropy-regions`` directly."""importoperatorimportnumpyasnpfromscipy.optimizeimportBounds,minimizefromastropyimportunitsasufromastropy.coordinatesimportSkyCoordfromregionsimport(CircleAnnulusSkyRegion,CircleSkyRegion,CompoundSkyRegion,EllipseSkyRegion,RectangleSkyRegion,Regions,)__all__=["compound_region_to_regions","make_concentric_annulus_sky_regions","make_orthogonal_rectangle_sky_regions","regions_to_compound_region","region_to_frame",]defcompound_region_center(compound_region):"""Compute center for a CompoundRegion. The center of the compound region is defined here as the geometric median of the individual centers of the regions. The geometric median is defined as the point the minimises the distance to all other points. Parameters ---------- compound_region : `CompoundRegion` Compound region. Returns ------- center : `~astropy.coordinates.SkyCoord` Geometric median of the positions of the individual regions. """regions=compound_region_to_regions(compound_region)iflen(regions)==1:returnregions[0].centerpositions=SkyCoord([region.center.icrsforregioninregions])deff(x,coords):"""Function to minimize."""lon,lat=xcenter=SkyCoord(lon*u.deg,lat*u.deg)returnnp.sum(center.separation(coords).deg)ra,dec=positions.ra.wrap_at("180d").deg,positions.dec.degub=np.array([np.max(ra),np.max(dec)])lb=np.array([np.min(ra),np.min(dec)])ifnp.all(ub==lb):bounds=Noneelse:bounds=Bounds(ub=ub,lb=lb)result=minimize(f,x0=[np.mean(ra),np.mean(dec)],args=(positions,),bounds=bounds,method="L-BFGS-B",)returnSkyCoord(result.x[0],result.x[1],frame="icrs",unit="deg")
[docs]defcompound_region_to_regions(region):"""Create list of regions from compound regions. Parameters ---------- region : `~regions.CompoundSkyRegion` or `~regions.SkyRegion` Compound sky region. Returns ------- regions : `~regions.Regions` List of regions. """regions=Regions([])ifisinstance(region,CompoundSkyRegion):ifregion.operatorisoperator.or_:regions_1=compound_region_to_regions(region.region1)regions.extend(regions_1)regions_2=compound_region_to_regions(region.region2)regions.extend(regions_2)else:raiseValueError("Only union operator supported")else:returnRegions([region])returnregions
[docs]defregions_to_compound_region(regions):"""Create compound region from list of regions, by creating the union. Parameters ---------- regions : `~regions.Regions` List of regions. Returns ------- compound : `~regions.CompoundSkyRegion` or `~regions.CompoundPixelRegion` Compound sky region. """region_union=regions[0]forregioninregions[1:]:region_union=region_union.union(region)returnregion_union
classSphericalCircleSkyRegion(CircleSkyRegion):"""Spherical circle sky region. TODO: is this separate class a good idea? - If yes, we could move it to the ``regions`` package? - If no, we should implement some other solution. Probably the alternative is to add extra methods to the ``CircleSkyRegion`` class and have that support both planar approximation and spherical case? Or we go with the approach to always make a TAN WCS and not have true cone select at all? """defcontains(self,skycoord,wcs=None):"""Defined by spherical distance."""separation=self.center.separation(skycoord)returnseparation<self.radius
[docs]defmake_orthogonal_rectangle_sky_regions(start_pos,end_pos,wcs,height,nbin=1):"""Utility returning an array of regions to make orthogonal projections. Parameters ---------- start_pos : `~astropy.regions.SkyCoord` First sky coordinate defining the line to which the orthogonal boxes made. end_pos : `~astropy.regions.SkyCoord` Second sky coordinate defining the line to which the orthogonal boxes made. height : `~astropy.quantity.Quantity` Height of the rectangle region. wcs : `~astropy.wcs.WCS` WCS projection object. nbin : int, optional Number of boxes along the line. Default is 1. Returns ------- regions : list of `~regions.RectangleSkyRegion` Regions in which the profiles are made. """pix_start=start_pos.to_pixel(wcs)pix_stop=end_pos.to_pixel(wcs)points=np.linspace(start=pix_start,stop=pix_stop,num=nbin+1).Tcenters=0.5*(points[:,:-1]+points[:,1:])coords=SkyCoord.from_pixel(centers[0],centers[1],wcs)width=start_pos.separation(end_pos).to("rad")/nbinangle=end_pos.position_angle(start_pos)-90*u.degregions=[]forcenterincoords:reg=RectangleSkyRegion(center=center,width=width,height=u.Quantity(height),angle=angle)regions.append(reg)returnregions
[docs]defmake_concentric_annulus_sky_regions(center,radius_max,radius_min=1e-5*u.deg,nbin=11):"""Make a list of concentric annulus regions. Parameters ---------- center : `~astropy.coordinates.SkyCoord` Center coordinate. radius_max : `~astropy.units.Quantity` Maximum radius. radius_min : `~astropy.units.Quantity`, optional Minimum radius. Default is 1e-5 deg. nbin : int, optional Number of boxes along the line. Default is 11. Returns ------- regions : list of `~regions.RectangleSkyRegion` Regions in which the profiles are made. """regions=[]edges=np.linspace(radius_min,u.Quantity(radius_max),nbin)forr_in,r_outinzip(edges[:-1],edges[1:]):region=CircleAnnulusSkyRegion(center=center,inner_radius=r_in,outer_radius=r_out,)regions.append(region)returnregions
[docs]defregion_to_frame(region,frame):"""Convert a region to a different frame. Parameters ---------- region : `~regions.SkyRegion` Region to transform. frame : {"icrs", "galactic"} Frame to transform the region into. Returns ------- region_new : `~regions.SkyRegion` Region in the given frame. """fromgammapy.mapsimportWcsGeomwcs=WcsGeom.create(skydir=region.center,binsz=0.01,frame=frame).wcsregion_new=region.to_pixel(wcs).to_sky(wcs)returnregion_new
defregion_circle_to_ellipse(region):"""Convert a CircleSkyRegion to an EllipseSkyRegion. Parameters ---------- region : `~regions.CircleSkyRegion` Region to transform. Returns ------- region_new : `~regions.EllipseSkyRegion` Elliptical region with same major and minor axis. """region_new=EllipseSkyRegion(center=region.center,width=region.radius,height=region.radius)returnregion_new