/// <summary> /// Matlab-friendly factory method to run a single Epoch. /// </summary> /// <remarks>Constructs an Epoch with homogenous stimulus ID, sample rate and units, then runs the /// constructed Epoch. /// </remarks> /// /// <param name="protocolID">Protocol ID of the constructed Epoch</param> /// <param name="parameters">Protocol parameters of the constructed Epoch</param> /// <param name="stimulusID">Stimulus plugin ID for all constructed stimuli</param> /// <param name="stimulusSampleRate">Simulus sample rate for all constructed stimuli</param> /// <param name="stimuli">Simulus data for output devices</param> /// <param name="backgrounds">Backgrounds for output devices</param> /// <param name="responses">Devices from which to record Responses</param> /// <param name="persistor">EpochPersistor to persist Epoch</param> public void RunEpoch( string protocolID, IDictionary <string, object> parameters, string stimulusID, Measurement stimulusSampleRate, IDictionary <ExternalDeviceBase, IEnumerable <IMeasurement> > stimuli, IDictionary <ExternalDeviceBase, IMeasurement> backgrounds, IEnumerable <ExternalDeviceBase> responses, EpochPersistor persistor) { var epoch = new Epoch(protocolID, parameters); foreach (var dev in stimuli.Keys) { var data = new OutputData(stimuli[dev], stimulusSampleRate, true); var stim = new RenderedStimulus(stimulusID, (IDictionary <string, object>) new Dictionary <string, object> { { "data", data } }, (IOutputData)data); epoch.Stimuli[dev] = stim; } foreach (var dev in responses) { epoch.Responses[dev] = new Response(); } foreach (var dev in backgrounds.Keys) { epoch.Background[dev] = new Epoch.EpochBackground(backgrounds[dev], stimulusSampleRate); } RunEpoch(epoch, persistor); }
/// <summary> /// Begin a new Epoch Group (i.e. a logical block of Epochs). As each Epoch Group is persisted /// to a separate data file, this method creates the appropriate output file and /// EpochPersistor instance. /// </summary> /// <param name="path">The name of the file into which to store the epoch; if the name /// ends in ".xml", it will store the file using the EpochXMLPersistor, and if the name /// ends in ".hdf5", it will store the file using the EpochHDF5Persistor. This file will /// be overwritten if it already exists at this location.</param> /// <param name="epochGroupLabel">Label for the new Epoch Group</param> /// <param name="source">Identifier for EpochGroup's Source</param> /// <param name="keywords"></param> /// <param name="properties"></param> /// <returns>The EpochPersistor instance to be used for saving Epochs</returns> /// <see cref="RunEpoch"/> public EpochPersistor BeginEpochGroup(string path, string epochGroupLabel, string source, IEnumerable <string> keywords, IDictionary <string, object> properties) { EpochPersistor result = null; if (path.EndsWith(".xml")) { result = new EpochXMLPersistor(path); } else if (path.EndsWith(".hdf5")) { result = new EpochHDF5Persistor(path, null); } else { throw new ArgumentException(String.Format("{0} doesn't look like a legit Epoch filename", path)); } var kws = keywords == null ? new string[0] : keywords.ToArray(); var props = properties ?? new Dictionary <string, object>(); result.BeginEpochGroup(epochGroupLabel, source, kws, props, Guid.NewGuid(), DateTime.Now); return(result); }
private void RunSingleEpoch(double sampleRate, int nChannels, EpochPersistor epochPersistor) { Epoch e; IExternalDevice dev0; RenderedStimulus stim1; IExternalDevice dev1; RenderedStimulus stim2; IList<IMeasurement> stimData; var controller = SetupController(sampleRate, out e, out dev0, out stim1, out dev1, out stim2, out stimData, nChannels); controller.RunEpoch(e, epochPersistor); Assert.AreEqual((TimeSpan)stim1.Duration, e.Responses[dev0].Duration); if (nChannels > 1) Assert.AreEqual((TimeSpan)stim2.Duration, e.Responses[dev1].Duration); var inputData = e.Responses[dev0].Data; const double MAX_VOLTAGE_DIFF = 0.001; int failures = inputData.Select((t, i) => t.QuantityInBaseUnit - stimData[i].QuantityInBaseUnit) .Count(dif => Math.Abs(dif) > (decimal) MAX_VOLTAGE_DIFF); Assert.AreEqual(0, failures); }
private void SaveEpoch(EpochPersistor persistor, Epoch e) { persistor.Serialize(e); OnSavedEpoch(e); }
/// <summary> /// The core entry point for the Controller Facade; push an Epoch in here, and when the /// Epoch is finished processing, control will be returned to you. /// /// <para>In other words, this /// method is blocking--the Controller cannot run more than one Epoch at a time.</para> /// </summary> /// /// <param name="e">Single Epoch to present</param> /// <param name="persistor">EpochPersistor for saving the data. May be null to indicate epoch should not be persisted</param> /// <exception cref="ValidationException">Validation failed for this Controller</exception> public void RunEpoch(Epoch e, EpochPersistor persistor) { var cts = new CancellationTokenSource(); var cancellationToken = cts.Token; Task persistenceTask = null; if (!ValidateEpoch(e)) { throw new ArgumentException("Epoch is not valid"); } if (!Validate()) { throw new ValidationException(Validate()); } // Starting with this Epoch var cEpoch = CurrentEpoch; CurrentEpoch = e; EventHandler <TimeStampedEventArgs> nextRequested = (c, args) => { DAQController.RequestStop(); OnDiscardedEpoch(CurrentEpoch); }; bool epochPersisted = false; EventHandler <TimeStampedEpochEventArgs> inputReceived = (c, args) => { if (CurrentEpoch != null && CurrentEpoch.IsComplete) { log.Debug("Epoch complete. Requesting DAQController stop."); DAQController.RequestStop(); if (persistor != null && !epochPersisted) { Epoch completedEpoch = CurrentEpoch; persistenceTask = Task.Factory.StartNew(() => { log.DebugFormat("Saving completed Epoch ({0})...", completedEpoch.StartTime); SaveEpoch(persistor, completedEpoch); }, cancellationToken, TaskCreationOptions.PreferFairness, SerialTaskScheduler) .ContinueWith((task) => { cancellationToken.ThrowIfCancellationRequested(); if (task.IsFaulted && task.Exception != null) { throw task.Exception; } OnCompletedEpoch(completedEpoch); }, cancellationToken); epochPersisted = true; } } }; EventHandler <TimeStampedExceptionEventArgs> exceptionalStop = (daq, args) => { log.Debug( "Discarding epoch due to exception"); OnDiscardedEpoch(CurrentEpoch); throw new SymphonyControllerException( "DAQ Controller stopped", args.Exception); }; try { NextEpochRequested += nextRequested; ReceivedInputData += inputReceived; DAQController.ExceptionalStop += exceptionalStop; e.StartTime = Maybe <DateTimeOffset> .Some(this.Clock.Now); log.DebugFormat("Starting epoch: {0}", CurrentEpoch.ProtocolID); DAQController.Start(false); } finally { CurrentEpoch = cEpoch; NextEpochRequested -= nextRequested; ReceivedInputData -= inputReceived; DAQController.ExceptionalStop -= exceptionalStop; DAQController.WaitForInputTasks(); //Clear remaining input UnusedInputData.Clear(); } if (persistenceTask != null) { try { persistenceTask.Wait(); } catch (AggregateException ex) { log.ErrorFormat("An error occurred while saving Epoch: {0}", ex); throw new SymphonyControllerException("Unable to write Epoch data to persistor.", ex.Flatten()); } } }
/// <summary> /// Closes an Epoch Group. This method should be called after running all Epochs in the /// Epoch Group represented by EpochPersistor to give the persistor a chance to write any /// neceesary closing information. /// <para> /// Closes the persistor's file. /// </para> /// </summary> /// <param name="e">EpochPersistor representing the completed EpochGroup</param> /// <see cref="BeginEpochGroup"/> public void EndEpochGroup(EpochPersistor e) { e.EndEpochGroup(); e.Close(); }