public static void DrawSummaryIndexFiles( Dictionary <string, double[]> dictionaryOfCsvColumns, IndexGenerationData indexGenerationData, FileInfo indexPropertiesConfigFileInfo, DirectoryInfo opDir, SiteDescription siteDescription, FileInfo sunriseDatafile = null, // no longer supported List <GapsAndJoins> erroneousSegments = null, // info if have fatal errors i.e. no signal bool verbose = false) { var dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; string dateString = $"{dto.Year}{dto.Month:D2}{dto.Day:D2}"; string opFileStem = $"{siteDescription.SiteName}_{dateString}"; // Calculate the index distribution statistics and write to a json file. Also save as png image var indexDistributions = IndexDistributions.WriteSummaryIndexDistributionStatistics(dictionaryOfCsvColumns, opDir, opFileStem); var start = ((DateTimeOffset)indexGenerationData.RecordingStartDate).TimeOfDay; string startTime = $"{start.Hours:d2}{start.Minutes:d2}h"; if (start.Hours == 0 && start.Minutes == 0) { startTime = "midnight"; } string titletext = $"SOURCE: \"{opFileStem}\". Starts at {startTime} {Meta.OrganizationTag}"; Bitmap tracksImage = IndexDisplay.DrawImageOfSummaryIndices( IndexProperties.GetIndexProperties(indexPropertiesConfigFileInfo), dictionaryOfCsvColumns, titletext, indexGenerationData.IndexCalculationDuration, indexGenerationData.RecordingStartDate, erroneousSegments, verbose); var imagePath = FilenameHelpers.AnalysisResultPath(opDir, opFileStem, SummaryIndicesStr, ImgFileExt); tracksImage.Save(imagePath); }
} // method DrawRidgeSpectrograms() public static Image DrawRidgeSpectrograms(DirectoryInfo inputDirectory, FileInfo ipConfig, string fileStem, double scale, Dictionary <string, double[, ]> spectra = null) { string analysisType = AcousticIndices.TowseyAcoustic; //double backgroundFilter = 0.0; // 0.0 means small values are removed. double backgroundFilter = 0.75; // 0.75 means small values are accentuated. var dataScale = TimeSpan.FromSeconds(scale); Dictionary <string, IndexProperties> indexProperties = IndexProperties.GetIndexProperties(ipConfig); string[] keys = SpectralPeakTracks.GetDefaultRidgeKeys(); // read the csv files of the indices in keys array if (spectra == null) { //C:\SensorNetworks\Output\BIRD50\Training\ID0001\Towsey.Acoustic\ID0001__Towsey.Acoustic.ACI spectra = IndexMatrices.ReadSpectralIndices(inputDirectory, fileStem, analysisType, keys); } var cs1 = new LDSpectrogramRGB(minuteOffset: TimeSpan.Zero, xScale: dataScale, sampleRate: 22050, frameWidth: 512, colorMap: null) { FileName = fileStem, BackgroundFilter = backgroundFilter, IndexCalculationDuration = dataScale, }; // set the relevant dictionary of index properties cs1.SetSpectralIndexProperties(indexProperties); cs1.SpectrogramMatrices = spectra; if (cs1.GetCountOfSpectrogramMatrices() == 0) { LoggedConsole.WriteLine("WARNING: " + fileStem + ": No spectrogram matrices in the dictionary. Spectrogram files do not exist?"); return(null); } else if (cs1.GetCountOfSpectrogramMatrices() < keys.Length) { LoggedConsole.WriteLine("WARNING: " + fileStem + ": Missing indices in the dictionary. Some files do not exist?"); return(null); } var stringFont = Drawing.Tahoma8; // constants for labels Color[] color = { Color.Blue, Color.Green, Color.Red, Color.Orange, Color.Purple }; int labelYvalue = 3; int labelIndex = 0; Image <Rgb24> ridges = null; foreach (string key in keys) { Image <Rgb24> greyScaleImage = (Image <Rgb24>)cs1.DrawGreyscaleSpectrogramOfIndex(key); var pixelWidth = greyScaleImage.Width; int height = greyScaleImage.Height; ridges.Mutate(g2 => { if (ridges == null) { ridges = new Image <Rgb24>(pixelWidth, height); g2.Clear(Color.White); } g2.DrawText(key, stringFont, color[labelIndex], new PointF(0, labelYvalue)); }); labelYvalue += 10; //g1.DrawLine(new Pen(Color.Black, 1), 0, 0, width, 0);//draw upper boundary //g1.DrawLine(new Pen(Color.Black, 1), 0, 1, width, 1);//draw upper boundary // transfer greyscale image to colour image for (int y = 0; y < height; y++) { for (int x = 0; x < pixelWidth; x++) { var col = greyScaleImage[x, y]; if (col.G < 150) { ridges[x, y] = color[labelIndex]; } } } labelIndex += 1; } //foreach key return(ridges); } // method DrawRidgeSpectrograms()
} // method DrawAggregatedSpectrograms() public static Image <Rgb24> DrawGrayScaleSpectrograms(Arguments arguments, string fileStem, TimeSpan dataScale, Dictionary <string, double[, ]> spectra = null) { // default values int sampleRate = 22050; int frameWidth = 512; //double backgroundFilter = 0.0; // 0.0 means small values are removed. double backgroundFilter = 0.75; // 0.75 means small values are accentuated. string analysisType = AcousticIndices.TowseyAcoustic; string[] keys = LDSpectrogramRGB.GetArrayOfAvailableKeys(); var inputDirectory = arguments.InputDataDirectory; Dictionary <string, IndexProperties> indexProperties = IndexProperties.GetIndexProperties(arguments.IndexPropertiesConfig.ToFileInfo()); if (spectra == null) { spectra = IndexMatrices.ReadSpectralIndices(inputDirectory.ToDirectoryInfo(), fileStem, analysisType, keys); } // note: the spectra are oriented as per visual orientation, i.e. xAxis = time frames //int frameCount = spectra[keys[0]].GetLength(1); var cs1 = new LDSpectrogramRGB(minuteOffset: TimeSpan.Zero, xScale: dataScale, sampleRate: sampleRate, frameWidth: frameWidth, colorMap: null) { FileName = fileStem, BackgroundFilter = backgroundFilter, IndexCalculationDuration = dataScale, }; cs1.SetSpectralIndexProperties(indexProperties); // set the relevant dictionary of index properties cs1.SpectrogramMatrices = spectra; if (cs1.GetCountOfSpectrogramMatrices() == 0) { LoggedConsole.WriteLine("WARNING: " + fileStem + ": No spectrogram matrices in the dictionary. Spectrogram files do not exist?"); return(null); } var list = new List <Image <Rgb24> >(); var stringFont = Drawing.Arial14; foreach (string key in keys) { var image = cs1.DrawGreyscaleSpectrogramOfIndex(key); int width = 70; int height = image.Height; var label = new Image <Rgb24>(width, height); label.Mutate(g1 => { g1.Clear(Color.Gray); g1.DrawText(key, stringFont, Color.Black, new PointF(4, 30)); g1.DrawLine(new Pen(Color.Black, 1), 0, 0, width, 0); //draw upper boundary g1.DrawLine(new Pen(Color.Black, 1), 0, 1, width, 1); //draw upper boundary }); var imagearray = new[] { label, image }; var labelledImage = ImageTools.CombineImagesInLine(imagearray); list.Add(labelledImage); } //foreach key var combinedImage = ImageTools.CombineImagesVertically(list); return(combinedImage); } // method DrawGrayScaleSpectrograms()
/// <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 wrt to pwd... we don't want to prematurely make asusmptions: // 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 FileInfo indicesPropertiesConfig = IndexProperties.Find(configuration, configFile); if (indicesPropertiesConfig == null || !indicesPropertiesConfig.Exists) { Log.Warn("IndexProperties config can not be found! 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); Bitmap 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}"); }
[DataRow(0.1, 35990)] // at this scale 10 pixels are missing... image is padded by 0 px, and misisng 10px public void TestImagesHaveCorrectLength(double renderScale, int expectedWidth) { // this value just represents a 'render as much as you can until this limit' threshold const int ImageWidth = 1024; var dataScale = 0.1.Seconds(); var imageScale = renderScale.Seconds(); var recordingDuration = 3599.Seconds(); // we simulate rendering the last tile at each resolution var tileDuration = imageScale.Multiply(ImageWidth); var numberOfTiles = (int)Math.Floor(recordingDuration.TotalSeconds / tileDuration.TotalSeconds); var startTime = tileDuration.Multiply(numberOfTiles); // 1 second short of an hour - this is the test case var endTime = recordingDuration; var config = new LdSpectrogramConfig() { // same color map to reduce memory stress for test ColorMap1 = SpectrogramConstants.RGBMap_BGN_PMN_EVN, ColorMap2 = SpectrogramConstants.RGBMap_BGN_PMN_EVN, }; var generationData = new IndexGenerationData() { IndexCalculationDuration = dataScale, FrameLength = 512, FrameStep = 0, RecordingDuration = recordingDuration, }; var indexProperties = IndexProperties.GetIndexProperties(PathHelper.ResolveConfigFile("IndexPropertiesConfig.yml")); // create some fake spectra int duration = (int)(recordingDuration.TotalSeconds / dataScale.TotalSeconds); var spectra = new Dictionary <string, double[, ]>(); foreach (var key in config.GetKeys()) { var values = new double[256, duration].Fill(indexProperties[key].DefaultValue); spectra.Add(key, values); } string basename = "abc"; var image = ZoomCommon.DrawIndexSpectrogramCommon( config, generationData, indexProperties, startTime, endTime, dataScale, imageScale, ImageWidth, spectra, basename); // since we just asked for a fragment of the image at the end, // the expected width is just for that last fragment var lastWidth = expectedWidth - (numberOfTiles * ImageWidth); Assert.That.ImageIsSize(lastWidth, 256, image); Image <Rgb24> bitmap = (Image <Rgb24>)image; // test output for debugging //image.Save("./" + renderScale + ".png"); Assert.That.ImageRegionIsColor(new Rectangle(0, 0, lastWidth, 256), Color.Black, bitmap, 0); }
public static void Execute(Arguments arguments) { var inputDirs = arguments.InputDataDirectories.Select(FileInfoExtensions.ToDirectoryInfo); var output = arguments.OutputDirectory.ToDirectoryInfo(); string date = "# DATE AND TIME: " + DateTime.Now; LoggedConsole.WriteLine("\n# DRAW an EASY IMAGE from consecutive days of SUMMARY INDICES in CSV files."); LoggedConsole.WriteLine("# IT IS ASSUMED THAT THE CSV files are already concatenated into 24 hour files."); LoggedConsole.WriteLine(date); LoggedConsole.WriteLine("# Summary Index.csv files are in directories:"); foreach (DirectoryInfo dir in inputDirs) { LoggedConsole.WriteLine(" {0}", dir.FullName); } LoggedConsole.WriteLine("# Output directory: " + output); if (arguments.StartDate == null) { LoggedConsole.WriteLine("# Start date = NULL (No argument provided). Will revise start date ...."); } else { LoggedConsole.WriteLine("# Start date = " + arguments.StartDate.ToString()); } if (arguments.EndDate == null) { LoggedConsole.WriteLine("# End date = NULL (No argument provided). Will revise end date ...."); } else { LoggedConsole.WriteLine("# End date = " + arguments.EndDate.ToString()); } LoggedConsole.WriteLine("# FILE FILTER = " + arguments.FileFilter); LoggedConsole.WriteLine(); // PATTERN SEARCH FOR SUMMARY INDEX FILES. //string pattern = "*__Towsey.Acoustic.Indices.csv"; FileInfo[] csvFiles = IndexMatrices.GetFilesInDirectories(inputDirs.ToArray(), arguments.FileFilter); //LoggedConsole.WriteLine("# Subdirectories Count = " + subDirectories.Length); LoggedConsole.WriteLine("# SummaryIndexFiles.csv Count = " + csvFiles.Length); if (csvFiles.Length == 0) { LoggedConsole.WriteErrorLine("\n\nWARNING from method DrawEasyImage.Execute():"); LoggedConsole.WriteErrorLine(" No SUMMARY index files were found."); LoggedConsole.WriteErrorLine(" RETURNING EMPTY HANDED!"); return; } // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo> //var sortedDictionaryOfDatesAndFiles = LDSpectrogramStitching.FilterFilesForDates(csvFiles, arguments.TimeSpanOffsetHint); // calculate new start date if passed value = null. DateTimeOffset?startDate = arguments.StartDate; DateTimeOffset?endDate = arguments.EndDate; TimeSpan totalTimespan = (DateTimeOffset)endDate - (DateTimeOffset)startDate; int dayCount = totalTimespan.Days + 1; // assume last day has full 24 hours of recording available. LoggedConsole.WriteLine("\n# Start date = " + startDate.ToString()); LoggedConsole.WriteLine("# End date = " + endDate.ToString()); LoggedConsole.WriteLine(string.Format("# Elapsed time = {0:f1} hours", dayCount * 24)); LoggedConsole.WriteLine("# Day count = " + dayCount + " (inclusive of start and end days)"); LoggedConsole.WriteLine("# Time Zone = " + arguments.TimeSpanOffsetHint.ToString()); // create top level output directory if it does not exist. DirectoryInfo opDir = output; if (!opDir.Exists) { opDir.Create(); } // SET UP DEFAULT SITE LOCATION INFO -- DISCUSS IWTH ANTHONY // The following location data is used only to draw the sunrise/sunset tracks on images. double?latitude = null; double?longitude = null; var siteDescription = new SiteDescription(); siteDescription.SiteName = arguments.FileStemName; siteDescription.Latitude = latitude; siteDescription.Longitude = longitude; // the following required if drawing the index images FileInfo indexPropertiesConfig = null; // require IndexGenerationData and indexPropertiesConfig for drawing //indexGenerationData = IndexGenerationData.GetIndexGenerationData(csvFiles[0].Directory); indexPropertiesConfig = arguments.IndexPropertiesConfig.ToFileInfo(); Dictionary <string, IndexProperties> listOfIndexProperties = IndexProperties.GetIndexProperties(indexPropertiesConfig); Tuple <List <string>, List <double[]> > tuple = CsvTools.ReadCSVFile(csvFiles[0].FullName); var names = tuple.Item1; // default EASY indices int redID = 3; // backgroundNoise int grnID = 5; // avSNROfActiveframes int bluID = 7; // events per second string rep = @"bgn-avsnr-evn"; // ACI Ht Hpeaks EASY indices if (false) { redID = 11; // ACI grnID = 12; // Ht //bluID = 13; // HavgSp //bluID = 14; // Hvariance //bluID = 15; // Hpeaks bluID = 16; // Hcov //bluID = 7; // SPT rep = @"aci-ht-hcov"; //rep = @"aci-ht-spt"; } // LF, MF, HF if (true) { redID = 10; // LF grnID = 9; // MF bluID = 8; // HF rep = @"lf-mf-hf"; } IndexProperties redIndexProps = listOfIndexProperties[names[redID]]; IndexProperties grnIndexProps = listOfIndexProperties[names[grnID]]; IndexProperties bluIndexProps = listOfIndexProperties[names[bluID]]; int dayPixelHeight = 4; int rowCount = (dayPixelHeight * dayCount) + 35; // +30 for grid lines int colCount = 1440; var bitmap = new Image <Rgb24>(colCount, rowCount); var colour = Color.Yellow; int currentRow = 0; var oneDay = TimeSpan.FromHours(24); int graphWidth = colCount; int trackHeight = 20; var stringFont = Drawing.Arial8; string[] monthNames = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; // for drawing the y-axis scale int scaleWidth = trackHeight + 7; var yAxisScale = new Image <Rgb24>(scaleWidth, rowCount + (2 * trackHeight)); yAxisScale.Mutate(g => { g.Clear(Color.Black); // loop over days for (int d = 0; d < dayCount; d++) { var thisday = ((DateTimeOffset)startDate).AddDays(d); if (thisday.Day == 1) { int nextRow = currentRow + 1; for (int c = 0; c < colCount; c++) { bitmap[c, currentRow] = Color.Gray; bitmap[c, nextRow] = Color.Gray; } for (int c = 0; c < scaleWidth; c++) { yAxisScale[c, currentRow + trackHeight] = Color.Gray; yAxisScale[c, nextRow + trackHeight] = Color.Gray; } string month = monthNames[thisday.Month - 1]; if (thisday.Month == 1) // January { g.DrawText(thisday.Year.ToString(), stringFont, Color.White, new PointF(0, nextRow + trackHeight + 1)); //draw time g.DrawText(month, stringFont, Color.White, new PointF(1, nextRow + trackHeight + 11)); //draw time } else { g.DrawText(month, stringFont, Color.White, new PointF(1, nextRow + trackHeight + 1)); //draw time } currentRow += 2; } // get the exact date and time LoggedConsole.WriteLine($"READING DAY {d + 1} of {dayCount}: {thisday.ToString()}"); // CREATE DAY LEVEL OUTPUT DIRECTORY for this day string dateString = $"{thisday.Year}{thisday.Month:D2}{thisday.Day:D2}"; tuple = CsvTools.ReadCSVFile(csvFiles[d].FullName); var arrays = tuple.Item2; var redArray = arrays[redID]; var grnArray = arrays[grnID]; var bluArray = arrays[bluID]; // NormaliseMatrixValues the indices redArray = DataTools.NormaliseInZeroOne(redArray, redIndexProps.NormMin, redIndexProps.NormMax); grnArray = DataTools.NormaliseInZeroOne(grnArray, grnIndexProps.NormMin, grnIndexProps.NormMax); bluArray = DataTools.NormaliseInZeroOne(bluArray, bluIndexProps.NormMin, bluIndexProps.NormMax); for (int c = 0; c < colCount; c++) { for (int r = 0; r < dayPixelHeight; r++) { //transformedValue = Math.Sqrt(redArray[c]); var transformedValue = redArray[c] * redArray[c]; int redVal = (int)Math.Round(transformedValue * 255); if (redVal < 0) { redVal = 0; } else if (redVal > 255) { redVal = 255; } //transformedValue = Math.Sqrt(grnArray[c]); transformedValue = grnArray[c] * grnArray[c]; // square the value int grnVal = (int)Math.Round(transformedValue * 255); if (grnVal < 0) { grnVal = 0; } else if (grnVal > 255) { grnVal = 255; } //transformedValue = Math.Sqrt(bluArray[c]); transformedValue = bluArray[c] * bluArray[c]; // square the value int bluVal = (int)Math.Round(transformedValue * 255); if (bluVal < 0) { bluVal = 0; } else if (bluVal > 255) { bluVal = 255; } bitmap[c, currentRow + r] = Color.FromRgb((byte)redVal, (byte)grnVal, (byte)bluVal); } } // over all columns currentRow += dayPixelHeight; if (thisday.Day % 7 == 0) { for (int c = 0; c < colCount; c++) { bitmap[c, currentRow] = Color.Gray; } currentRow++; } } // over days }); // draw on civil dawn and dusk lines int startdayOfYear = ((DateTimeOffset)startDate).DayOfYear; int endDayOfYear = ((DateTimeOffset)endDate).DayOfYear; SunAndMoon.AddSunRiseSetLinesToImage(bitmap, arguments.BrisbaneSunriseDatafile.ToFileInfo(), startdayOfYear, endDayOfYear, dayPixelHeight); // add the time scales Image <Rgb24> timeBmp1 = ImageTrack.DrawTimeRelativeTrack(oneDay, graphWidth, trackHeight); var imageList = new [] { timeBmp1, bitmap, timeBmp1 }; Image <Rgb24> compositeBmp1 = (Image <Rgb24>)ImageTools.CombineImagesVertically(imageList); imageList = new [] { yAxisScale, compositeBmp1 }; Image <Rgb24> compositeBmp2 = (Image <Rgb24>)ImageTools.CombineImagesInLine(imageList); // indices used for image string indicesDescription = $"{redIndexProps.Name}|{grnIndexProps.Name}|{bluIndexProps.Name}"; string startString = $"{startDate.Value.Year}/{startDate.Value.Month}/{startDate.Value.Day}"; string endString = $"{endDate.Value.Year}/{endDate.Value.Month}/{endDate.Value.Day}"; string title = $"EASY: {arguments.FileStemName} From {startString} to {endString} Indices: {indicesDescription}"; Image <Rgb24> titleBar = ImageTrack.DrawTitleTrack(compositeBmp2.Width, trackHeight, title); imageList = new [] { titleBar, compositeBmp2 }; compositeBmp2 = (Image <Rgb24>)ImageTools.CombineImagesVertically(imageList); var outputFileName = Path.Combine(opDir.FullName, arguments.FileStemName + "." + rep + ".EASY.png"); compositeBmp2.Save(outputFileName); } // Execute()
//############################################################################################################################################################ //############################################################################################################################################################ public static double[,] NormaliseSpectrogramMatrix(IndexProperties indexProperties, double[,] matrix, double backgroundFilterCoeff) { matrix = MatrixTools.NormaliseInZeroOne(matrix, indexProperties.NormMin, indexProperties.NormMax); matrix = MatrixTools.FilterBackgroundValues(matrix, backgroundFilterCoeff); // to de-demphasize the background small values return(matrix); }
} // method DrawAggregatedSpectrograms() public static Image DrawGrayScaleSpectrograms(Arguments arguments, string fileStem, TimeSpan dataScale, Dictionary <string, double[, ]> spectra = null) { int sampleRate = 22050; int frameWidth = 512; //double backgroundFilter = 0.0; // 0.0 means small values are removed. double backgroundFilter = 0.75; // 0.75 means small values are accentuated. string analysisType = AcousticIndices.TowseyAcoustic; string[] keys = LDSpectrogramRGB.GetArrayOfAvailableKeys(); //LoggedConsole.WriteLine("# Spectrogram Config file: " + arguments.SpectrogramConfigPath); //LoggedConsole.WriteLine("# Index Properties Config file: " + arguments.IndexPropertiesConfig); var inputDirectory = arguments.InputDataDirectory; Dictionary <string, IndexProperties> indexProperties = IndexProperties.GetIndexProperties(arguments.IndexPropertiesConfig.ToFileInfo()); if (spectra == null) { //C:\SensorNetworks\Output\BIRD50\Training\ID0001\Towsey.Acoustic\ID0001__Towsey.Acoustic.ACI spectra = IndexMatrices.ReadSpectralIndices(inputDirectory.ToDirectoryInfo(), fileStem, analysisType, keys); } // note: the spectra are oriented as per visual orientation, i.e. xAxis = time frames //int frameCount = spectra[keys[0]].GetLength(1); var cs1 = new LDSpectrogramRGB(minuteOffset: TimeSpan.Zero, xScale: dataScale, sampleRate: sampleRate, frameWidth: frameWidth, colourMap: null) { FileName = fileStem, BackgroundFilter = backgroundFilter, IndexCalculationDuration = dataScale, }; cs1.SetSpectralIndexProperties(indexProperties); // set the relevant dictionary of index properties cs1.SpectrogramMatrices = spectra; if (cs1.GetCountOfSpectrogramMatrices() == 0) { LoggedConsole.WriteLine("WARNING: " + fileStem + ": No spectrogram matrices in the dictionary. Spectrogram files do not exist?"); return(null); } List <Image> list = new List <Image>(); Font stringFont = new Font("Arial", 14); foreach (string key in keys) { var image = cs1.DrawGreyscaleSpectrogramOfIndex(key); int width = 70; int height = image.Height; var label = new Bitmap(width, height); var g1 = Graphics.FromImage(label); g1.Clear(Color.Gray); g1.DrawString(key, stringFont, Brushes.Black, new PointF(4, 30)); g1.DrawLine(new Pen(Color.Black), 0, 0, width, 0); //draw upper boundary g1.DrawLine(new Pen(Color.Black), 0, 1, width, 1); //draw upper boundary Image[] imagearray = { label, image }; var labelledImage = ImageTools.CombineImagesInLine(imagearray); list.Add(labelledImage); } //foreach key var combinedImage = ImageTools.CombineImagesVertically(list.ToArray()); return(combinedImage); } // method DrawGrayScaleSpectrograms()
} // end Main() public static Image <Rgb24> GetImageSlice(string key, List <string> data, int rowId, int colId, int redId, int grnId, int bluId, IndexProperties ipRed, IndexProperties ipGrn, IndexProperties ipBlu, int freqBinCount) { Image <Rgb24> image = null; switch (key) { case KeyFreqBin: image = new Image <Rgb24>(TotalMinutesInDay, TotalDaysInYear); break; case KeyDayOfYear: image = new Image <Rgb24>(TotalMinutesInDay, freqBinCount); break; case KeyMinOfDay: image = new Image <Rgb24>(TotalDaysInYear, freqBinCount); break; } int maxRgbValue = 255; foreach (string line in data) { string[] fields = line.Split(','); int row = int.Parse(fields[rowId]); int col = int.Parse(fields[colId]); // these images must be inverted to show zero Hz at bottom if (key == KeyMinOfDay || key == KeyDayOfYear) { row = freqBinCount - 1 - row; } var redValue = double.Parse(fields[redId]); var grnValue = double.Parse(fields[grnId]); var bluValue = double.Parse(fields[bluId]); redValue = ipRed.NormaliseValue(redValue); grnValue = ipGrn.NormaliseValue(1 - grnValue); // temporal entropy bluValue = ipBlu.NormaliseValue(bluValue); // de-demphasize the background small values //MatrixTools.FilterBackgroundValues(matrix, this.BackgroundFilter); var r = Convert.ToByte(Math.Max(0, redValue * maxRgbValue)); var g = Convert.ToByte(Math.Max(0, grnValue * maxRgbValue)); var b = Convert.ToByte(Math.Max(0, bluValue * maxRgbValue)); var colour = Color.FromRgb(r, g, b); image[col, row] = colour; } image.Mutate(gr => { gr.DrawRectangle(new Pen(Color.DarkGray, 1), 0, 0, image.Width - 1, image.Height - 1); }); return(image); }
/// <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()
[DataRow(0.1, 35990)] // at this scale 10 pixels are missing... image is padded by 0 px, and misisng 10px public void TestImagesHaveCorrectLength(double renderScale, int expectedWidth) { // this value just represents a 'render as much as you can until this limit' threshold const int ImageWidth = 1024; var dataScale = 0.1.Seconds(); var imageScale = renderScale.Seconds(); var recordingDuration = 3599.Seconds(); // we simulate rendering the last tile at each resolution var tileDuration = imageScale.Multiply(ImageWidth); var numberOfTiles = (int)Math.Floor(recordingDuration.TotalSeconds / tileDuration.TotalSeconds); var startTime = tileDuration.Multiply(numberOfTiles); // 1 second short of an hour - this is the test case var endTime = recordingDuration; var config = new LdSpectrogramConfig(); var generationData = new IndexGenerationData() { IndexCalculationDuration = dataScale, FrameLength = 512, FrameStep = 0, RecordingDuration = recordingDuration, }; var indexProperties = IndexProperties.GetIndexProperties(PathHelper.ResolveConfigFile("IndexPropertiesConfig.yml")); // create some fake spectra int duration = (int)(recordingDuration.TotalSeconds / dataScale.TotalSeconds); var spectra = new Dictionary <string, double[, ]>(); foreach (var key in SpectralIndexValues.GetKeys()) { spectra.Add(key, new double[256, duration]); var bgnKey = nameof(SpectralIndexValues.BGN); if (key == bgnKey) { spectra[bgnKey].Fill(indexProperties[bgnKey].DefaultValue); } // due to a bug in DrawRgbColourMatrix the ACI calculation ends up being NaN. // This we fill one of our other spectra to a non-zero value to get the black colour we desire // Bug reference: https://github.com/QutEcoacoustics/audio-analysis/issues/154 var sumKey = nameof(SpectralIndexValues.SUM); if (key == sumKey) { // this forces ACI calculation to 0 / 1 instead of 0 / 0 spectra[sumKey].Fill(1.0); } } string basename = "abc"; var image = ZoomCommon.DrawIndexSpectrogramCommon( config, generationData, indexProperties, startTime, endTime, dataScale, imageScale, ImageWidth, spectra, basename); // since we just asked for a fragment of the image at the end, // the expected width is just for that last fragment var lastWidth = expectedWidth - (numberOfTiles * ImageWidth); Assert.That.ImageIsSize(lastWidth, 256, image); Bitmap bitmap = (Bitmap)image; // test output for debugging //image.Save("./" + renderScale + ".png"); Assert.That.ImageRegionIsColor(new Rectangle(0, 0, lastWidth, 256), Color.Black, bitmap, 0); }
internal static void HandleObjectCreation <T>(IUnifiedIMObject obj) { if (UnifiedSystem.AllowReferences) { UnifiedSystem.ResolveReferences(obj, typeof(T)); } if (UnifiedSystem.UseOmniBase) { lock (UnifiedSystem.OmniBase) UnifiedSystem.OmniBase.Add(obj.ObjectID, obj); } List <string> indexesToAdd = new List <string>(); if (UnifiedSystem.AllowReferences) { List <UnifiedIMReference> refs = new List <UnifiedIMReference>(); if (TypeReferences.ContainsKey(typeof(T))) { refs = TypeReferences[typeof(T)]; } /* * ObjectIndex<IUnifiedIMObject> oi = null; * if (CreateReferenceIndexes) * if (Indexes.ContainsKey(typeof(T))) * oi = Indexes[typeof(T)];*/ foreach (UnifiedIMReference reff in refs) { if (reff.HostType == typeof(T)) { //object value = reff.GetHostProperty(obj); //if(!obj.PropertyStates.ContainsKey(reff.HostProperty)) //obj.PropertyStates.Add(reff.HostProperty, new UIMPropertyState(value)); if (CreateReferenceIndexes && reff.HostProperty != "ObjectID") { // oi.AddIndex(reff.HostProperty, value, obj); indexesToAdd.Add(reff.HostProperty); } } else if (reff.TargetType == typeof(T)) { //object value = reff.GetTargetProperty(obj); //if (!obj.PropertyStates.ContainsKey(reff.TargetProperty)) // obj.PropertyStates.Add(reff.TargetProperty, new UIMPropertyState(value)); if (CreateReferenceIndexes && reff.TargetProperty != "ObjectID") { //oi.AddIndex(reff.TargetProperty, value, obj); indexesToAdd.Add(reff.TargetProperty); } } } } if (AllowIndexes) { if (IndexProperties.ContainsKey(typeof(T))) { indexesToAdd.AddRange(IndexProperties[typeof(T)]); } ObjectIndexer <IUnifiedIMObject> oi = null; if (Indexes.ContainsKey(typeof(T))) { oi = Indexes[typeof(T)]; } if (oi != null) { foreach (string idx in indexesToAdd.Distinct()) { object value = Property.Get(obj, idx); if (!obj.PropertyStates.ContainsKey(idx)) { obj.PropertyStates.Add(idx, new UIMPropertyState(value)); } oi.AddIndex(idx, value, obj); } } } }
public override string ToString() { return(string.Join(", ", IndexProperties.Select(x => x.Name))); }