diff --git a/js/models/message_handler.js b/js/models/message_handler.js index dc38e4b9..a365652f 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -40,10 +40,37 @@ export default class MessageHandler { case "stcs": overlay.add(A.footprintsFromSTCS(infos.stcs, options)); break; - case "circle": + case "circle_pixel": + [infos.ra, infos.dec] = this.aladin.pix2world(infos.x, infos.y, "ICRS"); + infos.radius = this.aladin.angularDist( + infos.x, + infos.y, + infos.x + infos.radius, + infos.y, + "ICRS", + ); + // Voluntary fallthrough + case "circle_sky": overlay.add(A.circle(infos.ra, infos.dec, infos.radius, options)); break; - case "ellipse": + case "ellipse_pixel": + [infos.ra, infos.dec] = this.aladin.pix2world(infos.x, infos.y, "ICRS"); + infos.a = this.aladin.angularDist( + infos.x, + infos.y, + infos.x + infos.a, + infos.y, + "ICRS", + ); + infos.b = this.aladin.angularDist( + infos.x, + infos.y, + infos.x, + infos.y + infos.b, + "ICRS", + ); + // Voluntary fallthrough + case "ellipse_sky": overlay.add( A.ellipse( infos.ra, @@ -55,12 +82,29 @@ export default class MessageHandler { ), ); break; - case "line": + case "line_pixel": + [infos.ra1, infos.dec1] = this.aladin.pix2world( + infos.x1, + infos.y1, + "ICRS", + ); + [infos.ra2, infos.dec2] = this.aladin.pix2world( + infos.x2, + infos.y2, + "ICRS", + ); + // Voluntary fallthrough + case "line_sky": overlay.add( A.line(infos.ra1, infos.dec1, infos.ra2, infos.dec2, options), ); break; - case "polygon": + case "polygon_pixel": + infos.vertices = infos.vertices.map(([x, y]) => + this.aladin.pix2world(x, y, "ICRS"), + ); + // Voluntary fallthrough + case "polygon_sky": overlay.add(A.polygon(infos.vertices, options)); break; } diff --git a/src/ipyaladin/aladin.py b/src/ipyaladin/aladin.py index dbfb73f3..09777225 100644 --- a/src/ipyaladin/aladin.py +++ b/src/ipyaladin/aladin.py @@ -18,19 +18,9 @@ try: from regions import ( - CircleSkyRegion, - EllipseSkyRegion, - LineSkyRegion, - PolygonSkyRegion, - RectangleSkyRegion, Region, ) except ImportError: - CircleSkyRegion = None - EllipseSkyRegion = None - LineSkyRegion = None - PolygonSkyRegion = None - RectangleSkyRegion = None Region = None from traitlets import ( Float, @@ -357,11 +347,7 @@ def add_overlay( self, region: Union[ str, - CircleSkyRegion, - EllipseSkyRegion, - LineSkyRegion, - PolygonSkyRegion, - RectangleSkyRegion, + Region, ], **overlay_options: any, ) -> None: @@ -369,17 +355,15 @@ def add_overlay( Parameters ---------- - region: str, CircleSkyRegion, EllipseSkyRegion, LineSkyRegion - The region to overlay. It can be a string, a CircleSkyRegion, - an EllipseSkyRegion, a LineSkyRegion or a RectangleSkyRegion. + region: str or regions object overlay_options: keyword arguments """ # Check if the regions library is installed and raise an error if not if ( - not isinstance(region, str) and CircleSkyRegion is None + not isinstance(region, str) and Region is None ): # Only need to check one of the imports - raise ValueError( + raise ImportError( "A region can be given as an STC-S string or a regions " "object. To read regions objects, you need to install the regions " "library with 'pip install regions'." diff --git a/src/ipyaladin/region_converter.py b/src/ipyaladin/region_converter.py index 3e207fb9..3688f19f 100644 --- a/src/ipyaladin/region_converter.py +++ b/src/ipyaladin/region_converter.py @@ -6,6 +6,12 @@ CircleSkyRegion, EllipseSkyRegion, LineSkyRegion, + CirclePixelRegion, + EllipsePixelRegion, + LinePixelRegion, + PolygonPixelRegion, + RectanglePixelRegion, + PixCoord, ) from astropy.coordinates import SkyCoord from typing import Union @@ -184,6 +190,47 @@ def rectangle_to_polygon_region(region: RectangleSkyRegion) -> PolygonSkyRegion: ) +def rectangle_pixel_to_polygon_pixel( + region: RectanglePixelRegion, +) -> PolygonPixelRegion: + """Convert a RectanglePixelRegion to a PolygonPixelRegion. + + Parameters + ---------- + region : RectanglePixelRegion + The region to convert. + + Returns + ------- + PolygonPixelRegion + The converted region. + + """ + center = region.center + vertices = [ + [center.x - region.width / 2, center.y - region.height / 2], + [center.x + region.width / 2, center.y - region.height / 2], + [center.x + region.width / 2, center.y + region.height / 2], + [center.x - region.width / 2, center.y + region.height / 2], + ] + rotation = region.angle.deg + if rotation != 0: + rotation = math.radians(rotation) + cos_rot = math.cos(rotation) + sin_rot = math.sin(rotation) + for vertex in vertices: + x = vertex[0] - center.x + y = vertex[1] - center.y + vertex[0] = x * cos_rot - y * sin_rot + center.x + vertex[1] = x * sin_rot + y * cos_rot + center.y + vertices = { + "x": [vertex[0] for vertex in vertices], + "y": [vertex[1] for vertex in vertices], + } + vertices = PixCoord(x=vertices["x"], y=vertices["y"]) + return PolygonPixelRegion(vertices=vertices, visual=region.visual, meta=region.meta) + + class RegionInfos: """Extract information from a region. @@ -200,10 +247,15 @@ def __init__(self, region: Union[str, Region]) -> None: self._region_parsers = { "str": self._from_stcs, "CircleSkyRegion": self._from_circle_sky_region, + "CirclePixelRegion": self._from_circle_pixel_region, "EllipseSkyRegion": self._from_ellipse_sky_region, + "EllipsePixelRegion": self._from_ellipse_pixel_region, "LineSkyRegion": self._from_line_sky_region, + "LinePixelRegion": self._from_line_pixel_region, "PolygonSkyRegion": self._from_polygon_sky_region, + "PolygonPixelRegion": self._from_polygon_pixel_region, "RectangleSkyRegion": self._from_rectangle_sky_region, + "RectanglePixelRegion": self._from_rectangle_pixel_region, } self.from_region(region) @@ -226,15 +278,23 @@ def _from_stcs(self, stcs: str) -> None: self.infos = {"stcs": stcs} def _from_circle_sky_region(self, region: CircleSkyRegion) -> None: - self.region_type = "circle" + self.region_type = "circle_sky" self.infos = { "ra": region.center.ra.deg, "dec": region.center.dec.deg, "radius": region.radius.deg, } + def _from_circle_pixel_region(self, region: CirclePixelRegion) -> None: + self.region_type = "circle_pixel" + self.infos = { + "x": region.center.x, + "y": region.center.y, + "radius": region.radius, + } + def _from_ellipse_sky_region(self, region: EllipseSkyRegion) -> None: - self.region_type = "ellipse" + self.region_type = "ellipse_sky" self.infos = { "ra": region.center.ra.deg, "dec": region.center.dec.deg, @@ -243,8 +303,18 @@ def _from_ellipse_sky_region(self, region: EllipseSkyRegion) -> None: "theta": region.angle.deg, } + def _from_ellipse_pixel_region(self, region: EllipsePixelRegion) -> None: + self.region_type = "ellipse_pixel" + self.infos = { + "x": region.center.x, + "y": region.center.y, + "a": region.width, + "b": region.height, + "theta": region.angle.deg, + } + def _from_line_sky_region(self, region: LineSkyRegion) -> None: - self.region_type = "line" + self.region_type = "line_sky" self.infos = { "ra1": region.start.ra.deg, "dec1": region.start.dec.deg, @@ -252,11 +322,29 @@ def _from_line_sky_region(self, region: LineSkyRegion) -> None: "dec2": region.end.dec.deg, } + def _from_line_pixel_region(self, region: LinePixelRegion) -> None: + self.region_type = "line_pixel" + self.infos = { + "x1": region.start.x, + "y1": region.start.y, + "x2": region.end.x, + "y2": region.end.y, + } + def _from_polygon_sky_region(self, region: PolygonSkyRegion) -> None: - self.region_type = "polygon" + self.region_type = "polygon_sky" vertices = [[coord.ra.deg, coord.dec.deg] for coord in region.vertices] self.infos = {"vertices": vertices} + def _from_polygon_pixel_region(self, region: PolygonPixelRegion) -> None: + self.region_type = "polygon_pixel" + vertices = [[coord.x, coord.y] for coord in region.vertices] + self.infos = {"vertices": vertices} + def _from_rectangle_sky_region(self, region: RectangleSkyRegion) -> None: # Rectangle is interpreted as a polygon in Aladin Lite self._from_polygon_sky_region(rectangle_to_polygon_region(region)) + + def _from_rectangle_pixel_region(self, region: RectanglePixelRegion) -> None: + # Rectangle is interpreted as a polygon in Aladin Lite + self._from_polygon_pixel_region(rectangle_pixel_to_polygon_pixel(region))