Python mouse click event.xdata using twinx() - python

I am using canvas.mpl_connect mouse click listener for my e.g. 100x100 contourf plot with xlim from 0 to 99. Doing so I get e.g [x,y]= 10,20 as desired. However I have to display a second x-axis with different coordinates (e.g. xlim from 0.01 to 1) but I dont want event.xdata to return the coordinates in the style of the second axis. Is there a possibility to do so?

You could use the transformations in matplotlib. You would want to convert from the data-coordinates in ax2 to display coordinates (which are universal between the two axes) and then into data coordinates for ax1. Helpfully, you can combine transformations.
For example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots(1)
# First axis, with x-values going from 0 to 100
x1 = np.linspace(0, 100, 101)
y1 = np.sin(2 * np.pi * x1 / max(x1))
ax1.plot(x1, y1, 'b.-')
# Second axis, x values going from 0 to 1
ax2 = ax1.twiny()
x2 = np.linspace(0, 1, 11)
y2 = np.cos(2 * np.pi * x2 / max(x2))
ax2.plot(x2, y2, 'r.-')
# Create a combined transform from ax2 data to ax1 data
combinedTransform = ax2.transData + ax1.transData.inverted()
def onclick(event):
# event has x and y in data coordinates for ax2:
pt_data2 = (event.xdata, event.ydata)
# Convert them into data coordinates for ax1:
pt_data1 = combinedTransform.transform(pt_data2)
# ...
cid = fig.canvas.mpl_connect('button_press_event', onclick)
It feels like there would be a nicer way (somehow tell the event listener which axis you want the xdata and ydata to be valid for, but I don't know it. Sorry)

thanks, I implemented something like this as well. The problem is that there is actually no direct linear transformation behind my data. I solved the issue by just calling the second axis in a function after I finished setting the marker or choosing points. It's not beautiful but should be fine for a Master's thesis!

Related

Plotting a surface for a robot reach bubble in Python

I'm trying to simulate a robot reach bubble. The goal would be to export it into a CAD file and visualize the possible workspace. My approach was to plot all potential endpoints using forward kinematics for the robot, considering linkage lengths and joint limits. This may be a brute-force way to generate the endpoints (Rx, Ry, Rz), but it comes out to be very accurate (at least for 2D examples). [This image shows the provided workspace for an IRB robot and my results when plotting the points in 2D][1]
[1]: https://i.stack.imgur.com/g3cP7.jpg
I can display a three-dimensional figure of the bubble as a scatterplot; however, to export it into a CAD file, I need to mesh it first, which requires converting it into a surface, as I understand. This is the part I'm having trouble with.
Using matplotlib's ax.surface_plot(Rx, Ry, Rz) I receive an error stating that Rz must be a 2-dimensional value. I fiddled with np.meshgrid() and np.mgrid() functions but have been unable to create a simple surface of the bubble. What can I do to convert this scatterplot into a surface? Is there another approach that I'm missing?
Another thing that dawned on me is that I'd likely want to remove some of the intermediate points inside the reach bubble. Ideally, the surface would be composed of the outer ends and the hollow points from the center radius.
Below is a code that results in 1D arrays:
# Reach bubble 3D
import NumPy as np
import matplotlib.pyplot as plt
# Initialize figure and label axes
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
dr = np.pi/180 # Degree to radian multiple
pi = np.pi
# Define important robot dimensions and axis limits for IRB 1100
z0 = 0.327 # Fixed height from base to A1
link1 = 0.28
link2 = 0.3
a1 = np.linspace(0, 2*pi, 8) # Angle limits for axes
a2 = np.linspace(-115*dr, 113*dr, 12)
a3 = np.linspace(-205*dr, 55*dr, 12)
Rx = []
Ry = []
Rz = []
for i1 in a1:
for i2 in a2:
for i3 in a3:
r = link1*np.sin(i2) + link2*np.sin(pi/2+i2+i3)
Rx.append(r*np.cos(i1))
Ry.append(r*np.sin(i1))
Rz.append(z0 + link1*np.cos(i2) + link2*np.cos(pi/2+i2+i3))
# Plot reach points
ax.scatter(Rx, Ry, Rz, c='r', marker='o', alpha = 0.2)
plt.show()
Below is a code that results in 3D arrays but doesn't use for loops:
# 3D Reach bubble for robot
import numpy as np
import matplotlib.pyplot as plt
# Initialize figure and label axes
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
pi = np.pi
dr = pi/180 # Degree to radian multiple
# Define important robot dimensions and axis limits for GP8
z0 = 0.327 # Fixed height from base to A1
link1 = 0.28
link2 = 0.3
link3 = 0.064
a1, a2, a3 = np.mgrid[0:2*pi:15j, -115*dr:113*dr:13j, -205*dr:55*dr:17j]
r = link1*np.sin(a2) + link2*np.sin(pi/2+a2+a3)
Rx = r*np.cos(a1)
Ry = r*np.sin(a1)
Rz = z0 + link1*np.cos(a2) + link2*np.cos(pi/2+a2+a3)
# Plot reach points
ax.plot_surface(Rx, Ry, Rz, color = 'Red', alpha = 0.2)
ax.scatter(Rx, Ry, Rz, c='r', marker='o', alpha = 0.2)

Rotating circle going around the circumference of a larger circle on Matplotlib?

I'm trying to use matplotlib to make a program that will use any two circles (any radius) and the adjacent circle will rotate around the main larger circle. I've looked at matplotlib.animation and it doesn't seem to work. Apparently animations just won't work with shapes?
Here's my code so far (I've deleted the animations subroutine as they seem to just brick the program)
import matplotlib.pyplot as plt
import matplotlib.animation
firstcircle = int(input("please input radius for the largest circle"))
secondcircle = int(input("please input radius for the adjacent circle"))
largest = int(firstcircle*2+secondcircle*2)
difference = int(0-(largest))
difference2 = int(0+(largest))
def createList(r1, r2):
return [item for item in range(r1, r2+1)]
x = (createList(difference,difference2))
y = (createList(difference,difference2))
print(difference)
print(difference2)
def circle():
theta = np.linspace(0, 2*np.pi, 100)
r = np.sqrt(firstcircle**2)
x1 = r*np.cos(theta)
x2 = r*np.sin(theta)
theta2 = np.linspace(0, 2*np.pi, 100)
r1 = np.sqrt(secondcircle**2)
x3 = r1*np.cos(theta2)+firstcircle+secondcircle
x4 = r1*np.sin(theta2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.plot(x1,x2)
circlemove ,= ax.plot(x3,x4)
ax.set_aspect(1)
plt.tight_layout()
plt.xlim(difference,difference2)
plt.ylim(difference,difference2)
plt.grid(linestyle='--')
plt.show()
circle()
You can use Matplotlib FuncAnimation with a function as argument (update in the code below) to call at each new frame. Use theta2 as argument to the update function (trough the frames parameter in the FuncAnimation) and use it together with the already declared variables x3 and x4 to give the perception of the smaller circle progressing around the larger one. Next, use set_data to allow the circlemove object to display different data points for each new frame.
from matplotlib.animation import FuncAnimation
...
...
plt.xlim(difference,difference2)
plt.ylim(difference,difference2)
plt.grid(linestyle='--')
def update(angle):
r_a = r1 + firstcircle
x_a = x3 + r_a * np.cos(angle) - firstcircle - secondcircle
y_a = x4 + r_a * np.sin(angle)
circlemove.set_data(x_a, y_a)
return circlemove
anim = FuncAnimation(fig, update, frames=theta2, repeat=True)
plt.show()
circle()

scatterplot and combined polar histogram in matplotlib

I am attempting to produce a plot like this which combines a cartesian scatter plot and a polar histogram. (Radial lines optional)
A similar solution (by Nicolas Legrand) exists for looking at differences in x and y (code here), but we need to look at ratios (i.e. x/y).
More specifically, this is useful when we want to look at the relative risk measure which is the ratio of two probabilities.
The scatter plot on it's own is obviously not a problem, but the polar histogram is more advanced.
The most promising lead I have found is this central example from the matplotlib gallery here
I have attempted to do this, but have run up against the limits of my matplotlib skills. Any efforts moving towards this goal would be great.
I'm sure that others will have better suggestions, but one method that gets something like you want (without the need for extra axes artists) is to use a polar projection with a scatter and bar chart together. Something like
import matplotlib.pyplot as plt
import numpy as np
x = np.random.uniform(size=100)
y = np.random.uniform(size=100)
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi, bins=np.linspace(0, np.pi/2, 21), density=True)
colors = plt.cm.Spectral(h / h.max())
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, r, marker='.')
ax.bar(b[:-1], h, width=b[1:] - b[:-1],
align='edge', bottom=np.max(r) + 0.2, color=colors)
# Cut off at 90 degrees
ax.set_thetamax(90)
# Set the r grid to cover the scatter plot
ax.set_rgrids([0, 0.5, 1])
# Let's put a line at 1 assuming we want a ratio of some sort
ax.set_thetagrids([45], [1])
which will give
It is missing axes labels and some beautification, but it might be a place to start. I hope it is helpful.
You can use two axes on top of each other:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.8,.8], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-1)
ax2.set_thetamax(90)
plt.show()
Ok. Thanks to the answer from Nicolas, and the answer from tomjn I have a working solution :)
import numpy as np
import matplotlib.pyplot as plt
# Scatter data
n = 50
x = 0.3 + np.random.randn(n)*0.1
y = 0.4 + np.random.randn(n)*0.02
def radial_corner_plot(x, y, n_hist_bins=51):
"""Scatter plot with radial histogram of x/y ratios"""
# Axis setup
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.6,.6], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-20)
ax2.set_thetamax(90)
# define useful constant
offset_in_radians = np.pi/4
def rotate_hist_axis(ax):
"""rotate so that 0 degrees is pointing up and right"""
ax.set_theta_offset(offset_in_radians)
ax.set_thetamin(-45)
ax.set_thetamax(45)
return ax
# Convert scatter data to histogram data
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi,
bins=np.linspace(0, np.pi/2, n_hist_bins),
density=True)
# SCATTER PLOT -------------------------------------------------------
ax1.scatter(x,y)
ax1.set(xlim=[0, 1], ylim=[0, 1], xlabel="x", ylabel="y")
ax1.spines['right'].set_visible(False)
ax1.spines['top'].set_visible(False)
# HISTOGRAM ----------------------------------------------------------
ax2 = rotate_hist_axis(ax2)
# rotation of axis requires rotation in bin positions
b = b - offset_in_radians
# plot the histogram
bars = ax2.bar(b[:-1], h, width=b[1:] - b[:-1], align='edge')
def update_hist_ticks(ax, desired_ratios):
"""Update tick positions and corresponding tick labels"""
x = np.ones(len(desired_ratios))
y = 1/desired_ratios
phi = np.arctan2(y,x) - offset_in_radians
# define ticklabels
xticklabels = [str(round(float(label), 2)) for label in desired_ratios]
# apply updates
ax2.set(xticks=phi, xticklabels=xticklabels)
return ax
ax2 = update_hist_ticks(ax2, np.array([1/8, 1/4, 1/2, 1, 2, 4, 8]))
# just have radial grid lines
ax2.grid(which="major", axis="y")
# remove bin count labels
ax2.set_yticks([])
return (fig, [ax1, ax2])
fig, ax = radial_corner_plot(x, y)
Thanks for the pointers!

Expanding axes to fill figure, same scale on x and y

I know 2 things but separately.
figure.tight_layout
will expand my current axes
axes.aspect('equal')
will keep same scale on x and y.
But when I use them both I get square axes view and I want it to be expanded.
By keeping same scale I mean there is same distance from 0 to 1 on x and y axis.
Is there any way to make it happen? Keep same scale and expand to full figure(not only a square)
The answer should work with autoscale
There might be less clumsy way, but at least you can do it manually. A very simple example:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0,1],[1,0])
ax.set_aspect(1)
ax.set_xlim(0, 1.5)
creates
which honours the aspect ratio.
If you want to have the automatic scaling offered by the tight_layout, then you'll have to do some maths of your own:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0,1],[1,0])
fig.tight_layout()
# capture the axis positioning in pixels
bb = fig.transFigure.transform(ax.get_position())
x0, y0 = bb[0]
x1, y1 = bb[1]
width = x1 - x0
height = y1 - y0
# set the aspect ratio
ax.set_aspect(1)
# calculate the aspect ratio of the plot
plot_aspect = width / height
# get the axis limits in data coordinates
ax0, ax1 = ax.get_xlim()
ay0, ay1 = ax.get_ylim()
awidth = ax1 - ax0
aheight = ay1 - ay0
# calculate the plot aspect
data_aspect = awidth / aheight
# check which one needs to be corrected
if data_aspect < plot_aspect:
ax.set_xlim(ax0, ax0 + plot_aspect * aheight)
else:
ax.set_ylim(ay0, ay0 + awidth / plot_aspect)
Of course, you may set the xlim and ylim any way you want, you might, for example, want to add an equal amount of space to either end of the scale.
The solution that worked in my case was to call
axis.aspect("equal")
axis.set_adjustable("datalim")
stolen from this example in the documentation.

How to extract an arbitrary line of values from a numpy array?

I have a numpy array that contains some image data. I would like to plot the 'profile' of a transect drawn across the image. The simplest case is a profile running parallel to the edge of the image, so if the image array is imdat, then the profile at a selected point (r,c) is simply imdat[r] (horizontal) or imdat[:,c] (vertical).
Now, I want to take as input two points (r1,c1) and (r2,c2), both lying inside imdat. I would like to plot the profile of the values along the line connecting these two points.
What is the best way to get values from a numpy array, along such a line? More generally, along a path/polygon?
I have used slicing and indexing before, but I can't seem to arrive at an elegant solution for such a where consecutive slice elements are not in the same row or column. Thanks for your help.
#Sven's answer is the easy way, but it's rather inefficient for large arrays. If you're dealing with a relatively small array, you won't notice the difference, if you're wanting a profile from a large (e.g. >50 MB) you may want to try a couple of other approaches. You'll need to work in "pixel" coordinates for these, though, so there's an extra layer of complexity.
There are two more memory-efficient ways. 1) use scipy.ndimage.map_coordinates if you need bilinear or cubic interpolation. 2) if you just want nearest neighbor sampling, then just use indexing directly.
As an example of the first:
import numpy as np
import scipy.ndimage
import matplotlib.pyplot as plt
#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 1000
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x,y)))
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')
axes[1].plot(zi)
plt.show()
The equivalent using nearest-neighbor interpolation would look something like this:
import numpy as np
import matplotlib.pyplot as plt
#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 1000
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line
zi = z[x.astype(np.int), y.astype(np.int)]
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')
axes[1].plot(zi)
plt.show()
However, if you're using nearest-neighbor, you probably would only want samples at each pixel, so you'd probably do something more like this, instead...
import numpy as np
import matplotlib.pyplot as plt
#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
length = int(np.hypot(x1-x0, y1-y0))
x, y = np.linspace(x0, x1, length), np.linspace(y0, y1, length)
# Extract the values along the line
zi = z[x.astype(np.int), y.astype(np.int)]
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')
axes[1].plot(zi)
plt.show()
Probably the easiest way to do this is to use scipy.interpolate.interp2d():
# construct interpolation function
# (assuming your data is in the 2-d array "data")
x = numpy.arange(data.shape[1])
y = numpy.arange(data.shape[0])
f = scipy.interpolate.interp2d(x, y, data)
# extract values on line from r1, c1 to r2, c2
num_points = 100
xvalues = numpy.linspace(c1, c2, num_points)
yvalues = numpy.linspace(r1, r2, num_points)
zvalues = f(xvalues, yvalues)
I've been testing the above routines with galaxy images and think I found a small error. I think a transpose needs to be added to the otherwise great solution provided by Joe. Here is a slightly modified version of his code that reveals the error. If you run it without the transpose, you can see the profile doesn't match up; with the transpose it looks okay. This isn't apparent in Joe's solution since he uses a symmetric image.
import numpy as np
import scipy.ndimage
import matplotlib.pyplot as plt
import scipy.misc # ADDED THIS LINE
#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)
lena = scipy.misc.lena() # ADDED THIS ASYMMETRIC IMAGE
z = lena[320:420,330:430] # ADDED THIS ASYMMETRIC IMAGE
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 500
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x,y))) # THIS DOESN'T WORK CORRECTLY
zi = scipy.ndimage.map_coordinates(np.transpose(z), np.vstack((x,y))) # THIS SEEMS TO WORK CORRECTLY
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')
axes[1].plot(zi)
plt.show()
Here's the version WITHOUT the transpose. Notice that only a small fraction on the left should be bright according to the image but the plot shows almost half of the plot as bright.
Here's the version WITH the transpose. In this image, the plot seems to match well with what you'd expect from the red line in the image.
For a canned solution look into scikit-image's measure.profile_line function.
It's built on top of scipy.ndimage.map_coordinates as in #Joe's answer and has some extra useful functionality baked in.
Combining this answer with the Event Handling example on MPL's documentation, here's the code to allow for GUI-based dragging to draw/update your slice, by dragging on the plot data (this is coded for pcolormesh plots):
import numpy as np
import matplotlib.pyplot as plt
# Handle mouse clicks on the plot:
class LineSlice:
'''Allow user to drag a line on a pcolor/pcolormesh plot, and plot the Z values from that line on a separate axis.
Example
-------
fig, (ax1, ax2) = plt.subplots( nrows=2 ) # one figure, two axes
img = ax1.pcolormesh( x, y, Z ) # pcolormesh on the 1st axis
lntr = LineSlice( img, ax2 ) # Connect the handler, plot LineSlice onto 2nd axis
Arguments
---------
img: the pcolormesh plot to extract data from and that the User's clicks will be recorded for.
ax2: the axis on which to plot the data values from the dragged line.
'''
def __init__(self, img, ax):
'''
img: the pcolormesh instance to get data from/that user should click on
ax: the axis to plot the line slice on
'''
self.img = img
self.ax = ax
self.data = img.get_array().reshape(img._meshWidth, img._meshHeight)
# register the event handlers:
self.cidclick = img.figure.canvas.mpl_connect('button_press_event', self)
self.cidrelease = img.figure.canvas.mpl_connect('button_release_event', self)
self.markers, self.arrow = None, None # the lineslice indicators on the pcolormesh plot
self.line = None # the lineslice values plotted in a line
#end __init__
def __call__(self, event):
'''Matplotlib will run this function whenever the user triggers an event on our figure'''
if event.inaxes != self.img.axes: return # exit if clicks weren't within the `img` axes
if self.img.figure.canvas.manager.toolbar._active is not None: return # exit if pyplot toolbar (zooming etc.) is active
if event.name == 'button_press_event':
self.p1 = (event.xdata, event.ydata) # save 1st point
elif event.name == 'button_release_event':
self.p2 = (event.xdata, event.ydata) # save 2nd point
self.drawLineSlice() # draw the Line Slice position & data
#end __call__
def drawLineSlice( self ):
''' Draw the region along which the Line Slice will be extracted, onto the original self.img pcolormesh plot. Also update the self.axis plot to show the line slice data.'''
'''Uses code from these hints:
http://stackoverflow.com/questions/7878398/how-to-extract-an-arbitrary-line-of-values-from-a-numpy-array
http://stackoverflow.com/questions/34840366/matplotlib-pcolor-get-array-returns-flattened-array-how-to-get-2d-data-ba
'''
x0,y0 = self.p1[0], self.p1[1] # get user's selected coordinates
x1,y1 = self.p2[0], self.p2[1]
length = int( np.hypot(x1-x0, y1-y0) )
x, y = np.linspace(x0, x1, length), np.linspace(y0, y1, length)
# Extract the values along the line with nearest-neighbor pixel value:
# get temp. data from the pcolor plot
zi = self.data[x.astype(np.int), y.astype(np.int)]
# Extract the values along the line, using cubic interpolation:
#import scipy.ndimage
#zi = scipy.ndimage.map_coordinates(self.data, np.vstack((x,y)))
# if plots exist, delete them:
if self.markers != None:
if isinstance(self.markers, list):
self.markers[0].remove()
else:
self.markers.remove()
if self.arrow != None:
self.arrow.remove()
# plot the endpoints
self.markers = self.img.axes.plot([x0, x1], [y0, y1], 'wo')
# plot an arrow:
self.arrow = self.img.axes.annotate("",
xy=(x0, y0), # start point
xycoords='data',
xytext=(x1, y1), # end point
textcoords='data',
arrowprops=dict(
arrowstyle="<-",
connectionstyle="arc3",
color='white',
alpha=0.7,
linewidth=3
),
)
# plot the data along the line on provided `ax`:
if self.line != None:
self.line[0].remove() # delete the plot
self.line = self.ax.plot(zi)
#end drawLineSlice()
#end class LineTrace
# load the data:
D = np.genfromtxt(DataFilePath, ...)
fig, ax1, ax2 = plt.subplots(nrows=2, ncols=1)
# plot the data
img = ax1.pcolormesh( np.arange( len(D[0,:]) ), np.arange(len(D[:,0])), D )
# register the event handler:
LnTr = LineSlice(img, ax2) # args: the pcolor plot (img) & the axis to plot the values on (ax2)
This results in the following (after adding axis labels etc.), after dragging on the pcolor plot:
Here is a method without using scipy package(s). It should run much faster and is easy to understand. Basically, any pair of coordinates between point 1 (pt1) and point 2 (pt2) can be converted to x- and y- pixel integers, so we don't need any interpolation.
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
def euclideanDistance(coord1,coord2):
return np.sqrt((coord1[0]-coord2[0])**2+(coord1[1]-coord2[1])**2)
def getLinecut(image,X,Y,pt1,pt2):
row_col_1, row_col_2 = getRowCol(pt1,X,Y), getRowCol(pt2,X,Y)
row1,col1 = np.asarray(row_col_1).astype(float)
row2,col2 = np.asarray(row_col_2).astype(float)
dist = np.sqrt((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)
N = int(euclideanDistance(row_col_1,row_col_2))#int(np.sqrt((row1-row2)**2+(col1-col2)**2))
rowList = [int(row1 + (row2-row1)/N*ind) for ind in range(N)]
colList = [int(col1 + (col2-col1)/N*ind) for ind in range(N)]
distList = [dist/N*ind for ind in range(N)]
return distList,image[rowList,colList]#rowList,colList
def getRowCol(pt,X,Y):
if X.min()<=pt[0]<=X.max() and Y.min()<=pt[1]<=Y.max():
pass
else:
raise ValueError('The input center is not within the given scope.')
center_coord_rowCol = (np.argmin(abs(Y-pt[1])),np.argmin(abs(X-pt[0])))
return center_coord_rowCol
image = np.asarray(Image.open('./Picture1.png'))[:,:,1]
image_copy = image.copy().astype(float)
X = np.linspace(-27,27,np.shape(image)[1])#[::-1]
Y = np.linspace(-15,15,np.shape(image)[0])[::-1]
pt1, pt2 = (-12,-14), (20,13)
distList, linecut = getLinecut(image_copy,X,Y,pt1,pt2)
plt.plot(distList, linecut)
plt.figure()
plt.pcolormesh(X,Y,image_copy)
plt.plot([pt1[0],pt2[0]],[pt1[1],pt2[1]],color='red')
plt.gca().set_aspect(1)
Picture1.png figure used:
See here for more details:
https://github.com/xuejianma/fastLinecut_radialLinecut
There is another function of the code: taking an average of several angle-evenly-spaced lines.

Categories