Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib rectangleSelector - Set initial position

I am working on a project on which I want to select an area on an image in order to do some treatments on this area. So I found this wonderful tool RectangleSelector of matplotlib. But what I want is setting the rectangle at an initial position on the image, instead of waiting for the user clicking a first area (for example a rectangle of 10x10 pixels on top/left of the image).

At first I thought I have to use state_modifier_keys but it doesn't seem to be this.

So, is it possible ?

An example (with plots but it is the same question) can be:

from __future__ import print_function
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt


def line_select_callback(eclick, erelease):
    'eclick and erelease are the press and release events'
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
    print(" The button you used were: %s %s" % (eclick.button, erelease.button))


def toggle_selector(event):
    print(' Key pressed.')
    if event.key in ['Q', 'q'] and toggle_selector.RS.active:
        print(' RectangleSelector deactivated.')
        toggle_selector.RS.set_active(False)
    if event.key in ['A', 'a'] and not toggle_selector.RS.active:
        print(' RectangleSelector activated.')
        toggle_selector.RS.set_active(True)


fig, current_ax = plt.subplots()                 # make a new plotting range
N = 100000                                       # If N is large one can see
x = np.linspace(0.0, 10.0, N)                    # improvement by use blitting!

plt.plot(x, +np.sin(.2*np.pi*x), lw=3.5, c='b', alpha=.7)  # plot something
plt.plot(x, +np.cos(.2*np.pi*x), lw=3.5, c='r', alpha=.5)
plt.plot(x, -np.sin(.2*np.pi*x), lw=3.5, c='g', alpha=.3)

print("\n      click  -->  release")

# drawtype is 'box' or 'line' or 'none'
toggle_selector.RS = RectangleSelector(current_ax, line_select_callback,
                                       drawtype='box', useblit=True,
                                       button=[1, 3],  # don't use middle button
                                       minspanx=5, minspany=5,
                                       spancoords='pixels',
                                       interactive=True)
plt.connect('key_press_event', toggle_selector)
plt.show()
like image 632
Mathieu Gauquelin Avatar asked Oct 20 '25 12:10

Mathieu Gauquelin


2 Answers

Without blitting

If you're willing not to use blitting, the solution would be to set the RectangleSelector's patch visible (RS.to_draw.set_visible(True)) and set its extent to the data you want it to be (RS.extents = (0,4,0,1)).

from __future__ import print_function
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt


def line_select_callback(eclick, erelease):
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))

fig, current_ax = plt.subplots()
N = 100000 
x = np.linspace(0.0, 10.0, N) 

plt.plot(x, +np.sin(.2*np.pi*x), lw=3.5, c='b', alpha=.7)  # plot something
plt.plot(x, +np.cos(.2*np.pi*x), lw=3.5, c='r', alpha=.5)
plt.plot(x, -np.sin(.2*np.pi*x), lw=3.5, c='g', alpha=.3)


# drawtype is 'box' or 'line' or 'none'
RS = RectangleSelector(current_ax, line_select_callback,
                                       drawtype='box', useblit=False,
                                       button=[1, 3],  # don't use middle button
                                       minspanx=5, minspany=5,
                                       spancoords='pixels',
                                       interactive=True)

RS.to_draw.set_visible(True)
fig.canvas.draw()
RS.extents = (0,4,0,1)

plt.show()

With blitting

The above will unfortunately not work when blitting is used.
A solution with blitting would need to involve copying the background, then setting the rectangle visible, but setting the animated property to false. Then for the first time the canvas is drawn the rectangle is shown. However for later use we would need to set the animated property to True again. Hence for the first time a click happens inside the figure, we need to set the animated property to True and blit the canvas with a background which has been saved before showing the rectangle.

from __future__ import print_function
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt


def line_select_callback(eclick, erelease):
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))

fig, current_ax = plt.subplots()
N = 100000 
x = np.linspace(0.0, 10.0, N) 

plt.plot(x, +np.sin(.2*np.pi*x), lw=3.5, c='b', alpha=.7)  # plot something
plt.plot(x, +np.cos(.2*np.pi*x), lw=3.5, c='r', alpha=.5)
plt.plot(x, -np.sin(.2*np.pi*x), lw=3.5, c='g', alpha=.3)


# drawtype is 'box' or 'line' or 'none'
RS = RectangleSelector(current_ax, line_select_callback,
                                       drawtype='box', useblit=True,
                                       button=[1, 3],  # don't use middle button
                                       minspanx=5, minspany=5,
                                       spancoords='pixels',
                                       interactive=True)
fig.canvas.draw()
bg = fig.canvas.copy_from_bbox(RS.ax.bbox)
RS.set_visible(True)

ext = (0,4,0,1)
RS.draw_shape(ext)
# Update displayed handles
RS._corner_handles.set_data(*RS.corners)
RS._edge_handles.set_data(*RS.edge_centers)
RS._center_handle.set_data(*RS.center)
for artist in RS.artists:
    RS.ax.draw_artist(artist)
    artist.set_animated(False)
fig.canvas.draw()

def initclick(evt):
    RS.background = bg
    RS.update()
    for artist in RS.artists:
        artist.set_animated(True)
    fig.canvas.mpl_disconnect(cid)

cid = fig.canvas.mpl_connect("button_press_event",initclick)

plt.show()
like image 95
ImportanceOfBeingErnest Avatar answered Oct 22 '25 00:10

ImportanceOfBeingErnest


For matplotlib v1.21.1 and py3.7 this works (both with RS useblit=True and False):

RS.to_draw.set_visible(True)
RS.extents = (x1,x2,y1,y2)

Note the order of coordinates for rectangle: x1, x2, y1, y2 (not x1, y1, x2, y2!)

like image 34
Oliver Zendel Avatar answered Oct 22 '25 01:10

Oliver Zendel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!