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++; + } + } + + } + + + } + +}