Commit 6ae2cffe authored by Richard Torenvliet's avatar Richard Torenvliet

Refactored code to keep things a bit more seperate

parent d63e9150
...@@ -37,5 +37,4 @@ show_reconstruction: ...@@ -37,5 +37,4 @@ show_reconstruction:
--n_components 6 --n_components 6
test: test:
py.test -f src/*_test.py python -m py.test -f src/*_test.py
from matplotlib.tri import Triangulation
import numpy as np import numpy as np
import cv2
import pca
def get_mean(imm_points): def get_mean(imm_points):
...@@ -33,3 +37,57 @@ def get_mean(imm_points): ...@@ -33,3 +37,57 @@ def get_mean(imm_points):
mean. [[x_mean_0, y_mean_0], ... [x_mean_n, y_mean_n]] mean. [[x_mean_0, y_mean_0], ... [x_mean_n, y_mean_n]]
""" """
return np.mean(imm_points, axis=0) return np.mean(imm_points, axis=0)
def get_triangles(x_vector, y_vector):
""" perform triangulation between two 2d vectors"""
return Triangulation(x_vector, y_vector).triangles
def build_feature_vectors(files, get_points, flattened=False):
"""
Gets the aam points from the files and appends them seperately to one
array.
Args:
files (list): list files
return:
list: list of feature vectors
"""
points = get_points(files)
if flattened:
points = pca.flatten_feature_vectors(points)
return points
def get_pixel_values(image, points):
h, w, c = image.shape
points[:, 0] = points[:, 0] * w
points[:, 1] = points[:, 1] * h
image = cv2.blur(image, (3, 3))
hull = cv2.convexHull(points, returnPoints=True)
rect = cv2.boundingRect(hull)
pixels = []
x, y, w, h = rect
# pixels = np.zeros((h, w, c), dtype=np.uint8)
for i in np.linspace(0, 1, num=100):
for j in np.linspace(0, 1, num=100):
y_loc_g = int(i * h + y)
x_loc_g = int(j * w + x)
y_loc = min(int(i * h), h - 1)
x_loc = min(int(j * w), w - 1)
if cv2.pointPolygonTest(hull, (x_loc_g, y_loc_g), measureDist=False) >= 0:
pixels.extend(image[y_loc_g][x_loc_g])
return np.asarray(pixels, dtype=np.uint8), hull
import numpy as np import numpy as np
from aam import get_mean from aam import get_mean, get_pixel_values
import imm_points
import pca
def test_build_mean_aan(): def test_build_mean_aan():
...@@ -39,3 +41,15 @@ def test_zero_mean_aan(): ...@@ -39,3 +41,15 @@ def test_zero_mean_aan():
) )
np.testing.assert_array_equal(zero_mean, expected) np.testing.assert_array_equal(zero_mean, expected)
def test_get_pixel_values():
asf_file = '../data/imm_face_db/40-2m.asf'
imm = imm_points.IMMPoints(filename=asf_file)
points = imm.get_points()
image = imm.get_image()
pixels, hull = get_pixel_values(image, points)
assert False
from matplotlib.tri import Triangulation from matplotlib.tri import Triangulation
import cv2 import cv2
import numpy as np import numpy as np
import argparse import argparse
import os
class IMMPoints(): class IMMPoints():
...@@ -21,12 +23,16 @@ class IMMPoints(): ...@@ -21,12 +23,16 @@ class IMMPoints():
def get_points(self): def get_points(self):
return self.points return self.points
def get_image(self):
cv2.imread(self.image_file)
return cv2.imread(self.image_file)
def import_file(self, filename): def import_file(self, filename):
with open(filename, 'r') as f: with open(filename, 'r') as f:
lines = f.readlines() lines = f.readlines()
# store the filename we've got
self.filename = lines[-1].strip()
data = lines[16:74] data = lines[16:74]
dir_name = os.path.dirname(filename)
self.image_file = "{}/{}".format(dir_name, lines[-1].strip())
for d in data: for d in data:
self.points.append(d.split()[2:4]) self.points.append(d.split()[2:4])
...@@ -34,7 +40,6 @@ class IMMPoints(): ...@@ -34,7 +40,6 @@ class IMMPoints():
self.points = np.asarray(self.points, dtype='f') self.points = np.asarray(self.points, dtype='f')
def draw_triangles(self, img, points): def draw_triangles(self, img, points):
assert(len(self.points) > 0)
h, w, c = img.shape h, w, c = img.shape
points[:, 0] = points[:, 0] * w points[:, 0] = points[:, 0] * w
...@@ -63,69 +68,19 @@ class IMMPoints(): ...@@ -63,69 +68,19 @@ class IMMPoints():
assert(len(self.points) > 0) assert(len(self.points) > 0)
assert(len(self.filename) > 0) assert(len(self.filename) > 0)
img = cv2.imread('data/imm_face_db/' + self.filename) img = self.get_image()
self.draw_triangles(img, self.points) self.draw_triangles(img, self.points)
def flatten_feature_vectors(data): def get_imm_landmarks(files):
""" points = []
Flattens the feature vectors inside a ndarray
Example:
input:
[
[[1, 2], [3, 4], [5, 6]],
...
[[1, 2], [3, 4], [5, 6]]
]
output:
[
[1, 2, 3, 4, 5, 6],
...
[1, 2, 3, 4, 5, 6]
]
Args:
data (numpy array): array of feature vectors
return:
array: (numpy array): array flattened feature vectors
"""
flattened = []
rows, _, _ = data.shape
for i in range(rows):
flattened.append(np.ndarray.flatten(data[i]))
return np.array(flattened)
def build_feature_vectors(files, flattened=False):
"""
Gets the aam points from the files and appends them seperately to one
array.
Args:
files (list): list files
return:
list: list of feature vectors
"""
imm_points = []
for f in files: for f in files:
imm = IMMPoints(filename=f) imm = IMMPoints(filename=f)
imm_points.append(imm.get_points()) points.append(imm.get_points())
imm_points = np.array(imm_points)
if flattened:
imm_points = flatten_feature_vectors(imm_points)
return imm_points return np.asarray(points)
def add_parser_options(): def add_parser_options():
......
import numpy as np
from imm_points import flatten_feature_vectors
def test_flatten_feature_vectors():
imm_points = np.array([
[[1, 2], [2, 4]],
[[2, 3], [3, 6]],
])
expected = np.array([
[1, 2, 2, 4],
[2, 3, 3, 6]
])
result = flatten_feature_vectors(imm_points)
np.testing.assert_array_equal(result, expected)
import copy
import argparse import argparse
import logging import logging
import sys import sys
import cv2
import numpy as np
# local imports # local imports
import pca import pca
import aam
from aam import get_mean import imm_points as imm
from imm_points import IMMPoints, build_feature_vectors, \
flatten_feature_vectors
logging.basicConfig(level=logging.INFO, logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(name)s: %(message)s') format='%(asctime)s %(levelname)s %(name)s: %(message)s')
...@@ -20,7 +14,6 @@ logger = logging.getLogger(__name__) ...@@ -20,7 +14,6 @@ logger = logging.getLogger(__name__)
def add_parser_options(): def add_parser_options():
parser = argparse.ArgumentParser(description='IMMPoints tool') parser = argparse.ArgumentParser(description='IMMPoints tool')
pca_group = parser.add_argument_group('show_pca') pca_group = parser.add_argument_group('show_pca')
pca_group.add_argument( pca_group.add_argument(
...@@ -63,27 +56,26 @@ def save_pca_model(args): ...@@ -63,27 +56,26 @@ def save_pca_model(args):
It is saved in the following way: It is saved in the following way:
np.load(filename, np.assary([U, s, Vt, mean_values]) np.load(filename, np.assary([Vt, mean_values])
And accessed by: And accessed by:
UsVtm = np.load(args.model_file) Vtm = np.load(args.model_file)
U = UsVtm[0] Vt = Vtm[0]
s = UsVtm[1] mean_values = Vtm[1][0]
Vt = UsVtm[2]
mean_values = UsVtm[3]
""" """
assert args.asf, '--asf files should be given' assert args.asf, '--asf files should be given'
assert args.model_file, '--model_file needs to be provided to save the pca model' assert args.model_file, '--model_file needs to be provided to save the pca model'
imm_points = build_feature_vectors(args.asf, flattened=True) points = aam.build_feature_vectors(args.asf,
mean_values = get_mean(imm_points) imm.get_imm_landmarks, flattened=True)
mean_values = aam.get_mean(points)
U, s, Vt = pca.pca(imm_points, mean_values) _, _, Vt = pca.pca(points, mean_values)
pca.save(U, s, Vt, mean_values, args.model_file) pca.save(Vt, mean_values, args.model_file)
logger.info('saved pca model in %s', args.model_file) logger.info('saved pca model in %s', args.model_file)
...@@ -92,59 +84,21 @@ def show_pca_model(args): ...@@ -92,59 +84,21 @@ def show_pca_model(args):
assert args.asf, '--asf files should be given to allow the image to be shown' assert args.asf, '--asf files should be given to allow the image to be shown'
assert args.model_file, '--model_file needs to be provided to get the pca model' assert args.model_file, '--model_file needs to be provided to get the pca model'
U, s, Vt, mean_values = pca.load(args.model_file) Vt, mean_values = pca.load(args.model_file)
# init trackbars
# index = 0
# cv2.namedWindow('index')
# cv2.createTrackbar('index', 'index', index, len(args.asf) - 1, trackbarUpdate)
n_components = args.n_components
view.init_eigenvalue_trackbars(n_components, s, window='eigenvalues')
# use a copy of s to manipulate so we never touch the original
s_copy = copy.copy(s)
reconstruction = np.dot(Vt[:n_components], x - mean_values)
while True:
imm = IMMPoints(filename=args.asf[index])
reconstruction = np.dot(V[:n_components], x - mean_values)
# reconstruction = pca.reconstruct(U[index], s_copy, Vt, n_components, mean_values)
# reshape to x, y values
reconstructed = reconstruction.reshape((58, 2))
imm = IMMPoints(points=reconstructed)
img = np.full((480, 640, 3), 255, np.uint8)
imm.show_on_img(img)
for i in range(n_components):
s_copy[i] = s[i] + (
(cv2.getTrackbarPos(str(i), 'eigenvalues') - 50) / 10.0)
index = cv2.getTrackbarPos('index', 'index')
imm.show(window_name='original')
k = cv2.waitKey(1) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
def reconstruct_with_model(args): def reconstruct_with_model(args):
assert args.asf, '--asf files should be given to allow the image to be shown' assert args.asf, '--asf files should be given to allow the image to be shown'
assert args.model_file, '--model_file needs to be provided to get the pca model' assert args.model_file, '--model_file needs to be provided to get the pca model'
# clear args. arguments are conflicting with parseargs # clear sys args. arguments are conflicting with parseargs
# kivy will parse args upon import and will crash if it finds our # kivy will parse args upon import and will crash if it finds our
# 'unsuported by kivy' arguments. # 'unsupported by kivy' arguments.
sys.argv[1:] = [] sys.argv[1:] = []
from view.reconstruct import ReconstructApp from view.reconstruct import ReconstructApp
U, s, Vt, mean_values = pca.load(args.model_file) Vt, mean_values = pca.load(args.model_file)
ReconstructApp( ReconstructApp(
args=args, eigen_vectors=Vt, mean_values=mean_values, args=args, eigen_vectors=Vt, mean_values=mean_values,
).run() ).run()
......
...@@ -2,6 +2,14 @@ import numpy as np ...@@ -2,6 +2,14 @@ import numpy as np
def pca(data, mean_values): def pca(data, mean_values):
"""
Perform Singlar Value Decomposition
Returns:
U (ndarray): U matrix
s (ndarray): 1d singular values (diagonal in array form)
Vt (ndarray): Vt matrix
"""
# subtract mean # subtract mean
zero_mean = data - mean_values zero_mean = data - mean_values
U, s, Vt = np.linalg.svd(zero_mean, full_matrices=False) U, s, Vt = np.linalg.svd(zero_mean, full_matrices=False)
...@@ -28,24 +36,23 @@ def reconstruct(feature_vector, Vt, mean_values, n_components=10): ...@@ -28,24 +36,23 @@ def reconstruct(feature_vector, Vt, mean_values, n_components=10):
return np.dot(Vt[:n_components].T, yk) + mean_values return np.dot(Vt[:n_components].T, yk) + mean_values
def save(U, s, Vt, mean_values, filename): def save(Vt, mean_values, filename):
""" """
Store the U, s, Vt and mean of all the asf datafiles given by the asf Store the U, s, Vt and mean of all the asf datafiles given by the asf
files. files.
It is stored in the following way: It is stored in the following way:
np.load(filename, np.assary([U, s, Vt, mean_values]) np.load(filename, np.assary([Vt, [mean_values]])
And accessed by: And accessed by:
UsVtm = np.load(args.model_file) Vtm = np.load(args.model_file)
U = UsVtm[0] Vt = Vtm[0]
s = UsVtm[1] mean_values = Vtm[1][0]
Vt = UsVtm[2]
mean_values = UsVtm[3]
""" """
np.save(filename, np.asarray([U, s, Vt, mean_values])) saving = np.asarray([Vt, [mean_values]])
np.save(filename, saving)
def load(filename): def load(filename):
...@@ -53,28 +60,56 @@ def load(filename): ...@@ -53,28 +60,56 @@ def load(filename):
The model stored by pca.store (see ``pca.store`` method above) is loaded as: The model stored by pca.store (see ``pca.store`` method above) is loaded as:
UsVtm = np.load(args.model_file) UsVtm = np.load(args.model_file)
U = UsVtm[0] Vt = Vtm[0]
s = UsVtm[1] mean_values = Vtm[1][0]
Vt = UsVtm[2]
mean_values = UsVtm[3]
Returns: Returns:
(tuple): U, s, Vt, mean_values (tuple): Vt, mean_values
U (numpy ndarray): One feature vector from the reduced SVD.
U should have shape (n_features,), (i.e., one dimensional)
s (numpy ndarray): The singular values as a one dimensional array
Vt (numpy ndarray): Two dimensional array with dimensions Vt (numpy ndarray): Two dimensional array with dimensions
(n_features, n_features) (n_features, n_features)
mean_values (numpy ndarray): mean values of the features of the model, mean_values (numpy ndarray): mean values of the features of the model,
this should have dimensions (n_featurs, ) this should have dimensions (n_featurs, )
""" """
# load the stored model file # load the stored model file
UsVtm = np.load(filename) Vtm = np.load(filename)
Vt = Vtm[0]
mean_values = Vtm[1][0]
return Vt, mean_values
def flatten_feature_vectors(data):
"""
Flattens the feature vectors inside a ndarray
Example:
input:
[
[[1, 2], [3, 4], [5, 6]],
...
[[1, 2], [3, 4], [5, 6]]
]
output:
[
[1, 2, 3, 4, 5, 6],
...
[1, 2, 3, 4, 5, 6]
]
Args:
data (numpy array): array of feature vectors
return:
array: (numpy array): array flattened feature vectors
"""
flattened = []
rows, _, _ = data.shape
U = UsVtm[0] for i in range(rows):
s = UsVtm[1] flattened.append(np.ndarray.flatten(data[i]))
Vt = UsVtm[2]
mean_values = UsVtm[3]
return U, s, Vt, mean_values return np.array(flattened)
import numpy as np import numpy as np
from pca import pca, reconstruct from pca import flatten_feature_vectors
def test_flatten_feature_vectors():
imm_points = np.array([
[[1, 2], [2, 4]],
[[2, 3], [3, 6]],
])
expected = np.array([
[1, 2, 2, 4],
[2, 3, 3, 6]
])
result = flatten_feature_vectors(imm_points)
np.testing.assert_array_equal(result, expected)
...@@ -2,7 +2,6 @@ import kivy ...@@ -2,7 +2,6 @@ import kivy
kivy.require('1.0.7') kivy.require('1.0.7')
import numpy as np import numpy as np
from matplotlib.tri import Triangulation
from kivy.app import App from kivy.app import App
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
...@@ -18,9 +17,11 @@ from kivy.graphics.context_instructions import Color ...@@ -18,9 +17,11 @@ from kivy.graphics.context_instructions import Color
from functools import partial from functools import partial
from math import cos, sin, pi from math import cos, sin, pi
from imm_points import IMMPoints, build_feature_vectors, \ import imm_points as imm
flatten_feature_vectors #import IMMPoints, build_feature_vectors, \
# flatten_feature_vectors
import pca import pca
import aam
class ImageCanvas(Widget): class ImageCanvas(Widget):
...@@ -34,12 +35,13 @@ class ImageCanvas(Widget): ...@@ -34,12 +35,13 @@ class ImageCanvas(Widget):
self.image = Image(pos=self.pos, size=self.size, source=self.filename_image) self.image = Image(pos=self.pos, size=self.size, source=self.filename_image)
self.mesh = Mesh(mode='triangle_fan') self.mesh = Mesh(mode='triangle_fan')
self.triangles = InstructionGroup() self.triangles = InstructionGroup()
self.middle_group = InstructionGroup() self.outline = InstructionGroup()
self.bind(pos=self.update_rect, size=self.update_rect) self.bind(pos=self.update_rect, size=self.update_rect)
def get_rendered_size(self): def get_rendered_size(self):
"""get the rendered size of the image """
get the rendered size of the image
Returns: Returns:
(tuple) width, height in pixels (tuple) width, height in pixels
""" """
...@@ -73,7 +75,7 @@ class ImageCanvas(Widget): ...@@ -73,7 +75,7 @@ class ImageCanvas(Widget):
self.triangles.clear() self.triangles.clear()
image_width, image_height = self.get_rendered_size() image_width, image_height = self.get_rendered_size()
triangles = Triangulation(reconstructed[:, 0], reconstructed[:, 1]).triangles triangles = aam.get_triangles(reconstructed[:, 0], reconstructed[:, 1])
for tri in triangles: for tri in triangles:
self.triangles.add(Color(0, 0, 1, 1)) self.triangles.add(Color(0, 0, 1, 1))
...@@ -92,7 +94,6 @@ class ImageCanvas(Widget): ...@@ -92,7 +94,6 @@ class ImageCanvas(Widget):
self.triangles.add(Line(circle=(x[2], y[2], 3))) self.triangles.add(Line(circle=(x[2], y[2], 3)))
self.canvas.add(self.triangles) self.canvas.add(self.triangles)
self.canvas.add(self.middle_group)
self.canvas.ask_update() self.canvas.ask_update()
def build_mesh(self, reconstructed): def build_mesh(self, reconstructed):
...@@ -109,8 +110,8 @@ class ImageCanvas(Widget): ...@@ -109,8 +110,8 @@ class ImageCanvas(Widget):
xy_vertices = np.array(xy_vertices) xy_vertices = np.array(xy_vertices)
indices = [] indices = []
indices = Triangulation(xy_vertices[:, 0], xy_vertices[:, 1]).triangles indices = aam.get_triangles(xy_vertices[:, 0], xy_vertices[:, 1])
indices = np.ndarray.flatten(indices)[:30] indices = np.ndarray.flatten(indices)
self.mesh.vertices = vertices self.mesh.vertices = vertices
self.mesh.indices = indices self.mesh.indices = indices
...@@ -126,6 +127,7 @@ class RootWidget(BoxLayout): ...@@ -126,6 +127,7 @@ class RootWidget(BoxLayout):
self.n_components = kwargs['args'].n_components self.n_components = kwargs['args'].n_components
self.multipliers = np.ones(self.Vt.shape[1]) self.multipliers = np.ones(self.Vt.shape[1])
# slider index
self.index = 0 self.index = 0
self.filename = '' self.filename = ''
...@@ -137,11 +139,12 @@ class RootWidget(BoxLayout): ...@@ -137,11 +139,12 @@ class RootWidget(BoxLayout):
n_components_slider.value = self.n_components n_components_slider.value = self.n_components
n_components_slider.bind(value=self.update_n_components) n_components_slider.bind(value=self.update_n_components)
self.landmark_list = build_feature_vectors(self.images, flattened=True)
self.ids['image_viewer'].bind(size=self.on_resize) self.ids['image_viewer'].bind(size=self.on_resize)
box_layout = self.ids['eigenvalues'] box_layout = self.ids['eigenvalues']
self.landmark_list = aam.build_feature_vectors(self.images,
imm.get_imm_landmarks, flattened=True)
for c in range(self.n_components): for c in range(self.n_components):
slider = Slider(min=-10, max=10, value=0, id=str(c)) slider = Slider(min=-10, max=10, value=0, id=str(c))
box_layout.add_widget(slider) box_layout.add_widget(slider)
...@@ -149,7 +152,6 @@ class RootWidget(BoxLayout): ...@@ -149,7 +152,6 @@ class RootWidget(BoxLayout):
def reset_sliders(self): def reset_sliders(self):
self.multipliers = np.ones(self.Vt.shape[1]) self.multipliers = np.ones(self.Vt.shape[1])
box_layout = self.ids['eigenvalues'] box_layout = self.ids['eigenvalues']
for c in box_layout.children: for c in box_layout.children:
...@@ -164,7 +166,7 @@ class RootWidget(BoxLayout): ...@@ -164,7 +166,7 @@ class RootWidget(BoxLayout):
n_components=self.n_components n_components=self.n_components
) )
reconstruction = reconstruction.reshape((58, 2)) reconstruction = reconstruction.reshape((-1, 2))
self.ids['image_viewer'].update_rect() self.ids['image_viewer'].update_rect()
self.ids['image_viewer'].update_image(self.filename) self.ids['image_viewer'].update_image(self.filename)
...@@ -199,7 +201,8 @@ class ReconstructApp(App): ...@@ -199,7 +201,8 @@ class ReconstructApp(App):
super(ReconstructApp, self).__init__(**kwargs) super(ReconstructApp, self).__init__(**kwargs)
def build(self): def build(self):
return RootWidget(args=self.args, eigen_vectors=self.eigen_vectors, return RootWidget(
args=self.args, eigen_vectors=self.eigen_vectors,
mean_values=self.mean_values mean_values=self.mean_values
) )
......
<Widget> # Draw lines between widgets for easy debugging
canvas.after: # <Widget>
Line: # canvas.after:
rectangle: self.x+1,self.y+1,self.width-1,self.height-1 # Line:
dash_offset: 5 # rectangle: self.x+1,self.y+1,self.width-1,self.height-1
dash_length: 3 # dash_offset: 5
# dash_length: 3
<ImageCanvas> <ImageCanvas>
Widget
<RootWidget> <RootWidget>
BoxLayout: BoxLayout:
...@@ -39,55 +39,3 @@ ...@@ -39,55 +39,3 @@
BoxLayout: BoxLayout:
orientation: 'vertical' orientation: 'vertical'
id: eigenvalues id: eigenvalues
# size_hint_y: None
# height: sp(100)
# BoxLayout:
# orientation: 'vertical'
# Slider:
# id: e1
# min: -360.
# max: 360.
# Label:
# text: 'angle_start = {}'.format(e1.value)
# BoxLayout:
# orientation: 'vertical'
# Slider:
# id: e2
# min: -360.
# max: 360.
# value: 360
# Label:
# text: 'angle_end = {}'.format(e2.value)
# BoxLayout:
# size_hint_y: None
# height: sp(100)
# BoxLayout:
# orientation: 'vertical'
# Slider:
# id: wm
# min: 0
# max: 2
# value: 1
# Label:
# text: 'Width mult. = {}'.format(wm.value)
# BoxLayout:
# orientation: 'vertical'
# Slider:
# id: hm
# min: 0
# max: 2
# value: 1
# Label:
# text: 'Height mult. = {}'.format(hm.value)
# Button:
# text: 'Reset ratios'
# on_press: wm.value = 1; hm.value = 1
# FloatLayout:
# canvas:
# Color:
# rgb: 1, 1, 1
# Rectangle:
# pos: 100, 100
# size: 200 * wm.value, 201 * hm.value
# source: 'data/imm_face_db/16-2m.jpg'
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment