6

I have a 3x1 point vector representing the start point of some line, and a 3x1 point vector representing the end of some line. I would like to sample an arbitrary amount of points along the line connected by these two points.

np.linspace does exactly what I need but in 1 dimension. Is there a similar functionality that can be extended to 3 dimensions?

Thanks

Carpetfizz
  • 8,707
  • 22
  • 85
  • 146
  • How about a linear function to map the 1d array onto your 3d space - linear interpolation? – hpaulj Mar 15 '18 at 18:47
  • You could do something with `np.interp` or `np.repeat` + `np.cumsum` i.e. `np.repeat((b - a) / 10, [10, 10, 10]).reshape(3, -1)` But that might be a lot more work than just `itertools.starmap`. – Brad Solomon Mar 15 '18 at 19:07
  • i.e. `np.append(a[:, None], np.repeat((b - a) / 10, [10, 10, 10]).reshape(3, -1).cumsum(axis=1), axis=1)` – Brad Solomon Mar 15 '18 at 19:08
  • 1
    Does [this](https://stackoverflow.com/q/42617529/7207392) answer your question? Let me know whether you are ok with closing your question. – Paul Panzer Mar 15 '18 at 19:27
  • Possible duplicate of [How can I vectorize linspace in numpy](https://stackoverflow.com/questions/42617529/how-can-i-vectorize-linspace-in-numpy) – Brad Solomon Mar 15 '18 at 22:40

2 Answers2

6

My interpolation suggestion:

In [664]: p1=np.array([0,1,2])
In [665]: p2=np.array([10,9,8])
In [666]: l1 = np.linspace(0,1,11)
In [667]: l1
Out[667]: array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
In [668]: p1+(p2-p1)*l1[:,None]
Out[668]: 
array([[ 0. ,  1. ,  2. ],
       [ 1. ,  1.8,  2.6],
       [ 2. ,  2.6,  3.2],
       [ 3. ,  3.4,  3.8],
       [ 4. ,  4.2,  4.4],
       [ 5. ,  5. ,  5. ],
       [ 6. ,  5.8,  5.6],
       [ 7. ,  6.6,  6.2],
       [ 8. ,  7.4,  6.8],
       [ 9. ,  8.2,  7.4],
       [10. ,  9. ,  8. ]])

Equivalent with 3 linspace calls

In [671]: np.stack([np.linspace(i,j,11) for i,j in zip(p1,p2)],axis=1)
Out[671]: 
array([[ 0. ,  1. ,  2. ],
       [ 1. ,  1.8,  2.6],
       [ 2. ,  2.6,  3.2],
       [ 3. ,  3.4,  3.8],
       [ 4. ,  4.2,  4.4],
       [ 5. ,  5. ,  5. ],
       [ 6. ,  5.8,  5.6],
       [ 7. ,  6.6,  6.2],
       [ 8. ,  7.4,  6.8],
       [ 9. ,  8.2,  7.4],
       [10. ,  9. ,  8. ]])

A variation on this is:

np.c_[tuple(slice(i,j,11j) for i,j in zip(p1,p2))]

Really the same calculation, just different syntax.


outer can be used instead:

p1+np.outer(l1,(p2-p1))

But even that uses broadcasting. p1 is (3,) and the outer is (11,3), the result is (11,3).


@Brad's approach handles end points differently

In [686]: np.append(p1[:, None], np.repeat((p2 - p1) / 10, [10, 10, 10]).reshape
     ...: (3, -1).cumsum(axis=1), axis=1)
Out[686]: 
array([[ 0. ,  1. ,  2. ,  3. ,  4. ,  5. ,  6. ,  7. ,  8. ,  9. , 10. ],
       [ 1. ,  0.8,  1.6,  2.4,  3.2,  4. ,  4.8,  5.6,  6.4,  7.2,  8. ],
       [ 2. ,  0.6,  1.2,  1.8,  2.4,  3. ,  3.6,  4.2,  4.8,  5.4,  6. ]])
In [687]: _.shape
Out[687]: (3, 11)
hpaulj
  • 221,503
  • 14
  • 230
  • 353
1

Not sure if np.linspace has changed in the 4 years since this question was asked, but you can pass array-like values as start and stop, and the results are the same as hpaulj's answer.

Example (using random points):

import numpy as np

startpts = np.array([0, 0, 0])
endpts = np.array([12, 3, 8])

out = np.linspace(start=startpts, stop=endpts, num=10)

returns the same thing as:

out = startpts+(endpts-startpts)*np.linspace(0,1,10)[:,np.newaxis]

And it can also be expanded to take in multiple pairs of points:

startpts = np.array([[0, 0, 0],[1, 2, 0],[2,3,4]])
endpts = np.array([[12,3, 8],[13,5, 8],[14,4,5]])
out = np.linspace(start=startpts, stop=endpts, num=10, axis=1)
Breadman10
  • 81
  • 6