/// <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);
        }
예제 #2
0
 /// <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,
예제 #9
0
 /// <inheritdoc/>
 public abstract AnalysisResult2 Analyze <T>(AnalysisSettings analysisSettings, SegmentSettings <T> segmentSettings);
예제 #10
0
 /// <inheritdoc/>
 public virtual void BeforeAnalyze(AnalysisSettings analysisSettings)
 {
     return; // noop
 }
예제 #11
0
 /// <inheritdoc/>
 public abstract void SummariseResults(AnalysisSettings settings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results);