diff --git a/gui/OpenFaceDemo/MainWindow.xaml b/gui/OpenFaceDemo/MainWindow.xaml
index db3743e..3983d7a 100644
--- a/gui/OpenFaceDemo/MainWindow.xaml
+++ b/gui/OpenFaceDemo/MainWindow.xaml
@@ -30,6 +30,9 @@
-
+
+
+
+
diff --git a/gui/OpenFaceDemo/MainWindow.xaml.cs b/gui/OpenFaceDemo/MainWindow.xaml.cs
index f3695fd..9c4eeda 100644
--- a/gui/OpenFaceDemo/MainWindow.xaml.cs
+++ b/gui/OpenFaceDemo/MainWindow.xaml.cs
@@ -22,7 +22,6 @@ using CppInterop.LandmarkDetector;
using CameraInterop;
using FaceAnalyser_Interop;
using System.Windows.Threading;
-using OpenFaceDemo.UI_items;
namespace OpenFaceDemo
{
@@ -31,7 +30,7 @@ namespace OpenFaceDemo
///
public partial class MainWindow : Window
{
-
+
// -----------------------------------------------------------------
// Members
// -----------------------------------------------------------------
diff --git a/gui/OpenFaceDemo/OpenFaceDemo.csproj b/gui/OpenFaceDemo/OpenFaceDemo.csproj
index a451973..a85ef50 100644
--- a/gui/OpenFaceDemo/OpenFaceDemo.csproj
+++ b/gui/OpenFaceDemo/OpenFaceDemo.csproj
@@ -54,6 +54,9 @@
MinimumRecommendedRules.ruleset
true
+
+ logo1.ico
+
@@ -75,9 +78,13 @@
MSBuild:Compile
Designer
+
CameraSelection.xaml
+
+ TimeSeriesPlot.xaml
+
MSBuild:Compile
Designer
@@ -94,6 +101,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
@@ -132,6 +143,9 @@
CppInerop
+
+
+
diff --git a/gui/OpenFaceDemo/UI_items/AxesBorder.cs b/gui/OpenFaceDemo/UI_items/AxesBorder.cs
new file mode 100644
index 0000000..21d098f
--- /dev/null
+++ b/gui/OpenFaceDemo/UI_items/AxesBorder.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace OpenFaceDemo
+{
+ class AxesBorder : Border
+ {
+
+ public double MinVal { get; set; }
+ public double MaxVal { get; set; }
+ public int NumVertGrid { get; set; }
+
+ public bool ShowXLabel { get; set; }
+ public bool ShowYLabel { get; set; }
+
+ public string RangeLabel { get; set; }
+
+ public bool XTicks { get; set; }
+
+ public Orientation Orientation { get; set; }
+
+ public AxesBorder()
+ {
+ MinVal = -1;
+ MaxVal = 1;
+ NumVertGrid = 5;
+ ShowXLabel = true;
+ ShowYLabel = true;
+ XTicks = true;
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+ if (Orientation == System.Windows.Controls.Orientation.Horizontal)
+ RenderHorizontal(dc);
+ else
+ RenderVertical(dc);
+ }
+
+ private void RenderHorizontal(DrawingContext dc)
+ {
+ Pen p = new Pen(Brushes.Black, 1);
+ Pen q = new Pen(Brushes.LightGray, 1);
+
+ double padLeft = Padding.Left;
+ double padBottom = Padding.Bottom - 2 + 10;
+ double padTop = Padding.Top;
+ double padRight = Padding.Right;
+
+ // Draw horizontal gridlines
+
+ double step_size = (MaxVal - MinVal)/(NumVertGrid - 1.0);
+
+ for (int i = 0; i < NumVertGrid; i++)
+ {
+ double y = (int)(padTop + ((NumVertGrid - 1.0) - i) * ((ActualHeight - padBottom - padTop) / (NumVertGrid - 1.0))) - 0.5;
+
+ double y_val = MinVal + i * step_size;
+
+ if (y_val != 0)
+ dc.DrawLine(q, new Point(padLeft, y), new Point(ActualWidth - padRight, y));
+ else
+ dc.DrawLine(p, new Point(padLeft, y), new Point(ActualWidth - padRight, y));
+
+ dc.DrawLine(p, new Point(padLeft - 10, y), new Point(padLeft, y));
+
+ //var t = FT((i / 2.0 - 1).ToString("0.0"), 10);
+ var t = FT((MinVal + i * step_size).ToString("0.0"), 10);
+ dc.DrawText(t, new Point(padLeft - t.Width - 12, y - t.Height / 2));
+ }
+
+ // Draw vertical gridlines
+
+ for (int i = 0; i < 11; i++)
+ {
+ double x = (int)(padLeft + (10 - i) * ((ActualWidth - padLeft - padRight) / 10.0)) - 0.5;
+ if (i < 10)
+ dc.DrawLine(q, new Point(x, ActualHeight - padBottom), new Point(x, padTop));
+ dc.DrawLine(p, new Point(x, ActualHeight - padBottom + 10), new Point(x, ActualHeight - padBottom));
+
+ if(XTicks)
+ {
+ var t = FT(i.ToString(), 10);
+ dc.DrawText(t, new Point(x - t.Width / 2, ActualHeight - padBottom + t.Height));
+ }
+ }
+
+ // Draw y axis
+
+ dc.DrawLine(p, new Point(((int)padLeft) - 0.5, padTop), new Point(((int)padLeft) - 0.5, ActualHeight - padBottom));
+
+ // Draw x axis
+ //dc.DrawLine(p, new Point(padLeft, ((int)((ActualHeight - padBottom - padTop) / 2 + padTop)) - 0.5), new Point(ActualWidth - padRight, ((int)((ActualHeight - padBottom - padTop) / 2 + padTop)) - 0.5));
+
+ // Draw x axis label
+ if(ShowXLabel)
+ {
+ FormattedText ft = FT("History (seconds)", 20);
+ dc.DrawText(ft, new Point(padLeft + (ActualWidth - padLeft - padRight) / 2 - ft.Width / 2, ActualHeight - ft.Height));
+ }
+
+ // Draw y axis label
+ if(ShowYLabel)
+ {
+ FormattedText ft = FT(RangeLabel, 20);
+ dc.PushTransform(new RotateTransform(-90));
+ dc.DrawText(ft, new Point(-ft.Width - ActualHeight / 2 + ft.Width / 2, 0));
+ }
+ }
+
+ private void RenderVertical(DrawingContext dc)
+ {
+ Pen p = new Pen(Brushes.Black, 1);
+ Pen q = new Pen(Brushes.LightGray, 1);
+
+ double padLeft = Padding.Left;
+ double padBottom = Padding.Bottom - 2 + 10;
+ double padTop = Padding.Top;
+ double padRight = Padding.Right;
+
+ // Draw horizontal gridlines
+
+ for (int i = 0; i < 11; i++)
+ {
+ double y = (int)(padTop + (10 - i) * ((ActualHeight - padBottom - padTop) / 10.0)) - 0.5;
+ if (i > 0)
+ dc.DrawLine(q, new Point(padLeft, y), new Point(ActualWidth - padRight, y));
+ dc.DrawLine(p, new Point(padLeft - 10, y), new Point(padLeft, y));
+ var t = FT(i.ToString(), 10);
+ dc.DrawText(t, new Point(padLeft - t.Width - 12, y - t.Height / 2));
+ }
+
+ // Draw vertical gridlines
+
+ for (int i = 0; i < 5; i++)
+ {
+ double x = (int)(padLeft + (4 - i) * ((ActualWidth - padLeft - padRight) / 4.0)) - 0.5;
+ if (i < 10)
+ dc.DrawLine(q, new Point(x, ActualHeight - padBottom), new Point(x, padTop));
+ dc.DrawLine(p, new Point(x, ActualHeight - padBottom + 10), new Point(x, ActualHeight - padBottom));
+
+ var t = FT(((4-i) / 2.0 - 1).ToString("0.0"), 10);
+ dc.DrawText(t, new Point(x - t.Width / 2, ActualHeight - padBottom + t.Height));
+ }
+
+ // Draw y axis
+
+ dc.DrawLine(p, new Point(((int)((ActualWidth - padRight - padLeft) / 2 + padLeft)) - 0.5, padTop), new Point(((int)((ActualWidth - padRight - padLeft) / 2 + padLeft)) - 0.5, ActualHeight - padBottom));
+
+ // Draw x axis
+ dc.DrawLine(p, new Point(padLeft, ((int)((ActualHeight - padBottom))) - 0.5), new Point(ActualWidth - padRight, ((int)((ActualHeight - padBottom))) - 0.5));
+
+ // Draw x axis label
+
+ FormattedText ft = FT(RangeLabel, 20);
+ dc.DrawText(ft, new Point(padLeft + (ActualWidth - padLeft - padRight) / 2 - ft.Width / 2, ActualHeight - ft.Height));
+
+ // Draw y axis label
+
+ ft = FT("History (seconds)", 20);
+ dc.PushTransform(new RotateTransform(-90));
+ dc.DrawText(ft, new Point(-ft.Width - ActualHeight / 2 + ft.Width / 2, 0));
+ }
+
+ private FormattedText FT(string text, int size)
+ {
+ return new FormattedText(text, CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), size, Brushes.Black);
+ }
+
+ }
+}
diff --git a/gui/OpenFaceDemo/UI_items/CameraSelection.xaml b/gui/OpenFaceDemo/UI_items/CameraSelection.xaml
index e1a26a9..b578345 100644
--- a/gui/OpenFaceDemo/UI_items/CameraSelection.xaml
+++ b/gui/OpenFaceDemo/UI_items/CameraSelection.xaml
@@ -1,9 +1,9 @@
-
diff --git a/gui/OpenFaceDemo/UI_items/CameraSelection.xaml.cs b/gui/OpenFaceDemo/UI_items/CameraSelection.xaml.cs
index 3660fa2..7f37721 100644
--- a/gui/OpenFaceDemo/UI_items/CameraSelection.xaml.cs
+++ b/gui/OpenFaceDemo/UI_items/CameraSelection.xaml.cs
@@ -16,7 +16,7 @@ using CppInterop;
using System.Windows.Threading;
using System.Threading;
-namespace OpenFaceDemo.UI_items
+namespace OpenFaceDemo
{
///
/// Interaction logic for CameraSelection.xaml
diff --git a/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml b/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml
new file mode 100644
index 0000000..1e4cadf
--- /dev/null
+++ b/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml
@@ -0,0 +1,8 @@
+
+
diff --git a/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml.cs b/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml.cs
new file mode 100644
index 0000000..76e8f84
--- /dev/null
+++ b/gui/OpenFaceDemo/UI_items/TimeSeriesPlot.xaml.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace OpenFaceDemo
+{
+ public class DataPoint
+ {
+ public DataPoint()
+ {
+ Time = TimeSeriesPlot.CurrentTime;
+
+ }
+ public DateTime Time { get; set; }
+
+ public Dictionary values = new Dictionary();
+
+ public double Confidence { get; set; }
+ }
+ ///
+ /// Interaction logic for TimeSeriesPlot.xaml
+ ///
+ public partial class TimeSeriesPlot : UserControl
+ {
+
+ #region High-Resolution Timing
+ static DateTime startTime;
+ static Stopwatch sw = new Stopwatch();
+ static TimeSeriesPlot()
+ {
+ startTime = DateTime.Now;
+ sw.Start();
+ }
+ public static DateTime CurrentTime
+ {
+ get { return startTime + sw.Elapsed; }
+ }
+ #endregion
+
+
+ public Orientation Orientation { get; set; }
+
+ public bool ShowLegend { get; set; }
+
+ Queue dataPoints = new Queue();
+ TimeSpan historyLength = TimeSpan.FromSeconds(10);
+ Dictionary brushes = new Dictionary();
+ Dictionary brush_thicknesses = new Dictionary();
+ Dictionary line_names = new Dictionary();
+ Dictionary brush_colors = new Dictionary();
+
+ public TimeSeriesPlot()
+ {
+ InitializeComponent();
+ ShowLegend = false;
+ ClipToBounds = true;
+ DispatcherTimer dt = new DispatcherTimer(TimeSpan.FromMilliseconds(20), DispatcherPriority.Background, Timer_Tick, Dispatcher);
+ }
+
+ private void PruneData()
+ {
+ lock (dataPoints)
+ {
+ while (dataPoints.Count > 0 && dataPoints.Peek().Time < CurrentTime - historyLength - TimeSpan.FromSeconds(2))
+ dataPoints.Dequeue();
+ }
+ }
+
+ public void AddDataPoint(DataPoint dp)
+ {
+ lock (dataPoints)
+ dataPoints.Enqueue(dp);
+ }
+
+ private void Timer_Tick(object sender, EventArgs e)
+ {
+ PruneData();
+
+ if (this.IsVisible)
+ InvalidateVisual();
+ }
+
+ private FormattedText FT(string text, int size)
+ {
+ return new FormattedText(text, System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), size, Brushes.Black);
+ }
+
+ public void AssocColor(int seriesId, Color b)
+ {
+ Color bTransparent = b;
+ bTransparent.A = 0;
+
+ GradientStopCollection gs = new GradientStopCollection();
+ gs.Add(new GradientStop(bTransparent, 0));
+ gs.Add(new GradientStop(b, 0.2));
+ LinearGradientBrush g = new LinearGradientBrush(gs, new Point(0, 0), Orientation == System.Windows.Controls.Orientation.Horizontal ? new Point(ActualWidth, 0) : new Point(0, ActualHeight));
+ g.MappingMode = BrushMappingMode.Absolute;
+ g.Freeze();
+ brushes[seriesId] = g;
+
+ brush_colors[seriesId] = b;
+ }
+
+ public void AssocThickness(int seriesId, int thickness)
+ {
+ brush_thicknesses[seriesId] = thickness;
+ }
+
+ public void AssocName(int seriesId, String name)
+ {
+ line_names[seriesId] = name;
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+
+
+ DataPoint[] localPoints;
+ lock (dataPoints)
+ localPoints = dataPoints.ToArray();
+
+ var pfs = new Dictionary();
+
+ for (int i = 0; i < localPoints.Length; i++)
+ {
+ var ptTime = localPoints[i].Time;
+ var ptAge = (DateTime.Now - ptTime).TotalSeconds;
+ foreach (var kvp in localPoints[i].values)
+ {
+ var seriesId = kvp.Key;
+
+ double v = Math.Min(Math.Max(kvp.Value, 0), 1);
+
+
+ double x = 0;
+ double y = 0;
+
+ if (Orientation == System.Windows.Controls.Orientation.Horizontal)
+ {
+ x = ActualWidth - (CurrentTime - localPoints[i].Time).TotalMilliseconds * (ActualWidth / historyLength.TotalMilliseconds);
+ y = ActualHeight - ActualHeight * v;
+ }
+ else
+ {
+ y = ActualHeight - (CurrentTime - localPoints[i].Time).TotalMilliseconds * (ActualHeight / historyLength.TotalMilliseconds);
+ x = ActualWidth * v;
+ }
+
+ if (!pfs.ContainsKey(seriesId))
+ {
+ pfs[seriesId] = new PathFigure();
+ pfs[seriesId].IsClosed = false;
+ pfs[seriesId].StartPoint = new Point(x, y);
+ }
+ else
+ {
+ pfs[seriesId].Segments.Add(new LineSegment(new Point(x, y), true));
+ }
+ }
+ }
+
+
+ foreach (var kvp in pfs)
+ {
+ var seriesId = kvp.Key;
+ var pf = kvp.Value;
+
+ Brush b = brushes.ContainsKey(seriesId) ? brushes[seriesId] : Brushes.Black;
+
+ int thickness = brush_thicknesses.ContainsKey(seriesId) ? brush_thicknesses[seriesId] : 2;
+
+ PathGeometry pg = new PathGeometry(new PathFigure[] { pf });
+
+
+ Pen p = new Pen(b, thickness);
+
+ if (line_names.ContainsKey(seriesId) && line_names[seriesId].CompareTo("State rapport") == 0)
+ {
+ double[] dashValues = { 5.0, 5.0, 5.0 };
+ p.DashStyle = new System.Windows.Media.DashStyle(dashValues, 0);
+ }
+ dc.DrawGeometry(null, p, pg);
+ }
+
+ if (ShowLegend && line_names.Count > 0)
+ {
+ int height_one = 18;
+ int height = height_one * line_names.Count;
+
+ Pen p = new Pen(Brushes.Black, 1);
+ Brush legend_b = new SolidColorBrush(Color.FromRgb(255, 255, 255));
+
+ dc.DrawRectangle(legend_b, p, new Rect(0, 1, 100, height));
+
+ int i = 0;
+ foreach (var key_name_pair in line_names)
+ {
+ var line_name = key_name_pair.Value;
+ FormattedText ft = FT(line_name, 11);
+
+ // Draw the text
+ dc.DrawText(ft, new Point(15, 1 + height_one * i));
+ // Draw example lines
+
+ Brush legend_c = new SolidColorBrush(brush_colors[key_name_pair.Key]);
+ Pen p_line = new Pen(legend_c, brush_thicknesses[key_name_pair.Key]);
+ dc.DrawLine(p_line, new Point(0, 1 + height_one * i + height_one / 2), new Point(14, 1 + height_one * i + height_one / 2));
+ i++;
+ }
+ }
+
+ }
+
+
+ }
+
+}