Repository: spacetelescope/jdaviz

Issue:
Drag and draw annulus with hardcoded width
**Reporter:** pllim

As proposed by @kecnry , here is the desired workflow:

1. User select new shape tool from the existing shapes drop-down.
2. User click and drag to draw. At this point, it is basically the circle draw tool.
3. When finalized, this tool magically creates an annulus shape with hardcoded width (proposed: `inner_radius = outer_radius * 0.5`).
4. To modify the annulus shape, user has to edit it manually in the Subset Tools plugin.

New features to be implemented:

a. A new shape tool that subclass the circular shape but then turns it into annulus when finalized.
b. Subset Tools plugin needs special logic so that user can edit only the following for annulus (which is a very special case of compound subset): One center, inner radius, outer radius. @javerbukh proposed maybe we can subclass subset state or attach metadata to the state.

Under the hood:

```
Annulus subset state
|___ ROI state --> CircularROI
|___ Invert state --> ROI state --> Circular ROI
```
where
```pyhon
subset_state.state1.roi.xc == subset_state.state2.state1.roi.xc
```
and
```python
subset_state.state1.roi.yc == subset_state.state2.state1.roi.yc
```
and
```python
subset_state.state1.roi.radius > subset_state.state2.state1.roi.radius
```

Also see https://github.com/glue-viz/glue-astronomy/blob/336e2cca4a93025e34e4a0d4313b4031f2f794dc/glue_astronomy/translators/regions.py#L52

This needs new glue-astronomy release upstream (>0.7).

[🐱](https://jira.stsci.edu/browse/JDAT-3379)

 --- 

 DISCLAIMER: This issue was autocreated by the Jdaviz Issue Creation Bot on behalf of the reporter. If any information is incorrect, please contact [Duy Nguyen](https://github.com/duytnguyendtn)

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) Report
Patch coverage: **`100.00`**% and project coverage change: **`+0.01`** :tada:
> Comparison is base [(`1174347`)](https://app.codecov.io/gh/spacetelescope/jdaviz/commit/1174347d10ba17298ccde00d8385b8e95b1df241?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) 91.77% compared to head [(`5b1cdc4`)](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) 91.78%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2240      +/-   ##
==========================================
+ Coverage   91.77%   91.78%   +0.01%     
==========================================
  Files         150      150              
  Lines       16847    16824      -23     
==========================================
- Hits        15461    15442      -19     
+ Misses       1386     1382       -4     
```


| [Impacted Files](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) | Coverage Δ | |
|---|---|---|
| [jdaviz/configs/imviz/plugins/viewers.py](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-amRhdml6L2NvbmZpZ3MvaW12aXovcGx1Z2lucy92aWV3ZXJzLnB5) | `94.30% <ø> (ø)` | |
| [jdaviz/app.py](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-amRhdml6L2FwcC5weQ==) | `93.33% <100.00%> (-0.13%)` | :arrow_down: |
| [...igs/default/plugins/subset\_plugin/subset\_plugin.py](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-amRhdml6L2NvbmZpZ3MvZGVmYXVsdC9wbHVnaW5zL3N1YnNldF9wbHVnaW4vc3Vic2V0X3BsdWdpbi5weQ==) | `94.38% <100.00%> (+0.57%)` | :arrow_up: |
| [jdaviz/core/tools.py](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-amRhdml6L2NvcmUvdG9vbHMucHk=) | `86.19% <100.00%> (+0.05%)` | :arrow_up: |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/spacetelescope/jdaviz/pull/2240?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope).


Patch:
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..3e8f9e57
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,25 @@
+Brett Graham <brettgraham@gmail.com>
+Brett M. Morris <bmmorris@stsci.edu> <morrisbrettm@gmail.com>
+Camilla Pacifici <camilla.pacifici@gmail.com> <cpacifici@stsci.edu>
+Craig Jones <craig@brechmos.org> <crjones@stsci.edu>
+Duy Nguyen <duytnguyendtn.open@gmail.com> <dtn5ah@virginia.edu>
+Duy Nguyen <duytnguyendtn.open@gmail.com> <dnguyen@stsci.edu>
+Duy Nguyen <duytnguyendtn.open@gmail.com> Duy Tuong Nguyen <dtn5ah@virginia.edu>
+Duy Nguyen <duytnguyendtn.open@gmail.com> Duy Tuong Nguyen <duytnguyendtn.open@gmail.com>
+Duy Nguyen <duytnguyendtn.open@gmail.com> duytnguyendtn <duytnguyendtn.open@gmail.com>
+Ivo Busko <busko@stsci.edu> Ivo <busko@stsci.edu>
+Ivo Busko <busko@stsci.edu> busko <busko@stsci.edu>
+Ivo Busko <busko@stsci.edu> busko@stsci.edu <New1trilha>
+Jennifer Kotler <jkotler@stsci.edu> <jkotler@esme.stsci.edu>
+Jennifer Kotler <jkotler@stsci.edu> <jennlkotler@gmail.com>
+Jesse Averbukh <javerbukh@stsci.edu> <javerbukh@gmail.com>
+Josh Soref <2119212+jsoref@users.noreply.github.com> <jsoref@users.noreply.github.com>
+Kyle Conroy <kyleconroy@gmail.com> Kyle <kyleconroy@gmail.com>
+Mario Buikhuizen <mariobuikhuizen@gmail.com> <mbuikhuizen@gmail.com>
+Nicholas Earl <contact@nicholasearl.me> <nearl@stsci.edu>
+Nicholas Earl <contact@nicholasearl.me> nmearl <contact@nicholasearl.me>
+Ori Fox <ofox@stsci.edu> Ori <ofox@stsci.edu>
+P. L. Lim <2090236+pllim@users.noreply.github.com>
+Patrick Ogle <pogle@stsci.edu> PatrickOgle <pogle@stsci.edu>
+Ricky O'Steen <39831871+rosteen@users.noreply.github.com> <rosteen@stsci.edu>
+Ricky O'Steen <39831871+rosteen@users.noreply.github.com> rosteen <39831871+rosteen@users.noreply.github.com>
diff --git a/CHANGES.rst b/CHANGES.rst
index a8b8611b..3b6639f1 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,7 +20,8 @@ Imviz
 - Added the ability to load DS9 region files (``.reg``) using the ``IMPORT DATA``
   button. However, this only works after loading at least one image into Imviz. [#2201]
 
-- Added support for new ``CircularAnnulusROI`` subset from glue. [#2201]
+- Added support for new ``CircularAnnulusROI`` subset from glue, including
+  a new draw tool. [#2201, #2240]
 
 Mosviz
 ^^^^^^
diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst
index 4ae367b3..a69819d6 100644
--- a/docs/imviz/plugins.rst
+++ b/docs/imviz/plugins.rst
@@ -202,6 +202,15 @@ an interactively selected region. A typical workflow is as follows:
    in display data unit. Otherwise, it is only informational.
    If this field is not applicable for you, leave it at 0.
    **This field resets every time Data selection changes if auto-population not possible.**
+
+   .. warning::
+
+       If your data is in surface brightness units and pixels on the image
+       have varying sky area, you should first convert your data from
+       surface brightness to flux units before using this plugin.
+       This is because, for performance reasons, the plugin multiplies
+       by the area after the aperture sum is calculated.
+
 7. If you also want photometry result in the unit of counts, you can enter a
    conversion factor in the :guilabel:`Counts conversion factor` field. The value
    must be in the unit of display data unit per counts. This is used to convert linear
diff --git a/jdaviz/app.py b/jdaviz/app.py
index f8d16356..1bdd8df3 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -12,11 +12,8 @@ import ipyvue
 from astropy import units as u
 from astropy.nddata import CCDData, NDData
 from astropy.io import fits
-from astropy.coordinates import Angle
 from astropy.time import Time
 from astropy.utils.decorators import deprecated
-from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion
-
 from echo import CallbackProperty, DictCallbackProperty, ListCallbackProperty
 from ipygoldenlayout import GoldenLayout
 from ipysplitpanes import SplitPanes
@@ -39,9 +36,9 @@ from glue.core.message import (DataCollectionAddMessage,
 from glue.core.state_objects import State
 from glue.core.subset import (Subset, RangeSubsetState, RoiSubsetState,
                               CompositeSubsetState, InvertState)
-from glue.core.roi import CircularROI, EllipticalROI, RectangularROI
 from glue.core.units import unit_converter
 from glue_astronomy.spectral_coordinates import SpectralCoordinates
+from glue_astronomy.translators.regions import roi_subset_state_to_region
 from glue_jupyter.app import JupyterApplication
 from glue_jupyter.common.toolbar_vuetify import read_icon
 from glue_jupyter.state_traitlets_helpers import GlueState
@@ -1055,27 +1052,8 @@ class Application(VuetifyTemplate, HubListener):
         return spec_region
 
     def _get_roi_subset_definition(self, subset_state):
-        _around_decimals = 6
-        roi = subset_state.roi
-        roi_as_region = None
-        if isinstance(roi, CircularROI):
-            x, y = roi.get_center()
-            r = roi.radius
-            roi_as_region = CirclePixelRegion(PixCoord(x, y), r)
-
-        elif isinstance(roi, RectangularROI):
-            theta = np.around(np.degrees(roi.theta), decimals=_around_decimals)
-            roi_as_region = RectanglePixelRegion(PixCoord(roi.center()[0], roi.center()[1]),
-                                                 roi.width(), roi.height(), Angle(theta, "deg"))
-
-        elif isinstance(roi, EllipticalROI):
-            xc = roi.xc
-            yc = roi.yc
-            rx = roi.radius_x
-            ry = roi.radius_y
-            theta = np.around(np.degrees(roi.theta), decimals=_around_decimals)
-            roi_as_region = EllipsePixelRegion(PixCoord(xc, yc), rx * 2, ry * 2, Angle(theta, "deg"))  # noqa: E501
-
+        # TODO: Imviz: Return sky region if link type is WCS.
+        roi_as_region = roi_subset_state_to_region(subset_state)
         return [{"name": subset_state.roi.__class__.__name__,
                  "glue_state": subset_state.__class__.__name__,
                  "region": roi_as_region,
diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
index 5bd4fb22..12d881ee 100644
--- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
+++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
@@ -7,8 +7,8 @@ import astropy.units as u
 from glue.core.message import EditSubsetMessage, SubsetUpdateMessage
 from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode,
                                         ReplaceMode, XorMode)
-from glue.core.roi import CircularROI, EllipticalROI, RectangularROI
-from glue.core.subset import RoiSubsetState, RangeSubsetState, CompositeSubsetState
+from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI
+from glue.core.subset import RoiSubsetState, RangeSubsetState
 from glue.icons import icon_path
 from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu
 from glue_jupyter.common.toolbar_vuetify import read_icon
@@ -149,7 +149,11 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
         _around_decimals = 6  # Avoid 30 degrees from coming back as 29.999999999999996
         if not subset_information:
             return
-        if len(subset_information) == 1:
+        if ((len(subset_information) == 1) and
+                (isinstance(subset_information[0]["subset_state"], RangeSubsetState) or
+                 (isinstance(subset_information[0]["subset_state"], RoiSubsetState) and
+                  isinstance(subset_information[0]["subset_state"].roi,
+                             (CircularROI, RectangularROI, EllipticalROI))))):
             self.is_centerable = True
         else:
             self.is_centerable = False
@@ -161,7 +165,7 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
             glue_state = spec["glue_state"]
             if isinstance(subset_state, RoiSubsetState):
                 if isinstance(subset_state.roi, CircularROI):
-                    x, y = subset_state.roi.get_center()
+                    x, y = subset_state.roi.center()
                     r = subset_state.roi.radius
                     subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x},
                                          {"name": "Y Center", "att": "yc", "value": y, "orig": y},
@@ -178,8 +182,7 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
                         {"name": "Angle", "att": "theta", "value": theta, "orig": theta})
 
                 elif isinstance(subset_state.roi, EllipticalROI):
-                    xc = subset_state.roi.xc
-                    yc = subset_state.roi.yc
+                    xc, yc = subset_state.roi.center()
                     rx = subset_state.roi.radius_x
                     ry = subset_state.roi.radius_y
                     theta = np.around(np.degrees(subset_state.roi.theta), decimals=_around_decimals)
@@ -190,6 +193,17 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
                         {"name": "Y Radius", "att": "radius_y", "value": ry, "orig": ry},
                         {"name": "Angle", "att": "theta", "value": theta, "orig": theta}]
 
+                elif isinstance(subset_state.roi, CircularAnnulusROI):
+                    x, y = subset_state.roi.center()
+                    inner_r = subset_state.roi.inner_radius
+                    outer_r = subset_state.roi.outer_radius
+                    subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x},
+                                         {"name": "Y Center", "att": "yc", "value": y, "orig": y},
+                                         {"name": "Inner radius", "att": "inner_radius",
+                                          "value": inner_r, "orig": inner_r},
+                                         {"name": "Outer radius", "att": "outer_radius",
+                                          "value": outer_r, "orig": outer_r}]
+
                 subset_type = subset_state.roi.__class__.__name__
 
             elif isinstance(subset_state, RangeSubsetState):
@@ -303,6 +317,7 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
         reason = ""
         for index, sub in enumerate(self.subset_definitions):
             lo = hi = xmin = xmax = ymin = ymax = None
+            inner_radius = outer_radius = None
             for d_att in sub:
                 if d_att["att"] == "lo":
                     lo = d_att["value"]
@@ -320,6 +335,10 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
                     ymin = d_att["value"]
                 elif d_att["att"] == "ymax":
                     ymax = d_att["value"]
+                elif d_att["att"] == "outer_radius":
+                    outer_radius = d_att["value"]
+                elif d_att["att"] == "inner_radius":
+                    inner_radius = d_att["value"]
 
                 if lo and hi and hi <= lo:
                     status = False
@@ -329,6 +348,10 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
                     status = False
                     reason = "Failed to update Subset: width and length must be positive scalars"
                     break
+                elif inner_radius and outer_radius and inner_radius >= outer_radius:
+                    status = False
+                    reason = "Failed to update Subset: inner radius must be less than outer radius"
+                    break
 
         return status, reason
 
@@ -375,39 +398,13 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
             depending on the Subset type, if applicable.
             If Subset is not centerable, this returns `None`.
 
-        Raises
-        ------
-        NotImplementedError
-            Subset type is not supported.
-
         """
         # Composite region cannot be centered.
         if not self.is_centerable:  # no-op
             return
 
         subset_state = self.subset_select.selected_subset_state
-
-        if isinstance(subset_state, RoiSubsetState):
-            sbst_obj = subset_state.roi
-            if isinstance(sbst_obj, (CircularROI, EllipticalROI)):
-                cen = sbst_obj.get_center()
-            elif isinstance(sbst_obj, RectangularROI):
-                cen = sbst_obj.center()
-            else:  # pragma: no cover
-                raise NotImplementedError(
-                    f'Getting center of {sbst_obj.__class__} is not supported')
-
-        elif isinstance(subset_state, RangeSubsetState):
-            cen = (subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo
-
-        elif isinstance(subset_state, CompositeSubsetState):
-            cen = None
-
-        else:  # pragma: no cover
-            raise NotImplementedError(
-                f'Getting center of {subset_state.__class__} is not supported')
-
-        return cen
+        return subset_state.center()
 
     def set_center(self, new_cen, update=False):
         """Set the desired center for the selected Subset, if applicable.
@@ -439,7 +436,7 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
         if isinstance(subset_state, RoiSubsetState):
             x, y = new_cen
             sbst_obj = subset_state.roi
-            if isinstance(sbst_obj, (CircularROI, EllipticalROI)):
+            if isinstance(sbst_obj, (CircularROI, CircularAnnulusROI, EllipticalROI)):
                 self._set_value_in_subset_definition(0, "X Center", "value", x)
                 self._set_value_in_subset_definition(0, "Y Center", "value", y)
             elif isinstance(sbst_obj, RectangularROI):
@@ -454,7 +451,7 @@ class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
                 raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported')
 
         elif isinstance(subset_state, RangeSubsetState):
-            dx = new_cen - ((subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo)
+            dx = new_cen - subset_state.center()
             self._set_value_in_subset_definition(0, "Lower bound", "value", subset_state.lo + dx)
             self._set_value_in_subset_definition(0, "Upper bound", "value", subset_state.hi + dx)
 
diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py
index 46467b92..958d076f 100644
--- a/jdaviz/configs/imviz/plugins/viewers.py
+++ b/jdaviz/configs/imviz/plugins/viewers.py
@@ -24,7 +24,7 @@ class ImvizImageView(JdavizViewerMixin, BqplotImageView, AstrowidgetsImageViewer
                     ['jdaviz:homezoom', 'jdaviz:prevzoom'],
                     ['jdaviz:boxzoommatch', 'jdaviz:boxzoom'],
                     ['jdaviz:panzoommatch', 'jdaviz:imagepanzoom'],
-                    ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse',
+                    ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse', 'bqplot:circannulus',
                      'jdaviz:singlepixelregion'],
                     ['jdaviz:blinkonce', 'jdaviz:contrastbias'],
                     ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export', 'jdaviz:sidebar_compass']
diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py
index f86618d7..f6dea95d 100644
--- a/jdaviz/core/tools.py
+++ b/jdaviz/core/tools.py
@@ -9,8 +9,9 @@ from glue_jupyter.bqplot.common.tools import (CheckableTool,
                                               HomeTool, BqplotPanZoomMode,
                                               BqplotPanZoomXMode, BqplotPanZoomYMode,
                                               BqplotRectangleMode, BqplotCircleMode,
-                                              BqplotEllipseMode, BqplotXRangeMode,
-                                              BqplotYRangeMode, BqplotSelectionTool,
+                                              BqplotEllipseMode, BqplotCircularAnnulusMode,
+                                              BqplotXRangeMode, BqplotYRangeMode,
+                                              BqplotSelectionTool,
                                               INTERACT_COLOR)
 from bqplot.interacts import BrushSelector, BrushIntervalSelector
 
@@ -25,6 +26,7 @@ ICON_DIR = os.path.join(os.path.dirname(__file__), '..', 'data', 'icons')
 BqplotRectangleMode.icon = os.path.join(ICON_DIR, 'select_xy.svg')
 BqplotCircleMode.icon = os.path.join(ICON_DIR, 'select_circle.svg')
 BqplotEllipseMode.icon = os.path.join(ICON_DIR, 'select_ellipse.svg')
+BqplotCircularAnnulusMode.icon = os.path.join(ICON_DIR, 'select_annulus.svg')
 BqplotXRangeMode.icon = os.path.join(ICON_DIR, 'select_x.svg')
 BqplotYRangeMode.icon = os.path.join(ICON_DIR, 'select_y.svg')
 
diff --git a/jdaviz/data/icons/select_annulus.svg b/jdaviz/data/icons/select_annulus.svg
new file mode 100644
index 00000000..86863e14
--- /dev/null
+++ b/jdaviz/data/icons/select_annulus.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 664 700" style="enable-background:new 0 0 664 700;" xml:space="preserve">
+<circle cx="55.5" cy="102.6" r="41.8"/>
+<circle cx="197.6" cy="58.4" r="41.8"/>
+<circle cx="389.4" cy="321.3" r="41.8"/>
+<circle cx="330.5" cy="448.6" r="41.8"/>
+<path d="M354.2,74.7c-169,0-306,137-306,306s137,306,306,306s306-137,306-306S523.2,74.7,354.2,74.7z M131.6,453.4
+	c-23.1,0-41.8-18.7-41.8-41.8s18.7-41.8,41.8-41.8c23.1,0,41.8,18.7,41.8,41.8S154.7,453.4,131.6,453.4z M354.2,546.1
+	c-82.5,0-149.3-74-149.3-165.4s66.9-165.4,149.3-165.4s149.3,74,149.3,165.4S436.6,546.1,354.2,546.1z"/>
+</svg>
diff --git a/pyproject.toml b/pyproject.toml
index 44e46a75..e06a9f79 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,7 @@ dependencies = [
     "bqplot>=0.12.37",
     "bqplot-image-gl>=1.4.11",
     "glue-core>=1.11",
-    "glue-jupyter>=0.16.3",
+    "glue-jupyter>=0.17",
     "echo>=0.5.0",
     "ipykernel>=6.19.4",
     "ipyvue>=1.6",
@@ -26,7 +26,7 @@ dependencies = [
     "specutils>=1.9",
     "specreduce>=1.3.0,<1.4.0",
     "photutils>=1.4",
-    "glue-astronomy>=0.9",
+    "glue-astronomy>=0.10",
     "asteval>=0.9.23",
     "idna",
     "vispy>=0.6.5",


Test patch:
diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py
index 8d4066bd..3d06573f 100644
--- a/jdaviz/tests/test_subsets.py
+++ b/jdaviz/tests/test_subsets.py
@@ -3,9 +3,10 @@ import pytest
 from astropy import units as u
 from astropy.tests.helper import assert_quantity_allclose
 from glue.core import Data
-from glue.core.roi import CircularROI, EllipticalROI, RectangularROI, XRangeROI
+from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI, XRangeROI
 from glue.core.edit_subset_mode import AndMode, AndNotMode, OrMode, XorMode
-from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion
+from regions import (PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion,
+                     CircleAnnulusPixelRegion)
 from numpy.testing import assert_allclose
 from specutils import SpectralRegion, Spectrum1D
 
@@ -316,7 +317,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper):
                        'subset_state': reg[-1]['subset_state']}
 
     cubeviz_helper.app.session.edit_subset_mode.mode = OrMode
-    viewer.apply_roi(EllipticalROI(30, 30, 3, 6))
+    viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6))
     reg = cubeviz_helper.app.get_subsets("Subset 1")
     ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30),
                                   width=6, height=12, angle=0.0 * u.deg)
@@ -368,7 +369,7 @@ def test_composite_region_with_consecutive_and_not_states(cubeviz_helper):
                        'subset_state': reg[-1]['subset_state']}
 
     cubeviz_helper.app.session.edit_subset_mode.mode = AndNotMode
-    viewer.apply_roi(EllipticalROI(30, 30, 3, 6))
+    viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6))
     reg = cubeviz_helper.app.get_subsets("Subset 1")
     ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30),
                                   width=6, height=12, angle=0.0 * u.deg)
@@ -413,7 +414,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs):
     arr = np.ones((10, 10))
 
     data_label = 'image-data'
-    viewer = imviz_helper.app.get_viewer('imviz-0')
+    viewer = imviz_helper.default_viewer
     imviz_helper.load_data(arr, data_label=data_label, show_in_viewer=True)
     viewer.apply_roi(CircularROI(xc=5, yc=5, radius=2))
     reg = imviz_helper.app.get_subsets("Subset 1")
@@ -422,7 +423,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs):
                        'subset_state': reg[-1]['subset_state']}
 
     imviz_helper.app.session.edit_subset_mode.mode = AndNotMode
-    viewer.apply_roi(RectangularROI(2, 4, 2, 4))
+    viewer.apply_roi(RectangularROI(xmin=2, xmax=4, ymin=2, ymax=4))
     reg = imviz_helper.app.get_subsets("Subset 1")
     rectangle1 = RectanglePixelRegion(center=PixCoord(x=3, y=3),
                                       width=2, height=2, angle=0.0 * u.deg)
@@ -430,17 +431,25 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs):
                        'subset_state': reg[-1]['subset_state']}
 
     imviz_helper.app.session.edit_subset_mode.mode = AndNotMode
-    viewer.apply_roi(EllipticalROI(3, 3, 3, 6))
+    viewer.apply_roi(EllipticalROI(xc=3, yc=3, radius_x=3, radius_y=6))
     reg = imviz_helper.app.get_subsets("Subset 1")
     ellipse1 = EllipsePixelRegion(center=PixCoord(x=3, y=3),
                                   width=6, height=12, angle=0.0 * u.deg)
     assert reg[-1] == {'name': 'EllipticalROI', 'glue_state': 'AndNotState', 'region': ellipse1,
                        'subset_state': reg[-1]['subset_state']}
 
+    imviz_helper.app.session.edit_subset_mode.mode = OrMode
+    viewer.apply_roi(CircularAnnulusROI(xc=5, yc=5, inner_radius=2.5, outer_radius=5))
+    reg = imviz_helper.app.get_subsets("Subset 1")
+    ann1 = CircleAnnulusPixelRegion(center=PixCoord(x=5, y=5), inner_radius=2.5, outer_radius=5)
+    assert reg[-1] == {'name': 'CircularAnnulusROI', 'glue_state': 'OrState', 'region': ann1,
+                       'subset_state': reg[-1]['subset_state']}
+
     subset_plugin = imviz_helper.app.get_tray_item_from_name('g-subset-plugin')
     assert subset_plugin.subset_selected == "Subset 1"
-    assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI']
-    assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState']
+    assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI',
+                                          'CircularAnnulusROI']
+    assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState', 'OrState']
 
 
 def test_with_invalid_subset_name(cubeviz_helper):