From 1f8ba78f5373d124b2e49c397bcd621161a79cb8 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Wed, 18 Oct 2023 20:00:56 -0400 Subject: [PATCH] Move POC code to app --- .../plugins/plot_options/plot_options.py | 76 +++++- notebooks/concepts/imviz_colorbar_mpl.ipynb | 254 +++++++++++++----- 2 files changed, 263 insertions(+), 67 deletions(-) diff --git a/jdaviz/configs/default/plugins/plot_options/plot_options.py b/jdaviz/configs/default/plugins/plot_options/plot_options.py index b1ab0e6722..c74ae26f10 100644 --- a/jdaviz/configs/default/plugins/plot_options/plot_options.py +++ b/jdaviz/configs/default/plugins/plot_options/plot_options.py @@ -1,4 +1,5 @@ import os +import matplotlib import numpy as np from astropy.visualization import ( @@ -8,10 +9,11 @@ from traitlets import Any, Dict, Float, Bool, Int, List, Unicode, observe from glue.core.subset_group import GroupedSubset -from glue.config import stretches +from glue.config import colormaps, stretches from glue.viewers.scatter.state import ScatterViewerState from glue.viewers.profile.state import ProfileViewerState, ProfileLayerState from glue.viewers.image.state import ImageSubsetLayerState +from glue.viewers.image.composite_array import COLOR_CONVERTER from glue_jupyter.bqplot.image.state import BqplotImageLayerState from glue_jupyter.common.toolbar_vuetify import read_icon @@ -412,6 +414,7 @@ def state_attr_for_line_visible(state): self.stretch_histogram.add_line('vmin', x=[0, 0], y=[0, 1], ynorm=True, color='#c75d2c') self.stretch_histogram.add_line('vmax', x=[0, 0], y=[0, 1], ynorm=True, color='#c75d2c') + self.stretch_histogram.add_scatter('colorbar', x=[0, 0], y=[0, 1], marker='square', stroke_width=33) # noqa: E501 with self.stretch_histogram.figure.hold_sync(): self.stretch_histogram.figure.axes[0].label = 'pixel value' self.stretch_histogram.figure.axes[0].num_ticks = 3 @@ -661,6 +664,73 @@ def _update_stretch_histogram(self, msg={}): # update the n_bins since this may be a new layer self._histogram_nbins_changed() + @observe('is_active', 'image_color_mode_value', 'image_color_value', 'image_colormap_value', + 'image_contrast_value', 'image_bias_value', + 'stretch_function_value', 'stretch_vmin_value', 'stretch_vmax_value', + 'stretch_hist_nbins', 'stretch_hist_zoom_limits') + @skip_if_no_updates_since_last_active() + def _update_stretch_histogram_colorbar(self, msg={}): + """Renders a colorbar on top of the histogram.""" + if not self._viewer_is_image_viewer() or not hasattr(self, 'stretch_histogram'): + # don't update histogram if selected viewer is not an image viewer, + # or the stretch histogram hasn't been initialized: + return + + if self.multiselect: + with self.stretch_histogram.hold_sync(): + self.stretch_histogram._marks["colorbar"].x = [] + self.stretch_histogram._marks["colorbar"].y = [] + return + + if len(self.layer.selected_obj): + layer = self.layer.selected_obj[0] + else: + # skip further updates if no data are available: + return + + if isinstance(layer.layer, GroupedSubset): + # don't update histogram for subsets: + return + + # Cannot use layer.state because it can be out-of-sync + with self.stretch_histogram.hold_sync(): + color_mode = self.image_color_mode_value + interval = ManualInterval(self.stretch_vmin.value, self.stretch_vmax.value) + contrast_bias = ContrastBiasStretch(self.image_contrast_value, self.image_bias_value) + stretch = stretches.members[self.stretch_function_value] + + x = self.stretch_histogram.figure.marks[0].x + y = np.full(x.shape, self.stretch_histogram.figure.axes[1].scale.max) + + # Copied from the __call__ internals of glue/viewers/image/composite_array.py + data = interval(x) + data = contrast_bias(data, out=data) + data = stretch(data, out=data) + + if color_mode == 'Colormaps': + cmap = colormaps[self.image_colormap.text] + if hasattr(cmap, "get_bad"): + bad_color = cmap.get_bad().tolist()[:3] + layer_cmap = cmap.with_extremes(bad=bad_color + [self.image_opacity_value]) + else: + layer_cmap = cmap + + # Compute colormapped image + plane = layer_cmap(data) + + else: # Monochromatic + # Get color + color = COLOR_CONVERTER.to_rgba_array(self.image_color_value)[0] + plane = data[:, np.newaxis] * color + plane[:, 3] = 1 + + plane = np.clip(plane, 0, 1, out=plane) + ipycolors = [matplotlib.colors.rgb2hex(p, keep_alpha=False) for p in plane] + + self.stretch_histogram._marks["colorbar"].x = x + self.stretch_histogram._marks["colorbar"].y = y + self.stretch_histogram._marks["colorbar"].colors = ipycolors + @observe('is_active', 'stretch_vmin_value', 'stretch_vmax_value', 'layer_selected', 'stretch_hist_nbins', 'image_contrast_value', 'image_bias_value', 'stretch_curve_visible') @@ -683,6 +753,10 @@ def _update_stretch_curve(self, msg=None): return for layer in self.layer.selected_obj: + if isinstance(layer.layer, GroupedSubset): + # don't update histogram for subsets: + return + # clear old mark, if it exists: mark_label = f'{mark_label_prefix}{layer.label}' mark_exists = mark_label in self.stretch_histogram.marks diff --git a/notebooks/concepts/imviz_colorbar_mpl.ipynb b/notebooks/concepts/imviz_colorbar_mpl.ipynb index ab437d31bd..349ba4916e 100644 --- a/notebooks/concepts/imviz_colorbar_mpl.ipynb +++ b/notebooks/concepts/imviz_colorbar_mpl.ipynb @@ -19,6 +19,9 @@ "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", + "from regions import CirclePixelRegion, PixCoord\n", + "\n", + "from jdaviz import Imviz\n", "\n", "%matplotlib inline" ] @@ -60,16 +63,6 @@ "How, how does Imviz fare?" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b04c9b4-ddf4-4025-aea2-77d5920e53dd", - "metadata": {}, - "outputs": [], - "source": [ - "from jdaviz import Imviz" - ] - }, { "cell_type": "code", "execution_count": null, @@ -102,6 +95,26 @@ "plg.open_in_tray()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "83167260-2cc8-4e33-9fd0-9105db23d521", + "metadata": {}, + "outputs": [], + "source": [ + "imviz.default_viewer.zoom_level = \"fit\"" + ] + }, + { + "cell_type": "markdown", + "id": "e3f7d496-1447-4e0a-851b-baee27dcb2e5", + "metadata": {}, + "source": [ + "At this point, it is good to pop the histogram out and put it side by side with this notebook to see any changes.\n", + "\n", + "Now these changes in the plugin should change the colorbar." + ] + }, { "cell_type": "code", "execution_count": null, @@ -110,6 +123,7 @@ "outputs": [], "source": [ "# Colormap (to compare with Matplotlib above)\n", + "plg.image_color_mode = \"Colormaps\"\n", "plg.image_colormap = \"Viridis\"" ] }, @@ -121,8 +135,20 @@ "outputs": [], "source": [ "# Monochromatic\n", - "plg.image_color_mode = \"Monochromatic\"\n", - "plg.image_color = \"red\"" + "#plg.image_color_mode = \"Monochromatic\"\n", + "#plg.image_color = \"red\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef143d48-e7f6-4fb7-8a96-2a04ffa04785", + "metadata": {}, + "outputs": [], + "source": [ + "# Does not change colorbar but just want to see this\n", + "# together with changing stretch function below.\n", + "plg.stretch_curve_visible = True" ] }, { @@ -132,95 +158,94 @@ "metadata": {}, "outputs": [], "source": [ - "plg.stretch_function = \"Square Root\"" + "plg.stretch_function = \"Logarithmic\"\n", + "#plg.stretch_function = \"Square Root\"\n", + "#plg.stretch_function = \"Linear\"" ] }, { "cell_type": "code", "execution_count": null, - "id": "83167260-2cc8-4e33-9fd0-9105db23d521", + "id": "5e8fb2ea-c973-4315-97db-3e23277aca89", "metadata": {}, "outputs": [], "source": [ - "imviz.default_viewer.zoom_level = \"fit\"" + "plg.stretch_preset = \"95%\"\n", + "#plg.stretch_preset = \"Min/Max\"" ] }, { "cell_type": "code", "execution_count": null, - "id": "0e2e02e3-7a25-47c1-a6db-b5ccafcc823d", + "id": "7c72d225-520f-42e1-bda6-c84fc91bb131", "metadata": {}, "outputs": [], "source": [ - "from astropy.visualization import ManualInterval, ContrastBiasStretch\n", - "from glue.config import colormaps, stretches\n", - "from glue.viewers.image.composite_array import COLOR_CONVERTER\n", - "\n", - "# Copied from glue/viewers/image/composite_array.py\n", - "\n", - "color_mode = plg._obj.image_color_mode_value\n", - "interval = ManualInterval(vmin=plg._obj.stretch_vmin_value, vmax=plg._obj.stretch_vmax_value)\n", - "contrast_bias = ContrastBiasStretch(plg._obj.image_contrast_value, plg._obj.image_bias_value)\n", - "stretch = stretches.members[plg._obj.stretch_function_value]\n", - "array = plg._obj.stretch_histogram.figure.marks[0].x\n", - "\n", - "data = interval(array)\n", - "data = contrast_bias(data, out=data)\n", - "data = stretch(data, out=data)\n", - "\n", - "if color_mode == 'Colormaps':\n", - " cmap = colormaps[plg._obj.image_colormap.text]\n", - " if hasattr(cmap, \"get_bad\"):\n", - " bad_color = cmap.get_bad().tolist()[:3]\n", - " layer_cmap = cmap.with_extremes(bad=bad_color + [plg._obj.image_opacity_value])\n", - " else:\n", - " layer_cmap = cmap\n", - "\n", - " # Compute colormapped image\n", - " plane = layer_cmap(data)\n", - "\n", - "else: # Monochromatic\n", - " # Get color\n", - " color = COLOR_CONVERTER.to_rgba_array(plg._obj.image_color_value)[0]\n", - " plane = data[:, np.newaxis] * color\n", - " plane[:, 3] = 1\n", - "\n", - "plane = np.clip(plane, 0, 1, out=plane)" + "plg.stretch_vmin = 3000" ] }, { "cell_type": "code", "execution_count": null, - "id": "25389f05-caf9-4975-b83b-ed4881f4b307", + "id": "0c34b4ca-6729-47f5-a97c-dde3a80b3488", "metadata": {}, "outputs": [], "source": [ - "import matplotlib\n", - "\n", - "ipycolors = [matplotlib.colors.rgb2hex(p, keep_alpha=False) for p in plane]" + "plg.stretch_vmax = 35000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82ada867-05c7-4a76-b52b-41f890b56e76", + "metadata": {}, + "outputs": [], + "source": [ + "plg.stretch_hist_nbins = 50" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9685500-9f4f-4ad4-997f-0a8e1d4b661f", + "metadata": {}, + "outputs": [], + "source": [ + "# Zoom in before limiting histogram to zoom limits below.\n", + "imviz.default_viewer.zoom(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88f8f4e5-82ed-4184-ad11-36d7c244367a", + "metadata": {}, + "outputs": [], + "source": [ + "plg.stretch_hist_zoom_limits = True\n", + "#plg.stretch_hist_zoom_limits = False" ] }, { "cell_type": "code", "execution_count": null, - "id": "2ef6b087-04af-4f91-8bc5-bd0c50f2601a", + "id": "9ae7aad4-e2a9-4bb2-9bdf-1b65187a6ca5", "metadata": {}, "outputs": [], "source": [ - "# This draws dots across the histogram. Drawing line does not work. ynorm=True does not put it on top.\n", - "#x = plg._obj.stretch_histogram.figure.marks[0].x\n", - "#plg._obj.stretch_histogram.add_scatter('colorbar', x=x, y=np.ones(x.size), ynorm=True, color=ipycolors)" + "plg.image_contrast = 4\n", + "#plg.image_contrast = 1.0" ] }, { "cell_type": "code", "execution_count": null, - "id": "637b0051-b1d1-4018-9bb6-e502dc9ebcb5", + "id": "d32cdbeb-21c9-4c01-8d1c-c0ca26c025f2", "metadata": {}, "outputs": [], "source": [ - "# This encodes colorbar inside histogram bars\n", - "plg._obj.stretch_histogram.figure.marks[1].colors = ipycolors" + "plg.image_bias = 1\n", + "#plg.image_bias = 0.5" ] }, { @@ -231,19 +256,75 @@ "outputs": [], "source": [ "# Re-displays the histogram\n", - "plg._obj.stretch_histogram" + "#plg._obj.stretch_histogram" + ] + }, + { + "cell_type": "markdown", + "id": "54164d4e-d1ec-4744-9d38-0003b4a79106", + "metadata": {}, + "source": [ + "What about another data? With Subset?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91b34c6c-cba2-4d20-97bf-d6f5dcbcd675", + "metadata": {}, + "outputs": [], + "source": [ + "reg = CirclePixelRegion(PixCoord(100, 100), 10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3b63137-fc41-4836-b440-6cca3f7b9560", + "metadata": {}, + "outputs": [], + "source": [ + "imviz.load_regions(reg)" ] }, { "cell_type": "code", "execution_count": null, - "id": "09810929-c901-45c4-9595-34b322a8de50", + "id": "8431cabb-35c5-49c7-add4-e380fcf4bfb3", "metadata": {}, "outputs": [], "source": [ - "# Matplotlib rendering of the same colors, as reassurance\n", - "x = plg._obj.stretch_histogram.figure.marks[0].x\n", - "plt.scatter(x, np.ones(x.size), color=ipycolors)" + "image2 = np.random.random(image.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9af2028b-0f6b-42db-ac0a-57e3e277a700", + "metadata": {}, + "outputs": [], + "source": [ + "imviz.load_data(image2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7118a1f3-bf8f-4d1f-ac7e-f4b7fcc473fb", + "metadata": {}, + "outputs": [], + "source": [ + "plg.layer = \"Array\"\n", + "#plg.layer = \"Subset 1\" # Buggy!\n", + "#plg.layer = \"test image\"" + ] + }, + { + "cell_type": "markdown", + "id": "1a74b677-b572-4702-b2f6-4fbf30261983", + "metadata": {}, + "source": [ + "What about multi-viewer?" ] }, { @@ -252,6 +333,47 @@ "id": "b6fc89f6-801c-4944-945e-73c475b5fd72", "metadata": {}, "outputs": [], + "source": [ + "v2 = imviz.create_image_viewer(\"v2\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1666c1c3-ee3c-49c0-9426-ecbc8228873c", + "metadata": {}, + "outputs": [], + "source": [ + "v2.add_data(\"test image\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "961bc5c7-ebfd-4b6e-adf6-78103990dcc0", + "metadata": {}, + "outputs": [], + "source": [ + "plg.viewer = \"v2\"\n", + "#plg.viewer = \"imviz-0\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ed3219e-9e05-46fd-96fe-3a78e5a43c26", + "metadata": {}, + "outputs": [], + "source": [ + "#imviz.destroy_viewer(\"v2\") # Buggy!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36c96416-4981-4d05-a423-51f74e8e66fa", + "metadata": {}, + "outputs": [], "source": [] } ],