Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
AmpScan
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
26
Issues
26
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Joshua Steer
AmpScan
Commits
2c63b0e8
Commit
2c63b0e8
authored
Jul 26, 2019
by
jp6g18
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into 'Omar'
# Conflicts: # AmpScan/ampVis.py # GUIs/AmpScanGUI.py
parents
5a608740
13398e31
Pipeline
#884
failed with stage
in 25 seconds
Changes
27
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
622 additions
and
168 deletions
+622
-168
.gitlab-ci.yml
.gitlab-ci.yml
+12
-2
AmpScan/align.py
AmpScan/align.py
+20
-15
AmpScan/ampVis.py
AmpScan/ampVis.py
+5
-3
AmpScan/core.py
AmpScan/core.py
+74
-20
AmpScan/registration.py
AmpScan/registration.py
+25
-20
AmpScan/smooth.py
AmpScan/smooth.py
+1
-1
AmpScan/ssm.py
AmpScan/ssm.py
+23
-20
AmpScan/trim.py
AmpScan/trim.py
+12
-3
CHANGELOG
CHANGELOG
+0
-0
CONTRIBUTING.md
CONTRIBUTING.md
+1
-0
GUIs/AmpScanGUI.py
GUIs/AmpScanGUI.py
+130
-71
docs/conf.py
docs/conf.py
+1
-1
docs/examples/Workflow1-Importstl.ipynb
docs/examples/Workflow1-Importstl.ipynb
+3
-3
docs/index.rst
docs/index.rst
+14
-7
joss/paper.md
joss/paper.md
+2
-2
tests/__init__py.py
tests/__init__py.py
+0
-0
tests/ascii_examples/sample_stl_sphere_ASCII.stl
tests/ascii_examples/sample_stl_sphere_ASCII.stl
+0
-0
tests/core_tests.md
tests/core_tests.md
+8
-0
tests/pca_tests/stl_file_3.stl
tests/pca_tests/stl_file_3.stl
+3
-0
tests/stl_file.stl
tests/stl_file.stl
+3
-0
tests/stl_file_2.stl
tests/stl_file_2.stl
+3
-0
tests/stl_file_3.stl
tests/stl_file_3.stl
+0
-0
tests/test_basics.py
tests/test_basics.py
+34
-0
tests/test_core.py
tests/test_core.py
+167
-0
tests/test_smoothing.py
tests/test_smoothing.py
+23
-0
tests/test_trim.py
tests/test_trim.py
+30
-0
tests/util.py
tests/util.py
+28
-0
No files found.
.gitlab-ci.yml
View file @
2c63b0e8
sample_job
:
script
:
python tests/sample_test.py
#doctests and unitests:
# script: pytest --doctest-modules -v --ignore=GUIs
# Temporarily added back old testing
unittests
:
script
:
python -m unittest discover tests -v
core doctests
:
script
:
python -m doctest -v AmpScan/core.py
registration doctests
:
script
:
python -m doctest -v AmpScan/registration.py
align doctests
:
script
:
python -m doctest -v AmpScan/align.py
AmpScan/align.py
View file @
2c63b0e8
...
...
@@ -10,8 +10,13 @@ import vtk
import
math
from
scipy
import
spatial
from
scipy.optimize
import
minimize
from
.core
import
AmpObject
from
.ampVis
import
vtkRenWin
from
AmpScan.core
import
AmpObject
from
AmpScan.ampVis
import
vtkRenWin
# For doc examples
import
os
staticfh
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file.stl"
movingfh
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file_2.stl"
class
align
(
object
):
...
...
@@ -41,9 +46,9 @@ class align(object):
Examples
--------
>>> static = Amp
Scan.Amp
Object(staticfh)
>>> moving = Amp
Scan.Amp
Object(movingfh)
>>> al =
AmpScan.
align(moving, static).m
>>> static = AmpObject(staticfh)
>>> moving = AmpObject(movingfh)
>>> al = align(moving, static).m
"""
...
...
@@ -131,7 +136,7 @@ class align(object):
R
=
Rs
[:,
:,
-
1
]
#Simpl
[
U
,
s
,
V
]
=
np
.
linalg
.
svd
(
R
)
R
=
np
.
dot
(
U
,
V
.
T
)
R
=
np
.
dot
(
U
,
V
)
self
.
tForm
=
np
.
r_
[
np
.
c_
[
R
,
np
.
zeros
(
3
)],
np
.
append
(
Ts
[:,
-
1
],
1
)[:,
None
].
T
]
self
.
R
=
R
self
.
T
=
Ts
[:,
-
1
]
...
...
@@ -176,9 +181,9 @@ class align(object):
Examples
--------
>>> static = Amp
Scan.Amp
Object(staticfh)
>>> moving = Amp
Scan.Amp
Object(movingfh)
>>> al =
AmpScan.
align(moving, static, method='linPoint2Plane').m
>>> static = AmpObject(staticfh)
>>> moving = AmpObject(movingfh)
>>> al = align(moving, static, method='linPoint2Plane').m
"""
cn
=
np
.
c_
[
np
.
cross
(
mv
,
sn
),
sn
]
...
...
@@ -229,9 +234,9 @@ class align(object):
Examples
--------
>>> static = Amp
Scan.Amp
Object(staticfh)
>>> moving = Amp
Scan.Amp
Object(movingfh)
>>> al =
AmpScan.
align(moving, static, method='linPoint2Point').m
>>> static = AmpObject(staticfh)
>>> moving = AmpObject(movingfh)
>>> al = align(moving, static, method='linPoint2Point').m
"""
mCent
=
mv
-
mv
.
mean
(
axis
=
0
)
...
...
@@ -271,9 +276,9 @@ class align(object):
Examples
--------
>>> static = Amp
Scan.Amp
Object(staticfh)
>>> moving = Amp
Scan.Amp
Object(movingfh)
>>> al =
AmpScan.
align(moving, static, method='optPoint2Point', opt='SLSQP').m
>>> static = AmpObject(staticfh)
>>> moving = AmpObject(movingfh)
>>> al = align(moving, static, method='optPoint2Point', opt='SLSQP').m
"""
X
=
np
.
zeros
(
6
)
...
...
AmpScan/ampVis.py
View file @
2c63b0e8
...
...
@@ -374,7 +374,7 @@ class visMixin(object):
def
genIm
(
self
,
size
=
[
512
,
512
],
views
=
[[
0
,
-
1
,
0
]],
background
=
[
1.0
,
1.0
,
1.0
],
projection
=
True
,
shading
=
True
,
mag
=
10
,
out
=
'im'
,
fh
=
'test.tiff'
,
zoom
=
1.0
,
az
=
0
,
el
=
0
,
crop
=
False
):
zoom
=
1.0
,
az
=
0
,
el
=
0
,
crop
=
False
,
cam
=
None
):
r
"""
Creates a temporary off screen vtkRenWin which is then either returned
as a numpy array or saved as a .png file
...
...
@@ -411,6 +411,7 @@ class visMixin(object):
self
.
addActor
()
# Generate a renderer window
win
=
vtkRenWin
()
win
.
OffScreenRenderingOn
()
# Set the number of viewports
win
.
setnumViewports
(
len
(
views
))
# Set the background colour
...
...
@@ -427,7 +428,8 @@ class visMixin(object):
# win.setProjection(projection, viewport=i)
win
.
renderActors
([
self
.
actor
,],
zoom
=
zoom
)
win
.
rens
[
0
].
GetActiveCamera
().
Azimuth
(
az
)
win
.
rens
[
0
].
GetActiveCamera
().
Elevation
(
el
)
if
cam
is
not
None
:
win
.
rens
[
0
].
SetActiveCamera
(
cam
)
win
.
Render
()
if
out
==
'im'
:
im
=
win
.
getImage
()
...
...
@@ -438,7 +440,7 @@ class visMixin(object):
mask
=
np
.
all
(
im
==
1
,
axis
=
2
)
mask
=
~
np
.
all
(
mask
,
axis
=
0
)
im
=
im
[:,
mask
,
:]
return
im
return
im
,
win
elif
out
==
'fh'
:
win
.
getScreenshot
(
fh
)
return
...
...
AmpScan/core.py
View file @
2c63b0e8
...
...
@@ -6,11 +6,17 @@ Copyright: Joshua Steer 2018, Joshua.Steer@soton.ac.uk
"""
import
numpy
as
np
import
os
import
struct
from
.trim
import
trimMixin
from
.smooth
import
smoothMixin
from
.analyse
import
analyseMixin
from
.ampVis
import
visMixin
from
AmpScan.trim
import
trimMixin
from
AmpScan.smooth
import
smoothMixin
from
AmpScan.analyse
import
analyseMixin
from
AmpScan.ampVis
import
visMixin
# The file path used in doc examples
filename
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file.stl"
class
AmpObject
(
trimMixin
,
smoothMixin
,
analyseMixin
,
visMixin
):
r
"""
...
...
@@ -36,8 +42,7 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
Examples
-------
>>> fh = 'test.stl'
>>> amp = AmpScan.AmpObject(fh)
>>> amp = AmpObject(filename)
"""
...
...
@@ -149,13 +154,12 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
Examples
--------
>>> fh = 'test.stl'
>>> amp = AmpObject(fh, unify=False)
>>> amp = AmpObject(filename, unify=False)
>>> amp.vert.shape
(
600
, 3)
(
44832
, 3)
>>> amp.unifyVert()
>>> amp.vert.shape
(
125
, 3)
(
7530
, 3)
"""
# Requires numpy 1.13
...
...
@@ -314,7 +318,16 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
Translation in [x, y, z]
"""
self
.
vert
[:]
+=
trans
# Check that trans is array like
if
isinstance
(
trans
,
(
list
,
np
.
ndarray
,
tuple
)):
# Check that trans has exactly 3 dimensions
if
len
(
trans
)
==
3
:
self
.
vert
[:]
+=
trans
else
:
raise
ValueError
(
"Translation has incorrect dimensions. Expected 3 but found: "
+
str
(
len
(
trans
)))
else
:
raise
TypeError
(
"Translation is not array_like: "
+
trans
)
def
centre
(
self
):
r
"""
...
...
@@ -337,10 +350,15 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
Examples
--------
>>> amp = AmpObject(
'test.stl'
)
>>> amp = AmpObject(
filename
)
>>> ang = [np.pi/2, -np.pi/4, np.pi/3]
>>> amp.rotateAng(ang, ang='rad')
"""
# Check that ang is valid
if
ang
not
in
(
'rad'
,
'deg'
):
raise
ValueError
(
"Ang expected 'rad' or 'deg' but {} was found"
.
format
(
ang
))
if
isinstance
(
rot
,
(
tuple
,
list
,
np
.
ndarray
)):
R
=
self
.
rotMatrix
(
rot
,
ang
)
self
.
rotate
(
R
,
norms
)
...
...
@@ -359,6 +377,18 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
norms: boolean, default True
"""
if
isinstance
(
R
,
(
list
,
tuple
)):
# Make R a np array if its a list or tuple
R
=
np
.
array
(
R
,
np
.
float
)
elif
not
isinstance
(
R
,
np
.
ndarray
):
# If
raise
TypeError
(
"Expected R to be array-like but found: "
+
str
(
type
(
R
)))
if
len
(
R
)
!=
3
or
len
(
R
[
0
])
!=
3
:
# Incorrect dimensions
if
isinstance
(
R
,
np
.
ndarray
):
raise
ValueError
(
"Expected 3x3 array, but found: {}"
.
format
(
R
.
shape
))
else
:
raise
ValueError
(
"Expected 3x3 array, but found: 3x"
+
str
(
len
(
R
)))
self
.
vert
[:,
:]
=
np
.
dot
(
self
.
vert
,
R
.
T
)
if
norms
is
True
:
self
.
norm
[:,
:]
=
np
.
dot
(
self
.
norm
,
R
.
T
)
...
...
@@ -380,9 +410,15 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
"""
if
R
is
not
None
:
self
.
rotate
(
R
,
True
)
if
isinstance
(
R
,
(
tuple
,
list
,
np
.
ndarray
)):
self
.
rotate
(
R
,
True
)
else
:
raise
TypeError
(
"Expecting array-like rotation, but found: "
+
type
(
R
))
if
T
is
not
None
:
self
.
translate
(
T
)
if
isinstance
(
T
,
(
tuple
,
list
,
np
.
ndarray
)):
self
.
translate
(
T
)
else
:
raise
TypeError
(
"Expecting array-like translation, but found: "
+
type
(
T
))
@
staticmethod
...
...
@@ -396,7 +432,7 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
rot: array_like
Rotation around [x, y, z]
ang: str, default 'rad'
Specif
t if the Euler angles are in degrees or radians
Specif
y if the Euler angles are in degrees or radians
Returns
-------
...
...
@@ -404,8 +440,20 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
The calculated 3x3 rotation matrix
"""
# Check that rot is valid
if
not
isinstance
(
rot
,
(
tuple
,
list
,
np
.
ndarray
)):
raise
TypeError
(
"Expecting array-like rotation, but found: "
+
type
(
rot
))
elif
len
(
rot
)
!=
3
:
raise
ValueError
(
"Expecting 3 arguments but found: {}"
.
format
(
len
(
rot
)))
# Check that ang is valid
if
ang
not
in
(
'rad'
,
'deg'
):
raise
ValueError
(
"Ang expected 'rad' or 'deg' but {} was found"
.
format
(
ang
))
if
ang
==
'deg'
:
rot
=
np
.
deg2rad
(
rot
)
[
angx
,
angy
,
angz
]
=
rot
Rx
=
np
.
array
([[
1
,
0
,
0
],
[
0
,
np
.
cos
(
angx
),
-
np
.
sin
(
angx
)],
...
...
@@ -429,8 +477,14 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin):
The axis in which to flip the mesh
"""
self
.
vert
[:,
axis
]
*=
-
1.0
# Switch face order to normals face same direction
self
.
faces
[:,
[
1
,
2
]]
=
self
.
faces
[:,
[
2
,
1
]]
self
.
calcNorm
()
self
.
calcVNorm
()
if
isinstance
(
axis
,
int
):
if
0
<=
axis
<
3
:
# Check axis is between 0-2
self
.
vert
[:,
axis
]
*=
-
1.0
# Switch face order to normals face same direction
self
.
faces
[:,
[
1
,
2
]]
=
self
.
faces
[:,
[
2
,
1
]]
self
.
calcNorm
()
self
.
calcVNorm
()
else
:
raise
ValueError
(
"Expected axis to be within range 0-2 but found: {}"
.
format
(
axis
))
else
:
raise
TypeError
(
"Expected axis to be int, but found: {}"
.
format
(
type
(
axis
)))
AmpScan/registration.py
View file @
2c63b0e8
...
...
@@ -6,9 +6,14 @@ Copyright: Joshua Steer 2018, Joshua.Steer@soton.ac.uk
import
numpy
as
np
import
copy
from
scipy
import
spatial
from
.core
import
AmpObject
from
AmpScan
.core
import
AmpObject
import
matplotlib.pyplot
as
plt
# For the doc examples
import
os
basefh
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file.stl"
targfh
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file_2.stl"
class
registration
(
object
):
r
"""
Registration methods between two AmpObject meshes. This function morphs the baseline
...
...
@@ -36,9 +41,10 @@ class registration(object):
Examples
--------
>>> baseline = AmpScan.AmpObject(basefh)
>>> target = AmpScan.AmpObject(targfh)
>>> reg = AmpScan.registration(steps=10, neigh=10, smooth=1).reg
>>> from AmpScan.core import AmpObject
>>> baseline = AmpObject(basefh)
>>> target = AmpObject(targfh)
>>> reg = registration(baseline, target, steps=10, neigh=10, smooth=1).reg
"""
def
__init__
(
self
,
baseline
,
target
,
method
=
'point2plane'
,
*
args
,
**
kwargs
):
...
...
@@ -86,53 +92,52 @@ class registration(object):
[
self
.
b
.
vert
,
self
.
b
.
faces
,
self
.
b
.
values
]))
regData
=
copy
.
deepcopy
(
bData
)
self
.
reg
=
AmpObject
(
regData
,
stype
=
'reg'
)
self
.
disp
=
AmpObject
({
'vert'
:
np
.
zeros
(
self
.
reg
.
vert
.
shape
),
'faces'
:
self
.
reg
.
faces
,
'values'
:
self
.
reg
.
values
})
if
scale
is
not
None
:
tmin
=
self
.
t
.
vert
.
min
(
axis
=
0
)[
2
]
rmin
=
self
.
reg
.
vert
.
min
(
axis
=
0
)[
2
]
SF
=
((
tmin
-
scale
)
/
(
rmin
-
scale
))
-
1
logic
=
self
.
reg
.
vert
[:,
2
]
<
scale
d
=
(
self
.
reg
.
vert
[
logic
,
2
]
-
scale
)
*
SF
self
.
reg
.
vert
[
logic
,
2
]
+=
d
self
.
disp
.
vert
[
logic
,
2
]
+=
d
self
.
reg
.
vert
=
self
.
b
.
vert
+
self
.
disp
.
vert
normals
=
np
.
cross
(
self
.
t
.
vert
[
self
.
t
.
faces
[:,
1
]]
-
self
.
t
.
vert
[
self
.
t
.
faces
[:,
0
]],
self
.
t
.
vert
[
self
.
t
.
faces
[:,
2
]]
-
self
.
t
.
vert
[
self
.
t
.
faces
[:,
0
]])
mag
=
(
normals
**
2
).
sum
(
axis
=
1
)
if
subset
is
None
:
rVert
=
self
.
reg
.
vert
else
:
rVert
=
self
.
reg
.
vert
[
subset
]
for
step
in
np
.
arange
(
steps
,
0
,
-
1
,
dtype
=
float
):
# Index of 10 centroids nearest to each baseline vertex
ind
=
tTree
.
query
(
rVert
,
neigh
)[
1
]
# D = np.zeros(self.reg.vert.shape)
ind
=
tTree
.
query
(
self
.
reg
.
vert
,
neigh
)[
1
]
# Define normals for faces of nearest faces
norms
=
normals
[
ind
]
# Get a point on each face
fPoints
=
self
.
t
.
vert
[
self
.
t
.
faces
[
ind
,
0
]]
# Calculate dot product between point on face and normals
d
=
np
.
einsum
(
'ijk, ijk->ij'
,
norms
,
fPoints
)
t
=
(
d
-
np
.
einsum
(
'ijk, ik->ij'
,
norms
,
rV
ert
))
/
mag
[
ind
]
t
=
(
d
-
np
.
einsum
(
'ijk, ik->ij'
,
norms
,
self
.
reg
.
v
ert
))
/
mag
[
ind
]
# Calculate the vector from old point to new point
G
=
rV
ert
[:,
None
,
:]
+
np
.
einsum
(
'ijk, ij->ijk'
,
norms
,
t
)
G
=
self
.
reg
.
v
ert
[:,
None
,
:]
+
np
.
einsum
(
'ijk, ij->ijk'
,
norms
,
t
)
# Ensure new points lie inside points otherwise set to 99999
# Find smallest distance from old to new point
if
inside
is
False
:
G
=
G
-
rV
ert
[:,
None
,
:]
G
=
G
-
self
.
reg
.
v
ert
[:,
None
,
:]
GMag
=
np
.
sqrt
(
np
.
einsum
(
'ijk, ijk->ij'
,
G
,
G
))
GInd
=
GMag
.
argmin
(
axis
=
1
)
else
:
G
,
GInd
=
self
.
__calcBarycentric
(
rV
ert
,
G
,
ind
)
G
,
GInd
=
self
.
__calcBarycentric
(
self
.
reg
.
v
ert
,
G
,
ind
)
# Define vector from baseline point to intersect point
D
=
G
[
np
.
arange
(
len
(
G
)),
GInd
,
:]
rVert
+=
D
/
step
# rVert += D/step
self
.
disp
.
vert
+=
D
/
step
if
smooth
>
0
and
step
>
1
:
# v = self.reg.vert[~subset]
self
.
reg
.
lp_smooth
(
smooth
,
brim
=
fixBrim
)
# self.reg.vert[~subset] = v
self
.
disp
.
lp_smooth
(
smooth
,
brim
=
fixBrim
)
self
.
reg
.
vert
=
self
.
b
.
vert
+
self
.
disp
.
vert
else
:
self
.
reg
.
vert
=
self
.
b
.
vert
+
self
.
disp
.
vert
self
.
reg
.
calcNorm
()
self
.
reg
.
calcStruct
()
self
.
reg
.
values
[:]
=
self
.
calcError
(
error
)
...
...
AmpScan/smooth.py
View file @
2c63b0e8
...
...
@@ -52,7 +52,7 @@ class smoothMixin(object):
def
smoothValues
(
self
,
n
=
1
):
"""
Function to apply a simple laplacian smooth to the values array.
Identical to the vertex smoothing ex
pect it applies the smoothing
Identical to the vertex smoothing ex
cept it applies the smoothing
to the values
Parameters
...
...
AmpScan/ssm.py
View file @
2c63b0e8
...
...
@@ -9,6 +9,7 @@ import os
import
numpy
as
np
from
.core
import
AmpObject
from
.registration
import
registration
import
os
class
pca
(
object
):
...
...
@@ -18,20 +19,20 @@ class pca(object):
Examples
--------
>>> import os
>>> p = pca()
>>> p.importFolder(
'/path/'
)
>>> p.
baseline('dir/baselinefh.stl'
)
>>> p.register(save
= '/regpath/'
)
>>> p.importFolder(
os.getcwd()+"\\tests\\pca_tests"
)
>>> p.
setBaseline(os.getcwd()+"\\tests\\stl_file_3.stl"
)
>>> p.register(save
=os.getcwd()+"\\tests\\pca_tests\\"
)
>>> p.pca()
>>> sfs = [
0, 0.1, -0.5 ... 0
]
>>> sfs = [
1, 2
]
>>> newS = p.newShape(sfs)
"""
def
__init__
(
self
):
self
.
shapes
=
[]
def
setBaseline
(
self
,
baseline
):
r
"""
Function to set the baseline mesh used for registration of the
...
...
@@ -44,8 +45,7 @@ class pca(object):
"""
self
.
baseline
=
AmpObject
(
baseline
,
'limb'
)
def
importFolder
(
self
,
path
,
unify
=
True
):
r
"""
Function to import multiple stl files from folder into the pca object
...
...
@@ -60,10 +60,10 @@ class pca(object):
"""
self
.
fnames
=
[
f
for
f
in
os
.
listdir
(
path
)
if
f
.
endswith
(
'.stl'
)]
self
.
shapes
=
[
AmpObject
(
path
+
f
,
'limb'
,
unify
=
unify
)
for
f
in
self
.
fnames
]
self
.
shapes
=
[
AmpObject
(
os
.
path
.
join
(
path
,
f
)
,
'limb'
,
unify
=
unify
)
for
f
in
self
.
fnames
]
for
s
in
self
.
shapes
:
s
.
lp_smooth
(
3
,
brim
=
True
)
def
sliceFiles
(
self
,
height
):
r
"""
Function to run a planar trim on all the training data for the PCA
...
...
@@ -77,7 +77,7 @@ class pca(object):
"""
for
s
in
self
.
shapes
:
s
.
planarTrim
(
height
)
def
register
(
self
,
scale
=
None
,
save
=
None
,
baseline
=
True
):
r
"""
Register all the AmpObject training data to the baseline AmpObject
...
...
@@ -100,12 +100,11 @@ class pca(object):
self
.
registered
.
append
(
r
)
if
save
is
not
None
:
for
f
,
r
in
zip
(
self
.
fnames
,
self
.
registered
):
r
.
save
(
save
+
f
)
r
.
save
(
os
.
path
.
join
(
save
,
f
)
)
self
.
X
=
np
.
array
([
r
.
vert
.
flatten
()
for
r
in
self
.
registered
]).
T
if
baseline
is
True
:
self
.
X
=
np
.
c_
[
self
.
X
,
self
.
baseline
.
vert
.
flatten
()]
def
pca
(
self
):
r
"""
Function to run mean centered pca using a singular value decomposition
...
...
@@ -117,8 +116,8 @@ class pca(object):
(
self
.
pca_U
,
self
.
pca_S
,
self
.
pca_V
)
=
np
.
linalg
.
svd
(
X_meanC
,
full_matrices
=
False
)
self
.
pc_weights
=
np
.
dot
(
np
.
diag
(
self
.
pca_S
),
self
.
pca_V
)
self
.
pc_stdevs
=
np
.
std
(
self
.
pc_weights
,
axis
=
1
)
def
newShape
(
self
,
sfs
,
scale
=
'eigs'
):
def
newShape
(
self
,
sfs
,
scale
=
'eigs'
):
r
"""
Function to calculate a new shape based upon the eigenvalues
or stdevs
...
...
@@ -133,11 +132,15 @@ class pca(object):
to standard deviations about the mean
"""
try
:
len
(
sfs
)
==
len
(
self
.
pc_stdevs
)
except
:
ValueError
(
'sfs must be of the same length as the number of '
'principal components'
)
if
not
isinstance
(
sfs
,
(
list
,
tuple
,
np
.
ndarray
)):
raise
TypeError
(
'sfs is invalid type (expected array-like, found: {}'
.
format
(
type
(
sfs
)))
if
len
(
sfs
)
!=
len
(
self
.
pc_stdevs
):
raise
ValueError
(
'sfs must be of the same length as the number of '
'principal components (expected {} but found {})'
.
format
(
len
(
self
.
pc_stdevs
),
len
(
sfs
)))
if
scale
==
'eigs'
:
sf
=
(
self
.
pca_U
*
sfs
).
sum
(
axis
=
1
)
elif
scale
==
'std'
:
sf
=
(
self
.
pca_U
*
self
.
pc_stdevs
*
sfs
).
sum
(
axis
=
1
)
else
:
raise
ValueError
(
"Invalid scale (expected 'eigs' or 'std' but found{}"
.
format
(
scale
))
return
self
.
pca_mean
+
sf
AmpScan/trim.py
View file @
2c63b0e8
...
...
@@ -5,6 +5,12 @@ Copyright: Joshua Steer 2018, Joshua.Steer@soton.ac.uk
"""
import
numpy
as
np
from
numbers
import
Number
import
os
# Used by doc tests
filename
=
os
.
getcwd
()
+
"
\\
tests
\\
stl_file.stl"
class
trimMixin
(
object
):
r
"""
...
...
@@ -26,11 +32,13 @@ class trimMixin(object):
Examples
--------
>>> amp = AmpObject(fh)
>>> from AmpScan import AmpObject
>>> amp = AmpObject(filename)
>>> amp.planarTrim(100, 2)
"""
if
isinstance
(
height
,
float
)
and
isinstance
(
plane
,
int
):
if
isinstance
(
height
,
Number
)
and
isinstance
(
plane
,
int
):
# planar values for each vert on face
fv
=
self
.
vert
[
self
.
faces
,
plane
]
# Number points on each face are above cut plane
...
...
@@ -49,6 +57,7 @@ class trimMixin(object):
self
.
faces
=
self
.
faces
[
fvlogic
!=
3
,
:]
self
.
faces
=
vInd
[
self
.
faces
]
self
.
vert
=
self
.
vert
[
~
delv
,
:]
self
.
values
=
self
.
values
[
~
delv
]
self
.
calcStruct
()
else
:
raise
TypeError
(
"height arg must be a float and plane arg must be an int"
)
\ No newline at end of file
raise
TypeError
(
"height arg must be a float"
)
CHANGELOG
0 → 100644
View file @
2c63b0e8
CONTRIBUTING.md
0 → 100644
View file @
2c63b0e8
To contribute to the open source AmpScan package
\ No newline at end of file
GUIs/AmpScanGUI.py
View file @
2c63b0e8
...
...
@@ -10,9 +10,9 @@ from PyQt5.QtGui import (QColor, QFontMetrics, QImage, QPainter, QIcon,
QOpenGLVersionProfile
)
from
PyQt5.QtWidgets
import
(
QAction
,
QApplication
,
QGridLayout
,
QHBoxLayout
,
QMainWindow
,
QMessageBox
,
QComboBox
,
QButtonGroup
,
QOpenGLWidget
,
QFileDialog
,
QLabel
,
QPushButton
,
QOpenGLWidget
,
QFileDialog
,
QLabel
,
QPushButton
,
QSlider
,
QWidget
,
QTableWidget
,
QTableWidgetItem
,
QAbstractButton
,
QCheckBox
)
QAbstractButton
,
QCheckBox
,
QErrorMessage
)
class
AmpScanGUI
(
QMainWindow
):
...
...
@@ -113,16 +113,19 @@ class AmpScanGUI(QMainWindow):
Numpy style docstring.
"""
self
.
alCont
=
AlignControls
(
self
.
filesDrop
,
self
)
self
.
alCont
.
show
()
self
.
alCont
.
centre
.
clicked
.
connect
(
self
.
centreMesh
)
self
.
alCont
.
icp
.
clicked
.
connect
(
self
.
runICP
)
self
.
alCont
.
xrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatex
)
self
.
alCont
.
yrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatey
)
self
.
alCont
.
zrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatez
)
self
.
alCont
.
xtraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transx
)
self
.
alCont
.
ytraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transy
)
self
.
alCont
.
ztraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transz
)
if
self
.
objectsReady
(
1
):
self
.
alCont
=
AlignControls
(
self
.
filesDrop
,
self
)
self
.
alCont
.
show
()
self
.
alCont
.
centre
.
clicked
.
connect
(
self
.
centreMesh
)
self
.
alCont
.
icp
.
clicked
.
connect
(
self
.
runICP
)
self
.
alCont
.
xrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatex
)
self
.
alCont
.
yrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatey
)
self
.
alCont
.
zrotButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
rotatez
)
self
.
alCont
.
xtraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transx
)
self
.
alCont
.
ytraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transy
)
self
.
alCont
.
ztraButton
.
buttonClicked
[
QAbstractButton
].
connect
(
self
.
transz
)
else
:
show_message
(
"Must be at least 1 object loaded to run align"
)
def
Point_Pick
(
self
):
"""
...
...
@@ -155,7 +158,6 @@ class AmpScanGUI(QMainWindow):
self
.
pnt
=
None
vtkRenWin
.
delMarker
(
self
.
renWin
)
def
rotatex
(
self
,
button
):
moving
=
str
(
self
.
alCont
.
moving
.
currentText
())
ang
=
float
(
button
.
text
())
...
...
@@ -216,59 +218,64 @@ class AmpScanGUI(QMainWindow):
self
.
renWin
.
Render
()
def
runICP
(
self
):
static
=
str
(
self
.
alCont
.
static
.
currentText
())
moving
=
str
(
self
.
alCont
.
moving
.
currentText
())
al
=
align
(
self
.
files
[
moving
],
self
.
files
[
static
],
maxiter
=
10
,
method
=
'linPoint2Plane'
).
m
al
.
tform
=
vtk
.
vtkTransform
()
al
.
tform
.
PostMultiply
()
al
.
addActor
()