Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added translator for CircleAnnulusPixelRegion #90

Merged
merged 4 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions glue_astronomy/translators/regions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from glue.config import subset_state_translator
from glue.core.subset import (RoiSubsetState, RangeSubsetState, OrState, AndState,
XorState, MultiOrState, Subset, MultiRangeSubsetState)
XorState, MultiOrState, Subset, MultiRangeSubsetState, InvertState)
from glue.core.roi import (RectangularROI, PolygonalROI, CircularROI, PointROI,
RangeROI, AbstractMplRoi, EllipticalROI)
from glue.viewers.image.pixel_selection_subset_state import PixelSubsetState

from astropy import units as u
from regions import (RectanglePixelRegion, PolygonPixelRegion, CirclePixelRegion,
PointPixelRegion, PixCoord, EllipsePixelRegion)
PointPixelRegion, PixCoord, EllipsePixelRegion,
AnnulusPixelRegion, CircleAnnulusPixelRegion)

__all__ = ["range_to_rect", "AstropyRegionsHandler"]


def range_to_rect(data, ori, low, high):
Expand Down Expand Up @@ -46,6 +49,38 @@ def range_to_rect(data, ori, low, high):
return RectanglePixelRegion(PixCoord(xcen, ycen), width, height)


def _is_annulus(subset_state):
# subset_state.state1 = outer circle
# subset_state.state2 = inner circle
# subset_state.state2 is inverted, so we need its state1
return ((not isinstance(subset_state.state1, InvertState)) and
isinstance(subset_state.state1.roi, CircularROI) and
isinstance(subset_state.state2, InvertState) and
isinstance(subset_state.state2.state1.roi, CircularROI) and
(subset_state.state1.roi.xc == subset_state.state2.state1.roi.xc) and
(subset_state.state1.roi.yc == subset_state.state2.state1.roi.yc) and
(subset_state.state1.roi.radius > subset_state.state2.state1.roi.radius))


# Put this here because there is nowhere else to put it.
# https://github.com/glue-viz/glue/issues/2390
def _annulus_to_subset_state(reg, data):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""AnnulusPixelRegion to glue subset state."""
if not isinstance(reg, AnnulusPixelRegion): # pragma: no cover
raise ValueError(f"{reg} is not an AnnulusPixelRegion instance")
# TODO: Add ellipse and rectangle annulus support.
if not isinstance(reg, CircleAnnulusPixelRegion): # pragma: no cover
raise NotImplementedError(f"{reg} not supported")

xcen = reg.center.x
ycen = reg.center.y
state1 = RoiSubsetState(data.pixel_component_ids[1], data.pixel_component_ids[0],
CircularROI(xcen, ycen, reg.outer_radius))
state2 = RoiSubsetState(data.pixel_component_ids[1], data.pixel_component_ids[0],
CircularROI(xcen, ycen, reg.inner_radius))
return AndState(state1, ~state2)


@subset_state_translator('astropy-regions')
class AstropyRegionsHandler:

Expand Down Expand Up @@ -132,11 +167,17 @@ def to_object(self, subset):
return PointPixelRegion(PixCoord(*subset_state.get_xy(data, 1, 0)))

elif isinstance(subset_state, AndState):
temp_sub1 = Subset(data=data)
temp_sub1.subset_state = subset_state.state1
temp_sub2 = Subset(data=data)
temp_sub2.subset_state = subset_state.state2
return self.to_object(temp_sub1) & self.to_object(temp_sub2)
if _is_annulus(subset_state):
return CircleAnnulusPixelRegion(
center=PixCoord(x=subset_state.state1.roi.xc, y=subset_state.state1.roi.yc),
inner_radius=subset_state.state2.state1.roi.radius,
outer_radius=subset_state.state1.roi.radius)
else:
temp_sub1 = Subset(data=data)
temp_sub1.subset_state = subset_state.state1
temp_sub2 = Subset(data=data)
temp_sub2.subset_state = subset_state.state2
return self.to_object(temp_sub1) & self.to_object(temp_sub2)

elif isinstance(subset_state, OrState):
temp_sub1 = Subset(data=data)
Expand Down
33 changes: 25 additions & 8 deletions glue_astronomy/translators/tests/test_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from packaging.version import Version

from regions import (RectanglePixelRegion, PolygonPixelRegion, CirclePixelRegion,
EllipsePixelRegion, PointPixelRegion, CompoundPixelRegion, PixCoord)
EllipsePixelRegion, PointPixelRegion, CompoundPixelRegion,
CircleAnnulusPixelRegion, PixCoord)

from glue.core import Data, DataCollection
from glue.core.roi import (RectangularROI, PolygonalROI, CircularROI, EllipticalROI,
Expand All @@ -18,11 +19,13 @@
from glue.viewers.image.pixel_selection_subset_state import PixelSubsetState
from glue import __version__ as glue_version

from glue_astronomy.translators.regions import _annulus_to_subset_state


class TestAstropyRegions:

def setup_method(self, method):
self.data = Data(flux=np.ones((128, 256)))
self.data = Data(flux=np.ones((128, 256))) # ny, nx
self.dc = DataCollection([self.data])

def test_rectangular_roi(self):
Expand Down Expand Up @@ -304,6 +307,20 @@ def test_and_region(self):
assert not reg.contains(PixCoord(5.1, 6.1))
assert not reg.contains(PixCoord(11, 12))

def test_circular_annulus(self):
reg_orig = CircleAnnulusPixelRegion(
center=PixCoord(x=50, y=25), inner_radius=7, outer_radius=13)
subset_state = _annulus_to_subset_state(reg_orig, self.data)
self.dc.new_subset_group(subset_state=subset_state, label='annulus_1')
reg = self.data.get_selection_definition(subset_id='annulus_1', format='astropy-regions')

# Would round-trip if translator worked correctly.
assert isinstance(reg, CircleAnnulusPixelRegion)
assert reg.center.x == reg_orig.center.x
assert reg.center.y == reg_orig.center.y
assert reg.outer_radius == reg_orig.outer_radius
assert reg.inner_radius == reg_orig.inner_radius

def test_or_region(self):
subset_state1 = RoiSubsetState(self.data.pixel_component_ids[1],
self.data.pixel_component_ids[0],
Expand Down Expand Up @@ -394,7 +411,7 @@ def test_main_component_combos(self):
multior_region = self.data.get_selection_definition(subset_id='multior',
format='astropy-regions')

for reg in and_region, or_region, xor_region, multior_region:
for reg in (and_region, or_region, xor_region, multior_region):
assert isinstance(reg, CompoundPixelRegion)
assert isinstance(reg.region1, RectanglePixelRegion)
assert isinstance(reg.region2, CirclePixelRegion)
Expand Down Expand Up @@ -436,7 +453,7 @@ def test_subset_id(self):

self.dc.new_subset_group(subset_state=subset_state, label='rectangular')

for subset_id in [None, 0, 'rectangular']:
for subset_id in (None, 0, 'rectangular'):
reg = self.data.get_selection_definition(format='astropy-regions',
subset_id=subset_id)
assert isinstance(reg, RectanglePixelRegion)
Expand All @@ -445,15 +462,15 @@ def test_subset_id(self):
assert_allclose(reg.width, 2.5)
assert_allclose(reg.height, 3.5)

with pytest.raises(ValueError) as exc:
with pytest.raises(ValueError, match="No subset found with the label 'circular'"):
self.data.get_selection_definition(format='astropy-regions',
subset_id='circular')
assert exc.value.args[0] == "No subset found with the label 'circular'"

def test_unsupported(self):
self.dc.new_subset_group(subset_state=self.data.id['flux'] > 0.5,
label='Flux-based selection')
with pytest.raises(NotImplementedError) as exc:
with pytest.raises(
NotImplementedError,
match='Subset states of type InequalitySubsetState are not supported'):
self.data.get_selection_definition(format='astropy-regions',
subset_id='Flux-based selection')
assert exc.value.args[0] == 'Subset states of type InequalitySubsetState are not supported'