From 33521f676393d543465c43858ceeef04063e69b8 Mon Sep 17 00:00:00 2001 From: Neil Lawrence Date: Mon, 29 Apr 2013 21:45:15 +0100 Subject: [PATCH] Unification of the visualize object hierarchy and standardization of the click and move behaviour of lvm and lvm_dimselect. Set colours of input sensitivity histogram to red for left (port) and green for right (starboard). --- GPy/core/model.py | 2 +- GPy/examples/dimensionality_reduction.py | 44 +++- GPy/util/mocap.py | 1 - GPy/util/visualize.py | 317 +++++++++++++++-------- 4 files changed, 240 insertions(+), 124 deletions(-) diff --git a/GPy/core/model.py b/GPy/core/model.py index f3542ce8..6fa66429 100644 --- a/GPy/core/model.py +++ b/GPy/core/model.py @@ -253,7 +253,7 @@ class model(parameterised): :max_f_eval: maximum number of function evaluations :messages: whether to display during optimisation - :param optimzer: whice optimizer to use (defaults to self.preferred optimizer) + :param optimzer: which optimizer to use (defaults to self.preferred optimizer) :type optimzer: string TODO: valid strings? """ if optimizer is None: diff --git a/GPy/examples/dimensionality_reduction.py b/GPy/examples/dimensionality_reduction.py index 9da161f2..75820407 100644 --- a/GPy/examples/dimensionality_reduction.py +++ b/GPy/examples/dimensionality_reduction.py @@ -81,11 +81,19 @@ def BGPLVM_oil(optimize=True, N=100, Q=10, M=15, max_f_eval=300): else: m.ensure_default_constraints() - # plot - print(m) - m.plot_latent(labels=m.data_labels) - pb.figure() - pb.bar(np.arange(m.kern.D), 1. / m.input_sensitivity()) + y = m.likelihood.Y[0, :] + fig,(latent_axes,hist_axes) = plt.subplots(1,2) + plt.sca(latent_axes) + m.plot_latent() + data_show = GPy.util.visualize.vector_show(y) + lvm_visualizer = GPy.util.visualize.lvm_dimselect(m.X[0, :], m, data_show, latent_axes=latent_axes, hist_axes=hist_axes) + raw_input('Press enter to finish') + plt.close('all') + # # plot + # print(m) + # m.plot_latent(labels=m.data_labels) + # pb.figure() + # pb.bar(np.arange(m.kern.D), 1. / m.input_sensitivity()) return m def oil_100(): @@ -348,7 +356,7 @@ def brendan_faces(): ax = m.plot_latent() y = m.likelihood.Y[0, :] data_show = GPy.util.visualize.image_show(y[None, :], dimensions=(20, 28), transpose=True, invert=False, scale=False) - lvm_visualizer = GPy.util.visualize.lvm(m, data_show, ax) + lvm_visualizer = GPy.util.visualize.lvm(m.X[0, :], m, data_show, ax) raw_input('Press enter to finish') plt.close('all') @@ -365,7 +373,29 @@ def stick(): ax = m.plot_latent() y = m.likelihood.Y[0, :] data_show = GPy.util.visualize.stick_show(y[None, :], connect=data['connect']) - lvm_visualizer = GPy.util.visualize.lvm(m, data_show, ax) + lvm_visualizer = GPy.util.visualize.lvm(m.X[0, :], m, data_show, ax) + raw_input('Press enter to finish') + plt.close('all') + + return m + +def cmu_mocap(subject='35', motion=['01'], in_place=True): + + data = GPy.util.datasets.cmu_mocap(subject, motion) + Y = data['Y'] + if in_place: + # Make figure move in place. + data['Y'][:, 0:3]=0.0 + m = GPy.models.GPLVM(data['Y'], 2, normalize_Y=True) + + # optimize + m.ensure_default_constraints() + m.optimize(messages=1, max_f_eval=10000) + + ax = m.plot_latent() + y = m.likelihood.Y[0, :] + data_show = GPy.util.visualize.skeleton_show(y[None, :], data['skel']) + lvm_visualizer = GPy.util.visualize.lvm(m.X[0, :], m, data_show, ax) raw_input('Press enter to finish') plt.close('all') diff --git a/GPy/util/mocap.py b/GPy/util/mocap.py index 76650086..174728bd 100644 --- a/GPy/util/mocap.py +++ b/GPy/util/mocap.py @@ -532,7 +532,6 @@ class acclaim_skeleton(skeleton): self.vertices[0].meta['orientation'] = [float(parts[1]), float(parts[2]), float(parts[3])] - print self.vertices[0].meta['orientation'] lin = self.read_line(fid) return lin diff --git a/GPy/util/visualize.py b/GPy/util/visualize.py index 9754db63..9475c85b 100644 --- a/GPy/util/visualize.py +++ b/GPy/util/visualize.py @@ -3,121 +3,7 @@ from mpl_toolkits.mplot3d import Axes3D import GPy import numpy as np import matplotlib as mpl - -class lvm: - def __init__(self, model, data_visualize, latent_axes, latent_index=[0,1]): - if isinstance(latent_axes,mpl.axes.Axes): - self.cid = latent_axes.figure.canvas.mpl_connect('button_press_event', self.on_click) - self.cid = latent_axes.figure.canvas.mpl_connect('motion_notify_event', self.on_move) - self.cid = latent_axes.figure.canvas.mpl_connect('axes_leave_event', self.on_leave) - self.cid = latent_axes.figure.canvas.mpl_connect('axes_enter_event', self.on_enter) - else: - self.cid = latent_axes[0].figure.canvas.mpl_connect('button_press_event', self.on_click) - self.cid = latent_axes[0].figure.canvas.mpl_connect('motion_notify_event', self.on_move) - self.cid = latent_axes[0].figure.canvas.mpl_connect('axes_leave_event', self.on_leave) - self.cid = latent_axes[0].figure.canvas.mpl_connect('axes_enter_event', self.on_enter) - self.data_visualize = data_visualize - self.model = model - self.latent_axes = latent_axes - - self.called = False - self.move_on = False - self.latent_index = latent_index - self.latent_dim = model.Q - - def on_enter(self,event): - pass - def on_leave(self,event): - pass - - def on_click(self, event): - #print 'click', event.xdata, event.ydata - if event.inaxes!=self.latent_axes: return - self.move_on = not self.move_on - # if self.called: - # self.xs.append(event.xdata) - # self.ys.append(event.ydata) - # self.line.set_data(self.xs, self.ys) - # self.line.figure.canvas.draw() - # else: - # self.xs = [event.xdata] - # self.ys = [event.ydata] - # self.line, = self.latent_axes.plot(event.xdata, event.ydata) - self.called = True - def on_move(self, event): - if event.inaxes!=self.latent_axes: return - if self.called and self.move_on: - # Call modify code on move - #print 'move', event.xdata, event.ydata - latent_values = np.zeros((1,self.latent_dim)) - latent_values[0,self.latent_index] = np.array([event.xdata, event.ydata]) - y = self.model.predict(latent_values)[0] - self.data_visualize.modify(y) - #print 'y', y - -class lvm_subplots(lvm): - """ - latent_axes is a np array of dimension np.ceil(Q/2) + 1, - one for each pair of the axes, and the last one for the sensitiity histogram - """ - def __init__(self, model, data_visualize, latent_axes=None, latent_index=[0,1]): - self.nplots = int(np.ceil(model.Q/2.))+1 - lvm.__init__(self,model,data_visualize,latent_axes,latent_index) - self.latent_values = np.zeros(2*np.ceil(self.model.Q/2.)) # possibly an extra dimension on this - assert latent_axes.size == self.nplots - - -class lvm_dimselect(lvm): - """ - A visualizer for latent variable models - with selection by clicking on the histogram - """ - def __init__(self, model, data_visualize): - self.fig,(latent_axes,self.hist_axes) = plt.subplots(1,2) - - lvm.__init__(self,model,data_visualize,latent_axes,[0,1]) - self.latent_values_clicked = np.zeros(model.Q) - self.clicked_handle = self.latent_axes.plot([0],[0],'rx',mew=2)[0] - print "use left and right mouse butons to select dimensions" - - def on_click(self, event): - #print "click" - if event.inaxes==self.hist_axes: - self.hist_axes.cla() - self.hist_axes.bar(np.arange(self.model.Q),1./self.model.input_sensitivity(),color='b') - new_index = max(0,min(int(np.round(event.xdata-0.5)),self.model.Q-1)) - self.latent_index[(0 if event.button==1 else 1)] = new_index - self.hist_axes.bar(np.array(self.latent_index),1./self.model.input_sensitivity()[self.latent_index],color='r') - self.latent_axes.cla() - self.model.plot_latent(which_indices = self.latent_index,ax=self.latent_axes) - self.clicked_handle = self.latent_axes.plot([self.latent_values_clicked[self.latent_index[0]]],self.latent_values_clicked[self.latent_index[1]],'rx',mew=2)[0] - if event.inaxes==self.latent_axes: - self.clicked_handle.set_visible(False) - self.latent_values_clicked[self.latent_index] = np.array([event.xdata,event.ydata]) - self.clicked_handle = self.latent_axes.plot([self.latent_values_clicked[self.latent_index[0]]],self.latent_values_clicked[self.latent_index[1]],'rx',mew=2)[0] - self.fig.canvas.draw() - self.move_on=True - self.called = True - - - def on_move(self, event): - #print "move" - if event.inaxes!=self.latent_axes: return - if self.called and self.move_on: - latent_values = self.latent_values_clicked.copy() - latent_values[self.latent_index] = np.array([event.xdata, event.ydata]) - y = self.model.predict(latent_values[None,:])[0] - self.data_visualize.modify(y) - - def on_leave(self,event): - latent_values = self.latent_values_clicked.copy() - y = self.model.predict(latent_values[None,:])[0] - self.data_visualize.modify(y) - - - - - +import time class data_show: """ @@ -155,6 +41,171 @@ class vector_show(data_show): self.handle.set_data(xdata, self.vals) self.axes.figure.canvas.draw() + +class lvm(data_show): + def __init__(self, vals, model, data_visualize, latent_axes=None, latent_index=[0,1]): + """Visualize a latent variable model + + :param model: the latent variable model to visualize. + :param data_visualize: the object used to visualize the data which has been modelled. + :type data_visualize: visualize.data_show type. + :param latent_axes: the axes where the latent visualization should be plotted. + """ + if vals == None: + vals = model.X[0] + + data_show.__init__(self, vals, axes=latent_axes) + + if isinstance(latent_axes,mpl.axes.Axes): + self.cid = latent_axes.figure.canvas.mpl_connect('button_press_event', self.on_click) + self.cid = latent_axes.figure.canvas.mpl_connect('motion_notify_event', self.on_move) + self.cid = latent_axes.figure.canvas.mpl_connect('axes_leave_event', self.on_leave) + self.cid = latent_axes.figure.canvas.mpl_connect('axes_enter_event', self.on_enter) + else: + self.cid = latent_axes[0].figure.canvas.mpl_connect('button_press_event', self.on_click) + self.cid = latent_axes[0].figure.canvas.mpl_connect('motion_notify_event', self.on_move) + self.cid = latent_axes[0].figure.canvas.mpl_connect('axes_leave_event', self.on_leave) + self.cid = latent_axes[0].figure.canvas.mpl_connect('axes_enter_event', self.on_enter) + + self.data_visualize = data_visualize + self.model = model + self.latent_axes = latent_axes + + self.called = False + self.move_on = False + self.latent_index = latent_index + self.latent_dim = model.Q + + # The red cross which shows current latent point. + self.latent_values = vals + self.latent_handle = self.latent_axes.plot([0],[0],'rx',mew=2)[0] + self.modify(vals) + + def modify(self, vals): + """When latent values are modified update the latent representation and ulso update the output visualization.""" + + y = self.model.predict(vals)[0] + self.data_visualize.modify(y) + self.latent_handle.set_data(vals[self.latent_index[0]], vals[self.latent_index[1]]) + self.axes.figure.canvas.draw() + + + def on_enter(self,event): + pass + def on_leave(self,event): + pass + + def on_click(self, event): + #print 'click', event.xdata, event.ydata + if event.inaxes!=self.latent_axes: return + self.move_on = not self.move_on + # if self.called: + # self.xs.append(event.xdata) + # self.ys.append(event.ydata) + # self.line.set_data(self.xs, self.ys) + # self.line.figure.canvas.draw() + # else: + # self.xs = [event.xdata] + # self.ys = [event.ydata] + # self.line, = self.latent_axes.plot(event.xdata, event.ydata) + self.called = True + def on_move(self, event): + if event.inaxes!=self.latent_axes: return + if self.called and self.move_on: + # Call modify code on move + self.latent_values[self.latent_index[0]]=event.xdata + self.latent_values[self.latent_index[1]]=event.ydata + self.modify(self.latent_values) + +class lvm_subplots(lvm): + """ + latent_axes is a np array of dimension np.ceil(Q/2) + 1, + one for each pair of the axes, and the last one for the sensitiity histogram + """ + def __init__(self, vals, model, data_visualize, latent_axes=None, latent_index=[0,1]): + lvm.__init__(self, vals, model,data_visualize,latent_axes,[0,1]) + self.nplots = int(np.ceil(model.Q/2.))+1 + lvm.__init__(self,model,data_visualize,latent_axes,latent_index) + self.latent_values = np.zeros(2*np.ceil(self.model.Q/2.)) # possibly an extra dimension on this + assert latent_axes.size == self.nplots + + +class lvm_dimselect(lvm): + """ + A visualizer for latent variable models which allows selection of the latent dimensions to use by clicking on a histogram of their length scales. + """ + def __init__(self, vals, model, data_visualize, latent_axes=None, hist_axes=None, latent_index=[0, 1]): + if latent_axes==None and hist_axes==None: + self.fig,(latent_axes,self.hist_axes) = plt.subplots(1,2) + elif hist_axes==None: + fig=plt.figure() + self.hist_axes = fig.add_subplot(111) + else: + self.hist_axes = hist_axes + + lvm.__init__(self,vals,model,data_visualize,latent_axes,latent_index) + self.draw_histogram() + print "use left and right mouse butons to select dimensions" + + def draw_histogram(self): + # A click in the histogram axis for selection a dimension. + self.hist_axes.cla() + self.hist_axes.bar(np.arange(self.model.Q),1./self.model.input_sensitivity(),color='b') + + if self.latent_index[1] == self.latent_index[0]: + self.hist_axes.bar(np.array(self.latent_index[0]),1./self.model.input_sensitivity()[self.latent_index[0]],color='y') + self.hist_axes.bar(np.array(self.latent_index[1]),1./self.model.input_sensitivity()[self.latent_index[1]],color='y') + + else: + self.hist_axes.bar(np.array(self.latent_index[0]),1./self.model.input_sensitivity()[self.latent_index[0]],color='g') + self.hist_axes.bar(np.array(self.latent_index[1]),1./self.model.input_sensitivity()[self.latent_index[1]],color='r') + + self.hist_axes.figure.canvas.draw() + + def on_click(self, event): + + if event.inaxes==self.hist_axes: + new_index = max(0,min(int(np.round(event.xdata-0.5)),self.model.Q-1)) + if event.button == 1: + # Make it red if and y-axis (red=port=left) if it is a left button click + self.latent_index[1] = new_index + else: + # Make it green and x-axis (green=starboard=right) if it is a right button click + self.latent_index[0] = new_index + + self.draw_histogram() + + self.latent_axes.cla() + self.model.plot_latent(which_indices=self.latent_index, + ax=self.latent_axes) + self.latent_handle = self.latent_axes.plot([0],[0],'rx',mew=2)[0] + self.modify(self.latent_values) + + elif event.inaxes==self.latent_axes: + self.move_on = not self.move_on + + self.called = True + + + def on_move(self, event): + #print "move" + if event.inaxes!=self.latent_axes: return + if self.called and self.move_on: + self.latent_values[self.latent_index[0]]=event.xdata + self.latent_values[self.latent_index[1]]=event.ydata + self.modify(self.latent_values) + + def on_leave(self,event): + latent_values = self.latent_values.copy() + y = self.model.predict(latent_values[None,:])[0] + self.data_visualize.modify(y) + + + + + + + class image_show(data_show): """Show a data vector as an image.""" def __init__(self, vals, axes=None, dimensions=(16,16), transpose=False, invert=False, scale=False): @@ -269,12 +320,24 @@ class stick_show(mocap_data_show): class skeleton_show(mocap_data_show): """data_show class for visualizing motion capture data encoded as a skeleton with angles.""" def __init__(self, vals, skel, padding=0, axes=None): + """data_show class for visualizing motion capture data encoded as a skeleton with angles. + :param vals: set of modeled angles to use for printing in the axis when it's first created. + :type vals: np.array + :param skel: skeleton object that has the parameters of the motion capture skeleton associated with it. + :type skel: mocap.skeleton object + :param padding: + :type int + """ self.skel = skel self.padding = padding connect = skel.connection_matrix() mocap_data_show.__init__(self, vals, axes, connect) def process_values(self, vals): + """Takes a set of angles and converts them to the x,y,z coordinates in the internal prepresentation of the class, ready for plotting. + + :param vals: the values that are being modelled.""" + if self.padding>0: channels = np.zeros((vals.shape[0], vals.shape[1]+self.padding)) channels[:, 0:vals.shape[0]] = vals @@ -296,3 +359,27 @@ class skeleton_show(mocap_data_show): if nVals[i] != nVals[j]: connect[i, j] = False return vals, connect + + +def data_play(Y, visualizer, frame_rate=30): + """Play a data set using the data_show object given. + + :Y: the data set to be visualized. + :param visualizer: the data show objectwhether to display during optimisation + :type visualizer: data_show + + Example usage: + + This example loads in the CMU mocap database (http://mocap.cs.cmu.edu) subject number 35 motion number 01. It then plays it using the mocap_show visualize object. + + data = GPy.util.datasets.cmu_mocap(subject='35', train_motions=['01']) + Y = data['Y'] + Y[:, 0:3] = 0. # Make figure walk in place + visualize = GPy.util.visualize.skeleton_show(Y[0, :], data['skel']) + GPy.util.visualize.data_play(Y, visualize) + """ + + + for y in Y: + visualizer.modify(y) + time.sleep(1./float(frame_rate))