diff --git a/.gitignore b/.gitignore
index 60996a5b..3aac9ad8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,9 @@ matlab_runners/Feature Point Experiments/yt_features/
matlab_runners/Feature Point Experiments/yt_features_clm/
matlab_runners/Gaze Experiments/mpii_out/
build/
+Debug/
+Release/AU_predictors/
+Release/
+ipch/
+matlab_runners/Demos/output_features_seq/
+matlab_runners/Demos/output_features_vid/
diff --git a/OpenFace.sln b/OpenFace.sln
index 84732ad4..73c1f0b0 100644
--- a/OpenFace.sln
+++ b/OpenFace.sln
@@ -13,7 +13,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FeatureExtraction", "exe\Fe
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{99FEBA13-BDDF-4076-B57E-D8EF4076E20D}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Executables", "Executables", "{9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Command line Executables", "Command line Executables", "{9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FaceLandmarkVidMulti", "exe\FaceLandmarkVidMulti\FaceLandmarkVidMulti.vcxproj", "{C3FAF36F-44BC-4454-87C2-C5106575FE50}"
EndProject
@@ -23,6 +23,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FaceLandmarkVid", "exe\Face
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FaceLandmarkImg", "exe\FaceLandmarkImg\FaceLandmarkImg.vcxproj", "{DDC3535E-526C-44EC-9DF4-739E2D3A323B}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GUI", "GUI", "{C9D8D0B0-11EC-41CB-8524-2DDB5BE94297}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppInerop", "lib\local\CppInerop\CppInerop.vcxproj", "{78196985-EE54-411F-822B-5A23EDF80642}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UtilLibs", "UtilLibs", "{D5F7B3E2-01FE-4F4A-8CE1-274D74D395D4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CamCom", "lib\local\CamCom\CamCom.vcxproj", "{0CEC6A75-17BA-4DC5-9405-C74154921E60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFaceOffline", "gui\OpenFaceOffline\OpenFaceOffline.csproj", "{A4760F41-2B1F-4144-B7B2-62785AFFE79B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -95,12 +105,36 @@ Global
{DDC3535E-526C-44EC-9DF4-739E2D3A323B}.Release|Win32.Build.0 = Release|Win32
{DDC3535E-526C-44EC-9DF4-739E2D3A323B}.Release|x64.ActiveCfg = Release|x64
{DDC3535E-526C-44EC-9DF4-739E2D3A323B}.Release|x64.Build.0 = Release|x64
+ {78196985-EE54-411F-822B-5A23EDF80642}.Debug|Win32.ActiveCfg = Debug|Win32
+ {78196985-EE54-411F-822B-5A23EDF80642}.Debug|Win32.Build.0 = Debug|Win32
+ {78196985-EE54-411F-822B-5A23EDF80642}.Debug|x64.ActiveCfg = Debug|x64
+ {78196985-EE54-411F-822B-5A23EDF80642}.Debug|x64.Build.0 = Debug|x64
+ {78196985-EE54-411F-822B-5A23EDF80642}.Release|Win32.ActiveCfg = Release|Win32
+ {78196985-EE54-411F-822B-5A23EDF80642}.Release|Win32.Build.0 = Release|Win32
+ {78196985-EE54-411F-822B-5A23EDF80642}.Release|x64.ActiveCfg = Release|x64
+ {78196985-EE54-411F-822B-5A23EDF80642}.Release|x64.Build.0 = Release|x64
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Debug|Win32.Build.0 = Debug|Win32
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Debug|x64.ActiveCfg = Debug|x64
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Debug|x64.Build.0 = Debug|x64
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Release|Win32.ActiveCfg = Release|Win32
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Release|Win32.Build.0 = Release|Win32
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Release|x64.ActiveCfg = Release|x64
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}.Release|x64.Build.0 = Release|x64
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Debug|Win32.ActiveCfg = Debug|x86
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Debug|Win32.Build.0 = Debug|x86
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Debug|x64.Build.0 = Debug|Any CPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Release|Win32.ActiveCfg = Release|Any CPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Release|Win32.Build.0 = Release|Any CPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Release|x64.ActiveCfg = Release|Any CPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {B47A5F12-2567-44E9-AE49-35763EC82149} = {99FEBA13-BDDF-4076-B57E-D8EF4076E20D}
+ {B47A5F12-2567-44E9-AE49-35763EC82149} = {D5F7B3E2-01FE-4F4A-8CE1-274D74D395D4}
{BDC1D107-DE17-4705-8E7B-CDDE8BFB2BF8} = {99FEBA13-BDDF-4076-B57E-D8EF4076E20D}
{0E7FC556-0E80-45EA-A876-DDE4C2FEDCD7} = {99FEBA13-BDDF-4076-B57E-D8EF4076E20D}
{8A23C00D-767D-422D-89A3-CF225E3DAB4B} = {9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}
@@ -108,5 +142,8 @@ Global
{2D80FA0B-2DE8-4475-BA5A-C08A9E1EDAAC} = {9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}
{34032CF2-1B99-4A25-9050-E9C13DD4CD0A} = {9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}
{DDC3535E-526C-44EC-9DF4-739E2D3A323B} = {9961DDAC-BE6E-4A6E-8EEF-FFC7D67BD631}
+ {78196985-EE54-411F-822B-5A23EDF80642} = {D5F7B3E2-01FE-4F4A-8CE1-274D74D395D4}
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60} = {D5F7B3E2-01FE-4F4A-8CE1-274D74D395D4}
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B} = {C9D8D0B0-11EC-41CB-8524-2DDB5BE94297}
EndGlobalSection
EndGlobal
diff --git a/gui/OpenFace-offline/App.config b/gui/OpenFace-offline/App.config
new file mode 100644
index 00000000..88fa4027
--- /dev/null
+++ b/gui/OpenFace-offline/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/App.xaml b/gui/OpenFace-offline/App.xaml
new file mode 100644
index 00000000..a7c9cabe
--- /dev/null
+++ b/gui/OpenFace-offline/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/gui/OpenFace-offline/App.xaml.cs b/gui/OpenFace-offline/App.xaml.cs
new file mode 100644
index 00000000..21fd7428
--- /dev/null
+++ b/gui/OpenFace-offline/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/gui/OpenFace-offline/FpsTracker.cs b/gui/OpenFace-offline/FpsTracker.cs
new file mode 100644
index 00000000..a7f2ee7b
--- /dev/null
+++ b/gui/OpenFace-offline/FpsTracker.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+
+namespace OpenFaceOffline
+{
+ public class FpsTracker
+ {
+ public TimeSpan HistoryLength { get; set; }
+ public FpsTracker()
+ {
+ HistoryLength = TimeSpan.FromSeconds(2);
+ }
+
+ private Queue frameTimes = new Queue();
+
+ private void DiscardOldFrames()
+ {
+ while (frameTimes.Count > 0 && (MainWindow.CurrentTime - frameTimes.Peek()) > HistoryLength)
+ frameTimes.Dequeue();
+ }
+
+ public void AddFrame()
+ {
+ frameTimes.Enqueue(MainWindow.CurrentTime);
+ DiscardOldFrames();
+ }
+
+ public double GetFPS()
+ {
+ DiscardOldFrames();
+
+ if (frameTimes.Count == 0)
+ return 0;
+
+ return frameTimes.Count / (MainWindow.CurrentTime - frameTimes.Peek()).TotalSeconds;
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/MainWindow.xaml b/gui/OpenFace-offline/MainWindow.xaml
new file mode 100644
index 00000000..ea38aadf
--- /dev/null
+++ b/gui/OpenFace-offline/MainWindow.xaml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Appearance features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Geometry features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action Units
+
+
+
+
+ Classification
+
+
+
+
+
+
+ Regression
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFace-offline/MainWindow.xaml.cs b/gui/OpenFace-offline/MainWindow.xaml.cs
new file mode 100644
index 00000000..2417eb0a
--- /dev/null
+++ b/gui/OpenFace-offline/MainWindow.xaml.cs
@@ -0,0 +1,1249 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+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.Navigation;
+using System.Windows.Shapes;
+using Microsoft.Win32;
+
+using OpenCVWrappers;
+using CppInterop;
+using CppInterop.LandmarkDetector;
+using CameraInterop;
+using FaceAnalyser_Interop;
+using System.Windows.Threading;
+using System.IO;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+
+ // Timing for measuring FPS
+ #region High-Resolution Timing
+ static DateTime startTime;
+ static Stopwatch sw = new Stopwatch();
+
+ static MainWindow()
+ {
+ startTime = DateTime.Now;
+ sw.Start();
+ }
+
+ public static DateTime CurrentTime
+ {
+ get { return startTime + sw.Elapsed; }
+ }
+ #endregion
+
+ Thread processing_thread;
+
+ // Some members for displaying the results
+ private Capture capture;
+ private WriteableBitmap latest_img;
+ private WriteableBitmap latest_aligned_face;
+ private WriteableBitmap latest_HOG_descriptor;
+
+ // Managing the running of the analysis system
+ private volatile bool thread_running;
+ private volatile bool thread_paused = false;
+ // Allows for going forward in time step by step
+ // Useful for visualising things
+ private volatile int skip_frames = 0;
+
+ FpsTracker processing_fps = new FpsTracker();
+
+ volatile bool detectionSucceeding = false;
+
+ volatile bool reset = false;
+
+ // For tracking
+ FaceModelParameters clnf_params;
+ CLNF clnf_model;
+ FaceAnalyserManaged face_analyser;
+
+ // Recording parameters (default values)
+ bool record_aus = false; // Recording Action Units
+ bool record_pose = false; // head location and orientation
+ bool record_params = false; // rigid and non-rigid shape parameters
+ bool record_2D_landmarks = false; // 2D landmark location
+ bool record_3D_landmarks = false; // 3D landmark locations in world coordinates
+ bool record_HOG = false; // HOG features extracted from face images
+ bool record_gaze = false; // Gaze recording
+ bool record_aligned = false; // aligned face images
+ bool record_tracked_vid = false;
+
+ // Visualisation options
+ bool show_tracked_video = true;
+ bool show_appearance = true;
+ bool show_geometry = true;
+ bool show_aus = true;
+
+ // TODO classifiers converted to regressors
+
+ // TODO indication that track is done
+
+ // The recording managers
+ StreamWriter output_head_pose_file;
+ StreamWriter output_clnf_params_file;
+ StreamWriter output_2D_landmarks_file;
+ StreamWriter output_3D_landmarks_file;
+ StreamWriter output_au_class;
+ StreamWriter output_au_reg;
+ StreamWriter output_gaze;
+
+ // Where the recording is done (by default in a record directory, from where the application executed)
+ String record_root = "./record";
+
+ // For AU visualisation and output
+ List au_class_names;
+ List au_reg_names;
+
+ // For AU prediction
+ bool dynamic_AU_shift = true;
+ bool dynamic_AU_scale = false;
+ bool use_dynamic_models = true;
+
+ private volatile bool mirror_image = false;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ // Set the icon
+ Uri iconUri = new Uri("logo1.ico", UriKind.RelativeOrAbsolute);
+ this.Icon = BitmapFrame.Create(iconUri);
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 2000), (Action)(() =>
+ {
+ RecordAUCheckBox.IsChecked = record_aus;
+ RecordAlignedCheckBox.IsChecked = record_aligned;
+ RecordTrackedVidCheckBox.IsChecked = record_tracked_vid;
+ RecordHOGCheckBox.IsChecked = record_HOG;
+ RecordGazeCheckBox.IsChecked = record_gaze;
+ RecordLandmarks2DCheckBox.IsChecked = record_2D_landmarks;
+ RecordLandmarks3DCheckBox.IsChecked = record_3D_landmarks;
+ RecordParamsCheckBox.IsChecked = record_params;
+ RecordPoseCheckBox.IsChecked = record_pose;
+
+ UseDynamicModelsCheckBox.IsChecked = use_dynamic_models;
+ UseDynamicScalingCheckBox.IsChecked = dynamic_AU_scale;
+ UseDynamicShiftingCheckBox.IsChecked = dynamic_AU_shift;
+ }));
+
+ String root = AppDomain.CurrentDomain.BaseDirectory;
+
+ clnf_params = new FaceModelParameters(root);
+ clnf_model = new CLNF(clnf_params);
+ face_analyser = new FaceAnalyserManaged(root, use_dynamic_models);
+
+ }
+
+ private bool ProcessFrame(CLNF clnf_model, FaceModelParameters clnf_params, RawImage frame, RawImage grayscale_frame, double fx, double fy, double cx, double cy)
+ {
+ detectionSucceeding = clnf_model.DetectLandmarksInVideo(grayscale_frame, clnf_params);
+ return detectionSucceeding;
+
+ }
+
+ private List>> ProcessImage(CLNF clnf_model, FaceModelParameters clnf_params, RawImage frame, RawImage grayscale_frame)
+ {
+ List>> landmark_detections = clnf_model.DetectMultiFaceLandmarksInImage(grayscale_frame, clnf_params);
+ return landmark_detections;
+
+ }
+
+ private void SetupRecording(String root, String filename, int width, int height)
+ {
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ RecordingMenu.IsEnabled = false;
+ UseDynamicModelsCheckBox.IsEnabled = false;
+ }));
+
+ if (!System.IO.Directory.Exists(root))
+ {
+ System.IO.Directory.CreateDirectory(root);
+ }
+
+ if (record_pose)
+ {
+ String filename_poses = root + "/" + filename + ".pose";
+ output_head_pose_file = new StreamWriter(filename_poses);
+ output_head_pose_file.WriteLine("frame,success,confidence,pose_X(mm),pose_Y(mm),pose_Z(mm),pitch(rad),yaw(rad),roll(rad)");
+ }
+
+ if (record_params)
+ {
+ String filename_params = root + "/" + filename + ".params";
+ output_clnf_params_file = new StreamWriter(filename_params);
+
+ output_clnf_params_file.Write("frame,success,confidence,scale,rot_x,rot_y,rot_z,tx,ty");
+ for (int i = 0; i < clnf_model.GetNumModes(); ++i)
+ {
+ output_clnf_params_file.Write(",p" + i);
+ }
+ output_clnf_params_file.WriteLine();
+ }
+
+ if (record_2D_landmarks)
+ {
+ String filename_2d_landmarks = root + "/" + filename + ".landmarks_2d";
+ output_2D_landmarks_file = new StreamWriter(filename_2d_landmarks);
+
+ output_2D_landmarks_file.Write("frame,success,confidence");
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_2D_landmarks_file.Write(",x" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_2D_landmarks_file.Write(",y" + i);
+ }
+ output_2D_landmarks_file.WriteLine();
+ }
+
+ if (record_aus)
+ {
+ String filename_au_class = root + "/" + filename + ".au_class";
+ output_au_class = new StreamWriter(filename_au_class);
+
+ output_au_class.Write("frame,success,confidence");
+ au_class_names = face_analyser.GetClassActionUnitsNames();
+ au_class_names.Sort();
+ foreach (var name in au_class_names)
+ {
+ output_au_class.Write("," + name);
+ }
+ output_au_class.WriteLine();
+
+ String filename_au_reg = root + "/" + filename + ".au_reg";
+ output_au_reg = new StreamWriter(filename_au_reg);
+
+ output_au_reg.Write("frame,success,confidence");
+ au_reg_names = face_analyser.GetRegActionUnitsNames();
+ au_reg_names.Sort();
+ foreach (var name in au_reg_names)
+ {
+ output_au_reg.Write("," + name);
+ }
+ output_au_reg.WriteLine();
+
+ }
+
+ if (record_gaze)
+ {
+ String filename_gaze = root + "/" + filename + ".gaze";
+ output_gaze = new StreamWriter(filename_gaze);
+
+ output_gaze.Write("frame, success, confidence, x_0, y_0, z_0, x_1, y_1, z_1, x_h0, y_h0, z_h0, x_h1, y_h1, z_h1");
+ }
+
+ if (record_3D_landmarks)
+ {
+ String filename_3d_landmarks = root + "/" + filename + ".landmarks_3d";
+ output_3D_landmarks_file = new StreamWriter(filename_3d_landmarks);
+
+ output_3D_landmarks_file.Write("frame,success,confidence");
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_3D_landmarks_file.Write(",X" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_3D_landmarks_file.Write(",Y" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_3D_landmarks_file.Write(",Z" + i);
+ }
+ output_3D_landmarks_file.WriteLine();
+ }
+
+ if (record_aligned)
+ {
+ String aligned_root = root + "/" + filename + "_aligned/";
+ System.IO.Directory.CreateDirectory(aligned_root);
+ face_analyser.SetupAlignedImageRecording(aligned_root);
+ }
+
+ if (record_tracked_vid)
+ {
+ String vid_loc = root + "/" + filename + ".avi";
+ System.IO.Directory.CreateDirectory(root);
+ face_analyser.SetupTrackingRecording(vid_loc, width, height, 30);
+ }
+
+ if (record_HOG)
+ {
+ String filename_HOG = root + "/" + filename + ".hog";
+ face_analyser.SetupHOGRecording(filename_HOG);
+ }
+
+ }
+
+ private void StopRecording()
+ {
+ if (record_pose && output_head_pose_file != null)
+ output_head_pose_file.Close();
+
+ if (record_params && output_clnf_params_file != null)
+ output_clnf_params_file.Close();
+
+ if (record_2D_landmarks && output_2D_landmarks_file != null)
+ output_2D_landmarks_file.Close();
+
+ if (record_3D_landmarks && output_3D_landmarks_file != null)
+ output_3D_landmarks_file.Close();
+
+ if (record_gaze && output_gaze != null)
+ output_gaze.Close();
+
+ if (record_HOG)
+ face_analyser.StopHOGRecording();
+
+ if (record_tracked_vid)
+ face_analyser.StopTrackingRecording();
+
+ if (record_aus && output_au_class != null && output_au_reg != null)
+ {
+ output_au_class.Close();
+ output_au_reg.Close();
+ }
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ RecordingMenu.IsEnabled = true;
+ UseDynamicModelsCheckBox.IsEnabled = true;
+
+ }));
+
+ }
+
+ // Recording the relevant objects
+ private void RecordFrame(CLNF clnf_model, bool success_b, int frame_ind, RawImage frame, RawImage grayscale_frame, double fx, double fy, double cx, double cy)
+ {
+ double confidence = (-clnf_model.GetConfidence()) / 2.0 + 0.5;
+
+ List pose = new List();
+ clnf_model.GetCorrectedPoseWorld(pose, fx, fy, cx, cy);
+
+ int success = 0;
+ if (success_b)
+ success = 1;
+
+ if (record_pose)
+ {
+ String pose_string = String.Format("{0},{1},{2:F3},{3:F3},{4:F3},{5:F3},{6:F3},{7:F3},{8:F3}", frame_ind, success, confidence, pose[0], pose[1], pose[2], pose[3], pose[4], pose[5]);
+ output_head_pose_file.WriteLine(pose_string);
+ }
+
+ if (record_params)
+ {
+ output_clnf_params_file.Write(String.Format("{0},{1},{2,0:F3}", frame_ind, success, confidence));
+
+ List all_params = clnf_model.GetParams();
+
+ for (int i = 0; i < all_params.Count; ++i)
+ {
+ String param = String.Format("{0,0:F5}", all_params[i]);
+ output_clnf_params_file.Write("," + param);
+ }
+ output_clnf_params_file.WriteLine();
+ }
+
+ if (record_2D_landmarks)
+ {
+ List> landmarks_2d = clnf_model.CalculateLandmarks();
+
+ output_2D_landmarks_file.Write(String.Format("{0},{1},{2:F3}", frame_ind, success, confidence));
+
+ for (int i = 0; i < landmarks_2d.Count; ++i)
+ {
+ output_2D_landmarks_file.Write(",{0:F2}", landmarks_2d[i].Item1);
+ }
+ for (int i = 0; i < landmarks_2d.Count; ++i)
+ {
+ output_2D_landmarks_file.Write(",{0:F2}", landmarks_2d[i].Item2);
+ }
+ output_2D_landmarks_file.WriteLine();
+ }
+
+ if (record_3D_landmarks)
+ {
+ List landmarks_3d = clnf_model.Calculate3DLandmarks(fx, fy, cx, cy);
+
+ output_3D_landmarks_file.Write(String.Format("{0},{1},{2:F3}", frame_ind, success, confidence));
+
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ {
+ output_3D_landmarks_file.Write(",{0:F2}", landmarks_3d[i].X);
+ }
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ {
+ output_3D_landmarks_file.Write(",{0:F2}", landmarks_3d[i].Y);
+ }
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ {
+ output_3D_landmarks_file.Write(",{0:F2}", landmarks_3d[i].Z);
+ }
+ output_3D_landmarks_file.WriteLine();
+ }
+
+ if (record_aus)
+ {
+ var au_classes = face_analyser.GetCurrentAUsClass();
+ var au_regs = face_analyser.GetCurrentAUsReg();
+
+ output_au_class.Write(String.Format("{0},{1},{2:F3}", frame_ind, success, confidence));
+
+ foreach (var name_class in au_class_names)
+ {
+ output_au_class.Write(",{0:F0}", au_classes[name_class]);
+ }
+ output_au_class.WriteLine();
+
+ output_au_reg.Write(String.Format("{0},{1},{2:F3}", frame_ind, success, confidence));
+
+ foreach (var name_reg in au_reg_names)
+ {
+ output_au_reg.Write(",{0:F2}", au_regs[name_reg]);
+ }
+ output_au_reg.WriteLine();
+
+ }
+
+ if (record_gaze)
+ {
+
+ var gaze_cam = face_analyser.GetGazeCamera();
+
+ output_gaze.Write(String.Format("{0},{1},{2:F3}", frame_ind, success, confidence));
+
+ output_gaze.Write(String.Format(",{0:F3},{1:F3},{2:F3},{3:F3},{4:F3},{5:F3}", gaze_cam.Item1.Item1, gaze_cam.Item1.Item2, gaze_cam.Item1.Item3,
+ gaze_cam.Item2.Item1, gaze_cam.Item2.Item2, gaze_cam.Item2.Item3));
+
+ output_gaze.WriteLine();
+
+ }
+
+ if (record_aligned)
+ {
+ face_analyser.RecordAlignedFrame(frame_ind);
+ }
+
+ if (record_HOG)
+ {
+ face_analyser.RecordHOGFrame();
+ }
+
+ if (record_tracked_vid)
+ {
+ face_analyser.RecordTrackedFace();
+ }
+ }
+
+ // The main function call for processing images, video files or webcam feed
+ private void ProcessingLoop(String[] filenames, int cam_id = -1, int width = -1, int height = -1, bool multi_face = false)
+ {
+
+ thread_running = true;
+
+ mirror_image = false;
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ ResetButton.IsEnabled = true;
+ PauseButton.IsEnabled = true;
+ StopButton.IsEnabled = true;
+ }));
+
+ // Create the video capture and call the VideoLoop
+ if (filenames != null)
+ {
+ clnf_params.optimiseForVideo();
+ if (cam_id == -2)
+ {
+ List image_files_all = new List();
+ foreach (string image_name in filenames)
+ image_files_all.Add(image_name);
+
+ // Loading an image sequence that represents a video
+ capture = new Capture(image_files_all);
+
+ if (capture.isOpened())
+ {
+ // Prepare recording if any based on the directory
+ String file_no_ext = System.IO.Path.GetDirectoryName(filenames[0]);
+ file_no_ext = System.IO.Path.GetFileName(file_no_ext);
+ SetupRecording(record_root, file_no_ext, capture.width, capture.height);
+
+ // Start the actual processing
+ VideoLoop();
+
+ // Clear up the recording
+ StopRecording();
+
+ }
+ else
+ {
+ string messageBoxText = "Failed to open an image";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ else if (cam_id == -3)
+ {
+ SetupImageMode();
+ clnf_params.optimiseForImages();
+ // Loading an image file (or a number of them)
+ foreach (string filename in filenames)
+ {
+ if (!thread_running)
+ {
+ continue;
+ }
+
+ capture = new Capture(filename);
+
+ if (capture.isOpened())
+ {
+ // Start the actual processing
+ ProcessImage();
+
+ }
+ else
+ {
+ string messageBoxText = "File is not an image or the decoder is not supported.";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ }
+ else
+ {
+ clnf_params.optimiseForVideo();
+ // Loading a video file (or a number of them)
+ foreach (string filename in filenames)
+ {
+ if (!thread_running)
+ {
+ continue;
+ }
+
+ capture = new Capture(filename);
+
+ if (capture.isOpened())
+ {
+ // Prepare recording if any
+ String file_no_ext = System.IO.Path.GetFileNameWithoutExtension(filename);
+
+ SetupRecording(record_root, file_no_ext, capture.width, capture.height);
+
+ // Start the actual processing
+ VideoLoop();
+
+ // Clear up the recording
+ StopRecording();
+ }
+ else
+ {
+ string messageBoxText = "File is not a video or the codec is not supported.";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ }
+ }
+ else
+ {
+ capture = new Capture(cam_id, width, height);
+ mirror_image = true;
+
+ if (capture.isOpened())
+ {
+ // Prepare recording if any
+ String dir_out = DateTime.Now.ToString("yyyy-MMM-dd--HH-mm");
+
+ SetupRecording(record_root + "/" + dir_out, "webcam", width, height);
+
+ // Start the actual processing
+ VideoLoop();
+
+ StopRecording();
+ }
+ else
+ {
+
+ string messageBoxText = "Failed to open a webcam";
+ string caption = "Webcam failure";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+
+ // TODO this should be up a level
+ // Some GUI clean up
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ Console.WriteLine("Cleaning up after processing is done");
+ PauseButton.IsEnabled = false;
+ StopButton.IsEnabled = false;
+ ResetButton.IsEnabled = false;
+ NextFiveFramesButton.IsEnabled = false;
+ NextFrameButton.IsEnabled = false;
+ }));
+
+ }
+
+ // Capturing and processing the video frame by frame
+ private void ProcessImage()
+ {
+ Thread.CurrentThread.IsBackground = true;
+
+ clnf_model.Reset();
+ face_analyser.Reset();
+
+
+ //////////////////////////////////////////////
+ // CAPTURE FRAME AND DETECT LANDMARKS FOLLOWED BY THE REQUIRED IMAGE PROCESSING
+ //////////////////////////////////////////////
+ RawImage frame = null;
+ double progress = -1;
+
+ frame = new RawImage(capture.GetNextFrame(mirror_image));
+ progress = capture.GetProgress();
+
+ if (frame.Width == 0)
+ {
+ // This indicates that we reached the end of the video file
+ return;
+ }
+
+ var grayFrame = new RawImage(capture.GetCurrentFrameGray());
+
+ if (grayFrame == null)
+ {
+ Console.WriteLine("Gray is empty");
+ return;
+ }
+
+ List>> landmark_detections = ProcessImage(clnf_model, clnf_params, frame, grayFrame);
+
+ List landmark_points = new List();
+
+ for (int i = 0; i < landmark_detections.Count; ++i)
+ {
+
+ List> landmarks = landmark_detections[i];
+ foreach (var p in landmarks)
+ {
+ landmark_points.Add(new Point(p.Item1, p.Item2));
+ }
+ }
+
+ // Visualisation
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ if (show_tracked_video)
+ {
+ if (latest_img == null)
+ {
+ latest_img = frame.CreateWriteableBitmap();
+ }
+
+ frame.UpdateWriteableBitmap(latest_img);
+
+ video.Source = latest_img;
+ video.Confidence = 1;
+ video.FPS = processing_fps.GetFPS();
+ video.Progress = progress;
+
+ video.OverlayLines = new List>();
+
+ video.OverlayPoints = landmark_points;
+ }
+
+ }));
+
+ latest_img = null;
+ }
+
+
+ // Capturing and processing the video frame by frame
+ private void VideoLoop()
+ {
+ Thread.CurrentThread.IsBackground = true;
+
+ DateTime? startTime = CurrentTime;
+
+ var lastFrameTime = CurrentTime;
+
+ clnf_model.Reset();
+ face_analyser.Reset();
+
+ // TODO add an ability to change these through a calibration procedure or setting menu
+ double fx, fy, cx, cy;
+ fx = 500.0;
+ fy = 500.0;
+ cx = cy = -1;
+
+ int frame_id = 0;
+
+ while (thread_running)
+ {
+ //////////////////////////////////////////////
+ // CAPTURE FRAME AND DETECT LANDMARKS FOLLOWED BY THE REQUIRED IMAGE PROCESSING
+ //////////////////////////////////////////////
+ RawImage frame = null;
+ double progress = -1;
+
+ frame = new RawImage(capture.GetNextFrame(mirror_image));
+ progress = capture.GetProgress();
+
+ if (frame.Width == 0)
+ {
+ // This indicates that we reached the end of the video file
+ break;
+ }
+
+ // TODO stop button actually clears the video
+
+ lastFrameTime = CurrentTime;
+ processing_fps.AddFrame();
+
+ var grayFrame = new RawImage(capture.GetCurrentFrameGray());
+
+ if (grayFrame == null)
+ {
+ Console.WriteLine("Gray is empty");
+ continue;
+ }
+
+ // This is more ore less guess work, but seems to work well enough
+ if (cx == -1)
+ {
+ fx = fx * (grayFrame.Width / 640.0);
+ fy = fy * (grayFrame.Height / 480.0);
+
+ fx = (fx + fy) / 2.0;
+ fy = fx;
+
+ cx = grayFrame.Width / 2f;
+ cy = grayFrame.Height / 2f;
+ }
+
+ bool detectionSucceeding = ProcessFrame(clnf_model, clnf_params, frame, grayFrame, fx, fy, cx, cy);
+
+
+
+ double confidence = (-clnf_model.GetConfidence()) / 2.0 + 0.5;
+
+ if (confidence < 0)
+ confidence = 0;
+ else if (confidence > 1)
+ confidence = 1;
+
+ List pose = new List();
+ clnf_model.GetCorrectedPoseWorld(pose, fx, fy, cx, cy);
+ List non_rigid_params = clnf_model.GetNonRigidParams();
+
+ // The face analysis step (only done if recording AUs, HOGs or video)
+ if (record_aus || record_HOG || record_aligned || show_aus || show_appearance || record_tracked_vid || record_gaze)
+ {
+ face_analyser.AddNextFrame(frame, clnf_model, fx, fy, cx, cy, false, show_appearance, record_tracked_vid);
+ }
+
+ List> lines = null;
+ List> landmarks = null;
+ List> gaze_lines = null;
+
+ if (detectionSucceeding)
+ {
+ landmarks = clnf_model.CalculateLandmarks();
+ lines = clnf_model.CalculateBox((float)fx, (float)fy, (float)cx, (float)cy);
+ gaze_lines = face_analyser.CalculateGazeLines((float)fx, (float)fy, (float)cx, (float)cy);
+
+ }
+
+ // Visualisation
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ if (show_aus)
+ {
+ var au_classes = face_analyser.GetCurrentAUsClass();
+ var au_regs = face_analyser.GetCurrentAUsReg();
+
+ auClassGraph.Update(au_classes);
+
+ var au_regs_scaled = new Dictionary();
+ foreach (var au_reg in au_regs)
+ {
+ au_regs_scaled[au_reg.Key] = au_reg.Value / 5.0;
+ if (au_regs_scaled[au_reg.Key] < 0)
+ au_regs_scaled[au_reg.Key] = 0;
+
+ if (au_regs_scaled[au_reg.Key] > 1)
+ au_regs_scaled[au_reg.Key] = 1;
+ }
+ auRegGraph.Update(au_regs_scaled);
+ }
+
+ if (show_geometry)
+ {
+ int yaw = (int)(pose[4] * 180 / Math.PI + 0.5);
+ int roll = (int)(pose[5] * 180 / Math.PI + 0.5);
+ int pitch = (int)(pose[3] * 180 / Math.PI + 0.5);
+
+ YawLabel.Content = yaw + "°";
+ RollLabel.Content = roll + "°";
+ PitchLabel.Content = pitch + "°";
+
+ XPoseLabel.Content = (int)pose[0] + " mm";
+ YPoseLabel.Content = (int)pose[1] + " mm";
+ ZPoseLabel.Content = (int)pose[2] + " mm";
+
+ nonRigidGraph.Update(non_rigid_params);
+
+ // Update eye gaze
+ var gaze_both = face_analyser.GetGazeCamera();
+ double x = (gaze_both.Item1.Item1 + gaze_both.Item2.Item1) / 2.0;
+ double y = (gaze_both.Item1.Item2 + gaze_both.Item2.Item2) / 2.0;
+
+ // Tweak it to a more presentable value
+ x = (int)(x * 35);
+ y = (int)(y * 70);
+
+ if (x < -10)
+ x = -10;
+ if (x > 10)
+ x = 10;
+ if (y < -10)
+ y = -10;
+ if (y > 10)
+ y = 10;
+
+ GazeXLabel.Content = x / 10.0;
+ GazeYLabel.Content = y / 10.0;
+
+
+ }
+
+ if (show_tracked_video)
+ {
+ if (latest_img == null)
+ {
+ latest_img = frame.CreateWriteableBitmap();
+ }
+
+ frame.UpdateWriteableBitmap(latest_img);
+
+ video.Source = latest_img;
+ video.Confidence = confidence;
+ video.FPS = processing_fps.GetFPS();
+ video.Progress = progress;
+
+ if (!detectionSucceeding)
+ {
+ video.OverlayLines.Clear();
+ video.OverlayPoints.Clear();
+ video.GazeLines.Clear();
+ }
+ else
+ {
+ video.OverlayLines = lines;
+
+ List landmark_points = new List();
+ foreach (var p in landmarks)
+ {
+ landmark_points.Add(new Point(p.Item1, p.Item2));
+ }
+
+ video.OverlayPoints = landmark_points;
+
+ video.GazeLines = gaze_lines;
+ }
+ }
+
+ if (show_appearance)
+ {
+ RawImage aligned_face = face_analyser.GetLatestAlignedFace();
+ RawImage hog_face = face_analyser.GetLatestHOGDescriptorVisualisation();
+
+ if (latest_aligned_face == null)
+ {
+ latest_aligned_face = aligned_face.CreateWriteableBitmap();
+ latest_HOG_descriptor = hog_face.CreateWriteableBitmap();
+ }
+
+ aligned_face.UpdateWriteableBitmap(latest_aligned_face);
+ hog_face.UpdateWriteableBitmap(latest_HOG_descriptor);
+
+ AlignedFace.Source = latest_aligned_face;
+ AlignedHOG.Source = latest_HOG_descriptor;
+ }
+ }));
+
+ // Recording the tracked model
+ RecordFrame(clnf_model, detectionSucceeding, frame_id, frame, grayFrame, fx, fy, cx, cy);
+
+ if (reset)
+ {
+ clnf_model.Reset();
+ face_analyser.Reset();
+ reset = false;
+ }
+
+ while (thread_running & thread_paused && skip_frames == 0)
+ {
+ Thread.Sleep(10);
+ }
+
+ frame_id++;
+
+ if (skip_frames > 0)
+ skip_frames--;
+
+ }
+
+ latest_img = null;
+ skip_frames = 0;
+
+ // Unpause if it's paused
+ if (thread_paused)
+ {
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ PauseButton_Click(null, null);
+ }));
+ }
+ }
+
+ private void StopTracking()
+ {
+ // First complete the running of the thread
+ if (processing_thread != null)
+ {
+ // Tell the other thread to finish
+ thread_running = false;
+ processing_thread.Join();
+ }
+ }
+
+ private void imageFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => imageOpen()).Start();
+ }
+
+ private void imageOpen()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Image files|*.jpg;*.jpeg;*.bmp;*.png;*.gif";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] image_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(image_files, -3));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+ private void videoFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => openVideoFile()).Start();
+ }
+
+ private void openVideoFile()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Video files|*.avi;*.wmv;*.mov;*.mpg;*.mpeg;*.mp4";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] video_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(video_files));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+ private void imageSequenceFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => imageSequenceOpen()).Start();
+ }
+
+ private void imageSequenceOpen()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Image files|*.jpg;*.jpeg;*.bmp;*.png;*.gif";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] image_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(image_files, -2));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_running = false;
+ processing_thread.Join();
+
+ capture.Dispose();
+ }
+ face_analyser.Dispose();
+ }
+
+ // Stopping the tracking
+ private void StopButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_paused = false;
+ thread_running = false;
+ processing_thread.Join();
+
+ PauseButton.IsEnabled = false;
+ NextFrameButton.IsEnabled = false;
+ NextFiveFramesButton.IsEnabled = false;
+ StopButton.IsEnabled = false;
+ ResetButton.IsEnabled = false;
+ RecordingMenu.IsEnabled = true;
+
+ UseDynamicModelsCheckBox.IsEnabled = true;
+ }
+ }
+
+ // Stopping the tracking
+ private void ResetButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ reset = true;
+ }
+ }
+
+ // Stopping the tracking
+ private void PauseButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_paused = !thread_paused;
+
+ ResetButton.IsEnabled = !thread_paused;
+
+ NextFrameButton.IsEnabled = thread_paused;
+ NextFiveFramesButton.IsEnabled = thread_paused;
+
+ if (thread_paused)
+ {
+ PauseButton.Content = "Resume";
+ }
+ else
+ {
+ PauseButton.Content = "Pause";
+ }
+ }
+ }
+
+ private void SkipButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender.Equals(NextFrameButton))
+ {
+ skip_frames += 1;
+ }
+ else if (sender.Equals(NextFiveFramesButton))
+ {
+ skip_frames += 5;
+ }
+ }
+
+ private void VisualisationCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ show_tracked_video = ShowVideoCheckBox.IsChecked;
+ show_appearance = ShowAppearanceFeaturesCheckBox.IsChecked;
+ show_geometry = ShowGeometryFeaturesCheckBox.IsChecked;
+ show_aus = ShowAUsCheckBox.IsChecked;
+
+ // Collapsing or restoring the windows here
+ if (!show_tracked_video)
+ {
+ VideoBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ VideoBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[0].Width = new GridLength(2.1, GridUnitType.Star);
+ }
+
+ if (!show_appearance)
+ {
+ AppearanceBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ AppearanceBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[1].Width = new GridLength(0.8, GridUnitType.Star);
+ }
+
+ // Collapsing or restoring the windows here
+ if (!show_geometry)
+ {
+ GeometryBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ GeometryBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[2].Width = new GridLength(1.0, GridUnitType.Star);
+ }
+
+ // Collapsing or restoring the windows here
+ if (!show_aus)
+ {
+ ActionUnitBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[3].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ ActionUnitBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[3].Width = new GridLength(1.6, GridUnitType.Star);
+ }
+
+ }
+
+ private void SetupImageMode()
+ {
+ // Turn off recording
+ record_aus = false;
+ record_aligned = false;
+ record_HOG = false;
+ record_gaze = false;
+ record_tracked_vid = false;
+ record_2D_landmarks = false;
+ record_3D_landmarks = false;
+ record_params = false;
+ record_pose = false;
+
+ // Turn off unneeded visualisations
+ show_tracked_video = true;
+ show_appearance = false;
+ show_geometry = false;
+ show_aus = false;
+
+ // Actually update the GUI accordingly
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 2000), (Action)(() =>
+ {
+ RecordAUCheckBox.IsChecked = record_aus;
+ RecordAlignedCheckBox.IsChecked = record_aligned;
+ RecordTrackedVidCheckBox.IsChecked = record_tracked_vid;
+ RecordHOGCheckBox.IsChecked = record_HOG;
+ RecordGazeCheckBox.IsChecked = record_gaze;
+ RecordLandmarks2DCheckBox.IsChecked = record_2D_landmarks;
+ RecordLandmarks3DCheckBox.IsChecked = record_3D_landmarks;
+ RecordParamsCheckBox.IsChecked = record_params;
+ RecordPoseCheckBox.IsChecked = record_pose;
+
+ ShowVideoCheckBox.IsChecked = true;
+ ShowAppearanceFeaturesCheckBox.IsChecked = false;
+ ShowGeometryFeaturesCheckBox.IsChecked = false;
+ ShowAUsCheckBox.IsChecked = false;
+
+ VisualisationCheckBox_Click(null, null);
+ }));
+
+ // TODO change what next and back buttons do?
+ }
+
+ private void recordCheckBox_click(object sender, RoutedEventArgs e)
+ {
+ record_aus = RecordAUCheckBox.IsChecked;
+ record_aligned = RecordAlignedCheckBox.IsChecked;
+ record_HOG = RecordHOGCheckBox.IsChecked;
+ record_gaze = RecordGazeCheckBox.IsChecked;
+ record_tracked_vid = RecordTrackedVidCheckBox.IsChecked;
+ record_2D_landmarks = RecordLandmarks2DCheckBox.IsChecked;
+ record_3D_landmarks = RecordLandmarks3DCheckBox.IsChecked;
+ record_params = RecordParamsCheckBox.IsChecked;
+ record_pose = RecordPoseCheckBox.IsChecked;
+ }
+
+ private void UseDynamicModelsCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ dynamic_AU_shift = UseDynamicShiftingCheckBox.IsChecked;
+ dynamic_AU_scale = UseDynamicScalingCheckBox.IsChecked;
+
+ if (use_dynamic_models != UseDynamicModelsCheckBox.IsChecked)
+ {
+ // Change the face analyser, this should be safe as the model is only allowed to change when not running
+ String root = AppDomain.CurrentDomain.BaseDirectory;
+ face_analyser = new FaceAnalyserManaged(root, UseDynamicModelsCheckBox.IsChecked);
+ }
+ use_dynamic_models = UseDynamicModelsCheckBox.IsChecked;
+ }
+
+ }
+}
diff --git a/gui/OpenFace-offline/OpenFaceOffline.csproj b/gui/OpenFace-offline/OpenFaceOffline.csproj
new file mode 100644
index 00000000..71f59c2f
--- /dev/null
+++ b/gui/OpenFace-offline/OpenFaceOffline.csproj
@@ -0,0 +1,178 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {9661BE5C-2EE5-495E-BA64-6588602F411B}
+ WinExe
+ Properties
+ OpenFaceOffline
+ OpenFaceOffline
+ v4.5.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+
+
+
+
+
+
+ true
+ ..\..\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ ..\..\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ true
+ ..\..\x64\Debug\
+ DEBUG;TRACE
+ full
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ ..\..\x64\Release\
+ TRACE
+ true
+ pdbonly
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ BarGraph.xaml
+
+
+
+ BarGraphHorizontal.xaml
+
+
+ MultiBarGraph.xaml
+
+
+ MultiBarGraphHorz.xaml
+
+
+ SimpleImage.xaml
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ OverlayImage.xaml
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+ {78196985-ee54-411f-822b-5a23edf80642}
+ CppInerop
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/Properties/AssemblyInfo.cs b/gui/OpenFace-offline/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..75449e41
--- /dev/null
+++ b/gui/OpenFace-offline/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("OpenFaceOffline")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("OpenFaceOffline")]
+[assembly: AssemblyCopyright("Copyright © Cambridge 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/gui/OpenFace-offline/Properties/Resources.Designer.cs b/gui/OpenFace-offline/Properties/Resources.Designer.cs
new file mode 100644
index 00000000..a359eeae
--- /dev/null
+++ b/gui/OpenFace-offline/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace OpenFaceOffline.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenFaceOffline.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/Properties/Resources.resx b/gui/OpenFace-offline/Properties/Resources.resx
new file mode 100644
index 00000000..af7dbebb
--- /dev/null
+++ b/gui/OpenFace-offline/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/Properties/Settings.Designer.cs b/gui/OpenFace-offline/Properties/Settings.Designer.cs
new file mode 100644
index 00000000..f31bb9d0
--- /dev/null
+++ b/gui/OpenFace-offline/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace OpenFaceOffline.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/Properties/Settings.settings b/gui/OpenFace-offline/Properties/Settings.settings
new file mode 100644
index 00000000..033d7a5e
--- /dev/null
+++ b/gui/OpenFace-offline/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/UI_items/BarGraph.xaml b/gui/OpenFace-offline/UI_items/BarGraph.xaml
new file mode 100644
index 00000000..2101d740
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/BarGraph.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFace-offline/UI_items/BarGraph.xaml.cs b/gui/OpenFace-offline/UI_items/BarGraph.xaml.cs
new file mode 100644
index 00000000..0a089c76
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/BarGraph.xaml.cs
@@ -0,0 +1,39 @@
+using System.Windows.Controls;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for BarGraph.xaml
+ ///
+ public partial class BarGraph : UserControl
+ {
+ private double targetValue = 0;
+
+ public BarGraph()
+ {
+ InitializeComponent();
+ }
+
+ public void SetValue(double value)
+ {
+ targetValue = 1.5 * value;
+ if (targetValue > 0)
+ {
+ if (targetValue > barContainerPos.ActualHeight)
+ targetValue = barContainerPos.ActualHeight;
+
+ barPos.Height = targetValue;
+ barNeg.Height = 0;
+ }
+ if (targetValue < 0)
+ {
+ if (-targetValue > barContainerNeg.ActualHeight)
+ targetValue = -barContainerNeg.ActualHeight;
+
+ barPos.Height = 0;
+ barNeg.Height = -targetValue;
+ }
+ }
+
+ }
+}
diff --git a/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml b/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml
new file mode 100644
index 00000000..f5ccbe5e
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml.cs b/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml.cs
new file mode 100644
index 00000000..8c18d4a0
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/BarGraphHorizontal.xaml.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for BarGraphHorizontal.xaml
+ ///
+ public partial class BarGraphHorizontal : UserControl
+ {
+ double targetValue = 0;
+
+ public BarGraphHorizontal(String label)
+ {
+ InitializeComponent();
+ Label.Content = label;
+ }
+
+ public void SetValue(double value)
+ {
+ targetValue = value;
+ barPos.Width = targetValue * barContainerPos.ActualWidth;
+ }
+
+ public double GetTarget()
+ {
+ return targetValue;
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml b/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml
new file mode 100644
index 00000000..836c9742
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml.cs b/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml.cs
new file mode 100644
index 00000000..c1019417
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/MultiBarGraph.xaml.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MultiBarGraph.xaml
+ ///
+ public partial class MultiBarGraph : UserControl
+ {
+
+ int num_bars = 0;
+ List graphs;
+
+ public MultiBarGraph()
+ {
+ InitializeComponent();
+
+ graphs = new List();
+ }
+
+ public void Update(List data)
+ {
+ // Create new bars if necessary
+ if (num_bars != data.Count)
+ {
+ num_bars = data.Count;
+ barGrid.Children.Clear();
+ foreach (var value in data)
+ {
+ BarGraph newBar = new BarGraph();
+ graphs.Add(newBar);
+ barGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ Grid.SetColumn(newBar, graphs.Count);
+ barGrid.Children.Add(newBar);
+
+ }
+ }
+
+ // Update the bars
+ for (int i = 0; i < data.Count; ++i)
+ {
+ graphs[i].SetValue(data[i]);
+ }
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml b/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml
new file mode 100644
index 00000000..db5728f6
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml.cs b/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml.cs
new file mode 100644
index 00000000..f960b71e
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/MultiBarGraphHorz.xaml.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MultiBarGraphHorz.xaml
+ ///
+ public partial class MultiBarGraphHorz : UserControl
+ {
+ int num_bars = 0;
+ Dictionary graphs;
+
+ // Name mapping
+ Dictionary mapping;
+
+ public MultiBarGraphHorz()
+ {
+ InitializeComponent();
+
+ graphs = new Dictionary();
+
+ mapping = new Dictionary();
+ mapping["AU01"] = "Inner Brow raiser";
+ mapping["AU02"] = "Outer Brow raiser";
+ mapping["AU04"] = "Brow lowerer";
+ mapping["AU05"] = "Upper lid raiser";
+ mapping["AU06"] = "Cheek raiser";
+ mapping["AU07"] = "Lid tightener";
+ mapping["AU09"] = "Nose wrinkler";
+ mapping["AU10"] = "Upper lip raiser";
+ mapping["AU12"] = "Lip corner puller (smile)";
+ mapping["AU14"] = "Dimpler";
+ mapping["AU15"] = "Lip corner depressor";
+ mapping["AU17"] = "Chin Raiser";
+ mapping["AU20"] = "Lip Stretcher";
+ mapping["AU23"] = "Lip tightener";
+ mapping["AU25"] = "Lips part";
+ mapping["AU26"] = "Jaw drop";
+ mapping["AU28"] = "Lip suck";
+ mapping["AU45"] = "Blink";
+
+
+
+ }
+
+ public void Update(Dictionary data)
+ {
+ // Create new bars if necessary
+ if (num_bars != data.Count)
+ {
+ num_bars = data.Count;
+ barGrid.Children.Clear();
+
+ // Make sure AUs are sorted
+ var data_labels = data.Keys.ToList();
+ data_labels.Sort();
+
+ foreach (var label in data_labels)
+ {
+ BarGraphHorizontal newBar = new BarGraphHorizontal(label + " - " + mapping[label]);
+ barGrid.RowDefinitions.Add(new RowDefinition());
+ Grid.SetRow(newBar, graphs.Count);
+ graphs.Add(label, newBar);
+ barGrid.Children.Add(newBar);
+ }
+ }
+
+ // Update the bars
+ foreach (var value in data)
+ {
+ double old_value = graphs[value.Key].GetTarget();
+ // some smoothing as well
+ graphs[value.Key].SetValue(old_value * 0.15 + 0.85 * value.Value);
+ }
+ }
+ }
+}
diff --git a/gui/OpenFace-offline/UI_items/OverlayImage.xaml b/gui/OpenFace-offline/UI_items/OverlayImage.xaml
new file mode 100644
index 00000000..8dcdaea5
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/OverlayImage.xaml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/UI_items/OverlayImage.xaml.cs b/gui/OpenFace-offline/UI_items/OverlayImage.xaml.cs
new file mode 100644
index 00000000..4546fec2
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/OverlayImage.xaml.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for OverlayImage.xaml
+ ///
+ public partial class OverlayImage : Image
+ {
+ public OverlayImage()
+ {
+ InitializeComponent();
+ OverlayLines = new List>();
+ OverlayPoints = new List();
+ GazeLines = new List>();
+
+ Progress = -1;
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+ if (OverlayLines == null)
+ OverlayLines = new List>();
+
+ if (OverlayPoints == null)
+ OverlayPoints = new List();
+
+ if (Source == null || !(Source is WriteableBitmap))
+ return;
+
+ var width = ((WriteableBitmap)Source).PixelWidth;
+ var height = ((WriteableBitmap)Source).PixelHeight;
+
+ foreach (var line in OverlayLines)
+ {
+
+ var p1 = new Point(ActualWidth * line.Item1.X / width, ActualHeight * line.Item1.Y / height);
+ var p2 = new Point(ActualWidth * line.Item2.X / width, ActualHeight * line.Item2.Y / height);
+
+ dc.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, (byte)(100 + (155 * (1 - Confidence))), (byte)(100 + (155 * Confidence)), 100)), 2), p1, p2);
+ }
+
+ foreach (var line in GazeLines)
+ {
+
+ var p1 = new Point(ActualWidth * line.Item1.X / width, ActualHeight * line.Item1.Y / height);
+ var p2 = new Point(ActualWidth * line.Item2.X / width, ActualHeight * line.Item2.Y / height);
+
+ dc.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, (byte)(240), (byte)(30), (byte)100)), 3), p1, p2);
+
+ }
+
+ foreach (var p in OverlayPoints)
+ {
+
+ var q = new Point(ActualWidth * p.X / width, ActualHeight * p.Y / height);
+
+ dc.DrawEllipse(new SolidColorBrush(Color.FromArgb((byte)(200 * Confidence), 255, 255, 100)), null, q, 2, 2);
+ }
+
+ double scaling = ActualWidth / 400.0;
+
+ int confidence_width = (int)(107.0 * scaling);
+ int confidence_height = (int)(18.0 * scaling);
+
+ Brush conf_brush = new SolidColorBrush(Color.FromRgb((byte)((1 - Confidence) * 255), (byte)(Confidence * 255), (byte)40));
+ dc.DrawRoundedRectangle(conf_brush, new Pen(Brushes.Black, 0.5 * scaling), new Rect(ActualWidth - confidence_width - 1, 0, confidence_width, confidence_height), 3.0 * scaling, 3.0 * scaling);
+
+ FormattedText txt = new FormattedText("Confidence: " + (int)(100 * Confidence) + "%", System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 12.0 * scaling, Brushes.Black);
+ dc.DrawText(txt, new Point(ActualWidth - confidence_width + 2, 2));
+
+ int fps_width = (int)(52.0 * scaling);
+ int fps_height = (int)(18.0 * scaling);
+
+ dc.DrawRoundedRectangle(Brushes.WhiteSmoke, new Pen(Brushes.Black, 0.5 * scaling), new Rect(0, 0, fps_width, fps_height), 3.0 * scaling, 3.0 * scaling);
+ FormattedText fps_txt = new FormattedText("FPS: " + (int)FPS, System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 12.0 * scaling, Brushes.Black);
+ dc.DrawText(fps_txt, new Point(2.0 * scaling, 0));
+
+ old_width = width;
+ old_height = height;
+
+ // Drawing a progress bar at the bottom of the image
+ if (Progress > 0)
+ {
+ int progress_bar_height = (int)(6.0 * scaling);
+ dc.DrawRectangle(Brushes.GreenYellow, new Pen(Brushes.Black, 0.5 * scaling), new Rect(0, ActualHeight - progress_bar_height, Progress * ActualWidth, progress_bar_height));
+ }
+
+ }
+
+ public List> OverlayLines { get; set; }
+ public List> GazeLines { get; set; }
+ public List OverlayPoints { get; set; }
+ public double Confidence { get; set; }
+ public double FPS { get; set; }
+
+ // 0 to 1 indicates how much video has been processed so far
+ public double Progress { get; set; }
+
+ int old_width;
+ int old_height;
+ }
+}
diff --git a/gui/OpenFace-offline/UI_items/SimpleImage.xaml b/gui/OpenFace-offline/UI_items/SimpleImage.xaml
new file mode 100644
index 00000000..9cf612d7
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/SimpleImage.xaml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/gui/OpenFace-offline/UI_items/SimpleImage.xaml.cs b/gui/OpenFace-offline/UI_items/SimpleImage.xaml.cs
new file mode 100644
index 00000000..7ef9830e
--- /dev/null
+++ b/gui/OpenFace-offline/UI_items/SimpleImage.xaml.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for OverlayImage.xaml
+ ///
+ public partial class SimpleImage : Image
+ {
+ public SimpleImage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+ if (Source == null || !(Source is WriteableBitmap))
+ return;
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/App.config b/gui/OpenFaceOffline/App.config
new file mode 100644
index 00000000..88fa4027
--- /dev/null
+++ b/gui/OpenFaceOffline/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/App.xaml b/gui/OpenFaceOffline/App.xaml
new file mode 100644
index 00000000..a7c9cabe
--- /dev/null
+++ b/gui/OpenFaceOffline/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/App.xaml.cs b/gui/OpenFaceOffline/App.xaml.cs
new file mode 100644
index 00000000..300701a5
--- /dev/null
+++ b/gui/OpenFaceOffline/App.xaml.cs
@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/gui/OpenFaceOffline/FpsTracker.cs b/gui/OpenFaceOffline/FpsTracker.cs
new file mode 100644
index 00000000..6bb59080
--- /dev/null
+++ b/gui/OpenFaceOffline/FpsTracker.cs
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+
+namespace OpenFaceOffline
+{
+ public class FpsTracker
+ {
+ public TimeSpan HistoryLength { get; set; }
+ public FpsTracker()
+ {
+ HistoryLength = TimeSpan.FromSeconds(2);
+ }
+
+ private Queue frameTimes = new Queue();
+
+ private void DiscardOldFrames()
+ {
+ while (frameTimes.Count > 0 && (MainWindow.CurrentTime - frameTimes.Peek()) > HistoryLength)
+ frameTimes.Dequeue();
+ }
+
+ public void AddFrame()
+ {
+ frameTimes.Enqueue(MainWindow.CurrentTime);
+ DiscardOldFrames();
+ }
+
+ public double GetFPS()
+ {
+ DiscardOldFrames();
+
+ if (frameTimes.Count == 0)
+ return 0;
+
+ return frameTimes.Count / (MainWindow.CurrentTime - frameTimes.Peek()).TotalSeconds;
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/MainWindow.xaml b/gui/OpenFaceOffline/MainWindow.xaml
new file mode 100644
index 00000000..90499553
--- /dev/null
+++ b/gui/OpenFaceOffline/MainWindow.xaml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Appearance features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Geometry features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action Units
+
+
+
+
+ Classification
+
+
+
+
+
+
+ Regression
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/MainWindow.xaml.cs b/gui/OpenFaceOffline/MainWindow.xaml.cs
new file mode 100644
index 00000000..295d740c
--- /dev/null
+++ b/gui/OpenFaceOffline/MainWindow.xaml.cs
@@ -0,0 +1,1194 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using System.Windows;
+using System.Windows.Threading;
+using System.Windows.Media.Imaging;
+using System.IO;
+using Microsoft.Win32;
+
+// Internal libraries
+using OpenCVWrappers;
+using CppInterop;
+using CppInterop.LandmarkDetector;
+using CameraInterop;
+using FaceAnalyser_Interop;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+
+ // Timing for measuring FPS
+ #region High-Resolution Timing
+ static DateTime startTime;
+ static Stopwatch sw = new Stopwatch();
+
+ static MainWindow()
+ {
+ startTime = DateTime.Now;
+ sw.Start();
+ }
+
+ public static DateTime CurrentTime
+ {
+ get { return startTime + sw.Elapsed; }
+ }
+ #endregion
+
+ // -----------------------------------------------------------------
+ // Members
+ // -----------------------------------------------------------------
+
+ Thread processing_thread;
+
+ // Some members for displaying the results
+ private Capture capture;
+ private WriteableBitmap latest_img;
+ private WriteableBitmap latest_aligned_face;
+ private WriteableBitmap latest_HOG_descriptor;
+
+ // Managing the running of the analysis system
+ private volatile bool thread_running;
+ private volatile bool thread_paused = false;
+ // Allows for going forward in time step by step
+ // Useful for visualising things
+ private volatile int skip_frames = 0;
+
+ FpsTracker processing_fps = new FpsTracker();
+
+ volatile bool detectionSucceeding = false;
+
+ volatile bool reset = false;
+
+ // For tracking
+ FaceModelParameters clnf_params;
+ CLNF clnf_model;
+ FaceAnalyserManaged face_analyser;
+
+ // Recording parameters (default values)
+ // TODO these should only be initialized when starting the recording, might not need to have them as members (also all should be on by default)
+ bool record_HOG = false; // HOG features extracted from face images
+ bool record_aligned = false; // aligned face images
+ bool record_tracked_vid = false;
+
+ // Visualisation options
+ bool show_tracked_video = true;
+ bool show_appearance = true;
+ bool show_geometry = true;
+ bool show_aus = true;
+
+ // TODO classifiers converted to regressors
+
+ // TODO indication that track is done
+
+ // The recording managers, TODO they should be all one
+ StreamWriter output_features_file;
+
+ // Where the recording is done (by default in a record directory, from where the application executed), TODO maybe the same folder as iput?
+ String record_root = "./record";
+
+ // For AU visualisation and output
+ List au_class_names;
+ List au_reg_names;
+
+ // For AU prediction
+ bool dynamic_AU_shift = true;
+ bool dynamic_AU_scale = false;
+ bool use_dynamic_models = true;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ // Set the icon
+ Uri iconUri = new Uri("logo1.ico", UriKind.RelativeOrAbsolute);
+ this.Icon = BitmapFrame.Create(iconUri);
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 2000), (Action)(() =>
+ {
+ RecordAlignedCheckBox.IsChecked = record_aligned;
+ RecordTrackedVidCheckBox.IsChecked = record_tracked_vid;
+ RecordHOGCheckBox.IsChecked = record_HOG;
+
+ UseDynamicModelsCheckBox.IsChecked = use_dynamic_models;
+ UseDynamicScalingCheckBox.IsChecked = dynamic_AU_scale;
+ UseDynamicShiftingCheckBox.IsChecked = dynamic_AU_shift;
+ }));
+
+ String root = AppDomain.CurrentDomain.BaseDirectory;
+
+ clnf_params = new FaceModelParameters(root);
+ clnf_model = new CLNF(clnf_params);
+ face_analyser = new FaceAnalyserManaged(root, use_dynamic_models);
+
+ }
+
+ // ----------------------------------------------------------
+ // Actual work gets done here
+
+ // The main function call for processing images or video files
+ private void ProcessingLoop(String[] filenames, int cam_id = -1, int width = -1, int height = -1, bool multi_face = false)
+ {
+
+ thread_running = true;
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ ResetButton.IsEnabled = true;
+ PauseButton.IsEnabled = true;
+ StopButton.IsEnabled = true;
+ }));
+
+ // Create the video capture and call the VideoLoop
+ if (filenames != null)
+ {
+ clnf_params.optimiseForVideo();
+ if (cam_id == -2)
+ {
+ List image_files_all = new List();
+ foreach (string image_name in filenames)
+ image_files_all.Add(image_name);
+
+ // Loading an image sequence that represents a video
+ capture = new Capture(image_files_all);
+
+ if (capture.isOpened())
+ {
+ // Prepare recording if any based on the directory
+ String file_no_ext = System.IO.Path.GetDirectoryName(filenames[0]);
+ file_no_ext = System.IO.Path.GetFileName(file_no_ext);
+ SetupRecording(record_root, file_no_ext, capture.width, capture.height);
+
+ // Start the actual processing
+ VideoLoop();
+
+ // Clear up the recording
+ StopRecording();
+
+ }
+ else
+ {
+ string messageBoxText = "Failed to open an image";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ else if (cam_id == -3)
+ {
+ SetupImageMode();
+ clnf_params.optimiseForImages();
+ // Loading an image file (or a number of them)
+ foreach (string filename in filenames)
+ {
+ if (!thread_running)
+ {
+ continue;
+ }
+
+ capture = new Capture(filename);
+
+ if (capture.isOpened())
+ {
+ // Start the actual processing
+ ProcessImage();
+
+ }
+ else
+ {
+ string messageBoxText = "File is not an image or the decoder is not supported.";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ }
+ else
+ {
+ clnf_params.optimiseForVideo();
+ // Loading a video file (or a number of them)
+ foreach (string filename in filenames)
+ {
+ if (!thread_running)
+ {
+ continue;
+ }
+
+ capture = new Capture(filename);
+
+ if (capture.isOpened())
+ {
+ // Prepare recording if any
+ String file_no_ext = System.IO.Path.GetFileNameWithoutExtension(filename);
+
+ SetupRecording(record_root, file_no_ext, capture.width, capture.height);
+
+ // Start the actual processing
+ VideoLoop();
+
+ // Clear up the recording
+ StopRecording();
+ }
+ else
+ {
+ string messageBoxText = "File is not a video or the codec is not supported.";
+ string caption = "Not valid file";
+ MessageBoxButton button = MessageBoxButton.OK;
+ MessageBoxImage icon = MessageBoxImage.Warning;
+
+ // Display message box
+ MessageBox.Show(messageBoxText, caption, button, icon);
+ }
+ }
+ }
+ }
+
+ // TODO this should be up a level
+ // Some GUI clean up
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ Console.WriteLine("Cleaning up after processing is done");
+ PauseButton.IsEnabled = false;
+ StopButton.IsEnabled = false;
+ ResetButton.IsEnabled = false;
+ NextFiveFramesButton.IsEnabled = false;
+ NextFrameButton.IsEnabled = false;
+ }));
+
+ }
+
+ // Capturing and processing the video frame by frame
+ private void ProcessImage()
+ {
+ Thread.CurrentThread.IsBackground = true;
+
+ clnf_model.Reset();
+ face_analyser.Reset();
+
+
+ //////////////////////////////////////////////
+ // CAPTURE FRAME AND DETECT LANDMARKS FOLLOWED BY THE REQUIRED IMAGE PROCESSING
+ //////////////////////////////////////////////
+ RawImage frame = null;
+ double progress = -1;
+
+ frame = new RawImage(capture.GetNextFrame(false));
+ progress = capture.GetProgress();
+
+ if (frame.Width == 0)
+ {
+ // This indicates that we reached the end of the video file
+ return;
+ }
+
+ var grayFrame = new RawImage(capture.GetCurrentFrameGray());
+
+ if (grayFrame == null)
+ {
+ Console.WriteLine("Gray is empty");
+ return;
+ }
+
+ List>> landmark_detections = ProcessImage(clnf_model, clnf_params, frame, grayFrame);
+
+ List landmark_points = new List();
+
+ for (int i = 0; i < landmark_detections.Count; ++i)
+ {
+
+ List> landmarks = landmark_detections[i];
+ foreach (var p in landmarks)
+ {
+ landmark_points.Add(new Point(p.Item1, p.Item2));
+ }
+ }
+
+ // Visualisation
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ if (show_tracked_video)
+ {
+ if (latest_img == null)
+ {
+ latest_img = frame.CreateWriteableBitmap();
+ }
+
+ frame.UpdateWriteableBitmap(latest_img);
+
+ video.Source = latest_img;
+ video.Confidence = 1;
+ video.FPS = processing_fps.GetFPS();
+ video.Progress = progress;
+
+ video.OverlayLines = new List>();
+
+ video.OverlayPoints = landmark_points;
+ }
+
+ }));
+
+ latest_img = null;
+ }
+
+
+ // Capturing and processing the video frame by frame
+ private void VideoLoop()
+ {
+ Thread.CurrentThread.IsBackground = true;
+
+ DateTime? startTime = CurrentTime;
+
+ var lastFrameTime = CurrentTime;
+
+ clnf_model.Reset();
+ face_analyser.Reset();
+
+ // TODO add an ability to change these through a calibration procedure or setting menu
+ double fx, fy, cx, cy;
+ fx = 500.0;
+ fy = 500.0;
+ cx = cy = -1;
+
+ int frame_id = 0;
+
+ double fps = capture.GetFPS();
+ if (fps <= 0) fps = 30;
+
+ // Check wich things need to be recorded
+ bool output_2D_landmarks = RecordLandmarks2DCheckBox.IsChecked;
+ bool output_3D_landmarks = RecordLandmarks3DCheckBox.IsChecked;
+ bool output_model_params = RecordParamsCheckBox.IsChecked;
+ bool output_pose = RecordPoseCheckBox.IsChecked;
+ bool output_AUs = RecordAUCheckBox.IsChecked;
+ bool output_gaze = RecordGazeCheckBox.IsChecked;
+
+ while (thread_running)
+ {
+ //////////////////////////////////////////////
+ // CAPTURE FRAME AND DETECT LANDMARKS FOLLOWED BY THE REQUIRED IMAGE PROCESSING
+ //////////////////////////////////////////////
+ RawImage frame = null;
+ double progress = -1;
+
+ frame = new RawImage(capture.GetNextFrame(false));
+ progress = capture.GetProgress();
+
+ if (frame.Width == 0)
+ {
+ // This indicates that we reached the end of the video file
+ break;
+ }
+
+ // TODO stop button should actually clear the video
+ lastFrameTime = CurrentTime;
+ processing_fps.AddFrame();
+
+ var grayFrame = new RawImage(capture.GetCurrentFrameGray());
+
+ if (grayFrame == null)
+ {
+ Console.WriteLine("Gray is empty");
+ continue;
+ }
+
+ // This is more ore less guess work, but seems to work well enough
+ if (cx == -1)
+ {
+ fx = fx * (grayFrame.Width / 640.0);
+ fy = fy * (grayFrame.Height / 480.0);
+
+ fx = (fx + fy) / 2.0;
+ fy = fx;
+
+ cx = grayFrame.Width / 2f;
+ cy = grayFrame.Height / 2f;
+ }
+
+ bool detectionSucceeding = ProcessFrame(clnf_model, clnf_params, frame, grayFrame, fx, fy, cx, cy);
+
+ double confidence = (-clnf_model.GetConfidence()) / 2.0 + 0.5;
+
+ if (confidence < 0)
+ confidence = 0;
+ else if (confidence > 1)
+ confidence = 1;
+
+ List pose = new List();
+ clnf_model.GetCorrectedPoseWorld(pose, fx, fy, cx, cy);
+ List non_rigid_params = clnf_model.GetNonRigidParams();
+
+ // The face analysis step (only done if recording AUs, HOGs or video)
+ if (output_AUs || record_HOG || record_aligned || show_aus || show_appearance || record_tracked_vid || output_gaze)
+ {
+ face_analyser.AddNextFrame(frame, clnf_model, fx, fy, cx, cy, false, show_appearance, record_tracked_vid);
+ }
+
+ List> lines = null;
+ List> landmarks = null;
+ List> gaze_lines = null;
+
+ if (detectionSucceeding)
+ {
+ landmarks = clnf_model.CalculateLandmarks();
+ lines = clnf_model.CalculateBox((float)fx, (float)fy, (float)cx, (float)cy);
+ //gaze_lines = face_analyser.CalculateGazeLines((float)fx, (float)fy, (float)cx, (float)cy); // TODO figure out what is happening here
+ gaze_lines = new List>();
+ }
+
+ // Visualisation
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ if (show_aus)
+ {
+ var au_classes = face_analyser.GetCurrentAUsClass();
+ var au_regs = face_analyser.GetCurrentAUsReg();
+
+ auClassGraph.Update(au_classes);
+
+ var au_regs_scaled = new Dictionary();
+ foreach (var au_reg in au_regs)
+ {
+ au_regs_scaled[au_reg.Key] = au_reg.Value / 5.0;
+ if (au_regs_scaled[au_reg.Key] < 0)
+ au_regs_scaled[au_reg.Key] = 0;
+
+ if (au_regs_scaled[au_reg.Key] > 1)
+ au_regs_scaled[au_reg.Key] = 1;
+ }
+ auRegGraph.Update(au_regs_scaled);
+ }
+
+ if (show_geometry)
+ {
+ int yaw = (int)(pose[4] * 180 / Math.PI + 0.5);
+ int roll = (int)(pose[5] * 180 / Math.PI + 0.5);
+ int pitch = (int)(pose[3] * 180 / Math.PI + 0.5);
+
+ YawLabel.Content = yaw + "°";
+ RollLabel.Content = roll + "°";
+ PitchLabel.Content = pitch + "°";
+
+ XPoseLabel.Content = (int)pose[0] + " mm";
+ YPoseLabel.Content = (int)pose[1] + " mm";
+ ZPoseLabel.Content = (int)pose[2] + " mm";
+
+ nonRigidGraph.Update(non_rigid_params);
+
+ // Update eye gaze
+ var gaze_both = face_analyser.GetGazeCamera();
+ double x = (gaze_both.Item1.Item1 + gaze_both.Item2.Item1) / 2.0;
+ double y = (gaze_both.Item1.Item2 + gaze_both.Item2.Item2) / 2.0;
+
+ // Tweak it to a more presentable value
+ x = (int)(x * 35);
+ y = (int)(y * 70);
+
+ if (x < -10)
+ x = -10;
+ if (x > 10)
+ x = 10;
+ if (y < -10)
+ y = -10;
+ if (y > 10)
+ y = 10;
+
+ GazeXLabel.Content = x / 10.0;
+ GazeYLabel.Content = y / 10.0;
+
+
+ }
+
+ if (show_tracked_video)
+ {
+ if (latest_img == null)
+ {
+ latest_img = frame.CreateWriteableBitmap();
+ }
+
+ frame.UpdateWriteableBitmap(latest_img);
+
+ video.Source = latest_img;
+ video.Confidence = confidence;
+ video.FPS = processing_fps.GetFPS();
+ video.Progress = progress;
+
+ if (!detectionSucceeding)
+ {
+ video.OverlayLines.Clear();
+ video.OverlayPoints.Clear();
+ video.GazeLines.Clear();
+ }
+ else
+ {
+ video.OverlayLines = lines;
+
+ List landmark_points = new List();
+ foreach (var p in landmarks)
+ {
+ landmark_points.Add(new Point(p.Item1, p.Item2));
+ }
+
+ video.OverlayPoints = landmark_points;
+
+ video.GazeLines = gaze_lines;
+ }
+ }
+
+ if (show_appearance)
+ {
+ RawImage aligned_face = face_analyser.GetLatestAlignedFace();
+ RawImage hog_face = face_analyser.GetLatestHOGDescriptorVisualisation();
+
+ if (latest_aligned_face == null)
+ {
+ latest_aligned_face = aligned_face.CreateWriteableBitmap();
+ latest_HOG_descriptor = hog_face.CreateWriteableBitmap();
+ }
+
+ aligned_face.UpdateWriteableBitmap(latest_aligned_face);
+ hog_face.UpdateWriteableBitmap(latest_HOG_descriptor);
+
+ AlignedFace.Source = latest_aligned_face;
+ AlignedHOG.Source = latest_HOG_descriptor;
+ }
+ }));
+
+ // Recording the tracked model
+ RecordFrame(clnf_model, detectionSucceeding, frame_id, frame, grayFrame, (fps * (double)frame_id)/1000.0,
+ output_2D_landmarks, output_2D_landmarks, output_model_params, output_pose, output_AUs, output_gaze, fx, fy, cx, cy);
+
+ if (reset)
+ {
+ clnf_model.Reset();
+ face_analyser.Reset();
+ reset = false;
+ }
+
+ while (thread_running & thread_paused && skip_frames == 0)
+ {
+ Thread.Sleep(10);
+ }
+
+ frame_id++;
+
+ if (skip_frames > 0)
+ skip_frames--;
+
+ }
+
+ latest_img = null;
+ skip_frames = 0;
+
+ // Unpause if it's paused
+ if (thread_paused)
+ {
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ PauseButton_Click(null, null);
+ }));
+ }
+ }
+
+ private void StopTracking()
+ {
+ // First complete the running of the thread
+ if (processing_thread != null)
+ {
+ // Tell the other thread to finish
+ thread_running = false;
+ processing_thread.Join();
+ }
+ }
+
+
+
+ // ----------------------------------------------------------
+ // Interacting with landmark detection and face analysis
+
+ private bool ProcessFrame(CLNF clnf_model, FaceModelParameters clnf_params, RawImage frame, RawImage grayscale_frame, double fx, double fy, double cx, double cy)
+ {
+ detectionSucceeding = clnf_model.DetectLandmarksInVideo(grayscale_frame, clnf_params);
+ return detectionSucceeding;
+
+ }
+
+ private List>> ProcessImage(CLNF clnf_model, FaceModelParameters clnf_params, RawImage frame, RawImage grayscale_frame)
+ {
+ List>> landmark_detections = clnf_model.DetectMultiFaceLandmarksInImage(grayscale_frame, clnf_params);
+ return landmark_detections;
+
+ }
+
+
+ // ----------------------------------------------------------
+ // Recording helpers (TODO simplify)
+
+ private void SetupRecording(String root, String filename, int width, int height, bool output_2D_landmarks, bool output_3D_landmarks,
+ bool output_model_params, bool output_pose, bool output_AUs, bool output_gaze)
+ {
+ // Disallow changing recording settings when the recording starts, TODO move this up a bit
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ RecordingMenu.IsEnabled = false;
+ UseDynamicModelsCheckBox.IsEnabled = false;
+ }));
+
+ if (!System.IO.Directory.Exists(root))
+ {
+ System.IO.Directory.CreateDirectory(root);
+ }
+
+ output_features_file = new StreamWriter(root + "/" + filename + ".txt");
+ output_features_file.Write("frame, timestamp, confidence, success");
+
+ if (output_gaze)
+ output_features_file.Write(", gaze_0_x, gaze_0_y, gaze_0_z, gaze_1_x, gaze_1_y, gaze_2_z");
+
+ if (output_pose)
+ output_features_file.Write(", pose_Tx, pose_Ty, pose_Tz, pose_Rx, pose_Ry, pose_Rz");
+
+ if (output_2D_landmarks)
+ {
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_features_file.Write(", x_" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_features_file.Write(", y_" + i);
+ }
+ }
+
+ if (output_3D_landmarks)
+ {
+
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_features_file.Write(", X_" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_features_file.Write(", Y_" + i);
+ }
+ for (int i = 0; i < clnf_model.GetNumPoints(); ++i)
+ {
+ output_features_file.Write(", Z_" + i);
+ }
+ }
+
+ if (output_model_params)
+ {
+ output_features_file.Write(", p_scale, p_rx, p_ry, p_rz, p_tx, p_ty");
+ for (int i = 0; i < clnf_model.GetNumModes(); ++i)
+ {
+ output_features_file.Write(", p_" + i);
+ }
+ }
+
+ if (output_AUs)
+ {
+
+ au_reg_names = face_analyser.GetRegActionUnitsNames();
+ au_reg_names.Sort();
+ foreach (var name in au_reg_names)
+ {
+ output_features_file.Write(", " + name + "_r");
+ }
+
+ au_class_names = face_analyser.GetClassActionUnitsNames();
+ au_class_names.Sort();
+ foreach (var name in au_class_names)
+ {
+ output_features_file.Write(", " + name + "_c");
+ }
+
+ }
+
+ output_features_file.WriteLine();
+
+
+ if (record_aligned)
+ {
+ String aligned_root = root + "/" + filename + "_aligned/";
+ System.IO.Directory.CreateDirectory(aligned_root);
+ face_analyser.SetupAlignedImageRecording(aligned_root);
+ }
+
+ if (record_tracked_vid)
+ {
+ String vid_loc = root + "/" + filename + ".avi";
+ System.IO.Directory.CreateDirectory(root);
+ face_analyser.SetupTrackingRecording(vid_loc, width, height, 30);
+ }
+
+ if (record_HOG)
+ {
+ String filename_HOG = root + "/" + filename + ".hog";
+ face_analyser.SetupHOGRecording(filename_HOG);
+ }
+
+ }
+
+ private void StopRecording()
+ {
+ if (output_features_file != null)
+ output_features_file.Close();
+
+ if (record_HOG)
+ face_analyser.StopHOGRecording();
+
+ if (record_tracked_vid)
+ face_analyser.StopTrackingRecording();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 200), (Action)(() =>
+ {
+ RecordingMenu.IsEnabled = true;
+ UseDynamicModelsCheckBox.IsEnabled = true;
+
+ }));
+
+ }
+
+ // Recording the relevant objects
+ private void RecordFrame(CLNF clnf_model, bool success, int frame_ind, RawImage frame, RawImage grayscale_frame, double time_stamp, bool output_2D_landmarks, bool output_3D_landmarks,
+ bool output_model_params, bool output_pose, bool output_AUs, bool output_gaze, double fx, double fy, double cx, double cy)
+ {
+ double confidence = (-clnf_model.GetConfidence()) / 2.0 + 0.5;
+
+ List pose = new List();
+ clnf_model.GetCorrectedPoseWorld(pose, fx, fy, cx, cy);
+
+ output_features_file.Write(String.Format("{0}, {1}, {2:F3}, {3}", frame_ind, time_stamp, confidence, success ? 1 : 0));
+
+ if (output_gaze)
+ {
+ var gaze = face_analyser.GetGazeCamera();
+
+ output_features_file.Write(String.Format(", {0:F3}, {1:F3}, {2:F3}, {3:F3}, {4:F3}, {5:F3}", gaze.Item1.Item1, gaze.Item1.Item2, gaze.Item1.Item3,
+ gaze.Item2.Item1, gaze.Item2.Item2, gaze.Item2.Item3));
+ }
+
+ if (output_pose)
+ output_features_file.WriteLine(String.Format("{1:F3},{2:F3},{3:F3},{4:F3},{5:F3},{6:F3}", pose[0], pose[1], pose[2], pose[3], pose[4], pose[5]));
+
+ if (output_2D_landmarks)
+ {
+ List> landmarks_2d = clnf_model.CalculateLandmarks();
+
+ for (int i = 0; i < landmarks_2d.Count; ++i)
+ output_features_file.Write(", {0:F2}", landmarks_2d[i].Item1);
+
+ for (int i = 0; i < landmarks_2d.Count; ++i)
+ output_features_file.Write(", {0:F2}", landmarks_2d[i].Item2);
+ }
+
+ if (output_3D_landmarks)
+ {
+ List landmarks_3d = clnf_model.Calculate3DLandmarks(fx, fy, cx, cy);
+
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ output_features_file.Write(", {0:F2}", landmarks_3d[i].X);
+
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ output_features_file.Write(", {0:F2}", landmarks_3d[i].Y);
+
+ for (int i = 0; i < landmarks_3d.Count; ++i)
+ output_features_file.Write(", {0:F2}", landmarks_3d[i].Z);
+ }
+
+ if (output_model_params)
+ {
+ List all_params = clnf_model.GetParams();
+
+ for (int i = 0; i < all_params.Count; ++i)
+ output_features_file.Write(String.Format(", {0,0:F5}", all_params[i]));
+ }
+
+ if (output_AUs)
+ {
+ var au_regs = face_analyser.GetCurrentAUsReg();
+
+ foreach (var name_reg in au_reg_names)
+ output_features_file.Write(", {0:F2}", au_regs[name_reg]);
+
+ var au_classes = face_analyser.GetCurrentAUsClass();
+
+ foreach (var name_class in au_class_names)
+ output_features_file.Write(", {0:F0}", au_classes[name_class]);
+
+ }
+
+ output_features_file.WriteLine();
+
+ if (record_aligned)
+ {
+ face_analyser.RecordAlignedFrame(frame_ind);
+ }
+
+ if (record_HOG)
+ {
+ face_analyser.RecordHOGFrame();
+ }
+
+ if (record_tracked_vid)
+ {
+ face_analyser.RecordTrackedFace();
+ }
+ }
+
+
+ // ----------------------------------------------------------
+ // Mode handling (image, video)
+ // ----------------------------------------------------------
+ private void SetupImageMode()
+ {
+ // Turn off recording
+ record_aligned = false;
+ record_HOG = false;
+ record_tracked_vid = false;
+
+ // Turn off unneeded visualisations
+ show_tracked_video = true;
+ show_appearance = false;
+ show_geometry = false;
+ show_aus = false;
+
+ // Actually update the GUI accordingly
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 2000), (Action)(() =>
+ {
+ RecordAlignedCheckBox.IsChecked = record_aligned;
+ RecordTrackedVidCheckBox.IsChecked = record_tracked_vid;
+ RecordHOGCheckBox.IsChecked = record_HOG;
+
+ ShowVideoCheckBox.IsChecked = true;
+ ShowAppearanceFeaturesCheckBox.IsChecked = false;
+ ShowGeometryFeaturesCheckBox.IsChecked = false;
+ ShowAUsCheckBox.IsChecked = false;
+
+ VisualisationCheckBox_Click(null, null);
+ }));
+
+ // TODO change what next and back buttons do?
+ }
+
+
+ // ----------------------------------------------------------
+ // Opening Videos/Images
+ // ----------------------------------------------------------
+
+ private void videoFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => openVideoFile()).Start();
+ }
+
+ private void openVideoFile()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Video files|*.avi;*.wmv;*.mov;*.mpg;*.mpeg;*.mp4";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] video_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(video_files));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+
+ private void imageFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => imageOpen()).Start();
+ }
+
+ private void imageOpen()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Image files|*.jpg;*.jpeg;*.bmp;*.png;*.gif";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] image_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(image_files, -3));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+ private void imageSequenceFileOpenClick(object sender, RoutedEventArgs e)
+ {
+ new Thread(() => imageSequenceOpen()).Start();
+ }
+
+ private void imageSequenceOpen()
+ {
+ StopTracking();
+
+ Dispatcher.Invoke(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 2, 0), (Action)(() =>
+ {
+ var d = new OpenFileDialog();
+ d.Multiselect = true;
+ d.Filter = "Image files|*.jpg;*.jpeg;*.bmp;*.png;*.gif";
+
+ if (d.ShowDialog(this) == true)
+ {
+
+ string[] image_files = d.FileNames;
+
+ processing_thread = new Thread(() => ProcessingLoop(image_files, -2));
+ processing_thread.Start();
+
+ }
+ }));
+ }
+
+ // --------------------------------------------------------
+ // Button handling
+ // --------------------------------------------------------
+
+ // Cleanup stuff when closing the window
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_running = false;
+ processing_thread.Join();
+
+ capture.Dispose();
+ }
+ face_analyser.Dispose();
+ }
+
+ // Stopping the tracking
+ private void StopButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_paused = false;
+ thread_running = false;
+ processing_thread.Join();
+
+ PauseButton.IsEnabled = false;
+ NextFrameButton.IsEnabled = false;
+ NextFiveFramesButton.IsEnabled = false;
+ StopButton.IsEnabled = false;
+ ResetButton.IsEnabled = false;
+ RecordingMenu.IsEnabled = true;
+
+ UseDynamicModelsCheckBox.IsEnabled = true;
+ }
+ }
+
+ // Resetting the tracker
+ private void ResetButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ reset = true;
+ }
+ }
+
+ private void PauseButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (processing_thread != null)
+ {
+ // Stop capture and tracking
+ thread_paused = !thread_paused;
+
+ ResetButton.IsEnabled = !thread_paused;
+
+ NextFrameButton.IsEnabled = thread_paused;
+ NextFiveFramesButton.IsEnabled = thread_paused;
+
+ if (thread_paused)
+ {
+ PauseButton.Content = "Resume";
+ }
+ else
+ {
+ PauseButton.Content = "Pause";
+ }
+ }
+ }
+
+ private void SkipButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender.Equals(NextFrameButton))
+ {
+ skip_frames += 1;
+ }
+ else if (sender.Equals(NextFiveFramesButton))
+ {
+ skip_frames += 5;
+ }
+ }
+
+
+ private void VisualisationCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ show_tracked_video = ShowVideoCheckBox.IsChecked;
+ show_appearance = ShowAppearanceFeaturesCheckBox.IsChecked;
+ show_geometry = ShowGeometryFeaturesCheckBox.IsChecked;
+ show_aus = ShowAUsCheckBox.IsChecked;
+
+ // Collapsing or restoring the windows here
+ if (!show_tracked_video)
+ {
+ VideoBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ VideoBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[0].Width = new GridLength(2.1, GridUnitType.Star);
+ }
+
+ if (!show_appearance)
+ {
+ AppearanceBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ AppearanceBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[1].Width = new GridLength(0.8, GridUnitType.Star);
+ }
+
+ // Collapsing or restoring the windows here
+ if (!show_geometry)
+ {
+ GeometryBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ GeometryBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[2].Width = new GridLength(1.0, GridUnitType.Star);
+ }
+
+ // Collapsing or restoring the windows here
+ if (!show_aus)
+ {
+ ActionUnitBorder.Visibility = System.Windows.Visibility.Collapsed;
+ MainGrid.ColumnDefinitions[3].Width = new GridLength(0, GridUnitType.Star);
+ }
+ else
+ {
+ ActionUnitBorder.Visibility = System.Windows.Visibility.Visible;
+ MainGrid.ColumnDefinitions[3].Width = new GridLength(1.6, GridUnitType.Star);
+ }
+
+ }
+
+
+ private void recordCheckBox_click(object sender, RoutedEventArgs e)
+ {
+ record_aligned = RecordAlignedCheckBox.IsChecked;
+ record_HOG = RecordHOGCheckBox.IsChecked;
+ record_tracked_vid = RecordTrackedVidCheckBox.IsChecked;
+ }
+
+ private void UseDynamicModelsCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ dynamic_AU_shift = UseDynamicShiftingCheckBox.IsChecked;
+ dynamic_AU_scale = UseDynamicScalingCheckBox.IsChecked;
+
+ if (use_dynamic_models != UseDynamicModelsCheckBox.IsChecked)
+ {
+ // Change the face analyser, this should be safe as the model is only allowed to change when not running
+ String root = AppDomain.CurrentDomain.BaseDirectory;
+ face_analyser = new FaceAnalyserManaged(root, UseDynamicModelsCheckBox.IsChecked);
+ }
+ use_dynamic_models = UseDynamicModelsCheckBox.IsChecked;
+ }
+
+ }
+}
diff --git a/gui/OpenFaceOffline/OpenFaceOffline.csproj b/gui/OpenFaceOffline/OpenFaceOffline.csproj
new file mode 100644
index 00000000..c9e4d7c3
--- /dev/null
+++ b/gui/OpenFaceOffline/OpenFaceOffline.csproj
@@ -0,0 +1,184 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {A4760F41-2B1F-4144-B7B2-62785AFFE79B}
+ WinExe
+ Properties
+ OpenFaceOffline
+ OpenFaceOffline
+ v4.5.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\x64\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x64
+ pdbonly
+ true
+ ..\..\x64\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ ..\..\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ ..\..\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ logo1.ico
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+ BarGraph.xaml
+
+
+ BarGraphHorizontal.xaml
+
+
+ MultiBarGraph.xaml
+
+
+ MultiBarGraphHorz.xaml
+
+
+ OverlayImage.xaml
+
+
+ SimpleImage.xaml
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+ {78196985-ee54-411f-822b-5a23edf80642}
+ CppInerop
+
+
+
+
+
+
+
+ xcopy /I /E /Y /D "$(ProjectDir)logo1.ico" "$(SolutionDir)/$(Configuration)"
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/Properties/AssemblyInfo.cs b/gui/OpenFaceOffline/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..74fe00e8
--- /dev/null
+++ b/gui/OpenFaceOffline/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("OpenFaceOffline")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("OpenFaceOffline")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/gui/OpenFaceOffline/Properties/Resources.Designer.cs b/gui/OpenFaceOffline/Properties/Resources.Designer.cs
new file mode 100644
index 00000000..644558ec
--- /dev/null
+++ b/gui/OpenFaceOffline/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace OpenFaceOffline.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenFaceOffline.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/Properties/Resources.resx b/gui/OpenFaceOffline/Properties/Resources.resx
new file mode 100644
index 00000000..af7dbebb
--- /dev/null
+++ b/gui/OpenFaceOffline/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/Properties/Settings.Designer.cs b/gui/OpenFaceOffline/Properties/Settings.Designer.cs
new file mode 100644
index 00000000..6ab083e5
--- /dev/null
+++ b/gui/OpenFaceOffline/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace OpenFaceOffline.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/Properties/Settings.settings b/gui/OpenFaceOffline/Properties/Settings.settings
new file mode 100644
index 00000000..033d7a5e
--- /dev/null
+++ b/gui/OpenFaceOffline/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/UI_items/BarGraph.xaml b/gui/OpenFaceOffline/UI_items/BarGraph.xaml
new file mode 100644
index 00000000..2101d740
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/BarGraph.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/UI_items/BarGraph.xaml.cs b/gui/OpenFaceOffline/UI_items/BarGraph.xaml.cs
new file mode 100644
index 00000000..35303f96
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/BarGraph.xaml.cs
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System.Windows.Controls;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for BarGraph.xaml
+ ///
+ public partial class BarGraph : UserControl
+ {
+ private double targetValue = 0;
+
+ public BarGraph()
+ {
+ InitializeComponent();
+ }
+
+ public void SetValue(double value)
+ {
+ targetValue = 1.5 * value;
+ if (targetValue > 0)
+ {
+ if (targetValue > barContainerPos.ActualHeight)
+ targetValue = barContainerPos.ActualHeight;
+
+ barPos.Height = targetValue;
+ barNeg.Height = 0;
+ }
+ if (targetValue < 0)
+ {
+ if (-targetValue > barContainerNeg.ActualHeight)
+ targetValue = -barContainerNeg.ActualHeight;
+
+ barPos.Height = 0;
+ barNeg.Height = -targetValue;
+ }
+ }
+
+ }
+}
diff --git a/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml b/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml
new file mode 100644
index 00000000..f5ccbe5e
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml.cs b/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml.cs
new file mode 100644
index 00000000..44a7d1a2
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/BarGraphHorizontal.xaml.cs
@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for BarGraphHorizontal.xaml
+ ///
+ public partial class BarGraphHorizontal : UserControl
+ {
+ double targetValue = 0;
+
+ public BarGraphHorizontal(String label)
+ {
+ InitializeComponent();
+ Label.Content = label;
+ }
+
+ public void SetValue(double value)
+ {
+ targetValue = value;
+ barPos.Width = targetValue * barContainerPos.ActualWidth;
+ }
+
+ public double GetTarget()
+ {
+ return targetValue;
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml b/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml
new file mode 100644
index 00000000..836c9742
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml.cs b/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml.cs
new file mode 100644
index 00000000..7f329096
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/MultiBarGraph.xaml.cs
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MultiBarGraph.xaml
+ ///
+ public partial class MultiBarGraph : UserControl
+ {
+
+ int num_bars = 0;
+ List graphs;
+
+ public MultiBarGraph()
+ {
+ InitializeComponent();
+
+ graphs = new List();
+ }
+
+ public void Update(List data)
+ {
+ // Create new bars if necessary
+ if (num_bars != data.Count)
+ {
+ num_bars = data.Count;
+ barGrid.Children.Clear();
+ foreach (var value in data)
+ {
+ BarGraph newBar = new BarGraph();
+ graphs.Add(newBar);
+ barGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ Grid.SetColumn(newBar, graphs.Count);
+ barGrid.Children.Add(newBar);
+
+ }
+ }
+
+ // Update the bars
+ for (int i = 0; i < data.Count; ++i)
+ {
+ graphs[i].SetValue(data[i]);
+ }
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml b/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml
new file mode 100644
index 00000000..db5728f6
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml.cs b/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml.cs
new file mode 100644
index 00000000..f9af7c78
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/MultiBarGraphHorz.xaml.cs
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for MultiBarGraphHorz.xaml
+ ///
+ public partial class MultiBarGraphHorz : UserControl
+ {
+ int num_bars = 0;
+ Dictionary graphs;
+
+ // Name mapping
+ Dictionary mapping;
+
+ public MultiBarGraphHorz()
+ {
+ InitializeComponent();
+
+ graphs = new Dictionary();
+
+ mapping = new Dictionary();
+ mapping["AU01"] = "Inner Brow raiser";
+ mapping["AU02"] = "Outer Brow raiser";
+ mapping["AU04"] = "Brow lowerer";
+ mapping["AU05"] = "Upper lid raiser";
+ mapping["AU06"] = "Cheek raiser";
+ mapping["AU07"] = "Lid tightener";
+ mapping["AU09"] = "Nose wrinkler";
+ mapping["AU10"] = "Upper lip raiser";
+ mapping["AU12"] = "Lip corner puller (smile)";
+ mapping["AU14"] = "Dimpler";
+ mapping["AU15"] = "Lip corner depressor";
+ mapping["AU17"] = "Chin Raiser";
+ mapping["AU20"] = "Lip Stretcher";
+ mapping["AU23"] = "Lip tightener";
+ mapping["AU25"] = "Lips part";
+ mapping["AU26"] = "Jaw drop";
+ mapping["AU28"] = "Lip suck";
+ mapping["AU45"] = "Blink";
+
+
+
+ }
+
+ public void Update(Dictionary data)
+ {
+ // Create new bars if necessary
+ if (num_bars != data.Count)
+ {
+ num_bars = data.Count;
+ barGrid.Children.Clear();
+
+ // Make sure AUs are sorted
+ var data_labels = data.Keys.ToList();
+ data_labels.Sort();
+
+ foreach (var label in data_labels)
+ {
+ BarGraphHorizontal newBar = new BarGraphHorizontal(label + " - " + mapping[label]);
+ barGrid.RowDefinitions.Add(new RowDefinition());
+ Grid.SetRow(newBar, graphs.Count);
+ graphs.Add(label, newBar);
+ barGrid.Children.Add(newBar);
+ }
+ }
+
+ // Update the bars
+ foreach (var value in data)
+ {
+ double old_value = graphs[value.Key].GetTarget();
+ // some smoothing as well
+ graphs[value.Key].SetValue(old_value * 0.15 + 0.85 * value.Value);
+ }
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/UI_items/OverlayImage.xaml b/gui/OpenFaceOffline/UI_items/OverlayImage.xaml
new file mode 100644
index 00000000..8dcdaea5
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/OverlayImage.xaml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/UI_items/OverlayImage.xaml.cs b/gui/OpenFaceOffline/UI_items/OverlayImage.xaml.cs
new file mode 100644
index 00000000..b7bce904
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/OverlayImage.xaml.cs
@@ -0,0 +1,168 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for OverlayImage.xaml
+ ///
+ public partial class OverlayImage : Image
+ {
+ public OverlayImage()
+ {
+ InitializeComponent();
+ OverlayLines = new List>();
+ OverlayPoints = new List();
+ GazeLines = new List>();
+
+ Progress = -1;
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+ if (OverlayLines == null)
+ OverlayLines = new List>();
+
+ if (OverlayPoints == null)
+ OverlayPoints = new List();
+
+ if (Source == null || !(Source is WriteableBitmap))
+ return;
+
+ var width = ((WriteableBitmap)Source).PixelWidth;
+ var height = ((WriteableBitmap)Source).PixelHeight;
+
+ foreach (var line in OverlayLines)
+ {
+
+ var p1 = new Point(ActualWidth * line.Item1.X / width, ActualHeight * line.Item1.Y / height);
+ var p2 = new Point(ActualWidth * line.Item2.X / width, ActualHeight * line.Item2.Y / height);
+
+ dc.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, (byte)(100 + (155 * (1 - Confidence))), (byte)(100 + (155 * Confidence)), 100)), 2), p1, p2);
+ }
+
+ foreach (var line in GazeLines)
+ {
+
+ var p1 = new Point(ActualWidth * line.Item1.X / width, ActualHeight * line.Item1.Y / height);
+ var p2 = new Point(ActualWidth * line.Item2.X / width, ActualHeight * line.Item2.Y / height);
+
+ dc.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, (byte)(240), (byte)(30), (byte)100)), 3), p1, p2);
+
+ }
+
+ foreach (var p in OverlayPoints)
+ {
+
+ var q = new Point(ActualWidth * p.X / width, ActualHeight * p.Y / height);
+
+ dc.DrawEllipse(new SolidColorBrush(Color.FromArgb((byte)(200 * Confidence), 255, 255, 100)), null, q, 2, 2);
+ }
+
+ double scaling = ActualWidth / 400.0;
+
+ int confidence_width = (int)(107.0 * scaling);
+ int confidence_height = (int)(18.0 * scaling);
+
+ Brush conf_brush = new SolidColorBrush(Color.FromRgb((byte)((1 - Confidence) * 255), (byte)(Confidence * 255), (byte)40));
+ dc.DrawRoundedRectangle(conf_brush, new Pen(Brushes.Black, 0.5 * scaling), new Rect(ActualWidth - confidence_width - 1, 0, confidence_width, confidence_height), 3.0 * scaling, 3.0 * scaling);
+
+ FormattedText txt = new FormattedText("Confidence: " + (int)(100 * Confidence) + "%", System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 12.0 * scaling, Brushes.Black);
+ dc.DrawText(txt, new Point(ActualWidth - confidence_width + 2, 2));
+
+ int fps_width = (int)(52.0 * scaling);
+ int fps_height = (int)(18.0 * scaling);
+
+ dc.DrawRoundedRectangle(Brushes.WhiteSmoke, new Pen(Brushes.Black, 0.5 * scaling), new Rect(0, 0, fps_width, fps_height), 3.0 * scaling, 3.0 * scaling);
+ FormattedText fps_txt = new FormattedText("FPS: " + (int)FPS, System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 12.0 * scaling, Brushes.Black);
+ dc.DrawText(fps_txt, new Point(2.0 * scaling, 0));
+
+ old_width = width;
+ old_height = height;
+
+ // Drawing a progress bar at the bottom of the image
+ if (Progress > 0)
+ {
+ int progress_bar_height = (int)(6.0 * scaling);
+ dc.DrawRectangle(Brushes.GreenYellow, new Pen(Brushes.Black, 0.5 * scaling), new Rect(0, ActualHeight - progress_bar_height, Progress * ActualWidth, progress_bar_height));
+ }
+
+ }
+
+ public List> OverlayLines { get; set; }
+ public List> GazeLines { get; set; }
+ public List OverlayPoints { get; set; }
+ public double Confidence { get; set; }
+ public double FPS { get; set; }
+
+ // 0 to 1 indicates how much video has been processed so far
+ public double Progress { get; set; }
+
+ int old_width;
+ int old_height;
+ }
+}
diff --git a/gui/OpenFaceOffline/UI_items/SimpleImage.xaml b/gui/OpenFaceOffline/UI_items/SimpleImage.xaml
new file mode 100644
index 00000000..9cf612d7
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/SimpleImage.xaml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/gui/OpenFaceOffline/UI_items/SimpleImage.xaml.cs b/gui/OpenFaceOffline/UI_items/SimpleImage.xaml.cs
new file mode 100644
index 00000000..19cb0ebb
--- /dev/null
+++ b/gui/OpenFaceOffline/UI_items/SimpleImage.xaml.cs
@@ -0,0 +1,94 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016, Carnegie Mellon University and University of Cambridge,
+// all rights reserved.
+//
+// THIS SOFTWARE IS PROVIDED “AS IS” FOR ACADEMIC USE ONLY AND ANY EXPRESS
+// OR IMPLIED WARRANTIES WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY.
+// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Notwithstanding the license granted herein, Licensee acknowledges that certain components
+// of the Software may be covered by so-called “open source” software licenses (“Open Source
+// Components”), which means any software licenses approved as open source licenses by the
+// Open Source Initiative or any substantially similar licenses, including without limitation any
+// license that, as a condition of distribution of the software licensed under such license,
+// requires that the distributor make the software available in source code format. Licensor shall
+// provide a list of Open Source Components for a particular version of the Software upon
+// Licensee’s request. Licensee will comply with the applicable terms of such licenses and to
+// the extent required by the licenses covering Open Source Components, the terms of such
+// licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the
+// licenses applicable to Open Source Components prohibit any of the restrictions in this
+// License Agreement with respect to such Open Source Component, such restrictions will not
+// apply to such Open Source Component. To the extent the terms of the licenses applicable to
+// Open Source Components require Licensor to make an offer to provide source code or
+// related information in connection with the Software, such offer is hereby made. Any request
+// for source code or related information should be directed to cl-face-tracker-distribution@lists.cam.ac.uk
+// Licensee acknowledges receipt of notices for the Open Source Components for the initial
+// delivery of the Software.
+
+// * Any publications arising from the use of this software, including but
+// not limited to academic journal and conference publications, technical
+// reports and manuals, must cite at least one of the following works:
+//
+// OpenFace: an open source facial behavior analysis toolkit
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
+// in IEEE Winter Conference on Applications of Computer Vision, 2016
+//
+// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
+// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
+// in IEEE International. Conference on Computer Vision (ICCV), 2015
+//
+// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
+// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
+// in Facial Expression Recognition and Analysis Challenge,
+// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
+//
+// Constrained Local Neural Fields for robust facial landmark detection in the wild.
+// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
+// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+
+namespace OpenFaceOffline
+{
+ ///
+ /// Interaction logic for OverlayImage.xaml
+ ///
+ public partial class SimpleImage : Image
+ {
+ public SimpleImage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnRender(DrawingContext dc)
+ {
+ base.OnRender(dc);
+
+ if (Source == null || !(Source is WriteableBitmap))
+ return;
+ }
+ }
+}
diff --git a/gui/OpenFaceOffline/logo1.ico b/gui/OpenFaceOffline/logo1.ico
new file mode 100644
index 00000000..60a49af1
Binary files /dev/null and b/gui/OpenFaceOffline/logo1.ico differ
diff --git a/lib/local/CamCom/CamCom.props b/lib/local/CamCom/CamCom.props
new file mode 100644
index 00000000..9e4a2663
--- /dev/null
+++ b/lib/local/CamCom/CamCom.props
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ $(SolutionDir)lib\local\CamCom;%(AdditionalIncludeDirectories)
+
+
+ dxva2.lib;evr.lib;mf.lib;mfplat.lib;mfplay.lib;mfreadwrite.lib;mfuuid.lib;Strmiids.lib;%(AdditionalDependencies)
+
+
+
+
\ No newline at end of file
diff --git a/lib/local/CamCom/CamCom.vcxproj b/lib/local/CamCom/CamCom.vcxproj
new file mode 100644
index 00000000..5866b40d
--- /dev/null
+++ b/lib/local/CamCom/CamCom.vcxproj
@@ -0,0 +1,167 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {0CEC6A75-17BA-4DC5-9405-C74154921E60}
+ Win32Proj
+ CamCom
+ 8.1
+
+
+
+ StaticLibrary
+ true
+ v140
+ Unicode
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v140
+ Unicode
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ ./;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+
+
+
+
+
+
+ Level3
+ Disabled
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ ./;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ ./;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ ./;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/local/CamCom/CamCom.vcxproj.filters b/lib/local/CamCom/CamCom.vcxproj.filters
new file mode 100644
index 00000000..06e1b61a
--- /dev/null
+++ b/lib/local/CamCom/CamCom.vcxproj.filters
@@ -0,0 +1,45 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/lib/local/CamCom/camera_helper.cpp b/lib/local/CamCom/camera_helper.cpp
new file mode 100644
index 00000000..0b060ad7
--- /dev/null
+++ b/lib/local/CamCom/camera_helper.cpp
@@ -0,0 +1,192 @@
+#include "stdafx.h"
+
+#include "camera_helper.h"
+
+//#include
+
+namespace {
+ namespace internal {
+ template struct property_traits {};
+
+ template
+ struct camera_control_property_traits
+ {
+ typedef IAMCameraControl Interface;
+ static long id() { return Id; }
+ };
+
+ template
+ struct video_proc_amp_property_traits
+ {
+ typedef IAMVideoProcAmp Interface;
+ static long id() { return Id; }
+ };
+
+ template<> struct property_traits < camera::Exposure > : public camera_control_property_traits {};
+ template<> struct property_traits < camera::Focus > : public camera_control_property_traits{};
+ template<> struct property_traits < camera::Zoom > : public camera_control_property_traits{};
+ template<> struct property_traits < camera::Pan > : public camera_control_property_traits{};
+ template<> struct property_traits < camera::Tilt > : public camera_control_property_traits{};
+ template<> struct property_traits < camera::Roll > : public camera_control_property_traits{};
+ template<> struct property_traits < camera::Iris > : public camera_control_property_traits{};
+
+ template<> struct property_traits < camera::Brightness > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Contrast > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Hue > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Saturation > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Sharpness > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Gamma > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::ColorEnable > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::WhiteBalance > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::BacklightCompensation > : public video_proc_amp_property_traits{};
+ template<> struct property_traits < camera::Gain > : public video_proc_amp_property_traits < VideoProcAmp_Gain > {};
+
+ template struct interface_traits {};
+ template<> struct interface_traits < IAMCameraControl >
+ {
+ static long auto_flag() { return CameraControl_Flags_Auto; }
+ static long manual_flag() { return CameraControl_Flags_Manual; }
+ };
+ template<> struct interface_traits
+ {
+ static long auto_flag() { return VideoProcAmp_Flags_Auto; }
+ static long manual_flag() { return VideoProcAmp_Flags_Manual; }
+ };
+
+ template
+ bool has(long id, const SourcePtr& source_ptr) {
+ comet::com_ptr pInterface = comet::com_cast(source_ptr);
+ if (!pInterface) return false;
+ long value, flags;
+ HRESULT hr = pInterface->Get(id, &value, &flags);
+ return SUCCEEDED(hr);
+ }
+
+ template
+ cam_prop_range get_range(long id, const SourcePtr& source_ptr) {
+ comet::com_ptr pInterface = comet::try_cast(source_ptr);
+ cam_prop_range range;
+ long flags;
+ pInterface->GetRange(id, &range.min, &range.max, &range.step, &range.defaultValue, &flags) | comet::raise_exception;
+ range.canBeAuto = (flags & interface_traits::auto_flag()) != 0;
+ range.canBeManual = (flags & interface_traits::manual_flag()) != 0;
+ return range;
+ }
+
+ template
+ cam_prop_value get(long id, const SourcePtr& source_ptr) {
+ comet::com_ptr pInterface = comet::try_cast(source_ptr);
+ cam_prop_value value;
+ long flags;
+ pInterface->Get(id, &value.value, &flags) | comet::raise_exception;
+ value.isAuto = (flags & interface_traits::auto_flag()) != 0 || (flags & interface_traits::manual_flag()) == 0;
+ return value;
+ }
+
+ template
+ void set(long id, const cam_prop_value& value, const SourcePtr& source_ptr) {
+ comet::com_ptr pInterface = comet::try_cast(source_ptr);
+ long flags = value.isAuto ? interface_traits::auto_flag() : interface_traits::manual_flag();
+ pInterface->Set(id, value.value, flags) | comet::raise_exception;
+ }
+ }
+}
+
+#define MAP_OVER_CAMERA_PROPERTIES(FUNC) \
+ FUNC(::camera::Exposure, Exposure) \
+ FUNC(::camera::Focus, Focus) \
+ FUNC(::camera::Zoom, Zoom) \
+ FUNC(::camera::Pan, Pan) \
+ FUNC(::camera::Tilt, Tilt) \
+ FUNC(::camera::Roll, Roll) \
+ FUNC(::camera::Iris, Iris) \
+ FUNC(::camera::Brightness, Brightness) \
+ FUNC(::camera::Contrast, Contrast) \
+ FUNC(::camera::Hue, Hue) \
+ FUNC(::camera::Saturation, Saturation) \
+ FUNC(::camera::Sharpness, Sharpness) \
+ FUNC(::camera::Gamma, Gamma) \
+ FUNC(::camera::ColorEnable, Color Enable) \
+ FUNC(::camera::WhiteBalance, White Balance) \
+ FUNC(::camera::BacklightCompensation, Backlight Compensation) \
+ FUNC(::camera::Gain, Gain)
+
+
+bool camera::has(Property property) const
+{
+ switch (property) {
+#define CASE(PROP, NAME) case PROP: return ::internal::has<::internal::property_traits::Interface>(::internal::property_traits::id(), source_ptr);
+ MAP_OVER_CAMERA_PROPERTIES(CASE)
+#undef CASE
+ default:
+ return false;
+ }
+}
+cam_prop_range camera::get_range(Property property) const
+{
+ switch (property) {
+#define CASE(PROP, NAME) case PROP: return ::internal::get_range<::internal::property_traits::Interface>(::internal::property_traits::id(), source_ptr);
+ MAP_OVER_CAMERA_PROPERTIES(CASE)
+#undef CASE
+ default:
+ throw std::runtime_error("No such property: " + std::string(camera::property_name(property)));
+ }
+}
+cam_prop_value camera::get(Property property) const
+{
+ switch (property) {
+#define CASE(PROP, NAME) case PROP: return ::internal::get<::internal::property_traits::Interface>(::internal::property_traits::id(), source_ptr);
+ MAP_OVER_CAMERA_PROPERTIES(CASE)
+#undef CASE
+ default:
+ throw std::runtime_error("No such property: " + std::string(camera::property_name(property)));
+ }
+}
+void camera::set(Property property, const cam_prop_value& value)
+{
+ switch (property) {
+#define CASE(PROP, NAME) case PROP: ::internal::set<::internal::property_traits::Interface>(::internal::property_traits::id(), value, source_ptr); break;
+ MAP_OVER_CAMERA_PROPERTIES(CASE)
+#undef CASE
+ }
+}
+
+std::vector camera::list_properties()
+{
+ std::vector properties;
+ properties.push_back(Exposure); // CameraControl
+ properties.push_back(Focus);
+ properties.push_back(Zoom);
+ properties.push_back(Pan);
+ properties.push_back(Tilt);
+ properties.push_back(Roll);
+ properties.push_back(Iris);
+
+ properties.push_back(Brightness);
+ properties.push_back(Contrast);
+ properties.push_back(Hue);
+ properties.push_back(Saturation);
+ properties.push_back(Sharpness);
+ properties.push_back(Gamma);
+ properties.push_back(ColorEnable);
+ properties.push_back(WhiteBalance);
+ properties.push_back(BacklightCompensation);
+ properties.push_back(Gain);
+
+ return properties;
+//#define PROPENTRY(PROP, NAME) PROP,
+// MAP_OVER_CAMERA_PROPERTIES(PROPENTRY)
+//#undef PROPENTRY
+// };
+}
+
+const char* camera::property_name(Property property)
+{
+ switch (property) {
+#define CASE(PROP, NAME) case PROP: return #NAME;
+ MAP_OVER_CAMERA_PROPERTIES(CASE)
+#undef CASE
+ default:
+ return "UNKNOWN";
+ }
+}
diff --git a/lib/local/CamCom/camera_helper.h b/lib/local/CamCom/camera_helper.h
new file mode 100644
index 00000000..68746475
--- /dev/null
+++ b/lib/local/CamCom/camera_helper.h
@@ -0,0 +1,720 @@
+#ifndef camera_helper_h__
+#define camera_helper_h__
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include
+#include
+
+namespace comet {
+
+#define MAKE_COMET_COMTYPE(T, BASE) \
+ template<> struct comtype< ::T > \
+ { \
+ static const IID& uuid() { return IID_ ## T; } \
+ typedef ::BASE base; \
+ }
+
+ MAKE_COMET_COMTYPE(IMF2DBuffer, IUnknown);
+ MAKE_COMET_COMTYPE(IAMVideoProcAmp, IUnknown);
+ MAKE_COMET_COMTYPE(IAMCameraControl, IUnknown);
+}
+
+struct cam_prop_range
+{
+ long min;
+ long max;
+ long step;
+ long defaultValue;
+ bool canBeAuto;
+ bool canBeManual;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const cam_prop_range& val)
+{
+ os << "[" << val.min << ":" << val.step << ":" << val.max << "], " << val.defaultValue;
+ if (val.canBeAuto && val.canBeManual)
+ os << " (auto/manual)";
+ else if (val.canBeAuto)
+ os << " (auto)";
+ else if (val.canBeManual)
+ os << " (manual)";
+ return os;
+}
+
+struct cam_prop_value
+{
+ long value;
+ bool isAuto;
+
+ cam_prop_value() : value(0), isAuto(true)
+ {
+ }
+
+ cam_prop_value(long value, bool isAuto = false) : value(value), isAuto(isAuto)
+ {
+ }
+
+ operator long() const
+ {
+ return value;
+ }
+
+ static cam_prop_value AUTO(long value = 0) {
+ return cam_prop_value(value, true);
+ };
+ static cam_prop_value MANUAL(long value = 0) {
+ return cam_prop_value(value, false);
+ };
+};
+
+inline std::ostream& operator<<(std::ostream& os, const cam_prop_value& val)
+{
+ os << val.value;
+ os << " (";
+ if (val.isAuto)
+ os << "auto";
+ else
+ os << "manual";
+ os << ")";
+ return os;
+}
+
+namespace MediaFormat
+{
+ enum e
+ {
+ Unknown,
+ RGB32, // MFVideoFormat_RGB32
+ ARGB32, // MFVideoFormat_ARGB32
+ RGB24, // MFVideoFormat_RGB24
+ RGB555, // MFVideoFormat_RGB555
+ RGB565, // MFVideoFormat_RGB565
+ RGB8, // MFVideoFormat_RGB8
+ AI44, // MFVideoFormat_AI44
+ AYUV, // MFVideoFormat_AYUV
+ YUY2, // MFVideoFormat_YUY2
+ YVYU, // MFVideoFormat_YVYU
+ YVU9, // MFVideoFormat_YVU9
+ UYVY, // MFVideoFormat_UYVY
+ NV11, // MFVideoFormat_NV11
+ NV12, // MFVideoFormat_NV12
+ YV12, // MFVideoFormat_YV12
+ I420, // MFVideoFormat_I420
+ IYUV, // MFVideoFormat_IYUV
+ Y210, // MFVideoFormat_Y210
+ Y216, // MFVideoFormat_Y216
+ Y410, // MFVideoFormat_Y410
+ Y416, // MFVideoFormat_Y416
+ Y41P, // MFVideoFormat_Y41P
+ Y41T, // MFVideoFormat_Y41T
+ Y42T, // MFVideoFormat_Y42T
+ P210, // MFVideoFormat_P210
+ P216, // MFVideoFormat_P216
+ P010, // MFVideoFormat_P010
+ P016, // MFVideoFormat_P016
+ v210, // MFVideoFormat_v210
+ v216, // MFVideoFormat_v216
+ v410, // MFVideoFormat_v410
+ MP43, // MFVideoFormat_MP43
+ MP4S, // MFVideoFormat_MP4S
+ M4S2, // MFVideoFormat_M4S2
+ MP4V, // MFVideoFormat_MP4V
+ WMV1, // MFVideoFormat_WMV1
+ WMV2, // MFVideoFormat_WMV2
+ WMV3, // MFVideoFormat_WMV3
+ WVC1, // MFVideoFormat_WVC1
+ MSS1, // MFVideoFormat_MSS1
+ MSS2, // MFVideoFormat_MSS2
+ MPG1, // MFVideoFormat_MPG1
+ DVSL, // MFVideoFormat_DVSL
+ DVSD, // MFVideoFormat_DVSD
+ DVHD, // MFVideoFormat_DVHD
+ DV25, // MFVideoFormat_DV25
+ DV50, // MFVideoFormat_DV50
+ DVH1, // MFVideoFormat_DVH1
+ DVC, // MFVideoFormat_DVC
+ H264, // MFVideoFormat_H264
+ MJPG, // MFVideoFormat_MJPG
+ YUV_420O, // MFVideoFormat_420O
+ H263, // MFVideoFormat_H263
+ H264_ES, // MFVideoFormat_H264_ES
+ MPEG2, // MFVideoFormat_MPEG2
+ };
+
+ inline e fromGUID(GUID guid)
+ {
+ if (guid == MFVideoFormat_RGB32) return RGB32;
+ else if (guid == MFVideoFormat_ARGB32) return ARGB32;
+ else if (guid == MFVideoFormat_RGB24) return RGB24;
+ else if (guid == MFVideoFormat_RGB555) return RGB555;
+ else if (guid == MFVideoFormat_RGB565) return RGB565;
+ else if (guid == MFVideoFormat_RGB8) return RGB8;
+ else if (guid == MFVideoFormat_AI44) return AI44;
+ else if (guid == MFVideoFormat_AYUV) return AYUV;
+ else if (guid == MFVideoFormat_YUY2) return YUY2;
+ else if (guid == MFVideoFormat_YVYU) return YVYU;
+ else if (guid == MFVideoFormat_YVU9) return YVU9;
+ else if (guid == MFVideoFormat_UYVY) return UYVY;
+ else if (guid == MFVideoFormat_NV11) return NV11;
+ else if (guid == MFVideoFormat_NV12) return NV12;
+ else if (guid == MFVideoFormat_YV12) return YV12;
+ else if (guid == MFVideoFormat_I420) return I420;
+ else if (guid == MFVideoFormat_IYUV) return IYUV;
+ else if (guid == MFVideoFormat_Y210) return Y210;
+ else if (guid == MFVideoFormat_Y216) return Y216;
+ else if (guid == MFVideoFormat_Y410) return Y410;
+ else if (guid == MFVideoFormat_Y416) return Y416;
+ else if (guid == MFVideoFormat_Y41P) return Y41P;
+ else if (guid == MFVideoFormat_Y41T) return Y41T;
+ else if (guid == MFVideoFormat_Y42T) return Y42T;
+ else if (guid == MFVideoFormat_P210) return P210;
+ else if (guid == MFVideoFormat_P216) return P216;
+ else if (guid == MFVideoFormat_P010) return P010;
+ else if (guid == MFVideoFormat_P016) return P016;
+ else if (guid == MFVideoFormat_v210) return v210;
+ else if (guid == MFVideoFormat_v216) return v216;
+ else if (guid == MFVideoFormat_v410) return v410;
+ else if (guid == MFVideoFormat_MP43) return MP43;
+ else if (guid == MFVideoFormat_MP4S) return MP4S;
+ else if (guid == MFVideoFormat_M4S2) return M4S2;
+ else if (guid == MFVideoFormat_MP4V) return MP4V;
+ else if (guid == MFVideoFormat_WMV1) return WMV1;
+ else if (guid == MFVideoFormat_WMV2) return WMV2;
+ else if (guid == MFVideoFormat_WMV3) return WMV3;
+ else if (guid == MFVideoFormat_WVC1) return WVC1;
+ else if (guid == MFVideoFormat_MSS1) return MSS1;
+ else if (guid == MFVideoFormat_MSS2) return MSS2;
+ else if (guid == MFVideoFormat_MPG1) return MPG1;
+ else if (guid == MFVideoFormat_DVSL) return DVSL;
+ else if (guid == MFVideoFormat_DVSD) return DVSD;
+ else if (guid == MFVideoFormat_DVHD) return DVHD;
+ else if (guid == MFVideoFormat_DV25) return DV25;
+ else if (guid == MFVideoFormat_DV50) return DV50;
+ else if (guid == MFVideoFormat_DVH1) return DVH1;
+ else if (guid == MFVideoFormat_DVC) return DVC;
+ else if (guid == MFVideoFormat_H264) return H264;
+ else if (guid == MFVideoFormat_MJPG) return MJPG;
+ else if (guid == MFVideoFormat_420O) return YUV_420O;
+ else if (guid == MFVideoFormat_H263) return H263;
+ else if (guid == MFVideoFormat_H264_ES) return H264_ES;
+ else if (guid == MFVideoFormat_MPEG2) return MPEG2;
+ else return Unknown;
+ }
+
+ inline const char* to_string (const MediaFormat::e& format)
+ {
+ switch (format)
+ {
+ case RGB32: return "RGB32";
+ case ARGB32: return "ARGB32";
+ case RGB24: return "RGB24";
+ case RGB555: return "RGB555";
+ case RGB565: return "RGB565";
+ case RGB8: return "RGB8";
+ case AI44: return "AI44";
+ case AYUV: return "AYUV";
+ case YUY2: return "YUY2";
+ case YVYU: return "YVYU";
+ case YVU9: return "YVU9";
+ case UYVY: return "UYVY";
+ case NV11: return "NV11";
+ case NV12: return "NV12";
+ case YV12: return "YV12";
+ case I420: return "I420";
+ case IYUV: return "IYUV";
+ case Y210: return "Y210";
+ case Y216: return "Y216";
+ case Y410: return "Y410";
+ case Y416: return "Y416";
+ case Y41P: return "Y41P";
+ case Y41T: return "Y41T";
+ case Y42T: return "Y42T";
+ case P210: return "P210";
+ case P216: return "P216";
+ case P010: return "P010";
+ case P016: return "P016";
+ case v210: return "v210";
+ case v216: return "v216";
+ case v410: return "v410";
+ case MP43: return "MP43";
+ case MP4S: return "MP4S";
+ case M4S2: return "M4S2";
+ case MP4V: return "MP4V";
+ case WMV1: return "WMV1";
+ case WMV2: return "WMV2";
+ case WMV3: return "WMV3";
+ case WVC1: return "WVC1";
+ case MSS1: return "MSS1";
+ case MSS2: return "MSS2";
+ case MPG1: return "MPG1";
+ case DVSL: return "DVSL";
+ case DVSD: return "DVSD";
+ case DVHD: return "DVHD";
+ case DV25: return "DV25";
+ case DV50: return "DV50";
+ case DVH1: return "DVH1";
+ case DVC: return "DVC";
+ case H264: return "H264";
+ case MJPG: return "MJPG";
+ case YUV_420O: return "420O";
+ case H263: return "H263";
+ case H264_ES: return "H264_ES";
+ case MPEG2: return "MPEG2";
+ default: return "Unknown";
+ }
+ }
+
+ inline std::ostream& operator<< (std::ostream& os, const MediaFormat::e& format)
+ {
+ return os << to_string(format);
+ }
+}
+
+class media_type
+{
+public:
+ media_type() : _ptr(NULL)
+ {
+ }
+
+ media_type(IMFMediaType* ptr) : _ptr(ptr)
+ {
+ }
+
+ media_type(comet::com_ptr ptr) : _ptr(ptr)
+ {
+ }
+
+ cv::Size resolution() const
+ {
+ UINT32 width, height;
+ MFGetAttributeSize(_ptr.in(), MF_MT_FRAME_SIZE, &width, &height) | comet::raise_exception;
+ return cv::Size(width, height);
+ }
+
+ double framerate() const
+ {
+ UINT32 num, denom;
+ MFGetAttributeRatio(_ptr.in(), MF_MT_FRAME_RATE, &num, &denom) | comet::raise_exception;
+ return static_cast(num) / denom;
+ }
+
+ MediaFormat::e format() const
+ {
+ GUID subtype;
+ _ptr->GetGUID(MF_MT_SUBTYPE, &subtype) | comet::raise_exception;
+ return MediaFormat::fromGUID(subtype);
+ }
+
+#ifdef _TYPEDEF_BOOL_TYPE
+ typedef media_type _Myt;
+ _TYPEDEF_BOOL_TYPE;
+ _OPERATOR_BOOL() const _NOEXCEPT
+ { // test for non-null pointer
+ return (_ptr != 0 ? _CONVERTIBLE_TO_TRUE : 0);
+ }
+#else
+ explicit operator bool() const
+ { // test for non-null pointer
+ return _ptr != 0;
+ }
+#endif
+
+ comet::com_ptr _ptr;
+};
+
+
+class camera
+{
+public:
+ camera()
+ {
+ }
+
+ camera(IMFActivate* ptr) : activate_ptr(ptr)
+ {
+ }
+
+ camera(comet::com_ptr ptr) : activate_ptr(ptr)
+ {
+ }
+
+ // TODO actually implement these or make sure they are deleted/created correctly, this might be the reason for weird mapping of stuff
+ camera(const camera& other)
+ {
+// this->
+// this = other;
+ }
+ //= delete;
+
+ camera(camera&& other)
+ {
+ *this = std::move(other);
+ }
+
+ //camera& operator=(const camera& other) = delete;
+
+ camera& operator=(camera&& other)
+ {
+ activate_ptr.swap(other.activate_ptr);
+ source_ptr.swap(other.source_ptr);
+ reader_ptr.swap(other.reader_ptr);
+ return *this;
+ }
+
+ ~camera()
+ {
+ shutdown();
+ }
+
+ bool is_active() const
+ {
+ return !source_ptr.is_null();
+ }
+
+ void activate()
+ {
+ if (activate_ptr)
+ activate_ptr->ActivateObject(IID_IMFMediaSource, reinterpret_cast(source_ptr.out())) | comet::raise_exception;
+ reader_ptr = NULL;
+ }
+
+ void shutdown()
+ {
+ if (activate_ptr)
+ activate_ptr->ShutdownObject() | comet::raise_exception;
+ source_ptr = NULL;
+ reader_ptr = NULL;
+ }
+
+ cv::Mat read_frame(int streamIndex = MF_SOURCE_READER_FIRST_VIDEO_STREAM, int bufferIndex = 0)
+ {
+ if (!activate_ptr)
+ return cv::Mat();
+
+ if (!reader_ptr)
+ {
+ comet::com_ptr pAttributes;
+ MFCreateAttributes(pAttributes.out(), 1) | comet::raise_exception;
+ pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE) | comet::raise_exception;
+ pAttributes->SetUINT32(MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE) | comet::raise_exception;
+
+ MFCreateSourceReaderFromMediaSource(source_ptr.in(), pAttributes.in(), reader_ptr.out()) | comet::raise_exception;
+ }
+
+ comet::com_ptr sample;
+ DWORD actualStreamIndex, flags;
+ LONGLONG timestamp;
+ try
+ {
+ do
+ {
+ reader_ptr->ReadSample(
+ streamIndex, // Stream index.
+ 0, // Flags.
+ &actualStreamIndex, // Receives the actual stream index.
+ &flags, // Receives status flags.
+ ×tamp, // Receives the time stamp.
+ sample.out() // Receives the sample or NULL.
+ ) | comet::raise_exception;
+ } while (sample == NULL && (flags & MF_SOURCE_READERF_STREAMTICK));
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Error getting frame: " << e.what() << std::endl;
+ std::cerr << " flags: " << flags << std::endl;
+ shutdown();
+ activate();
+ throw;
+ }
+
+ media_type cur_media_type;
+ reader_ptr->GetCurrentMediaType(actualStreamIndex, cur_media_type._ptr.out()) | comet::raise_exception;
+
+ //PrintAttributes(cur_media_type._ptr.in());
+
+ auto format = cur_media_type.format();
+
+ cv::Mat ret;
+
+ DWORD bufCount;
+ sample->GetBufferCount(&bufCount) | comet::raise_exception;
+
+ DWORD bufIndex = bufferIndex >= 0 ? bufferIndex : bufCount - bufferIndex;
+
+ if (bufCount > bufIndex)
+ {
+ comet::com_ptr buffer;
+ sample->GetBufferByIndex(bufferIndex, buffer.out()) | comet::raise_exception;
+
+ switch (format)
+ {
+ case MediaFormat::RGB24:
+ case MediaFormat::ARGB32:
+ case MediaFormat::RGB32:
+ {
+ comet::com_ptr buf2d = try_cast(buffer);
+
+ //DWORD length;
+ //buf2d->GetContiguousLength(&length) | comet::raise_exception;
+
+ //ret.create();
+
+ //COMET_ASSERT(ret.dataend - ret.datastart == length);
+
+ auto resolution = cur_media_type.resolution();
+
+ struct buf2d_lock
+ {
+ comet::com_ptr& buf2d;
+
+ buf2d_lock(comet::com_ptr& buf2d, BYTE*& scanline0, LONG& pitch) : buf2d(buf2d)
+ {
+ buf2d->Lock2D(&scanline0, &pitch) | comet::raise_exception;
+ }
+
+ ~buf2d_lock()
+ {
+ buf2d->Unlock2D() | comet::raise_exception;
+ }
+ };
+
+ BYTE *scanline0;
+ LONG pitch;
+ buf2d_lock buf_lock_token(buf2d, scanline0, pitch);
+ if (pitch >= 0)
+ {
+ cv::Mat buf2dmat(resolution,
+ (format == MediaFormat::RGB24) ? CV_8UC3 : CV_8UC4,
+ scanline0,
+ pitch);
+ buf2dmat.copyTo(ret);
+ }
+ else
+ {
+ cv::Mat buf2dmat(resolution,
+ (format == MediaFormat::RGB24) ? CV_8UC3 : CV_8UC4,
+ scanline0 + pitch*(resolution.height-1),
+ -pitch);
+ cv::flip(buf2dmat, ret, 0);
+ }
+
+ break;
+ }
+ case MediaFormat::MJPG:
+ {
+ struct buf_lock
+ {
+ comet::com_ptr& buffer;
+
+ buf_lock(comet::com_ptr& buffer, BYTE*& data, DWORD& maxLength, DWORD& length) : buffer(buffer)
+ {
+ buffer->Lock(&data, &maxLength, &length) | comet::raise_exception;
+ }
+
+ ~buf_lock()
+ {
+ buffer->Unlock() | comet::raise_exception;
+ }
+ };
+
+ BYTE* data;
+ DWORD length;
+ DWORD maxLength;
+
+ buf_lock buf_lock_token(buffer, data, maxLength, length);
+
+ ret = cv::imdecode(cv::Mat(length, 1, CV_8U, data), cv::IMREAD_COLOR);
+
+ break;
+ }
+ default:
+ std::stringstream sstream;
+ sstream << "Unknown media format: " << format;
+ throw std::runtime_error(sstream.str());
+ }
+ }
+
+ return ret;
+ }
+
+ std::string name() const
+ {
+ return get_attr_str(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
+ }
+
+ std::string symlink() const
+ {
+ return get_attr_str(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
+ }
+
+ // TODO change
+ //explicit operator bool() const
+ //{
+ // return !activate_ptr.is_null();
+ //}
+
+ enum Property
+ {
+ // CameraControl
+ Exposure,
+ Focus,
+ Zoom,
+ Pan,
+ Tilt,
+ Roll,
+ Iris,
+
+ // VideoProcAmp
+ Brightness,
+ Contrast,
+ Hue,
+ Saturation,
+ Sharpness,
+ Gamma,
+ ColorEnable,
+ WhiteBalance,
+ BacklightCompensation,
+ Gain
+ };
+
+ bool has(Property property) const;
+ cam_prop_range get_range(Property property) const;
+ cam_prop_value get(Property property) const;
+ void set(Property property, const cam_prop_value& value);
+
+ static std::vector list_properties();
+ static const char* property_name(Property);
+
+ std::vector media_types(int streamIndex = 0) const
+ {
+ auto pHandler = getMediaTypeHandler(streamIndex);
+
+ DWORD cTypes = 0;
+ pHandler->GetMediaTypeCount(&cTypes) | comet::raise_exception;
+
+ std::vector ret;
+ for (DWORD i = 0; i < cTypes; i++)
+ {
+ comet::com_ptr pType;
+ pHandler->GetMediaTypeByIndex(i, pType.out()) | comet::raise_exception;
+ ret.emplace_back(pType);
+ }
+
+ return ret;
+ }
+
+ media_type get_media_type(int streamIndex = 0)
+ {
+ media_type ret;
+ getMediaTypeHandler(streamIndex)->GetCurrentMediaType(ret._ptr.out()) | comet::raise_exception;
+ return ret;
+ }
+
+ void set_media_type(const media_type& type, int streamIndex = 0)
+ {
+ getMediaTypeHandler(streamIndex)->SetCurrentMediaType(type._ptr.in()) | comet::raise_exception;
+ if (reader_ptr)
+ {
+ reader_ptr->SetCurrentMediaType(streamIndex, nullptr, type._ptr.in()) | comet::raise_exception;
+ }
+ }
+
+ // TODO change
+ //explicit operator bool() {
+ // return !activate_ptr.is_null();
+ //}
+
+private:
+ std::string get_attr_str(REFGUID guid) const
+ {
+ comet::task_ptr pStr;
+ UINT32 strLen;
+ activate_ptr->GetAllocatedString(guid, pStr.out(), &strLen) | comet::raise_exception;
+ return comet::bstr_t(pStr.in(), strLen).s_str();
+ }
+
+ comet::com_ptr getMediaTypeHandler(int streamIndex = 0) const
+ {
+ comet::com_ptr pPD;
+ source_ptr->CreatePresentationDescriptor(pPD.out()) | comet::raise_exception;
+
+ BOOL fSelected;
+ comet::com_ptr pSD;
+ pPD->GetStreamDescriptorByIndex(streamIndex, &fSelected, pSD.out()) | comet::raise_exception;
+
+ comet::com_ptr pHandler;
+ pSD->GetMediaTypeHandler(pHandler.out()) | comet::raise_exception;
+
+ return pHandler;
+ }
+
+ comet::com_ptr activate_ptr;
+ comet::com_ptr source_ptr;
+ comet::com_ptr reader_ptr;
+};
+
+class camera_helper
+{
+public:
+ static std::vector get_all_cameras()
+ {
+ comet::com_ptr config;
+ MFCreateAttributes(config.out(), 1) | comet::raise_exception;
+
+ config->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID) | comet::raise_exception;
+
+ comet::com_ptr_array com_ptr_array;
+ MFEnumDeviceSources(config.in(), com_ptr_array.out(), com_ptr_array.out_count()) | comet::raise_exception;
+
+ std::vector ret;
+ for (size_t i = 0; i < com_ptr_array.count(); ++i)
+ {
+ ret.emplace_back(com_ptr_array[i]);
+ }
+ return ret;
+ }
+
+ static camera get_camera_by_symlink(const std::string& symlink)
+ {
+ // This is how you should do it, but for some reason it gives an activate pointer with no friendly name
+
+ // comet::com_ptr config;
+ // MFCreateAttributes(config.out(), 1) | comet::raise_exception;
+ //
+ // config->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID) | comet::raise_exception;
+ // comet::bstr_t symlink_bstr(symlink);
+ // config->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, symlink_bstr.c_str()) | comet::raise_exception;
+ //
+ // comet::com_ptr activate_ptr;
+ // MFCreateDeviceSourceActivate(config.in(), activate_ptr.out()) | comet::raise_exception;
+ //
+ // return camera(activate_ptr);
+
+ for(auto&& camera : get_all_cameras())
+ {
+ if (camera.symlink() == symlink)
+ return std::move(camera);
+ }
+
+ throw std::runtime_error("No camera with symlink: " + std::string(symlink));
+ }
+};
+
+#endif // camera_helper_h__
diff --git a/lib/local/CamCom/comet/array.h b/lib/local/CamCom/comet/array.h
new file mode 100644
index 00000000..f305f746
--- /dev/null
+++ b/lib/local/CamCom/comet/array.h
@@ -0,0 +1,150 @@
+/** \file
+* Array wrapper.
+*/
+/*
+* Copyright 2001 Sofus Mortensen
+*
+* This material is provided "as is", with absolutely no warranty
+* expressed or implied. Any use is at your own risk. Permission to
+* use or copy this software for any purpose is hereby granted without
+* fee, provided the above notices are retained on all copies.
+* Permission to modify the code and to distribute modified code is
+* granted, provided the above notices are retained, and a notice that
+* the code was modified is included with the above copyright notice.
+*
+* This header is part of Comet version 2.
+* https://github.com/alamaison/comet
+*
+*/
+
+/*
+* comet::array_t is adapted from class array by Nicolai M. Josuttis
+*
+* (C) Copyright Nicolai M. Josuttis 2001.
+* Permission to copy, use, modify, sell and distribute this software
+* is granted provided this copyright notice appears in all copies.
+* This software is provided "as is" without express or implied
+* warranty, and with no claim as to its suitability for any purpose.
+*
+*/
+
+#ifndef COMET_ARRAY_H
+#define COMET_ARRAY_H
+
+#include
+#include
+#include
+#include
+#include
+
+namespace comet {
+
+#pragma pack(push)
+#pragma pack(1)
+
+ /*!\addtogroup Misc
+ */
+ //@{
+
+ template class array_t
+ {
+ T a_[SZ];
+ public:
+ typedef T value_type;
+ typedef typename std::vector::iterator iterator;
+ typedef typename std::vector::const_iterator const_iterator;
+
+ typedef typename std::vector::reverse_iterator reverse_iterator;
+ typedef typename std::vector::const_reverse_iterator const_reverse_iterator;
+
+ typedef typename std::vector::size_type size_type;
+ typedef typename std::vector::difference_type difference_type;
+
+ typedef T& reference;
+ typedef const T& const_reference;
+
+ // reference operator[](size_type i) { return a_[i]; }
+ // const_reference operator[](size_type i) const { return a_[i]; }
+
+ iterator begin() { return iterator(a_); }
+ iterator end() { return iterator(a_ + SZ); }
+ const_iterator begin() const { return const_iterator(a_); }
+ const_iterator end() const { return const_iterator(a_ + SZ); }
+
+ reverse_iterator rbegin() { return reverse_iterator(a_); }
+ reverse_iterator rend() { return reverse_iterator(a_ + SZ); }
+ const_reverse_iterator rbegin() const { return const_reverse_iterator(a_); }
+ const_reverse_iterator rend() const { return const_reverse_iterator(a_ + SZ); }
+
+ operator const T*() const { return a_; }
+ operator T*() { return a_; }
+
+ static size_type size() { return SZ; }
+ static bool empty() { return false; }
+ static size_type max_size() { return SZ; }
+ enum { static_size = SZ };
+
+ reference front() { return a_[0]; }
+ const_reference front() const { return a_[0]; }
+ reference back() { return a_[SZ-1]; };
+ const_reference back() const { return a_[SZ-1]; }
+
+ // swap (note: linear complexity)
+ void swap (array_t& y) {
+ std::swap_ranges(begin(),end(),y.begin());
+ }
+
+ // assignment with type conversion
+ template
+ array_t& operator= (const array_t& rhs) {
+ std::copy(rhs.begin(),rhs.end(), begin());
+ return *this;
+ }
+
+ // assign one value to all elements
+ void assign (const T& value)
+ {
+ std::fill_n(begin(),size(),value);
+ }
+
+ reference at(size_type i) { rangecheck(i); return a_[i]; }
+ const_reference at(size_type i) const { rangecheck(i); return a_[i]; }
+
+ private:
+ // check range (may be private because it is static)
+ static void rangecheck (size_type i) {
+ if (i >= size()) { throw std::range_error("array"); }
+ }
+
+ };
+ //@}
+#pragma pack(pop)
+
+ // comparisons
+ template
+ bool operator== (const array_t& x, const array_t& y) {
+ return std::equal(x.begin(), x.end(), y.begin());
+ }
+ template
+ bool operator< (const array_t& x, const array_t& y) {
+ return std::lexicographical_compare(x.begin(),x.end(),y.begin(),y.end());
+ }
+ template
+ bool operator!= (const array_t& x, const array_t& y) {
+ return !(x==y);
+ }
+ template
+ bool operator> (const array_t& x, const array_t& y) {
+ return y
+ bool operator<= (const array_t& x, const array_t& y) {
+ return !(y
+ bool operator>= (const array_t& x, const array_t& y) {
+ return !(x
+
+# if defined(COMET_ASSERT_THROWS) || defined(COMET_ASSERT_THROWS_ALWAYS)
+# if defined(COMET_ASSERT_THROWS_ALWAYS) || !defined(NDEBUG)
+namespace comet
+{
+ /*! \defgroup ErrorHandling Error handling.
+ */
+ //@{
+//! Indicates a comet assertion failed.
+/** This is enabled for debug builds if COMET_ASSERT_THROWS is defined and
+ * enabled for both debug and release if COMET_ASSERT_THROWS_ALWAYS is defined.
+ */
+struct assert_failed : std::runtime_error
+{
+ assert_failed( const char *val) : runtime_error(val) {}
+};
+ //@}
+}
+# define COMET_ASSERT(x_) if (x_) ; else throw comet::assert_failed("Assert Failed: " #x_ );
+# define COMET_THROWS_ASSERT throw( comet::assert_failed)
+# else
+# define COMET_ASSERT(x_) ;
+# endif
+
+# else
+# define COMET_THROWS_ASSERT throw()
+# ifndef __CYGWIN__
+
+# include
+# define COMET_ASSERT _ASSERTE
+
+# else
+
+# include
+# define COMET_ASSERT assert
+# endif
+# endif
+/*! \addtogroup ErrorHandling
+ */
+//@{
+/** \def COMET_ASSERT Assertion in commet.
+ * \sa COMET_THROWS_ASSERT COMET_ASSERT_THROWS_ALWAYS
+ */
+ //@}
+
+#endif
diff --git a/lib/local/CamCom/comet/atl_module.h b/lib/local/CamCom/comet/atl_module.h
new file mode 100644
index 00000000..5c37bdcb
--- /dev/null
+++ b/lib/local/CamCom/comet/atl_module.h
@@ -0,0 +1,121 @@
+/** \file
+ * ATL Module Compatability wrapper.
+ */
+/*
+ * Copyright 2000, 2001 Paul Hollingsworth, Michael Geddes
+ * Copyright 2013 Alexander Lamaison
+ *
+ * This material is provided "as is", with absolutely no warranty
+ * expressed or implied. Any use is at your own risk. Permission to
+ * use or copy this software for any purpose is hereby granted without
+ * fee, provided the above notices are retained on all copies.
+ * Permission to modify the code and to distribute modified code is
+ * granted, provided the above notices are retained, and a notice that
+ * the code was modified is included with the above copyright notice.
+ *
+ * This header is part of Comet version 2.
+ * https://github.com/alamaison/comet
+ */
+
+#ifndef COMET_ATL_MODULE_H
+#define COMET_ATL_MODULE_H
+
+#include
+
+#include
+
+namespace comet {
+ /*! \defgroup ATL ATL conversion.
+ */
+ //@{
+
+ /** \struct declspec_comtype atl_module.h comet/atl_module.h
+ * Helper to create a 'comtype' for a non-comet interface.
+ * \code
+ * namespace comet{
+ * template<> struct comtype :
+ * declspec_comtype
+ * { };
+ * };
+ * \endcode
+ *
+ */
+ template struct declspec_comtype
+ {
+ static const uuid_t& uuid() { return uuid_t::create_const_reference(__uuidof(ITF)); }
+ typedef BASE base;
+ };
+
+
+ /// Placeholder for an empty comet typelibrary.
+ struct empty_typelib
+ {
+ typedef nil coclasses;
+ };
+
+ template
+ class atl_module_ex : public ATL_MODULE
+ {
+ // ATL will take the responsibility of registering the embedded type library.
+ public:
+ HRESULT Init(_ATL_OBJMAP_ENTRY* p, HINSTANCE h, const GUID* plibid = NULL)
+ {
+ module().instance(h);
+ return ATL_MODULE::Init(p, h, plibid);
+ }
+
+ HRESULT RegisterServer(BOOL bRegTypeLib = FALSE, const CLSID* pCLSID = NULL)
+ {
+ HRESULT hr = ATL_MODULE::RegisterServer(bRegTypeLib, pCLSID);
+ if(SUCCEEDED(hr))
+ hr = COM_SERVER::DllRegisterServer();
+ return hr;
+ }
+
+ HRESULT UnregisterServer(BOOL bUnRegTypeLib, const CLSID* pCLSID = NULL)
+ {
+ COM_SERVER::DllUnregisterServer();
+ return ATL_MODULE::UnregisterServer(bUnRegTypeLib, pCLSID);
+ }
+
+ HRESULT GetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
+ {
+ HRESULT hr = COM_SERVER::DllGetClassObject( rclsid, riid, ppv);
+ if( hr == CLASS_E_CLASSNOTAVAILABLE )
+ {
+ hr = ATL_MODULE::GetClassObject(rclsid, riid,ppv);
+ }
+ return hr;
+ }
+
+ LONG GetLockCount()
+ {
+ return module().rc() + ATL_MODULE::GetLockCount();
+ }
+ };
+
+ /** Wraps an ATL::CComModule to provide co-existing ATL/Comet co-classes.
+ * ATL will take responsibility for registering the embedded type-library.
+ *
+ * Here is an example of how to use it:
+ * \code
+ * struct typelib_subset
+ * {
+ * typedef COMET_STRICT_TYPENAME comet::typelist::make_list< CoClass1, CoClass2 > coclasses;
+ * };
+ * comet::atl_module _Module;
+ * \endcode
+ * And in std.h:
+ * \code
+ * struct typelib_subset;
+ * extern comet::atl_module _Module;
+ * \endcode
+ */
+ template
+ class atl_module : public atl_module_ex< com_server >, ATL_MODULE >
+ {
+ };
+ //@}
+} // namespace comet
+
+#endif
diff --git a/lib/local/CamCom/comet/auto_buffer.h b/lib/local/CamCom/comet/auto_buffer.h
new file mode 100644
index 00000000..0a304bd1
--- /dev/null
+++ b/lib/local/CamCom/comet/auto_buffer.h
@@ -0,0 +1,99 @@
+/** \file
+ * Simple uncopyable buffer class.
+ */
+ /*
+ * Copyright 2004, Michael Geddes, Lijun Qin.
+ *
+ * This material is provided "as is", with absolutely no warranty
+ * expressed or implied. Any use is at your own risk. Permission to
+ * use or copy this software for any purpose is hereby granted without
+ * fee, provided the above notices are retained on all copies.
+ * Permission to modify the code and to distribute modified code is
+ * granted, provided the above notices are retained, and a notice that
+ * the code was modified is included with the above copyright notice.
+ *
+ * This header is part of Comet version 2.
+ * https://github.com/alamaison/comet
+ */
+
+#ifndef INCLUDE_COMET_AUTO_BUFFER_H
+#define INCLUDE_COMET_AUTO_BUFFER_H
+
+#ifdef _SHOW_INC
+#pragma message(" #Include " __FILE__)
+#endif
+
+namespace comet
+{
+ /*!\addtogroup Misc
+ */
+ //@{
+
+ /** \class auto_buffer_t auto_buffer.h comet/auto_buffer.h
+ * Simle auto-deleting buffer class.
+ * Non-copyable /returnable.
+ */
+ template
+ class auto_buffer_t
+ {
+ public:
+ typedef size_t size_type;
+ /** Create a buffer of the given size.
+ * This is the only valid constructor.
+ */
+ explicit auto_buffer_t(size_type size)
+ {
+ begin_ = new T[size];
+ }
+ /// Auto-delete the buffer.
+ ~auto_buffer_t() throw() { delete[] begin_; }
+
+ /// Clear the buffer.
+ void clear() throw()
+ {
+ delete[] begin_;
+ begin_ = 0;
+ }
+ /// Is the buffer empty.
+ bool empty() const throw()
+ {
+ return begin_ != NULL;
+ }
+ /// Resize the buffer.
+ void resize( size_type newSize) throw()
+ {
+ delete[] begin_;
+ begin_ = new T[newSize];
+ }
+
+ /// Return a reference to the specifed element.
+ T & at( size_type t) throw() { return begin_[t]; }
+ T & operator[]( size_type t) throw() { return begin_[t]; }
+ /// Return a const reference to the specifed element.
+ const T & at( size_type t) const throw() { return begin_[t]; }
+ const T & operator[]( size_type t) const throw() { return begin_[t]; }
+
+ /// Detatch the memory.
+ T *detach()
+ {
+ T *val = begin_;
+ begin_ = NULL;
+ return val;
+ }
+ /// Return the memory.
+ T *get() { return begin_; }
+ const T *get()const { return begin_; }
+
+ private:
+ /// Can't assign.
+ auto_buffer_t &operator=(const auto_buffer_t &);
+ /// can't copy.
+ auto_buffer_t(const auto_buffer_t&);
+
+ /// Pointer to memory.
+ T *begin_;
+ };
+ //@}
+}
+
+#endif /* INCLUDE_COMET_AUTO_BUFFER_H */
diff --git a/lib/local/CamCom/comet/bstr.h b/lib/local/CamCom/comet/bstr.h
new file mode 100644
index 00000000..23fd332d
--- /dev/null
+++ b/lib/local/CamCom/comet/bstr.h
@@ -0,0 +1,948 @@
+/** \file
+ * BSTR wrapper classes.
+ */
+/*
+ * Copyright 2000-2004 Sofus Mortensen, Michael Geddes
+ * Copyright 2012 Alexander Lamaison
+ *
+ * This material is provided "as is", with absolutely no warranty
+ * expressed or implied. Any use is at your own risk. Permission to
+ * use or copy this software for any purpose is hereby granted without
+ * fee, provided the above notices are retained on all copies.
+ * Permission to modify the code and to distribute modified code is
+ * granted, provided the above notices are retained, and a notice that
+ * the code was modified is included with the above copyright notice.
+ *
+ * This header is part of Comet version 2.
+ * https://github.com/alamaison/comet
+ */
+
+#ifndef COMET_BSTR_H
+#define COMET_BSTR_H
+
+#include
+
+#ifdef COMET_BROKEN_WTYPES
+#include
+#endif // COMET_BROKEN_WTYPES
+#include
+#include
+#include
+#ifndef COMET_GCC_HEADERS
+#include
+#endif // COMET_GCC_HEADERS
+
+#include
+#include
+#undef max
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#pragma warning(push)
+#pragma warning(disable : 4522 4521)
+
+#pragma comment( lib, "oleaut32" )
+
+namespace comet {
+
+#ifndef NORM_IGNOREKASHIDA
+#define NORM_IGNOREKASHIDA 0x00040000
+#endif // NORM_IGNOREKASHIDA
+ /*! \addtogroup COMType
+ */
+ //@{
+ //! Comparsion flags.
+ /*! Can be used with \link comet::bstr_t::cmp cmp \endlink or the comparsion functors.
+ \sa cmp
+ less
+ less_equal
+ greater
+ greater_equal
+ equal_to
+ not_equal_to
+ */
+
+ enum compare_flags_t
+ {
+ cf_ignore_case = NORM_IGNORECASE, //!< Ignore case.
+ cf_ingore_nonspace = NORM_IGNORENONSPACE, //!< Ignore nonspacing chars.
+ cf_ignore_symbols = NORM_IGNORESYMBOLS, //!< Ignore symbols.
+ cf_ignore_width = NORM_IGNOREWIDTH, //!< Ignore string width.
+ cf_ignore_kanatype = NORM_IGNOREKANATYPE, //!< Ignore Kana type.
+ cf_ignore_kashida = NORM_IGNOREKASHIDA //!< Ignore Arabic kashida characters.
+ };
+ //@}
+
+ namespace impl {
+
+ inline const wchar_t* null_to_empty(const wchar_t* s)
+ { return s ? s : L""; }
+
+ } // namespace
+
+
+ /*! \addtogroup COMType
+ */
+ //@{
+
+ /*! \class bstr_t bstr.h comet/bstr.h
+ * BSTR Wrapper.
+ * \sa bstr_t
+ */
+ class bstr_t {
+ public:
+ typedef wchar_t value_type;
+#if !(defined(_STLP_DEBUG) || (defined(_HAS_ITERATOR_DEBUGGING)) && _MSC_VER >= 1400)
+ typedef std::wstring::iterator iterator;
+ typedef std::wstring::const_iterator const_iterator;
+
+ typedef std::wstring::reverse_iterator reverse_iterator;
+ typedef std::wstring::const_reverse_iterator const_reverse_iterator;
+#else // _STLP_DEBUG
+ typedef wchar_t *iterator;
+ typedef const wchar_t *const_iterator;
+#if defined(COMET_STD_ITERATOR)
+ typedef std::reverse_iterator reverse_iterator;
+ typedef std::reverse_iterator const_reverse_iterator;
+#else
+ // workaround for broken reverse_iterator implementations due to no partial specialisation
+ typedef std::reverse_iterator reverse_iterator;
+ typedef std::reverse_iterator const_reverse_iterator;
+#endif
+#endif // _STLP_DEBUG
+
+ typedef std::wstring::size_type size_type;
+ typedef std::wstring::difference_type difference_type;
+
+ typedef wchar_t& reference;
+ typedef const wchar_t const_reference;
+
+ private:
+ BSTR str_;
+
+ void construct() { str_ = 0; }
+ void construct(BSTR s, bool copy) throw(std::bad_alloc)
+ { if (copy) str_ = copy_str(s); else str_ = s; }
+
+ void construct(const wchar_t* s) throw(std::bad_alloc)
+ { str_ = copy_str(s); }
+
+ void construct(const wchar_t* s, size_t len) throw(std::bad_alloc)
+ { str_ = copy_str(s, len); }
+
+ void construct(const char* s) throw(std::bad_alloc, std::runtime_error) {
+ convert_str(s, -1);
+ }
+
+ void construct(const char* s, size_t len)
+ {
+ if (len >= static_cast(std::numeric_limits::max()))
+ throw std::length_error(
+ "String exceeded maximum length for conversion");
+
+ convert_str(s, static_cast(len+1));
+ }
+
+ void construct(const uuid_t& u, bool braces)
+ {
+ str_ = impl::bad_alloc_check(::SysAllocStringLen(0, braces?38:36));
+ u.copy_to_str(str_+(braces?1:0));
+ if (braces)
+ {
+ str_[0]=L'{';
+ str_[37]=L'}';
+ }
+ }
+
+ void construct(const wchar_t* s1, size_t l1, const wchar_t* s2, size_t l2) throw(std::bad_alloc)
+ {
+ str_ = impl::bad_alloc_check(::SysAllocStringLen(NULL, UINT(l1+l2)));
+ if (l1) memcpy(str_, s1, sizeof(wchar_t)*(l1));
+ if (l2) memcpy(str_+l1, s2, sizeof(wchar_t)*(1+l2));
+ }
+
+ void destroy() throw()
+ { if (str_) ::SysFreeString(str_); }
+
+ bool is_regular() const throw()
+ { return !str_ || length() == wcslen(str_); }
+
+ static BSTR copy_str(const wchar_t* src) throw(std::bad_alloc)
+ { return src ? impl::bad_alloc_check(::SysAllocString(src)) : 0; }
+
+ static BSTR copy_str(const wchar_t* src, size_t len) throw(std::bad_alloc)
+ { return src ? impl::bad_alloc_check(::SysAllocStringLen(src, UINT(len))) : 0; }
+
+ static BSTR copy_str(BSTR src) throw(std::bad_alloc)
+ { return src ? impl::bad_alloc_check(::SysAllocStringLen(src, ::SysStringLen(src))) : 0; }
+
+ void convert_str(const char* s, int l) throw(std::bad_alloc, std::runtime_error)
+ {
+ if (s != 0) {
+#if defined(_MBCS) || !defined(COMET_NO_MBCS)
+ int wl = ::MultiByteToWideChar(CP_ACP, 0, s, l, NULL,0);
+#else
+ int wl = ((l>=0)?l: (1+strlen(s)));
+ COMET_ASSERT( wl == ::MultiByteToWideChar( CP_ACP, 0, s, l, NULL,0));
+#endif
+ str_ = impl::bad_alloc_check(::SysAllocStringLen(0, wl - 1));
+ if (::MultiByteToWideChar(CP_ACP, 0, s, l, str_, wl) == 0)
+ {
+ destroy();
+ throw std::runtime_error("MultiByteToWideChar has failed");
+ }
+ } else str_ = 0;
+ }
+
+ public:
+ /*! Default constructor
+ Constructs a null string.
+ */
+ bstr_t() throw()
+ {
+ construct();
+ }
+
+ //! Copy constructor
+ /*!
+ \param s
+ String initialise bstr_t from.
+
+ \exception std::bad_alloc
+ On memory exhaustion std::bad_alloc is thrown.
+ */
+ bstr_t(const bstr_t& s) throw(std::bad_alloc)
+ {
+ construct(s.str_, true);
+ }
+
+ //! Construct string from const wchar_t*
+ /*!
+ \param s
+ String to initialise bstr_t from.
+
+ \exception std::bad_alloc
+ On memory exhaustion std::bad_alloc is thrown.
+ */
+ bstr_t(const wchar_t* s) throw(std::bad_alloc)
+ {
+ construct(s);
+ }
+
+ bstr_t(const wchar_t* s, size_t len) throw(std::bad_alloc)
+ {
+ construct(s, len);
+ }
+
+ //! Construct string from const char*
+ /*!
+ \param s
+ String to initialise bstr_t from.
+
+ \exception std::bad_alloc
+ On memory exhaustion std::bad_alloc is thrown.
+ \exception std::runtime_error
+ Should string conversion fail, std::runtime_error will be thrown.
+ */
+ bstr_t(const char* s) throw(std::runtime_error)
+ {
+ construct(s);
+ }
+
+ bstr_t(const char* s, size_t len) throw(std::bad_alloc)
+ {
+ construct(s, len);
+ }
+
+ //! Construct string from const std::string&
+ /*!
+ \param s
+ String to initialise bstr_t from.
+
+ \exception std::bad_alloc
+ On memory exhaustion std::bad_alloc is thrown.
+ \exception std::length_error
+ If this given string is too long to be converted,
+ std::length_error is thrown.
+ \exception std::runtime_error
+ Should string conversion fail, std::runtime_error is thrown.
+ */
+ bstr_t(const std::string& s)
+ {
+ construct(s.c_str(), s.length());
+ }
+
+ //! Construct string from BSTR
+ /*!
+ Takes ownership of specified BSTR. To prevent misuse the BSTR must be wrapped using auto_attach.
+
+ \code
+ bstr_t bs( auto_attach( myBSTR ) );
+ \endcode
+
+ \param s
+ String to initialise bstr_t from.
+ */
+ bstr_t(const impl::auto_attach_t& s) throw()
+ {
+ construct(s.get(), false);
+ }
+
+
+ //! Construct string from const std::wstring&
+ /*!
+ \param s
+ String to initialise bstr_t from.
+
+ \exception std::bad_alloc
+ On memory exhaustion std::bad_alloc is thrown.
+ */
+ bstr_t(const std::wstring& s) throw(std::bad_alloc)
+ {
+ construct(s.c_str(), s.length());
+ }
+
+ explicit bstr_t(size_type sz, wchar_t c) throw(std::bad_alloc)
+ {
+ str_ = impl::bad_alloc_check(::SysAllocStringLen(0, UINT(sz)));
+ std::fill(begin(), end(), c);
+ }
+
+ explicit bstr_t(size_type sz) throw(std::bad_alloc)
+ {
+ str_ = impl::bad_alloc_check(::SysAllocStringLen(0, UINT(sz)));
+ }
+
+ template
+ explicit bstr_t(IT first, IT last)
+ {
+ str_ = 0;
+ assign(first, last);
+ }
+
+ explicit bstr_t(const uuid_t& u, bool braces = false)
+ {
+ construct(u, braces);
+ }
+
+ private:
+ bstr_t(const wchar_t* s1, size_t l1, const wchar_t* s2, size_t l2) throw(std::bad_alloc)
+ {
+ construct(s1, l1, s2, l2);
+ }
+
+ public:
+ //! Destructor
+ /*!
+ Deletes the wrapped BSTR.
+ */
+ ~bstr_t() throw()
+ {
+ destroy();
+ }
+
+ //! Swap
+ void swap(bstr_t& x) throw()
+ {
+ std::swap(str_, x.str_);
+ }
+
+ //! Explicit conversion to const wchar_t*
+ const wchar_t* c_str() const throw()
+ { return impl::null_to_empty(str_); }
+
+ //! Explicit conversion to std::wstring
+ std::wstring w_str() const throw()
+ { return impl::null_to_empty(str_); }
+
+#ifdef _MBCS
+#ifndef COMET_NO_MBCS_WARNING
+#pragma message( "Warning: _MBCS is defined. bstr_t::s_str may return an std::string containing multibyte characters" )
+#endif
+#endif
+
+ //! Explicit conversion to std::string
+ std::string s_str() const
+ {
+ if (is_empty()) return std::string();
+
+ if (length() > static_cast(std::numeric_limits