From d871ccb7e47b38ce51522982a4112b34e5048eeb Mon Sep 17 00:00:00 2001 From: Philip-S-Martin Date: Fri, 2 Apr 2021 01:13:29 -0500 Subject: [PATCH] 2021.2 New Features: - Video files labeled - New numeric prompt type - Prompt text is now definable Fixes: - Can run without camera - Can run without microphone --- InstallForge/2021_1_InstallProject.ifp | Bin 6830 -> 7249 bytes McIntyreAFC/SpreadsheetAFC.cs | 26 ++++++--- ProtocolMasterCore/Prompt/DefaultPrompts.cs | 7 ++- .../Prompt/IPromptUserNumber.cs | 8 +++ .../Prompt/IPromptUserSelect.cs | 2 +- .../Prompt/PromptTargetStore.cs | 3 ++ .../Protocol/ExtensionManager.cs | 4 ++ .../Protocol/Interpreter/IInterpreter.cs | 3 +- .../Interpreter/InterpreterManager.cs | 6 ++- .../NullExtensions/NullInterpreter.cs | 3 ++ ProtocolMasterWPF/Helpers/ClockAnimator.cs | 2 +- ProtocolMasterWPF/Model/Camera.cs | 51 +++++++++--------- ProtocolMasterWPF/Model/CameraContainer.cs | 29 +++++++--- ProtocolMasterWPF/Model/LocalFileStreamer.cs | 4 +- ProtocolMasterWPF/Model/Session.cs | 17 +++--- ProtocolMasterWPF/View/DropdownDialog.xaml | 3 +- ProtocolMasterWPF/View/DropdownDialog.xaml.cs | 30 ++++++++--- ProtocolMasterWPF/View/SessionView.xaml.cs | 2 + ProtocolMasterWPF/View/TimelineView.xaml.cs | 2 +- .../ViewModel/CameraViewModel.cs | 14 +++-- 20 files changed, 152 insertions(+), 64 deletions(-) create mode 100644 ProtocolMasterCore/Prompt/IPromptUserNumber.cs diff --git a/InstallForge/2021_1_InstallProject.ifp b/InstallForge/2021_1_InstallProject.ifp index 2d5a8835eed24a02469592eb08d0761e82ec8316..b741da46393fced1d189913c00827985a3a210e5 100644 GIT binary patch delta 1781 zcmZ2ydeLHoD2r-verZv1YD_>zW=>{-Zm_OzVo^zEUQ9qyeo20EevWTqaY<^?AbY{V-~f(`*bJ_qOgqEx+N1#c%_E-(+6Ux?3haDxg`l6bic&Gan627!4b8l+E>`-LaZ=VLQ9r@-MtlSTN& zNwQPO8tgAqQ$1s{eJM1#i(i{GTY)+ zKv1U$?2XhL=@}$H4&|0m(m$!*~999E>w@ETrIunH-IaY#xFEpPWf$#qAP9H*}q$ z|15gwZi{RIyg|e{UvT9oHN7PNr^LlZG2bY_2fKiU0k{nzH$NUmq;|MfxDHFB>pR>g z#3OS!_z;=K!4!wsd51g3r7iP_TGcJVJ&zce>RtpvK4!0 G&h-h2*cUMX diff --git a/McIntyreAFC/SpreadsheetAFC.cs b/McIntyreAFC/SpreadsheetAFC.cs index 7885960..a12366c 100644 --- a/McIntyreAFC/SpreadsheetAFC.cs +++ b/McIntyreAFC/SpreadsheetAFC.cs @@ -5,18 +5,20 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace McIntyreAFC { [InterpreterMeta("SpreadsheetAFC", "1.1")] - public class SpreadsheetAFC : ExcelDataInterpreter, IInterpreter, IPromptUserSelect + public class SpreadsheetAFC : ExcelDataInterpreter, IInterpreter, IPromptUserSelect, IPromptUserNumber { private delegate bool RowReader(Dictionary map); Dictionary mappedRowReaders; Dictionary protocols; Protocol baseData; public UserSelectHandler UserSelectPrompt { get; set; } + public UserNumberHandler UserNumberPrompt { get; set; } public SpreadsheetAFC() { mappedRowReaders = new Dictionary(){ @@ -29,6 +31,12 @@ public SpreadsheetAFC() protocols = new Dictionary(); } public bool IsCanceled { get; set; } + + string protocolName = null; + string experimentName = null; + int subjectNumber = 0; + public string ProtocolLabel => $"{(experimentName != null ? experimentName : "NoExperiment")}_{(protocolName != null ? protocolName : "NoProtocol")}_{subjectNumber:D2}"; + private Protocol GetOrCreateProtocol(string protocolName) { Protocol result; @@ -42,8 +50,9 @@ private Protocol GetOrCreateProtocol(string protocolName) return result; } } - public List Generate(string protocolName = null) + public List Generate(string argument) { + experimentName = new Regex("\\(\\d+\\)|_| ").Replace( argument, ""); if (DataReader == null) return null; do { @@ -73,14 +82,19 @@ public List Generate(string protocolName = null) { if (protocolName == null) { - string dropdownResponse = UserSelectPrompt(protocols.Keys.ToArray()); - if (dropdownResponse != null) - return protocols[dropdownResponse].Generate(); + protocolName = UserSelectPrompt(protocols.Keys.ToArray(), "Select a Protocol:"); + if (protocolName != null) + { + subjectNumber = UserNumberPrompt(1, 99, "Select a Subject:"); + return protocols[protocolName].Generate(); + } else return null; } else { - return protocols[protocolName].Generate(); + { + return protocols[protocolName].Generate(); + } } } else return null; diff --git a/ProtocolMasterCore/Prompt/DefaultPrompts.cs b/ProtocolMasterCore/Prompt/DefaultPrompts.cs index 9c572fb..f9a947f 100644 --- a/ProtocolMasterCore/Prompt/DefaultPrompts.cs +++ b/ProtocolMasterCore/Prompt/DefaultPrompts.cs @@ -2,11 +2,16 @@ { public static class DefaultPrompts { - public static string UserSelect(string[] options) + public static string UserSelect(string[] options, string prompt) { if (options != null && options.Length > 0 && options[0] != null) return options[0]; return "null"; } + + public static int UserNumber(int min, int max, string prompt) + { + return min; + } } } diff --git a/ProtocolMasterCore/Prompt/IPromptUserNumber.cs b/ProtocolMasterCore/Prompt/IPromptUserNumber.cs new file mode 100644 index 0000000..c38ad6b --- /dev/null +++ b/ProtocolMasterCore/Prompt/IPromptUserNumber.cs @@ -0,0 +1,8 @@ +namespace ProtocolMasterCore.Prompt +{ + public delegate int UserNumberHandler(int min, int max, string prompt = "Please select a number:"); + public interface IPromptUserNumber + { + public UserNumberHandler UserNumberPrompt { set; } + } +} diff --git a/ProtocolMasterCore/Prompt/IPromptUserSelect.cs b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs index 2dd4334..a1c333d 100644 --- a/ProtocolMasterCore/Prompt/IPromptUserSelect.cs +++ b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs @@ -1,6 +1,6 @@ namespace ProtocolMasterCore.Prompt { - public delegate string UserSelectHandler(string[] keys); + public delegate string UserSelectHandler(string[] keys, string prompt = "Please select an item:"); public interface IPromptUserSelect { public UserSelectHandler UserSelectPrompt { set; } diff --git a/ProtocolMasterCore/Prompt/PromptTargetStore.cs b/ProtocolMasterCore/Prompt/PromptTargetStore.cs index a5ffee1..e2bacbe 100644 --- a/ProtocolMasterCore/Prompt/PromptTargetStore.cs +++ b/ProtocolMasterCore/Prompt/PromptTargetStore.cs @@ -3,9 +3,12 @@ public class PromptTargetStore { public UserSelectHandler UserSelect { get; set; } + public UserNumberHandler UserNumber { get; set; } + public PromptTargetStore() { UserSelect = DefaultPrompts.UserSelect; + UserNumber = DefaultPrompts.UserNumber; } } } diff --git a/ProtocolMasterCore/Protocol/ExtensionManager.cs b/ProtocolMasterCore/Protocol/ExtensionManager.cs index b7f498d..5d8fa1b 100644 --- a/ProtocolMasterCore/Protocol/ExtensionManager.cs +++ b/ProtocolMasterCore/Protocol/ExtensionManager.cs @@ -82,6 +82,10 @@ protected E CreateSelectedExtension() { (extension as IPromptUserSelect).UserSelectPrompt = PromptTargets.UserSelect; } + if (typeof(IPromptUserNumber).IsAssignableFrom(extension.GetType())) + { + (extension as IPromptUserNumber).UserNumberPrompt = PromptTargets.UserNumber; + } return extension; } else diff --git a/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs b/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs index 7694210..d6fb4b7 100644 --- a/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs +++ b/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs @@ -4,6 +4,7 @@ namespace ProtocolMasterCore.Protocol.Interpreter { public interface IInterpreter : IExtension { - List Generate(string protocolName); + string ProtocolLabel { get; } + List Generate(string argument); } } diff --git a/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs index cb09088..47b9a05 100644 --- a/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs +++ b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs @@ -4,7 +4,7 @@ namespace ProtocolMasterCore.Protocol.Interpreter { - public delegate void ProtocolEventsLoader(List events); + public delegate void ProtocolEventsLoader(List events, string label); public class InterpreterManager : ExtensionManager { IInterpreter interpreter; @@ -18,14 +18,16 @@ internal List GenerateData(Stream stream, string argument = null) System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); ExcelDataInterpreter spreadSheetInterpreter = interpreter as ExcelDataInterpreter; if (stream != null) + { spreadSheetInterpreter.SetReader(ExcelReaderFactory.CreateReader(stream)); + } else return null; } // pre-fill event data List result = interpreter.Generate(argument); if (interpreter.IsCanceled) return null; DisposeSelectedExtension(); - OnEventsLoaded?.Invoke(result); + OnEventsLoaded?.Invoke(result, interpreter.ProtocolLabel); return result; } } diff --git a/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs index fd0b03e..f99188f 100644 --- a/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs +++ b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs @@ -7,6 +7,9 @@ namespace ProtocolMasterCore.Protocol.NullExtensions public class NullInterpreter : IInterpreter { public bool IsCanceled { get; set; } + + public string ProtocolLabel => "No Protocol"; + public List Generate(string protocolName) { return null; diff --git a/ProtocolMasterWPF/Helpers/ClockAnimator.cs b/ProtocolMasterWPF/Helpers/ClockAnimator.cs index 224a17e..0bb582f 100644 --- a/ProtocolMasterWPF/Helpers/ClockAnimator.cs +++ b/ProtocolMasterWPF/Helpers/ClockAnimator.cs @@ -23,7 +23,7 @@ public ClockAnimator() { PrepAnimator(); } - public void FindMaxTime(List events) + public void FindMaxTime(List events, string label) { if (events == null) return; double milliseconds = events.Where(i => i.Arguments.ContainsKey("TimeEndMs")).Max(i => Convert.ToDouble( i.Arguments["TimeEndMs"])); diff --git a/ProtocolMasterWPF/Model/Camera.cs b/ProtocolMasterWPF/Model/Camera.cs index 184bd5a..d94ec27 100644 --- a/ProtocolMasterWPF/Model/Camera.cs +++ b/ProtocolMasterWPF/Model/Camera.cs @@ -32,35 +32,38 @@ private async void InitVideoStore() } public void InitializeCap(DeviceInformation videoDevice, DeviceInformation audioDevice) { - if(videoDevice == null) + if (videoDevice == null) Log.Error($"Video Device null"); - if (audioDevice == null) - { - try - { - MediaCap.InitializeAsync(new MediaCaptureInitializationSettings() - { - VideoDeviceId = videoDevice.Id - }).AsTask().Wait(); - } - catch (UnauthorizedAccessException ex) - { - Log.Error($"The app was denied access to the camera: {ex}"); - } - } else { - try + if (audioDevice == null) { - MediaCap.InitializeAsync(new MediaCaptureInitializationSettings() + try { - VideoDeviceId = videoDevice.Id, - AudioDeviceId = audioDevice.Id - }).AsTask().Wait(); + MediaCap.InitializeAsync(new MediaCaptureInitializationSettings() + { + VideoDeviceId = videoDevice.Id + }).AsTask().Wait(); + } + catch (UnauthorizedAccessException ex) + { + Log.Error($"The app was denied access to the camera: {ex}"); + } } - catch (UnauthorizedAccessException ex) + else { - Log.Error($"The app was denied access to the camera: {ex}"); + try + { + MediaCap.InitializeAsync(new MediaCaptureInitializationSettings() + { + VideoDeviceId = videoDevice.Id, + AudioDeviceId = audioDevice.Id + }).AsTask().Wait(); + } + catch (UnauthorizedAccessException ex) + { + Log.Error($"The app was denied access to the camera: {ex}"); + } } } } @@ -76,11 +79,11 @@ public async void StartPreview() } } public bool IsRecording { get; private set; } - public async void StartRecord() + public async void StartRecord(string label) { try { - StorageFile file = await videoStore.CreateFileAsync("Recording (1).wmv", CreationCollisionOption.GenerateUniqueName); + StorageFile file = await videoStore.CreateFileAsync($"{label}.wmv", CreationCollisionOption.GenerateUniqueName); await MediaCap.StartRecordToStorageFileAsync(MediaEncodingProfile.CreateWmv(VideoEncodingQuality.Auto), file); IsRecording = true; Log.Error("Began Recording"); diff --git a/ProtocolMasterWPF/Model/CameraContainer.cs b/ProtocolMasterWPF/Model/CameraContainer.cs index a51e9c5..2fd61ad 100644 --- a/ProtocolMasterWPF/Model/CameraContainer.cs +++ b/ProtocolMasterWPF/Model/CameraContainer.cs @@ -1,4 +1,5 @@ -using ProtocolMasterCore.Utility; +using ProtocolMasterCore.Protocol; +using ProtocolMasterCore.Utility; using ProtocolMasterWPF.Properties; using System; using System.Collections.Generic; @@ -34,7 +35,7 @@ public DeviceInformation VideoDevice _videoDevice = value; NotifyProperty(); ResetCam(); - if (value.Id != Settings.Default.CameraID) + if (value != null && value.Id != Settings.Default.CameraID) { Settings.Default.CameraID = value.Id; Settings.Default.Save(); @@ -50,7 +51,7 @@ public DeviceInformation AudioDevice _audioDevice = value; NotifyProperty(); ResetCam(); - if(value.Id != Settings.Default.MicrophoneID) + if (value != null && value.Id != Settings.Default.MicrophoneID) { Settings.Default.MicrophoneID = value.Id; Settings.Default.Save(); @@ -58,7 +59,19 @@ public DeviceInformation AudioDevice } } private DeviceInformation _audioDevice; - public void StartRecord() => Cam.StartRecord(); + + string label = "Recording"; + public void SetLabel(List events, string label) + { + if (label != null) + this.label = label; + else this.label = "Recording"; + } + public void StartRecord() + { + if(VideoDevice != null) + Cam.StartRecord(label); + } public void StopRecord() => Cam.StopRecord(); private void ResetCam() @@ -75,7 +88,9 @@ public void InitDefaultDevices() catch (Exception e) { Log.Error($"Default Camera could not be seleted, exception: {e}"); - VideoDevice = MediaDevices.Instance.VideoDevices.First(); + if (MediaDevices.Instance.VideoDevices.Count != 0) + VideoDevice = MediaDevices.Instance.VideoDevices.First(); + else VideoDevice = null; } try { @@ -84,7 +99,9 @@ public void InitDefaultDevices() catch (Exception e) { Log.Error($"Default Microphone could not be seleted, exception: {e}"); - AudioDevice = MediaDevices.Instance.AudioDevices.First(); + if (MediaDevices.Instance.AudioDevices.Count != 0) + AudioDevice = MediaDevices.Instance.AudioDevices.First(); + else AudioDevice = null; } } } diff --git a/ProtocolMasterWPF/Model/LocalFileStreamer.cs b/ProtocolMasterWPF/Model/LocalFileStreamer.cs index 2d8beba..4a8b439 100644 --- a/ProtocolMasterWPF/Model/LocalFileStreamer.cs +++ b/ProtocolMasterWPF/Model/LocalFileStreamer.cs @@ -8,7 +8,7 @@ namespace ProtocolMasterWPF.Model { internal class LocalFileStreamer : IStreamStarter { - public string Name { get => LocalFile.Name; } + public string Name { get => Path.GetFileNameWithoutExtension(LocalFile.FullName); } public FileInfo LocalFile { get; private set; } public LocalFileStreamer(FileInfo source) { @@ -21,7 +21,7 @@ public Stream StartStream() { result = LocalFile.Open(FileMode.Open); } - catch(Exception e) + catch (Exception e) { Log.Error($"Could not open Local File {Name}, Exception: {e}"); result = null; diff --git a/ProtocolMasterWPF/Model/Session.cs b/ProtocolMasterWPF/Model/Session.cs index 3e26241..36a73eb 100644 --- a/ProtocolMasterWPF/Model/Session.cs +++ b/ProtocolMasterWPF/Model/Session.cs @@ -103,7 +103,8 @@ public Session() Cam = new CameraContainer(); - OnStart += Cam.StartRecord; + Protocol.InterpreterManager.OnEventsLoaded += Cam.SetLabel; + Protocol.DriverManager.OnProtocolStart += Cam.StartRecord; OnStop += Cam.StopRecord; } public void InitDefaultExtensions() @@ -161,7 +162,7 @@ public void Start(bool overrideCheck = false) { Protocol.Cancel(); }); - Protocol.Interpret(Selection.StartStream()); + Protocol.Interpret(Selection.StartStream(), Selection.ToString()); if (!CancelToken.IsCancellationRequested) { OnRun?.Invoke(); @@ -172,7 +173,7 @@ public void Start(bool overrideCheck = false) }, CancelSource.Token); SessionTask.Start(); } - else throw new Exception($"Cannot start in state {State.ToString()}"); + else throw new Exception($"Cannot start in state {State}"); } public void Stop(bool overrideCheck = false) { @@ -182,7 +183,7 @@ public void Stop(bool overrideCheck = false) OnStop?.Invoke(); State = SessionState.Viewing; } - else throw new Exception($"Cannot stop in state {State.ToString()}"); + else throw new Exception($"Cannot stop in state {State}"); } public void Reset(bool overrideCheck = false) { @@ -195,7 +196,7 @@ public void Reset(bool overrideCheck = false) else State = SessionState.NotReady; } - else throw new Exception($"Cannot reset in state {State.ToString()}"); + else throw new Exception($"Cannot reset in state {State}"); } public void Preview(bool overrideCheck = false) { @@ -209,13 +210,13 @@ public void Preview(bool overrideCheck = false) { Protocol.Cancel(); }); - Protocol.Interpret(Selection.StartStream()); + Protocol.Interpret(Selection.StartStream(), Selection.ToString()); }, CancelSource.Token); SessionTask.Start(); OnPreview?.Invoke(); State = SessionState.Viewing; } - else throw new Exception($"Cannot preview in state {State.ToString()}"); + else throw new Exception($"Cannot preview in state {State}"); } public void OpenSelection(bool overrideCheck = false) { @@ -223,7 +224,7 @@ public void OpenSelection(bool overrideCheck = false) { State = SessionState.Selecting; } - else throw new Exception($"Cannot open selection in state {State.ToString()}"); + else throw new Exception($"Cannot open selection in state {State}"); } public void MakeSelection(object select) { diff --git a/ProtocolMasterWPF/View/DropdownDialog.xaml b/ProtocolMasterWPF/View/DropdownDialog.xaml index 475394a..62b8d09 100644 --- a/ProtocolMasterWPF/View/DropdownDialog.xaml +++ b/ProtocolMasterWPF/View/DropdownDialog.xaml @@ -13,12 +13,13 @@ + VerticalAlignment="Stretch" MinWidth="200" MaxHeight="400"/>