Skip to content

Commit

Permalink
Merge pull request #173 from sushobhana/CRTF
Browse files Browse the repository at this point in the history
Converts a CRTF region string to Shape class.
  • Loading branch information
keflavich committed Jun 4, 2018
2 parents ab18ab4 + 28fd814 commit 79cc8df
Show file tree
Hide file tree
Showing 20 changed files with 997 additions and 239 deletions.
34 changes: 34 additions & 0 deletions docs/crtf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.. _gs-crtf:

Reading/writing to CRTF region files
====================================

The regions package provides the functionality to serialise and de-serialise
Python lists of `~regions.Region` objects to CRTF region strings.
De-serialisation is done using the `~regions.CRTFParser`. It converts the CRTF
string to `~regions.ShapeList` object, which is a list of `~regions.Shape` each
representing one CRTF region. The `~regions.Shape` objects can be converted to
`~regions.Region` objects.

.. code-block:: python
>>> from regions import CRTFParser
>>> reg_string = 'circle[[42deg, 43deg], 3deg], coord=J2000, color=green '
>>> parser = CRTFParser(reg_string)
>>> print(parser.shapes[0])
Shape
Format Type : CRTF
Type: reg
Coord sys : fk5
Region type : circle
Coord: [<Angle 42.0 deg>, <Angle 43.0 deg>, <Quantity 3.0 deg>]
Meta: {'color': 'green', 'coord': 'j2000', 'type': 'reg'}
Composite: False
Include: True
>>> regions = parser.shapes.to_regions()
>>> print(regions[0])
Region: CircleSkyRegion
center: <FK5 Coordinate (equinox=J2000.000): (ra, dec) in deg
( 42., 43.)>
radius: 3.0 deg
9 changes: 5 additions & 4 deletions docs/ds9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ representing one DS9 region. The `~regions.Shape` objects can be converted to
>>> parser = DS9Parser(reg_string)
>>> print(parser.shapes[0])
Shape
Format Type : DS9
Coord sys : galactic
Region type : circle
Coord: [<Angle 42.0 deg>, <Angle 43.0 deg>, <Quantity 3.0 deg>]
Meta: {'color': 'green'}
Meta: {'color': 'green', 'include': ''}
Composite: False
Include: True
>>> regions = parser.shapes.to_region()
>>> regions = parser.shapes.to_regions()
>>> print(regions[0])
Region: CircleSkyRegion
center: <Galactic Coordinate: (l, b) in deg
[( 42., 43.)]>
radius: 3.0 deg
( 42., 43.)>
radius: 3.0 deg
Serialisation is done using the `~regions.ds9_objects_to_string` function

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ User guide
masks
plotting
ds9
crtf
shapely

Advanced
Expand Down
2 changes: 1 addition & 1 deletion regions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
if not _ASTROPY_SETUP_:
from ._utils.examples import *
from .core import *
from .io import *
from .shapes import *
from .io import *
3 changes: 2 additions & 1 deletion regions/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Region core functionality.
"""
Region core functionality.
"""
from .core import *
from .pixcoord import *
Expand Down
10 changes: 9 additions & 1 deletion regions/io/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
from .ds9 import *
from .core import *

from .ds9.read import *
from .ds9.write import *
from .ds9.core import *

from .crtf.read import *
from .crtf.write import *
from .crtf.core import *
223 changes: 223 additions & 0 deletions regions/io/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

from __future__ import absolute_import, division, print_function
from collections import OrderedDict
from warnings import warn

from astropy import units as u
from astropy import coordinates
from astropy.coordinates import BaseCoordinateFrame
from astropy import log

__all__ = ['ShapeList', 'Shape']

from .. import shapes
from ..core import PixCoord
from .ds9.core import DS9RegionParserWarning, DS9RegionParserError
from .crtf.core import CRTFRegionParserWarning, CRTFRegionParserError


class ShapeList(list):
"""
List of Shape
"""
def to_regions(self):
regions = list()
for shape in self:
# Skip elliptical annulus for now
if shape.region_type == 'ellipse' and len(shape.coord) > 5:
msg = 'Skipping elliptical annulus {}'.format(shape)
warn(msg, DS9RegionParserWarning)
continue
log.debug(shape)
region = shape.to_region()
log.debug(region)
regions.append(region)
return regions


class Shape(object):
"""
Helper class to represent a DS9/CRTF Region.
This serves as intermediate step in the parsing process.
Parameters
----------
format_type : str
File Format type
coordsys : str
Coordinate system
region_type : str
Region type
coord : list of `~astropy.coordinates.Angle` or `~astropy.units.Quantity`
Coordinates
meta : dict
Meta attributes
composite : bool
Composite region
include : bool
Include/exclude region
"""

shape_to_sky_region = {'DS9': dict(circle=shapes.CircleSkyRegion,
ellipse=shapes.EllipseSkyRegion,
box=shapes.RectangleSkyRegion,
polygon=shapes.PolygonSkyRegion,
annulus=shapes.CircleAnnulusSkyRegion,
line=shapes.LineSkyRegion,
point=shapes.PointSkyRegion
),

'CRTF': dict(circle=shapes.CircleSkyRegion,
ellipse=shapes.EllipseSkyRegion,
centerbox=shapes.RectangleSkyRegion,
rotatedbox=shapes.RectangleSkyRegion,
pol=shapes.PolygonSkyRegion,
annulus=shapes.CircleAnnulusSkyRegion,
line=shapes.LineSkyRegion,
point=shapes.PointSkyRegion)
}
shape_to_pixel_region = {'DS9': dict(circle=shapes.CirclePixelRegion,
ellipse=shapes.EllipsePixelRegion,
box=shapes.RectanglePixelRegion,
polygon=shapes.PolygonPixelRegion,
annulus=shapes.CircleAnnulusPixelRegion,
line=shapes.LinePixelRegion,
point=shapes.PointPixelRegion
),

'CRTF': dict(circle=shapes.CirclePixelRegion,
ellipse=shapes.EllipsePixelRegion,
centerbox=shapes.RectanglePixelRegion,
rotatedbox=shapes.RectanglePixelRegion,
poly=shapes.PolygonPixelRegion,
annulus=shapes.CircleAnnulusPixelRegion,
line=shapes.LinePixelRegion,
point=shapes.PointPixelRegion
)
}

error = {'DS9': DS9RegionParserError, 'CRTF': CRTFRegionParserError}
warning = {'DS9': DS9RegionParserWarning, 'CRTF': CRTFRegionParserWarning}

def __init__(self, format_type, coordsys, region_type, coord, meta, composite, include):

from . import CRTFRegionParser, DS9Parser
self.parser = {'DS9': DS9Parser, 'CRTF': CRTFRegionParser}

self.format_type = format_type
self.coordsys = coordsys
self.region_type = region_type
self.coord = coord
self.meta = meta
self.composite = composite
self.include = include

def __str__(self):
ss = self.__class__.__name__
ss += '\nFormat Type : {}'.format(self.format_type)
if self.format_type == 'CRTF':
ss += '\nType : {}'.format(self.meta.get('type', 'reg'))
ss += '\nCoord sys : {}'.format(self.coordsys)
ss += '\nRegion type : {}'.format(self.region_type)
if self.region_type == 'symbol':
ss += '\nSymbol : {}'.format(self.meta['symbol'])
if self.region_type == 'text':
ss += '\nText : {}'.format(self.meta['string'])
ss += '\nCoord: {}'.format(self.coord)
ss += '\nMeta: {}'.format(self.meta)
ss += '\nComposite: {}'.format(self.composite)
ss += '\nInclude: {}'.format(self.include)
ss += '\n'
return ss

def convert_coords(self):
"""
Process list of coordinates
This mainly seaches for tuple of coordinates in the coordinate list and
creates a SkyCoord or PixCoord object from them if appropriate for a
given region type. This involves again some coordinate transformation,
so this step could be moved to the parsing process
"""
if self.coordsys in self.parser[self.format_type].coordsys_mapping:
coords = self._convert_sky_coords()
else:
coords = self._convert_pix_coords()

if self.region_type == 'line':
coords = [coords[0][0], coords[0][1]]

return coords

def _convert_sky_coords(self):
"""
Convert to sky coords
"""
parsed_angles = [(x, y)
for x, y in zip(self.coord[:-1:2], self.coord[1::2])
if (isinstance(x, coordinates.Angle) and
isinstance(y, coordinates.Angle))
]
frame = coordinates.frame_transform_graph.lookup_name(self.coordsys)

lon, lat = zip(*parsed_angles)
if hasattr(lon, '__len__') and hasattr(lat, '__len__') and len(lon) == 1 and len(lat) == 1:
# force entries to be scalar if they are length-1
lon, lat = u.Quantity(lon[0]), u.Quantity(lat[0])
else:
# otherwise, they are vector quantities
lon, lat = u.Quantity(lon), u.Quantity(lat)
sphcoords = coordinates.UnitSphericalRepresentation(lon, lat)
coords = [frame(sphcoords)]

if self.region_type != 'polygon':
coords += self.coord[len(coords * 2):]

return coords

def _convert_pix_coords(self):
"""
Convert to pixel coordinates, `regions.PixCoord`
"""
if self.region_type in ['polygon', 'line', 'poly']:
# have to special-case polygon in the phys coord case
# b/c can't typecheck when iterating as in sky coord case
coords = [PixCoord(self.coord[0::2], self.coord[1::2])]
else:
temp = [_.value for _ in self.coord]
coord = PixCoord(temp[0], temp[1])
coords = [coord] + temp[2:]

return coords

def to_region(self):
"""
Convert to region object
"""

coords = self.convert_coords()
log.debug(coords)
viz_keywords = ['color', 'dashed', 'width', 'point', 'font', 'symsize', 'symsize', 'fontsize', 'fontstyle',
'usetex', 'labelpos', 'labeloff', 'linewidth', 'linestyle']

if isinstance(coords[0], BaseCoordinateFrame):
reg = self.shape_to_sky_region[self.format_type][self.region_type](*coords)
elif isinstance(coords[0], PixCoord):
reg = self.shape_to_pixel_region[self.format_type][self.region_type](*coords)
else:
self._raise_error("No central coordinate")

reg.visual = OrderedDict()
reg.meta = OrderedDict()
for key in self.meta.keys():
if key in viz_keywords:
reg.visual[key] = self.meta[key]
else:
reg.meta[key] = self.meta[key]
reg.meta['include'] = self.include
return reg

def _raise_error(self, msg):
raise self.error[self.format_type](msg)
Empty file added regions/io/crtf/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions regions/io/crtf/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, division, print_function

from astropy.utils.exceptions import AstropyUserWarning

__all__ = [
'CRTFRegionParserWarning',
'CRTFRegionParserError',
]


class CRTFRegionParserWarning(AstropyUserWarning):
"""
A generic warning class for CRTF region parsing
"""


class CRTFRegionParserError(ValueError):
"""
A generic error class for CRTF region parsing
"""
Loading

0 comments on commit 79cc8df

Please sign in to comment.