Source code for ginga.util.ap_region

#
# ap_region.py -- astropy-regions support
#
# This is open-source software licensed under a BSD license.
# Please see the file LICENSE.txt for details.
#
"""
This module provides Ginga support for DS9 type region files and objects via
the ``astropy-regions`` package.
"""
import numpy as np

from astropy import units as u
from astropy.coordinates import SkyCoord

from ginga.canvas.CanvasObject import get_canvas_types

HAVE_REGIONS = False
try:
    import regions
    HAVE_REGIONS = True
except ImportError:
    pass


__all__ = ['astropy_region_to_ginga_canvas_object', 'add_region',
           'ginga_canvas_object_to_astropy_region']


# mappings of point styles
pt_ginga = {'*': 'square', 'x': 'cross', '+': 'plus', 'D': 'diamond'}
pt_regions = {v: k for k, v in pt_ginga.items()}
# mappings of arrow styles
arr_ginga = {'0 0': 'none', '1 0': 'start', '0 1': 'end', '1 1': 'both'}
arr_regions = {v: k for k, v in arr_ginga.items()}


[docs]def astropy_region_to_ginga_canvas_object(r, logger=None): """ Convert an astropy-region object to a Ginga canvas object. Parameters ---------- r : subclass of `~regions.PixelRegion` The region object to be converted logger : a Python logger (optional, default: None) A logger to which errors will be written Returns ------- obj : subclass of `~ginga.canvas.CanvasObject` The corresponding Ginga canvas object """ if not HAVE_REGIONS: raise ValueError("Please install the Astropy 'regions' package to use this function") dc = get_canvas_types() obj = None if isinstance(r, (regions.CirclePixelRegion,)): obj = dc.Circle(r.center.x, r.center.y, r.radius) elif isinstance(r, (regions.CircleSkyRegion,)): obj = dc.Circle(r.center.ra.deg, r.center.dec.deg, r.radius.to(u.deg).value, coord='wcs') elif isinstance(r, (regions.EllipsePixelRegion,)): obj = dc.Ellipse(r.center.x, r.center.y, r.width * 0.5, r.height * 0.5, rot_deg=r.angle.to(u.deg).value) elif isinstance(r, (regions.EllipseSkyRegion,)): obj = dc.Ellipse(r.center.ra.deg, r.center.dec.deg, (r.width * 0.5).to(u.deg).value, (r.height * 0.5).to(u.deg).value, rot_deg=r.angle.to(u.deg).value, coord='wcs') # NOTE: need to check for Text before Point, because Text seems to be # a subclass of Point in regions elif isinstance(r, (regions.TextPixelRegion,)): # NOTE: font needed here, but will be overridden later if specified # in the region's visuals obj = dc.Text(r.center.x, r.center.y, text=r.text, font='sans', rot_deg=float(r.visual.get('textangle', 0.0))) elif isinstance(r, (regions.TextSkyRegion,)): # NOTE: font needed here, but will be overridden later if specified # in the region's visuals obj = dc.Text(r.center.ra.deg, r.center.dec.deg, text=r.text, font='sans', rot_deg=float(r.visual.get('textangle', 0.0)), coord='wcs') elif isinstance(r, (regions.PointPixelRegion,)): # what is a reasonable default radius? radius = 15 # pixels # convert the regions-encoded style for a point into the # corresponding ginga style for a point, defaulting to "diamond" # if there is no direct match. style = r.visual.get('symbol', '*') style = pt_ginga.get(style, 'diamond') obj = dc.Point(r.center.x, r.center.y, radius, style=style) elif isinstance(r, (regions.PointSkyRegion,)): # what is a reasonable default radius? radius = 0.001 # degrees # see comment for PointPixelRegion style = r.visual.get('symbol', '*') style = pt_ginga.get(style, 'diamond') obj = dc.Point(r.center.ra.deg, r.center.dec.deg, radius, style=style, coord='wcs') elif isinstance(r, (regions.LinePixelRegion,)): obj = dc.Line(r.start.x, r.start.y, r.end.x, r.end.y) obj.arrow = arr_ginga[r.meta.get('line', '0 0')] elif isinstance(r, (regions.LineSkyRegion,)): obj = dc.Line(r.start.ra.deg, r.start.dec.deg, r.end.ra.deg, r.end.dec.deg, coord='wcs') obj.arrow = arr_ginga[r.meta.get('line', '0 0')] elif isinstance(r, (regions.RectanglePixelRegion,)): obj = dc.Box(r.center.x, r.center.y, r.width * 0.5, r.height * 0.5, rot_deg=r.angle.to(u.deg).value) elif isinstance(r, (regions.RectangleSkyRegion,)): obj = dc.Box(r.center.ra.deg, r.center.dec.deg, (r.width * 0.5).to(u.deg).value, (r.height * 0.5).to(u.deg).value, rot_deg=r.angle.to(u.deg).value, coord='wcs') elif isinstance(r, (regions.PolygonPixelRegion,)): points = np.array(r.vertices.xy).T obj = dc.Polygon(points) elif isinstance(r, (regions.PolygonSkyRegion,)): points = np.array((r.vertices.ra.deg, r.vertices.dec.deg)).T obj = dc.Polygon(points, coord='wcs') elif isinstance(r, (regions.CircleAnnulusPixelRegion,)): rin = r.inner_radius rout = r.outer_radius wd = rout - rin obj = dc.Annulus(r.center.x, r.center.y, rin, width=wd, atype='circle') elif isinstance(r, (regions.CircleAnnulusSkyRegion,)): rin = r.inner_radius.to(u.deg).value rout = r.outer_radius.to(u.deg).value wd = rout - rin obj = dc.Annulus(r.center.ra.deg, r.center.dec.deg, rin, width=wd, atype='circle', coord='wcs') elif isinstance(r, (regions.EllipseAnnulusPixelRegion,)): xwd = (r.outer_width - r.inner_width) * 0.5 ywd = (r.outer_height - r.inner_height) * 0.5 obj = dc.Annulus2R(r.center.x, r.center.y, r.inner_width * 0.5, r.inner_height * 0.5, xwidth=xwd, ywidth=ywd, atype='ellipse', rot_deg=r.angle.to(u.deg).value) elif isinstance(r, (regions.EllipseAnnulusSkyRegion,)): xwd = ((r.outer_width - r.inner_width) * 0.5).to(u.deg).value ywd = ((r.outer_height - r.inner_height) * 0.5).to(u.deg).value obj = dc.Annulus2R(r.center.ra.deg, r.center.dec.deg, (r.inner_width * 0.5).to(u.deg).value, (r.inner_height * 0.5).to(u.deg).value, xwidth=xwd, ywidth=ywd, atype='ellipse', rot_deg=r.angle.to(u.deg).value, coord='wcs') elif isinstance(r, (regions.RectangleAnnulusPixelRegion,)): xwd = (r.outer_width - r.inner_width) * 0.5 ywd = (r.outer_height - r.inner_height) * 0.5 obj = dc.Annulus2R(r.center.x, r.center.y, r.inner_width * 0.5, r.inner_height * 0.5, xwidth=xwd, ywidth=ywd, atype='box', rot_deg=r.angle.to(u.deg).value) elif isinstance(r, (regions.RectangleAnnulusSkyRegion,)): xwd = ((r.outer_width - r.inner_width) * 0.5).to(u.deg).value ywd = ((r.outer_height - r.inner_height) * 0.5).to(u.deg).value obj = dc.Annulus2R(r.center.ra.deg, r.center.dec.deg, (r.inner_width * 0.5).to(u.deg).value, (r.inner_height * 0.5).to(u.deg).value, xwidth=xwd, ywidth=ywd, atype='box', rot_deg=r.angle.to(u.deg).value, coord='wcs') else: errmsg = "Don't know how to convert this object of type: {}".format(str(type(r))) if logger is not None: # if a logger is passed, simply note the error message in the # log and convert the object to a Text with the error message logger.error(errmsg, exc_info=True) obj = dc.Text(0, 0, text=errmsg, font='sans') else: raise ValueError(errmsg) # Set visual styling attributes obj.color = r.visual.get('edgecolor', r.visual.get('color', 'green')) if hasattr(obj, 'font'): obj.font = r.visual.get('fontname', 'Sans') if 'fontsize' in r.visual: obj.fontsize = int(r.visual['fontsize']) if hasattr(obj, 'linewidth'): obj.linewidth = r.visual.get('linewidth', 1) if hasattr(obj, 'fill'): obj.fill = r.visual.get('fill', False) obj.fillcolor = r.visual.get('facecolor', obj.color) # Limited support for other metadata obj.editable = r.meta.get('edit', True) obj.set_data(name=r.meta.get('name', None)) # needed for compound objects like annulus obj.sync_state() return obj
[docs]def add_region(canvas, r, tag=None, redraw=True): """ Convenience function to plot an astropy-regions object on a Ginga canvas. Parameters ---------- canvas : `~ginga.canvas.types.layer.DrawingCanvas` The Ginga canvas on which the region should be plotted. r : subclass of `~regions.PixelRegion` The region object to be plotted tag : str or None (optional, default: None) Caller can optionally pass a specific tag for the canvas object redraw : bool (optional, default: True) True if the viewers of the canvas should be updated immediately """ obj = astropy_region_to_ginga_canvas_object(r) if tag is None: tag = obj.get_data('name') if obj is not None: canvas.add(obj, tag=tag, redraw=redraw) return obj
[docs]def ginga_canvas_object_to_astropy_region(obj, frame='icrs', logger=None): """ Convert a Ginga canvas object to an astropy-region object. Parameters ---------- obj : subclass of `~ginga.canvas.CanvasObject.CanvasObjectBase` The Ginga canvas object to be converted frame : str (optional, default: 'icrs') The type of astropy frame that should be generated for Sky regions logger : a Python logger (optional, default: None) A logger to which errors will be written Returns ------- r : subclass of `~regions.PixelRegion` or `~regions.SkyRegion` The corresponding astropy-region object """ if not HAVE_REGIONS: raise ValueError("Please install the Astropy 'regions' package to use this function") dc = get_canvas_types() r = None if isinstance(obj, (dc.Circle,)): if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.CirclePixelRegion(center=center, radius=obj.radius) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.CircleSkyRegion(center=center, radius=obj.radius * u.deg) elif isinstance(obj, (dc.Ellipse,)): if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.EllipsePixelRegion(center=center, width=obj.xradius * 2, height=obj.yradius * 2, angle=obj.rot_deg * u.deg) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.EllipseSkyRegion(center=center, width=obj.xradius * 2 * u.deg, height=obj.yradius * 2 * u.deg, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Text,)): if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.TextPixelRegion(center=center, text=obj.text) r.visual['textangle'] = str(obj.rot_deg) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.TextSkyRegion(center=center, text=obj.text) r.visual['textangle'] = str(obj.rot_deg) elif isinstance(obj, (dc.Point,)): style = pt_regions.get(obj.style, '*') if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.PointPixelRegion(center=center) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.PointSkyRegion(center=center) r.visual['symbol'] = style elif isinstance(obj, (dc.Line,)): if obj.coord in ('data', None): start = regions.PixCoord(x=obj.x1, y=obj.y1) end = regions.PixCoord(x=obj.x2, y=obj.y2) r = regions.LinePixelRegion(start=start, end=end) elif obj.coord == 'wcs': start = SkyCoord(obj.x1, obj.y1, unit='deg', frame=frame) end = SkyCoord(obj.x2, obj.y2, unit='deg', frame=frame) r = regions.LineSkyRegion(start=start, end=end) r.meta['line'] = arr_regions.get(obj.arrow, '0 0') elif isinstance(obj, (dc.Box,)): if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.RectanglePixelRegion(center=center, width=obj.xradius * 2, height=obj.yradius * 2, angle=obj.rot_deg * u.deg) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.RectangleSkyRegion(center=center, width=obj.xradius * 2 * u.deg, height=obj.yradius * 2 * u.deg, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Polygon,)): x, y = np.asarray(obj.points).T if obj.coord in ('data', None): vertices = regions.PixCoord(x=x, y=y) r = regions.PolygonPixelRegion(vertices=vertices) elif obj.coord == 'wcs': vertices = SkyCoord(x, y, unit='deg', frame=frame) r = regions.PolygonSkyRegion(vertices=vertices) elif isinstance(obj, (dc.Annulus,)) and obj.atype == 'circle': if obj.coord in ('data', None): rin = obj.radius rout = rin + obj.width center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.CircleAnnulusPixelRegion(center=center, inner_radius=rin, outer_radius=rout) elif obj.coord == 'wcs': rin = obj.radius * u.deg rout = rin + obj.width * u.deg center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.CircleAnnulusSkyRegion(center=center, inner_radius=rin, outer_radius=rout) elif isinstance(obj, (dc.Annulus2R,)) and obj.atype == 'ellipse': if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.EllipseAnnulusPixelRegion(center=center, inner_width=obj.xradius * 2, inner_height=obj.yradius * 2, outer_width=obj.xradius * 2 + obj.xwidth * 2, outer_height=obj.yradius * 2 + obj.ywidth * 2, angle=obj.rot_deg * u.deg) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.EllipseAnnulusSkyRegion(center=center, inner_width=obj.xradius * 2 * u.deg, inner_height=obj.yradius * 2 * u.deg, outer_width=obj.xradius * 2 * u.deg + obj.xwidth * 2 * u.deg, outer_height=obj.yradius * 2 * u.deg + obj.ywidth * 2 * u.deg, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Annulus2R,)) and obj.atype == 'box': if obj.coord in ('data', None): center = regions.PixCoord(x=obj.x, y=obj.y) r = regions.RectangleAnnulusPixelRegion(center=center, inner_width=obj.xradius * 2, inner_height=obj.yradius * 2, outer_width=obj.xradius * 2 + obj.xwidth * 2, outer_height=obj.yradius * 2 + obj.ywidth * 2, angle=obj.rot_deg * u.deg) elif obj.coord == 'wcs': center = SkyCoord(obj.x, obj.y, unit='deg', frame=frame) r = regions.RectangleAnnulusSkyRegion(center=center, inner_width=obj.xradius * 2 * u.deg, inner_height=obj.yradius * 2 * u.deg, outer_width=obj.xradius * 2 * u.deg + obj.xwidth * 2 * u.deg, outer_height=obj.yradius * 2 * u.deg + obj.ywidth * 2 * u.deg, angle=obj.rot_deg * u.deg) else: errmsg = "Don't know how to convert this kind of object: {}".format(obj.kind) if logger is not None: # if a logger is passed, simply note the error message in the # log and convert the object to a TextPixelRegion with the # error message logger.error(errmsg, exc_info=True) r = regions.TextPixelRegion(center=regions.PixCoord(x=0, y=0), text=errmsg) else: raise ValueError(errmsg) # Set visual styling attributes r.visual['color'] = obj.color r.visual['edgecolor'] = obj.color if hasattr(obj, 'font'): r.visual['fontname'] = obj.font if obj.fontsize is not None: r.visual['fontsize'] = str(obj.fontsize) if hasattr(obj, 'linewidth'): r.visual['linewidth'] = obj.linewidth if hasattr(obj, 'fill'): r.visual['fill'] = 1 if obj.fill else 0 r.visual['facecolor'] = obj.fillcolor # Limited support for other metadata r.meta['edit'] = 1 if obj.editable else 0 meta = obj.get_data() if meta is not None and meta.get('name', None) is not None: r.meta['name'] = meta.get('name') return r
def import_regions(regions_file, format='ds9', logger=None): """ Convenience function to read a file containing regions and return a list of matching Ginga canvas objects. Parameters ---------- regions_file : str Path of a astropy-regions compatible file format : str (optional, default: 'ds9') Format of the astropy-regions compatible file logger : a Python logger (optional, default: None) A logger to which errors will be written Returns ------- objs : list Returns a list of Ginga canvas objects that can be added to a Ginga canvas """ regs = regions.Regions.read(regions_file, format=format) return [astropy_region_to_ginga_canvas_object(r, logger=logger) for r in regs] def export_regions(objs, logger=None): """ Convenience function to convert a sequence of Ginga canvas objects to a ds9 file containing regions and return a list of matching astropy-regions shapes. Parameters ---------- objs : seq of subclasses of `~ginga.canvas.CanvasObject.CanvasObjectBase` Sequence of Ginga canvas objects compatible with Regions logger : a Python logger (optional, default: None) A logger to which errors will be written Returns ------- regions : `~regions.Regions` object Returns an astropy-regions object """ def _g2r(obj): return ginga_canvas_object_to_astropy_region(obj, logger=logger) regs = regions.Regions(map(_g2r, objs)) return regs def export_regions_canvas(canvas, logger=None): """ Convenience function to convert a Ginga canvas's collection of objects to a ds9 file containing regions and return a list of matching astropy-regions shapes. Parameters ---------- canvas : a `~ginga.canvas.types.layer.Canvas` object or subclass thereof a Ginga canvas object logger : a Python logger (optional, default: None) A logger to which errors will be written Returns ------- regions : `~regions.Regions` object Returns an astropy-regions object """ # TODO: support nested canvases, etc? def _g2r(obj): return ginga_canvas_object_to_astropy_region(obj, logger=logger) objs = canvas.objects return regions.Regions(map(_g2r, objs))