Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
O
opencv
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
opencv
Commits
fd619787
Commit
fd619787
authored
Mar 17, 2016
by
Vladislav Sovrasov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add goodfeatures python test and cleanup in test directory
parent
947307ba
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
37 additions
and
2406 deletions
+37
-2406
calchist.py
modules/python/test/calchist.py
+0
-53
camera_calibration.py
modules/python/test/camera_calibration.py
+0
-360
findstereocorrespondence.py
modules/python/test/findstereocorrespondence.py
+0
-25
goodfeatures.py
modules/python/test/goodfeatures.py
+0
-36
leak1.py
modules/python/test/leak1.py
+0
-9
leak2.py
modules/python/test/leak2.py
+0
-12
leak3.py
modules/python/test/leak3.py
+0
-8
leak4.py
modules/python/test/leak4.py
+0
-11
precornerdetect.py
modules/python/test/precornerdetect.py
+0
-16
test_goodfeatures.py
modules/python/test/test_goodfeatures.py
+37
-0
ticket_6.py
modules/python/test/ticket_6.py
+0
-78
tickets.py
modules/python/test/tickets.py
+0
-91
transformations.py
modules/python/test/transformations.py
+0
-1707
No files found.
modules/python/test/calchist.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
# Calculating and displaying 2D Hue-Saturation histogram of a color image
import
sys
import
cv2.cv
as
cv
def
hs_histogram
(
src
):
# Convert to HSV
hsv
=
cv
.
CreateImage
(
cv
.
GetSize
(
src
),
8
,
3
)
cv
.
CvtColor
(
src
,
hsv
,
cv
.
CV_BGR2HSV
)
# Extract the H and S planes
h_plane
=
cv
.
CreateMat
(
src
.
rows
,
src
.
cols
,
cv
.
CV_8UC1
)
s_plane
=
cv
.
CreateMat
(
src
.
rows
,
src
.
cols
,
cv
.
CV_8UC1
)
cv
.
Split
(
hsv
,
h_plane
,
s_plane
,
None
,
None
)
planes
=
[
h_plane
,
s_plane
]
h_bins
=
30
s_bins
=
32
hist_size
=
[
h_bins
,
s_bins
]
# hue varies from 0 (~0 deg red) to 180 (~360 deg red again */
h_ranges
=
[
0
,
180
]
# saturation varies from 0 (black-gray-white) to
# 255 (pure spectrum color)
s_ranges
=
[
0
,
255
]
ranges
=
[
h_ranges
,
s_ranges
]
scale
=
10
hist
=
cv
.
CreateHist
([
h_bins
,
s_bins
],
cv
.
CV_HIST_ARRAY
,
ranges
,
1
)
cv
.
CalcHist
([
cv
.
GetImage
(
i
)
for
i
in
planes
],
hist
)
(
_
,
max_value
,
_
,
_
)
=
cv
.
GetMinMaxHistValue
(
hist
)
hist_img
=
cv
.
CreateImage
((
h_bins
*
scale
,
s_bins
*
scale
),
8
,
3
)
for
h
in
range
(
h_bins
):
for
s
in
range
(
s_bins
):
bin_val
=
cv
.
QueryHistValue_2D
(
hist
,
h
,
s
)
intensity
=
cv
.
Round
(
bin_val
*
255
/
max_value
)
cv
.
Rectangle
(
hist_img
,
(
h
*
scale
,
s
*
scale
),
((
h
+
1
)
*
scale
-
1
,
(
s
+
1
)
*
scale
-
1
),
cv
.
RGB
(
intensity
,
intensity
,
intensity
),
cv
.
CV_FILLED
)
return
hist_img
if
__name__
==
'__main__'
:
src
=
cv
.
LoadImageM
(
sys
.
argv
[
1
])
cv
.
NamedWindow
(
"Source"
,
1
)
cv
.
ShowImage
(
"Source"
,
src
)
cv
.
NamedWindow
(
"H-S Histogram"
,
1
)
cv
.
ShowImage
(
"H-S Histogram"
,
hs_histogram
(
src
))
cv
.
WaitKey
(
0
)
modules/python/test/camera_calibration.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
sys
import
math
import
time
import
random
import
numpy
import
transformations
import
cv2.cv
as
cv
def
clamp
(
a
,
x
,
b
):
return
numpy
.
maximum
(
a
,
numpy
.
minimum
(
x
,
b
))
def
norm
(
v
):
mag
=
numpy
.
sqrt
(
sum
([
e
*
e
for
e
in
v
]))
return
v
/
mag
class
Vec3
:
def
__init__
(
self
,
x
,
y
,
z
):
self
.
v
=
(
x
,
y
,
z
)
def
x
(
self
):
return
self
.
v
[
0
]
def
y
(
self
):
return
self
.
v
[
1
]
def
z
(
self
):
return
self
.
v
[
2
]
def
__repr__
(
self
):
return
"<Vec3 (
%
s,
%
s,
%
s)>"
%
tuple
([
repr
(
c
)
for
c
in
self
.
v
])
def
__add__
(
self
,
other
):
return
Vec3
(
*
[
self
.
v
[
i
]
+
other
.
v
[
i
]
for
i
in
range
(
3
)])
def
__sub__
(
self
,
other
):
return
Vec3
(
*
[
self
.
v
[
i
]
-
other
.
v
[
i
]
for
i
in
range
(
3
)])
def
__mul__
(
self
,
other
):
if
isinstance
(
other
,
Vec3
):
return
Vec3
(
*
[
self
.
v
[
i
]
*
other
.
v
[
i
]
for
i
in
range
(
3
)])
else
:
return
Vec3
(
*
[
self
.
v
[
i
]
*
other
for
i
in
range
(
3
)])
def
mag2
(
self
):
return
sum
([
e
*
e
for
e
in
self
.
v
])
def
__abs__
(
self
):
return
numpy
.
sqrt
(
sum
([
e
*
e
for
e
in
self
.
v
]))
def
norm
(
self
):
return
self
*
(
1.0
/
abs
(
self
))
def
dot
(
self
,
other
):
return
sum
([
self
.
v
[
i
]
*
other
.
v
[
i
]
for
i
in
range
(
3
)])
def
cross
(
self
,
other
):
(
ax
,
ay
,
az
)
=
self
.
v
(
bx
,
by
,
bz
)
=
other
.
v
return
Vec3
(
ay
*
bz
-
by
*
az
,
az
*
bx
-
bz
*
ax
,
ax
*
by
-
bx
*
ay
)
class
Ray
:
def
__init__
(
self
,
o
,
d
):
self
.
o
=
o
self
.
d
=
d
def
project
(
self
,
d
):
return
self
.
o
+
self
.
d
*
d
class
Camera
:
def
__init__
(
self
,
F
):
R
=
Vec3
(
1.
,
0.
,
0.
)
U
=
Vec3
(
0
,
1.
,
0
)
self
.
center
=
Vec3
(
0
,
0
,
0
)
self
.
pcenter
=
Vec3
(
0
,
0
,
F
)
self
.
up
=
U
self
.
right
=
R
def
genray
(
self
,
x
,
y
):
""" -1 <= y <= 1 """
r
=
numpy
.
sqrt
(
x
*
x
+
y
*
y
)
if
0
:
rprime
=
r
+
(
0.17
*
r
**
2
)
else
:
rprime
=
(
10
*
numpy
.
sqrt
(
17
*
r
+
25
)
-
50
)
/
17
print
"scale"
,
rprime
/
r
x
*=
rprime
/
r
y
*=
rprime
/
r
o
=
self
.
center
r
=
(
self
.
pcenter
+
(
self
.
right
*
x
)
+
(
self
.
up
*
y
))
-
o
return
Ray
(
o
,
r
.
norm
())
class
Sphere
:
def
__init__
(
self
,
center
,
radius
):
self
.
center
=
center
self
.
radius
=
radius
def
hit
(
self
,
r
):
# a = mag2(r.d)
a
=
1.
v
=
r
.
o
-
self
.
center
b
=
2
*
r
.
d
.
dot
(
v
)
c
=
self
.
center
.
mag2
()
+
r
.
o
.
mag2
()
+
-
2
*
self
.
center
.
dot
(
r
.
o
)
-
(
self
.
radius
**
2
)
det
=
(
b
*
b
)
-
(
4
*
c
)
pred
=
0
<
det
sq
=
numpy
.
sqrt
(
abs
(
det
))
h0
=
(
-
b
-
sq
)
/
(
2
)
h1
=
(
-
b
+
sq
)
/
(
2
)
h
=
numpy
.
minimum
(
h0
,
h1
)
pred
=
pred
&
(
h
>
0
)
normal
=
(
r
.
project
(
h
)
-
self
.
center
)
*
(
1.0
/
self
.
radius
)
return
(
pred
,
numpy
.
where
(
pred
,
h
,
999999.
),
normal
)
def
pt2plane
(
p
,
plane
):
return
p
.
dot
(
plane
)
*
(
1.
/
abs
(
plane
))
class
Plane
:
def
__init__
(
self
,
p
,
n
,
right
):
self
.
D
=
-
pt2plane
(
p
,
n
)
self
.
Pn
=
n
self
.
right
=
right
self
.
rightD
=
-
pt2plane
(
p
,
right
)
self
.
up
=
n
.
cross
(
right
)
self
.
upD
=
-
pt2plane
(
p
,
self
.
up
)
def
hit
(
self
,
r
):
Vd
=
self
.
Pn
.
dot
(
r
.
d
)
V0
=
-
(
self
.
Pn
.
dot
(
r
.
o
)
+
self
.
D
)
h
=
V0
/
Vd
pred
=
(
0
<=
h
)
return
(
pred
,
numpy
.
where
(
pred
,
h
,
999999.
),
self
.
Pn
)
def
localxy
(
self
,
loc
):
x
=
(
loc
.
dot
(
self
.
right
)
+
self
.
rightD
)
y
=
(
loc
.
dot
(
self
.
up
)
+
self
.
upD
)
return
(
x
,
y
)
# lena = numpy.fromstring(cv.LoadImage("../samples/c/lena.jpg", 0).tostring(), numpy.uint8) / 255.0
def
texture
(
xy
):
x
,
y
=
xy
xa
=
numpy
.
floor
(
x
*
512
)
ya
=
numpy
.
floor
(
y
*
512
)
a
=
(
512
*
ya
)
+
xa
safe
=
(
0
<=
x
)
&
(
0
<=
y
)
&
(
x
<
1
)
&
(
y
<
1
)
if
0
:
a
=
numpy
.
where
(
safe
,
a
,
0
)
.
astype
(
numpy
.
int
)
return
numpy
.
where
(
safe
,
numpy
.
take
(
lena
,
a
),
0.0
)
else
:
xi
=
numpy
.
floor
(
x
*
11
)
.
astype
(
numpy
.
int
)
yi
=
numpy
.
floor
(
y
*
11
)
.
astype
(
numpy
.
int
)
inside
=
(
1
<=
xi
)
&
(
xi
<
10
)
&
(
2
<=
yi
)
&
(
yi
<
9
)
checker
=
(
xi
&
1
)
^
(
yi
&
1
)
final
=
numpy
.
where
(
inside
,
checker
,
1.0
)
return
numpy
.
where
(
safe
,
final
,
0.5
)
def
under
(
vv
,
m
):
return
Vec3
(
*
(
numpy
.
dot
(
m
,
vv
.
v
+
(
1
,))[:
3
]))
class
Renderer
:
def
__init__
(
self
,
w
,
h
,
oversample
):
self
.
w
=
w
self
.
h
=
h
random
.
seed
(
1
)
x
=
numpy
.
arange
(
self
.
w
*
self
.
h
)
%
self
.
w
y
=
numpy
.
floor
(
numpy
.
arange
(
self
.
w
*
self
.
h
)
/
self
.
w
)
h2
=
h
/
2.0
w2
=
w
/
2.0
self
.
r
=
[
None
]
*
oversample
for
o
in
range
(
oversample
):
stoch_x
=
numpy
.
random
.
rand
(
self
.
w
*
self
.
h
)
stoch_y
=
numpy
.
random
.
rand
(
self
.
w
*
self
.
h
)
nx
=
(
x
+
stoch_x
-
0.5
-
w2
)
/
h2
ny
=
(
y
+
stoch_y
-
0.5
-
h2
)
/
h2
self
.
r
[
o
]
=
cam
.
genray
(
nx
,
ny
)
self
.
rnds
=
[
random
.
random
()
for
i
in
range
(
10
)]
def
frame
(
self
,
i
):
rnds
=
self
.
rnds
roll
=
math
.
sin
(
i
*
.
01
*
rnds
[
0
]
+
rnds
[
1
])
pitch
=
math
.
sin
(
i
*
.
01
*
rnds
[
2
]
+
rnds
[
3
])
yaw
=
math
.
pi
*
math
.
sin
(
i
*
.
01
*
rnds
[
4
]
+
rnds
[
5
])
x
=
math
.
sin
(
i
*
0.01
*
rnds
[
6
])
y
=
math
.
sin
(
i
*
0.01
*
rnds
[
7
])
x
,
y
,
z
=
-
0.5
,
0.5
,
1
roll
,
pitch
,
yaw
=
(
0
,
0
,
0
)
z
=
4
+
3
*
math
.
sin
(
i
*
0.1
*
rnds
[
8
])
print
z
rz
=
transformations
.
euler_matrix
(
roll
,
pitch
,
yaw
)
p
=
Plane
(
Vec3
(
x
,
y
,
z
),
under
(
Vec3
(
0
,
0
,
-
1
),
rz
),
under
(
Vec3
(
1
,
0
,
0
),
rz
))
acc
=
0
for
r
in
self
.
r
:
(
pred
,
h
,
norm
)
=
p
.
hit
(
r
)
l
=
numpy
.
where
(
pred
,
texture
(
p
.
localxy
(
r
.
project
(
h
))),
0.0
)
acc
+=
l
acc
*=
(
1.0
/
len
(
self
.
r
))
# print "took", time.time() - st
img
=
cv
.
CreateMat
(
self
.
h
,
self
.
w
,
cv
.
CV_8UC1
)
cv
.
SetData
(
img
,
(
clamp
(
0
,
acc
,
1
)
*
255
)
.
astype
(
numpy
.
uint8
)
.
tostring
(),
self
.
w
)
return
img
#########################################################################
num_x_ints
=
8
num_y_ints
=
6
num_pts
=
num_x_ints
*
num_y_ints
def
get_corners
(
mono
,
refine
=
False
):
(
ok
,
corners
)
=
cv
.
FindChessboardCorners
(
mono
,
(
num_x_ints
,
num_y_ints
),
cv
.
CV_CALIB_CB_ADAPTIVE_THRESH
|
cv
.
CV_CALIB_CB_NORMALIZE_IMAGE
)
if
refine
and
ok
:
corners
=
cv
.
FindCornerSubPix
(
mono
,
corners
,
(
5
,
5
),
(
-
1
,
-
1
),
(
cv
.
CV_TERMCRIT_EPS
+
cv
.
CV_TERMCRIT_ITER
,
30
,
0.1
))
return
(
ok
,
corners
)
def
mk_object_points
(
nimages
,
squaresize
=
1
):
opts
=
cv
.
CreateMat
(
nimages
*
num_pts
,
3
,
cv
.
CV_32FC1
)
for
i
in
range
(
nimages
):
for
j
in
range
(
num_pts
):
opts
[
i
*
num_pts
+
j
,
0
]
=
(
j
/
num_x_ints
)
*
squaresize
opts
[
i
*
num_pts
+
j
,
1
]
=
(
j
%
num_x_ints
)
*
squaresize
opts
[
i
*
num_pts
+
j
,
2
]
=
0
return
opts
def
mk_image_points
(
goodcorners
):
ipts
=
cv
.
CreateMat
(
len
(
goodcorners
)
*
num_pts
,
2
,
cv
.
CV_32FC1
)
for
(
i
,
co
)
in
enumerate
(
goodcorners
):
for
j
in
range
(
num_pts
):
ipts
[
i
*
num_pts
+
j
,
0
]
=
co
[
j
][
0
]
ipts
[
i
*
num_pts
+
j
,
1
]
=
co
[
j
][
1
]
return
ipts
def
mk_point_counts
(
nimages
):
npts
=
cv
.
CreateMat
(
nimages
,
1
,
cv
.
CV_32SC1
)
for
i
in
range
(
nimages
):
npts
[
i
,
0
]
=
num_pts
return
npts
def
cvmat_iterator
(
cvmat
):
for
i
in
range
(
cvmat
.
rows
):
for
j
in
range
(
cvmat
.
cols
):
yield
cvmat
[
i
,
j
]
cam
=
Camera
(
3.0
)
rend
=
Renderer
(
640
,
480
,
2
)
cv
.
NamedWindow
(
"snap"
)
#images = [rend.frame(i) for i in range(0, 2000, 400)]
images
=
[
rend
.
frame
(
i
)
for
i
in
[
1200
]]
if
0
:
for
i
,
img
in
enumerate
(
images
):
cv
.
SaveImage
(
"final/
%06
d.png"
%
i
,
img
)
size
=
cv
.
GetSize
(
images
[
0
])
corners
=
[
get_corners
(
i
)
for
i
in
images
]
goodcorners
=
[
co
for
(
im
,
(
ok
,
co
))
in
zip
(
images
,
corners
)
if
ok
]
def
checkerboard_error
(
xformed
):
def
pt2line
(
a
,
b
,
c
):
x0
,
y0
=
a
x1
,
y1
=
b
x2
,
y2
=
c
return
abs
((
x2
-
x1
)
*
(
y1
-
y0
)
-
(
x1
-
x0
)
*
(
y2
-
y1
))
/
math
.
sqrt
((
x2
-
x1
)
**
2
+
(
y2
-
y1
)
**
2
)
errorsum
=
0.
for
im
in
xformed
:
for
row
in
range
(
6
):
l0
=
im
[
8
*
row
]
l1
=
im
[
8
*
row
+
7
]
for
col
in
range
(
1
,
7
):
e
=
pt2line
(
im
[
8
*
row
+
col
],
l0
,
l1
)
#print "row", row, "e", e
errorsum
+=
e
return
errorsum
if
True
:
from
scipy.optimize
import
fmin
def
xf
(
pt
,
poly
):
x
,
y
=
pt
r
=
math
.
sqrt
((
x
-
320
)
**
2
+
(
y
-
240
)
**
2
)
fr
=
poly
(
r
)
/
r
return
(
320
+
(
x
-
320
)
*
fr
,
240
+
(
y
-
240
)
*
fr
)
def
silly
(
p
,
goodcorners
):
# print "eval", p
d
=
1.0
# - sum(p)
poly
=
numpy
.
poly1d
(
list
(
p
)
+
[
d
,
0.
])
xformed
=
[[
xf
(
pt
,
poly
)
for
pt
in
co
]
for
co
in
goodcorners
]
return
checkerboard_error
(
xformed
)
x0
=
[
0.
]
#print silly(x0, goodcorners)
print
"initial error"
,
silly
(
x0
,
goodcorners
)
xopt
=
fmin
(
silly
,
x0
,
args
=
(
goodcorners
,))
print
"xopt"
,
xopt
print
"final error"
,
silly
(
xopt
,
goodcorners
)
d
=
1.0
# - sum(xopt)
poly
=
numpy
.
poly1d
(
list
(
xopt
)
+
[
d
,
0.
])
print
"final polynomial"
print
poly
for
co
in
goodcorners
:
scrib
=
cv
.
CreateMat
(
480
,
640
,
cv
.
CV_8UC3
)
cv
.
SetZero
(
scrib
)
cv
.
DrawChessboardCorners
(
scrib
,
(
num_x_ints
,
num_y_ints
),
[
xf
(
pt
,
poly
)
for
pt
in
co
],
True
)
cv
.
ShowImage
(
"snap"
,
scrib
)
cv
.
WaitKey
()
sys
.
exit
(
0
)
for
(
i
,
(
img
,
(
ok
,
co
)))
in
enumerate
(
zip
(
images
,
corners
)):
scrib
=
cv
.
CreateMat
(
img
.
rows
,
img
.
cols
,
cv
.
CV_8UC3
)
cv
.
CvtColor
(
img
,
scrib
,
cv
.
CV_GRAY2BGR
)
if
ok
:
cv
.
DrawChessboardCorners
(
scrib
,
(
num_x_ints
,
num_y_ints
),
co
,
True
)
cv
.
ShowImage
(
"snap"
,
scrib
)
cv
.
WaitKey
()
print
len
(
goodcorners
)
ipts
=
mk_image_points
(
goodcorners
)
opts
=
mk_object_points
(
len
(
goodcorners
),
.
1
)
npts
=
mk_point_counts
(
len
(
goodcorners
))
intrinsics
=
cv
.
CreateMat
(
3
,
3
,
cv
.
CV_64FC1
)
distortion
=
cv
.
CreateMat
(
4
,
1
,
cv
.
CV_64FC1
)
cv
.
SetZero
(
intrinsics
)
cv
.
SetZero
(
distortion
)
# focal lengths have 1/1 ratio
intrinsics
[
0
,
0
]
=
1.0
intrinsics
[
1
,
1
]
=
1.0
cv
.
CalibrateCamera2
(
opts
,
ipts
,
npts
,
cv
.
GetSize
(
images
[
0
]),
intrinsics
,
distortion
,
cv
.
CreateMat
(
len
(
goodcorners
),
3
,
cv
.
CV_32FC1
),
cv
.
CreateMat
(
len
(
goodcorners
),
3
,
cv
.
CV_32FC1
),
flags
=
0
)
# cv.CV_CALIB_ZERO_TANGENT_DIST)
print
"D ="
,
list
(
cvmat_iterator
(
distortion
))
print
"K ="
,
list
(
cvmat_iterator
(
intrinsics
))
mapx
=
cv
.
CreateImage
((
640
,
480
),
cv
.
IPL_DEPTH_32F
,
1
)
mapy
=
cv
.
CreateImage
((
640
,
480
),
cv
.
IPL_DEPTH_32F
,
1
)
cv
.
InitUndistortMap
(
intrinsics
,
distortion
,
mapx
,
mapy
)
for
img
in
images
:
r
=
cv
.
CloneMat
(
img
)
cv
.
Remap
(
img
,
r
,
mapx
,
mapy
)
cv
.
ShowImage
(
"snap"
,
r
)
cv
.
WaitKey
()
modules/python/test/findstereocorrespondence.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
sys
import
cv2.cv
as
cv
def
findstereocorrespondence
(
image_left
,
image_right
):
# image_left and image_right are the input 8-bit single-channel images
# from the left and the right cameras, respectively
(
r
,
c
)
=
(
image_left
.
rows
,
image_left
.
cols
)
disparity_left
=
cv
.
CreateMat
(
r
,
c
,
cv
.
CV_16S
)
disparity_right
=
cv
.
CreateMat
(
r
,
c
,
cv
.
CV_16S
)
state
=
cv
.
CreateStereoGCState
(
16
,
2
)
cv
.
FindStereoCorrespondenceGC
(
image_left
,
image_right
,
disparity_left
,
disparity_right
,
state
,
0
)
return
(
disparity_left
,
disparity_right
)
if
__name__
==
'__main__'
:
(
l
,
r
)
=
[
cv
.
LoadImageM
(
f
,
cv
.
CV_LOAD_IMAGE_GRAYSCALE
)
for
f
in
sys
.
argv
[
1
:]]
(
disparity_left
,
disparity_right
)
=
findstereocorrespondence
(
l
,
r
)
disparity_left_visual
=
cv
.
CreateMat
(
l
.
rows
,
l
.
cols
,
cv
.
CV_8U
)
cv
.
ConvertScale
(
disparity_left
,
disparity_left_visual
,
-
16
)
cv
.
SaveImage
(
"disparity.pgm"
,
disparity_left_visual
)
modules/python/test/goodfeatures.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
import
unittest
class
TestGoodFeaturesToTrack
(
unittest
.
TestCase
):
def
test
(
self
):
arr
=
cv
.
LoadImage
(
"../samples/c/lena.jpg"
,
0
)
original
=
cv
.
CloneImage
(
arr
)
size
=
cv
.
GetSize
(
arr
)
eig_image
=
cv
.
CreateImage
(
size
,
cv
.
IPL_DEPTH_32F
,
1
)
temp_image
=
cv
.
CreateImage
(
size
,
cv
.
IPL_DEPTH_32F
,
1
)
threshes
=
[
x
/
100.
for
x
in
range
(
1
,
10
)
]
results
=
dict
([(
t
,
cv
.
GoodFeaturesToTrack
(
arr
,
eig_image
,
temp_image
,
20000
,
t
,
2
,
useHarris
=
1
))
for
t
in
threshes
])
# Check that GoodFeaturesToTrack has not modified input image
self
.
assert_
(
arr
.
tostring
()
==
original
.
tostring
())
# Check for repeatability
for
i
in
range
(
10
):
results2
=
dict
([(
t
,
cv
.
GoodFeaturesToTrack
(
arr
,
eig_image
,
temp_image
,
20000
,
t
,
2
,
useHarris
=
1
))
for
t
in
threshes
])
self
.
assert_
(
results
==
results2
)
for
t0
,
t1
in
zip
(
threshes
,
threshes
[
1
:]):
r0
=
results
[
t0
]
r1
=
results
[
t1
]
# Increasing thresh should make result list shorter
self
.
assert_
(
len
(
r0
)
>
len
(
r1
))
# Increasing thresh should monly truncate result list
self
.
assert_
(
r0
[:
len
(
r1
)]
==
r1
)
if
__name__
==
'__main__'
:
unittest
.
main
()
modules/python/test/leak1.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
import
numpy
as
np
cv
.
NamedWindow
(
'Leak'
)
while
1
:
leak
=
np
.
random
.
random
((
480
,
640
))
*
255
cv
.
ShowImage
(
'Leak'
,
leak
.
astype
(
np
.
uint8
))
cv
.
WaitKey
(
10
)
modules/python/test/leak2.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
import
numpy
as
np
import
time
while
True
:
for
i
in
range
(
4000
):
a
=
cv
.
CreateImage
((
1024
,
1024
),
cv
.
IPL_DEPTH_8U
,
1
)
b
=
cv
.
CreateMat
(
1024
,
1024
,
cv
.
CV_8UC1
)
c
=
cv
.
CreateMatND
([
1024
,
1024
],
cv
.
CV_8UC1
)
print
"pause..."
modules/python/test/leak3.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
import
math
import
time
while
True
:
h
=
cv
.
CreateHist
([
40
],
cv
.
CV_HIST_ARRAY
,
[[
0
,
255
]],
1
)
modules/python/test/leak4.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
import
math
import
time
N
=
50000
print
"leak4"
while
True
:
seq
=
list
((
i
*
1.
,
i
*
1.
)
for
i
in
range
(
N
))
cv
.
Moments
(
seq
)
modules/python/test/precornerdetect.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
cv2.cv
as
cv
def
precornerdetect
(
image
):
# assume that the image is floating-point
corners
=
cv
.
CloneMat
(
image
)
cv
.
PreCornerDetect
(
image
,
corners
,
3
)
dilated_corners
=
cv
.
CloneMat
(
image
)
cv
.
Dilate
(
corners
,
dilated_corners
,
None
,
1
)
corner_mask
=
cv
.
CreateMat
(
image
.
rows
,
image
.
cols
,
cv
.
CV_8UC1
)
cv
.
Sub
(
corners
,
dilated_corners
,
corners
)
cv
.
CmpS
(
corners
,
0
,
corner_mask
,
cv
.
CV_CMP_GE
)
return
(
corners
,
corner_mask
)
modules/python/test/test_goodfeatures.py
0 → 100644
View file @
fd619787
#!/usr/bin/env python
# Python 2/3 compatibility
from
__future__
import
print_function
import
cv2
import
numpy
as
np
from
tests_common
import
NewOpenCVTests
class
TestGoodFeaturesToTrack_test
(
NewOpenCVTests
):
def
test_goodFeaturesToTrack
(
self
):
arr
=
self
.
get_sample
(
'samples/data/lena.jpg'
,
0
)
original
=
arr
.
copy
(
True
)
threshes
=
[
x
/
100.
for
x
in
range
(
1
,
10
)
]
numPoints
=
20000
results
=
dict
([(
t
,
cv2
.
goodFeaturesToTrack
(
arr
,
numPoints
,
t
,
2
,
useHarrisDetector
=
True
))
for
t
in
threshes
])
# Check that GoodFeaturesToTrack has not modified input image
self
.
assertTrue
(
arr
.
tostring
()
==
original
.
tostring
())
# Check for repeatability
for
i
in
range
(
1
):
results2
=
dict
([(
t
,
cv2
.
goodFeaturesToTrack
(
arr
,
numPoints
,
t
,
2
,
useHarrisDetector
=
True
))
for
t
in
threshes
])
for
t
in
threshes
:
self
.
assertTrue
(
len
(
results2
[
t
])
==
len
(
results
[
t
]))
for
i
in
range
(
len
(
results
[
t
])):
self
.
assertTrue
(
cv2
.
norm
(
results
[
t
][
i
][
0
]
-
results2
[
t
][
i
][
0
])
==
0
)
for
t0
,
t1
in
zip
(
threshes
,
threshes
[
1
:]):
r0
=
results
[
t0
]
r1
=
results
[
t1
]
# Increasing thresh should make result list shorter
self
.
assertTrue
(
len
(
r0
)
>
len
(
r1
))
# Increasing thresh should monly truncate result list
for
i
in
range
(
len
(
r1
)):
self
.
assertTrue
(
cv2
.
norm
(
r1
[
i
][
0
]
-
r0
[
i
][
0
])
==
0
)
\ No newline at end of file
modules/python/test/ticket_6.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
urllib
import
cv2.cv
as
cv
import
Image
import
unittest
class
TestLoadImage
(
unittest
.
TestCase
):
def
setUp
(
self
):
open
(
"large.jpg"
,
"w"
)
.
write
(
urllib
.
urlopen
(
"http://www.cs.ubc.ca/labs/lci/curious_george/img/ROS_bug_imgs/IMG_3560.jpg"
)
.
read
())
def
test_load
(
self
):
pilim
=
Image
.
open
(
"large.jpg"
)
cvim
=
cv
.
LoadImage
(
"large.jpg"
)
self
.
assert_
(
len
(
pilim
.
tostring
())
==
len
(
cvim
.
tostring
()))
class
Creating
(
unittest
.
TestCase
):
size
=
(
640
,
480
)
repeat
=
100
def
test_0_Create
(
self
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cnt
=
cv
.
CountNonZero
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Created image is not black. CountNonZero=
%
i"
%
cnt
)
def
test_2_CreateRepeat
(
self
):
cnt
=
0
for
i
in
range
(
self
.
repeat
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cnt
+=
cv
.
CountNonZero
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Created images are not black. Mean CountNonZero=
%.3
f"
%
(
1.
*
cnt
/
self
.
repeat
))
def
test_2a_MemCreated
(
self
):
cnt
=
0
v
=
[]
for
i
in
range
(
self
.
repeat
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cv
.
FillPoly
(
image
,
[[(
0
,
0
),
(
0
,
100
),
(
100
,
0
)]],
0
)
cnt
+=
cv
.
CountNonZero
(
image
)
v
.
append
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Memorized images are not black. Mean CountNonZero=
%.3
f"
%
(
1.
*
cnt
/
self
.
repeat
))
def
test_3_tostirng
(
self
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
image
.
tostring
()
cnt
=
cv
.
CountNonZero
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"After tostring(): CountNonZero=
%
i"
%
cnt
)
def
test_40_tostringRepeat
(
self
):
cnt
=
0
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cv
.
Set
(
image
,
cv
.
Scalar
(
0
,
0
,
0
,
0
))
for
i
in
range
(
self
.
repeat
*
100
):
image
.
tostring
()
cnt
=
cv
.
CountNonZero
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Repeating tostring(): Mean CountNonZero=
%.3
f"
%
(
1.
*
cnt
/
self
.
repeat
))
def
test_41_CreateToStringRepeat
(
self
):
cnt
=
0
for
i
in
range
(
self
.
repeat
*
100
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cv
.
Set
(
image
,
cv
.
Scalar
(
0
,
0
,
0
,
0
))
image
.
tostring
()
cnt
+=
cv
.
CountNonZero
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Repeating create and tostring(): Mean CountNonZero=
%.3
f"
%
(
1.
*
cnt
/
self
.
repeat
))
def
test_4a_MemCreatedToString
(
self
):
cnt
=
0
v
=
[]
for
i
in
range
(
self
.
repeat
):
image
=
cv
.
CreateImage
(
self
.
size
,
cv
.
IPL_DEPTH_8U
,
1
)
cv
.
Set
(
image
,
cv
.
Scalar
(
0
,
0
,
0
,
0
))
image
.
tostring
()
cnt
+=
cv
.
CountNonZero
(
image
)
v
.
append
(
image
)
self
.
assertEqual
(
cnt
,
0
,
msg
=
"Repeating and memorizing after tostring(): Mean CountNonZero=
%.3
f"
%
(
1.
*
cnt
/
self
.
repeat
))
if
__name__
==
'__main__'
:
unittest
.
main
()
modules/python/test/tickets.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
import
unittest
import
random
import
time
import
math
import
sys
import
array
import
os
import
cv2.cv
as
cv
def
find_sample
(
s
):
for
d
in
[
"../samples/c/"
,
"../doc/pics/"
]:
path
=
os
.
path
.
join
(
d
,
s
)
if
os
.
access
(
path
,
os
.
R_OK
):
return
path
return
s
class
TestTickets
(
unittest
.
TestCase
):
def
test_2542670
(
self
):
xys
=
[(
94
,
121
),
(
94
,
122
),
(
93
,
123
),
(
92
,
123
),
(
91
,
124
),
(
91
,
125
),
(
91
,
126
),
(
92
,
127
),
(
92
,
128
),
(
92
,
129
),
(
92
,
130
),
(
92
,
131
),
(
91
,
132
),
(
90
,
131
),
(
90
,
130
),
(
90
,
131
),
(
91
,
132
),
(
92
,
133
),
(
92
,
134
),
(
93
,
135
),
(
94
,
136
),
(
94
,
137
),
(
94
,
138
),
(
95
,
139
),
(
96
,
140
),
(
96
,
141
),
(
96
,
142
),
(
96
,
143
),
(
97
,
144
),
(
97
,
145
),
(
98
,
146
),
(
99
,
146
),
(
100
,
146
),
(
101
,
146
),
(
102
,
146
),
(
103
,
146
),
(
104
,
146
),
(
105
,
146
),
(
106
,
146
),
(
107
,
146
),
(
108
,
146
),
(
109
,
146
),
(
110
,
146
),
(
111
,
146
),
(
112
,
146
),
(
113
,
146
),
(
114
,
146
),
(
115
,
146
),
(
116
,
146
),
(
117
,
146
),
(
118
,
146
),
(
119
,
146
),
(
120
,
146
),
(
121
,
146
),
(
122
,
146
),
(
123
,
146
),
(
124
,
146
),
(
125
,
146
),
(
126
,
146
),
(
126
,
145
),
(
126
,
144
),
(
126
,
143
),
(
126
,
142
),
(
126
,
141
),
(
126
,
140
),
(
127
,
139
),
(
127
,
138
),
(
127
,
137
),
(
127
,
136
),
(
127
,
135
),
(
127
,
134
),
(
127
,
133
),
(
128
,
132
),
(
129
,
132
),
(
130
,
131
),
(
131
,
130
),
(
131
,
129
),
(
131
,
128
),
(
132
,
127
),
(
133
,
126
),
(
134
,
125
),
(
134
,
124
),
(
135
,
123
),
(
136
,
122
),
(
136
,
121
),
(
135
,
121
),
(
134
,
121
),
(
133
,
121
),
(
132
,
121
),
(
131
,
121
),
(
130
,
121
),
(
129
,
121
),
(
128
,
121
),
(
127
,
121
),
(
126
,
121
),
(
125
,
121
),
(
124
,
121
),
(
123
,
121
),
(
122
,
121
),
(
121
,
121
),
(
120
,
121
),
(
119
,
121
),
(
118
,
121
),
(
117
,
121
),
(
116
,
121
),
(
115
,
121
),
(
114
,
121
),
(
113
,
121
),
(
112
,
121
),
(
111
,
121
),
(
110
,
121
),
(
109
,
121
),
(
108
,
121
),
(
107
,
121
),
(
106
,
121
),
(
105
,
121
),
(
104
,
121
),
(
103
,
121
),
(
102
,
121
),
(
101
,
121
),
(
100
,
121
),
(
99
,
121
),
(
98
,
121
),
(
97
,
121
),
(
96
,
121
),
(
95
,
121
)]
#xys = xys[:12] + xys[16:]
pts
=
cv
.
CreateMat
(
len
(
xys
),
1
,
cv
.
CV_32SC2
)
for
i
,(
x
,
y
)
in
enumerate
(
xys
):
pts
[
i
,
0
]
=
(
x
,
y
)
storage
=
cv
.
CreateMemStorage
()
hull
=
cv
.
ConvexHull2
(
pts
,
storage
)
hullp
=
cv
.
ConvexHull2
(
pts
,
storage
,
return_points
=
1
)
defects
=
cv
.
ConvexityDefects
(
pts
,
hull
,
storage
)
vis
=
cv
.
CreateImage
((
1000
,
1000
),
8
,
3
)
x0
=
min
([
x
for
(
x
,
y
)
in
xys
])
-
10
x1
=
max
([
x
for
(
x
,
y
)
in
xys
])
+
10
y0
=
min
([
y
for
(
y
,
y
)
in
xys
])
-
10
y1
=
max
([
y
for
(
y
,
y
)
in
xys
])
+
10
def
xform
(
pt
):
x
,
y
=
pt
return
(
1000
*
(
x
-
x0
)
/
(
x1
-
x0
),
1000
*
(
y
-
y0
)
/
(
y1
-
y0
))
for
d
in
defects
[:
2
]:
cv
.
Zero
(
vis
)
# First draw the defect as a red triangle
cv
.
FillConvexPoly
(
vis
,
[
xform
(
p
)
for
p
in
d
[:
3
]],
cv
.
RGB
(
255
,
0
,
0
))
# Draw the convex hull as a thick green line
for
a
,
b
in
zip
(
hullp
,
hullp
[
1
:]):
cv
.
Line
(
vis
,
xform
(
a
),
xform
(
b
),
cv
.
RGB
(
0
,
128
,
0
),
3
)
# Draw the original contour as a white line
for
a
,
b
in
zip
(
xys
,
xys
[
1
:]):
cv
.
Line
(
vis
,
xform
(
a
),
xform
(
b
),
(
255
,
255
,
255
))
self
.
snap
(
vis
)
def
test_2686307
(
self
):
lena
=
cv
.
LoadImage
(
find_sample
(
"lena.jpg"
),
1
)
dst
=
cv
.
CreateImage
((
512
,
512
),
8
,
3
)
cv
.
Set
(
dst
,
(
128
,
192
,
255
))
mask
=
cv
.
CreateImage
((
512
,
512
),
8
,
1
)
cv
.
Zero
(
mask
)
cv
.
Rectangle
(
mask
,
(
10
,
10
),
(
300
,
100
),
255
,
-
1
)
cv
.
Copy
(
lena
,
dst
,
mask
)
self
.
snapL
([
lena
,
dst
,
mask
])
m
=
cv
.
CreateMat
(
480
,
640
,
cv
.
CV_8UC1
)
print
"ji"
,
m
print
m
.
rows
,
m
.
cols
,
m
.
type
,
m
.
step
def
snap
(
self
,
img
):
self
.
snapL
([
img
])
def
snapL
(
self
,
L
):
for
i
,
img
in
enumerate
(
L
):
cv
.
NamedWindow
(
"snap-
%
d"
%
i
,
1
)
cv
.
ShowImage
(
"snap-
%
d"
%
i
,
img
)
cv
.
WaitKey
()
cv
.
DestroyAllWindows
()
if
__name__
==
'__main__'
:
random
.
seed
(
0
)
if
len
(
sys
.
argv
)
==
1
:
suite
=
unittest
.
TestLoader
()
.
loadTestsFromTestCase
(
TestTickets
)
unittest
.
TextTestRunner
(
verbosity
=
2
)
.
run
(
suite
)
else
:
suite
=
unittest
.
TestSuite
()
suite
.
addTest
(
TestTickets
(
sys
.
argv
[
1
]))
unittest
.
TextTestRunner
(
verbosity
=
2
)
.
run
(
suite
)
modules/python/test/transformations.py
deleted
100755 → 0
View file @
947307ba
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# transformations.py
# Copyright (c) 2006, Christoph Gohlke
# Copyright (c) 2006-2009, The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the copyright holders nor the names of any
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Homogeneous Transformation Matrices and Quaternions.
A library for calculating 4x4 matrices for translating, rotating, reflecting,
scaling, shearing, projecting, orthogonalizing, and superimposing arrays of
3D homogeneous coordinates as well as for converting between rotation matrices,
Euler angles, and quaternions. Also includes an Arcball control object and
functions to decompose transformation matrices.
:Authors:
`Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`__,
Laboratory for Fluorescence Dynamics, University of California, Irvine
:Version: 20090418
Requirements
------------
* `Python 2.6 <http://www.python.org>`__
* `Numpy 1.3 <http://numpy.scipy.org>`__
* `transformations.c 20090418 <http://www.lfd.uci.edu/~gohlke/>`__
(optional implementation of some functions in C)
Notes
-----
Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using
numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using
numpy.dot(M, v) for shape (4,
\
*) "point of arrays", respectively
numpy.dot(v, M.T) for shape (
\
*, 4) "array of points".
Calculations are carried out with numpy.float64 precision.
This Python implementation is not optimized for speed.
Vector, point, quaternion, and matrix function arguments are expected to be
"array like", i.e. tuple, list, or numpy arrays.
Return types are numpy arrays unless specified otherwise.
Angles are in radians unless specified otherwise.
Quaternions ix+jy+kz+w are represented as [x, y, z, w].
Use the transpose of transformation matrices for OpenGL glMultMatrixd().
A triple of Euler angles can be applied/interpreted in 24 ways, which can
be specified using a 4 character string or encoded 4-tuple:
*Axes 4-string*: e.g. 'sxyz' or 'ryxy'
- first character : rotations are applied to 's'tatic or 'r'otating frame
- remaining characters : successive rotation axis 'x', 'y', or 'z'
*Axes 4-tuple*: e.g. (0, 0, 0, 0) or (1, 1, 1, 1)
- inner axis: code of axis ('x':0, 'y':1, 'z':2) of rightmost matrix.
- parity : even (0) if inner axis 'x' is followed by 'y', 'y' is followed
by 'z', or 'z' is followed by 'x'. Otherwise odd (1).
- repetition : first and last axis are same (1) or different (0).
- frame : rotations are applied to static (0) or rotating (1) frame.
References
----------
(1) Matrices and transformations. Ronald Goldman.
In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
(2) More matrices and transformations: shear and pseudo-perspective.
Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
(3) Decomposing a matrix into simple transformations. Spencer Thomas.
In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
(4) Recovering the data from the transformation matrix. Ronald Goldman.
In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
(5) Euler angle conversion. Ken Shoemake.
In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
(6) Arcball rotation control. Ken Shoemake.
In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
(7) Representing attitude: Euler angles, unit quaternions, and rotation
vectors. James Diebel. 2006.
(8) A discussion of the solution for the best rotation to relate two sets
of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
(9) Closed-form solution of absolute orientation using unit quaternions.
BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642.
(10) Quaternions. Ken Shoemake.
http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
(11) From quaternion to matrix and back. JMP van Waveren. 2005.
http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
(12) Uniform random rotations. Ken Shoemake.
In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
Examples
--------
>>> alpha, beta, gamma = 0.123, -1.234, 2.345
>>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
>>> I = identity_matrix()
>>> Rx = rotation_matrix(alpha, xaxis)
>>> Ry = rotation_matrix(beta, yaxis)
>>> Rz = rotation_matrix(gamma, zaxis)
>>> R = concatenate_matrices(Rx, Ry, Rz)
>>> euler = euler_from_matrix(R, 'rxyz')
>>> numpy.allclose([alpha, beta, gamma], euler)
True
>>> Re = euler_matrix(alpha, beta, gamma, 'rxyz')
>>> is_same_transform(R, Re)
True
>>> al, be, ga = euler_from_matrix(Re, 'rxyz')
>>> is_same_transform(Re, euler_matrix(al, be, ga, 'rxyz'))
True
>>> qx = quaternion_about_axis(alpha, xaxis)
>>> qy = quaternion_about_axis(beta, yaxis)
>>> qz = quaternion_about_axis(gamma, zaxis)
>>> q = quaternion_multiply(qx, qy)
>>> q = quaternion_multiply(q, qz)
>>> Rq = quaternion_matrix(q)
>>> is_same_transform(R, Rq)
True
>>> S = scale_matrix(1.23, origin)
>>> T = translation_matrix((1, 2, 3))
>>> Z = shear_matrix(beta, xaxis, origin, zaxis)
>>> R = random_rotation_matrix(numpy.random.rand(3))
>>> M = concatenate_matrices(T, R, Z, S)
>>> scale, shear, angles, trans, persp = decompose_matrix(M)
>>> numpy.allclose(scale, 1.23)
True
>>> numpy.allclose(trans, (1, 2, 3))
True
>>> numpy.allclose(shear, (0, math.tan(beta), 0))
True
>>> is_same_transform(R, euler_matrix(axes='sxyz', *angles))
True
>>> M1 = compose_matrix(scale, shear, angles, trans, persp)
>>> is_same_transform(M, M1)
True
"""
from
__future__
import
division
import
warnings
import
math
import
numpy
# Documentation in HTML format can be generated with Epydoc
__docformat__
=
"restructuredtext en"
def
identity_matrix
():
"""Return 4x4 identity/unit matrix.
>>> I = identity_matrix()
>>> numpy.allclose(I, numpy.dot(I, I))
True
>>> numpy.sum(I), numpy.trace(I)
(4.0, 4.0)
>>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64))
True
"""
return
numpy
.
identity
(
4
,
dtype
=
numpy
.
float64
)
def
translation_matrix
(
direction
):
"""Return matrix to translate by direction vector.
>>> v = numpy.random.random(3) - 0.5
>>> numpy.allclose(v, translation_matrix(v)[:3, 3])
True
"""
M
=
numpy
.
identity
(
4
)
M
[:
3
,
3
]
=
direction
[:
3
]
return
M
def
translation_from_matrix
(
matrix
):
"""Return translation vector from translation matrix.
>>> v0 = numpy.random.random(3) - 0.5
>>> v1 = translation_from_matrix(translation_matrix(v0))
>>> numpy.allclose(v0, v1)
True
"""
return
numpy
.
array
(
matrix
,
copy
=
False
)[:
3
,
3
]
.
copy
()
def
reflection_matrix
(
point
,
normal
):
"""Return matrix to mirror at plane defined by point and normal vector.
>>> v0 = numpy.random.random(4) - 0.5
>>> v0[3] = 1.0
>>> v1 = numpy.random.random(3) - 0.5
>>> R = reflection_matrix(v0, v1)
>>> numpy.allclose(2., numpy.trace(R))
True
>>> numpy.allclose(v0, numpy.dot(R, v0))
True
>>> v2 = v0.copy()
>>> v2[:3] += v1
>>> v3 = v0.copy()
>>> v2[:3] -= v1
>>> numpy.allclose(v2, numpy.dot(R, v3))
True
"""
normal
=
unit_vector
(
normal
[:
3
])
M
=
numpy
.
identity
(
4
)
M
[:
3
,
:
3
]
-=
2.0
*
numpy
.
outer
(
normal
,
normal
)
M
[:
3
,
3
]
=
(
2.0
*
numpy
.
dot
(
point
[:
3
],
normal
))
*
normal
return
M
def
reflection_from_matrix
(
matrix
):
"""Return mirror plane point and normal vector from reflection matrix.
>>> v0 = numpy.random.random(3) - 0.5
>>> v1 = numpy.random.random(3) - 0.5
>>> M0 = reflection_matrix(v0, v1)
>>> point, normal = reflection_from_matrix(M0)
>>> M1 = reflection_matrix(point, normal)
>>> is_same_transform(M0, M1)
True
"""
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)
# normal: unit eigenvector corresponding to eigenvalue -1
l
,
V
=
numpy
.
linalg
.
eig
(
M
[:
3
,
:
3
])
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
+
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no unit eigenvector corresponding to eigenvalue -1"
)
normal
=
numpy
.
real
(
V
[:,
i
[
0
]])
.
squeeze
()
# point: any unit eigenvector corresponding to eigenvalue 1
l
,
V
=
numpy
.
linalg
.
eig
(
M
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no unit eigenvector corresponding to eigenvalue 1"
)
point
=
numpy
.
real
(
V
[:,
i
[
-
1
]])
.
squeeze
()
point
/=
point
[
3
]
return
point
,
normal
def
rotation_matrix
(
angle
,
direction
,
point
=
None
):
"""Return matrix to rotate about axis defined by point and direction.
>>> angle = (random.random() - 0.5) * (2*math.pi)
>>> direc = numpy.random.random(3) - 0.5
>>> point = numpy.random.random(3) - 0.5
>>> R0 = rotation_matrix(angle, direc, point)
>>> R1 = rotation_matrix(angle-2*math.pi, direc, point)
>>> is_same_transform(R0, R1)
True
>>> R0 = rotation_matrix(angle, direc, point)
>>> R1 = rotation_matrix(-angle, -direc, point)
>>> is_same_transform(R0, R1)
True
>>> I = numpy.identity(4, numpy.float64)
>>> numpy.allclose(I, rotation_matrix(math.pi*2, direc))
True
>>> numpy.allclose(2., numpy.trace(rotation_matrix(math.pi/2,
... direc, point)))
True
"""
sina
=
math
.
sin
(
angle
)
cosa
=
math
.
cos
(
angle
)
direction
=
unit_vector
(
direction
[:
3
])
# rotation matrix around unit vector
R
=
numpy
.
array
(((
cosa
,
0.0
,
0.0
),
(
0.0
,
cosa
,
0.0
),
(
0.0
,
0.0
,
cosa
)),
dtype
=
numpy
.
float64
)
R
+=
numpy
.
outer
(
direction
,
direction
)
*
(
1.0
-
cosa
)
direction
*=
sina
R
+=
numpy
.
array
(((
0.0
,
-
direction
[
2
],
direction
[
1
]),
(
direction
[
2
],
0.0
,
-
direction
[
0
]),
(
-
direction
[
1
],
direction
[
0
],
0.0
)),
dtype
=
numpy
.
float64
)
M
=
numpy
.
identity
(
4
)
M
[:
3
,
:
3
]
=
R
if
point
is
not
None
:
# rotation not around origin
point
=
numpy
.
array
(
point
[:
3
],
dtype
=
numpy
.
float64
,
copy
=
False
)
M
[:
3
,
3
]
=
point
-
numpy
.
dot
(
R
,
point
)
return
M
def
rotation_from_matrix
(
matrix
):
"""Return rotation angle and axis from rotation matrix.
>>> angle = (random.random() - 0.5) * (2*math.pi)
>>> direc = numpy.random.random(3) - 0.5
>>> point = numpy.random.random(3) - 0.5
>>> R0 = rotation_matrix(angle, direc, point)
>>> angle, direc, point = rotation_from_matrix(R0)
>>> R1 = rotation_matrix(angle, direc, point)
>>> is_same_transform(R0, R1)
True
"""
R
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)
R33
=
R
[:
3
,
:
3
]
# direction: unit eigenvector of R33 corresponding to eigenvalue of 1
l
,
W
=
numpy
.
linalg
.
eig
(
R33
.
T
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no unit eigenvector corresponding to eigenvalue 1"
)
direction
=
numpy
.
real
(
W
[:,
i
[
-
1
]])
.
squeeze
()
# point: unit eigenvector of R33 corresponding to eigenvalue of 1
l
,
Q
=
numpy
.
linalg
.
eig
(
R
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no unit eigenvector corresponding to eigenvalue 1"
)
point
=
numpy
.
real
(
Q
[:,
i
[
-
1
]])
.
squeeze
()
point
/=
point
[
3
]
# rotation angle depending on direction
cosa
=
(
numpy
.
trace
(
R33
)
-
1.0
)
/
2.0
if
abs
(
direction
[
2
])
>
1e-8
:
sina
=
(
R
[
1
,
0
]
+
(
cosa
-
1.0
)
*
direction
[
0
]
*
direction
[
1
])
/
direction
[
2
]
elif
abs
(
direction
[
1
])
>
1e-8
:
sina
=
(
R
[
0
,
2
]
+
(
cosa
-
1.0
)
*
direction
[
0
]
*
direction
[
2
])
/
direction
[
1
]
else
:
sina
=
(
R
[
2
,
1
]
+
(
cosa
-
1.0
)
*
direction
[
1
]
*
direction
[
2
])
/
direction
[
0
]
angle
=
math
.
atan2
(
sina
,
cosa
)
return
angle
,
direction
,
point
def
scale_matrix
(
factor
,
origin
=
None
,
direction
=
None
):
"""Return matrix to scale by factor around origin in direction.
Use factor -1 for point symmetry.
>>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0
>>> v[3] = 1.0
>>> S = scale_matrix(-1.234)
>>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
True
>>> factor = random.random() * 10 - 5
>>> origin = numpy.random.random(3) - 0.5
>>> direct = numpy.random.random(3) - 0.5
>>> S = scale_matrix(factor, origin)
>>> S = scale_matrix(factor, origin, direct)
"""
if
direction
is
None
:
# uniform scaling
M
=
numpy
.
array
(((
factor
,
0.0
,
0.0
,
0.0
),
(
0.0
,
factor
,
0.0
,
0.0
),
(
0.0
,
0.0
,
factor
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
)),
dtype
=
numpy
.
float64
)
if
origin
is
not
None
:
M
[:
3
,
3
]
=
origin
[:
3
]
M
[:
3
,
3
]
*=
1.0
-
factor
else
:
# nonuniform scaling
direction
=
unit_vector
(
direction
[:
3
])
factor
=
1.0
-
factor
M
=
numpy
.
identity
(
4
)
M
[:
3
,
:
3
]
-=
factor
*
numpy
.
outer
(
direction
,
direction
)
if
origin
is
not
None
:
M
[:
3
,
3
]
=
(
factor
*
numpy
.
dot
(
origin
[:
3
],
direction
))
*
direction
return
M
def
scale_from_matrix
(
matrix
):
"""Return scaling factor, origin and direction from scaling matrix.
>>> factor = random.random() * 10 - 5
>>> origin = numpy.random.random(3) - 0.5
>>> direct = numpy.random.random(3) - 0.5
>>> S0 = scale_matrix(factor, origin)
>>> factor, origin, direction = scale_from_matrix(S0)
>>> S1 = scale_matrix(factor, origin, direction)
>>> is_same_transform(S0, S1)
True
>>> S0 = scale_matrix(factor, origin, direct)
>>> factor, origin, direction = scale_from_matrix(S0)
>>> S1 = scale_matrix(factor, origin, direction)
>>> is_same_transform(S0, S1)
True
"""
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)
M33
=
M
[:
3
,
:
3
]
factor
=
numpy
.
trace
(
M33
)
-
2.0
try
:
# direction: unit eigenvector corresponding to eigenvalue factor
l
,
V
=
numpy
.
linalg
.
eig
(
M33
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
factor
)
<
1e-8
)[
0
][
0
]
direction
=
numpy
.
real
(
V
[:,
i
])
.
squeeze
()
direction
/=
vector_norm
(
direction
)
except
IndexError
:
# uniform scaling
factor
=
(
factor
+
2.0
)
/
3.0
direction
=
None
# origin: any eigenvector corresponding to eigenvalue 1
l
,
V
=
numpy
.
linalg
.
eig
(
M
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no eigenvector corresponding to eigenvalue 1"
)
origin
=
numpy
.
real
(
V
[:,
i
[
-
1
]])
.
squeeze
()
origin
/=
origin
[
3
]
return
factor
,
origin
,
direction
def
projection_matrix
(
point
,
normal
,
direction
=
None
,
perspective
=
None
,
pseudo
=
False
):
"""Return matrix to project onto plane defined by point and normal.
Using either perspective point, projection direction, or none of both.
If pseudo is True, perspective projections will preserve relative depth
such that Perspective = dot(Orthogonal, PseudoPerspective).
>>> P = projection_matrix((0, 0, 0), (1, 0, 0))
>>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
True
>>> point = numpy.random.random(3) - 0.5
>>> normal = numpy.random.random(3) - 0.5
>>> direct = numpy.random.random(3) - 0.5
>>> persp = numpy.random.random(3) - 0.5
>>> P0 = projection_matrix(point, normal)
>>> P1 = projection_matrix(point, normal, direction=direct)
>>> P2 = projection_matrix(point, normal, perspective=persp)
>>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True)
>>> is_same_transform(P2, numpy.dot(P0, P3))
True
>>> P = projection_matrix((3, 0, 0), (1, 1, 0), (1, 0, 0))
>>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0
>>> v0[3] = 1.0
>>> v1 = numpy.dot(P, v0)
>>> numpy.allclose(v1[1], v0[1])
True
>>> numpy.allclose(v1[0], 3.0-v1[1])
True
"""
M
=
numpy
.
identity
(
4
)
point
=
numpy
.
array
(
point
[:
3
],
dtype
=
numpy
.
float64
,
copy
=
False
)
normal
=
unit_vector
(
normal
[:
3
])
if
perspective
is
not
None
:
# perspective projection
perspective
=
numpy
.
array
(
perspective
[:
3
],
dtype
=
numpy
.
float64
,
copy
=
False
)
M
[
0
,
0
]
=
M
[
1
,
1
]
=
M
[
2
,
2
]
=
numpy
.
dot
(
perspective
-
point
,
normal
)
M
[:
3
,
:
3
]
-=
numpy
.
outer
(
perspective
,
normal
)
if
pseudo
:
# preserve relative depth
M
[:
3
,
:
3
]
-=
numpy
.
outer
(
normal
,
normal
)
M
[:
3
,
3
]
=
numpy
.
dot
(
point
,
normal
)
*
(
perspective
+
normal
)
else
:
M
[:
3
,
3
]
=
numpy
.
dot
(
point
,
normal
)
*
perspective
M
[
3
,
:
3
]
=
-
normal
M
[
3
,
3
]
=
numpy
.
dot
(
perspective
,
normal
)
elif
direction
is
not
None
:
# parallel projection
direction
=
numpy
.
array
(
direction
[:
3
],
dtype
=
numpy
.
float64
,
copy
=
False
)
scale
=
numpy
.
dot
(
direction
,
normal
)
M
[:
3
,
:
3
]
-=
numpy
.
outer
(
direction
,
normal
)
/
scale
M
[:
3
,
3
]
=
direction
*
(
numpy
.
dot
(
point
,
normal
)
/
scale
)
else
:
# orthogonal projection
M
[:
3
,
:
3
]
-=
numpy
.
outer
(
normal
,
normal
)
M
[:
3
,
3
]
=
numpy
.
dot
(
point
,
normal
)
*
normal
return
M
def
projection_from_matrix
(
matrix
,
pseudo
=
False
):
"""Return projection plane and perspective point from projection matrix.
Return values are same as arguments for projection_matrix function:
point, normal, direction, perspective, and pseudo.
>>> point = numpy.random.random(3) - 0.5
>>> normal = numpy.random.random(3) - 0.5
>>> direct = numpy.random.random(3) - 0.5
>>> persp = numpy.random.random(3) - 0.5
>>> P0 = projection_matrix(point, normal)
>>> result = projection_from_matrix(P0)
>>> P1 = projection_matrix(*result)
>>> is_same_transform(P0, P1)
True
>>> P0 = projection_matrix(point, normal, direct)
>>> result = projection_from_matrix(P0)
>>> P1 = projection_matrix(*result)
>>> is_same_transform(P0, P1)
True
>>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False)
>>> result = projection_from_matrix(P0, pseudo=False)
>>> P1 = projection_matrix(*result)
>>> is_same_transform(P0, P1)
True
>>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True)
>>> result = projection_from_matrix(P0, pseudo=True)
>>> P1 = projection_matrix(*result)
>>> is_same_transform(P0, P1)
True
"""
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)
M33
=
M
[:
3
,
:
3
]
l
,
V
=
numpy
.
linalg
.
eig
(
M
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
pseudo
and
len
(
i
):
# point: any eigenvector corresponding to eigenvalue 1
point
=
numpy
.
real
(
V
[:,
i
[
-
1
]])
.
squeeze
()
point
/=
point
[
3
]
# direction: unit eigenvector corresponding to eigenvalue 0
l
,
V
=
numpy
.
linalg
.
eig
(
M33
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
))
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no eigenvector corresponding to eigenvalue 0"
)
direction
=
numpy
.
real
(
V
[:,
i
[
0
]])
.
squeeze
()
direction
/=
vector_norm
(
direction
)
# normal: unit eigenvector of M33.T corresponding to eigenvalue 0
l
,
V
=
numpy
.
linalg
.
eig
(
M33
.
T
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
))
<
1e-8
)[
0
]
if
len
(
i
):
# parallel projection
normal
=
numpy
.
real
(
V
[:,
i
[
0
]])
.
squeeze
()
normal
/=
vector_norm
(
normal
)
return
point
,
normal
,
direction
,
None
,
False
else
:
# orthogonal projection, where normal equals direction vector
return
point
,
direction
,
None
,
None
,
False
else
:
# perspective projection
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
))
>
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no eigenvector not corresponding to eigenvalue 0"
)
point
=
numpy
.
real
(
V
[:,
i
[
-
1
]])
.
squeeze
()
point
/=
point
[
3
]
normal
=
-
M
[
3
,
:
3
]
perspective
=
M
[:
3
,
3
]
/
numpy
.
dot
(
point
[:
3
],
normal
)
if
pseudo
:
perspective
-=
normal
return
point
,
normal
,
None
,
perspective
,
pseudo
def
clip_matrix
(
left
,
right
,
bottom
,
top
,
near
,
far
,
perspective
=
False
):
"""Return matrix to obtain normalized device coordinates from frustrum.
The frustrum bounds are axis-aligned along x (left, right),
y (bottom, top) and z (near, far).
Normalized device coordinates are in range [-1, 1] if coordinates are
inside the frustrum.
If perspective is True the frustrum is a truncated pyramid with the
perspective point at origin and direction along z axis, otherwise an
orthographic canonical view volume (a box).
Homogeneous coordinates transformed by the perspective clip matrix
need to be dehomogenized (devided by w coordinate).
>>> frustrum = numpy.random.rand(6)
>>> frustrum[1] += frustrum[0]
>>> frustrum[3] += frustrum[2]
>>> frustrum[5] += frustrum[4]
>>> M = clip_matrix(*frustrum, perspective=False)
>>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
array([-1., -1., -1., 1.])
>>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0])
array([ 1., 1., 1., 1.])
>>> M = clip_matrix(*frustrum, perspective=True)
>>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
>>> v / v[3]
array([-1., -1., -1., 1.])
>>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0])
>>> v / v[3]
array([ 1., 1., -1., 1.])
"""
if
left
>=
right
or
bottom
>=
top
or
near
>=
far
:
raise
ValueError
(
"invalid frustrum"
)
if
perspective
:
if
near
<=
_EPS
:
raise
ValueError
(
"invalid frustrum: near <= 0"
)
t
=
2.0
*
near
M
=
((
-
t
/
(
right
-
left
),
0.0
,
(
right
+
left
)
/
(
right
-
left
),
0.0
),
(
0.0
,
-
t
/
(
top
-
bottom
),
(
top
+
bottom
)
/
(
top
-
bottom
),
0.0
),
(
0.0
,
0.0
,
-
(
far
+
near
)
/
(
far
-
near
),
t
*
far
/
(
far
-
near
)),
(
0.0
,
0.0
,
-
1.0
,
0.0
))
else
:
M
=
((
2.0
/
(
right
-
left
),
0.0
,
0.0
,
(
right
+
left
)
/
(
left
-
right
)),
(
0.0
,
2.0
/
(
top
-
bottom
),
0.0
,
(
top
+
bottom
)
/
(
bottom
-
top
)),
(
0.0
,
0.0
,
2.0
/
(
far
-
near
),
(
far
+
near
)
/
(
near
-
far
)),
(
0.0
,
0.0
,
0.0
,
1.0
))
return
numpy
.
array
(
M
,
dtype
=
numpy
.
float64
)
def
shear_matrix
(
angle
,
direction
,
point
,
normal
):
"""Return matrix to shear by angle along direction vector on shear plane.
The shear plane is defined by a point and normal vector. The direction
vector must be orthogonal to the plane's normal vector.
A point P is transformed by the shear matrix into P" such that
the vector P-P" is parallel to the direction vector and its extent is
given by the angle of P-P'-P", where P' is the orthogonal projection
of P onto the shear plane.
>>> angle = (random.random() - 0.5) * 4*math.pi
>>> direct = numpy.random.random(3) - 0.5
>>> point = numpy.random.random(3) - 0.5
>>> normal = numpy.cross(direct, numpy.random.random(3))
>>> S = shear_matrix(angle, direct, point, normal)
>>> numpy.allclose(1.0, numpy.linalg.det(S))
True
"""
normal
=
unit_vector
(
normal
[:
3
])
direction
=
unit_vector
(
direction
[:
3
])
if
abs
(
numpy
.
dot
(
normal
,
direction
))
>
1e-6
:
raise
ValueError
(
"direction and normal vectors are not orthogonal"
)
angle
=
math
.
tan
(
angle
)
M
=
numpy
.
identity
(
4
)
M
[:
3
,
:
3
]
+=
angle
*
numpy
.
outer
(
direction
,
normal
)
M
[:
3
,
3
]
=
-
angle
*
numpy
.
dot
(
point
[:
3
],
normal
)
*
direction
return
M
def
shear_from_matrix
(
matrix
):
"""Return shear angle, direction and plane from shear matrix.
>>> angle = (random.random() - 0.5) * 4*math.pi
>>> direct = numpy.random.random(3) - 0.5
>>> point = numpy.random.random(3) - 0.5
>>> normal = numpy.cross(direct, numpy.random.random(3))
>>> S0 = shear_matrix(angle, direct, point, normal)
>>> angle, direct, point, normal = shear_from_matrix(S0)
>>> S1 = shear_matrix(angle, direct, point, normal)
>>> is_same_transform(S0, S1)
True
"""
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)
M33
=
M
[:
3
,
:
3
]
# normal: cross independent eigenvectors corresponding to the eigenvalue 1
l
,
V
=
numpy
.
linalg
.
eig
(
M33
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-4
)[
0
]
if
len
(
i
)
<
2
:
raise
ValueError
(
"No two linear independent eigenvectors found
%
s"
%
l
)
V
=
numpy
.
real
(
V
[:,
i
])
.
squeeze
()
.
T
lenorm
=
-
1.0
for
i0
,
i1
in
((
0
,
1
),
(
0
,
2
),
(
1
,
2
)):
n
=
numpy
.
cross
(
V
[
i0
],
V
[
i1
])
l
=
vector_norm
(
n
)
if
l
>
lenorm
:
lenorm
=
l
normal
=
n
normal
/=
lenorm
# direction and angle
direction
=
numpy
.
dot
(
M33
-
numpy
.
identity
(
3
),
normal
)
angle
=
vector_norm
(
direction
)
direction
/=
angle
angle
=
math
.
atan
(
angle
)
# point: eigenvector corresponding to eigenvalue 1
l
,
V
=
numpy
.
linalg
.
eig
(
M
)
i
=
numpy
.
where
(
abs
(
numpy
.
real
(
l
)
-
1.0
)
<
1e-8
)[
0
]
if
not
len
(
i
):
raise
ValueError
(
"no eigenvector corresponding to eigenvalue 1"
)
point
=
numpy
.
real
(
V
[:,
i
[
-
1
]])
.
squeeze
()
point
/=
point
[
3
]
return
angle
,
direction
,
point
,
normal
def
decompose_matrix
(
matrix
):
"""Return sequence of transformations from transformation matrix.
matrix : array_like
Non-degenerative homogeneous transformation matrix
Return tuple of:
scale : vector of 3 scaling factors
shear : list of shear factors for x-y, x-z, y-z axes
angles : list of Euler angles about static x, y, z axes
translate : translation vector along x, y, z axes
perspective : perspective partition of matrix
Raise ValueError if matrix is of wrong type or degenerative.
>>> T0 = translation_matrix((1, 2, 3))
>>> scale, shear, angles, trans, persp = decompose_matrix(T0)
>>> T1 = translation_matrix(trans)
>>> numpy.allclose(T0, T1)
True
>>> S = scale_matrix(0.123)
>>> scale, shear, angles, trans, persp = decompose_matrix(S)
>>> scale[0]
0.123
>>> R0 = euler_matrix(1, 2, 3)
>>> scale, shear, angles, trans, persp = decompose_matrix(R0)
>>> R1 = euler_matrix(*angles)
>>> numpy.allclose(R0, R1)
True
"""
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
True
)
.
T
if
abs
(
M
[
3
,
3
])
<
_EPS
:
raise
ValueError
(
"M[3, 3] is zero"
)
M
/=
M
[
3
,
3
]
P
=
M
.
copy
()
P
[:,
3
]
=
0
,
0
,
0
,
1
if
not
numpy
.
linalg
.
det
(
P
):
raise
ValueError
(
"Matrix is singular"
)
scale
=
numpy
.
zeros
((
3
,
),
dtype
=
numpy
.
float64
)
shear
=
[
0
,
0
,
0
]
angles
=
[
0
,
0
,
0
]
if
any
(
abs
(
M
[:
3
,
3
])
>
_EPS
):
perspective
=
numpy
.
dot
(
M
[:,
3
],
numpy
.
linalg
.
inv
(
P
.
T
))
M
[:,
3
]
=
0
,
0
,
0
,
1
else
:
perspective
=
numpy
.
array
((
0
,
0
,
0
,
1
),
dtype
=
numpy
.
float64
)
translate
=
M
[
3
,
:
3
]
.
copy
()
M
[
3
,
:
3
]
=
0
row
=
M
[:
3
,
:
3
]
.
copy
()
scale
[
0
]
=
vector_norm
(
row
[
0
])
row
[
0
]
/=
scale
[
0
]
shear
[
0
]
=
numpy
.
dot
(
row
[
0
],
row
[
1
])
row
[
1
]
-=
row
[
0
]
*
shear
[
0
]
scale
[
1
]
=
vector_norm
(
row
[
1
])
row
[
1
]
/=
scale
[
1
]
shear
[
0
]
/=
scale
[
1
]
shear
[
1
]
=
numpy
.
dot
(
row
[
0
],
row
[
2
])
row
[
2
]
-=
row
[
0
]
*
shear
[
1
]
shear
[
2
]
=
numpy
.
dot
(
row
[
1
],
row
[
2
])
row
[
2
]
-=
row
[
1
]
*
shear
[
2
]
scale
[
2
]
=
vector_norm
(
row
[
2
])
row
[
2
]
/=
scale
[
2
]
shear
[
1
:]
/=
scale
[
2
]
if
numpy
.
dot
(
row
[
0
],
numpy
.
cross
(
row
[
1
],
row
[
2
]))
<
0
:
scale
*=
-
1
row
*=
-
1
angles
[
1
]
=
math
.
asin
(
-
row
[
0
,
2
])
if
math
.
cos
(
angles
[
1
]):
angles
[
0
]
=
math
.
atan2
(
row
[
1
,
2
],
row
[
2
,
2
])
angles
[
2
]
=
math
.
atan2
(
row
[
0
,
1
],
row
[
0
,
0
])
else
:
#angles[0] = math.atan2(row[1, 0], row[1, 1])
angles
[
0
]
=
math
.
atan2
(
-
row
[
2
,
1
],
row
[
1
,
1
])
angles
[
2
]
=
0.0
return
scale
,
shear
,
angles
,
translate
,
perspective
def
compose_matrix
(
scale
=
None
,
shear
=
None
,
angles
=
None
,
translate
=
None
,
perspective
=
None
):
"""Return transformation matrix from sequence of transformations.
This is the inverse of the decompose_matrix function.
Sequence of transformations:
scale : vector of 3 scaling factors
shear : list of shear factors for x-y, x-z, y-z axes
angles : list of Euler angles about static x, y, z axes
translate : translation vector along x, y, z axes
perspective : perspective partition of matrix
>>> scale = numpy.random.random(3) - 0.5
>>> shear = numpy.random.random(3) - 0.5
>>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
>>> trans = numpy.random.random(3) - 0.5
>>> persp = numpy.random.random(4) - 0.5
>>> M0 = compose_matrix(scale, shear, angles, trans, persp)
>>> result = decompose_matrix(M0)
>>> M1 = compose_matrix(*result)
>>> is_same_transform(M0, M1)
True
"""
M
=
numpy
.
identity
(
4
)
if
perspective
is
not
None
:
P
=
numpy
.
identity
(
4
)
P
[
3
,
:]
=
perspective
[:
4
]
M
=
numpy
.
dot
(
M
,
P
)
if
translate
is
not
None
:
T
=
numpy
.
identity
(
4
)
T
[:
3
,
3
]
=
translate
[:
3
]
M
=
numpy
.
dot
(
M
,
T
)
if
angles
is
not
None
:
R
=
euler_matrix
(
angles
[
0
],
angles
[
1
],
angles
[
2
],
'sxyz'
)
M
=
numpy
.
dot
(
M
,
R
)
if
shear
is
not
None
:
Z
=
numpy
.
identity
(
4
)
Z
[
1
,
2
]
=
shear
[
2
]
Z
[
0
,
2
]
=
shear
[
1
]
Z
[
0
,
1
]
=
shear
[
0
]
M
=
numpy
.
dot
(
M
,
Z
)
if
scale
is
not
None
:
S
=
numpy
.
identity
(
4
)
S
[
0
,
0
]
=
scale
[
0
]
S
[
1
,
1
]
=
scale
[
1
]
S
[
2
,
2
]
=
scale
[
2
]
M
=
numpy
.
dot
(
M
,
S
)
M
/=
M
[
3
,
3
]
return
M
def
orthogonalization_matrix
(
lengths
,
angles
):
"""Return orthogonalization matrix for crystallographic cell coordinates.
Angles are expected in degrees.
The de-orthogonalization matrix is the inverse.
>>> O = orthogonalization_matrix((10., 10., 10.), (90., 90., 90.))
>>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
True
>>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7])
>>> numpy.allclose(numpy.sum(O), 43.063229)
True
"""
a
,
b
,
c
=
lengths
angles
=
numpy
.
radians
(
angles
)
sina
,
sinb
,
_
=
numpy
.
sin
(
angles
)
cosa
,
cosb
,
cosg
=
numpy
.
cos
(
angles
)
co
=
(
cosa
*
cosb
-
cosg
)
/
(
sina
*
sinb
)
return
numpy
.
array
((
(
a
*
sinb
*
math
.
sqrt
(
1.0
-
co
*
co
),
0.0
,
0.0
,
0.0
),
(
-
a
*
sinb
*
co
,
b
*
sina
,
0.0
,
0.0
),
(
a
*
cosb
,
b
*
cosa
,
c
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
)),
dtype
=
numpy
.
float64
)
def
superimposition_matrix
(
v0
,
v1
,
scaling
=
False
,
usesvd
=
True
):
"""Return matrix to transform given vector set into second vector set.
v0 and v1 are shape (3,
\
*) or (4,
\
*) arrays of at least 3 vectors.
If usesvd is True, the weighted sum of squared deviations (RMSD) is
minimized according to the algorithm by W. Kabsch [8]. Otherwise the
quaternion based algorithm by B. Horn [9] is used (slower when using
this Python implementation).
The returned matrix performs rotation, translation and uniform scaling
(if specified).
>>> v0 = numpy.random.rand(3, 10)
>>> M = superimposition_matrix(v0, v0)
>>> numpy.allclose(M, numpy.identity(4))
True
>>> R = random_rotation_matrix(numpy.random.random(3))
>>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1))
>>> v1 = numpy.dot(R, v0)
>>> M = superimposition_matrix(v0, v1)
>>> numpy.allclose(v1, numpy.dot(M, v0))
True
>>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0
>>> v0[3] = 1.0
>>> v1 = numpy.dot(R, v0)
>>> M = superimposition_matrix(v0, v1)
>>> numpy.allclose(v1, numpy.dot(M, v0))
True
>>> S = scale_matrix(random.random())
>>> T = translation_matrix(numpy.random.random(3)-0.5)
>>> M = concatenate_matrices(T, R, S)
>>> v1 = numpy.dot(M, v0)
>>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1)
>>> M = superimposition_matrix(v0, v1, scaling=True)
>>> numpy.allclose(v1, numpy.dot(M, v0))
True
>>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
>>> numpy.allclose(v1, numpy.dot(M, v0))
True
>>> v = numpy.empty((4, 100, 3), dtype=numpy.float64)
>>> v[:, :, 0] = v0
>>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
>>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
True
"""
v0
=
numpy
.
array
(
v0
,
dtype
=
numpy
.
float64
,
copy
=
False
)[:
3
]
v1
=
numpy
.
array
(
v1
,
dtype
=
numpy
.
float64
,
copy
=
False
)[:
3
]
if
v0
.
shape
!=
v1
.
shape
or
v0
.
shape
[
1
]
<
3
:
raise
ValueError
(
"Vector sets are of wrong shape or type."
)
# move centroids to origin
t0
=
numpy
.
mean
(
v0
,
axis
=
1
)
t1
=
numpy
.
mean
(
v1
,
axis
=
1
)
v0
=
v0
-
t0
.
reshape
(
3
,
1
)
v1
=
v1
-
t1
.
reshape
(
3
,
1
)
if
usesvd
:
# Singular Value Decomposition of covariance matrix
u
,
s
,
vh
=
numpy
.
linalg
.
svd
(
numpy
.
dot
(
v1
,
v0
.
T
))
# rotation matrix from SVD orthonormal bases
R
=
numpy
.
dot
(
u
,
vh
)
if
numpy
.
linalg
.
det
(
R
)
<
0.0
:
# R does not constitute right handed system
R
-=
numpy
.
outer
(
u
[:,
2
],
vh
[
2
,
:]
*
2.0
)
s
[
-
1
]
*=
-
1.0
# homogeneous transformation matrix
M
=
numpy
.
identity
(
4
)
M
[:
3
,
:
3
]
=
R
else
:
# compute symmetric matrix N
xx
,
yy
,
zz
=
numpy
.
sum
(
v0
*
v1
,
axis
=
1
)
xy
,
yz
,
zx
=
numpy
.
sum
(
v0
*
numpy
.
roll
(
v1
,
-
1
,
axis
=
0
),
axis
=
1
)
xz
,
yx
,
zy
=
numpy
.
sum
(
v0
*
numpy
.
roll
(
v1
,
-
2
,
axis
=
0
),
axis
=
1
)
N
=
((
xx
+
yy
+
zz
,
yz
-
zy
,
zx
-
xz
,
xy
-
yx
),
(
yz
-
zy
,
xx
-
yy
-
zz
,
xy
+
yx
,
zx
+
xz
),
(
zx
-
xz
,
xy
+
yx
,
-
xx
+
yy
-
zz
,
yz
+
zy
),
(
xy
-
yx
,
zx
+
xz
,
yz
+
zy
,
-
xx
-
yy
+
zz
))
# quaternion: eigenvector corresponding to most positive eigenvalue
l
,
V
=
numpy
.
linalg
.
eig
(
N
)
q
=
V
[:,
numpy
.
argmax
(
l
)]
q
/=
vector_norm
(
q
)
# unit quaternion
q
=
numpy
.
roll
(
q
,
-
1
)
# move w component to end
# homogeneous transformation matrix
M
=
quaternion_matrix
(
q
)
# scale: ratio of rms deviations from centroid
if
scaling
:
v0
*=
v0
v1
*=
v1
M
[:
3
,
:
3
]
*=
math
.
sqrt
(
numpy
.
sum
(
v1
)
/
numpy
.
sum
(
v0
))
# translation
M
[:
3
,
3
]
=
t1
T
=
numpy
.
identity
(
4
)
T
[:
3
,
3
]
=
-
t0
M
=
numpy
.
dot
(
M
,
T
)
return
M
def
euler_matrix
(
ai
,
aj
,
ak
,
axes
=
'sxyz'
):
"""Return homogeneous rotation matrix from Euler angles and axis sequence.
ai, aj, ak : Euler's roll, pitch and yaw angles
axes : One of 24 axis sequences as string or encoded tuple
>>> R = euler_matrix(1, 2, 3, 'syxz')
>>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
True
>>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1))
>>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
True
>>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
>>> for axes in _AXES2TUPLE.keys():
... R = euler_matrix(ai, aj, ak, axes)
>>> for axes in _TUPLE2AXES.keys():
... R = euler_matrix(ai, aj, ak, axes)
"""
try
:
firstaxis
,
parity
,
repetition
,
frame
=
_AXES2TUPLE
[
axes
]
except
(
AttributeError
,
KeyError
):
_
=
_TUPLE2AXES
[
axes
]
firstaxis
,
parity
,
repetition
,
frame
=
axes
i
=
firstaxis
j
=
_NEXT_AXIS
[
i
+
parity
]
k
=
_NEXT_AXIS
[
i
-
parity
+
1
]
if
frame
:
ai
,
ak
=
ak
,
ai
if
parity
:
ai
,
aj
,
ak
=
-
ai
,
-
aj
,
-
ak
si
,
sj
,
sk
=
math
.
sin
(
ai
),
math
.
sin
(
aj
),
math
.
sin
(
ak
)
ci
,
cj
,
ck
=
math
.
cos
(
ai
),
math
.
cos
(
aj
),
math
.
cos
(
ak
)
cc
,
cs
=
ci
*
ck
,
ci
*
sk
sc
,
ss
=
si
*
ck
,
si
*
sk
M
=
numpy
.
identity
(
4
)
if
repetition
:
M
[
i
,
i
]
=
cj
M
[
i
,
j
]
=
sj
*
si
M
[
i
,
k
]
=
sj
*
ci
M
[
j
,
i
]
=
sj
*
sk
M
[
j
,
j
]
=
-
cj
*
ss
+
cc
M
[
j
,
k
]
=
-
cj
*
cs
-
sc
M
[
k
,
i
]
=
-
sj
*
ck
M
[
k
,
j
]
=
cj
*
sc
+
cs
M
[
k
,
k
]
=
cj
*
cc
-
ss
else
:
M
[
i
,
i
]
=
cj
*
ck
M
[
i
,
j
]
=
sj
*
sc
-
cs
M
[
i
,
k
]
=
sj
*
cc
+
ss
M
[
j
,
i
]
=
cj
*
sk
M
[
j
,
j
]
=
sj
*
ss
+
cc
M
[
j
,
k
]
=
sj
*
cs
-
sc
M
[
k
,
i
]
=
-
sj
M
[
k
,
j
]
=
cj
*
si
M
[
k
,
k
]
=
cj
*
ci
return
M
def
euler_from_matrix
(
matrix
,
axes
=
'sxyz'
):
"""Return Euler angles from rotation matrix for specified axis sequence.
axes : One of 24 axis sequences as string or encoded tuple
Note that many Euler angle triplets can describe one matrix.
>>> R0 = euler_matrix(1, 2, 3, 'syxz')
>>> al, be, ga = euler_from_matrix(R0, 'syxz')
>>> R1 = euler_matrix(al, be, ga, 'syxz')
>>> numpy.allclose(R0, R1)
True
>>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
>>> for axes in _AXES2TUPLE.keys():
... R0 = euler_matrix(axes=axes, *angles)
... R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes))
... if not numpy.allclose(R0, R1): print axes, "failed"
"""
try
:
firstaxis
,
parity
,
repetition
,
frame
=
_AXES2TUPLE
[
axes
.
lower
()]
except
(
AttributeError
,
KeyError
):
_
=
_TUPLE2AXES
[
axes
]
firstaxis
,
parity
,
repetition
,
frame
=
axes
i
=
firstaxis
j
=
_NEXT_AXIS
[
i
+
parity
]
k
=
_NEXT_AXIS
[
i
-
parity
+
1
]
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)[:
3
,
:
3
]
if
repetition
:
sy
=
math
.
sqrt
(
M
[
i
,
j
]
*
M
[
i
,
j
]
+
M
[
i
,
k
]
*
M
[
i
,
k
])
if
sy
>
_EPS
:
ax
=
math
.
atan2
(
M
[
i
,
j
],
M
[
i
,
k
])
ay
=
math
.
atan2
(
sy
,
M
[
i
,
i
])
az
=
math
.
atan2
(
M
[
j
,
i
],
-
M
[
k
,
i
])
else
:
ax
=
math
.
atan2
(
-
M
[
j
,
k
],
M
[
j
,
j
])
ay
=
math
.
atan2
(
sy
,
M
[
i
,
i
])
az
=
0.0
else
:
cy
=
math
.
sqrt
(
M
[
i
,
i
]
*
M
[
i
,
i
]
+
M
[
j
,
i
]
*
M
[
j
,
i
])
if
cy
>
_EPS
:
ax
=
math
.
atan2
(
M
[
k
,
j
],
M
[
k
,
k
])
ay
=
math
.
atan2
(
-
M
[
k
,
i
],
cy
)
az
=
math
.
atan2
(
M
[
j
,
i
],
M
[
i
,
i
])
else
:
ax
=
math
.
atan2
(
-
M
[
j
,
k
],
M
[
j
,
j
])
ay
=
math
.
atan2
(
-
M
[
k
,
i
],
cy
)
az
=
0.0
if
parity
:
ax
,
ay
,
az
=
-
ax
,
-
ay
,
-
az
if
frame
:
ax
,
az
=
az
,
ax
return
ax
,
ay
,
az
def
euler_from_quaternion
(
quaternion
,
axes
=
'sxyz'
):
"""Return Euler angles from quaternion for specified axis sequence.
>>> angles = euler_from_quaternion([0.06146124, 0, 0, 0.99810947])
>>> numpy.allclose(angles, [0.123, 0, 0])
True
"""
return
euler_from_matrix
(
quaternion_matrix
(
quaternion
),
axes
)
def
quaternion_from_euler
(
ai
,
aj
,
ak
,
axes
=
'sxyz'
):
"""Return quaternion from Euler angles and axis sequence.
ai, aj, ak : Euler's roll, pitch and yaw angles
axes : One of 24 axis sequences as string or encoded tuple
>>> q = quaternion_from_euler(1, 2, 3, 'ryxz')
>>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953])
True
"""
try
:
firstaxis
,
parity
,
repetition
,
frame
=
_AXES2TUPLE
[
axes
.
lower
()]
except
(
AttributeError
,
KeyError
):
_
=
_TUPLE2AXES
[
axes
]
firstaxis
,
parity
,
repetition
,
frame
=
axes
i
=
firstaxis
j
=
_NEXT_AXIS
[
i
+
parity
]
k
=
_NEXT_AXIS
[
i
-
parity
+
1
]
if
frame
:
ai
,
ak
=
ak
,
ai
if
parity
:
aj
=
-
aj
ai
/=
2.0
aj
/=
2.0
ak
/=
2.0
ci
=
math
.
cos
(
ai
)
si
=
math
.
sin
(
ai
)
cj
=
math
.
cos
(
aj
)
sj
=
math
.
sin
(
aj
)
ck
=
math
.
cos
(
ak
)
sk
=
math
.
sin
(
ak
)
cc
=
ci
*
ck
cs
=
ci
*
sk
sc
=
si
*
ck
ss
=
si
*
sk
quaternion
=
numpy
.
empty
((
4
,
),
dtype
=
numpy
.
float64
)
if
repetition
:
quaternion
[
i
]
=
cj
*
(
cs
+
sc
)
quaternion
[
j
]
=
sj
*
(
cc
+
ss
)
quaternion
[
k
]
=
sj
*
(
cs
-
sc
)
quaternion
[
3
]
=
cj
*
(
cc
-
ss
)
else
:
quaternion
[
i
]
=
cj
*
sc
-
sj
*
cs
quaternion
[
j
]
=
cj
*
ss
+
sj
*
cc
quaternion
[
k
]
=
cj
*
cs
-
sj
*
sc
quaternion
[
3
]
=
cj
*
cc
+
sj
*
ss
if
parity
:
quaternion
[
j
]
*=
-
1
return
quaternion
def
quaternion_about_axis
(
angle
,
axis
):
"""Return quaternion for rotation about axis.
>>> q = quaternion_about_axis(0.123, (1, 0, 0))
>>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947])
True
"""
quaternion
=
numpy
.
zeros
((
4
,
),
dtype
=
numpy
.
float64
)
quaternion
[:
3
]
=
axis
[:
3
]
qlen
=
vector_norm
(
quaternion
)
if
qlen
>
_EPS
:
quaternion
*=
math
.
sin
(
angle
/
2.0
)
/
qlen
quaternion
[
3
]
=
math
.
cos
(
angle
/
2.0
)
return
quaternion
def
quaternion_matrix
(
quaternion
):
"""Return homogeneous rotation matrix from quaternion.
>>> R = quaternion_matrix([0.06146124, 0, 0, 0.99810947])
>>> numpy.allclose(R, rotation_matrix(0.123, (1, 0, 0)))
True
"""
q
=
numpy
.
array
(
quaternion
[:
4
],
dtype
=
numpy
.
float64
,
copy
=
True
)
nq
=
numpy
.
dot
(
q
,
q
)
if
nq
<
_EPS
:
return
numpy
.
identity
(
4
)
q
*=
math
.
sqrt
(
2.0
/
nq
)
q
=
numpy
.
outer
(
q
,
q
)
return
numpy
.
array
((
(
1.0
-
q
[
1
,
1
]
-
q
[
2
,
2
],
q
[
0
,
1
]
-
q
[
2
,
3
],
q
[
0
,
2
]
+
q
[
1
,
3
],
0.0
),
(
q
[
0
,
1
]
+
q
[
2
,
3
],
1.0
-
q
[
0
,
0
]
-
q
[
2
,
2
],
q
[
1
,
2
]
-
q
[
0
,
3
],
0.0
),
(
q
[
0
,
2
]
-
q
[
1
,
3
],
q
[
1
,
2
]
+
q
[
0
,
3
],
1.0
-
q
[
0
,
0
]
-
q
[
1
,
1
],
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
)
),
dtype
=
numpy
.
float64
)
def
quaternion_from_matrix
(
matrix
):
"""Return quaternion from rotation matrix.
>>> R = rotation_matrix(0.123, (1, 2, 3))
>>> q = quaternion_from_matrix(R)
>>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095])
True
"""
q
=
numpy
.
empty
((
4
,
),
dtype
=
numpy
.
float64
)
M
=
numpy
.
array
(
matrix
,
dtype
=
numpy
.
float64
,
copy
=
False
)[:
4
,
:
4
]
t
=
numpy
.
trace
(
M
)
if
t
>
M
[
3
,
3
]:
q
[
3
]
=
t
q
[
2
]
=
M
[
1
,
0
]
-
M
[
0
,
1
]
q
[
1
]
=
M
[
0
,
2
]
-
M
[
2
,
0
]
q
[
0
]
=
M
[
2
,
1
]
-
M
[
1
,
2
]
else
:
i
,
j
,
k
=
0
,
1
,
2
if
M
[
1
,
1
]
>
M
[
0
,
0
]:
i
,
j
,
k
=
1
,
2
,
0
if
M
[
2
,
2
]
>
M
[
i
,
i
]:
i
,
j
,
k
=
2
,
0
,
1
t
=
M
[
i
,
i
]
-
(
M
[
j
,
j
]
+
M
[
k
,
k
])
+
M
[
3
,
3
]
q
[
i
]
=
t
q
[
j
]
=
M
[
i
,
j
]
+
M
[
j
,
i
]
q
[
k
]
=
M
[
k
,
i
]
+
M
[
i
,
k
]
q
[
3
]
=
M
[
k
,
j
]
-
M
[
j
,
k
]
q
*=
0.5
/
math
.
sqrt
(
t
*
M
[
3
,
3
])
return
q
def
quaternion_multiply
(
quaternion1
,
quaternion0
):
"""Return multiplication of two quaternions.
>>> q = quaternion_multiply([1, -2, 3, 4], [-5, 6, 7, 8])
>>> numpy.allclose(q, [-44, -14, 48, 28])
True
"""
x0
,
y0
,
z0
,
w0
=
quaternion0
x1
,
y1
,
z1
,
w1
=
quaternion1
return
numpy
.
array
((
x1
*
w0
+
y1
*
z0
-
z1
*
y0
+
w1
*
x0
,
-
x1
*
z0
+
y1
*
w0
+
z1
*
x0
+
w1
*
y0
,
x1
*
y0
-
y1
*
x0
+
z1
*
w0
+
w1
*
z0
,
-
x1
*
x0
-
y1
*
y0
-
z1
*
z0
+
w1
*
w0
),
dtype
=
numpy
.
float64
)
def
quaternion_conjugate
(
quaternion
):
"""Return conjugate of quaternion.
>>> q0 = random_quaternion()
>>> q1 = quaternion_conjugate(q0)
>>> q1[3] == q0[3] and all(q1[:3] == -q0[:3])
True
"""
return
numpy
.
array
((
-
quaternion
[
0
],
-
quaternion
[
1
],
-
quaternion
[
2
],
quaternion
[
3
]),
dtype
=
numpy
.
float64
)
def
quaternion_inverse
(
quaternion
):
"""Return inverse of quaternion.
>>> q0 = random_quaternion()
>>> q1 = quaternion_inverse(q0)
>>> numpy.allclose(quaternion_multiply(q0, q1), [0, 0, 0, 1])
True
"""
return
quaternion_conjugate
(
quaternion
)
/
numpy
.
dot
(
quaternion
,
quaternion
)
def
quaternion_slerp
(
quat0
,
quat1
,
fraction
,
spin
=
0
,
shortestpath
=
True
):
"""Return spherical linear interpolation between two quaternions.
>>> q0 = random_quaternion()
>>> q1 = random_quaternion()
>>> q = quaternion_slerp(q0, q1, 0.0)
>>> numpy.allclose(q, q0)
True
>>> q = quaternion_slerp(q0, q1, 1.0, 1)
>>> numpy.allclose(q, q1)
True
>>> q = quaternion_slerp(q0, q1, 0.5)
>>> angle = math.acos(numpy.dot(q0, q))
>>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle) or
\
numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle)
True
"""
q0
=
unit_vector
(
quat0
[:
4
])
q1
=
unit_vector
(
quat1
[:
4
])
if
fraction
==
0.0
:
return
q0
elif
fraction
==
1.0
:
return
q1
d
=
numpy
.
dot
(
q0
,
q1
)
if
abs
(
abs
(
d
)
-
1.0
)
<
_EPS
:
return
q0
if
shortestpath
and
d
<
0.0
:
# invert rotation
d
=
-
d
q1
*=
-
1.0
angle
=
math
.
acos
(
d
)
+
spin
*
math
.
pi
if
abs
(
angle
)
<
_EPS
:
return
q0
isin
=
1.0
/
math
.
sin
(
angle
)
q0
*=
math
.
sin
((
1.0
-
fraction
)
*
angle
)
*
isin
q1
*=
math
.
sin
(
fraction
*
angle
)
*
isin
q0
+=
q1
return
q0
def
random_quaternion
(
rand
=
None
):
"""Return uniform random unit quaternion.
rand: array like or None
Three independent random variables that are uniformly distributed
between 0 and 1.
>>> q = random_quaternion()
>>> numpy.allclose(1.0, vector_norm(q))
True
>>> q = random_quaternion(numpy.random.random(3))
>>> q.shape
(4,)
"""
if
rand
is
None
:
rand
=
numpy
.
random
.
rand
(
3
)
else
:
assert
len
(
rand
)
==
3
r1
=
numpy
.
sqrt
(
1.0
-
rand
[
0
])
r2
=
numpy
.
sqrt
(
rand
[
0
])
pi2
=
math
.
pi
*
2.0
t1
=
pi2
*
rand
[
1
]
t2
=
pi2
*
rand
[
2
]
return
numpy
.
array
((
numpy
.
sin
(
t1
)
*
r1
,
numpy
.
cos
(
t1
)
*
r1
,
numpy
.
sin
(
t2
)
*
r2
,
numpy
.
cos
(
t2
)
*
r2
),
dtype
=
numpy
.
float64
)
def
random_rotation_matrix
(
rand
=
None
):
"""Return uniform random rotation matrix.
rnd: array like
Three independent random variables that are uniformly distributed
between 0 and 1 for each returned quaternion.
>>> R = random_rotation_matrix()
>>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
True
"""
return
quaternion_matrix
(
random_quaternion
(
rand
))
class
Arcball
(
object
):
"""Virtual Trackball Control.
>>> ball = Arcball()
>>> ball = Arcball(initial=numpy.identity(4))
>>> ball.place([320, 320], 320)
>>> ball.down([500, 250])
>>> ball.drag([475, 275])
>>> R = ball.matrix()
>>> numpy.allclose(numpy.sum(R), 3.90583455)
True
>>> ball = Arcball(initial=[0, 0, 0, 1])
>>> ball.place([320, 320], 320)
>>> ball.setaxes([1,1,0], [-1, 1, 0])
>>> ball.setconstrain(True)
>>> ball.down([400, 200])
>>> ball.drag([200, 400])
>>> R = ball.matrix()
>>> numpy.allclose(numpy.sum(R), 0.2055924)
True
>>> ball.next()
"""
def
__init__
(
self
,
initial
=
None
):
"""Initialize virtual trackball control.
initial : quaternion or rotation matrix
"""
self
.
_axis
=
None
self
.
_axes
=
None
self
.
_radius
=
1.0
self
.
_center
=
[
0.0
,
0.0
]
self
.
_vdown
=
numpy
.
array
([
0
,
0
,
1
],
dtype
=
numpy
.
float64
)
self
.
_constrain
=
False
if
initial
is
None
:
self
.
_qdown
=
numpy
.
array
([
0
,
0
,
0
,
1
],
dtype
=
numpy
.
float64
)
else
:
initial
=
numpy
.
array
(
initial
,
dtype
=
numpy
.
float64
)
if
initial
.
shape
==
(
4
,
4
):
self
.
_qdown
=
quaternion_from_matrix
(
initial
)
elif
initial
.
shape
==
(
4
,
):
initial
/=
vector_norm
(
initial
)
self
.
_qdown
=
initial
else
:
raise
ValueError
(
"initial not a quaternion or matrix."
)
self
.
_qnow
=
self
.
_qpre
=
self
.
_qdown
def
place
(
self
,
center
,
radius
):
"""Place Arcball, e.g. when window size changes.
center : sequence[2]
Window coordinates of trackball center.
radius : float
Radius of trackball in window coordinates.
"""
self
.
_radius
=
float
(
radius
)
self
.
_center
[
0
]
=
center
[
0
]
self
.
_center
[
1
]
=
center
[
1
]
def
setaxes
(
self
,
*
axes
):
"""Set axes to constrain rotations."""
if
axes
is
None
:
self
.
_axes
=
None
else
:
self
.
_axes
=
[
unit_vector
(
axis
)
for
axis
in
axes
]
def
setconstrain
(
self
,
constrain
):
"""Set state of constrain to axis mode."""
self
.
_constrain
=
constrain
==
True
def
getconstrain
(
self
):
"""Return state of constrain to axis mode."""
return
self
.
_constrain
def
down
(
self
,
point
):
"""Set initial cursor window coordinates and pick constrain-axis."""
self
.
_vdown
=
arcball_map_to_sphere
(
point
,
self
.
_center
,
self
.
_radius
)
self
.
_qdown
=
self
.
_qpre
=
self
.
_qnow
if
self
.
_constrain
and
self
.
_axes
is
not
None
:
self
.
_axis
=
arcball_nearest_axis
(
self
.
_vdown
,
self
.
_axes
)
self
.
_vdown
=
arcball_constrain_to_axis
(
self
.
_vdown
,
self
.
_axis
)
else
:
self
.
_axis
=
None
def
drag
(
self
,
point
):
"""Update current cursor window coordinates."""
vnow
=
arcball_map_to_sphere
(
point
,
self
.
_center
,
self
.
_radius
)
if
self
.
_axis
is
not
None
:
vnow
=
arcball_constrain_to_axis
(
vnow
,
self
.
_axis
)
self
.
_qpre
=
self
.
_qnow
t
=
numpy
.
cross
(
self
.
_vdown
,
vnow
)
if
numpy
.
dot
(
t
,
t
)
<
_EPS
:
self
.
_qnow
=
self
.
_qdown
else
:
q
=
[
t
[
0
],
t
[
1
],
t
[
2
],
numpy
.
dot
(
self
.
_vdown
,
vnow
)]
self
.
_qnow
=
quaternion_multiply
(
q
,
self
.
_qdown
)
def
next
(
self
,
acceleration
=
0.0
):
"""Continue rotation in direction of last drag."""
q
=
quaternion_slerp
(
self
.
_qpre
,
self
.
_qnow
,
2.0
+
acceleration
,
False
)
self
.
_qpre
,
self
.
_qnow
=
self
.
_qnow
,
q
def
matrix
(
self
):
"""Return homogeneous rotation matrix."""
return
quaternion_matrix
(
self
.
_qnow
)
def
arcball_map_to_sphere
(
point
,
center
,
radius
):
"""Return unit sphere coordinates from window coordinates."""
v
=
numpy
.
array
(((
point
[
0
]
-
center
[
0
])
/
radius
,
(
center
[
1
]
-
point
[
1
])
/
radius
,
0.0
),
dtype
=
numpy
.
float64
)
n
=
v
[
0
]
*
v
[
0
]
+
v
[
1
]
*
v
[
1
]
if
n
>
1.0
:
v
/=
math
.
sqrt
(
n
)
# position outside of sphere
else
:
v
[
2
]
=
math
.
sqrt
(
1.0
-
n
)
return
v
def
arcball_constrain_to_axis
(
point
,
axis
):
"""Return sphere point perpendicular to axis."""
v
=
numpy
.
array
(
point
,
dtype
=
numpy
.
float64
,
copy
=
True
)
a
=
numpy
.
array
(
axis
,
dtype
=
numpy
.
float64
,
copy
=
True
)
v
-=
a
*
numpy
.
dot
(
a
,
v
)
# on plane
n
=
vector_norm
(
v
)
if
n
>
_EPS
:
if
v
[
2
]
<
0.0
:
v
*=
-
1.0
v
/=
n
return
v
if
a
[
2
]
==
1.0
:
return
numpy
.
array
([
1
,
0
,
0
],
dtype
=
numpy
.
float64
)
return
unit_vector
([
-
a
[
1
],
a
[
0
],
0
])
def
arcball_nearest_axis
(
point
,
axes
):
"""Return axis, which arc is nearest to point."""
point
=
numpy
.
array
(
point
,
dtype
=
numpy
.
float64
,
copy
=
False
)
nearest
=
None
mx
=
-
1.0
for
axis
in
axes
:
t
=
numpy
.
dot
(
arcball_constrain_to_axis
(
point
,
axis
),
point
)
if
t
>
mx
:
nearest
=
axis
mx
=
t
return
nearest
# epsilon for testing whether a number is close to zero
_EPS
=
numpy
.
finfo
(
float
)
.
eps
*
4.0
# axis sequences for Euler angles
_NEXT_AXIS
=
[
1
,
2
,
0
,
1
]
# map axes strings to/from tuples of inner axis, parity, repetition, frame
_AXES2TUPLE
=
{
'sxyz'
:
(
0
,
0
,
0
,
0
),
'sxyx'
:
(
0
,
0
,
1
,
0
),
'sxzy'
:
(
0
,
1
,
0
,
0
),
'sxzx'
:
(
0
,
1
,
1
,
0
),
'syzx'
:
(
1
,
0
,
0
,
0
),
'syzy'
:
(
1
,
0
,
1
,
0
),
'syxz'
:
(
1
,
1
,
0
,
0
),
'syxy'
:
(
1
,
1
,
1
,
0
),
'szxy'
:
(
2
,
0
,
0
,
0
),
'szxz'
:
(
2
,
0
,
1
,
0
),
'szyx'
:
(
2
,
1
,
0
,
0
),
'szyz'
:
(
2
,
1
,
1
,
0
),
'rzyx'
:
(
0
,
0
,
0
,
1
),
'rxyx'
:
(
0
,
0
,
1
,
1
),
'ryzx'
:
(
0
,
1
,
0
,
1
),
'rxzx'
:
(
0
,
1
,
1
,
1
),
'rxzy'
:
(
1
,
0
,
0
,
1
),
'ryzy'
:
(
1
,
0
,
1
,
1
),
'rzxy'
:
(
1
,
1
,
0
,
1
),
'ryxy'
:
(
1
,
1
,
1
,
1
),
'ryxz'
:
(
2
,
0
,
0
,
1
),
'rzxz'
:
(
2
,
0
,
1
,
1
),
'rxyz'
:
(
2
,
1
,
0
,
1
),
'rzyz'
:
(
2
,
1
,
1
,
1
)}
_TUPLE2AXES
=
dict
((
v
,
k
)
for
k
,
v
in
_AXES2TUPLE
.
items
())
# helper functions
def
vector_norm
(
data
,
axis
=
None
,
out
=
None
):
"""Return length, i.e. eucledian norm, of ndarray along axis.
>>> v = numpy.random.random(3)
>>> n = vector_norm(v)
>>> numpy.allclose(n, numpy.linalg.norm(v))
True
>>> v = numpy.random.rand(6, 5, 3)
>>> n = vector_norm(v, axis=-1)
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
True
>>> n = vector_norm(v, axis=1)
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
True
>>> v = numpy.random.rand(5, 4, 3)
>>> n = numpy.empty((5, 3), dtype=numpy.float64)
>>> vector_norm(v, axis=1, out=n)
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
True
>>> vector_norm([])
0.0
>>> vector_norm([1.0])
1.0
"""
data
=
numpy
.
array
(
data
,
dtype
=
numpy
.
float64
,
copy
=
True
)
if
out
is
None
:
if
data
.
ndim
==
1
:
return
math
.
sqrt
(
numpy
.
dot
(
data
,
data
))
data
*=
data
out
=
numpy
.
atleast_1d
(
numpy
.
sum
(
data
,
axis
=
axis
))
numpy
.
sqrt
(
out
,
out
)
return
out
else
:
data
*=
data
numpy
.
sum
(
data
,
axis
=
axis
,
out
=
out
)
numpy
.
sqrt
(
out
,
out
)
def
unit_vector
(
data
,
axis
=
None
,
out
=
None
):
"""Return ndarray normalized by length, i.e. eucledian norm, along axis.
>>> v0 = numpy.random.random(3)
>>> v1 = unit_vector(v0)
>>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
True
>>> v0 = numpy.random.rand(5, 4, 3)
>>> v1 = unit_vector(v0, axis=-1)
>>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
>>> numpy.allclose(v1, v2)
True
>>> v1 = unit_vector(v0, axis=1)
>>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
>>> numpy.allclose(v1, v2)
True
>>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64)
>>> unit_vector(v0, axis=1, out=v1)
>>> numpy.allclose(v1, v2)
True
>>> list(unit_vector([]))
[]
>>> list(unit_vector([1.0]))
[1.0]
"""
if
out
is
None
:
data
=
numpy
.
array
(
data
,
dtype
=
numpy
.
float64
,
copy
=
True
)
if
data
.
ndim
==
1
:
data
/=
math
.
sqrt
(
numpy
.
dot
(
data
,
data
))
return
data
else
:
if
out
is
not
data
:
out
[:]
=
numpy
.
array
(
data
,
copy
=
False
)
data
=
out
length
=
numpy
.
atleast_1d
(
numpy
.
sum
(
data
*
data
,
axis
))
numpy
.
sqrt
(
length
,
length
)
if
axis
is
not
None
:
length
=
numpy
.
expand_dims
(
length
,
axis
)
data
/=
length
if
out
is
None
:
return
data
def
random_vector
(
size
):
"""Return array of random doubles in the half-open interval [0.0, 1.0).
>>> v = random_vector(10000)
>>> numpy.all(v >= 0.0) and numpy.all(v < 1.0)
True
>>> v0 = random_vector(10)
>>> v1 = random_vector(10)
>>> numpy.any(v0 == v1)
False
"""
return
numpy
.
random
.
random
(
size
)
def
inverse_matrix
(
matrix
):
"""Return inverse of square transformation matrix.
>>> M0 = random_rotation_matrix()
>>> M1 = inverse_matrix(M0.T)
>>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
True
>>> for size in range(1, 7):
... M0 = numpy.random.rand(size, size)
... M1 = inverse_matrix(M0)
... if not numpy.allclose(M1, numpy.linalg.inv(M0)): print size
"""
return
numpy
.
linalg
.
inv
(
matrix
)
def
concatenate_matrices
(
*
matrices
):
"""Return concatenation of series of transformation matrices.
>>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
>>> numpy.allclose(M, concatenate_matrices(M))
True
>>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T))
True
"""
M
=
numpy
.
identity
(
4
)
for
i
in
matrices
:
M
=
numpy
.
dot
(
M
,
i
)
return
M
def
is_same_transform
(
matrix0
,
matrix1
):
"""Return True if two matrices perform same transformation.
>>> is_same_transform(numpy.identity(4), numpy.identity(4))
True
>>> is_same_transform(numpy.identity(4), random_rotation_matrix())
False
"""
matrix0
=
numpy
.
array
(
matrix0
,
dtype
=
numpy
.
float64
,
copy
=
True
)
matrix0
/=
matrix0
[
3
,
3
]
matrix1
=
numpy
.
array
(
matrix1
,
dtype
=
numpy
.
float64
,
copy
=
True
)
matrix1
/=
matrix1
[
3
,
3
]
return
numpy
.
allclose
(
matrix0
,
matrix1
)
def
_import_module
(
module_name
,
warn
=
True
,
prefix
=
'_py_'
,
ignore
=
'_'
):
"""Try import all public attributes from module into global namespace.
Existing attributes with name clashes are renamed with prefix.
Attributes starting with underscore are ignored by default.
Return True on successful import.
"""
try
:
module
=
__import__
(
module_name
)
except
ImportError
:
if
warn
:
warnings
.
warn
(
"Failed to import module "
+
module_name
)
else
:
for
attr
in
dir
(
module
):
if
ignore
and
attr
.
startswith
(
ignore
):
continue
if
prefix
:
if
attr
in
globals
():
globals
()[
prefix
+
attr
]
=
globals
()[
attr
]
elif
warn
:
warnings
.
warn
(
"No Python implementation of "
+
attr
)
globals
()[
attr
]
=
getattr
(
module
,
attr
)
return
True
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment