I have a 3D numpy array, e.g. of shape (200,200,200).
I also have a 2D plane, which I can express either in the form
ax + by + cz + d = 0 or a (x,y,z) point and a direction vector in terms of the three x,y,z axes.
I want to "plot" this plane on the 3D numpy array, by essentially changing the value of all points that lie on this plane to a constant value.
Like this, but with only a single colour:
The most versatile solution to this (I think) is to get all the integer indices of points that lie along this line. I can then just use this to index the numpy array. However, if a library offered the ability to plot a plane directly, without providing the indices, then this would be fine.
I have seen problems on this site that seem superficially similar to my problem, such as approaches of finding the values that lie along an x,y,z vector using scipy.ndimage.map_coordinates, but of course I am dealing with a plane not a line (also this just returned the values, not the indices).
Another approach I considered, but seems difficult (and maybe slow) is something similar to this question where the reply shows how to draw a 2D triangle in a 3D space. I could instead draw a square in 3D space, but this shows that filling this square is not trivial, and also the square will only fill the entire plane if at least one axis is parallel to x, y or z (or a "corner" will remain unfilled).
Has anyone got any idea how I might achieve this?
If the plane is near horizontal, you can give values to x and y and calculate z:
a, b, c, d = 1, 2, 3, -600 # Plane parameters
v = np.arange(200)
x, y = np.meshgrid(v, v) # All xy combinations
z = np.rint((a*x + b*y + d) / -c).astype(int) # Rounded
plane_voxels = np.dstack([x,y,z]).reshape(-1,3)
print(plane_voxels)
Results in:
[[ 0 0 200]
[ 1 0 200]
[ 2 0 199]
...
[197 199 2]
[198 199 1]
[199 199 1]]
For the general case you need to find the dimension with less variability, that will be the calculated variable, and give values to the other two:
a, b, c, d = 1, 2, 3, -600
v = np.arange(200)
dim = np.argmax(np.abs((a, b, c)))
if dim == 0:
y, z = np.meshgrid(v, v)
x = np.rint((b*y + c*z + d) / -a).astype(int)
elif dim == 1:
x, z = np.meshgrid(v, v)
y = np.rint((a*x + c*z + d) / -b).astype(int)
elif dim == 2:
x, y = np.meshgrid(v, v)
z = np.rint((a*x + b*y + d) / -c).astype(int)
plane_voxels = np.dstack([x,y,z]).reshape(-1,3)
Here is an idea. First let's say you have a (3,3,3) of one. Then define your function of the plane equal to 0. create your coordinates with meshgrid and use it in your plane function. Finally in case you want some tolerance, use isclose to 0 as a mask of your array to change the values
n = 3 # change to 200 for your real case or arr.shape[0]
# just for the example
arr = np.ones([n]*3)
# plane function = 0
f = lambda x,y,z: 2*x + 3*y**2 + 4*z - 3
# create the mask
mask = np.isclose(f(*np.meshgrid(*[np.arange(n)]*3)),
0, # because your plane function is equal to 0
atol=1) # this is if you want some tolerance to catch nearby points
# change the value to whatever
arr[mask] = 5
print(arr)
[[[1. 5. 1.]
[5. 1. 1.]
[5. 1. 1.]]
[[5. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]]
I do not understand why polynomial.Polynomial.fit() gives coefficients very different from the expected coefficients :
import numpy as np
x = np.linspace(0, 10, 50)
y = x**2 + 5 * x + 10
print(np.polyfit(x, y, 2))
print(np.polynomial.polynomial.polyfit(x, y, 2))
print(np.polynomial.polynomial.Polynomial.fit(x, y, 2))
Gives :
[ 1. 5. 10.]
[10. 5. 1.]
poly([60. 75. 25.])
The two first results are OK, and thanks to this answer I understand why the two arrays are in reversed order.
However, I do not understand the signification of the third result. The coefficients looks wrong, though the polynomial that I got this way seems to give correct predicted values.
The answer is slightly hidden in the docs, of course. Looking at the class numpy.polynomial.polynomial.Polynomial(coef, domain=None, window=None)
It is clear that in general the coefficients [a, b, c, ...] are for the polynomial a + b * x + c * x**2 + .... However, there are the keyword parameters domain and window both with default [-1,1]. I am not into that class, so I am not sure about the purpose, but it is clear that a remapping takes place. Now in the case of polynomial.Polynomial.fit() one has a class method that automatically takes the x data as domain, but still makes the mapping to the window. Hence, in the OP [0-10] is mapped onto [-1,1]. This is done by x = x' / 5 - 1 or x' -> 5 * x + 5. Putting the latter in the OP polynomial we get
( 5 x' + 5 )**2 + 5 * ( 5 * x' + 5 ) + 10 = 25 * x'**2 + 75 * x' + 60
Voila.
To get the expected result one has to put
print(np.polynomial.polynomial.Polynomial.fit(x, y, 2, window=[0, 10] ) )
wich gives
poly([10. 5. 1.])
Buried in the docs:
Note that the coefficients are given in the scaled domain defined by the linear mapping between the window and domain. convert can be used to get the coefficients in the unscaled data domain.
So use:
poly.convert()
This will rescale your coefficients to what you are probably expecting.
Example for data generated from 1 + 2x + 3x^2:
from numpy.polynomial import Polynomial
test_poly = Polynomial.fit([0, 1, 2, 3, 4, 5],
[1, 6, 17, 34, 57, 86],
2)
print(test_poly)
print(test_poly.convert())
Output:
poly([24.75 42.5 18.75])
poly([1. 2. 3.])
I am trying to use scipy.optimize.fsolve to work out the x-intercept(s):
from scipy.optimize import fsolve
from numpy import array, empty
counter = 0
def f(x_):
global counter
counter += 1
return pow(x_, 3) * 3 - 9.5 * pow(x_, 2) + 10 * x_
x0_ = empty(2)
x0_[0] = 1
x0_[1] = 6
res = fsolve(f, x0=x0_)
print(counter)
print(res)
the function f(x): https://www.desmos.com/calculator/8j8djr01da
the result of this code is:
74
[0. 0.]
I expect the result to be
[0, 1.575, 3.175]
Can someone please offer some help.
Plus:
I can't understand the documentation of fsolve(x0), is that just a guess? I will be so appreciated if you can explain.
Plus Plus:
I will be working with lots of linear equations with unknown expressions and exponential, I am really looking for a way to work out the x-intercepts, in other words, the roots by the expression of f(x).I would be so glad if you can help.
You get the set of all roots for a polynomial by
numpy.roots([3, -9.5, +10, 0])
array([1.58333333+0.90905934j, 1.58333333-0.90905934j,
0. +0.j ])
It is not clear what your other expected real roots are, fsolve will only find the real root 0.
Of course, if you take the coefficients that you used in the Desmos graphing tool
numpy.roots([2, -9.5, +10, 0])
you will actually get the expected
array([3.17539053, 1.57460947, 0. ])
For scalar non-polynomial functions the interface scipy.optimize.find_root is perhaps more suitable, especially if you can provide a bracketing interval.
I just want to say that at the first step you define your function wrong:
it should be
def f(x_):
# global counter
# counter += 1
return pow(x_, 3) * 2 - 9.5 * pow(x_, 2) + 10 * x_
but notpow(x_, 3) * 3 - 9.5 * pow(x_, 2) + 10 * x_
If you then set x0_ precisely:
x0_=[0,1,3] # according to intersection on graph
res=fsolve(f, x0=x0_)
Give you the anticipated output:
[0. 1.57460947 3.17539053]
Sometimes you just have to be more careful :)
I'm looking to convert a series of ordered (pretty dense) 2D points describing arbitrary curves into a NURBS representation, which can be written into an IGES file.
I'm using scipy.interpolate's splprep to get a B-spline representation of the given series of points, and then I had presumed the NURBS definition would essentially be this plus saying all weights are equal to 1. However I think I am fundamentally misinterpreting the output of splprep, specifically the relation between 'B-spline coefficients' and the control points needed to manually recreate the spline in some CAD package (I am using Siemens NX11).
I've tried a simple example of approximating the function y = x^3 from a sparse set of points:
import scipy.interpolate as si
import numpy as np
import matplotlib.pyplot as plt
# Sparse points defining cubic
x = np.linspace(-1,1,7)
y = x**3
# Get B-spline representation
tck, u = si.splprep([x,y],s=0.0)
# Get (x,y) coordinates of control points
c_x = tck[1][0]
c_y = tck[1][1]
# Plotting
u_fine = np.linspace(0,1,1000)
x_fine, y_fine = si.splev(u_fine, tck)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'o', x_fine, y_fine)
ax.axis('equal')
plt.show()
Which gives the following parameters:
>>> t
array([ 0. , 0. , 0. , 0. , 0.39084883,
0.5 , 0.60915117, 1. , 1. , 1. , 1. ])
>>> c_x
array([ -1.00000000e+00, -9.17992269e-01, -6.42403598e-01,
-2.57934892e-16, 6.42403598e-01, 9.17992269e-01,
1.00000000e+00])
>>> c_y
array([ -1.00000000e+00, -7.12577481e-01, -6.82922469e-03,
-1.00363771e-18, 6.82922469e-03, 7.12577481e-01,
1.00000000e+00])
>>> k
3
>>> u
array([ 0. , 0.25341516, 0.39084883, 0.5 , 0.60915117,
0.74658484, 1. ])
>>>
I've assumed that the two sets of coefficients (c_x, c_y) describe the (x,y) coordinates of poles needed to construct the spline. Trying this manually in NX gives a similar spline, though not quite the same, with other points in the interval being evaluated differently than in Python. When I export this manual spline to IGES format, NX changes the knots to the below (while obviously keeping the same control points/poles and setting all weights = 1).
t_nx = np.array([0.0, 0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0])
Going the other way and writing the splprep knots (t) into the IGES definition (with said 'control points' and weights = 1) does not seem to give a valid spline. NX and at least one other package cannot evaluate it, citing 'invalid trim or parametric values for B-spline curve'.
There seem to me to be at least three possibilities:
A non-trivial conversion is necessary to go from non-rational to rational B-splines
There is an application-specific interpretation of IGES splines (i.e. my interpretation of splprep output is correct, but this is simplified/approximated by NX when manually drawn/during the IGES conversion routine). Seems unlikely.
The coefficients from splprep cannot be interpreted as control points in the manner I've described
I had written off the first possibility by comparing the equations for a scipy B-spline (link) and an IGES NURBS spline with all weights = 1 (link, page 14). They look identical, and it was this that led me to believe splprep coefficients = control points.
Any help clarifying any of the above points would be very much appreciated!
NB, I would like the possibility of representing closed curves, so want to stick to splprep if possible.
EDIT:
I thought it would be simpler to try this process first using splrep, as the outputs seemed more intuitive to me. I assumed the coefficients returned were the y-values of the control points, but didn't know to what x position they corresponded. I therefore tried to calculate them from the spline definition and input data using this matrix approach. The C matrix is just the input data. The N matrix is the evaluation of each basis function for each x-value, I did this using the (slightly modified) recursive functions shown here. Then all that remains is to invert N, and pre-multiply C by it to get the control points. The code and result is below:
import numpy as np
import scipy.interpolate as si
# Functions to evaluate B-spline basis functions
def B(x, k, i, t):
if k == 0:
return 1.0 if t[i] <= x < t[i+1] else 0.0
if t[i+k] == t[i]:
c1 = 0.0
else:
c1 = (x - t[i])/(t[i+k] - t[i]) * B(x, k-1, i, t)
if t[i+k+1] == t[i+1]:
c2 = 0.0
else:
c2 = (t[i+k+1] - x)/(t[i+k+1] - t[i+1]) * B(x, k-1, i+1, t)
return c1 + c2
def bspline(x, t, c, k):
n = len(t) - k - 1
assert (n >= k+1) and (len(c) >= n)
cont = []
for i in range(n):
res = B(x, k, i, t)
cont.append(res)
return cont
# Input data
x = np.linspace(-1,1,7)
y = x**3
# B-spline definition
t, c, k = si.splrep(x,y)
# Number of knots = m + 1 = n + k + 2
m = len(t) - 1
# Number of kth degree basis fcns
n = m - k - 1
# Define C and initialise N matrix
C_mat = np.column_stack((x,y))
N_mat = np.zeros(((n+1),(n+1)))
# Calculate basis functions for each x, store in matrix
for i, xs in enumerate(x):
row = bspline(xs, t, c, k)
N_mat[i,:] = row
# Last value must be one...
N_mat[-1,-1] = 1.0
# Invert the matrix
N_inv = np.linalg.inv(N_mat)
# Now calculate control points
P = np.dot(N_inv, C_mat)
Resulting in:
>>> P
array([[ -1.00000000e+00, -1.00000000e+00],
[ -7.77777778e-01, -3.33333333e-01],
[ -4.44444444e-01, -3.29597460e-17],
[ -3.12250226e-17, 8.67361738e-18],
[ 4.44444444e-01, -2.77555756e-17],
[ 7.77777778e-01, 3.33333333e-01],
[ 1.00000000e+00, 1.00000000e+00]])
I think it's correct because the y-values of P match the coefficients from splrep, c. Interestingly the x-values seem to be the knot averages (which could be separately calculated as below). Perhaps this result is obvious to someone very familiar with the maths, it certainly wasn't to me.
def knot_average(knots, degree):
"""
Determines knot average vector from knot vector.
:knots: A 1D numpy array describing knots of B-spline.
(NB expected from scipy.interpolate.splrep)
:degree: Integer describing degree of B-spline basis fcns
"""
# Chop first and last vals off
knots_to_average = knots[1:-1]
num_averaged_knots = len(knots_to_average) - degree + 1
knot_averages = np.zeros((num_averaged_knots,))
for i in range(num_averaged_knots):
avg = np.average(knots_to_average[i: i + degree])
knot_averages[i] = avg
return(knot_averages)
Now, to convert these to IGES NURBS I thought it was a case of defining the normalised knot vector, setting the weights all equal to one, and including the P control points from above. I normalised it as below, and have included the IGES file below that.
However when I try to import the file into NX, it again fails stating invalid trim parameters in the definition. Can anyone tell me if this is a valid NURBS definition?
Or perhaps this is some limitation with NX? For instance, I noticed when interactively drawing studio splines the knot vector was forced to be (clamped) uniform (as alluded to by fang). This constraint (and weights all = 1) must be required to uniquely define the curve. Interestingly if I force splrep to return a spline representation using a uniform knot vector (that is, clamped but otherwise uniform), the IGES is read in. I shouldn't think this is necessary though from NXs point of view - it defeats the purpose of having a NURBS in the first place. So it doesn't seem likely and I loop round wondering if my interpretation of the output of splrep is correct...can someone please point out where I've gone wrong?
# Original knot vector
>>> t
array([-1. , -1. , -1. , -1. , -0.33333333,
0. , 0.33333333, 1. , 1. , 1. , 1. ])
mini = min(t)
maxi = max(t)
r = maxi - mini
norm_t = (t-mini)/r
# Giving:
>>> norm_t
array([ 0. , 0. , 0. , 0. , 0.33333333,
0.5 , 0.66666667, 1. , 1. , 1. , 1. ])
IGES definition:
S 1
,,11Hspline_test,13Hsome_path.igs,19HSpline to iges v1.0,4H 0.1,,,,,,, G 1
1.0, 2,2HMM,,,8H 8:58:19,,,,; G 2
126 1 1 1 0 0 0D 1
126 27 4 0 Spline1 1D 2
126,6,3,0,0,1,0,0.0,0.0,0.0,0.0,0.33333,0.5,0.6666666,1.0,1.0,1.0,1.0, 1P 1
1.0,1.0,1.0,1.0,1.0,1.0,1.0,-1.0,-1.0,0.0,-0.7777,-0.33333,0.0, 1P 2
-0.444444,0.0,0.0,0.0,0.0,0.0,0.4444444,0.0,0.0,0.777777777,0.33333, 1P 3
0.0,1.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0; 1P 4
S 1G 2D 2P 4 T 1
On the off chance this niche query helps anyone else- it turns out the problem was incorrect formatting of the parameter data section in the IGES. The data describing the spline can't take up > 64 characters per line. The interpretation of splprep output was correct, the (c_x, c_y) arrays describe the (x,y) coordinates of successive poles. The equivalent NURBS definition just requires specification of all weights = 1.
I am calculating the theta for my AI like so:
theta = opt.fmin_cg(cost, initial_theta, gradient, (newX, y))
Which works great and gives me this output:
Optimization terminated successfully.
Current function value: 0.684355
Iterations: 6
Function evaluations: 15
Gradient evaluations: 15
When I print theta, I get this:
[ 0. -0.28132729 0.158859 ]
I now want to plot this on my scatter graph as a line, my expected output looks like this:
But when I try to perform this on my graph with the algorithm:
weights * features = weight0 + weight1 * feature1 + weight2 * feature2
Like so:
x_axis = np.array([min(newX[:, 1]), max(newX[:, 1])])
y_axis = x_axis * theta[1:]
ax.plot(x_axis, y_axis, linewidth=2)
plt.show()
The output looks like this:
What should y_axis = x_axis * theta[1:] be to match the algorithm?
Update:
newX derives from my training data frame and is created like this:
newX = np.zeros(shape=(x.shape[0], x.shape[1] + 1))
newX[:, 1:] = x.values
It now looks like this, the concept is 0 is the free weight:
[[0. 8. 2.]
[0. 0. 0.]
[0. 0. 0.]
...
[0. 0. 0.]
[0. 0. 0.]
[0. 1. 1.]]
IIUC, you are trying to plot your decision boundary for the logistic regression. This is not simply a y = mx + b problem, well it is but you first need to determine where your decision boundary is, typically it's at probability of 0.5. I assume the model you are going with something looks like h(x) = g(theta_0*x_0 + theta_1*x_1 + theta_2*x_2), where g(x) = 1 / (1 + e^-x) and x_1 and x_2 are your features that you are plotting, ie your y and x axis (I don't know which is y and which is x since I don't know your data). So for probability 0.5, you want to solve for h(x) = 0.5, ie theta_0*x_0 + theta_1*x_1 + theta_2*x_2 = 0
So what you want to plot is the line 0 = theta_0*x_0 + theta_1*x_1 + theta_2*x_2. Let's just say you have x_1 on your x axis and x_2 on your y axis. (x_0 is just 1, corresponding to theta_0, your intercept.)
So you'll need to pick (somewhat arbitrarily) x_1 values that will give you a good illustration of the boundary line. Min/max of your dataset works, which you've done. Then solve for x_2 given the formula above. You can define a function here: lambda x_1: (theta[0] + theta[1] * x_1) / theta[2]. I'm assuming your theta variable corresponds to [intercept, coeff for x_1, coeff for x_2]. So you'll end up with something like:
theta = [0., -0.28132729, 0.158859]
x = np.array([0, 10])
f = lambda x_1: (theta[0] + theta[1] * x_1) / theta[2]
y = f(x)
plt.plot(x, y)