Click here to Skip to main content
15,892,480 members
Articles / Multimedia / GDI+

C# Application to Create and Recognize Mouse Gestures (.NET)

Rate me:
Please Sign up or sign in to vote.
4.82/5 (39 votes)
17 Mar 2008CPOL5 min read 221.8K   8.1K   144  
This program can create and recognize mouse gestures.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Drawing.Drawing2D;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Threading;

namespace MouseGesture
{
	#region MainBoard class
	//This class is the panel where every gesture will be drawn
	public class MainBoard : System.Windows.Forms.Panel
	{
		#region Private members
		private ArrayList m_Gestures=new ArrayList();	//This array will contain every gesture
		private MouseState m_MouseState=MouseState.Waiting;	//Identifies the mouse state
		private BoardState m_BoardState=BoardState.None;	//Identifies the board state
		private Pen m_Pen;	//This object will be used to draw on the board
		private Color m_BoardColor;	//Board Background color
		private int m_PatternsToCreate;	//This member stores the value of patterns to create during the training set creation
		private ArrayList m_TrainingSet=new ArrayList();	//Stores the manual generated training set
		private int m_ActualPattern;	//This member is used to store how many gestures have been manually created until now
		private float m_CurrentMaxError;	//During the training, this member stores the current MaxError generated by the Neural Net
		private ArrayList m_Points = new ArrayList();	//This array stores the coords of every point of the gesture drawn
		private ArrayList m_Errors=new ArrayList();	//Stores every error generated during the training phase by a neural net
		private double m_MinError=1d;	//Min error value
		private double m_PeakError=0d;	//Max error value
		#endregion

		#region Public properties
		public Color BoardColor{get{return this.m_BoardColor;} set{this.m_BoardColor=(Color)value; this.Invalidate();}}
		public BoardState BState{get{return this.m_BoardState;}}
		public ArrayList KnownGestures{get{return this.m_Gestures;}}
		#endregion

		#region Events and delegates
		//This event is invoked at the beginning of gesture creation phase
		public delegate void BeginCreatingDelegate();
		public event BeginCreatingDelegate BeginCreating;

		//This event is invoked at the beginning of training phase
		public delegate void BeginTrainingDelegate();
		public event BeginTrainingDelegate BeginTraining;

		//This event is invoked at the end of gesture creation phase
		public delegate void EndCreatingDelegate();
		public event EndCreatingDelegate EndCreating;

		//This event is invoked at the end of training phase
		public delegate void EndTrainingDelegate();
		public event EndTrainingDelegate EndTraining;
		#endregion

		#region Static variables
		public static int GestureMinimumPoints{get{return 15;}}	//Min value of gesture points to be used or recognized
		public static int GestureMaximumPoints{get{return 100;}}	//Max value for gesture points (it's important because is used to determinate how many features can be used)
		public static int MaximumFeatures{get{return 14;}}	//Number of features used as inputs by neural nets (MUST be less or equal than GestureMaximumPoints)
		#endregion

		#region Visual objects
		private TransparentPanel m_ActualGesture;
		public TransparentPanel ActualGesture{get{return this.m_ActualGesture;}}
		private TransparentPanel m_RecognizedGesture;
		public TransparentPanel RecognizedGesture{get{return this.m_RecognizedGesture;}}
		private TransparentLabel m_Message;
		public TransparentLabel MessageBar{get{return this.m_Message;}}
		private TransparentLabel m_Infos;
		public TransparentLabel InfoPanel{get{return this.m_Infos;}}
		private GesturesManagementForm m_ManagementDialog;
		private CreateTrainingSetForm m_CreateTrainingSetForm;
		#endregion
		
		#region Contructor
		public MainBoard()
		{
			this.m_BoardColor=Color.White;
			this.m_Pen = new Pen(Color.Red,1); 
			this.m_GridPen = new Pen(Color.LightGray,1);
			this.m_GridSpaces = new GridSpaces(15,15);
			this.m_BoardState=BoardState.None;
			this.m_PointsColor=Color.Blue;
			
			this.InitializeComponents();
			this.LinkHandlers();

			this.SetStyle(ControlStyles.AllPaintingInWmPaint|ControlStyles.UserPaint|ControlStyles.DoubleBuffer,true);
			this.LoadSettings();
		}
		#endregion

		#region Board appearence
		//These functions are used to customize the appearence of the board
		#region Pen Get/Set thickness & color
		
		public void SetPenColor(Color color)
		{
			this.m_Pen.Color=color;
			this.Invalidate();
		}

		public Color GetPenColor()
		{
			return this.m_Pen.Color;
		}

		public void SetPenThickness(float thickness)
		{
			this.m_Pen.Width=thickness;
			this.Invalidate();
		}

		public double GetPenThickness()
		{
			return this.m_Pen.Width;
		}
		#endregion

		#region Points color
		private Color m_PointsColor;
		public Color PointsColor{get{return this.m_PointsColor;} set{this.m_PointsColor=value; this.Invalidate();}}
		#endregion

		#region GridPen Get/Set thickness & color
		private Pen m_GridPen;
		public void SetGridPenColor(Color color)
		{
			this.m_GridPen.Color=color;
			this.Invalidate();
		}

		public Color GetGridPenColor()
		{
			return this.m_GridPen.Color;
		}

		public void SetGridPenThickness(float thickness)
		{
			this.m_GridPen.Width=thickness;
			this.Invalidate();
		}

		public double GetGridPenThickness()
		{
			return this.m_GridPen.Width;
		}
		#endregion

		#region Grid Spaces Get/Set
		//These function are used to get/set spaces between two lines of the grid
		private GridSpaces m_GridSpaces;
		public int GetGridVerticalSpace()
		{
			return this.m_GridSpaces.Height;
		}

		public int GetGridHorizontalSpace()
		{
			return this.m_GridSpaces.Width;
		}

		public int GridVerticalSpace{set{this.m_GridSpaces.Height=value;}}

		public int GridHorizontalSpace{set{this.m_GridSpaces.Width=value;}}
		
		public void SetGridSpaces(int width, int height)
		{
			this.m_GridSpaces.Width=width;
			this.m_GridSpaces.Height=height;

			this.Invalidate();
		}
		#endregion

		#endregion

		#region Create training set
		//This function is invoked by outside (another form) to start the manual training set creation
		public void CreateTrainingSet(int tot, CreateTrainingSetForm form)
		{
			this.ClearActualGesturePanel();
			this.Parent.Enabled=true;
			this.m_ActualPattern=1;
			this.m_PatternsToCreate=tot;
			this.m_CreateTrainingSetForm=form;
			this.m_Infos.ShowMessage("Gesture 1/"+tot,MessageType.Normal);
			this.m_Message.ShowMessage("Draw a gesture for the training set",MessageType.Info);
			this.m_BoardState=BoardState.TSCreating;
			this.m_TrainingSet=new ArrayList();
		}
		#endregion

		#region Manage new mouse gesture
		//This function is invoked by outside (another form) to start the manual gesture creation
		public void CreateGesture()
		{
			this.m_Message.ShowMessage("Draw a new mouse gesture",MessageType.Info);
			this.m_BoardState=BoardState.Recording;
			if(this.BeginCreating!=null)
				this.BeginCreating();
		}

		//This function is used to add a new gesture to the gesture array
		public bool AddNewGesture(string Name, ArrayList Points)
		{
			//Purging the name (avoiding bad or indesidered chars)
			string GestureName=Parser.PurgeString(Name);

			//Check to see if purge has erased all the chars
			if(GestureName.Length>0)
			{
				//Gesture creation and add
				Gesture NewGesture=new Gesture(Name,"./Gestures/"+Name+".mgf",Points);
				this.m_Gestures.Add(NewGesture);
				NewGesture.SaveGesture(false,false);
				this.m_Message.ShowMessage("New mouse gesture added",MessageType.Info);
				return true;
			}
			else
			{
				//If the name is invalid, show an error message
				MessageBox.Show("The name for the gesture is not valid. Please, re-enter the name to save the gesture properly.","Warning!",MessageBoxButtons.OK,MessageBoxIcon.Warning);
				this.m_Message.ShowMessage("Mouse gesture name invalid",MessageType.Error);
				return false;
			}
		}
		#endregion

		#region Clear Board and Panel
		public void ClearAll()
		{
			this.m_Points=new ArrayList();
			this.m_ActualGesture.ClearPanel();
			this.m_RecognizedGesture.ClearPanel();
			this.m_Infos.ShowMessage("");
			this.Invalidate();
		}

		public void ClearActualGesturePanel()
		{
			this.m_Points=new ArrayList();
			this.m_ActualGesture.ClearPanel();
			this.Invalidate();
		}
		#endregion

		#region Custom initialization
		public void InitializeComponents()
		{
			this.m_ActualGesture=new TransparentPanel();
			this.m_ActualGesture.Parent=this;
			this.m_ActualGesture.SetBounds(this.Right-this.m_ActualGesture.Width-10,10,100,100);
			this.m_ActualGesture.Show();

			this.m_RecognizedGesture=new TransparentPanel();
			this.m_RecognizedGesture.Parent=this;
			this.m_RecognizedGesture.SetBounds(this.Right-this.m_ActualGesture.Width-10,130,100,100);
			this.m_RecognizedGesture.Show();
			
			this.m_Message=new TransparentLabel();
			this.m_Message.Parent=this;
			this.m_Message.SetBounds(0,this.Bottom-16,this.Width,16);
			this.m_Message.Show();

			this.m_Infos=new TransparentLabel();
			this.m_Infos.Parent=this;
			this.m_Infos.SetBounds(10,10,100,100);
			this.m_Infos.Transparency=20;
			this.m_Infos.Show();
		}
		#endregion

		#region Insert gesture in RecognizedGesture
		public void InsertRecognizedGesture(ArrayList points)
		{
			this.m_RecognizedGesture.InsertNewGesture(points);
		}
		#endregion

		#region Handlers Linking
		public void LinkHandlers()
		{
			this.MouseUp += new	System.Windows.Forms.MouseEventHandler(this.MouseUpOnBoard);
			this.MouseMove += new System.Windows.Forms.MouseEventHandler(MouseMoveOnBoard);
			this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MouseDownOnBoard);
			this.Paint += new System.Windows.Forms.PaintEventHandler(this.PaintBoard);
		}
		#endregion

		#region Event handlers
		
		#region Mouse Events handlers
		
		#region MouseMove handler
		//Every time the mouse moves, I need to check if his state is "recording". If so, I must store
		//the current position (in 2D coords)
		private void MouseMoveOnBoard(object sender,System.Windows.Forms.MouseEventArgs e)
		{
			if(this.m_MouseState==MouseState.Recording)
			{
				this.m_Points.Add(new PointF(e.X,e.Y));
				this.m_Message.ShowMessage("Points: "+this.m_Points.Count,MessageType.Normal);
				this.Invalidate();
			} 		
		}
		#endregion

		#region MouseDown handler
		//If the key pressed is RightButton, and the board is not in the training phase,
		//the recording gesture phase starts (every panel, or only some of them, will be
		//cleared... it depends on the board state).
		private void MouseDownOnBoard(object sender,System.Windows.Forms.MouseEventArgs e)
		{
			if(e.Button==MouseButtons.Right && this.m_BoardState!=BoardState.Training)
			{
				this.m_MouseState=MouseState.Recording;
				if(this.m_BoardState==BoardState.TSCreating)
					this.ClearActualGesturePanel();
				else
					this.ClearAll();
			}
		}
		#endregion

		#region MouseUp handler
		//This function controls one the most important part of the board functions
		//At the end of the recording phase, the right button must be released, so gesture recognizing, or
		//creation must be managed here
		private void MouseUpOnBoard(object sender,System.Windows.Forms.MouseEventArgs e)
		{
			//Check to see which button has been released
			if(e.Button==MouseButtons.Right)
			{
				//During the training phase the board must not recive any input by user
				if(this.m_BoardState!=BoardState.Training)
				{
					//Check to see if the mouse is in recording mode
					if(this.m_MouseState==MouseState.Recording)
					{
						//Mouse state will be set at "Waiting", since the recording phase has been stopped
						this.m_MouseState=MouseState.Waiting;

						//Adding the last point of the gesture (in this way I'm sure there are at least 2 points
						//the begininning point, and the ending one
						this.m_Points.Add(new PointF(e.X,e.Y));	

						//Check to see if there are enough points
						if(this.m_Points.Count<MainBoard.GestureMinimumPoints)
						{
							//Show a message to signal there are too few points
							this.m_Message.ShowMessage("Points: "+this.m_Points.Count+"  [Too few points! Please, draw the gesture again]",MessageType.Error);
							if(this.m_Points.Count>1)
							{
								//If there are less than minimum points, but at least 2 points, draw the gesture
								this.m_ActualGesture.InsertNewGesture(this.m_Points);
							}
						}
						else
						{
							//If there are enough points, check to see if they are less then or more than the maximum value
							if(this.m_Points.Count>MainBoard.GestureMaximumPoints)
							{
								//If necessary invoke the points-reduction function
								this.m_Message.ShowMessage("Points: "+this.m_Points.Count+"  [There are too many points. This gesture will be simplified]",MessageType.Info);
								int ExcessivePoints=GestureRegularization.ReduceGesturePoints(this.m_Points);
								this.m_Message.ShowMessage(this.m_Message.Text+" [Removed "+ExcessivePoints+" point"+((ExcessivePoints==1)?(""):("s"))+"]");
							}
							else if(this.m_Points.Count<MainBoard.GestureMaximumPoints)
							{
								//If necessary invoke the points-expansion function
								this.m_Message.ShowMessage("Points: "+this.m_Points.Count,MessageType.Normal);
								int GapPoints=GestureRegularization.ExpandGesturePoints(this.m_Points);
								this.m_Message.ShowMessage(this.m_Message.Text+" [Added "+GapPoints+" point"+((GapPoints==1)?(""):("s"))+"]");
							}

							//Check to avoid crashed if "unfortunally" gesture reduction... reduces too much! :P (now could be removed...)
							if(this.m_Points.Count>1)
								this.m_ActualGesture.InsertNewGesture(this.m_Points);

							//Check the board state to invoke appropriate method
							if(this.m_BoardState==BoardState.Recording)
							{
								//If board was in creation mode, show the preview/confirm form
								this.ShowGestureCreation();
							}
							else if(this.m_BoardState==BoardState.TSCreating)
							{
								//If the board was in training set creation mode, prompt a message, asking if the user accepts this gesture
								if(MessageBox.Show("Do you want to save this gesture to training set?","Save?",MessageBoxButtons.YesNo,MessageBoxIcon.Question)==DialogResult.Yes)
								{
									//Pattern counter update
									this.m_ActualPattern++;
									//New pattern cretion
									Pattern p=new Pattern(Gesture.ExtractFeatures(this.m_Points));
									//Pattern will be added to the training set
									this.m_TrainingSet.Add(p);

									//If the added pattern is the last one, shows the training set creation form
									if(this.m_ActualPattern==this.m_PatternsToCreate+1)
									{
										this.Parent.Enabled=false;
										this.m_CreateTrainingSetForm.Enabled=true;
										this.m_CreateTrainingSetForm.Visible=true;
							
										//Convert from a dynamic arraylist to a normal array
										Pattern[] patterns=new Pattern[this.m_PatternsToCreate];
										this.m_TrainingSet.CopyTo(patterns);

										//Set to "None" the board state
										this.m_BoardState=BoardState.None;
										//Import the training set into the training set creation form
										this.m_CreateTrainingSetForm.ImportAndUseGestures(patterns);
										this.ClearAll();
									}
									else
									{
										//If the pattern created is not the last one, than simply show a message o the infos and the message bar
										//and clean up actual gesture panel
										this.m_Infos.ShowMessage("Gesture "+this.m_ActualPattern+"/"+this.m_PatternsToCreate);
										this.m_Message.ShowMessage("Draw another gesture for the training set",MessageType.Info);
										this.ClearActualGesturePanel();
									}
								}
								else
								{
									//If the gesture has not been accepted, user must redraw it!
									this.m_Message.ShowMessage("Draw again the gesture",MessageType.Info);
									this.ClearActualGesturePanel();
								}
							}
							else if(this.m_BoardState==BoardState.Verifying)
							{
								//If the board is in verifying mode I have to record every output from every neural net
								double[] Outputs=new double[this.m_Gestures.Count];

								for(int z=0; z<this.m_Gestures.Count; z++)
								{
									//Get neural net output, only if it is a trained net
									if(((Gesture)this.m_Gestures[z]).Trained)
										Outputs[z]=((Gesture)this.m_Gestures[z]).Net.Run(Gesture.ExtractFeatures(this.m_Points));
									else
										Outputs[z]=-1;	//If not trained, the value returned by the net is -1 (less than every possible output (they are always between 0 and 1)
								}

								//Extract 3 or less (it depends on the gesture number) from output vector
								int[] Winners=this.GetWinners(Outputs);

								//Threashold check, to see if the gesture with the highest value is recognized
								bool Recognized=(Outputs[Winners[0]]>Neuron.Threashold);
								string msg="";
								for(int p=0; p<Winners.Length; p++)
									msg+=((Gesture)this.m_Gestures[Winners[p]]).Name+": "+((float)Outputs[Winners[p]])+((p!=Winners.Length-1)?("\n\n"):(""));

								//Show messages
								this.m_Infos.ShowMessage(msg,(Recognized)?(MessageType.Info):(MessageType.Error));
								this.m_Message.ShowMessage((Recognized)?("Mouse gesture recognized"):("Mouse gesture unknown"),(Recognized)?(MessageType.Info):(MessageType.Error));

								//If gesture have been recognized, then show the "prototype" on the Recognized gesture panel
								if(Recognized)
									this.m_RecognizedGesture.InsertNewGesture(((Gesture)this.m_Gestures[Winners[0]]).Points);
							}
						}
						//Force refresh
						this.Invalidate();
					}
				}
			}
		}
		#endregion

		#endregion

		#region Paint hanldler
		private void PaintBoard(object sender,System.Windows.Forms.PaintEventArgs e)
		{
			//Redraw the background (there is another way, I could simply use the Background color property... :P)
			e.Graphics.FillRectangle(new SolidBrush(this.m_BoardColor),0,0,this.ClientSize.Width,this.ClientSize.Height);
			
			#region Draw Grid
			//Draw grid with spaces defined previously
			for(int i=1; i<=(int)(this.ClientSize.Width/this.m_GridSpaces.Width); i++)
			{
				e.Graphics.DrawLine(this.m_GridPen,new PointF(i*this.m_GridSpaces.Width,0),new PointF(i*this.m_GridSpaces.Width,this.ClientSize.Height));
			}

			for(int j=1; j<=(int)(this.ClientSize.Height/this.m_GridSpaces.Height); j++)
			{
				e.Graphics.DrawLine(this.m_GridPen,new PointF(0,j*this.m_GridSpaces.Height),new PointF(this.ClientSize.Width,j*this.m_GridSpaces.Height));
			}
			#endregion
			
			//Check the board status
			if(this.m_BoardState==BoardState.Training)
			{
				//During the training phase, the error graph shold be drawn
				//We must adjust the pass between 2 lines of the graph, to avoid the graph to be larger than 
				//the client size.
				//To avoid this, the pass should be equals to the ratio between client width
				//and number of errors... obviusly if errors are less than width, pass will be 1...
				float Pass=0;
				if(this.m_CurrentMaxError>(this.Width-20))	//Use a 20 pixel space to avoid the graph to be too near to the end of the form
					Pass=((float)this.Width-20f)/((float)this.m_CurrentMaxError);
				else
					Pass=1;

				//Retrieve max error among al errors, scalingscalando every height of the graph according to
				//this value (so every error is inside the form)
				double MaxError=this.GetMaxError();
				//Graph max height
                float BaseHight=this.Height-this.m_Message.Height;

				//Draw a line for each error, using pass calculated before to retireve x-axis component,
				//and scaling heights with MaxError
				for(int h=0; h<this.m_Errors.Count; h++)
				{
					float y=BaseHight-(BaseHight-120)*((float)(((double)this.m_Errors[h])/(double)MaxError));
					e.Graphics.DrawLine(this.m_Pen,(float) h*Pass, (float) this.Height-this.m_Message.Height, (float)h*Pass, y);
				}
			}
			else
			{
				//If not in training mode, I need to show all that is drawn by user,
				//only if he drew at least 2 points (needed to draw a line)
				if(this.m_Points.Count>1)
				{
					//Convertyng dynamic array in constant array
					PointF[] pts=new PointF[this.m_Points.Count];
					this.m_Points.CopyTo(pts,0);
					//Draw lines by DrawLines function
					e.Graphics.DrawLines(this.m_Pen,pts);
				
					//If user-drawing has ended, the program draws anchor ponints
					//(points retrieved by mouse polling)
					if(this.m_MouseState==MouseState.Waiting)
					{
						foreach (PointF p in this.m_Points)
						{
							//For each anchor point draw a rectangle centered in point
							e.Graphics.FillRectangle(new SolidBrush(this.m_PointsColor),p.X-1f,p.Y-1f,2,2);
						}
					}
				}
			}
		}
		#endregion

		#region ParentResize handler
		//On parent resize, main board layout will be regenerated, according to new parent size 
		public void OnParentResize(object sender,System.EventArgs e)
		{
			this.Size=this.Parent.ClientSize;
			this.m_ActualGesture.SetBounds(this.Right-this.m_ActualGesture.Width-10, 10,this.m_ActualGesture.Width,this.m_ActualGesture.Height);
			this.m_RecognizedGesture.SetBounds(this.Right-this.m_ActualGesture.Width-10, 130,this.m_ActualGesture.Width,this.m_ActualGesture.Height);
			this.m_Message.SetBounds(0,this.Bottom-this.m_Message.Height,this.Width,this.m_Message.Height);
			this.m_Infos.SetBounds(10,10,100,100);
			this.Invalidate(true);
		}
		#endregion

		#endregion

		#region Gesture management

		#region Gestures importing
		//This function set up know-gestures array
		public void ImportGestures(ArrayList gestures)
		{
			this.m_Gestures=gestures;
		}
		#endregion

		#region ShowMessageBar function
		//The function is used to allow other form to put messages on the message bar.
		//Even if panels and bars have public properties, and could be used to show messages,
		//this function is another method to do that (I had to use this method before deciding to
		//give public access to panels/bats for cusumizing appearence)
		public void ShowMessageToBar(string message, MessageType type)
		{
			this.m_Message.ShowMessage(message,type);
		}
		#endregion

		#region GesturesCreationForm functions
		//This function shows the gesture creation form
		private void ShowGestureCreation()
		{
			GestureCreationForm CreationDialog=new GestureCreationForm(this,this.m_Points);
			CreationDialog.Closed+=new EventHandler(this.OnCloseGestureCreation);
			this.m_Message.ShowMessage("Gesture creation: open", MessageType.Normal);
			this.Parent.Enabled=false;
			CreationDialog.Show();
		}

		//This handler is called at gesture creation form close
		private void OnCloseGestureCreation(object sender, System.EventArgs e)
		{
			this.m_ManagementDialog.Visible=true;
			this.m_ManagementDialog.Enabled=true;
			this.m_ManagementDialog.RefreshGestureList();
			this.m_Message.ShowMessage("Gesture creation: closed", MessageType.Normal);
			if(this.EndCreating!=null)
				this.EndCreating();
		}
		#endregion

		#region GesturesManagementForm functions
		//The function shows gesture management form
		public void ShowGesturesManagement()
		{
			this.m_ManagementDialog=new GesturesManagementForm(this);
			this.m_ManagementDialog.Closed+=new EventHandler(this.OnCloseGestureManagement);
			this.ClearAll();
			this.m_Message.ShowMessage("Gesture management: open", MessageType.Normal);
			this.Invalidate();
			this.m_ManagementDialog.ShowDialog();
		}

		//This handler is called at gesture management form close
		private void OnCloseGestureManagement(object sender, System.EventArgs e)
		{
			this.ClearAll();
			this.Parent.Enabled=true;
			this.m_BoardState=BoardState.None;
			this.m_Message.ShowMessage("Gesture management: closed", MessageType.Normal);
		}
		#endregion

		#endregion

		#region Manage verifying

		#region Start Verifying
		//This function is called at the beginning of the verifying phase
		public void StartVerify()
		{
			this.ClearAll();
			this.m_BoardState=BoardState.Verifying;
			this.m_Message.ShowMessage("Draw a gesture to verify current net",MessageType.Info);
		}
		#endregion

		#region End Verifying
		//This function is called at the end of the verifying phase
		public void EndVerify()
		{
			this.ClearAll();
			this.m_BoardState=BoardState.None;
			this.m_Message.ShowMessage("Verifying stopped",MessageType.Normal);
		}
		#endregion

		#region Verifiyng result functions
		//The function gets the in input the outputs of neural nets, and returns an array of
		//indexes, ordered by output values, from the highest (the first) to the lowest (the last)
		//(This function is awful... maybe better using quck sort and avoid final resorting... :P)
		private int[] GetWinners(double[] outputs)
		{
			//Creating BestResults array (trying to show 3 best results, or less if
			//there are few gestures)
			int BestResults=(outputs.Length>=3)?(3):(outputs.Length);
			int[] ret=new int[BestResults];
			
			//Copy of neural net outputs (used to sort values)
			double[] TmpOut=new double[outputs.Length];
			outputs.CopyTo(TmpOut,0);

			//Creating index array
			int[] Indexes=new int[outputs.Length];
			for(int k=0; k<Indexes.Length; k++)
				Indexes[k]=k;

			//Sorting by bubble sort (from lowest to highest)
			for(int i=0; i<TmpOut.Length-1; i++)
			{
				for(int j=i; j<TmpOut.Length; j++)
				{
					if(TmpOut[i]>TmpOut[j])
					{
						double tmp=TmpOut[i];
						int tmpIndex=Indexes[i];
                        TmpOut[i]=TmpOut[j];
						Indexes[i]=Indexes[j];
						TmpOut[j]=tmp;
						Indexes[j]=tmpIndex;
					}
				}
			}

			//Creating output vector, ordered from highest, to lowest
			for(int y=0; y<ret.Length; y++)
				ret[y]=Indexes[(Indexes.Length-1)-y];

			return ret;
		}
		#endregion

		#endregion

		#region Manage training

		#region StartTrainingMode function
		//This function starts traingin phase
		public void StartTrainingMode(int maxerrors)
		{
			this.ClearActualGesturePanel();
			this.m_CurrentMaxError=maxerrors;
			this.m_BoardState=BoardState.Training;

			if(this.BeginTraining!=null)
				this.BeginTraining();

			this.m_Infos.ShowMessage("Epoch: "+this.m_Errors.Count,MessageType.Normal);
		}
		#endregion

		#region EndTrainingMode function
		//This functions ends treaining phase
		public void EndTrainingMode()
		{
			this.ClearAll();
			this.m_BoardState=BoardState.None;

			this.m_Message.ShowMessage("Training form: closed",MessageType.Normal);
			if(this.EndTraining!=null)
				this.EndTraining();
		}
		#endregion

		#region OnEpochEnded handler
		//Every time the neural net completes an epoch, it's needed to refresh the
		//error graph
		public void OnEpochEnded(TrainedEventArgs args)
		{
			this.m_Errors.Add(args.Error);
			if(this.m_PeakError<args.Error)
				this.m_PeakError=args.Error;
			if(this.m_MinError>args.Error)
				this.m_MinError=args.Error;

			this.m_Infos.ShowMessage("Epoch: "+this.m_Errors.Count+"\n\nVal: "+((float)args.Error)+"\n\nMin: "+(float)this.m_MinError+"\n\nMax: "+(float)this.m_PeakError,MessageType.Normal);
			this.Invalidate(true);
		}
		#endregion

		#region OnGestureTrained handler
		//When a gesture is trained, every structure used
		//to draw the graph, must be reinitialized
		public void OnGestureTrained()
		{
			this.m_Errors=new ArrayList();
			this.m_PeakError=0d;
			this.m_MinError=1d;
			this.Invalidate(true);
		}
		#endregion

		#region ClearError function
		//Clears the main board, from error graph
		public void ClearError()
		{
			this.m_Errors=new ArrayList();
			this.m_PeakError=0d;
			this.m_MinError=1d;
			this.Invalidate();
		}
		#endregion

		#region GetMaxError
		//This function returns the max value stored
		//in errors array (if array is empty, returns 0)
		public double GetMaxError()
		{
			if(this.m_Errors.Count>0)
			{
				double ret=(double)this.m_Errors[0];
				for(int i=1; i<this.m_Errors.Count; i++)
				{
					if(((double)this.m_Errors[i])>ret)
						ret=((double)this.m_Errors[i]);
				}
				return ret;
			}
			return 0;
		}
		#endregion

		#endregion

		#region Settings management
		
		#region SaveSettings function
		//Save settings function
		public void SaveSettings()
		{
			string SettingsPath=Environment.CurrentDirectory+"/Settings.cfg";
			if(File.Exists(SettingsPath)) 
			{
				try
				{
					StreamWriter sr = new StreamWriter(SettingsPath,false);
					sr.Write(this.SerializeSettings());
					sr.Close();
				}
				catch
				{
					MessageBox.Show("Can't write \'"+SettingsPath+"\'. File could be already opened by another application.","Warning!",MessageBoxButtons.OK,MessageBoxIcon.Warning); 
					return;
				}
			}
			else
			{
				StreamWriter sr = File.CreateText(SettingsPath);
				sr.Write(this.SerializeSettings());
				sr.Close();
			}		
		}
		#endregion
		
		#region LoadSettings function
		//Load settings function
		public void LoadSettings()
		{
			string SettingsPath=Environment.CurrentDirectory+"/Settings.cfg";

			//Check if settings file exists
			if(File.Exists(SettingsPath))
			{
				XmlTextReader TextReader = new XmlTextReader(SettingsPath);

				try
				{
					//Try to read the file
					while(TextReader.Read())
					{
						if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							//Try to parse the color. If parsing fails, function returns
							//RGBA=(0,0,0,0); in such a case, is used the default color
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.BoardColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBGridPenColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.SetGridPenColor(col);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBGridHor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.GridHorizontalSpace=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBGridVer")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.GridVerticalSpace=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBLineColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.SetPenColor(col);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBLineThickness")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.SetPenThickness(float.Parse(tmp));
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MBPtsColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.PointsColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "APColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.ActualGesture.BaseColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "APTransparency")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.ActualGesture.Transparency=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "APStdColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.ActualGesture.StandardColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "APStartColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.ActualGesture.StartColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "APEndColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.ActualGesture.EndColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "RPColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.RecognizedGesture.BaseColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "RPTransparency")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.RecognizedGesture.Transparency=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "RPStdColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.RecognizedGesture.StandardColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "RPStartColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.RecognizedGesture.StartColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "RPEndColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.RecognizedGesture.EndColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MPColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.MessageBar.BaseColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MPTransparency")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.MessageBar.Transparency=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MPNormColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.MessageBar.NormalColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "MPErrorColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.MessageBar.ErrorColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "IPColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.InfoPanel.BaseColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "IPTransparency")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							this.InfoPanel.Transparency=int.Parse(tmp);
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "IPNormColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.InfoPanel.NormalColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "IPInfoColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.InfoPanel.InfoColor=col;
						}
						else if(TextReader.NodeType == XmlNodeType.Element && TextReader.Name == "IPErrorColor")
						{
							string tmp=Parser.PurgeString(TextReader.ReadInnerXml());
							Color col=Parser.ParseColor(tmp);
							if(col.A!=0 && col.R!=0 && col.G!=0 && col.B!=0)
								this.InfoPanel.ErrorColor=col;
						}
					}
					TextReader.Close();
				}
				catch(XmlException)
				{
					MessageBox.Show("XML parsing error while reading \'"+SettingsPath+"\'. The gesture will be ignored!","Warning!",MessageBoxButtons.OK,MessageBoxIcon.Error);
					TextReader.Close();
				}
			}
		}
		#endregion

		#region Serialize settings
		//Settings XML file creation
		private string SerializeSettings()
		{
			string ret="<Settings>\n";

			//Board panel
			ret+="<MainBoard>\n";
			ret+="<MBColor>\n"+this.BoardColor.ToString()+"\n</MBColor>\n";
			ret+="<MBGridPenColor>\n"+this.GetGridPenColor().ToString()+"\n</MBGridPenColor>\n";
			ret+="<MBGridHor>\n"+this.GetGridHorizontalSpace().ToString()+"\n</MBGridHor>\n";
			ret+="<MBGridVer>\n"+this.GetGridVerticalSpace().ToString()+"\n</MBGridVer>\n";
			ret+="<MBLineColor>\n"+this.GetPenColor().ToString()+"\n</MBLineColor>\n";
			ret+="<MBLineThickness>\n"+this.GetPenThickness().ToString()+"\n</MBLineThickness>\n";
			ret+="<MBPtsColor>\n"+this.PointsColor.ToString()+"\n</MBPtsColor>\n";
			ret+="</MainBoard>\n";

			//Actual panel
			ret+="<ActualPanel>\n";
            ret+="<APColor>\n"+this.ActualGesture.BaseColor+"\n</APColor>";
			ret+="<APTransparency>\n"+this.ActualGesture.Transparency+"\n</APTransparency>\n";
			ret+="<APStdColor>\n"+this.ActualGesture.StandardColor+"\n</APStdColor>\n";
			ret+="<APStartColor>\n"+this.ActualGesture.StartColor+"\n</APStartColor>\n";
			ret+="<APEndColor>\n"+this.ActualGesture.EndColor+"\n</APEndColor>\n";
			ret+="</ActualPanel>\n";

			//Recognized panel
			ret+="<RecogPanel>\n";
			ret+="<RPColor>\n"+this.RecognizedGesture.BaseColor+"\n</RPColor>";
			ret+="<RPTransparency>\n"+this.RecognizedGesture.Transparency+"\n</RPTransparency>\n";
			ret+="<RPStdColor>\n"+this.RecognizedGesture.StandardColor+"\n</RPStdColor>\n";
			ret+="<RPStartColor>\n"+this.RecognizedGesture.StartColor+"\n</RPStartColor>\n";
			ret+="<RPEndColor>\n"+this.RecognizedGesture.EndColor+"\n</RPEndColor>\n";
			ret+="</RecogPanel>\n";
		
			//Message panel
			ret+="<MessagePanel>\n";
			ret+="<MPColor>\n"+this.MessageBar.BaseColor+"\n</MPColor>\n";
			ret+="<MPTransparency>\n"+this.MessageBar.Transparency+"\n</MPTransparency>\n";
			ret+="<MPNormColor>\n"+this.MessageBar.NormalColor+"\n</MPNormColor>\n";
			ret+="<MPInfoColor>\n"+this.MessageBar.InfoColor+"\n</MPInfoColor>\n";
			ret+="<MPErrorColor>\n"+this.MessageBar.ErrorColor+"\n</MPErrorColor>\n";
			ret+="</MessagePanel>\n";

			//Info panel
			ret+="<InfoPanel>\n";
			ret+="<IPColor>\n"+this.InfoPanel.BaseColor+"\n</IPColor>\n";
			ret+="<IPTransparency>\n"+this.InfoPanel.Transparency+"\n</IPTransparency>\n";
			ret+="<IPNormColor>\n"+this.InfoPanel.NormalColor+"\n</IPNormColor>\n";
			ret+="<IPInfoColor>\n"+this.InfoPanel.InfoColor+"\n</IPInfoColor>\n";
			ret+="<IPErrorColor>\n"+this.InfoPanel.ErrorColor+"\n</IPErrorColor>\n";
			ret+="</InfoPanel>\n";

			ret+="</Settings>";

			return ret;
		}
		#endregion

		#endregion

		#region FullNet management
		
		#region SaveFullNet function
		//Creates a full net file
		public void SaveFullNet(string path)
		{
			if(File.Exists(path)) 
			{
				try
				{
					StreamWriter sr = new StreamWriter(path,false);
					sr.Write(this.SerializeFullNet());
					sr.Close();
				}
				catch
				{
					MessageBox.Show("Can't write \'"+path+"\'. File could be already opened by another application.","Warning!",MessageBoxButtons.OK,MessageBoxIcon.Warning); 
					return;
				}
			}
			else
			{
				StreamWriter sr = File.CreateText(path);
				sr.Write(this.SerializeFullNet());
				sr.Close();
			}
		}
		#endregion

		#region SerializeFullNet function
		//Create string to bes tored in the XML full net file
		private string SerializeFullNet()
		{
			string str="";

			str+="<FullNet>\n";
			for(int i=0; i<this.KnownGestures.Count; i++)
			{
				if(((Gesture)this.KnownGestures[i]).Trained)
				{
					str+="<GestureAndNet>\n";
					str+=((Gesture)this.KnownGestures[i]).ToMGFstring(true,false)+"\n";
					str+=((Gesture)this.KnownGestures[i]).Net.ToMGNstring()+"\n";
					str+="</GestureAndNet>\n";
				}
			}
			str+="</FullNet>";

			return str;
		}
		#endregion

		#endregion
	}
	#endregion

	#region GridSpaces class
	//This class is a simpler version of the Size class
	public class GridSpaces
	{
		public int Width;
		public int Height;

		public GridSpaces(int width, int height)
		{
			this.Width=width;
			this.Height=height;
		}
	}
	#endregion

	#region enum MouseState
	//This enumerator shows all possible mouse states for this program
	public enum MouseState
	{
		Recording,	//RightButton down and user drawing gesture
		Waiting	//RightButton up, the program doesn't record gesture points
	}
	#endregion

	#region enum BoardState
	//This enumerator shows every state of the MainBoard
	public enum BoardState
	{
		None,	//Initial state
		Recording,	//In this state program can learn new gestures
		TSCreating,	//In this state user can import manually examples
		Verifying,	//In this state users can check if net answers correctly
		Training	//In this state MainBoard shows the training error praph
	}
	#endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Apex s.r.l.
Italy Italy
I got my Computer Science (Engineering) Master's Degree at the Siena University (Italy), but I'm from Rieti (a small town next to Rome).
My hobbies are RPG, MMORGP, programming and 3D graphics.
At the moment I'm employed at Apex s.r.l. (Modena, Italy) as a senior software developer, working for a WPF/WCF project in Rome.

Comments and Discussions