diff --git a/jdaviz/configs/default/plugins/plot_options/plot_options.py b/jdaviz/configs/default/plugins/plot_options/plot_options.py index 4a61551c42..b1ab0e6722 100644 --- a/jdaviz/configs/default/plugins/plot_options/plot_options.py +++ b/jdaviz/configs/default/plugins/plot_options/plot_options.py @@ -4,6 +4,7 @@ from astropy.visualization import ( ManualInterval, ContrastBiasStretch, PercentileInterval ) +from echo import delay_callback from traitlets import Any, Dict, Float, Bool, Int, List, Unicode, observe from glue.core.subset_group import GroupedSubset @@ -411,11 +412,12 @@ 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.figure.axes[0].label = 'pixel value' - self.stretch_histogram.figure.axes[0].num_ticks = 3 - self.stretch_histogram.figure.axes[0].tick_format = '0.1e' - self.stretch_histogram.figure.axes[1].label = 'density' - self.stretch_histogram.figure.axes[1].num_ticks = 2 + 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 + self.stretch_histogram.figure.axes[0].tick_format = '0.1e' + self.stretch_histogram.figure.axes[1].label = 'density' + self.stretch_histogram.figure.axes[1].num_ticks = 2 self.stretch_histogram_widget = f'IPY_MODEL_{self.stretch_histogram.model_id}' self.subset_visible = PlotOptionsSyncState(self, self.viewer, self.layer, 'visible', @@ -650,8 +652,9 @@ def _update_stretch_histogram(self, msg={}): stretch_vstep = (hist_lims[1] - hist_lims[0]) / 100. self.stretch_vstep = np.round(stretch_vstep, decimals=-int(np.log10(stretch_vstep))+1) # noqa - self.stretch_histogram.viewer.state.hist_x_min = hist_lims[0] - self.stretch_histogram.viewer.state.hist_x_max = hist_lims[1] + with delay_callback(self.stretch_histogram.viewer.state, 'hist_x_min', 'hist_x_max'): + self.stretch_histogram.viewer.state.hist_x_min = hist_lims[0] + self.stretch_histogram.viewer.state.hist_x_max = hist_lims[1] self.stretch_histogram.figure.title = f"{len(sub_data)} pixels" diff --git a/notebooks/concepts/imviz_colorbar_mpl.ipynb b/notebooks/concepts/imviz_colorbar_mpl.ipynb new file mode 100644 index 0000000000..ab437d31bd --- /dev/null +++ b/notebooks/concepts/imviz_colorbar_mpl.ipynb @@ -0,0 +1,279 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7b9bf568-7416-46ad-9108-0d1d97c9af32", + "metadata": {}, + "source": [ + "Concept notebook to explore colorbar implementation in bqplot vs Matplotlib.\n", + "\n", + "Matplotlib example is from https://docs.astropy.org/en/latest/visualization/normalization.html ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6374a84-beaa-4e98-ae25-75ece45cc54b", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f4c35a6-ba8f-4f63-b68a-607de30c08f9", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate a test image\n", + "image = np.arange(65536).reshape((256, 256))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c6f2384-b70b-4106-973d-b62623d67e5f", + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.visualization import simple_norm\n", + "\n", + "# Create an ImageNormalize object\n", + "norm = simple_norm(image, 'sqrt')\n", + "\n", + "# Display the image\n", + "fig, ax = plt.subplots()\n", + "im = ax.imshow(image, origin='lower', norm=norm)\n", + "fig.colorbar(im, location=\"bottom\")" + ] + }, + { + "cell_type": "markdown", + "id": "5ab7bbf6-372b-4aa0-9f02-a5dacace164e", + "metadata": {}, + "source": [ + "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, + "id": "e9349094-620b-40c4-8ae2-d424b5983119", + "metadata": {}, + "outputs": [], + "source": [ + "imviz = Imviz()\n", + "imviz.load_data(image, data_label=\"test image\")\n", + "imviz.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27a06c70-b7af-4055-9b50-2d1269ba38d3", + "metadata": {}, + "outputs": [], + "source": [ + "plg = imviz.plugins[\"Plot Options\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e261197-8db5-4051-9f9f-07e7245cc53d", + "metadata": {}, + "outputs": [], + "source": [ + "plg.open_in_tray()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed2c2107-1803-4481-81b9-e6ab162e7425", + "metadata": {}, + "outputs": [], + "source": [ + "# Colormap (to compare with Matplotlib above)\n", + "plg.image_colormap = \"Viridis\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc2331cf-e8e4-4ad9-ac80-3dac338c298a", + "metadata": {}, + "outputs": [], + "source": [ + "# Monochromatic\n", + "plg.image_color_mode = \"Monochromatic\"\n", + "plg.image_color = \"red\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52dd988b-a763-4caa-acee-3d39682bdcce", + "metadata": {}, + "outputs": [], + "source": [ + "plg.stretch_function = \"Square Root\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83167260-2cc8-4e33-9fd0-9105db23d521", + "metadata": {}, + "outputs": [], + "source": [ + "imviz.default_viewer.zoom_level = \"fit\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e2e02e3-7a25-47c1-a6db-b5ccafcc823d", + "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)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25389f05-caf9-4975-b83b-ed4881f4b307", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib\n", + "\n", + "ipycolors = [matplotlib.colors.rgb2hex(p, keep_alpha=False) for p in plane]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ef6b087-04af-4f91-8bc5-bd0c50f2601a", + "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)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "637b0051-b1d1-4018-9bb6-e502dc9ebcb5", + "metadata": {}, + "outputs": [], + "source": [ + "# This encodes colorbar inside histogram bars\n", + "plg._obj.stretch_histogram.figure.marks[1].colors = ipycolors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6b38132-8241-4c7e-876c-9e02cc97cfc6", + "metadata": {}, + "outputs": [], + "source": [ + "# Re-displays the histogram\n", + "plg._obj.stretch_histogram" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09810929-c901-45c4-9595-34b322a8de50", + "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)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6fc89f6-801c-4944-945e-73c475b5fd72", + "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.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}