//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); }
/// <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); }
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); }
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()
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); }
} //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()
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()
/// <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()
/// <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()
/// <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)); }
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)); }