//public static void ReadPCF(string protoCfgDir, Dictionary<string, string> settings)
        public static void ReadPCF(string protoCfgDir)
        {
            LoggedConsole.WriteLine("\nStarting static method: HMMBuilder.HMMSettings.ReadPCF()");
            //read configuration for each file in protoConfigs directory
            LoggedConsole.WriteLine(" Read prototype configuration (.pcf) files in directory: " + protoCfgDir);

            string pcfWildCard = "*.pcf";

            int maxStates = int.MinValue;

            StreamReader objReader = null;

            try
            {
                DirectoryInfo Dir      = new DirectoryInfo(protoCfgDir);
                FileInfo[]    FileList = Dir.GetFiles(pcfWildCard, SearchOption.TopDirectoryOnly);

                string txtLine = "";
                foreach (FileInfo FI in FileList)
                {
                    objReader = new StreamReader(FI.FullName);
                    Dictionary <string, string> confProto = new Dictionary <string, string>();

                    while ((txtLine = objReader.ReadLine()) != null)
                    {
                        if (Regex.IsMatch(txtLine.ToUpper(), @"<ENDSYS_SETUP>"))
                        {
                            validData = false;
                        }

                        if (validData)
                        {
                            if (Regex.IsMatch(txtLine, @"^\s*$")) //take off white lines
                            {
                                continue;
                            }

                            string[] param = Regex.Split(txtLine, separator);
                            Match    m     = Regex.Match(param[0].ToUpper(), @"\b\w+\b");
                            parameter = m.ToString();

                            if (parameter.Equals("NSTATES"))
                            {
                                if (int.Parse(param[1]) > maxStates)
                                {
                                    maxStates = int.Parse(param[1]);
                                }
                            }

                            string value = "";
                            if (confProto.TryGetValue(parameter, out value))
                            {
                                confProto[parameter] = param[1];
                            }
                            else
                            {
                                confProto.Add(parameter, param[1]);
                            }
                        }

                        if (Regex.IsMatch(txtLine.ToUpper(), @"<BEGINSYS_SETUP>"))
                        {
                            validData = true;
                        }
                    }
                    if (objReader != null)
                    {
                        objReader.Close();
                    }

                    //print config file
                    LoggedConsole.WriteLine("===========================");
                    LoggedConsole.WriteLine("Configuration for {0}", Path.GetFileNameWithoutExtension(FI.FullName));
                    LoggedConsole.WriteLine("===========================");
                    LoggedConsole.WriteLine("{0,-18}{1,-1}", "Parameter", "Value");
                    LoggedConsole.WriteLine("-----------------------");
                    foreach (KeyValuePair <string, string> pair in confProto)
                    {
                        LoggedConsole.WriteLine("{0,-18}{1,-1:D}", pair.Key, pair.Value);
                    }

                    confProtoDict.Add(Path.GetFileNameWithoutExtension(FI.FullName), confProto);
                } //end FOREACH PCF file in the protoConfig directory

                //makes sure that 'proto', if exsists, contains the biggest number of states among all pcf files
                if (!confProtoDict.ContainsKey("proto"))
                {
                    throw new Exception("Prototype file 'proto' not found in '" + protoCfgDir + "'.");
                }
                confProtoDict["proto"]["NSTATES"] = maxStates.ToString();
            }
            catch (Exception e)
            {
                LoggedConsole.WriteLine(e);
                throw (e);
            }

            //LoggedConsole.Write("\nPress ENTER key to continue:");
            //Console.ReadLine();
        } //end METHOD ReadPCF() to read and train prototype configurations
        /// <summary>
        /// This entrypoint should be used for testing short files (less than 2 minutes)
        /// </summary>
        public static void Execute(Arguments arguments)
        {
            MainEntry.WarnIfDeveloperEntryUsed("EventRecognizer entry does not do any audio maniuplation.");
            Log.Info("Running event recognizer");

            var sourceAudio     = arguments.Source;
            var configFile      = arguments.Config.ToFileInfo();
            var outputDirectory = arguments.Output;

            if (configFile == null)
            {
                throw new FileNotFoundException("No config file argument provided");
            }
            else if (!configFile.Exists)
            {
                Log.Warn($"Config file {configFile.FullName} not found... attempting to resolve config file");
                configFile = ConfigFile.Resolve(configFile.Name, Directory.GetCurrentDirectory().ToDirectoryInfo());
            }

            LoggedConsole.WriteLine("# Recording file:      " + sourceAudio.FullName);
            LoggedConsole.WriteLine("# Configuration file:  " + configFile);
            LoggedConsole.WriteLine("# Output folder:       " + outputDirectory);

            // find an appropriate event IAnalyzer
            IAnalyser2 recognizer = AnalyseLongRecording.FindAndCheckAnalyzer <IEventRecognizer>(
                arguments.AnalysisIdentifier,
                configFile.Name);

            Log.Info("Attempting to run recognizer: " + recognizer.Identifier);

            Log.Info("Reading configuration file");
            Config configuration = ConfigFile.Deserialize <RecognizerBase.RecognizerConfig>(configFile);

            // get default settings
            AnalysisSettings analysisSettings = recognizer.DefaultSettings;

            // convert arguments to analysis settings
            analysisSettings = arguments.ToAnalysisSettings(
                analysisSettings,
                outputIntermediate: true,
                resultSubDirectory: recognizer.Identifier,
                configuration: configuration);

            // Enable this if you want the Config file ResampleRate parameter to work.
            // Generally however the ResampleRate should remain at 22050Hz for all recognizers.
            //analysisSettings.AnalysisTargetSampleRate = (int) configuration[AnalysisKeys.ResampleRate];

            // get transform input audio file - if needed
            Log.Info("Querying source audio file");
            var audioUtilityRequest = new AudioUtilityRequest()
            {
                TargetSampleRate = analysisSettings.AnalysisTargetSampleRate,
            };
            var preparedFile = AudioFilePreparer.PrepareFile(
                outputDirectory,
                sourceAudio,
                MediaTypes.MediaTypeWav,
                audioUtilityRequest,
                outputDirectory);

            var source          = preparedFile.SourceInfo.ToSegment();
            var prepared        = preparedFile.TargetInfo.ToSegment(FileSegment.FileDateBehavior.None);
            var segmentSettings = new SegmentSettings <FileInfo>(
                analysisSettings,
                source,
                (analysisSettings.AnalysisOutputDirectory, analysisSettings.AnalysisTempDirectory),
                prepared);

            if (preparedFile.TargetInfo.SampleRate.Value != analysisSettings.AnalysisTargetSampleRate)
            {
                Log.Warn("Input audio sample rate does not match target sample rate");
            }

            // Execute a pre analyzer hook
            recognizer.BeforeAnalyze(analysisSettings);

            // execute actual analysis - output data will be written
            Log.Info("Running recognizer: " + recognizer.Identifier);
            AnalysisResult2 results = recognizer.Analyze(analysisSettings, segmentSettings);

            // run summarize code - output data can be written
            Log.Info("Running recognizer summary: " + recognizer.Identifier);
            recognizer.SummariseResults(
                analysisSettings,
                source,
                results.Events,
                results.SummaryIndices,
                results.SpectralIndices,
                new[] { results });

            //Log.Info("Recognizer run, saving extra results");
            // TODO: Michael, output anything else as you wish.

            Log.Debug("Clean up temporary files");
            if (source.Source.FullName != prepared.Source.FullName)
            {
                prepared.Source.Delete();
            }

            int eventCount = results?.Events?.Length ?? 0;

            Log.Info($"Number of detected events: {eventCount}");
            Log.Success(recognizer.Identifier + " recognizer has completed");
        }
        public static Image <Rgb24> DrawDistanceSpectrogram(LDSpectrogramRGB cs1, LDSpectrogramRGB cs2)
        {
            string[] keys = cs1.ColorMap.Split('-');

            string key = keys[0];

            double[,] m1Red = cs1.GetNormalisedSpectrogramMatrix(key);
            IndexDistributions.SpectralStats stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Red);
            cs1.IndexStats.Add(key, stats);
            m1Red = MatrixTools.Matrix2ZScores(m1Red, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("1.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            key             = keys[1];
            double[,] m1Grn = cs1.GetNormalisedSpectrogramMatrix(key);
            stats           = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Grn);
            cs1.IndexStats.Add(key, stats);
            m1Grn = MatrixTools.Matrix2ZScores(m1Grn, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("1.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            key             = keys[2];
            double[,] m1Blu = cs1.GetNormalisedSpectrogramMatrix(key);
            stats           = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Blu);
            cs1.IndexStats.Add(key, stats);
            m1Blu = MatrixTools.Matrix2ZScores(m1Blu, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("1.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            key             = keys[0];
            double[,] m2Red = cs2.GetNormalisedSpectrogramMatrix(key);
            stats           = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Red);
            cs2.IndexStats.Add(key, stats);
            m2Red = MatrixTools.Matrix2ZScores(m2Red, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("2.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            key             = keys[1];
            double[,] m2Grn = cs2.GetNormalisedSpectrogramMatrix(key);
            stats           = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Grn);
            cs2.IndexStats.Add(key, stats);
            m2Grn = MatrixTools.Matrix2ZScores(m2Grn, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("2.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            key             = keys[2];
            double[,] m2Blu = cs2.GetNormalisedSpectrogramMatrix(key);
            stats           = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Blu);
            cs2.IndexStats.Add(key, stats);
            m2Blu = MatrixTools.Matrix2ZScores(m2Blu, stats.Mode, stats.StandardDeviation);

            ////LoggedConsole.WriteLine("2.{0}: Min={1:f2}   Max={2:f2}    Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]);
            var v1 = new double[3];

            double[] mode1 =
            {
                cs1.IndexStats[keys[0]].Mode, cs1.IndexStats[keys[1]].Mode,
                cs1.IndexStats[keys[2]].Mode,
            };
            double[] stDv1 =
            {
                cs1.IndexStats[keys[0]].StandardDeviation, cs1.IndexStats[keys[1]].StandardDeviation,
                cs1.IndexStats[keys[2]].StandardDeviation,
            };
            LoggedConsole.WriteLine(
                "1: avACI={0:f3}+/-{1:f3};   avTEN={2:f3}+/-{3:f3};   avCVR={4:f3}+/-{5:f3}",
                mode1[0],
                stDv1[0],
                mode1[1],
                stDv1[1],
                mode1[2],
                stDv1[2]);

            var v2 = new double[3];

            double[] mode2 =
            {
                cs2.IndexStats[keys[0]].Mode, cs2.IndexStats[keys[1]].Mode,
                cs2.IndexStats[keys[2]].Mode,
            };
            double[] stDv2 =
            {
                cs2.IndexStats[keys[0]].StandardDeviation, cs2.IndexStats[keys[1]].StandardDeviation,
                cs2.IndexStats[keys[2]].StandardDeviation,
            };
            LoggedConsole.WriteLine(
                "2: avACI={0:f3}+/-{1:f3};   avTEN={2:f3}+/-{3:f3};   avCVR={4:f3}+/-{5:f3}",
                mode2[0],
                stDv2[0],
                mode2[1],
                stDv2[1],
                mode2[2],
                stDv2[2]);

            // assume all matrices are normalised and of the same dimensions
            int rows      = m1Red.GetLength(0); // number of rows
            int cols      = m1Red.GetLength(1); // number
            var d12Matrix = new double[rows, cols];
            var d11Matrix = new double[rows, cols];
            var d22Matrix = new double[rows, cols];

            for (int row = 0; row < rows; row++)
            {
                for (int col = 0; col < cols; col++)
                {
                    v1[0] = m1Red[row, col];
                    v1[1] = m1Grn[row, col];
                    v1[2] = m1Blu[row, col];

                    v2[0] = m2Red[row, col];
                    v2[1] = m2Grn[row, col];
                    v2[2] = m2Blu[row, col];

                    d12Matrix[row, col] = DataTools.EuclideanDistance(v1, v2);
                    d11Matrix[row, col] = (v1[0] + v1[1] + v1[2]) / 3; // get average of the normalised values
                    d22Matrix[row, col] = (v2[0] + v2[1] + v2[2]) / 3;

                    // following lines are for debugging purposes
                    // if ((row == 150) && (col == 1100))
                    // {
                    // LoggedConsole.WriteLine("V1={0:f3}, {1:f3}, {2:f3}", v1[0], v1[1], v1[2]);
                    // LoggedConsole.WriteLine("V2={0:f3}, {1:f3}, {2:f3}", v2[0], v2[1], v2[2]);
                    // LoggedConsole.WriteLine("EDist12={0:f4};   ED11={1:f4};   ED22={2:f4}", d12Matrix[row, col], d11Matrix[row, col], d22Matrix[row, col]);
                    // }
                }
            }

            double[] array = DataTools.Matrix2Array(d12Matrix);
            NormalDist.AverageAndSD(array, out var avDist, out var sdDist);
            for (int row = 0; row < rows; row++)
            {
                for (int col = 0; col < cols; col++)
                {
                    d12Matrix[row, col] = (d12Matrix[row, col] - avDist) / sdDist;
                }
            }

            double zScore;
            Dictionary <string, Color> colourChart = GetDifferenceColourChart();
            Color colour;

            var bmp = new Image <Rgb24>(cols, rows);

            for (int row = 0; row < rows; row++)
            {
                for (int col = 0; col < cols; col++)
                {
                    zScore = d12Matrix[row, col];

                    if (d11Matrix[row, col] >= d22Matrix[row, col])
                    {
                        if (zScore > 3.08)
                        {
                            colour = colourChart["+99.9%"];
                        }

                        // 99.9% conf
                        else
                        {
                            if (zScore > 2.33)
                            {
                                colour = colourChart["+99.0%"];
                            }

                            // 99.0% conf
                            else
                            {
                                if (zScore > 1.65)
                                {
                                    colour = colourChart["+95.0%"];
                                }

                                // 95% conf
                                else
                                {
                                    if (zScore < 0.0)
                                    {
                                        colour = colourChart["NoValue"];
                                    }
                                    else
                                    {
                                        // v = Convert.ToInt32(zScore * MaxRGBValue);
                                        // colour = Color.FromRgb(v, 0, v);
                                        colour = colourChart["+NotSig"];
                                    }
                                }
                            }
                        }

                        // if() else
                        bmp[col, row] = colour;
                    }
                    else
                    {
                        if (zScore > 3.08)
                        {
                            colour = colourChart["-99.9%"];
                        }

                        // 99.9% conf
                        else
                        {
                            if (zScore > 2.33)
                            {
                                colour = colourChart["-99.0%"];
                            }

                            // 99.0% conf
                            else
                            {
                                if (zScore > 1.65)
                                {
                                    colour = colourChart["-95.0%"];
                                }

                                // 95% conf
                                else
                                {
                                    if (zScore < 0.0)
                                    {
                                        colour = colourChart["NoValue"];
                                    }
                                    else
                                    {
                                        // v = Convert.ToInt32(zScore * MaxRGBValue);
                                        // if()
                                        // colour = Color.FromRgb(0, v, v);
                                        colour = colourChart["-NotSig"];
                                    }
                                }
                            }
                        }

                        // if() else
                        bmp[col, row] = colour;
                    }
                }

                // all rows
            }

            // all rows

            return(bmp);
        }
Exemple #4
0
        /// <summary>
        /// 2. Analyses long audio recording (mp3 or wav) as per passed config file. Outputs an events.csv file AND an
        /// indices.csv file
        /// Signed off: Michael Towsey 4th December 2012.
        /// </summary>
        public static void Execute(Arguments arguments)
        {
            if (arguments == null)
            {
                throw new NoDeveloperMethodException();
            }

            LoggedConsole.WriteLine("# PROCESS LONG RECORDING");
            LoggedConsole.WriteLine("# DATE AND TIME: " + DateTime.Now);

            // 1. set up the necessary files
            var sourceAudio        = arguments.Source;
            var configFile         = arguments.Config.ToFileInfo();
            var outputDirectory    = arguments.Output;
            var tempFilesDirectory = arguments.TempDir;

            // if a temp dir is not given, use output dir as temp dir
            if (tempFilesDirectory == null)
            {
                Log.Warn("No temporary directory provided, using output directory");
                tempFilesDirectory = outputDirectory;
            }

            // try an automatically find the config file
            if (configFile == null)
            {
                throw new FileNotFoundException("No config file argument provided");
            }
            else if (!configFile.Exists)
            {
                Log.Warn($"Config file {configFile.FullName} not found... attempting to resolve config file");

                // we use .ToString() here to get the original input string.
                // Using fullname always produces an absolute path relative to pwd... we don't want to prematurely make assumptions:
                // e.g. We require a missing absolute path to fail... that wouldn't work with .Name
                // e.g. We require a relative path to try and resolve, using .FullName would fail the first absolute check inside ResolveConfigFile
                configFile = ConfigFile.Resolve(configFile.ToString(), Directory.GetCurrentDirectory().ToDirectoryInfo());
            }

            if (arguments.StartOffset.HasValue ^ arguments.EndOffset.HasValue)
            {
                throw new InvalidStartOrEndException("If StartOffset or EndOffset is specified, then both must be specified");
            }

            if (arguments.StartOffset.HasValue && arguments.EndOffset.HasValue && arguments.EndOffset.Value <= arguments.StartOffset.Value)
            {
                throw new InvalidStartOrEndException("Start offset must be less than end offset.");
            }

            LoggedConsole.WriteLine("# Recording file:      " + sourceAudio.FullName);
            LoggedConsole.WriteLine("# Configuration file:  " + configFile);
            LoggedConsole.WriteLine("# Output folder:       " + outputDirectory);
            LoggedConsole.WriteLine("# Temp File Directory: " + tempFilesDirectory);

            // optionally copy logs / config to make results easier to understand
            // TODO: remove, see https://github.com/QutEcoacoustics/audio-analysis/issues/133
            if (arguments.WhenExitCopyConfig || arguments.WhenExitCopyLog)
            {
                AppDomain.CurrentDomain.ProcessExit += (sender, args) => { Cleanup(arguments, configFile); };
            }

            // 2. initialize the analyzer
            // we're changing the way resolving config files works. Ideally, we'd like to use statically typed config files
            // but we can't do that unless we know which type we have to load first! Currently analyzer to load is in
            // the config file so we can't know which analyzer we can use. Thus we will change to using the file name,
            // or an argument to resolve the analyzer to load.
            // Get analysis name:
            IAnalyser2 analyzer = FindAndCheckAnalyzer <IAnalyser2>(arguments.AnalysisIdentifier, configFile.Name);

            // 2. get the analysis config
            AnalyzerConfig configuration = analyzer.ParseConfig(configFile);

            SaveBehavior saveIntermediateWavFiles  = configuration.SaveIntermediateWavFiles;
            bool         saveIntermediateDataFiles = configuration.SaveIntermediateCsvFiles;
            SaveBehavior saveSonogramsImages       = configuration.SaveSonogramImages;

            bool filenameDate = configuration.RequireDateInFilename;

            if (configuration[AnalysisKeys.AnalysisName].IsNotWhitespace())
            {
                Log.Warn("Your config file has `AnalysisName` set - this property is deprecated and ignored");
            }

            // AT 2018-02: changed logic so default index properties loaded if not provided
            // IFF the analyzer has a static config property that includes IndexPropertiesConfig it will be loaded.
            // IFF the IndexPropertiesConfig is null/empty a default is loaded
            // IFF the IndexPropertiesConfig is not null/empty, and is not a file that can be found, a ConfigFile exception
            //   will be thrown.
            FileInfo indicesPropertiesConfig = IndexProperties.Find(configuration as IIndexPropertyReferenceConfiguration);

            if (indicesPropertiesConfig == null || !indicesPropertiesConfig.Exists)
            {
                Log.Warn("IndexPropertiesConfig was not specified! Loading a default");
                indicesPropertiesConfig = ConfigFile.Default <Dictionary <string, IndexProperties> >();
            }

            LoggedConsole.WriteLine("# IndexProperties Cfg: " + indicesPropertiesConfig.FullName);

            // min score for an acceptable event
            Log.Info("Minimum event threshold has been set to " + configuration.EventThreshold);

            FileSegment.FileDateBehavior defaultBehavior = FileSegment.FileDateBehavior.Try;
            if (filenameDate)
            {
                if (!FileDateHelpers.FileNameContainsDateTime(sourceAudio.Name))
                {
                    throw new InvalidFileDateException(
                              "When RequireDateInFilename option is set, the filename of the source audio file must contain "
                              + "a valid AND UNAMBIGUOUS date. Such a date was not able to be parsed.");
                }

                defaultBehavior = FileSegment.FileDateBehavior.Required;
            }

            // 3. initilize AnalysisCoordinator class that will do the analysis
            var analysisCoordinator = new AnalysisCoordinator(
                new LocalSourcePreparer(),
                saveIntermediateWavFiles,
                false,
                arguments.Parallel);

            // 4. get the segment of audio to be analysed
            // if tiling output, specify that FileSegment needs to be able to read the date
            var fileSegment         = new FileSegment(sourceAudio, arguments.AlignToMinute, null, defaultBehavior);
            var bothOffsetsProvided = arguments.StartOffset.HasValue && arguments.EndOffset.HasValue;

            if (bothOffsetsProvided)
            {
                fileSegment.SegmentStartOffset = TimeSpan.FromSeconds(arguments.StartOffset.Value);
                fileSegment.SegmentEndOffset   = TimeSpan.FromSeconds(arguments.EndOffset.Value);
            }
            else
            {
                Log.Debug("Neither start nor end segment offsets provided. Therefore both were ignored.");
            }

            // 6. initialize the analysis settings object
            var analysisSettings = analyzer.DefaultSettings;

            analysisSettings.ConfigFile                = configFile;
            analysisSettings.Configuration             = configuration;
            analysisSettings.AnalysisOutputDirectory   = outputDirectory;
            analysisSettings.AnalysisTempDirectory     = tempFilesDirectory;
            analysisSettings.AnalysisDataSaveBehavior  = saveIntermediateDataFiles;
            analysisSettings.AnalysisImageSaveBehavior = saveSonogramsImages;
            analysisSettings.AnalysisChannelSelection  = arguments.Channels;
            analysisSettings.AnalysisMixDownToMono     = arguments.MixDownToMono;

            var segmentDuration = configuration.SegmentDuration?.Seconds();

            if (!segmentDuration.HasValue)
            {
                segmentDuration = analysisSettings.AnalysisMaxSegmentDuration ?? TimeSpan.FromMinutes(1);
                Log.Warn(
                    $"Can't read `{nameof(AnalyzerConfig.SegmentDuration)}` from config file. "
                    + $"Default value of {segmentDuration} used)");
            }

            analysisSettings.AnalysisMaxSegmentDuration = segmentDuration.Value;

            var segmentOverlap = configuration.SegmentOverlap?.Seconds();

            if (!segmentOverlap.HasValue)
            {
                segmentOverlap = analysisSettings.SegmentOverlapDuration;
                Log.Warn(
                    $"Can't read `{nameof(AnalyzerConfig.SegmentOverlap)}` from config file. "
                    + $"Default value of {segmentOverlap} used)");
            }

            analysisSettings.SegmentOverlapDuration = segmentOverlap.Value;

            // set target sample rate
            var resampleRate = configuration.ResampleRate;

            if (!resampleRate.HasValue)
            {
                resampleRate = analysisSettings.AnalysisTargetSampleRate ?? AppConfigHelper.DefaultTargetSampleRate;
                Log.Warn(
                    $"Can't read {nameof(configuration.ResampleRate)} from config file. "
                    + $"Default value of {resampleRate} used)");
            }

            analysisSettings.AnalysisTargetSampleRate = resampleRate;

            Log.Info(
                $"{nameof(configuration.SegmentDuration)}={segmentDuration}, "
                + $"{nameof(configuration.SegmentOverlap)}={segmentOverlap}, "
                + $"{nameof(configuration.ResampleRate)}={resampleRate}");

            // 7. ####################################### DO THE ANALYSIS ###################################
            LoggedConsole.WriteLine("START ANALYSIS ...");
            var analyserResults = analysisCoordinator.Run(fileSegment, analyzer, analysisSettings);

            // ##############################################################################################
            // 8. PROCESS THE RESULTS
            LoggedConsole.WriteLine(string.Empty);
            LoggedConsole.WriteLine("START PROCESSING RESULTS ...");
            if (analyserResults == null)
            {
                LoggedConsole.WriteErrorLine("###################################################\n");
                LoggedConsole.WriteErrorLine("The Analysis Run Coordinator has returned a null result.");
                LoggedConsole.WriteErrorLine("###################################################\n");
                throw new AnalysisOptionDevilException();
            }

            // Merge and correct main result types
            EventBase[]         mergedEventResults         = ResultsTools.MergeResults(analyserResults, ar => ar.Events, ResultsTools.CorrectEvent);
            SummaryIndexBase[]  mergedIndicesResults       = ResultsTools.MergeResults(analyserResults, ar => ar.SummaryIndices, ResultsTools.CorrectSummaryIndex);
            SpectralIndexBase[] mergedSpectralIndexResults = ResultsTools.MergeResults(analyserResults, ar => ar.SpectralIndices, ResultsTools.CorrectSpectrumIndex);

            // not an exceptional state, do not throw exception
            if (mergedEventResults != null && mergedEventResults.Length == 0)
            {
                LoggedConsole.WriteWarnLine("The analysis produced no EVENTS (mergedResults had zero count)");
            }

            if (mergedIndicesResults != null && mergedIndicesResults.Length == 0)
            {
                LoggedConsole.WriteWarnLine("The analysis produced no Summary INDICES (mergedResults had zero count)");
            }

            if (mergedSpectralIndexResults != null && mergedSpectralIndexResults.Length == 0)
            {
                LoggedConsole.WriteWarnLine("The analysis produced no Spectral INDICES (merged results had zero count)");
            }

            // 9. CREATE SUMMARY INDICES IF NECESSARY (FROM EVENTS)
#if DEBUG
            // get the duration of the original source audio file - need this to convert Events datatable to Indices Datatable
            var audioUtility = new MasterAudioUtility(tempFilesDirectory);
            var mimeType     = MediaTypes.GetMediaType(sourceAudio.Extension);
            var sourceInfo   = audioUtility.Info(sourceAudio);

            // updated by reference all the way down in LocalSourcePreparer
            Debug.Assert(fileSegment.TargetFileDuration == sourceInfo.Duration);
#endif
            var duration = fileSegment.TargetFileDuration.Value;

            ResultsTools.ConvertEventsToIndices(
                analyzer,
                mergedEventResults,
                ref mergedIndicesResults,
                duration,
                configuration.EventThreshold);
            int eventsCount           = mergedEventResults?.Length ?? 0;
            int numberOfRowsOfIndices = mergedIndicesResults?.Length ?? 0;

            // 10. Allow analysers to post-process

            // TODO: remove results directory if possible
            var instanceOutputDirectory =
                AnalysisCoordinator.GetNamedDirectory(analysisSettings.AnalysisOutputDirectory, analyzer);

            // 11. IMPORTANT - this is where IAnalyser2's post processor gets called.
            // Produces all spectrograms and images of SPECTRAL INDICES.
            // Long duration spectrograms are drawn IFF analysis type is Towsey.Acoustic
            analyzer.SummariseResults(analysisSettings, fileSegment, mergedEventResults, mergedIndicesResults, mergedSpectralIndexResults, analyserResults);

            // 12. SAVE THE RESULTS
            string fileNameBase = Path.GetFileNameWithoutExtension(sourceAudio.Name);

            var eventsFile  = ResultsTools.SaveEvents(analyzer, fileNameBase, instanceOutputDirectory, mergedEventResults);
            var indicesFile = ResultsTools.SaveSummaryIndices(analyzer, fileNameBase, instanceOutputDirectory, mergedIndicesResults);
            var spectraFile = ResultsTools.SaveSpectralIndices(analyzer, fileNameBase, instanceOutputDirectory, mergedSpectralIndexResults);

            // 13. THIS IS WHERE SUMMARY INDICES ARE PROCESSED
            //     Convert summary indices to black and white tracks image
            if (mergedIndicesResults == null)
            {
                Log.Info("No summary indices produced");
            }
            else
            {
                if (indicesPropertiesConfig == null || !indicesPropertiesConfig.Exists)
                {
                    throw new InvalidOperationException("Cannot process indices without an index configuration file, the file could not be found!");
                }

                // this arbitrary amount of data.
                if (mergedIndicesResults.Length > 5000)
                {
                    Log.Warn("Summary Indices Image not able to be drawn - there are too many indices to render");
                }
                else
                {
                    var    basename   = Path.GetFileNameWithoutExtension(fileNameBase);
                    string imageTitle = $"SOURCE:{basename},   {Meta.OrganizationTag};  ";

                    // Draw Tracks-Image of Summary indices
                    // set time scale resolution for drawing of summary index tracks
                    TimeSpan      timeScale   = TimeSpan.FromSeconds(0.1);
                    Image <Rgb24> tracksImage =
                        IndexDisplay.DrawImageOfSummaryIndices(
                            IndexProperties.GetIndexProperties(indicesPropertiesConfig),
                            indicesFile,
                            imageTitle,
                            timeScale,
                            fileSegment.TargetFileStartDate);
                    var imagePath = FilenameHelpers.AnalysisResultPath(instanceOutputDirectory, basename, "SummaryIndices", ImageFileExt);
                    tracksImage.Save(imagePath);
                }
            }

            // 14. wrap up, write stats
            LoggedConsole.WriteLine("INDICES CSV file(s) = " + (indicesFile?.Name ?? "<<No indices result, no file!>>"));
            LoggedConsole.WriteLine("\tNumber of rows (i.e. minutes) in CSV file of indices = " + numberOfRowsOfIndices);
            LoggedConsole.WriteLine(string.Empty);

            if (eventsFile == null)
            {
                LoggedConsole.WriteLine("An Events CSV file was NOT returned.");
            }
            else
            {
                LoggedConsole.WriteLine("EVENTS CSV file(s) = " + eventsFile.Name);
                LoggedConsole.WriteLine("\tNumber of events = " + eventsCount);
            }

            Log.Success($"Analysis Complete.\nSource={sourceAudio.Name}\nOutput={instanceOutputDirectory.FullName}");
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="arguments"></param>
        public static void Execute(Arguments arguments)
        {
            string date = "# DATE AND TIME: " + DateTime.Now;

            LoggedConsole.WriteLine("Test of OtsuThresholder class");
            LoggedConsole.WriteLine(date);
            LoggedConsole.WriteLine();

            // Load Source image
            Image <Rgb24> srcImage = null;

            try
            {
                srcImage = (Image <Rgb24>)Image.Load(arguments.InputImageFile.FullName);
            }
            catch (IOException ioE)
            {
                Console.Error.WriteLine(ioE);
                Environment.Exit(1);
            }

            int width  = srcImage.Width;
            int height = srcImage.Height;

            /*
             *
             * // Get raw image data
             * byte[,] M = ConvertColourImageToGreyScaleMatrix((Image<Rgb24>)srcImage);
             *
             * // Sanity check image
             * if ((width * height) != (M.GetLength(0) * M.GetLength(1)))
             * {
             *  Console.Error.WriteLine("Unexpected image data size.");
             *  Environment.Exit(1);
             * }
             *
             * // Output Image<Rgb24> info
             * //Console.WriteLine("Loaded image: '%s', width: %d, height: %d, num bytes: %d\n", filename, width, height, srcData.Length);
             *
             * byte[] vector = DataTools.Matrix2Array(M);
             * byte[] outputArray;
             *
             * // Create Otsu Thresholder
             * OtsuThresholder thresholder = new OtsuThresholder();
             * int threshold = thresholder.CalculateThreshold(vector, out outputArray);
             *
             * byte[,] opByteMatrix = DataTools.Array2Matrix(outputArray, width, height);
             */

            byte[,] matrix     = ConvertColourImageToGreyScaleMatrix((Image <Rgb24>)srcImage);
            double[,] ipMatrix = MatrixTools.ConvertMatrixOfByte2Double(matrix);

            GetGlobalOtsuThreshold(ipMatrix, out var opByteMatrix, out var threshold, out var histoImage);
            Console.WriteLine("Threshold: {0}", threshold);

            Image <Rgb24> opImage = ConvertMatrixToGreyScaleImage(opByteMatrix);

            Image <Rgb24>[] imageArray = { srcImage, opImage, histoImage };

            var images = ImageTools.CombineImagesVertically(imageArray);

            images.Save(arguments.OutputFileName.FullName);
        }
        } //END of ClusterShapesWithFuzzyART.

        public static int[] RepeatClusterWithFuzzyART(double[,] trainingData, out int committedNodeCount)
        {
            if (trainingData == null)
            {
                LoggedConsole.WriteLine("WARNING: ClusterWithFuzzyART() PASSED NULL TRAINING DATA!");
                committedNodeCount = 0;
                return(null);
            }

            int trnSetSize      = trainingData.GetLength(0);
            int IPSize          = trainingData.GetLength(1);
            int F2Size          = trnSetSize;
            int numberOfRepeats = 1;
            int maxIterations   = 100;

            LoggedConsole.WriteLine("trnSetSize=" + trnSetSize + "  IPSize=" + IPSize + "  F2Size=" + F2Size);
            int[] noOfCommittedF2 = new int[numberOfRepeats];    // : array[1..MaxRepeatNo] of word;{# committed F2 units}

            //int[] iterToConv = new int[numberOfRepeats];    // : array[1..MaxRepeatNo] of word;{# training iterations}

            int code = 0;        //used for getting error messages

            //************************** INITIALISE PARAMETER VALUES *************************
            //double alpha = 0.2;  //increasing alpha proliferates categories - 0.57 is good value
            //double beta = 0.5;   //beta=1 for fast learning/no momentum. beta=0 for no change in weights
            //double rho = 0.9;   //vigilance parameter - increasing rho proliferates categories
            //double theta = 0.05; //threshold for contrast enhancing

            //double alpha = 0.2;  //increasing alpha proliferates categories - 0.57 is good value
            //double beta = 0.1;   //beta=1 for fast learning/no momentum. beta=0 for no change in weights
            //double rho = 0.9;   //vigilance parameter - increasing rho proliferates categories
            //double theta = 0.0; //threshold for contrast enhancing

            double alpha = 0.2;                               //increasing alpha proliferates categories - 0.57 is good value
            double beta  = 0.2;                               //beta=1 for fast learning/no momentum. beta=0 for no change in weights
            double rho   = 0.8;                               //vigilance parameter - increasing rho proliferates categories
            double theta = 0.0;                               //threshold for contrast enhancing

            FuzzyART fuzzyART = new FuzzyART(IPSize, F2Size); //initialise FuzzyART class

            fuzzyART.SetParameterValues(alpha, beta, rho, theta);
            fuzzyART.WriteParameters();

            //{********** DO REPEATS ***********}
            for (int rep = 0; rep < numberOfRepeats; rep++)
            {
                //{********* RUN NET for ONE SET OF PARAMETERS for ALL ITERATIONS *********}
                fuzzyART.InitialiseArrays();
                int seed = 12345 * (rep + 1);
                fuzzyART.TrainNet(trainingData, maxIterations, seed);

                if (code != 0)
                {
                    break;
                }

                noOfCommittedF2[rep] = fuzzyART.CountCommittedF2Nodes();

                //ScoreTrainingResults (noOfCommittedF2[rep], noClasses, F2classLabel, F2classProb);
                //wtsFpath = ART.ARTDir + ART.wtsFname + "s" + simul + rep + ART.wtsFExt;
                //art2a.WriteWts(wtsFpath, F2classLabel, F2classProb);
                //if (ART.DEBUG) LoggedConsole.WriteLine("wts= " + wtsFpath + "  train set= " + trnSetFpath);
                LoggedConsole.WriteLine("Number Of Committed F2 Nodes after rep" + rep + " = " + noOfCommittedF2[rep]);
            } //end; {for rep   = 1 to norepeats do}       {***** END OF REPEATS *****}

            committedNodeCount = noOfCommittedF2[0];
            int[] keepScore = fuzzyART.inputCategory;
            return(keepScore);
        } //END of RepeatClusterWithFuzzyART.
 public void WriteParameters()
 {
     LoggedConsole.WriteLine("\n  BinaryCluster:-  Vigilance=" + this.VigilanceRho + "   Momentum=" + this.MomentumBeta);
 }
        public static void ReadSpectralIndicesAndWriteToDataTable(string[] spectrogramKeys, DateTime thisDate, DirectoryInfo targetDirInfo, string targetFileName, string opFilePath)
        {
            TimeSpan roundingInterval = TimeSpan.FromMinutes(1);

            // thisDate.Round(roundingInterval); // could not get this to work
            int year            = thisDate.Year;
            int thisDayOfYear   = thisDate.DayOfYear;
            int thisStartMinute = (thisDate.Hour * 60) + thisDate.Minute;

            if (thisDate.Second > 30)
            {
                thisStartMinute++;
            }

            // reads all known files spectral indices
            int freqBinCount;
            Dictionary <string, double[, ]> dict = IndexMatrices.ReadSpectrogramCsvFiles(targetDirInfo, targetFileName, spectrogramKeys, out freqBinCount);

            if (dict.Count() == 0)
            {
                LoggedConsole.WriteLine("No spectrogram matrices in the dictionary. Spectrogram files do not exist?");
                return;
            }

            // set up the output file with headers if it does not exist
            if (!File.Exists(opFilePath))
            {
                string outputCsvHeader = "Year,DayOfYear,MinOfDay,FreqBin";
                foreach (string key in dict.Keys)
                {
                    outputCsvHeader = outputCsvHeader + "," + key;
                }

                FileTools.WriteTextFile(opFilePath, outputCsvHeader);
            }

            List <string> lines     = new List <string>();
            string        linestart = string.Format("{0},{1}", year, thisDayOfYear);

            //int minutesInThisMatrix = 2;
            // number of minutes = number of columns in matrix
            int minutesInThisMatrix = dict[spectrogramKeys[1]].GetLength(1);

            freqBinCount = dict[spectrogramKeys[1]].GetLength(0);

            for (int min = 0; min < minutesInThisMatrix; min++)
            {
                int numberOfMinutes = thisStartMinute + min;
                for (int bin = 0; bin < freqBinCount; bin++)
                {
                    int           binId = freqBinCount - bin - 1;
                    StringBuilder line  = new StringBuilder(linestart + "," + numberOfMinutes + "," + binId);

                    foreach (string key in dict.Keys)
                    {
                        double[,] matrix = dict[key];

                        // do not need more than 6 decimal places for values which will ultimately transformed to colour bytes.
                        // cuts file size from 12.2 MB to 7.4 MB
                        string str = string.Format(",{0:F6}", matrix[bin, min]);
                        line.Append(str);
                    }

                    lines.Add(line.ToString());
                }
            }

            FileTools.Append2TextFile(opFilePath, lines);
        }
Exemple #9
0
        private static void Main()
        {
            throw new NotSupportedException("THIS WILL FAIL IN PRODUCTION");
            Log.WriteLine("TESTING METHODS IN CLASS FileTools\n\n");

            bool doit1 = false;

            if (doit1) //test ReadTextFile(string fName)
            {
                string fName = testDir + "testTextFile.txt";
                var    array = ReadTextFile(fName);
                foreach (string line in array)
                {
                    LoggedConsole.WriteLine(line);
                }
            }

            bool doit2 = false;

            if (doit2) //test WriteTextFile(string fName)
            {
                string fName = testDir + "testOfWritingATextFile.txt";
                var    array = new List <string>();
                array.Add("string1");
                array.Add("string2");
                array.Add("string3");
                array.Add("string4");
                array.Add("string5");
                WriteTextFile(fName, array);
            }

            bool doit3 = false;

            if (doit3) //test ReadDoubles2Matrix(string fName)
            {
                string fName = testDir + "testOfReadingMatrixFile.txt";
                double[,] matrix = ReadDoubles2Matrix(fName);
                int rowCount = matrix.GetLength(0); //height
                int colCount = matrix.GetLength(1); //width

                //LoggedConsole.WriteLine("rowCount=" + rowCount + "  colCount=" + colCount);
                DataTools.writeMatrix(matrix);
            }

            bool doit4 = true;

            if (doit4) //test Method(parameters)
            {
                string fName = testDir + "testWriteOfMatrix2File.txt";
                double[,] matrix =
                {
                    {
                        0.1, 0.2, 0.3, 0.4, 0.5, 0.6,
                    },
                    {
                        0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
                    },
                    {
                        0.9, 1.0, 1.1, 1.2, 1.3, 1.4,
                    },
                };
                WriteMatrix2File(matrix, fName);
                LoggedConsole.WriteLine("Wrote following matrix to file " + fName);
                DataTools.writeMatrix(matrix);
            }

            //COPY THIS TEST TEMPLATE
            bool doit5 = false;

            if (doit5) //test Method(parameters)
            {
            }

            Log.WriteLine("\nFINISHED");    //end
            Log.WriteLine("CLOSE CONSOLE"); //end
        } //end MAIN
        internal void ReadConfigFile(Config configuration)
        {
            // common properties
            this.AnalysisName           = configuration[AnalysisKeys.AnalysisName] ?? "<no name>";
            this.SpeciesName            = configuration[AnalysisKeys.SpeciesName] ?? "<no species>";
            this.AbbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>";
            this.UpperBandMaxHz         = configuration.GetInt("UpperFreqBandTop");
            this.UpperBandMinHz         = configuration.GetInt("UpperFreqBandBottom");
            this.LowerBandMaxHz         = configuration.GetInt("LowerFreqBandTop");
            this.LowerBandMinHz         = configuration.GetInt("LowerFreqBandBottom");
            this.DecibelThreshold       = configuration.GetDoubleOrNull(AnalysisKeys.DecibelThreshold) ?? 3.0;

            // extract profiles
            bool hasProfiles = ConfigFile.HasProfiles(configuration);

            if (!hasProfiles)
            {
                throw new ConfigFileException($"The Config file for {this.SpeciesName} must contain at least one valid profile.");
            }

            this.ProfileNames = ConfigFile.GetProfileNames(configuration);
            foreach (var name in this.ProfileNames)
            {
                LoggedConsole.WriteLine($"The Config file for {this.SpeciesName}  contains the profile <{name}>.");
            }

            // Config profile = ConfigFile.GetProfile(configuration, "Trill");
            bool success = ConfigFile.TryGetProfile(configuration, "Trill", out var trillProfile);

            if (!success)
            {
                throw new ConfigFileException($"The Config file for {this.SpeciesName} must contain a \"Trill\" profile.");
            }

            LoggedConsole.WriteLine($"Analyzing profile: {this.ProfileNames[0]}");

            // Periods and Oscillations
            this.MinPeriod = trillProfile.GetDouble(AnalysisKeys.MinPeriodicity); //: 0.18
            this.MaxPeriod = trillProfile.GetDouble(AnalysisKeys.MaxPeriodicity); //: 0.25

            // minimum duration in seconds of a trill event
            this.MinDurationOfTrill = trillProfile.GetDouble(AnalysisKeys.MinDuration); //:3

            // maximum duration in seconds of a trill event
            this.MaxDurationOfTrill = trillProfile.GetDouble(AnalysisKeys.MaxDuration); //: 15
            //// minimum acceptable value of a DCT coefficient
            this.IntensityThreshold = trillProfile.GetDoubleOrNull(AnalysisKeys.IntensityThreshold) ?? 0.4;

            //double dctDuration = (double)configuration.GetDouble(AnalysisKeys.DctDuration);
            // This is the intensity threshold above
            //double dctThreshold = (double)configuration.GetDouble(AnalysisKeys.DctThreshold);

            LoggedConsole.WriteLine($"Analyzing profile: {this.ProfileNames[1]}");
            success = ConfigFile.TryGetProfile(configuration, "Tink", out var tinkProfile);
            if (!success)
            {
                throw new ConfigFileException($"The Config file for {this.SpeciesName} must contain a \"Tink\" profile.");
            }

            this.MinDurationOfTink = tinkProfile.GetDouble(AnalysisKeys.MinDuration);

            // maximum duration in seconds of a tink event
            this.MaxDurationOfTink = tinkProfile.GetDouble(AnalysisKeys.MaxDuration);
            this.EventThreshold    = trillProfile.GetDoubleOrNull(AnalysisKeys.EventThreshold) ?? 0.2;
        }
        } // FELTWithBinaryTemplate()

        /// <summary>
        /// Scans a recording given a dicitonary of parameters and a syntactic template
        /// Template has a different orientation to others.
        /// </summary>
        /// <param name="sonogram"></param>
        /// <param name="dict"></param>
        /// <param name="templateMatrix"></param>
        /// <param name="segmentStartOffset"></param>
        /// <param name="recording"></param>
        /// <param name="templatePath"></param>
        /// <returns></returns>
        public static Tuple <SpectrogramStandard, List <AcousticEvent>, double[]> FELTWithSprTemplate(SpectrogramStandard sonogram, Dictionary <string, string> dict, char[,] templateMatrix, TimeSpan segmentStartOffset)
        {
            //i: get parameters from dicitonary
            string callName       = dict[FeltTemplate_Create.key_CALL_NAME];
            bool   doSegmentation = bool.Parse(dict[FeltTemplate_Create.key_DO_SEGMENTATION]);
            double smoothWindow   = double.Parse(dict[FeltTemplate_Create.key_SMOOTH_WINDOW]);       //before segmentation
            int    minHz          = int.Parse(dict[FeltTemplate_Create.key_MIN_HZ]);
            int    maxHz          = int.Parse(dict[FeltTemplate_Create.key_MAX_HZ]);
            double minDuration    = double.Parse(dict[FeltTemplate_Create.key_MIN_DURATION]);        //min duration of event in seconds
            double dBThreshold    = double.Parse(dict[FeltTemplate_Create.key_DECIBEL_THRESHOLD]);   // = 9.0; // dB threshold

            dBThreshold = 4.0;
            int binCount = (int)(maxHz / sonogram.FBinWidth) - (int)(minHz / sonogram.FBinWidth) + 1;

            Log.WriteLine("Freq band: {0} Hz - {1} Hz. (Freq bin count = {2})", minHz, maxHz, binCount);

            //ii: TEMPLATE INFO
            double templateDuration = templateMatrix.GetLength(0) / sonogram.FramesPerSecond;

            Log.WriteIfVerbose("Template duration = {0:f3} seconds or {1} frames.", templateDuration, templateMatrix.GetLength(0));
            Log.WriteIfVerbose("Min Duration: " + minDuration + " seconds");

            //iii: DO SEGMENTATION
            double segmentationThreshold = 2.0;             // Standard deviations above backgorund noise
            double maxDuration           = double.MaxValue; // Do not constrain maximum length of events.
            var    tuple1        = AcousticEvent.GetSegmentationEvents((SpectrogramStandard)sonogram, doSegmentation, segmentStartOffset, minHz, maxHz, smoothWindow, segmentationThreshold, minDuration, maxDuration);
            var    segmentEvents = tuple1.Item1;

            //iv: Score sonogram for events matching template
            //#############################################################################################################################################
            var tuple2 = FindMatchingEvents.Execute_Spr_Match(templateMatrix, sonogram, segmentEvents, minHz, maxHz, dBThreshold);
            //var tuple2 = FindMatchingEvents.Execute_StewartGage(target, dynamicRange, (SpectralSonogram)sonogram, segmentEvents, minHz, maxHz, minDuration);
            //var tuple2 = FindMatchingEvents.Execute_SobelEdges(target, dynamicRange, (SpectralSonogram)sonogram, segmentEvents, minHz, maxHz, minDuration);
            //var tuple2 = FindMatchingEvents.Execute_MFCC_XCOR(target, dynamicRange, sonogram, segmentEvents, minHz, maxHz, minDuration);
            var scores = tuple2.Item1;

            //#############################################################################################################################################

            //v: PROCESS SCORE ARRAY
            //scores = DataTools.filterMovingAverage(scores, 3);
            LoggedConsole.WriteLine("Scores: min={0:f4}, max={1:f4}, threshold={2:f2}dB", scores.Min(), scores.Max(), dBThreshold);
            //Set (scores < 0.0) = 0.0;
            for (int i = 0; i < scores.Length; i++)
            {
                if (scores[i] < 0.0)
                {
                    scores[i] = 0.0;
                }
            }

            //vi: EXTRACT EVENTS
            List <AcousticEvent> matchEvents = AcousticEvent.ConvertScoreArray2Events(scores, minHz, maxHz, sonogram.FramesPerSecond, sonogram.FBinWidth, dBThreshold,
                                                                                      minDuration, maxDuration,
                                                                                      segmentStartOffset);

            foreach (AcousticEvent ev in matchEvents)
            {
                ev.FileName = sonogram.Configuration.SourceFName;
                ev.Name     = sonogram.Configuration.CallName;
            }

            // Edit the events to correct the start time, duration and end of events to match the max score and length of the template.
            AdjustEventLocation(matchEvents, callName, templateDuration, sonogram.Duration.TotalSeconds);

            return(Tuple.Create(sonogram, matchEvents, scores));
        } // FELTWithSprTemplate()
        /// <summary>
        /// ################ THE KEY ANALYSIS METHOD for TRILLS
        ///
        ///  See Anthony's ExempliGratia.Recognize() method in order to see how to use methods for config profiles.
        ///  </summary>
        /// <param name="recording"></param>
        /// <param name="sonoConfig"></param>
        /// <param name="lwConfig"></param>
        /// <param name="returnDebugImage"></param>
        /// <param name="segmentStartOffset"></param>
        /// <returns></returns>
        private static Tuple <BaseSonogram, double[, ], double[], List <AcousticEvent>, Image> Analysis(
            AudioRecording recording,
            SonogramConfig sonoConfig,
            LitoriaWatjulumConfig lwConfig,
            bool returnDebugImage,
            TimeSpan segmentStartOffset)
        {
            double intensityThreshold = lwConfig.IntensityThreshold;
            double minDuration        = lwConfig.MinDurationOfTrill; // seconds
            double maxDuration        = lwConfig.MaxDurationOfTrill; // seconds
            double minPeriod          = lwConfig.MinPeriod;          // seconds
            double maxPeriod          = lwConfig.MaxPeriod;          // seconds

            if (recording == null)
            {
                LoggedConsole.WriteLine("AudioRecording == null. Analysis not possible.");
                return(null);
            }

            //i: MAKE SONOGRAM
            //TimeSpan tsRecordingtDuration = recording.Duration();
            int    sr              = recording.SampleRate;
            double freqBinWidth    = sr / (double)sonoConfig.WindowSize;
            double framesPerSecond = freqBinWidth;

            // duration of DCT in seconds - want it to be about 3X or 4X the expected maximum period
            double dctDuration = 4 * maxPeriod;

            // duration of DCT in frames
            int dctLength = (int)Math.Round(framesPerSecond * dctDuration);

            // set up the cosine coefficients
            double[,] cosines = MFCCStuff.Cosines(dctLength, dctLength);

            int upperBandMinBin = (int)Math.Round(lwConfig.UpperBandMinHz / freqBinWidth) + 1;
            int upperBandMaxBin = (int)Math.Round(lwConfig.UpperBandMaxHz / freqBinWidth) + 1;
            int lowerBandMinBin = (int)Math.Round(lwConfig.LowerBandMinHz / freqBinWidth) + 1;
            int lowerBandMaxBin = (int)Math.Round(lwConfig.LowerBandMaxHz / freqBinWidth) + 1;

            BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
            int          rowCount = sonogram.Data.GetLength(0);

            //int colCount = sonogram.Data.GetLength(1);

            double[] lowerArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, lowerBandMinBin, rowCount - 1, lowerBandMaxBin);
            double[] upperArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, upperBandMinBin, rowCount - 1, upperBandMaxBin);

            //lowerArray = DataTools.filterMovingAverage(lowerArray, 3);
            //upperArray = DataTools.filterMovingAverage(upperArray, 3);

            double[] amplitudeScores  = DataTools.SumMinusDifference(lowerArray, upperArray);
            double[] differenceScores = DspFilters.SubtractBaseline(amplitudeScores, 7);

            // Could smooth here rather than above. Above seemed slightly better?
            //amplitudeScores = DataTools.filterMovingAverage(amplitudeScores, 7);
            //differenceScores = DataTools.filterMovingAverage(differenceScores, 7);

            //iii: CONVERT decibel sum-diff SCORES TO ACOUSTIC TRILL EVENTS
            var predictedTrillEvents = AcousticEvent.ConvertScoreArray2Events(
                amplitudeScores,
                lwConfig.LowerBandMinHz,
                lwConfig.UpperBandMaxHz,
                sonogram.FramesPerSecond,
                freqBinWidth,
                lwConfig.DecibelThreshold,
                minDuration,
                maxDuration,
                segmentStartOffset);

            for (int i = 0; i < differenceScores.Length; i++)
            {
                if (differenceScores[i] < 1.0)
                {
                    differenceScores[i] = 0.0;
                }
            }

            // LOOK FOR TRILL EVENTS
            // init the score array
            double[] scores = new double[rowCount];

            // var hits = new double[rowCount, colCount];
            double[,] hits = null;

            // init confirmed events
            var confirmedEvents = new List <AcousticEvent>();

            // add names into the returned events
            foreach (var ae in predictedTrillEvents)
            {
                int    eventStart       = ae.Oblong.RowTop;
                int    eventWidth       = ae.Oblong.RowWidth;
                int    step             = 2;
                double maximumIntensity = 0.0;

                // scan the event to get oscillation period and intensity
                for (int i = eventStart - (dctLength / 2); i < eventStart + eventWidth - (dctLength / 2); i += step)
                {
                    // Look for oscillations in the difference array
                    double[] differenceArray = DataTools.Subarray(differenceScores, i, dctLength);
                    Oscillations2014.GetOscillationUsingDct(differenceArray, framesPerSecond, cosines, out var oscilFreq, out var period, out var intensity);

                    bool periodWithinBounds = period > minPeriod && period < maxPeriod;

                    //Console.WriteLine($"step={i}    period={period:f4}");

                    if (!periodWithinBounds)
                    {
                        continue;
                    }

                    for (int j = 0; j < dctLength; j++) //lay down score for sample length
                    {
                        if (scores[i + j] < intensity)
                        {
                            scores[i + j] = intensity;
                        }
                    }

                    if (maximumIntensity < intensity)
                    {
                        maximumIntensity = intensity;
                    }
                }

                // add abbreviatedSpeciesName into event
                if (maximumIntensity >= intensityThreshold)
                {
                    ae.Name             = $"{lwConfig.AbbreviatedSpeciesName}.{lwConfig.ProfileNames[0]}";
                    ae.Score_MaxInEvent = maximumIntensity;
                    ae.Profile          = lwConfig.ProfileNames[0];
                    confirmedEvents.Add(ae);
                }
            }

            //######################################################################
            // LOOK FOR TINK EVENTS
            // CONVERT decibel sum-diff SCORES TO ACOUSTIC EVENTS
            double minDurationOfTink = lwConfig.MinDurationOfTink;  // seconds
            double maxDurationOfTink = lwConfig.MaxDurationOfTink;  // seconds

            // want stronger threshold for tink because brief.
            double tinkDecibelThreshold = lwConfig.DecibelThreshold + 3.0;
            var    predictedTinkEvents  = AcousticEvent.ConvertScoreArray2Events(
                amplitudeScores,
                lwConfig.LowerBandMinHz,
                lwConfig.UpperBandMaxHz,
                sonogram.FramesPerSecond,
                freqBinWidth,
                tinkDecibelThreshold,
                minDurationOfTink,
                maxDurationOfTink,
                segmentStartOffset);

            foreach (var ae2 in predictedTinkEvents)
            {
                // Prune the list of potential acoustic events, for example using Cosine Similarity.

                //rowtop,  rowWidth
                //int eventStart = ae2.Oblong.RowTop;
                //int eventWidth = ae2.Oblong.RowWidth;
                //int step = 2;
                //double maximumIntensity = 0.0;

                // add abbreviatedSpeciesName into event
                //if (maximumIntensity >= intensityThreshold)
                //{
                ae2.Name = $"{lwConfig.AbbreviatedSpeciesName}.{lwConfig.ProfileNames[1]}";

                //ae2.Score_MaxInEvent = maximumIntensity;
                ae2.Profile = lwConfig.ProfileNames[1];
                confirmedEvents.Add(ae2);

                //}
            }

            //######################################################################

            var   scorePlot  = new Plot(lwConfig.SpeciesName, scores, intensityThreshold);
            Image debugImage = null;

            if (returnDebugImage)
            {
                // display a variety of debug score arrays
                DataTools.Normalise(amplitudeScores, lwConfig.DecibelThreshold, out var normalisedScores, out var normalisedThreshold);
                var sumDiffPlot = new Plot("Sum Minus Difference", normalisedScores, normalisedThreshold);
                DataTools.Normalise(differenceScores, lwConfig.DecibelThreshold, out normalisedScores, out normalisedThreshold);
                var differencePlot = new Plot("Baseline Removed", normalisedScores, normalisedThreshold);

                var debugPlots = new List <Plot> {
                    scorePlot, sumDiffPlot, differencePlot
                };
                debugImage = DrawDebugImage(sonogram, confirmedEvents, debugPlots, hits);
            }

            // return new sonogram because it makes for more easy interpretation of the image
            var returnSonoConfig = new SonogramConfig
            {
                SourceFName   = recording.BaseName,
                WindowSize    = 512,
                WindowOverlap = 0,

                // the default window is HAMMING
                //WindowFunction = WindowFunctions.HANNING.ToString(),
                //WindowFunction = WindowFunctions.NONE.ToString(),
                // if do not use noise reduction can get a more sensitive recogniser.
                //NoiseReductionType = NoiseReductionType.NONE,
                NoiseReductionType = SNR.KeyToNoiseReductionType("STANDARD"),
            };
            BaseSonogram returnSonogram = new SpectrogramStandard(returnSonoConfig, recording.WavReader);

            return(Tuple.Create(returnSonogram, hits, scores, confirmedEvents, debugImage));
        } //Analysis()
        //public static Dictionary<string, Dictionary<string, string>> ConfProtoDict
        //{
        //    get { return confProtoDict; }
        //}

        #endregion

        #region Constructor
        #endregion

        #region Methods
        public static void ReadTCF(string mainConfigFN, string mfcConfFN, string mainConfTrainFN)
        {
            string txtLine = "";
            //Check if the MFC configuration file exists
            StreamReader mfcReader = null;
            StreamWriter mfcWriter = null;

            try
            {
                mfcReader = new StreamReader(mfcConfFN);
                try
                {
                    mfcWriter = File.CreateText(mainConfTrainFN);

                    while ((txtLine = mfcReader.ReadLine()) != null) //write all lines to file except SOURCEFORMAT
                    {
                        if (Regex.IsMatch(txtLine.ToUpper(), @"SOURCEFORMAT.*"))
                        {
                            continue; //skip this line
                        }
                        mfcWriter.WriteLine(txtLine);
                    }
                }
                catch (IOException e)
                {
                    LoggedConsole.WriteLine("Could not create codetrain file.");
                    throw (e);
                }
                catch (Exception e)
                {
                    LoggedConsole.WriteLine(e);
                    throw (e);
                }
                finally
                {
                    if (mfcWriter != null)
                    {
                        mfcWriter.Flush();
                        mfcWriter.Close();
                    }
                }
            }
            catch (IOException e)
            {
                LoggedConsole.WriteLine("Could not find configuration file: {0}", mfcConfFN);
                throw (e);
            }
            finally
            {
                if (mfcReader != null)
                {
                    mfcReader.Close();
                }
            }

            //string cwd = Directory.GetCurrentDirectory();
            //if(File.Exists(fileName))

            StreamReader objReader = null;

            try
            {
                LoggedConsole.WriteLine("Reading Main    Config file: " + mainConfigFN);
                objReader = new StreamReader(mainConfigFN);
                while ((txtLine = objReader.ReadLine()) != null)
                {
                    if (Regex.IsMatch(txtLine.ToUpper(), @"<ENDSYS_SETUP>") ||
                        Regex.IsMatch(txtLine.ToUpper(), @"<ENDTOOL_STEPS>"))
                    {
                        validData = false;
                    }

                    if (validData)
                    {
                        if (Regex.IsMatch(txtLine, @"^\s*$")) //take off space characters
                        {
                            continue;
                        }

                        string[] param = Regex.Split(txtLine, separator);
                        Match    m     = Regex.Match(param[0].ToUpper(), @"\b\w+\b");
                        parameter = m.ToString();

                        if (parameter.CompareTo("NMIXES") == 0)
                        {
                            //TO DO: handle Gaussian Mixtures Input
                        }

                        string value = "";
                        if (confParam.TryGetValue(parameter, out value))
                        {
                            confParam[parameter] = param[1].ToUpper();
                        }
                        else
                        {
                            confParam.Add(parameter, param[1].ToUpper());
                        }
                    }

                    if (Regex.IsMatch(txtLine.ToUpper(), @"<BEGINSYS_SETUP>") ||
                        Regex.IsMatch(txtLine.ToUpper(), @"<BEGINTOOL_STEPS>"))
                    {
                        validData = true;
                    }
                }
                //print config file
                LoggedConsole.WriteLine("Main Configuration File");
                LoggedConsole.WriteLine("=======================");
                LoggedConsole.WriteLine("{0,-18}{1,-1}", "Parameter", "Value");
                LoggedConsole.WriteLine("-----------------------");
                foreach (KeyValuePair <string, string> pair in confParam)
                {
                    LoggedConsole.WriteLine("{0,-18}{1,-1:D}", pair.Key, pair.Value);
                }
            }
            catch (IOException e)
            {
                LoggedConsole.WriteLine("Could not find configuration file: {0}", mainConfigFN);
                throw (e);
            }
            catch (Exception e)
            {
                LoggedConsole.WriteLine(e);
                throw (e);
            }
            finally
            {
                if (objReader != null)
                {
                    objReader.Close();
                }
            }
        }
        } //end METHOD ReadPCF() to read and train prototype configurations

        #endregion



        public static void WriteHMMprototypeFile(string prototypeHMM)
        {
            LoggedConsole.WriteLine("\nStarting static method: HMMBuilder.HMMSettings.WriteHMMprototypeFile()");

            //Create prototype HMM based on the call (non-SIL) parameters
            LoggedConsole.WriteLine(" Create prototype HMM based on the call (non-SIL) parameters in file <" + prototypeHMM + ">");

            string       prototypeDir = Path.GetDirectoryName(prototypeHMM);
            StreamWriter protoWriter  = null;

            try
            {
                // Create prototype dir if it does not exist.
                if (!Directory.Exists(prototypeDir))
                {
                    LoggedConsole.WriteLine(" Create prototype dir: " + prototypeDir);
                    Directory.CreateDirectory(prototypeDir);
                }

                LoggedConsole.WriteLine(" Create HMM prototype file: " + prototypeHMM);
                protoWriter = File.CreateText(prototypeHMM);

                //try to get the key 'proto' from the dictionary
                Dictionary <string, string> tmpDictVal = new Dictionary <string, string>();

                confProtoDict.TryGetValue("proto", out tmpDictVal);

                //write global options
                string vecSize  = "";
                string parmKind = "";
                if (tmpDictVal.TryGetValue("VECSIZE", out vecSize))
                {
                    if (tmpDictVal.TryGetValue("PARMKIND", out parmKind))
                    {
                        protoWriter.WriteLine("~o <VecSize> " + vecSize + " <" + parmKind + ">");
                        protoWriter.WriteLine("~h \"proto\"");
                    }
                    else
                    {
                        LoggedConsole.WriteLine("Parameter 'ParmKind' not specified in 'proto.pcf'");
                        //TO DO: create custom exception. For now throw something :-)
                        throw new Exception("Parameter 'ParmKind' not specified in 'proto.pcf'");
                    }
                }
                else
                {
                    LoggedConsole.WriteLine("Parameter 'VecSize' not specified in 'proto.pcf'");
                    //TO DO: create custom exception. For now throw something :-)
                    throw new Exception("Parameter 'VecSize' not specified in 'proto.pcf'");
                }

                protoWriter.WriteLine("<BeginHMM>");

                string nStates = "";
                if (tmpDictVal.TryGetValue("NSTATES", out nStates))
                {
                    protoWriter.WriteLine("  <NumStates> {0}", int.Parse(nStates) + 2);
                }
                else
                {
                    LoggedConsole.WriteLine("Parameter 'nStates' not specified in 'proto.pcf'");
                    //TO DO: create custom exception. For now throw something :-)
                    throw new Exception("Parameter 'nStates' not specified in 'proto.pcf'");
                }

                //write states
                string sWidths = "";
                if (tmpDictVal.TryGetValue("SWIDTHS", out sWidths))
                {
                    for (int i = 1; i <= int.Parse(nStates); i++)
                    {
                        protoWriter.WriteLine("  <State> {0}", i + 1);
                        protoWriter.WriteLine("    <Mean> " + sWidths);
                        string pad     = "      ";
                        string tmpLine = "";
                        for (int j = 1; j <= int.Parse(sWidths); j++)
                        {
                            tmpLine += "0.0 ";
                        }
                        protoWriter.WriteLine(pad + tmpLine);
                        tmpLine = "";
                        protoWriter.WriteLine("    <Variance> " + sWidths);
                        for (int j = 1; j <= int.Parse(sWidths); j++)
                        {
                            tmpLine += "1.0 ";
                        }
                        protoWriter.WriteLine(pad + tmpLine);
                    }
                }
                else
                {
                    LoggedConsole.WriteLine("Parameter 'sWidths' not specified in 'proto.pcf'");
                    //TO DO: create custom exception. For now throw something :-)
                    throw new Exception("Parameter 'sWidths' not specified in 'proto.pcf'");
                }

                //write Trans Matrix
                protoWriter.WriteLine("<TransP> {0}", int.Parse(nStates) + 2);
                for (int i = 1; i <= int.Parse(nStates) + 2; i++)
                {
                    for (int j = 1; j <= int.Parse(nStates) + 2; j++)
                    {
                        if ((i == 1) && (j == 2))
                        {
                            protoWriter.Write("  1.000e+0");
                        }
                        else
                        if ((i == j) && (i != 1) && (i != int.Parse(nStates) + 2))
                        {
                            if (i != int.Parse(nStates) + 1)
                            {
                                protoWriter.Write("  6.000e-1");
                            }
                            else
                            {
                                protoWriter.Write("  7.000e-1");
                            }
                        }
                        else
                        if (i == (j - 1))
                        {
                            if (i != int.Parse(nStates) + 1)
                            {
                                protoWriter.Write("  4.000e-1");
                            }
                            else
                            {
                                protoWriter.Write("  3.000e-1");
                            }
                        }
                        else
                        {
                            protoWriter.Write("  0.000e+0");
                        }
                    }
                    protoWriter.Write("\n");
                }

                protoWriter.WriteLine("<EndHMM>");
            } //end try for writing the HMM prototype file
            catch (IOException e)
            {
                LoggedConsole.WriteLine("Could not create the 'proto' file.");
                LoggedConsole.WriteLine(e.ToString());
                throw (e);
            }
            finally
            {
                if (protoWriter != null)
                {
                    protoWriter.Flush();
                    protoWriter.Close();
                }
            }
        } //end METHOD ReadPCF() to read and train prototype configurations
 public void WriteParameters()
 {
     LoggedConsole.WriteLine("\nFUZZY ART:- alpha=" + this.alpha + " beta=" + this.beta + " rho=" + this.rho + " theta=" + this.theta + " rhoStar=" + this.rhoStar);
 }
Exemple #16
0
        public void OctaveFrequencyScale1()
        {
            var recordingPath   = PathHelper.ResolveAsset("Recordings", "BAC2_20071008-085040.wav");
            var opFileStem      = "BAC2_20071008";
            var outputDir       = this.outputDirectory;
            var outputImagePath = Path.Combine(outputDir.FullName, "Octave1ScaleSonogram.png");

            var recording = new AudioRecording(recordingPath);

            // default octave scale
            var fst       = FreqScaleType.Linear125Octaves6Tones30Nyquist11025;
            var freqScale = new FrequencyScale(fst);

            var sonoConfig = new SonogramConfig
            {
                WindowSize              = freqScale.WindowSize,
                WindowOverlap           = 0.75,
                SourceFName             = recording.BaseName,
                NoiseReductionType      = NoiseReductionType.None,
                NoiseReductionParameter = 0.0,
            };

            // Generate amplitude sonogram and then conver to octave scale
            var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);

            // THIS IS THE CRITICAL LINE. COULD DO WITH SEPARATE UNIT TEST
            sonogram.Data = OctaveFreqScale.ConvertAmplitudeSpectrogramToDecibelOctaveScale(sonogram.Data, freqScale);

            // DO NOISE REDUCTION
            var dataMatrix = SNR.NoiseReduce_Standard(sonogram.Data);

            sonogram.Data = dataMatrix;
            sonogram.Configuration.WindowSize = freqScale.WindowSize;

            var image = sonogram.GetImageFullyAnnotated(sonogram.GetImage(), "SPECTROGRAM: " + fst.ToString(), freqScale.GridLineLocations);

            image.Save(outputImagePath);

            // DO FILE EQUALITY TESTS
            // Check that freqScale.OctaveBinBounds are correct
            var stemOfExpectedFile = opFileStem + "_Octave1ScaleBinBounds.EXPECTED.json";
            var stemOfActualFile   = opFileStem + "_Octave1ScaleBinBounds.ACTUAL.json";
            var expectedFile1      = PathHelper.ResolveAsset("FrequencyScale\\" + stemOfExpectedFile);

            if (!expectedFile1.Exists)
            {
                LoggedConsole.WriteErrorLine("An EXPECTED results file does not exist. Test will fail!");
                LoggedConsole.WriteErrorLine(
                    $"If ACTUAL results file is correct, move it to dir `{PathHelper.TestResources}` and change its suffix to <.EXPECTED.json>");
            }

            var resultFile1 = new FileInfo(Path.Combine(outputDir.FullName, stemOfActualFile));

            Json.Serialise(resultFile1, freqScale.BinBounds);
            FileEqualityHelpers.TextFileEqual(expectedFile1, resultFile1);

            // Check that freqScale.GridLineLocations are correct
            stemOfExpectedFile = opFileStem + "_Octave1ScaleGridLineLocations.EXPECTED.json";
            stemOfActualFile   = opFileStem + "_Octave1ScaleGridLineLocations.ACTUAL.json";
            var expectedFile2 = PathHelper.ResolveAsset("FrequencyScale\\" + stemOfExpectedFile);

            if (!expectedFile2.Exists)
            {
                LoggedConsole.WriteErrorLine("An EXPECTED results file does not exist. Test will fail!");
                LoggedConsole.WriteErrorLine(
                    $"If ACTUAL results file is correct, move it to dir `{PathHelper.TestResources}` and change its suffix to <.EXPECTED.json>");
            }

            var resultFile2 = new FileInfo(Path.Combine(outputDir.FullName, stemOfActualFile));

            Json.Serialise(resultFile2, freqScale.GridLineLocations);
            FileEqualityHelpers.TextFileEqual(expectedFile2, resultFile2);

            // Check that image dimensions are correct
            Assert.AreEqual(645, image.Width);
            Assert.AreEqual(310, image.Height);
        }
        }     //}  //end; TrainNet()

        public Tuple <int, int> TrainNet(double[,] dataArray, int maxIter, int seed)
        {
            int dataSetSize = dataArray.GetLength(0);

            int[] randomArray     = RandomNumber.RandomizeNumberOrder(dataSetSize, seed); //randomize order of trn set
            bool  trainSetLearned = false;                                                //     : boolean;
            bool  skippedBecauseFull;

            this.prevCategory = new int[dataSetSize]; //stores the winning F2 node for each input signal

            //{********* GO THROUGH THE TRAINING SET for 1 to MAX ITERATIONS *********}

            LoggedConsole.WriteLine("\n BEGIN TRAINING");
            LoggedConsole.WriteLine(" Maximum iterations = " + maxIter);

            //repeat //{training set until max iter or trn set learned}
            int iterNum = 0;

            while (!trainSetLearned && iterNum < maxIter)
            {
                iterNum++;
                skippedBecauseFull = false;

                //F2ScoreMatrix = new int[F2size, noClasses]; //keeps record of all F2 node classification results
                this.inputCategory = new int[dataSetSize]; //stores the winning F2 node for each input signal
                this.F2Wins        = new int[dataSetSize]; //stores the number of times each F2 node wins

                //initialise convergence criteria.
                // For ARTMAP want train set learned but for other ART versions want stable F2node allocations
                trainSetLearned = true;
                int changedCategory = 0;

                //{READ AND PROCESS signals until end of the data file}
                for (int sigNum = 0; sigNum < dataSetSize; sigNum++)
                {
                    //select an input signal. Later use sigID to enable test of convergence
                    int sigID = sigNum;                                         //do signals in order
                    if (ART.RandomiseTrnSetOrder)
                    {
                        sigID = randomArray[sigNum];  //pick at random
                    }

                    //{*************** GET INPUT, PRE-PROCESS and TRANSFER TO F0 of ART net ********}
                    double[] rawIP = this.GetOneIPVector(sigID, dataArray);
                    double[] IP    = this.ComplementCode(this.ContrastEnhance(rawIP));

                    //{*********** NOW PASS ONE INPUT SIGNAL THROUGH THE NETWORK ***********}
                    double[] OP = this.PropagateIPToF2(IP);

                    // change wts depending on prediction. Index is the winning node whose wts were changed
                    int index = this.ChangeWts(IP, OP);
                    if (index == -1)
                    {
                        skippedBecauseFull = true;
                        LoggedConsole.WriteLine(" BREAK LEARNING BECAUSE ALL F2 NODES COMMITTED");
                        break;
                    }
                    else
                    {
                        this.inputCategory[sigID] = index; //winning F2 node for current input
                        this.F2Wins[index]++;

                        //{test if training set is learned ie each signal is classified to the same F2 node as previous iteration}
                        if (this.inputCategory[sigID] != this.prevCategory[sigID])
                        {
                            trainSetLearned = false;
                            changedCategory++;
                        }

                        //LoggedConsole.WriteLine("sigNum=" + sigNum);
                    }

                    //scoring in case where have targets or labels for the training data
                    //F2ScoreMatrix[index, noClasses + 1]++;   //{total count going to F2node}
                    //F2ScoreMatrix[index, target]++;          //{# in class going to F2node}
                } //end loop - for (int sigNum = 0; sigNum < dataSetSize; sigNum++)

                for (int x = 0; x < dataSetSize; x++)
                {
                    this.prevCategory[x] = this.inputCategory[x];
                }

                //remove committed F2 nodes that are not having wins
                for (int j = 0; j < this.F2Size; j++)
                {
                    if (!this.uncommittedJ[j] && this.F2Wins[j] == 0)
                    {
                        this.uncommittedJ[j] = true;
                    }
                }

                if (ART.DEBUG)
                {
                    LoggedConsole.WriteLine(" iter={0:D2}  committed=" + this.CountCommittedF2Nodes() + "\t changedCategory=" + changedCategory, iterNum);
                }

                //Console.ReadLine();

                if (trainSetLearned)
                {
                    //if (FuzzyART.Verbose) LoggedConsole.WriteLine("Training set learned after " + iterNum + " iterations");
                    //return System.Tuple.Create(iterNum, CountCommittedF2Nodes());
                    break;
                }
            } //end of while (! trainSetLearned or (iterNum < maxIter) or terminate);

            return(Tuple.Create(iterNum, this.CountCommittedF2Nodes()));
        } //}  //end; TrainNet()
Exemple #18
0
        public void OctaveFrequencyScale2()
        {
            var recordingPath   = PathHelper.ResolveAsset(@"Recordings\MarineJasco_AMAR119-00000139.00000139.Chan_1-24bps.1375012796.2013-07-28-11-59-56-16bit-60sec.wav");
            var opFileStem      = "JascoMarineGBR1";
            var outputDir       = this.outputDirectory;
            var outputImagePath = Path.Combine(this.outputDirectory.FullName, "Octave2ScaleSonogram.png");

            var recording = new AudioRecording(recordingPath);
            var fst       = FreqScaleType.Linear125Octaves7Tones28Nyquist32000;
            var freqScale = new FrequencyScale(fst);

            var sonoConfig = new SonogramConfig
            {
                WindowSize              = freqScale.WindowSize,
                WindowOverlap           = 0.2,
                SourceFName             = recording.BaseName,
                NoiseReductionType      = NoiseReductionType.None,
                NoiseReductionParameter = 0.0,
            };

            var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);

            sonogram.Data = OctaveFreqScale.ConvertAmplitudeSpectrogramToDecibelOctaveScale(sonogram.Data, freqScale);

            // DO NOISE REDUCTION
            var dataMatrix = SNR.NoiseReduce_Standard(sonogram.Data);

            sonogram.Data = dataMatrix;
            sonogram.Configuration.WindowSize = freqScale.WindowSize;

            var image = sonogram.GetImageFullyAnnotated(sonogram.GetImage(), "SPECTROGRAM: " + fst.ToString(), freqScale.GridLineLocations);

            image.Save(outputImagePath);

            // DO FILE EQUALITY TESTS
            // Check that freqScale.OctaveBinBounds are correct
            var stemOfExpectedFile = opFileStem + "_Octave2ScaleBinBounds.EXPECTED.json";
            var stemOfActualFile   = opFileStem + "_Octave2ScaleBinBounds.ACTUAL.json";
            var expectedFile1      = PathHelper.ResolveAsset("FrequencyScale\\" + stemOfExpectedFile);

            if (!expectedFile1.Exists)
            {
                LoggedConsole.WriteErrorLine("An EXPECTED results file does not exist. Test will fail!");
                LoggedConsole.WriteErrorLine(
                    $"If ACTUAL results file is correct, move it to dir `{PathHelper.TestResources}` and change its suffix to <.EXPECTED.json>");
            }

            var resultFile1 = new FileInfo(Path.Combine(outputDir.FullName, stemOfActualFile));

            Json.Serialise(resultFile1, freqScale.BinBounds);
            FileEqualityHelpers.TextFileEqual(expectedFile1, resultFile1);

            // Check that freqScale.GridLineLocations are correct
            stemOfExpectedFile = opFileStem + "_Octave2ScaleGridLineLocations.EXPECTED.json";
            stemOfActualFile   = opFileStem + "_Octave2ScaleGridLineLocations.ACTUAL.json";
            var expectedFile2 = PathHelper.ResolveAsset("FrequencyScale\\" + stemOfExpectedFile);

            if (!expectedFile2.Exists)
            {
                LoggedConsole.WriteErrorLine("An EXPECTED results file does not exist. Test will fail!");
                LoggedConsole.WriteErrorLine(
                    $"If ACTUAL results file is correct, move it to dir `{PathHelper.TestResources}` and change its suffix to <.EXPECTED.json>");
            }

            var resultFile2 = new FileInfo(Path.Combine(outputDir.FullName, stemOfActualFile));

            Json.Serialise(resultFile2, freqScale.GridLineLocations);
            FileEqualityHelpers.TextFileEqual(expectedFile2, resultFile2);

            // Check that image dimensions are correct
            Assert.AreEqual(201, image.Width);
            Assert.AreEqual(310, image.Height);
        }
Exemple #19
0
        } //struct Indices

        /// <summary>
        ///
        /// </summary>
        /// <param name="signalEnvelope">envelope of the original signal.</param>
        /// <param name="spectrogram">the original amplitude spectrum BUT noise reduced.</param>
        /// <param name="binWidth">derived from original nyquist and window/2.</param>
        public static Dictionary <string, double> GetIndices(double[] signalEnvelope, TimeSpan audioDuration, TimeSpan frameStepDuration, double[,] spectrogram, int lowFreqBound, int midFreqBound, double binWidth)
        {
            int chunkDuration = 10; //seconds - assume that signal is not less than one minute duration

            double framesPerSecond = 1 / frameStepDuration.TotalSeconds;
            int    chunkCount      = (int)Math.Round(audioDuration.TotalSeconds / chunkDuration);
            int    framesPerChunk  = (int)(chunkDuration * framesPerSecond);
            int    nyquistBin      = spectrogram.GetLength(1);

            string[] classifications = new string[chunkCount];

            //get acoustic indices and convert to rain indices.
            var sb = new StringBuilder();

            for (int i = 0; i < chunkCount; i++)
            {
                int startSecond = i * chunkDuration;
                int start       = (int)(startSecond * framesPerSecond);
                int end         = start + framesPerChunk;
                if (end >= signalEnvelope.Length)
                {
                    end = signalEnvelope.Length - 1;
                }

                double[] chunkSignal = DataTools.Subarray(signalEnvelope, start, framesPerChunk);
                if (chunkSignal.Length < 50)
                {
                    continue;  //an arbitrary minimum length
                }

                double[,] chunkSpectro = DataTools.Submatrix(spectrogram, start, 1, end, nyquistBin - 1);

                RainStruct rainIndices    = Get10SecondIndices(chunkSignal, chunkSpectro, lowFreqBound, midFreqBound, frameStepDuration, binWidth);
                string     classification = ConvertAcousticIndices2Classifcations(rainIndices);
                classifications[i] = classification;

                //write indices and classification info to console
                string separator = ",";
                string line      = string.Format("{1:d2}{0} {2:d2}{0} {3:f1}{0} {4:f1}{0} {5:f1}{0} {6:f2}{0} {7:f3}{0} {8:f2}{0} {9:f2}{0} {10:f2}{0} {11:f2}{0} {12:f2}{0} {13:f2}{0} {14}", separator,
                                                 startSecond, startSecond + chunkDuration,
                                                 rainIndices.AvSig_dB, rainIndices.BgNoise, rainIndices.Snr,
                                                 rainIndices.Activity, rainIndices.Spikes, rainIndices.ACI,
                                                 rainIndices.LowFreqCover, rainIndices.MidFreqCover, rainIndices.HiFreqCover,
                                                 rainIndices.TemporalEntropy, rainIndices.SpectralEntropy, classification);

                //if (verbose)
                if (false)
                {
                    LoggedConsole.WriteLine(line);
                }

                //FOR PREPARING SEE.5 DATA  -------  write indices and clsasification info to file
                //sb.AppendLine(line);
            }

            //FOR PREPARING SEE.5 DATA   ------    write indices and clsasification info to file
            //string opDir = @"C:\SensorNetworks\Output\Rain";
            //string opPath = Path.Combine(opDir, recording.BaseName + ".Rain.csv");
            //FileTools.WriteTextFile(opPath, sb.ToString());

            Dictionary <string, double> dict = ConvertClassifcations2Dictionary(classifications);

            return(dict);
        } //Analysis()
Exemple #20
0
        public void TestFreqScaleOnArtificialSignal2()
        {
            int    sampleRate = 64000;
            double duration   = 30; // signal duration in seconds

            int[] harmonics       = { 500, 1000, 2000, 4000, 8000 };
            var   freqScale       = new FrequencyScale(FreqScaleType.Linear125Octaves7Tones28Nyquist32000);
            var   outputImagePath = Path.Combine(this.outputDirectory.FullName, "Signal2_OctaveFreqScale.png");
            var   recording       = DspFilters.GenerateTestRecording(sampleRate, duration, harmonics, WaveType.Cosine);

            // init the default sonogram config
            var sonoConfig = new SonogramConfig
            {
                WindowSize              = freqScale.WindowSize,
                WindowOverlap           = 0.2,
                SourceFName             = "Signal2",
                NoiseReductionType      = NoiseReductionType.None,
                NoiseReductionParameter = 0.0,
            };
            var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);

            sonogram.Data = OctaveFreqScale.ConvertAmplitudeSpectrogramToDecibelOctaveScale(sonogram.Data, freqScale);

            // pick a row, any row
            var oneSpectrum = MatrixTools.GetRow(sonogram.Data, 40);

            oneSpectrum = DataTools.filterMovingAverage(oneSpectrum, 5);
            var peaks = DataTools.GetPeaks(oneSpectrum);

            var peakIds = new List <int>();

            for (int i = 5; i < peaks.Length - 5; i++)
            {
                if (peaks[i])
                {
                    int peakId = freqScale.BinBounds[i, 0];
                    peakIds.Add(peakId);
                    LoggedConsole.WriteLine($"Spectral peak located in bin {peakId},  Herz={freqScale.BinBounds[i, 1]}");
                }
            }

            foreach (int h in harmonics)
            {
                LoggedConsole.WriteLine($"Harmonic {h}Herz should be in bin {freqScale.GetBinIdForHerzValue(h)}");
            }

            Assert.AreEqual(5, peakIds.Count);
            Assert.AreEqual(129, peakIds[0]);
            Assert.AreEqual(257, peakIds[1]);
            Assert.AreEqual(513, peakIds[2]);
            Assert.AreEqual(1025, peakIds[3]);
            Assert.AreEqual(2049, peakIds[4]);

            var    image = sonogram.GetImage();
            string title = $"Spectrogram of Harmonics: {DataTools.Array2String(harmonics)}   SR={sampleRate}  Window={freqScale.WindowSize}";

            image = sonogram.GetImageFullyAnnotated(image, title, freqScale.GridLineLocations);
            image.Save(outputImagePath);

            // Check that image dimensions are correct
            Assert.AreEqual(146, image.Width);
            Assert.AreEqual(310, image.Height);
        }
        public Tuple <int, int, int[], List <double[]> > TrainNet(List <double[]> trainingData, int maxIter, int seed, int initialWtCount)
        {
            int dataSetSize = trainingData.Count;

            int[] randomArray = RandomNumber.RandomizeNumberOrder(dataSetSize, seed); //randomize order of trn set

            // bool skippedBecauseFull;
            int[] inputCategory = new int[dataSetSize]; //stores the winning OP node for each current  input signal
            int[] prevCategory  = new int[dataSetSize]; //stores the winning OP node for each previous input signal
            this.InitialiseWtArrays(trainingData, randomArray, initialWtCount);

            //{********* GO THROUGH THE TRAINING SET for 1 to MAX ITERATIONS *********}
            //repeat //{training set until max iter or trn set learned}
            int[] opNodeWins      = null;  //stores the number of times each OP node wins
            int   iterNum         = 0;
            bool  trainSetLearned = false; //     : boolean;

            while (!trainSetLearned && iterNum < maxIter)
            {
                iterNum++;
                opNodeWins = new int[this.OPSize];      //stores the number of times each OP node wins

                //initialise convergence criteria.  Want stable F2node allocations
                trainSetLearned = true;
                int changedCategory = 0;

                //{READ AND PROCESS signals until end of the data file}
                for (int sigNum = 0; sigNum < dataSetSize; sigNum++)
                {
                    //select an input signal. Later use sigID to enable test of convergence
                    int sigID = sigNum; // do signals in order
                    if (RandomiseTrnSetOrder)
                    {
                        sigID = randomArray[sigNum]; //pick at random
                    }

                    //{*********** PASS ONE INPUT SIGNAL THROUGH THE NETWORK ***********}
                    double[] OP        = this.PropagateIP2OP(trainingData[sigID]); //output = AND divided by OR of two vectors
                    int      index     = DataTools.GetMaxIndex(OP);
                    double   winningOP = OP[index];

                    //create new category if similarity OP of best matching node is too low
                    if (winningOP < this.VigilanceRho)
                    {
                        this.ChangeWtsOfFirstUncommittedNode(trainingData[sigID]);
                    }

                    inputCategory[sigID] = index; //winning F2 node for current input
                    opNodeWins[index]++;

                    //{test if training set is learned ie each signal is classified to the same F2 node as previous iteration}
                    if (inputCategory[sigID] != prevCategory[sigID])
                    {
                        trainSetLearned = false;
                        changedCategory++;
                    }
                } //end loop over all signal inputs

                //set the previous categories
                for (int x = 0; x < dataSetSize; x++)
                {
                    prevCategory[x] = inputCategory[x];
                }

                //remove committed F2 nodes that are not having wins
                for (int j = 0; j < this.OPSize; j++)
                {
                    if (this.committedNode[j] && opNodeWins[j] == 0)
                    {
                        this.committedNode[j] = false;
                    }
                }

                if (Verbose)
                {
                    LoggedConsole.WriteLine(" iter={0:D2}  committed=" + this.CountCommittedF2Nodes() + "\t changedCategory=" + changedCategory, iterNum);
                }

                if (trainSetLearned)
                {
                    break;
                }
            } //end of while (! trainSetLearned or (iterNum < maxIter) or terminate);

            return(Tuple.Create(iterNum, this.CountCommittedF2Nodes(), inputCategory, this.wts));
        } //TrainNet()
        public static void Execute(Arguments arguments)
        {
            if (arguments == null)
            {
                arguments = Dev();
            }

            LoggedConsole.WriteLine("DATE AND TIME:" + DateTime.Now);
            LoggedConsole.WriteLine("Syntactic Pattern Recognition\n");
            //StringBuilder sb = new StringBuilder("DATE AND TIME:" + DateTime.Now + "\n");
            //sb.Append("SCAN ALL RECORDINGS IN A DIRECTORY USING HTK-RECOGNISER\n");

            Log.Verbosity = 1;

            FileInfo      recordingPath = arguments.Source;
            FileInfo      iniPath       = arguments.Config;
            DirectoryInfo outputDir     = arguments.Output;
            string        opFName       = "SPR-output.txt";
            string        opPath        = outputDir + opFName;

            Log.WriteIfVerbose("# Output folder =" + outputDir);

            // A: READ PARAMETER VALUES FROM INI FILE
            var config = new ConfigDictionary(iniPath);
            Dictionary <string, string> dict = config.GetTable();

            Dictionary <string, string> .KeyCollection keys = dict.Keys;

            string callName     = dict[key_CALL_NAME];
            double frameOverlap = Convert.ToDouble(dict[key_FRAME_OVERLAP]);
            //SPT PARAMETERS
            double intensityThreshold   = Convert.ToDouble(dict[key_SPT_INTENSITY_THRESHOLD]);
            int    smallLengthThreshold = Convert.ToInt32(dict[key_SPT_SMALL_LENGTH_THRESHOLD]);
            //WHIPBIRD PARAMETERS
            int    whistle_MinHz          = int.Parse(dict[key_WHISTLE_MIN_HZ]);
            int    whistle_MaxHz          = int.Parse(dict[key_WHISTLE_MAX_HZ]);
            double optimumWhistleDuration = double.Parse(dict[key_WHISTLE_DURATION]);   //optimum duration of whistle in seconds
            int    whip_MinHz             = (dict.ContainsKey(key_WHIP_MIN_HZ)) ? int.Parse(dict[key_WHIP_MIN_HZ]) : 0;
            int    whip_MaxHz             = (dict.ContainsKey(key_WHIP_MAX_HZ))   ? int.Parse(dict[key_WHIP_MAX_HZ])    : 0;
            double whipDuration           = (dict.ContainsKey(key_WHIP_DURATION)) ? double.Parse(dict[key_WHIP_DURATION]) : 0.0; //duration of whip in seconds
            //CURLEW PARAMETERS
            double minDuration = (dict.ContainsKey(key_MIN_DURATION)) ? double.Parse(dict[key_MIN_DURATION]) : 0.0;              //min duration of call in seconds
            double maxDuration = (dict.ContainsKey(key_MAX_DURATION)) ? double.Parse(dict[key_MAX_DURATION]) : 0.0;              //duration of call in seconds

            double eventThreshold = double.Parse(dict[key_EVENT_THRESHOLD]);                                                     //min score for an acceptable event
            int    DRAW_SONOGRAMS = Convert.ToInt16(dict[key_DRAW_SONOGRAMS]);

            // B: CHECK to see if conversion from .MP3 to .WAV is necessary
            var destinationAudioFile = recordingPath;

            //LOAD RECORDING AND MAKE SONOGRAM
            BaseSonogram sonogram = null;

            using (var recording = new AudioRecording(destinationAudioFile.FullName))
            {
                // if (recording.SampleRate != 22050) recording.ConvertSampleRate22kHz(); // THIS METHOD CALL IS OBSOLETE

                var sonoConfig = new SonogramConfig
                {
                    NoiseReductionType = NoiseReductionType.None,
                    //NoiseReductionType = NoiseReductionType.STANDARD,
                    WindowOverlap = frameOverlap,
                };
                sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
            }

            List <AcousticEvent> predictedEvents = null;

            double[,] hits = null;
            double[] scores = null;

            var audioFileName = Path.GetFileNameWithoutExtension(destinationAudioFile.FullName);

            if (callName.Equals("WHIPBIRD"))
            {
                //SPT
                var result1 = SPT.doSPT(sonogram, intensityThreshold, smallLengthThreshold);
                //SPR
                Log.WriteLine("SPR start: intensity threshold = " + intensityThreshold);
                int    slope       = 0;   //degrees of the circle. i.e. 90 = vertical line.
                double sensitivity = 0.7; //lower value = more sensitive
                var    mHori       = MarkLine(result1.Item1, slope, smallLengthThreshold, intensityThreshold, sensitivity);
                slope       = 87;         //84
                sensitivity = 0.8;        //lower value = more sensitive
                var mVert = MarkLine(result1.Item1, slope, smallLengthThreshold - 4, intensityThreshold + 1, sensitivity);
                Log.WriteLine("SPR finished");
                Log.WriteLine("Extract Whipbird calls - start");

                int minBound_Whistle = (int)(whistle_MinHz / sonogram.FBinWidth);
                int maxBound_Whistle = (int)(whistle_MaxHz / sonogram.FBinWidth);
                int whistleFrames    = (int)(sonogram.FramesPerSecond * optimumWhistleDuration); //86 = frames/sec.
                int minBound_Whip    = (int)(whip_MinHz / sonogram.FBinWidth);
                int maxBound_Whip    = (int)(whip_MaxHz / sonogram.FBinWidth);
                int whipFrames       = (int)(sonogram.FramesPerSecond * whipDuration); //86 = frames/sec.
                var result3          = DetectWhipBird(mHori, mVert, minBound_Whistle, maxBound_Whistle, whistleFrames, minBound_Whip, maxBound_Whip, whipFrames, smallLengthThreshold);
                scores = result3.Item1;
                hits   = DataTools.AddMatrices(mHori, mVert);

                predictedEvents = AcousticEvent.ConvertScoreArray2Events(
                    scores,
                    whip_MinHz,
                    whip_MaxHz,
                    sonogram.FramesPerSecond,
                    sonogram.FBinWidth,
                    eventThreshold,
                    minDuration,
                    maxDuration,
                    TimeSpan.Zero);
                foreach (AcousticEvent ev in predictedEvents)
                {
                    ev.FileName = audioFileName;
                    ev.Name     = callName;
                }

                sonogram.Data = result1.Item1;
                Log.WriteLine("Extract Whipbird calls - finished");
            }
            else if (callName.Equals("CURLEW"))
            {
                //SPT
                double backgroundThreshold = 4.0;
                var    result1             = SNR.NoiseReduce(sonogram.Data, NoiseReductionType.Standard, backgroundThreshold);
                //var result1 = SPT.doSPT(sonogram, intensityThreshold, smallLengthThreshold);
                //var result1 = doNoiseRemoval(sonogram, intensityThreshold, smallLengthThreshold);

                //SPR
                Log.WriteLine("SPR start: intensity threshold = " + intensityThreshold);
                int    slope       = 20;  //degrees of the circle. i.e. 90 = vertical line.
                double sensitivity = 0.8; //lower value = more sensitive
                var    mHori       = MarkLine(result1.Item1, slope, smallLengthThreshold, intensityThreshold, sensitivity);
                slope       = 160;
                sensitivity = 0.8;        //lower value = more sensitive
                var mVert = MarkLine(result1.Item1, slope, smallLengthThreshold - 3, intensityThreshold + 1, sensitivity);
                Log.WriteLine("SPR finished");

                //detect curlew calls
                int minBound_Whistle = (int)(whistle_MinHz / sonogram.FBinWidth);
                int maxBound_Whistle = (int)(whistle_MaxHz / sonogram.FBinWidth);
                int whistleFrames    = (int)(sonogram.FramesPerSecond * optimumWhistleDuration);
                var result3          = DetectCurlew(mHori, mVert, minBound_Whistle, maxBound_Whistle, whistleFrames, smallLengthThreshold);

                //process curlew scores - look for curlew characteristic periodicity
                double minPeriod        = 1.2;
                double maxPeriod        = 1.8;
                int    minPeriod_frames = (int)Math.Round(sonogram.FramesPerSecond * minPeriod);
                int    maxPeriod_frames = (int)Math.Round(sonogram.FramesPerSecond * maxPeriod);
                scores = DataTools.filterMovingAverage(result3.Item1, 21);
                scores = DataTools.PeriodicityDetection(scores, minPeriod_frames, maxPeriod_frames);

                //extract events
                predictedEvents = AcousticEvent.ConvertScoreArray2Events(
                    scores,
                    whistle_MinHz,
                    whistle_MaxHz,
                    sonogram.FramesPerSecond,
                    sonogram.FBinWidth,
                    eventThreshold,
                    minDuration,
                    maxDuration,
                    TimeSpan.Zero);
                foreach (AcousticEvent ev in predictedEvents)
                {
                    ev.FileName = audioFileName;
                    ev.Name     = callName;
                }

                hits          = DataTools.AddMatrices(mHori, mVert);
                sonogram.Data = result1.Item1;
                Log.WriteLine("Extract Curlew calls - finished");
            }
            else if (callName.Equals("CURRAWONG"))
            {
                //SPT
                var result1 = SPT.doSPT(sonogram, intensityThreshold, smallLengthThreshold);
                //SPR
                Log.WriteLine("SPR start: intensity threshold = " + intensityThreshold);
                int slope = 70;           //degrees of the circle. i.e. 90 = vertical line.
                //slope = 210;
                double sensitivity = 0.7; //lower value = more sensitive
                var    mHori       = MarkLine(result1.Item1, slope, smallLengthThreshold, intensityThreshold, sensitivity);
                slope = 110;
                //slope = 340;
                sensitivity = 0.7;        //lower value = more sensitive
                var mVert = MarkLine(result1.Item1, slope, smallLengthThreshold - 3, intensityThreshold + 1, sensitivity);
                Log.WriteLine("SPR finished");

                int minBound_Whistle = (int)(whistle_MinHz / sonogram.FBinWidth);
                int maxBound_Whistle = (int)(whistle_MaxHz / sonogram.FBinWidth);
                int whistleFrames    = (int)(sonogram.FramesPerSecond * optimumWhistleDuration); //86 = frames/sec.
                var result3          = DetectCurlew(mHori, mVert, minBound_Whistle, maxBound_Whistle, whistleFrames + 10, smallLengthThreshold);
                scores = result3.Item1;
                hits   = DataTools.AddMatrices(mHori, mVert);

                predictedEvents = AcousticEvent.ConvertIntensityArray2Events(
                    scores,
                    TimeSpan.Zero,
                    whistle_MinHz,
                    whistle_MaxHz,
                    sonogram.FramesPerSecond,
                    sonogram.FBinWidth,
                    eventThreshold,
                    0.5,
                    maxDuration);
                foreach (AcousticEvent ev in predictedEvents)
                {
                    ev.FileName = audioFileName;
                    //ev.Name = callName;
                }
            }

            //write event count to results file.
            double sigDuration = sonogram.Duration.TotalSeconds;
            //string fname = Path.GetFileName(recordingPath);
            int count = predictedEvents.Count;

            Log.WriteIfVerbose("Number of Events: " + count);
            string str = string.Format("{0}\t{1}\t{2}", callName, sigDuration, count);

            FileTools.WriteTextFile(opPath, AcousticEvent.WriteEvents(predictedEvents, str).ToString());

            // SAVE IMAGE
            string imageName = outputDir + audioFileName;
            string imagePath = imageName + ".png";

            if (File.Exists(imagePath))
            {
                int suffix = 1;
                while (File.Exists(imageName + "." + suffix.ToString() + ".png"))
                {
                    suffix++;
                }
                //{
                //    suffix = (suffix == string.Empty) ? "1" : (int.Parse(suffix) + 1).ToString();
                //}
                //File.Delete(outputDir + audioFileName + "." + suffix.ToString() + ".png");
                File.Move(imagePath, imageName + "." + suffix.ToString() + ".png");
            }
            //string newPath = imagePath + suffix + ".png";
            if (DRAW_SONOGRAMS == 2)
            {
                DrawSonogram(sonogram, imagePath, hits, scores, predictedEvents, eventThreshold);
            }
            else
            if ((DRAW_SONOGRAMS == 1) && (predictedEvents.Count > 0))
            {
                DrawSonogram(sonogram, imagePath, hits, scores, predictedEvents, eventThreshold);
            }

            Log.WriteIfVerbose("Image saved to: " + imagePath);
            //string savePath = outputDir + Path.GetFileNameWithoutExtension(recordingPath);
            //string suffix = string.Empty;
            //Image im = sonogram.GetImage(false, false);
            //string newPath = savePath + suffix + ".jpg";
            //im.Save(newPath);

            LoggedConsole.WriteLine("\nFINISHED RECORDING!");
            Console.ReadLine();
        }
        /// <summary>
        /// This method used to construct slices out of implicit 3-D spectrograms.
        /// As of December 2014 it contains hard coded variables just to get it working.
        /// </summary>
        public static void Main(Arguments arguments)
        {
            if (!arguments.OutputDir.Exists)
            {
                arguments.OutputDir.Create();
            }

            const string title   = "# READ LD data table files to prepare a 3D Spectrogram";
            string       dateNow = "# DATE AND TIME: " + DateTime.Now;

            LoggedConsole.WriteLine(title);
            LoggedConsole.WriteLine(dateNow);
            LoggedConsole.WriteLine("# Index    Properties: " + arguments.IndexPropertiesConfig.Name);
            LoggedConsole.WriteLine("# Input     directory: " + arguments.InputDir.Name);

            //LoggedConsole.WriteLine("# SonogramConfig:   " + arguments.SonoConfig.Name);
            LoggedConsole.WriteLine("# Table     directory: " + arguments.TableDir.Name);
            LoggedConsole.WriteLine("# Output    directory: " + arguments.OutputDir.Name);
            LoggedConsole.WriteLine("# Analysis SampleRate: " + arguments.SampleRate);
            LoggedConsole.WriteLine("# Analysis  FrameSize: " + arguments.FrameSize);

            bool verbose = arguments.Verbose;

            // 1. set up the necessary files
            DirectoryInfo inputDirInfo     = arguments.InputDir;
            DirectoryInfo dataTableDirInfo = arguments.TableDir;
            DirectoryInfo opDir            = arguments.OutputDir;

            // FileInfo configFile = arguments.SonoConfig;
            FileInfo indexPropertiesConfig = arguments.IndexPropertiesConfig;
            FileInfo sunriseSetData        = arguments.BrisbaneSunriseDatafile;
            int      sampleRate            = arguments.SampleRate;
            int      frameSize             = arguments.FrameSize;
            int      nyquistFreq           = sampleRate / 2;
            int      freqBinCount          = frameSize / 2;
            double   freqBinWidth          = nyquistFreq / (double)freqBinCount;

            // 2. convert spectral indices to a data table - need only do this once
            // ### IMPORTANT ######################################################################################################################
            // Uncomment the next line when converting spectral indices to a data table for the first time.
            // It calls method to read in index spectrograms and combine all the info into one index table per day
            //SpectralIndicesToAndFromTable.ReadAllSpectralIndicesAndWriteToDataTable(indexPropertiesConfig, inputDirInfo, dataTableDirInfo);

            // ############ use next seven lines to obtain slices at constant DAY OF YEAR
            string key           = KeyDayOfYear;
            int    step          = 1;
            int    firstIndex    = 71;
            int    maxSliceCount = TotalDaysInYear + 1;
            var    xInterval     = TimeSpan.FromMinutes(60); // one hour intervals = 60 pixels
            int    rowId         = 3;                        // FreqBin
            int    colId         = 2;                        // MinOfDay

            // ############ use next seven lines to obtain slices at constant FREQUENCY
            //string key = keyFreqBin;
            //int step = 100;
            //int firstIndex = 0;
            //int maxSliceCount = nyquistFreq;
            //var XInterval = TimeSpan.FromMinutes(60);
            //int rowID = 1; // DayOfYear
            //int colID = 2; // MinOfDay

            // ############ use next seven lines to obtain slices at constant MINUTE OF DAY
            //string key = keyMinOfDay;
            //int step = 5;
            //int firstIndex = 0;
            //int maxSliceCount = LDSpectrogram3D.Total_Minutes_In_Day;
            //var XInterval = TimeSpan.FromDays(30.4); // average days per month
            //int rowID = 3; // FreqBin
            //int colID = 1; // DayOfYear

            // These are the column names and order in the csv data strings.
            // Year, DayOfYear, MinOfDay, FreqBin, ACI, AVG, BGN, CVR, TEN, VAR
            string colorMap = "ACI-TEN-CVR";
            int    redId    = 4; // ACI
            int    grnId    = 8; // TEN
            int    bluId    = 7; // CVR
            int    year     = 2013;

            for (int sliceId = firstIndex; sliceId < maxSliceCount; sliceId += step)
            {
                // DEFINE THE SLICE
                //sliceID = 300; // Herz
                int arrayId = sliceId;
                if (key == "FreqBin")
                {
                    arrayId = (int)Math.Round(sliceId / freqBinWidth);
                }

                var fileStem = string.Format("SERF_2013_" + key + "_{0:d4}", arrayId);

                // 3. Read a data slice from the data table files
                List <string> data;
                var           outputFileName = $"{fileStem}.csv";
                var           path           = Path.Combine(opDir.FullName, outputFileName);
                if (File.Exists(path))
                {
                    data = FileTools.ReadTextFile(path);
                }
                else
                {
                    if (key == KeyDayOfYear)
                    {
                        data = GetDaySlice(dataTableDirInfo, year, arrayId);
                    }
                    else
                    {
                        data = GetDataSlice(dataTableDirInfo, key, arrayId);
                    }

                    FileTools.WriteTextFile(path, data);
                }

                // 4. Read the yaml file describing the Index Properties
                Dictionary <string, IndexProperties> dictIp = IndexProperties.GetIndexProperties(indexPropertiesConfig);
                dictIp = InitialiseIndexProperties.FilterIndexPropertiesForSpectralOnly(dictIp);

                // 5. Convert data slice to image
                string[]        indexNames = colorMap.Split('-');
                IndexProperties ipRed      = dictIp[indexNames[0]];
                IndexProperties ipGrn      = dictIp[indexNames[1]];
                IndexProperties ipBlu      = dictIp[indexNames[2]];
                Image <Rgb24>   image      = GetImageSlice(key, data, rowId, colId, redId, grnId, bluId, ipRed, ipGrn, ipBlu, freqBinCount);

                // 6. frame the image and save
                image = Frame3DSpectrogram(image, key, arrayId, year, colorMap, xInterval, nyquistFreq, sliceId, sunriseSetData);

                // 7. save the image
                outputFileName = $"{fileStem}.png";
                path           = Path.Combine(opDir.FullName, outputFileName);
                image.Save(path);
            } // end loop through slices
        }     // end Main()
Exemple #24
0
        /// <summary>
        /// All the passed files will be concatenated. Filtering needs to be done somewhere else.
        /// </summary>
        /// <param name="files">array of file names.</param>
        /// <param name="indexCalcDuration">used to match rows of indices to elapsed time in file names.</param>
        public static List <SummaryIndexValues> ConcatenateSummaryIndexFilesWithTimeCheck(FileInfo[] files, TimeSpan indexCalcDuration)
        {
            TimeSpan?offsetHint = new TimeSpan(10, 0, 0);

            DateTimeOffset[] dtoArray = new DateTimeOffset[files.Length];
            var summaryIndices        = new List <SummaryIndexValues>();

            // accumulate the start times for each of the files
            for (int f = 0; f < files.Length; f++)
            {
                if (!files[f].Exists)
                {
                    LoggedConsole.WriteWarnLine($"WARNING: Concatenation Time Check: MISSING FILE: {files[f].FullName}");
                    continue;
                }

                if (!FileDateHelpers.FileNameContainsDateTime(files[f].Name, out var date, offsetHint))
                {
                    LoggedConsole.WriteWarnLine($"WARNING: Concatenation Time Check: INVALID DateTime in File Name {files[f].Name}");
                }

                dtoArray[f] = date;
            }

            // we use the fileName field to distinguish unique input source files
            // this Set allows us to check they are unique and render joins
            var sourceFileNames = new HashSet <string>();

            // now loop through the files again to extract the indices
            for (int i = 0; i < files.Length; i++)
            {
                if (!files[i].Exists)
                {
                    continue;
                }

                var rowsOfCsvFile = Csv.ReadFromCsv <SummaryIndexValues>(files[i], throwOnMissingField: false);

                // check all rows have fileName set
                var thisSourceFileNames = new HashSet <string>();
                foreach (var summaryIndexValues in rowsOfCsvFile)
                {
                    if (summaryIndexValues.FileName.IsNullOrEmpty())
                    {
                        throw new InvalidOperationException($"A supplied summary index file did not have the `{nameof(SummaryIndexValues.FileName)}` field populated. File: {files[i].FullName}");
                    }

                    thisSourceFileNames.Add(summaryIndexValues.FileName);
                }

                // check all found filenames are unique
                foreach (var sourceFileName in thisSourceFileNames)
                {
                    if (sourceFileNames.Contains(sourceFileName))
                    {
                        throw new InvalidOperationException(
                                  $"The summary index files already read previously contained the filename {sourceFileName} - duplicates are not allowed. File: {files[i].FullName}");
                    }

                    sourceFileNames.Add(sourceFileName);
                }

                summaryIndices.AddRange(rowsOfCsvFile);

                // track the row counts
                int partialRowCount = rowsOfCsvFile.Count();

                // calculate elapsed time from the rows
                int accumulatedRowMinutes = (int)Math.Round(partialRowCount * indexCalcDuration.TotalMinutes);

                // calculate the partial elapsed minutes as indexed by file names.
                var elapsedMinutesInFileNames = 0;
                if (i < files.Length - 1)
                {
                    TimeSpan elapsedTimeAccordingtoFileNames = dtoArray[i + 1] - dtoArray[i];
                    elapsedMinutesInFileNames = (int)Math.Round(elapsedTimeAccordingtoFileNames.TotalMinutes);
                }
                else
                {
                    elapsedMinutesInFileNames = accumulatedRowMinutes; // a hack for the last file
                }

                // Check for Mismatch error in concatenation.
                if (accumulatedRowMinutes != elapsedMinutesInFileNames)
                {
                    string str1 = $"Concatenation: Elapsed Time Mismatch ERROR in csvFile {i + 1}/{files.Length}: {accumulatedRowMinutes} accumulatedRowMinutes != {elapsedMinutesInFileNames} elapsedMinutesInFileNames";
                    LoggedConsole.WriteWarnLine(str1);

                    //dictionary = RepairDictionaryOfArrays(dictionary, rowCounts[i], partialMinutes);
                    int scalingfactor = (int)Math.Round(60.0 / indexCalcDuration.TotalSeconds);
                    int minutesToAdd  = elapsedMinutesInFileNames - accumulatedRowMinutes;
                    int rowsToAdd     = minutesToAdd * scalingfactor;

                    // add in the missing summary index rows
                    for (int j = 0; j < rowsToAdd; j++)
                    {
                        var vector = new SummaryIndexValues {
                            FileName = MissingRowString
                        };
                        summaryIndices.Add(vector);
                    }
                }
            }

            // Can prune the list of summary indices as required.
            //int expectedRowCount = (int)Math.Round(numberOfMinutesInDay / indexCalcDuration.TotalMinutes);
            //if (totalRowCount != expectedRowCount)
            //{
            //    if (IndexMatrices.Verbose)
            //        LoggedConsole.WriteLine("WARNING: INCONSISTENT ELAPSED TIME CHECK from IndexMatrices.GetSummaryIndexFilesAndConcatenateWithTimeCheck() ");
            //    string str = String.Format("   Final Data Row Count = {0}     Estimated Cumulative Duration = {1} minutes", totalRowCount, expectedRowCount);
            //    if (IndexMatrices.Verbose)
            //        LoggedConsole.WriteLine(str);
            //    dictionary = RepairDictionaryOfArrays(dictionary, totalRowCount, expectedRowCount);
            //}

            return(summaryIndices);
        }
        /// <summary>
        /// Assume that we are processing data for one minute only.
        ///     From this one minute of data, we produce images at three scales.
        ///     A one minute recording framed at 20ms should yield 3000 frames.
        ///     But to achieve this where sr= 22050 and frameSize=512, we need an overlap of 71 samples.
        ///     Consequently only 2999 frames returned per minute.
        ///     Therefore have to pad end to get 3000 frames.
        /// </summary>
        public static TimeOffsetSingleLayerSuperTile[] DrawSuperTilesFromSingleFrameSpectrogram(DirectoryInfo dataDir, LdSpectrogramConfig analysisConfig, Dictionary <string, IndexProperties> indexProperties, SpectrogramZoomingConfig zoomingConfig, int minute, double[] imageScales, string basename, IndexGenerationData indexGeneration, ImageChrome chromeOption, TimeSpan alignmentPadding)
        {
            string fileStem = basename;

            // string analysisType = analysisConfig.AnalysisType;
            TimeSpan indexScale = indexGeneration.IndexCalculationDuration;
            TimeSpan frameScale = TimeSpan.FromSeconds(zoomingConfig.SpectralFrameDuration);

            var expectedDataDurationInSeconds = (int)indexGeneration.MaximumSegmentDuration.Value.TotalSeconds;
            var expectedFrameCount            = (int)Math.Round(expectedDataDurationInSeconds / zoomingConfig.SpectralFrameDuration);

            string fileName        = fileStem + "_" + minute + "min.csv";
            string csvPath         = Path.Combine(dataDir.FullName, fileName);
            bool   skipHeader      = true;
            bool   skipFirstColumn = true;

            // read spectrogram into a list of frames
            List <double[]> frameList = CsvTools.ReadCSVFileOfDoubles(csvPath, skipHeader, skipFirstColumn);

            if (frameList == null)
            {
                LoggedConsole.WriteErrorLine(
                    "WARNING: METHOD DrawSuperTilesFromSingleFrameSpectrogram(): NO SPECTRAL DATA SUPPLIED");
                return(null);
            }

            PadEndOfListOfFrames(frameList, expectedFrameCount);
            TrimEndOfListOfFrames(frameList, expectedFrameCount);

            //// frame count will be one less than expected for the recording segment because of frame overlap
            //// Therefore pad the end of the list of frames with the last frame.
            // int frameDiscrepancy = expectedFrameCount - frameList.Count;
            // if (frameDiscrepancy > 0)
            // {
            // double[] frame = frameList[frameList.Count - 1];
            // for (int d = 0; d < frameDiscrepancy; d++)
            // {
            // frameList.Add(frame);
            // }
            // }
            var frameData = new TemporalMatrix("rows", MatrixTools.ConvertList2Matrix(frameList), frameScale);

            frameData.SwapTemporalDimension();                              // so the two data matrices have the same temporal dimension

            TimeSpan startTime       = indexGeneration.AnalysisStartOffset; // default = zero minute of day i.e. midnight
            TimeSpan startTimeOfData = startTime + TimeSpan.FromMinutes(minute);

            var str = new TimeOffsetSingleLayerSuperTile[imageScales.Length];

            // make the images
            for (int scale = 0; scale < imageScales.Length; scale++)
            {
                TimeSpan imageScale        = TimeSpan.FromSeconds(imageScales[scale]);
                var      compressionFactor =
                    (int)Math.Round(imageScale.TotalMilliseconds / frameData.DataScale.TotalMilliseconds);
                double columnDuration = imageScale.TotalSeconds;

                // int expectedFrameCount = (int)Math.Round(expectedDataDurationInSeconds / columnDuration);

                // ############## RESEARCH CHOICE HERE >>>>  compress spectrograms to correct scale using either max or average
                // Average appears to offer better contrast.
                // double[,] data = frameData.CompressMatrixInTemporalDirectionByTakingMax(imageScale);
                double[,] data = frameData.CompressMatrixInTemporalDirectionByTakingAverage(imageScale);

                Image spectrogramImage = DrawFrameSpectrogramAtScale(
                    analysisConfig,
                    zoomingConfig,
                    startTimeOfData,
                    imageScale,
                    data,
                    indexGeneration,
                    chromeOption);

                str[scale] = new TimeOffsetSingleLayerSuperTile(
                    alignmentPadding,
                    SpectrogramType.Frame,
                    imageScale,
                    spectrogramImage,
                    startTimeOfData);
            }

            return(str);
        }
        /// <summary>
        /// ################ THE KEY ANALYSIS METHOD
        /// </summary>
        /// <param name="recording"></param>
        /// <param name="sonoConfig"></param>
        /// <param name="lbConfig"></param>
        /// <param name="drawDebugImage"></param>
        /// <param name="segmentStartOffset"></param>
        /// <returns></returns>
        public static Tuple <BaseSonogram, double[, ], double[], List <AcousticEvent>, Image> Analysis(
            AudioRecording recording,
            SonogramConfig sonoConfig,
            LitoriaBicolorConfig lbConfig,
            bool drawDebugImage,
            TimeSpan segmentStartOffset)
        {
            double decibelThreshold   = lbConfig.DecibelThreshold; //dB
            double intensityThreshold = lbConfig.IntensityThreshold;

            //double eventThreshold = lbConfig.EventThreshold; //in 0-1

            if (recording == null)
            {
                LoggedConsole.WriteLine("AudioRecording == null. Analysis not possible.");
                return(null);
            }

            //i: MAKE SONOGRAM
            //TimeSpan tsRecordingtDuration = recording.Duration();
            int    sr              = recording.SampleRate;
            double freqBinWidth    = sr / (double)sonoConfig.WindowSize;
            double framesPerSecond = freqBinWidth;

            // duration of DCT in seconds - want it to be about 3X or 4X the expected maximum period
            double dctDuration = 3 * lbConfig.MaxPeriod;

            // duration of DCT in frames
            int dctLength = (int)Math.Round(framesPerSecond * dctDuration);

            // set up the cosine coefficients
            double[,] cosines = MFCCStuff.Cosines(dctLength, dctLength);

            int upperBandMinBin = (int)Math.Round(lbConfig.UpperBandMinHz / freqBinWidth) + 1;
            int upperBandMaxBin = (int)Math.Round(lbConfig.UpperBandMaxHz / freqBinWidth) + 1;
            int lowerBandMinBin = (int)Math.Round(lbConfig.LowerBandMinHz / freqBinWidth) + 1;
            int lowerBandMaxBin = (int)Math.Round(lbConfig.LowerBandMaxHz / freqBinWidth) + 1;

            BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
            int          rowCount = sonogram.Data.GetLength(0);
            int          colCount = sonogram.Data.GetLength(1);

            double[] lowerArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, lowerBandMinBin, rowCount - 1, lowerBandMaxBin);
            double[] upperArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, upperBandMinBin, rowCount - 1, upperBandMaxBin);

            //lowerArray = DataTools.filterMovingAverage(lowerArray, 3);
            //upperArray = DataTools.filterMovingAverage(upperArray, 3);

            double[] amplitudeScores  = DataTools.SumMinusDifference(lowerArray, upperArray);
            double[] differenceScores = DspFilters.PreEmphasis(amplitudeScores, 1.0);

            // Could smooth here rather than above. Above seemed slightly better?
            amplitudeScores  = DataTools.filterMovingAverage(amplitudeScores, 7);
            differenceScores = DataTools.filterMovingAverage(differenceScores, 7);

            //iii: CONVERT decibel sum-diff SCORES TO ACOUSTIC EVENTS
            var predictedEvents = AcousticEvent.ConvertScoreArray2Events(
                amplitudeScores,
                lbConfig.LowerBandMinHz,
                lbConfig.UpperBandMaxHz,
                sonogram.FramesPerSecond,
                freqBinWidth,
                decibelThreshold,
                lbConfig.MinDuration,
                lbConfig.MaxDuration,
                segmentStartOffset);

            for (int i = 0; i < differenceScores.Length; i++)
            {
                if (differenceScores[i] < 1.0)
                {
                    differenceScores[i] = 0.0;
                }
            }

            // init the score array
            double[] scores = new double[rowCount];

            //iii: CONVERT SCORES TO ACOUSTIC EVENTS
            // var hits = new double[rowCount, colCount];
            double[,] hits = null;

            // init confirmed events
            var confirmedEvents = new List <AcousticEvent>();

            // add names into the returned events
            foreach (var ae in predictedEvents)
            {
                //rowtop,  rowWidth
                int    eventStart       = ae.Oblong.RowTop;
                int    eventWidth       = ae.Oblong.RowWidth;
                int    step             = 2;
                double maximumIntensity = 0.0;

                // scan the event to get oscillation period and intensity
                for (int i = eventStart - (dctLength / 2); i < eventStart + eventWidth - (dctLength / 2); i += step)
                {
                    // Look for oscillations in the difference array
                    double[] differenceArray = DataTools.Subarray(differenceScores, i, dctLength);
                    double   oscilFreq;
                    double   period;
                    double   intensity;
                    Oscillations2014.GetOscillation(differenceArray, framesPerSecond, cosines, out oscilFreq, out period, out intensity);

                    bool periodWithinBounds = period > lbConfig.MinPeriod && period < lbConfig.MaxPeriod;

                    //Console.WriteLine($"step={i}    period={period:f4}");

                    if (!periodWithinBounds)
                    {
                        continue;
                    }

                    for (int j = 0; j < dctLength; j++) //lay down score for sample length
                    {
                        if (scores[i + j] < intensity)
                        {
                            scores[i + j] = intensity;
                        }
                    }

                    if (maximumIntensity < intensity)
                    {
                        maximumIntensity = intensity;
                    }
                }

                // add abbreviatedSpeciesName into event
                if (maximumIntensity >= intensityThreshold)
                {
                    ae.Name             = "L.b";
                    ae.Score_MaxInEvent = maximumIntensity;
                    confirmedEvents.Add(ae);
                }
            }

            //######################################################################

            // calculate the cosine similarity scores
            var scorePlot = new Plot(lbConfig.SpeciesName, scores, intensityThreshold);

            //DEBUG IMAGE this recognizer only. MUST set false for deployment.
            Image debugImage = null;

            if (drawDebugImage)
            {
                // display a variety of debug score arrays
                double[] normalisedScores;
                double   normalisedThreshold;

                //DataTools.Normalise(scores, eventDecibelThreshold, out normalisedScores, out normalisedThreshold);
                //var debugPlot = new Plot("Score", normalisedScores, normalisedThreshold);
                //DataTools.Normalise(upperArray, eventDecibelThreshold, out normalisedScores, out normalisedThreshold);
                //var upperPlot = new Plot("Upper", normalisedScores, normalisedThreshold);
                //DataTools.Normalise(lowerArray, eventDecibelThreshold, out normalisedScores, out normalisedThreshold);
                //var lowerPlot = new Plot("Lower", normalisedScores, normalisedThreshold);
                DataTools.Normalise(amplitudeScores, decibelThreshold, out normalisedScores, out normalisedThreshold);
                var sumDiffPlot = new Plot("SumMinusDifference", normalisedScores, normalisedThreshold);
                DataTools.Normalise(differenceScores, 3.0, out normalisedScores, out normalisedThreshold);
                var differencePlot = new Plot("Difference", normalisedScores, normalisedThreshold);

                var debugPlots = new List <Plot> {
                    scorePlot, sumDiffPlot, differencePlot
                };

                // other debug plots
                //var debugPlots = new List<Plot> { scorePlot, upperPlot, lowerPlot, sumDiffPlot, differencePlot };
                debugImage = DisplayDebugImage(sonogram, confirmedEvents, debugPlots, hits);
            }

            // return new sonogram because it makes for more easy interpretation of the image
            var returnSonoConfig = new SonogramConfig
            {
                SourceFName   = recording.BaseName,
                WindowSize    = 512,
                WindowOverlap = 0,

                // the default window is HAMMING
                //WindowFunction = WindowFunctions.HANNING.ToString(),
                //WindowFunction = WindowFunctions.NONE.ToString(),
                // if do not use noise reduction can get a more sensitive recogniser.
                //NoiseReductionType = NoiseReductionType.NONE,
                NoiseReductionType = SNR.KeyToNoiseReductionType("STANDARD"),
            };
            BaseSonogram returnSonogram = new SpectrogramStandard(returnSonoConfig, recording.WavReader);

            return(Tuple.Create(returnSonogram, hits, scores, confirmedEvents, debugImage));
        } //Analysis()
Exemple #27
0
        /// <summary>
        /// THE KEY ANALYSIS METHOD.
        /// </summary>
        private static Tuple <BaseSonogram, double[, ], double[], List <AcousticEvent>, Image> Analysis(
            AudioRecording recording,
            SonogramConfig sonoConfig,
            LewinsRailConfig lrConfig,
            bool returnDebugImage,
            TimeSpan segmentStartOffset)
        {
            if (recording == null)
            {
                LoggedConsole.WriteLine("AudioRecording == null. Analysis not possible.");
                return(null);
            }

            int sr = recording.SampleRate;

            int upperBandMinHz = lrConfig.UpperBandMinHz;
            int upperBandMaxHz = lrConfig.UpperBandMaxHz;
            int lowerBandMinHz = lrConfig.LowerBandMinHz;
            int lowerBandMaxHz = lrConfig.LowerBandMaxHz;

            //double decibelThreshold = lrConfig.DecibelThreshold;   //dB
            //int windowSize = lrConfig.WindowSize;
            double eventThreshold = lrConfig.EventThreshold; //in 0-1
            double minDuration    = lrConfig.MinDuration;    // seconds
            double maxDuration    = lrConfig.MaxDuration;    // seconds
            double minPeriod      = lrConfig.MinPeriod;      // seconds
            double maxPeriod      = lrConfig.MaxPeriod;      // seconds

            //double freqBinWidth = sr / (double)windowSize;
            double freqBinWidth = sr / (double)sonoConfig.WindowSize;

            //i: MAKE SONOGRAM
            double framesPerSecond = freqBinWidth;

            //the Xcorrelation-FFT technique requires number of bins to scan to be power of 2.
            //assuming sr=17640 and window=1024, then  64 bins span 1100 Hz above the min Hz level. i.e. 500 to 1600
            //assuming sr=17640 and window=1024, then 128 bins span 2200 Hz above the min Hz level. i.e. 500 to 2700

            int upperBandMinBin = (int)Math.Round(upperBandMinHz / freqBinWidth) + 1;
            int upperBandMaxBin = (int)Math.Round(upperBandMaxHz / freqBinWidth) + 1;
            int lowerBandMinBin = (int)Math.Round(lowerBandMinHz / freqBinWidth) + 1;
            int lowerBandMaxBin = (int)Math.Round(lowerBandMaxHz / freqBinWidth) + 1;

            BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
            int          rowCount = sonogram.Data.GetLength(0);
            int          colCount = sonogram.Data.GetLength(1);

            //ALTERNATIVE IS TO USE THE AMPLITUDE SPECTRUM
            //var results2 = DSP_Frames.ExtractEnvelopeAndFFTs(recording.GetWavReader().Samples, sr, frameSize, windowOverlap);
            //double[,] matrix = results2.Item3;  //amplitude spectrogram. Note that column zero is the DC or average energy value and can be ignored.
            //double[] avAbsolute = results2.Item1; //average absolute value over the minute recording
            ////double[] envelope = results2.Item2;
            //double windowPower = results2.Item4;

            double[] lowerArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, lowerBandMinBin, rowCount - 1, lowerBandMaxBin);
            double[] upperArray = MatrixTools.GetRowAveragesOfSubmatrix(sonogram.Data, 0, upperBandMinBin, rowCount - 1, upperBandMaxBin);

            int step         = (int)Math.Round(framesPerSecond); //take one second steps
            int stepCount    = rowCount / step;
            int sampleLength = 64;                               //64 frames = 3.7 seconds. Suitable for Lewins Rail.

            double[] intensity   = new double[rowCount];
            double[] periodicity = new double[rowCount];

            //######################################################################
            //ii: DO THE ANALYSIS AND RECOVER SCORES
            for (int i = 0; i < stepCount; i++)
            {
                int      start         = step * i;
                double[] lowerSubarray = DataTools.Subarray(lowerArray, start, sampleLength);
                double[] upperSubarray = DataTools.Subarray(upperArray, start, sampleLength);
                if (lowerSubarray.Length != sampleLength || upperSubarray.Length != sampleLength)
                {
                    break;
                }

                var spectrum  = AutoAndCrossCorrelation.CrossCorr(lowerSubarray, upperSubarray);
                int zeroCount = 3;
                for (int s = 0; s < zeroCount; s++)
                {
                    spectrum[s] = 0.0;  //in real data these bins are dominant and hide other frequency content
                }

                spectrum = DataTools.NormaliseArea(spectrum);
                int    maxId  = DataTools.GetMaxIndex(spectrum);
                double period = 2 * sampleLength / (double)maxId / framesPerSecond; //convert maxID to period in seconds
                if (period < minPeriod || period > maxPeriod)
                {
                    continue;
                }

                // lay down score for sample length
                for (int j = 0; j < sampleLength; j++)
                {
                    if (intensity[start + j] < spectrum[maxId])
                    {
                        intensity[start + j] = spectrum[maxId];
                    }

                    periodicity[start + j] = period;
                }
            }

            //######################################################################

            //iii: CONVERT SCORES TO ACOUSTIC EVENTS
            intensity = DataTools.filterMovingAverage(intensity, 5);

            var predictedEvents = AcousticEvent.ConvertScoreArray2Events(
                intensity,
                lowerBandMinHz,
                upperBandMaxHz,
                sonogram.FramesPerSecond,
                freqBinWidth,
                eventThreshold,
                minDuration,
                maxDuration,
                segmentStartOffset);

            CropEvents(predictedEvents, upperArray, segmentStartOffset);
            var hits = new double[rowCount, colCount];

            //######################################################################

            var   scorePlot  = new Plot("L.pect", intensity, lrConfig.IntensityThreshold);
            Image debugImage = null;

            if (returnDebugImage)
            {
                // display a variety of debug score arrays
                DataTools.Normalise(intensity, lrConfig.DecibelThreshold, out var normalisedScores, out var normalisedThreshold);
                var intensityPlot = new Plot("Intensity", normalisedScores, normalisedThreshold);
                DataTools.Normalise(periodicity, 10, out normalisedScores, out normalisedThreshold);
                var periodicityPlot = new Plot("Periodicity", normalisedScores, normalisedThreshold);

                var debugPlots = new List <Plot> {
                    scorePlot, intensityPlot, periodicityPlot
                };
                debugImage = DrawDebugImage(sonogram, predictedEvents, debugPlots, hits);
            }

            return(Tuple.Create(sonogram, hits, intensity, predictedEvents, debugImage));
        } //Analysis()
Exemple #28
0
        /// <summary>
        /// HERVE GLOTIN
        /// Combined audio2csv + zooming spectrogram task.
        /// This is used to analyse Herve Glotin's BIRD50 data set.
        /// ############################# IMPORTANT ########################################
        /// In order to analyse the short recordings in BIRD50 dataset, need following change to code:
        /// need to modify    AudioAnalysis.AnalysisPrograms.AcousticIndices.cs #line648
        /// need to change    AnalysisMinSegmentDuration = TimeSpan.FromSeconds(20),
        /// to                AnalysisMinSegmentDuration = TimeSpan.FromSeconds(1),
        /// THIS iS to analyse BIRD50 short recordings.
        /// </summary>
        public static void HiRes1()
        {
            string recordingPath = @"C:\SensorNetworks\WavFiles\TestRecordings\TEST_7min_artificial.wav";

            //// HERVE GLOTIN BIRD50 TRAINING RECORDINGS
            //DirectoryInfo dataDir = new DirectoryInfo(@"D:\SensorNetworks\WavFiles\Glotin\Bird50\AmazonBird50_training_input");
            //string parentDir = @"C:\SensorNetworks\Output\BIRD50";
            //string speciesLabelsFile = parentDir + @"\AmazonBird50_training_output.csv";
            //int speciesCount = 50;
            //////set file name format -depends on train or test. E.g.  "ID0003";
            //string fileStemFormatString = "ID{0:d4}";   // for training files
            //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml";
            //string learningMode = "Train";

            //// HERVE GLOTIN BIRD50 TESTING RECORDINGS
            DirectoryInfo dataDir = new DirectoryInfo(@"D:\SensorNetworks\WavFiles\Glotin\Bird50\AmazonBird50_testing_input");
            string parentDir = @"C:\SensorNetworks\Output\BIRD50";
            string speciesLabelsFile = null;
            int speciesCount = 50;
            ////set file name format -depends on train or test. E.g.  "ID0003";
            string fileStemFormatString = "ID1{0:d3}"; // for testing files
            string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml";
            string learningMode = "Test";

            // HERVE GLOTIN BOMBYX WHALE RECORDINGS
            //DirectoryInfo dataDir = new DirectoryInfo(@"C:\SensorNetworks\WavFiles\WhaleFromGlotin");
            //string parentDir = @"C:\SensorNetworks\Output\Glotin\Bombyx_SpermWhales";
            //string speciesLabelsFile = null;
            //int speciesCount = 0;
            //////set file name format -depends on train or test. E.g.  "ID0003";
            //string fileStemFormatString = null;
            ////string fileStemFormatString = "ID1{0:d3}"; // for testing files
            //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiResGianniPavan.yml";
            //string learningMode = "Train";

            // GIANNI PAVAN SASSAFRAS RECORDINGS
            //DirectoryInfo dataDir = new DirectoryInfo(@"C:\SensorNetworks\WavFiles\GianniPavan\SABIOD - TEST SASSOFRATINO");
            //string parentDir = @"C:\SensorNetworks\Output\GianniPavan";
            //string speciesLabelsFile = null;
            //int speciesCount = 0;
            //string fileStemFormatString = null;
            //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiResGianniPavan.yml";
            //string learningMode = "Train";

            // ######################################################################

            string outputDir = parentDir + @"\" + learningMode;
            string imageOutputDir = parentDir + @"\" + learningMode + "Images";
            string csvDir = outputDir + @"\Towsey.Acoustic";
            string zoomOutputDir = outputDir;

            string audio2csvConfigPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\Towsey.AcousticHiRes.yml";
            string hiResZoomConfigPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\SpectrogramHiResConfig.yml";

            FileInfo[] wavFiles = { new FileInfo(recordingPath) };

            // comment next two lines when debugging a single recording file
            string match = @"*.wav";
            wavFiles = dataDir.GetFiles(match, SearchOption.AllDirectories);

            // READ IN THE SPECIES LABELS FILE AND SET UP THE DATA
            string[] fileID = new string[wavFiles.Length];
            int[] speciesID = new int[speciesCount];
            if (speciesLabelsFile != null)
            {
                BirdClefExperiment1.ReadGlotinsSpeciesLabelFile(speciesLabelsFile, wavFiles.Length, out fileID, out speciesID);
            }
            else // make seperate species name for each file
            {
                speciesID = new int[wavFiles.Length];
            }

            //LOOP THROUGH ALL WAV FILES
            //for (int i = 538; i < 539; i++)
            //for (int i = 0; i < 8; i++)
            for (int i = 0; i < wavFiles.Length; i++)
            {
                FileInfo file = wavFiles[i];
                recordingPath = file.FullName;
                string idName = Path.GetFileNameWithoutExtension(file.FullName);
                string name = string.Format("{0}_Species{1:d2}", idName, speciesID[i]);
                outputDir = parentDir + @"\" + learningMode + @"\" + name;
                csvDir = parentDir + @"\" + learningMode + @"\" + name + @"\Towsey.Acoustic";
                zoomOutputDir = outputDir;
                Console.WriteLine("\n\n");
                Console.WriteLine($@">>>>{i}: File<{name}>");

                try
                {
                    // A: analyse the recording files == audio2csv.
                    var audio2csvArguments = new AnalyseLongRecordings.AnalyseLongRecording.Arguments
                    {
                        Source = recordingPath.ToFileInfo(),
                        Config = audio2csvConfigPath,
                        Output = outputDir.ToDirectoryInfo(),
                    };

                    if (!audio2csvArguments.Source.Exists)
                    {
                        LoggedConsole.WriteWarnLine(" >>>>>>>>>>>> WARNING! The Source Recording file cannot be found! This will cause an exception.");
                    }

                    if (!File.Exists(audio2csvArguments.Config))
                    {
                        LoggedConsole.WriteWarnLine(" >>>>>>>>>>>> WARNING! The Configuration file cannot be found! This will cause an exception.");
                    }

                    AnalyseLongRecordings.AnalyseLongRecording.Execute(audio2csvArguments);

                    // B: Concatenate the summary indices and produce images
                    // Use the Zoomingspectrograms action.

                    // need to find out how long the recording is.
                    string fileName = audio2csvArguments.Source.BaseName();
                    string testFileName = fileName + @"__Towsey.Acoustic.ACI.csv";
                    List<string> data = FileTools.ReadTextFile(Path.Combine(csvDir, testFileName));
                    int lineCount = data.Count - 1;  // -1 for header.
                    int imageWidth = lineCount;

                    //assume scale is index calculation duration = 0.1s
                    // i.e. image resolution  0.1s/px. or 600px/min
                    double focalMinute = (double)lineCount / 600 / 2;
                    if (focalMinute < 0.016666)
                    {
                        focalMinute = 0.016666; // shortest recording = 1 second.
                    }

                    var zoomingArguments = new DrawZoomingSpectrograms.Arguments
                    {
                        // use the default set of index properties in the AnalysisConfig directory.
                        SourceDirectory = csvDir,
                        Output = zoomOutputDir,
                        SpectrogramZoomingConfig = hiResZoomConfigPath,

                        // draw a focused multi-resolution pyramid of images
                        ZoomAction = DrawZoomingSpectrograms.Arguments.ZoomActionType.Focused,
                    };

                    LoggedConsole.WriteLine("# Spectrogram Zooming config  : " + zoomingArguments.SpectrogramZoomingConfig);
                    LoggedConsole.WriteLine("# Input Directory             : " + zoomingArguments.SourceDirectory);
                    LoggedConsole.WriteLine("# Output Directory            : " + zoomingArguments.Output);

                    var common = new ZoomParameters(zoomingArguments.SourceDirectory.ToDirectoryEntry(), zoomingArguments.SpectrogramZoomingConfig.ToFileEntry(), false);
                    var io = FileSystemProvider.GetInputOutputFileSystems(
                        zoomingArguments.SourceDirectory,
                        FileSystemProvider.MakePath(zoomingArguments.Output, common.OriginalBasename, zoomingArguments.OutputFormat, "Tiles"))
                        .EnsureInputIsDirectory();

                    // Create directory if not exists
                    if (!Directory.Exists(zoomingArguments.Output))
                    {
                        Directory.CreateDirectory(zoomingArguments.Output);
                    }

                    ZoomFocusedSpectrograms.DrawStackOfZoomedSpectrograms(
                        zoomingArguments.SourceDirectory.ToDirectoryInfo(),
                        zoomingArguments.Output.ToDirectoryInfo(),
                        io,
                        common,
                        AcousticIndices.TowseyAcoustic,
                        TimeSpan.FromMinutes(focalMinute),
                        imageWidth);

                    // DRAW THE VARIOUS IMAGES
                    string fileStem = fileName;
                    if (fileStemFormatString != null)
                    {
                        fileStem = string.Format(fileStemFormatString, i + 1); // training images
                    }

                    var ldfcSpectrogramArguments = new DrawLongDurationSpectrograms.Arguments
                    {
                        // use the default set of index properties in the AnalysisConfig directory.
                        InputDataDirectory = csvDir,
                        OutputDirectory = imageOutputDir,
                        IndexPropertiesConfig = indexPropertiesConfig,
                    };

                    // there are two possible tasks
                    // 1: draw the aggregated grey scale spectrograms
                    int secDuration = DrawLongDurationSpectrograms.DrawAggregatedSpectrograms(ldfcSpectrogramArguments, fileStem);

                    // 2: draw the coloured ridge spectrograms
                    secDuration = DrawLongDurationSpectrograms.DrawRidgeSpectrograms(ldfcSpectrogramArguments, fileStem);

                    // copy files
                    // POW, EVN, SPT, RHZ, RVT, RPS, RNG
                    // WARNING: POW was removed December 2018
                    string[] copyArray = { "POW", "EVN", "SPT", "RHZ", "RVT", "RPS", "RNG" };
                    DirectoryInfo sourceDirectory = new DirectoryInfo(csvDir);
                    string destinationDirectory = parentDir + @"\TrainingClassifier";
                    foreach (string key in copyArray)
                    {
                        // ID0002__Towsey.Acoustic.BGN.csv    fileName += @"__Towsey.Acoustic.ACI.csv";
                        string sourceFileName = string.Format(idName + "__Towsey.Acoustic." + key + ".csv");
                        string sourcePath = Path.Combine(sourceDirectory.FullName, sourceFileName);
                        string nameOfParentDirectory = sourceDirectory.Parent.Name;
                        string destinationFileName = string.Format(nameOfParentDirectory + "." + key + ".csv");
                        string destinationPath = Path.Combine(destinationDirectory, destinationFileName);
                        File.Copy(sourcePath, destinationPath, true);
                    }
                } // try block
                catch (Exception e)
                {
                    LoggedConsole.WriteErrorLine(string.Format("ERROR!!!!! RECORDING {0}   FILE {1}", i, name));
                    LoggedConsole.WriteErrorLine(string.Format(e.ToString()));
                }
            } // end loop through all wav files
        } // HiRes1()
        /// <summary>
        /// This method compares the acoustic indices derived from two different long duration recordings of the same length.
        ///     It takes as input any number of csv files of acoustic indices in spectrogram columns.
        ///     Typically there will be at least three indices csv files for each of the original recordings to be compared.
        ///     The method produces four spectrogram image files:
        ///     1) A negative false-color spectrogram derived from the indices of recording 1.
        ///     2) A negative false-color spectrogram derived from the indices of recording 2.
        ///     3) A spectrogram of euclidean distances between the two input files.
        ///     4) The above three spectrograms combined in one image.
        /// </summary>
        public static void DrawDistanceSpectrogram(
            DirectoryInfo inputDirectory,
            FileInfo inputFileName1,
            FileInfo inputFileName2,
            DirectoryInfo outputDirectory)
        {
            // PARAMETERS
            string outputFileName1 = inputFileName1.Name;
            var    cs1             = new LDSpectrogramRGB(minuteOffset, xScale, sampleRate, frameWidth, colorMap);

            cs1.ColorMode        = colorMap;
            cs1.BackgroundFilter = backgroundFilterCoeff;
            string[] keys = colorMap.Split('-');
            cs1.ReadCsvFiles(inputDirectory, inputFileName1.Name, keys);
            double blueEnhanceParameter = 0.0;

            cs1.DrawNegativeFalseColorSpectrogram(outputDirectory, outputFileName1, blueEnhanceParameter);
            string imagePath = Path.Combine(outputDirectory.FullName, outputFileName1 + ".COLNEG.png");
            var    spg1Image = Image.Load <Rgb24>(imagePath);

            if (spg1Image == null)
            {
                LoggedConsole.WriteLine("SPECTROGRAM IMAGE DOES NOT EXIST: {0}", imagePath);
                return;
            }

            int nyquist       = cs1.SampleRate / 2;
            int hertzInterval = 1000;

            string title =
                $"FALSE COLOUR SPECTROGRAM: {inputFileName1}.      (scale:hours x kHz)       (colour: R-G-B={cs1.ColorMode})";
            var titleBar = LDSpectrogramRGB.DrawTitleBarOfFalseColourSpectrogram(title, spg1Image.Width);

            spg1Image = LDSpectrogramRGB.FrameLDSpectrogram(
                spg1Image,
                titleBar,
                cs1,
                nyquist,
                hertzInterval);

            string outputFileName2 = inputFileName2.Name;
            var    cs2             = new LDSpectrogramRGB(minuteOffset, xScale, sampleRate, frameWidth, colorMap)
            {
                ColorMode        = colorMap,
                BackgroundFilter = backgroundFilterCoeff,
            };

            cs2.ReadCsvFiles(inputDirectory, inputFileName2.Name, keys);

            // cs2.DrawGreyScaleSpectrograms(opdir, opFileName2);
            cs2.DrawNegativeFalseColorSpectrogram(outputDirectory, outputFileName2, blueEnhanceParameter);
            imagePath = Path.Combine(outputDirectory.FullName, outputFileName2 + ".COLNEG.png");
            var spg2Image = Image.Load <Rgb24>(imagePath);

            if (spg2Image == null)
            {
                LoggedConsole.WriteLine("SPECTROGRAM IMAGE DOES NOT EXIST: {0}", imagePath);
                return;
            }

            title =
                $"FALSE COLOUR SPECTROGRAM: {inputFileName2}.      (scale:hours x kHz)       (colour: R-G-B={cs2.ColorMode})";
            titleBar  = LDSpectrogramRGB.DrawTitleBarOfFalseColourSpectrogram(title, spg2Image.Width);
            spg2Image = LDSpectrogramRGB.FrameLDSpectrogram(
                spg2Image,
                titleBar,
                cs1,
                nyquist,
                hertzInterval);

            string outputFileName4 = inputFileName1 + ".EuclideanDistance.png";
            var    deltaSp         = DrawDistanceSpectrogram(cs1, cs2);

            Color[] colorArray = LDSpectrogramRGB.ColourChart2Array(GetDifferenceColourChart());
            titleBar = DrawTitleBarOfEuclidianDistanceSpectrogram(
                inputFileName1.Name,
                inputFileName2.Name,
                colorArray,
                deltaSp.Width,
                SpectrogramConstants.HEIGHT_OF_TITLE_BAR);
            deltaSp = LDSpectrogramRGB.FrameLDSpectrogram(deltaSp, titleBar, cs2, nyquist, hertzInterval);
            deltaSp.Save(Path.Combine(outputDirectory.FullName, outputFileName4));

            string outputFileName5 = inputFileName1 + ".2SpectrogramsAndDistance.png";

            var combinedImage = ImageTools.CombineImagesVertically(spg1Image, spg2Image, deltaSp);

            combinedImage.Save(Path.Combine(outputDirectory.FullName, outputFileName5));
        }
Exemple #30
0
        public static void ConcatenateDays()
        {
            DirectoryInfo parentDir      = new DirectoryInfo(@"C:\SensorNetworks\Output\Frommolt");
            DirectoryInfo dataDir        = new DirectoryInfo(parentDir + @"\AnalysisOutput\mono");
            var           imageDirectory = new DirectoryInfo(parentDir + @"\ConcatImageOutput");

            //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml";
            DateTimeOffset?startDate          = new DateTimeOffset(2012, 03, 29, 0, 0, 0, TimeSpan.Zero);
            DateTimeOffset?endDate            = new DateTimeOffset(2012, 06, 20, 0, 0, 0, TimeSpan.Zero);
            var            timeSpanOffsetHint = new TimeSpan(01, 0, 0);

            //string fileSuffix = @"2Maps.png";
            //string fileSuffix = @"ACI-ENT-EVN.png";
            // WARNING: POW was removed in December 2018
            string fileSuffix = @"BGN-POW-EVN.png";

            TimeSpan totalTimespan = (DateTimeOffset)endDate - (DateTimeOffset)startDate;
            int      dayCount      = totalTimespan.Days + 1; // assume last day has full 24 hours of recording available.

            bool verbose = true;

            if (verbose)
            {
                LoggedConsole.WriteLine("\n# Start date = " + startDate.ToString());
                LoggedConsole.WriteLine("# End   date = " + endDate.ToString());
                LoggedConsole.WriteLine($"# Elapsed time = {dayCount * 24:f1} hours");
                LoggedConsole.WriteLine("# Day  count = " + dayCount + " (inclusive of start and end days)");
                LoggedConsole.WriteLine("# Time Zone  = " + timeSpanOffsetHint.ToString());
            }

            //string dirMatch = "Monitoring_Rosin_2012*T*+0200_.merged.wav.channel_0.wav";
            string stem     = "Monitoring_Rosin_2012????T??0000+0200_.merged.wav.channel_";
            string dirMatch = stem + "?.wav";

            DirectoryInfo[] subDirectories = dataDir.GetDirectories(dirMatch, SearchOption.AllDirectories);

            string format   = "yyyyMMdd";
            string startDay = ((DateTimeOffset)startDate).ToString(format);

            //string fileMatch = stem + "?__" + fileSuffix;
            //FileInfo[] files = IndexMatrices.GetFilesInDirectories(subDirectories, fileMatch);

            // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo>
            //var sortedDictionaryOfDatesAndFiles = FileDateHelpers.FilterFilesForDates(files, timeSpanOffsetHint);

            //following needed if a day is missing.
            int defaultDayWidth  = 20;
            int defaultDayHeight = 300;

            var  brush      = Color.White;
            Font stringFont = Drawing.Tahoma12;

            var list = new List <Image <Rgb24> >();

            // loop over days
            for (int d = 0; d < dayCount; d++)
            {
                Console.WriteLine($"Day {d} of {dayCount} days");
                var    thisday = ((DateTimeOffset)startDate).AddDays(d);
                string date    = thisday.ToString(format);

                stem = "Monitoring_Rosin_" + date + "T??0000+0200_.merged.wav.channel_";
                string     fileMatch = stem + "?__" + fileSuffix;
                FileInfo[] files     = IndexMatrices.GetFilesInDirectories(subDirectories, fileMatch);
                if (files.Length == 0)
                {
                    Image <Rgb24> gapImage = new Image <Rgb24>(defaultDayWidth, defaultDayHeight);
                    gapImage.Mutate(g5 =>
                    {
                        g5.Clear(Color.Gray);
                        g5.DrawText("Day", stringFont, brush, new PointF(2, 5));
                        g5.DrawText("missing", stringFont, brush, new PointF(2, 35));
                    });

                    list.Add(gapImage);

                    continue;
                }

                // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo>
                //var sortedDictionaryOfDatesAndFiles = FileDateHelpers.FilterFilesForDates(files, timeSpanOffsetHint);

                var image = ConcatenateFourChannelImages(files, imageDirectory, fileSuffix, date);

                defaultDayHeight = image.Height;
                list.Add(image);
            }

            var combinedImage = ImageTools.CombineImagesInLine(list);

            Image <Rgb24> labelImage1 = new Image <Rgb24>(combinedImage.Width, 24);

            labelImage1.Mutate(g1 =>
            {
                g1.Clear(Color.Black);
                g1.DrawText(fileSuffix, stringFont, brush, new PointF(2, 2));
            });

            //labelImage1.Save(Path.Combine(imageDirectory.FullName, suffix1));
            combinedImage.Mutate(g => { g.DrawImage(labelImage1, 0, 0); });
            string fileName = string.Format(startDay + "." + fileSuffix);

            combinedImage.Save(Path.Combine(imageDirectory.FullName, fileName));
        }