From d5bdeaf0f658c95a9dd82bb35d9991b7816d9366 Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Thu, 3 Nov 2022 21:00:59 -0400
Subject: [PATCH 1/8] POC: Programmatically edit subsets
---
.../imviz_edit_subset_programmatic.ipynb | 158 ++++++++++++++++++
1 file changed, 158 insertions(+)
create mode 100644 notebooks/concepts/imviz_edit_subset_programmatic.ipynb
diff --git a/notebooks/concepts/imviz_edit_subset_programmatic.ipynb b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb
new file mode 100644
index 0000000000..ab19dee50c
--- /dev/null
+++ b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb
@@ -0,0 +1,158 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2dd982a0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import time\n",
+ "\n",
+ "import numpy as np\n",
+ "from glue.core.edit_subset_mode import ReplaceMode\n",
+ "from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion\n",
+ "\n",
+ "from jdaviz import Imviz"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c6734f52",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "arr = np.zeros((100, 100))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f0906159",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "rect = RectanglePixelRegion(center=PixCoord(x=40, y=20), width=30, height=5)\n",
+ "circ = CirclePixelRegion(center=PixCoord(x=20, y=80), radius=5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0b54561e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "imviz = Imviz()\n",
+ "imviz.load_data(arr, data_label='Pong')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "635d3952-e5c9-4428-91b0-d86c41d535d3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "imviz.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2030727b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "imviz.load_regions([rect, circ])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "08eab464",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "imviz.default_viewer.zoom_level = 5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bfaa6bb9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "subset_tool = imviz.plugins['Subset Tools']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "594c4fa8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "subset_tool.open_in_tray()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "46b47696",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for i in range(10):\n",
+ " subset_tool._obj.subset_selected = 'Subset 1'\n",
+ " subset_state = subset_tool._obj.subset_select.selected_subset_state\n",
+ " subset_roi = subset_state.roi\n",
+ " cur_x, cur_y = subset_roi.center() # API in glue is inconsistent for this; shape-dependent\n",
+ " subset_roi.move_to(cur_x + 5, cur_y)\n",
+ " subset_tool._obj.session.edit_subset_mode._combine_data(subset_state, override_mode=ReplaceMode)\n",
+ " \n",
+ " time.sleep(0.5)\n",
+ " \n",
+ " subset_tool._obj.subset_selected = 'Subset 2'\n",
+ " subset_state = subset_tool._obj.subset_select.selected_subset_state\n",
+ " subset_roi = subset_state.roi\n",
+ " cur_x, cur_y = subset_roi.get_center() # Circle API is different from rectangle.\n",
+ " subset_roi.move_to(2, -2) # For circle, this is delta, not absolute.\n",
+ " subset_tool._obj.session.edit_subset_mode._combine_data(subset_state, override_mode=ReplaceMode)\n",
+ " \n",
+ " time.sleep(0.5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "fe2c1fd3",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
From 4ea27bcb9a076ad76233949c9c714da6f6dd801e Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:19:19 -0500
Subject: [PATCH 2/8] Nicer API for centering
---
CHANGES.rst | 2 +
.../plugins/subset_plugin/subset_plugin.py | 41 +++++++++++++++++++
.../imviz_edit_subset_programmatic.ipynb | 25 +++++------
3 files changed, 53 insertions(+), 15 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index b7b79c30d0..0ef865de0b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,8 @@ New Features
- Spinner in plot options while processing changes to contour settings. [#1794]
+- Subset Tools plugin now allows recentering of spatial subset. [#1823]
+
Cubeviz
^^^^^^^
diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
index faf3fbaffe..64328742ef 100644
--- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
+++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
@@ -226,6 +226,47 @@ def vue_update_subset(self, *args):
self.hub.broadcast(SnackbarMessage(
f"Failed to update Subset: {repr(err)}", color='error', sender=self))
+ def get_center(self):
+ # Composite region cannot be edited, so just grab first element.
+ if not self.is_editable or self.subset_types[0] == "Range": # no-op
+ return
+
+ subset_state = self.subset_select.selected_subset_state
+ sbst_obj = subset_state.roi
+
+ if isinstance(sbst_obj, (CircularROI, EllipticalROI)):
+ cx, cy = sbst_obj.get_center()
+ elif isinstance(sbst_obj, RectangularROI):
+ cx, cy = sbst_obj.center()
+ else: # pragma: no cover
+ raise NotImplementedError(f'Getting center of {sbst_obj.__class__} is not supported')
+
+ return cx, cy
+
+ def set_center(self, x, y):
+ # Composite region cannot be edited, so just grab first element.
+ if not self.is_editable or self.subset_types[0] == "Range": # no-op
+ return
+
+ subset_state = self.subset_select.selected_subset_state
+ sbst_obj = subset_state.roi
+
+ if isinstance(sbst_obj, (CircularROI, 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):
+ cx, cy = sbst_obj.center()
+ dx = x - cx
+ dy = y - cy
+ self._set_value_in_subset_definition(0, "Xmin", "value", sbst_obj.xmin + dx)
+ self._set_value_in_subset_definition(0, "Xmax", "value", sbst_obj.xmax + dx)
+ self._set_value_in_subset_definition(0, "Ymin", "value", sbst_obj.ymin + dy)
+ self._set_value_in_subset_definition(0, "Ymax", "value", sbst_obj.ymax + dy)
+ else: # pragma: no cover
+ raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported')
+
+ self.vue_update_subset()
+
# List of JSON-like dict is nice for front-end but a pain to look up,
# so we use these helper functions.
diff --git a/notebooks/concepts/imviz_edit_subset_programmatic.ipynb b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb
index ab19dee50c..db4eae4ca6 100644
--- a/notebooks/concepts/imviz_edit_subset_programmatic.ipynb
+++ b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb
@@ -10,7 +10,6 @@
"import time\n",
"\n",
"import numpy as np\n",
- "from glue.core.edit_subset_mode import ReplaceMode\n",
"from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion\n",
"\n",
"from jdaviz import Imviz"
@@ -105,23 +104,19 @@
"metadata": {},
"outputs": [],
"source": [
+ "plg = subset_tool._obj\n",
+ "\n",
"for i in range(10):\n",
- " subset_tool._obj.subset_selected = 'Subset 1'\n",
- " subset_state = subset_tool._obj.subset_select.selected_subset_state\n",
- " subset_roi = subset_state.roi\n",
- " cur_x, cur_y = subset_roi.center() # API in glue is inconsistent for this; shape-dependent\n",
- " subset_roi.move_to(cur_x + 5, cur_y)\n",
- " subset_tool._obj.session.edit_subset_mode._combine_data(subset_state, override_mode=ReplaceMode)\n",
- " \n",
+ " plg.subset_selected = 'Subset 1'\n",
+ " cur_x, cur_y = plg.get_center()\n",
+ " plg.set_center(cur_x + 5, cur_y)\n",
+ "\n",
" time.sleep(0.5)\n",
" \n",
- " subset_tool._obj.subset_selected = 'Subset 2'\n",
- " subset_state = subset_tool._obj.subset_select.selected_subset_state\n",
- " subset_roi = subset_state.roi\n",
- " cur_x, cur_y = subset_roi.get_center() # Circle API is different from rectangle.\n",
- " subset_roi.move_to(2, -2) # For circle, this is delta, not absolute.\n",
- " subset_tool._obj.session.edit_subset_mode._combine_data(subset_state, override_mode=ReplaceMode)\n",
- " \n",
+ " plg.subset_selected = 'Subset 2'\n",
+ " cur_x, cur_y = plg.get_center()\n",
+ " plg.set_center(cur_x + 2, cur_y - 2)\n",
+ "\n",
" time.sleep(0.5)"
]
},
From ac4e8282ea5116c99c48afb18f8ff608eeffae0f Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Tue, 8 Nov 2022 18:49:39 -0500
Subject: [PATCH 3/8] WIP: Sub-plugin to recenter subset for Imviz only.
Fix GUI update and dithered use case.
---
CHANGES.rst | 4 +-
docs/imviz/plugins.rst | 11 +-
.../plugins/subset_plugin/subset_plugin.py | 105 +++++++++++++-----
.../plugins/subset_plugin/subset_plugin.vue | 23 ++++
.../aper_phot_simple/aper_phot_simple.py | 28 +----
jdaviz/configs/imviz/plugins/viewers.py | 13 ++-
jdaviz/core/region_translators.py | 55 +++++++++
.../imviz_edit_subset_programmatic.ipynb | 13 ++-
8 files changed, 191 insertions(+), 61 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 0ef865de0b..e8798fc96d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,8 +8,6 @@ New Features
- Spinner in plot options while processing changes to contour settings. [#1794]
-- Subset Tools plugin now allows recentering of spatial subset. [#1823]
-
Cubeviz
^^^^^^^
@@ -20,6 +18,8 @@ Imviz
- Warnings in aperture photometry plugin when using raw profile with large subsets. [#1801]
+- Subset Tools plugin now allows recentering of editable spatial subset. [#1823]
+
Mosviz
^^^^^^
diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst
index eca87e3c0b..339ecaef7a 100644
--- a/docs/imviz/plugins.rst
+++ b/docs/imviz/plugins.rst
@@ -44,7 +44,7 @@ This plugin allows you to select an existing subset to modify, or to select
:guilabel:`Create new` to create a new subset by selecting and using the region selector
in the spectrum viewer toolbar. You can also choose the operation that will be
applied by the selector tool. Note that these are synched with the subset tools
-in the app-level toolbar. It does not show static regions loaded
+in the app-level toolbar. It might not show some static regions loaded
via the API unless an interactive region is drawn after.
If an existing subset is selected, the parameters of the subset will also be
@@ -52,6 +52,15 @@ shown. Note that while parameters for compound regions (e.g., a subset with
multiple disjoint regions) are displayed, the logical operations joining them
(``OR``, ``AND``, etc.) are not shown.
+For a simple subset in Imviz only, you can choose to recenter it based
+on the selected Data. The centroid is calculated by
+:attr:`photutils.aperture.ApertureStats.centroid`, which is the
+center-of-mass of the data within the aperture.
+No background subtraction is performed. Click :guilabel:`Recenter`
+to change its parameters to the calculated centroid. However, the subset
+will not be moved to the new center until :guilabel:`Update` is also clicked
+(see below).
+
For a simple subset, you can edit its parameters by changing the values
in the corresponding editable text fields. Once you have entered the new
value(s), click :guilabel:`Update` to apply. You should see the subset
diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
index 64328742ef..95a3784597 100644
--- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
+++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
@@ -9,7 +9,7 @@
from jdaviz.core.events import SnackbarMessage
from jdaviz.core.registries import tray_registry
-from jdaviz.core.template_mixin import PluginTemplateMixin, SubsetSelect
+from jdaviz.core.template_mixin import PluginTemplateMixin, DatasetSelectMixin, SubsetSelect
__all__ = ['SubsetPlugin']
@@ -23,7 +23,7 @@
@tray_registry('g-subset-plugin', label="Subset Tools")
-class SubsetPlugin(PluginTemplateMixin):
+class SubsetPlugin(PluginTemplateMixin, DatasetSelectMixin):
template_file = __file__, "subset_plugin.vue"
select = List([]).tag(sync=True)
subset_items = List([]).tag(sync=True)
@@ -226,46 +226,95 @@ def vue_update_subset(self, *args):
self.hub.broadcast(SnackbarMessage(
f"Failed to update Subset: {repr(err)}", color='error', sender=self))
+ def vue_recenter_subset(self, *args):
+ # Composite region cannot be edited. This only works for Imviz.
+ if not self.is_editable or self.config != 'imviz': # no-op
+ return
+
+ from photutils.aperture import ApertureStats
+ from jdaviz.core.region_translators import regions2aperture, _get_region_from_spatial_subset
+
+ reg = _get_region_from_spatial_subset(self, self.subset_selected)
+ aperture = regions2aperture(reg)
+ data = self.dataset.selected_dc_item
+ comp = data.get_component(data.main_components[0])
+ comp_data = comp.data
+ phot_aperstats = ApertureStats(comp_data, aperture)
+
+ # Centroid was calculated in selected data, which might or might not be
+ # the reference data. However, Subset is always defined w.r.t.
+ # the reference data, so we need to convert back.
+ viewer = self.app._jdaviz_helper.default_viewer
+ x, y, _ = viewer._get_real_xy(
+ data, phot_aperstats.xcentroid, phot_aperstats.ycentroid, reverse=True)
+ self.set_center((x, y), update=False)
+
def get_center(self):
- # Composite region cannot be edited, so just grab first element.
- if not self.is_editable or self.subset_types[0] == "Range": # no-op
+ # Composite region cannot be edited.
+ if not self.is_editable: # no-op
return
subset_state = self.subset_select.selected_subset_state
- sbst_obj = subset_state.roi
- if isinstance(sbst_obj, (CircularROI, EllipticalROI)):
- cx, cy = sbst_obj.get_center()
- elif isinstance(sbst_obj, RectangularROI):
- cx, cy = sbst_obj.center()
+ 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
+
else: # pragma: no cover
- raise NotImplementedError(f'Getting center of {sbst_obj.__class__} is not supported')
+ raise NotImplementedError(
+ f'Getting center of {subset_state.__class__} is not supported')
- return cx, cy
+ return cen
- def set_center(self, x, y):
+ def set_center(self, new_cen, update=False):
# Composite region cannot be edited, so just grab first element.
- if not self.is_editable or self.subset_types[0] == "Range": # no-op
+ if not self.is_editable: # no-op
return
subset_state = self.subset_select.selected_subset_state
- sbst_obj = subset_state.roi
-
- if isinstance(sbst_obj, (CircularROI, 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):
- cx, cy = sbst_obj.center()
- dx = x - cx
- dy = y - cy
- self._set_value_in_subset_definition(0, "Xmin", "value", sbst_obj.xmin + dx)
- self._set_value_in_subset_definition(0, "Xmax", "value", sbst_obj.xmax + dx)
- self._set_value_in_subset_definition(0, "Ymin", "value", sbst_obj.ymin + dy)
- self._set_value_in_subset_definition(0, "Ymax", "value", sbst_obj.ymax + dy)
+
+ if isinstance(subset_state, RoiSubsetState):
+ x, y = new_cen
+ sbst_obj = subset_state.roi
+ if isinstance(sbst_obj, (CircularROI, 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):
+ cx, cy = sbst_obj.center()
+ dx = x - cx
+ dy = y - cy
+ self._set_value_in_subset_definition(0, "Xmin", "value", sbst_obj.xmin + dx)
+ self._set_value_in_subset_definition(0, "Xmax", "value", sbst_obj.xmax + dx)
+ self._set_value_in_subset_definition(0, "Ymin", "value", sbst_obj.ymin + dy)
+ self._set_value_in_subset_definition(0, "Ymax", "value", sbst_obj.ymax + dy)
+ else: # pragma: no cover
+ 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)
+ 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)
+
else: # pragma: no cover
- raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported')
+ raise NotImplementedError(
+ f'Getting center of {subset_state.__class__} is not supported')
- self.vue_update_subset()
+ if update:
+ self.vue_update_subset()
+ else:
+ # Force UI to update on browser without changing the subset.
+ tmp = self.subset_definitions
+ self.subset_definitions = []
+ self.subset_definitions = tmp
# List of JSON-like dict is nice for front-end but a pain to look up,
# so we use these helper functions.
diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue
index de2a1b7725..31faf9dd3e 100644
--- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue
+++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue
@@ -22,6 +22,29 @@
+
+