/// <inheritdoc/> public object Clone() { AnalysisSettings deepClone = this.DeepClone <AnalysisSettings>(); Log.Trace("Instance Id of old: {0}, vs new {1}".Format2(this.InstanceId, deepClone.InstanceId)); return(deepClone); }
/// <summary> /// Initializes a new instance of the <see cref="AnalysisResult2"/> class. /// This is the standard result class for <c>IAnalyser2</c> results. /// </summary> /// <param name="settingsUsed"> /// Represents the settings used for the analysis. /// </param> /// <param name="segmentSettings">The settings for the segment that was analyzed.</param> /// <param name="durationAnalyzed"> /// Records the actual duration analyzed by the analysis. /// </param> public AnalysisResult2(AnalysisSettings settingsUsed, SegmentSettingsBase segmentSettings, TimeSpan durationAnalyzed) { this.SegmentSettings = segmentSettings; this.SettingsUsed = (AnalysisSettings)settingsUsed.Clone(); this.OutputFiles = new Dictionary <string, FileInfo>(); this.SegmentAudioDuration = durationAnalyzed; this.MiscellaneousResults = new Dictionary <string, object>(); this.SummaryIndices = new SummaryIndexBase[0]; this.SpectralIndices = new SpectralIndexBase[0]; this.Events = new EventBase[0]; }
internal SegmentSettingsBase(AnalysisSettings analysisSettings, FileSegment preparedFile, DirectoryInfo segmentTempDirectory, DirectoryInfo segmentOutputDirectory) { Contract.Requires <ArgumentNullException>(analysisSettings != null, $"{nameof(analysisSettings)} must not be null"); Contract.Requires <ArgumentNullException>(segmentOutputDirectory != null, $"{nameof(segmentOutputDirectory)} must not be null"); Contract.Requires <ArgumentNullException>(segmentTempDirectory != null, $"{nameof(segmentTempDirectory)} must not be null"); Contract.Requires <ArgumentNullException>(preparedFile != null, $"{nameof(preparedFile)} must not be null"); this.AnalysisSettings = analysisSettings; this.SegmentTempDirectory = segmentTempDirectory; this.SegmentOutputDirectory = segmentOutputDirectory; this.PreparedFile = preparedFile; string basename = Path.GetFileNameWithoutExtension(preparedFile.Source.Name); // if user requests, save the intermediate csv files // always save csv to output dir this.SegmentEventsFile = AnalysisResultPath(segmentOutputDirectory, basename, StandardEventsSuffix, "csv").ToFileInfo(); this.SegmentSummaryIndicesFile = AnalysisResultPath(segmentOutputDirectory, basename, StandardIndicesSuffix, "csv").ToFileInfo(); this.SegmentSpectrumIndicesDirectory = this.SegmentOutputDirectory; this.SegmentImageFile = AnalysisResultPath(segmentOutputDirectory, basename, "Image", "png").ToFileInfo(); }
private static void ValidateResult <T>( AnalysisSettings preAnalysisSettings, AnalysisResult2 result, SegmentSettings <T> segmentSettings, TimeSpan preparedFileDuration, bool parallelized) { Contract.Ensures( result.SettingsUsed != null, "The settings used in the analysis must be populated in the analysis result."); Contract.Ensures( result.SegmentStartOffset == segmentSettings.SegmentStartOffset, "The segment start offset of the result should match the start offset that it was instructed to analyze"); Contract.Ensures( Math.Abs((result.SegmentAudioDuration - preparedFileDuration).TotalMilliseconds) < 1.0, "The duration analyzed (reported by the analysis result) should be withing a millisecond of the provided audio file"); if (preAnalysisSettings.AnalysisImageSaveBehavior == SaveBehavior.Always || preAnalysisSettings.AnalysisImageSaveBehavior == SaveBehavior.WhenEventsDetected && result.Events.Length > 0) { Contract.Ensures( segmentSettings.SegmentImageFile.RefreshInfo().Exists, "If the analysis was instructed to produce an image file, then it should exist"); } Contract.Ensures( result.Events != null, "The Events array should never be null. No events should be represented by a zero length Events array."); if (result.Events.Length != 0 && preAnalysisSettings.AnalysisDataSaveBehavior) { Contract.Ensures( result.EventsFile.RefreshInfo().Exists, "If events were produced and an events file was expected, then the events file should exist"); } Contract.Ensures( result.SummaryIndices != null, "The SummaryIndices array should never be null. No SummaryIndices should be represented by a zero length SummaryIndices array."); if (result.SummaryIndices.Length != 0 && preAnalysisSettings.AnalysisDataSaveBehavior) { Contract.Ensures( result.SummaryIndicesFile.RefreshInfo().Exists, "If SummaryIndices were produced and an SummaryIndices file was expected, then the SummaryIndices file should exist"); } Contract.Ensures( result.SpectralIndices != null, "The SpectralIndices array should never be null. No SpectralIndices should be represented by a zero length SpectralIndices array."); if (result.SpectralIndices.Length != 0 && preAnalysisSettings.AnalysisDataSaveBehavior) { foreach (var spectraIndicesFile in result.SpectraIndicesFiles) { Contract.Ensures( spectraIndicesFile.RefreshInfo().Exists, "If SpectralIndices were produced and SpectralIndices files were expected, then the SpectralIndices files should exist"); } } foreach (var eventBase in result.Events) { Contract.Ensures( eventBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds, "Every event should be found within the bounds of the current segment. This error occurs when segmentStartOffset is not set correctly."); // ReSharper disable CompareOfFloatsByEqualityOperator Contract.Ensures( eventBase.EventStartSeconds == eventBase.ResultStartSeconds, "The relative EventStartSeconds should equal the seconds component of StartOffset"); // ReSharper restore CompareOfFloatsByEqualityOperator Contract.Ensures( Math.Abs(eventBase.SegmentStartSeconds - result.SegmentStartOffset.TotalSeconds) < 0.0001, "Segment start offsets must match"); Contract.Ensures( eventBase.SegmentDurationSeconds > 0.0, "eventBase.SegmentDurationSeconds must be greater than 0.0"); } foreach (var summaryIndexBase in result.SummaryIndices) { Contract.Ensures( summaryIndexBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds, "Every summary index generated by this analysis should be found within the bounds of the segment analyzed"); } foreach (var spectralIndexBase in result.SpectralIndices) { Contract.Ensures( spectralIndexBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds, "Every spectral index generated by this analysis should be found within the bounds of the segment analyzed"); } }
private static List <ISegment <TSource> > PrepareAnalysisSegments <TSource>( ISourcePreparer preparer, ISegment <TSource>[] segments, AnalysisSettings settings) { // ensure all segments are valid and have source metadata set double duration = 0; foreach (var segment in segments) { var segmentDuration = segment.EndOffsetSeconds - segment.StartOffsetSeconds; duration += segmentDuration; Contract.Requires <InvalidSegmentException>( segmentDuration > 0, $"Segment {segment} was invalid because end was less than start"); Contract.Requires <InvalidSegmentException>(segment.Source != null, $"Segment {segment} source was null"); Contract.Requires( segment.SourceMetadata.NotNull(), $"Segment {segment} must have metadata supplied."); // it should equal itself (because it is in the list) but should not equal anything else var matchingSegments = segments.Where(x => x.Equals(segment)).ToArray(); Contract.Requires <InvalidSegmentException>( matchingSegments.Length == 1, $"Supplied segment is a duplicate of another segment. Supplied:\n{segment}\nMatches\n: {string.Join("\n-----------\n", matchingSegments.Select(x => x.ToString()))}"); } Log.Info($"Analysis Coordinator will analyze a total of {duration} seconds of audio"); // split the provided segments up into processable chunks var analysisSegments = preparer.CalculateSegments(segments, settings).ToArray(); // ensure after splitting there are no identical segments // we use a a dictionary here because a HashSet does not support supplying an initial capacity (uggh) var postCutSegments = new List <ISegment <TSource> >(analysisSegments.Length); foreach (var analysisSegment in analysisSegments) { // check if the segment is too short... and if so, remove it var tooShort = analysisSegment.EndOffsetSeconds - analysisSegment.StartOffsetSeconds < settings.AnalysisMinSegmentDuration.TotalSeconds; if (tooShort) { Log.Warn("Analysis segment removed because it was too short " + $"(less than {settings.AnalysisMinSegmentDuration}): {analysisSegment}"); continue; } // ensure there are no identical segments (no use processing the same piece of audio twice // with the same analysis! // warning this is an O^2 operation. if (postCutSegments.Any(x => x.Equals(analysisSegment))) { Log.Warn($"A duplicate analysis segment was removed: {analysisSegment}"); continue; } postCutSegments.Add(analysisSegment); } return(postCutSegments); }
/// <summary> /// Analyze one or more file segments using the same analysis and settings. /// Note each segment could be sourced from separate original audio files! /// If using a remote source preparer the segments could even be downloaded from a remote source!. /// </summary> /// <param name="segments"> /// The file Segments. /// </param> /// <param name="analysis"> /// The analysis. /// </param> /// <param name="settings"> /// The settings. /// </param> /// <returns> /// The analysis results. /// </returns> public AnalysisResult2[] Run <TSource>( ISegment <TSource>[] segments, IAnalyser2 analysis, AnalysisSettings settings) { Contract.Requires(settings != null, "Settings must not be null."); Contract.Requires(analysis != null, "Analysis must not be null."); Contract.Requires(segments != null, "File Segments must not be null."); // do not allow the program to continue // if there are no possible segments to process because the original file // is too short. var tooShort = segments .FirstOrDefault(segment => segment.SourceMetadata.Duration < settings.AnalysisMinSegmentDuration); if (tooShort != null) { Log.Fatal("Provided audio recording is too short too analyze!"); throw new AudioRecordingTooShortException( "{0} is too short to analyze with current analysisSettings.AnalysisMinSegmentDuration ({1})" .Format2(tooShort.Source, settings.AnalysisMinSegmentDuration)); } // ensure output directory exists Contract.Requires( settings.AnalysisOutputDirectory.TryCreate(), $"Attempt to create AnalysisOutputDirectory failed: {settings.AnalysisOutputDirectory}"); // try and create temp directory (returns true if already exists) if (!settings.IsAnalysisTempDirectoryValid) { // ensure a temp directory is always set Log.Warn( "No temporary directory provided, using random directory: " + settings.AnalysisTempDirectoryFallback); } // calculate the sub-segments of the given file segments that match what the analysis expects. var analysisSegments = PrepareAnalysisSegments(this.SourcePreparer, segments, settings); // Execute a pre analyzer hook Log.Info("Executing BeforeAnalyze"); analysis.BeforeAnalyze(settings); Log.Debug("Completed BeforeAnalyze"); AnalysisResult2[] results; // clone analysis settings for parallelism concerns: // - as each iteration modifies settings. This causes hard to track down bugs // clones are made for sequential runs to to ensure consistency var settingsForThisItem = (AnalysisSettings)settings.Clone(); Log.Info($"Analysis started in {(this.IsParallel ? "parallel" : "sequence")}."); var stopwatch = new Stopwatch(); stopwatch.Start(); // Analyze the sub-segments in parallel or sequentially (IsParallel property), // Create and delete directories and/or files as indicated by properties // DeleteFinished and UniqueDirectoryPerSegment if (this.IsParallel) { results = this.RunParallel(analysisSegments, analysis, settingsForThisItem); Array.Sort(results); } else { // sequential results = this.RunSequential(analysisSegments, analysis, settingsForThisItem); } stopwatch.Stop(); Log.Info($"Analysis complete, took {stopwatch.Elapsed}."); // TODO: execute SummariseResults hook here eventually // delete temp directories // only delete directory if we are using one that was created specifically for this analysis settings.AnalysisTempDirectoryFallback.TryDelete(true, $"Item {settings.InstanceId}"); return(results); }
/// <summary> /// Analyze one file segment using the analysis and settings. /// </summary> /// <param name="segment"> /// The file Segment. /// </param> /// <param name="analysis"> /// The analysis. /// </param> /// <param name="settings"> /// The settings. /// </param> /// <returns> /// The analysis results. /// </returns> public AnalysisResult2[] Run <TSource>(ISegment <TSource> segment, IAnalyser2 analysis, AnalysisSettings settings) { return(this.Run(new[] { segment }, analysis, settings)); }
public SegmentSettings( AnalysisSettings analysisSettings, ISegment <TSegment> segment, (DirectoryInfo Output, DirectoryInfo Temp) dirs,
/// <inheritdoc/> public abstract AnalysisResult2 Analyze <T>(AnalysisSettings analysisSettings, SegmentSettings <T> segmentSettings);
/// <inheritdoc/> public virtual void BeforeAnalyze(AnalysisSettings analysisSettings) { return; // noop }
/// <inheritdoc/> public abstract void SummariseResults(AnalysisSettings settings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results);