Draw region on heatmap using a mouse

I have a Jupyter lab notebook that loads an image dataset and then I use Plotly graph_objs to display the image.

What I would like to be able to do is draw a polygon (or circle) region on the image using the mouse and get the region information back in Python. But, I don’t see any simple way to do it (well, or complex, either). In the menu in the top right at one point I did see a way to draw a box, but it was not persistent and I didn’t see a way to get the corner information back in Python.

So, is there a way to draw polygons or circles, using the mouse, on a heatmap and get the region information back into Python. It would also be important to be able to move and delete the regions.

from ipywidgets import IntSlider
import ipywidgets
import numpy as np
import plotly.graph_objs as go

class Viewer:
    
    def __init__(self):
        
        # Load the data
        self._image_data = np.random.random((256, 256))
        
        self.create_image()
        
        # Create the float view
        self._sl = IntSlider(description='Slice #:', max=20)
        
        self._fig = go.FigureWidget(self._gofig)
        
        # Display the float view
        display(ipywidgets.widgets.VBox([self._fig, self._sl]))
                
    def create_image(self):
        
        self._current_slice = 0
        
        self._trace1 = {
          "x": np.arange(74),
          "y": np.arange(74), 
          "z": self._image_data, 
          "type": "heatmap"
        }
        self._data = go.Data([self._trace1])
        
        
        layout = {
          "xaxis": {
            "gridcolor": "rgb(255,255,255)", 
            "showgrid": True, 
            "showline": False, 
            "showticklabels": True, 
            "tickcolor": "rgb(127,127,127)", 
            "ticks": "outside", 
            "title": "x", 
            "type": "linear", 
            "zeroline": False
          }, 
          "yaxis": {
            "gridcolor": "rgb(255,255,255)", 
            "showgrid": True, 
            "showline": False, 
            "showticklabels": True, 
            "tickcolor": "rgb(127,127,127)", 
            "ticks": "outside", 
            "title": "y", 
            "type": "linear", 
            "zeroline": False,
            "scaleanchor": "x"
          }
        }
        
        self._gofig = go.Figure(data=self._data, layout=layout)

v = Viewer()

Hi @brechmos,

Heatmap traces don’t natively support selection, so you’ll need a little bit of a workaround. But it’s not too complicated :slightly_smiling_face: The idea is to add a dummy scatter trace to the figure and then install a selection callback on that trace. From the selection callback you can get access to the box extents and use those to do what you want. Here’s an example

import plotly.graph_objs as go
from ipywidgets import Output, VBox

fig = go.FigureWidget(
    data=[go.Scatter(y=[None]),
          go.Heatmap(z=[[1, 20, 30],
                        [20, 1, 60],
                        [30, 60, 1]])],
    layout=go.Layout(dragmode='select')
)

out = Output()

@out.capture(clear_output=True)
def do_selection(trace, points, selector):
    print('xrange', selector.xrange)
    print('yrange', selector.yrange)
    

# Be sure to pull trace out of figure before installing callbacks
selection_trace = fig.data[0]    
selection_trace.on_selection(do_selection)

VBox([fig, out])

Hope that helps!
-Jon

Jon,

That is interesting, so, I guess I could draw the box in the callback using the points for persistence. Though how could I move or delete the persisted box?

Craig

Hi @brechmos,

Yeah, you could use a scatter line trace or a box shape to draw the outline after the selection is drawn. I do something like this in this datashader notebook I developed https://anaconda.org/jonmmease/spatial-partitioning-for-datashader-points-rendering/notebook. See the GIF in section " Example 1: Visualizing spatial queries".

I’m not sure of the best way to delete it. You could listen for click events to delete to suppose.

What you really want is probably this persistent selection feature request in the plotly.js project. https://github.com/plotly/plotly.js/issues/1851

-Jon