/// <summary> /// Joins summary indices csv files together. /// This method merges ALL the passed files of acoustic indices /// It is assumed you are concatenating a sequence of consecutive short recordings. /// </summary> public static List <SummaryIndexValues> ConcatenateAllSummaryIndexFiles( FileInfo[] summaryIndexFiles, DirectoryInfo opDir, IndexGenerationData indexGenerationData, string outputFileBaseName) { var indexResolution = indexGenerationData.IndexCalculationDuration; var summaryIndices = IndexMatrices.ConcatenateSummaryIndexFilesWithTimeCheck(summaryIndexFiles, indexResolution); if (summaryIndices.Count == 0) { LoggedConsole.WriteErrorLine("WARNING: LDSpectrogramStitching.ConcatenateAllSummaryIndexFiles(): Empty List of SUMMARY indices returned!"); return(null); } // check length of data and make adjustments if required. // NOTHING done with this info at the moment. Could be used to truncate data to 24 hours. //int totalRowMinutes = (int)Math.Round(summaryIndices.Count() * indexResolution.TotalMinutes); // write out the list of data file names to JSON file. var arrayOfFileNames = summaryIndices.Select(x => x.FileName).ToArray(); var path = FilenameHelpers.AnalysisResultPath(opDir, outputFileBaseName, "FileNames", "json"); Json.Serialise(new FileInfo(path), arrayOfFileNames); return(summaryIndices); }
/// <summary> /// Loads a csv file of summary indices, normalises the values for visualisation and displays a TracksImage /// </summary> /// <param name="arguments"></param> /// <exception cref="InvalidOperationException"></exception> /// <returns></returns> public static void Main(Arguments arguments) { bool verbose = true; if (verbose) { string date = "# DATE AND TIME: " + DateTime.Now; LoggedConsole.WriteLine("# MAKE AN IMAGE FROM A CSV FILE OF SUMMARY INDICES DERIVED FROM AN AUDIO RECORDING"); LoggedConsole.WriteLine(date); LoggedConsole.WriteLine("# Input .csv file: " + arguments.InputCsv); LoggedConsole.WriteLine("# Output image file: " + arguments.Output); LoggedConsole.WriteLine(); } var input = arguments.InputCsv.ToFileInfo(); var output = arguments.Output.ToFileInfo(); output.CreateParentDirectories(); // Find required index generation data var igd = IndexGenerationData.GetIndexGenerationData(input.Directory.ToDirectoryEntry()); // Convert summary indices to image string fileName = input.BaseName(); string title = $"SOURCE:{fileName}, {Meta.OrganizationTag}; "; Image <Rgb24> tracksImage = IndexDisplay.DrawImageOfSummaryIndexTracks( input, arguments.IndexPropertiesConfig.ToFileInfo(), title, igd.IndexCalculationDuration, igd.RecordingStartDate); tracksImage.Save(output.FullName); }
/// <summary> /// This is cut down version of the method of same name in LDSpectrogramRGB.cs. /// </summary> /// <param name="ldSpectrogramConfig">config for ldfc spectrogram.</param> /// <param name="outputDirectory">outputDirectory.</param> /// <param name="indexGenerationData">indexGenerationData.</param> /// <param name="basename">stem name of the original recording.</param> /// <param name="indexSpectrograms">Optional spectra to pass in. If specified the spectra will not be loaded from disk!.</param> private static string DrawSpectrogramsFromSpectralIndices( LdSpectrogramConfig ldSpectrogramConfig, DirectoryInfo outputDirectory, IndexGenerationData indexGenerationData, string basename, Dictionary <string, double[, ]> indexSpectrograms = null) { string colorMap1 = ldSpectrogramConfig.ColorMap1; // SpectrogramConstants.RGBMap_ACI_ENT_EVN; string colorMap2 = ldSpectrogramConfig.ColorMap2; // SpectrogramConstants.RGBMap_BGN_PMN_OSC; double blueEnhanceParameter = ldSpectrogramConfig.BlueEnhanceParameter.Value; var cs1 = new LDSpectrogramRGB(ldSpectrogramConfig, indexGenerationData, colorMap1); string fileStem = basename; cs1.FileName = fileStem; // calculate start time by combining DatetimeOffset with minute offset. cs1.StartOffset = indexGenerationData.AnalysisStartOffset; if (indexGenerationData.RecordingStartDate.HasValue) { DateTimeOffset dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; cs1.RecordingStartDate = dto; if (dto != null) { cs1.StartOffset = dto.TimeOfDay + cs1.StartOffset; } } var indexProperties = IndexCalculateSixOnly.GetIndexProperties(); cs1.SetSpectralIndexProperties(indexProperties); // Load the Index Spectrograms into a Dictionary cs1.LoadSpectrogramDictionary(indexSpectrograms); if (cs1.GetCountOfSpectrogramMatrices() == 0) { Log.Error("No spectrogram matrices in the dictionary. Spectrogram files do not exist?"); throw new InvalidOperationException("Cannot find spectrogram matrix files"); } // draw all available gray scale index spectrograms. var keys = indexProperties.Keys.ToArray(); cs1.DrawGreyScaleSpectrograms(outputDirectory, fileStem, keys); // create two false-color spectrogram images var image1NoChrome = cs1.DrawFalseColorSpectrogramChromeless(cs1.ColorMode, colorMap1, blueEnhanceParameter); var image2NoChrome = cs1.DrawFalseColorSpectrogramChromeless(cs1.ColorMode, colorMap2, blueEnhanceParameter); var spacer = new Image <Rgb24>(image1NoChrome.Width, 10); var imageList = new[] { image1NoChrome, spacer, image2NoChrome, spacer }; Image image3 = ImageTools.CombineImagesVertically(imageList); var outputPath = FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, "2Maps", "png"); image3.Save(outputPath); return(outputPath); }
/// <summary> /// Read in required files. /// We expect a valid indices output directory (the input directory in this action) /// to contain a SpectralIndexStatistics.json and a IndexGenerationData.json file. /// </summary> public static (FileInfo indexGenerationDataFile, FileInfo indexDistributionsFile) CheckNeededFilesExist( DirectoryInfo indicesDirectory) { // MT NOTE: This file (IndexDistributions.json) should not be compulsory requirement for this activity. At the most a warning could be // written to say that file not found. // AT NOTE 2017-11-06: Now loads the file as an optional dependency return( indexGenerationDataFile : IndexGenerationData.FindFile(indicesDirectory), indexDistributionsFile : indicesDirectory.EnumerateFiles("*" + Indices.IndexDistributions.SpectralIndexStatisticsFilenameFragment + "*").SingleOrDefault()); }
/// <summary> /// ONLY Use this concatenation method when you want to concatenate the files for a fixed single day. /// The files to be concatenated must be somewhere in the subdirectory structure of the passed list of data directories /// Read them into a dictionary /// MOST RECENT METHOD TO CONCATENATE Spectral INDEX.CSV FILES - Early September 2015. /// It is designed to deal with Yvonne's case where want to concatenate files distributed over arbitrary directories. /// It only merges files for the passed fixed date. i.e only 24 hours /// </summary> public static void DrawSpectralIndexFiles( Dictionary <string, double[, ]> dictionary, LdSpectrogramConfig sgConfig, IndexGenerationData indexGenerationData, FileInfo indexPropertiesConfigFileInfo, DirectoryInfo opDir, SiteDescription siteDescription, FileInfo sunriseDataFile = null, List <GapsAndJoins> segmentErrors = null) { // derive new indices such as sqrt(PMN), NCDI etc -- main reason for this is to view what their distributions look like. dictionary = IndexMatrices.AddDerivedIndices(dictionary); // Calculate the index distribution statistics and write to a json file. Also save as png image if (indexGenerationData.RecordingStartDate != null) { DateTimeOffset dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; string dateString = $"{dto.Year}{dto.Month:D2}{dto.Day:D2}"; string opFileStem = $"{siteDescription.SiteName}_{dateString}"; var indexDistributions = IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionary, opDir, opFileStem); //SummaryIndexBase[] summaryIndices = null; string analysisType = "Towsey.Acoustic"; Tuple <Image, string>[] tuple = LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( opDir, // topLevelDirectories[0], // this should not be required but it is - because things have gotten complicated ! opDir, sgConfig, indexPropertiesConfigFileInfo, indexGenerationData, opFileStem, analysisType, dictionary, null, //summaryIndices, indexDistributions, siteDescription, sunriseDataFile, segmentErrors, ImageChrome.With); } }
public static void DrawSummaryIndexFiles( Dictionary <string, double[]> dictionaryOfCsvColumns, IndexGenerationData indexGenerationData, FileInfo indexPropertiesConfigFileInfo, DirectoryInfo opDir, SiteDescription siteDescription, FileInfo sunriseDatafile = null, 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, sunriseDatafile, erroneousSegments, verbose); var imagePath = FilenameHelpers.AnalysisResultPath(opDir, opFileStem, SummaryIndicesStr, ImgFileExt); tracksImage.Save(imagePath); }
public static Image <Rgb24> DrawFrameSpectrogramAtScale( LdSpectrogramConfig config, SpectrogramZoomingConfig zoomingConfig, TimeSpan startTimeOfData, TimeSpan frameScale, double[,] frameData, IndexGenerationData indexGeneration, ImageChrome chromeOption) { // TODO: the following normalisation bounds could be passed instead of using hard coded. double min = zoomingConfig.LowerNormalizationBoundForDecibelSpectrograms; double max = zoomingConfig.UpperNormalizationBoundForDecibelSpectrograms; //need to correctly orient the matrix for this method frameData = MatrixTools.MatrixRotate90Clockwise(frameData); // Get an unchromed image var spectrogramImage = ZoomFocusedSpectrograms.DrawStandardSpectrogramInFalseColour(frameData); if (chromeOption == ImageChrome.Without) { return(spectrogramImage); } int nyquist = indexGeneration.SampleRateResampled / 2; int herzInterval = 1000; string title = $"ZOOM SCALE={frameScale.TotalMilliseconds}ms/pixel "; var titleBar = ZoomFocusedSpectrograms.DrawTitleBarOfZoomSpectrogram(title, spectrogramImage.Width); spectrogramImage = ZoomFocusedSpectrograms.FrameZoomSpectrogram( spectrogramImage, titleBar, startTimeOfData, frameScale, config.XAxisTicInterval, nyquist, herzInterval); return(spectrogramImage); }
public static Image DrawIndexSpectrogramCommon( LdSpectrogramConfig config, IndexGenerationData indexGenerationData, Dictionary <string, IndexProperties> indexProperties, TimeSpan startTime, TimeSpan endTime, TimeSpan dataScale, TimeSpan imageScale, int imageWidth, Dictionary <string, double[, ]> spectra, string basename) { double scalingFactor = Math.Round(imageScale.TotalMilliseconds / dataScale.TotalMilliseconds); Contract.Requires(scalingFactor >= 1.0, $"Compression scale `{scalingFactor}`is invalid"); // calculate data duration from column count of abitrary matrix //TimeSpan dataDuration = TimeSpan.FromSeconds(matrix.GetLength(1) * dataScale.TotalSeconds); int columnCount = spectra.FirstValue().GetLength(1); var startIndex = (int)(startTime.Ticks / dataScale.Ticks); var endIndex = (int)(endTime.Ticks / dataScale.Ticks); Contract.Ensures(endIndex <= columnCount); // extract subset of target data var spectralSelection = new Dictionary <string, double[, ]>(); foreach (string key in spectra.Keys) { var matrix = spectra[key]; int rowCount = matrix.GetLength(0); spectralSelection[key] = MatrixTools.Submatrix(matrix, 0, startIndex, rowCount - 1, endIndex - 1); Contract.Ensures( spectralSelection[key].GetLength(1) == (endTime - startTime).Ticks / dataScale.Ticks, "The expected number of frames should be extracted."); } // compress spectrograms to correct scale if (scalingFactor > 1) { // we add rounding to the compression so that fractional pixels get rendered spectralSelection = IndexMatrices.CompressIndexSpectrograms( spectralSelection, imageScale, dataScale, d => Math.Round(d, MidpointRounding.AwayFromZero)); } else { // this else is unnecessary - completely defensive code Contract.Ensures(scalingFactor == 1); } // check that have not compressed matrices to zero length if (spectralSelection.FirstValue().GetLength(0) == 0 || spectralSelection.FirstValue().GetLength(1) == 0) { throw new InvalidOperationException("Spectral matrices compressed to zero size"); } // DEFINE the DEFAULT colour maps for the false-colour spectrograms // Then obtain values from spectrogramDrawingConfig. NOTE: WE REQUIRE LENGTH = 11 chars. string colorMap1 = "ACI-ENT-EVN"; if (config.ColorMap1 != null && config.ColorMap1.Length == 11) { colorMap1 = config.ColorMap1; } string colorMap2 = "BGN-PMN-EVN"; if (config.ColorMap2 != null && config.ColorMap2.Length == 11) { colorMap2 = config.ColorMap2; } double backgroundFilterCoeff = indexGenerationData.BackgroundFilterCoeff; // double colourGain = (double?)configuration.ColourGain ?? SpectrogramConstants.COLOUR_GAIN; // determines colour saturation var cs1 = new LDSpectrogramRGB(config, indexGenerationData, colorMap1) { FileName = basename, BackgroundFilter = backgroundFilterCoeff, }; cs1.SetSpectralIndexProperties(indexProperties); // set the relevant dictionary of index properties cs1.LoadSpectrogramDictionary(spectralSelection); // set up piecewise linear function to determine colour weights var logResolution = Math.Log(imageScale.TotalMilliseconds, 2); double upperResolution = Math.Log(32768, 2); double lowerResolution = Math.Log(256, 2); double range = upperResolution - lowerResolution; double blendWeight1; if (logResolution >= upperResolution) { blendWeight1 = 1.0; } else if (logResolution <= lowerResolution) { blendWeight1 = 0.0; } else { blendWeight1 = (logResolution - lowerResolution) / range; } double blendWeight2 = 1 - blendWeight1; //else if (imageScaleInMsPerPixel > 2000) //{ // blendWeight1 = 0.7; // blendWeight2 = 0.3; //} //else if (imageScaleInMsPerPixel > 1000) //{ // blendWeight1 = 0.3; // blendWeight2 = 0.7; //} //else if (imageScaleInMsPerPixel > 500) //{ // // > 0.5 seconds // blendWeight1 = 0.2; // blendWeight2 = 0.8; //} //else if (imageScaleInMsPerPixel > 300) //{ // // > 0.5 seconds // blendWeight1 = 0.1; // blendWeight2 = 0.9; //} var ldfcSpectrogram = cs1.DrawBlendedFalseColourSpectrogram(colorMap1, colorMap2, blendWeight1, blendWeight2); if (ldfcSpectrogram == null) { throw new InvalidOperationException("Null Image returned from DrawBlendedFalseColourSpectrogram"); } return(ldfcSpectrogram); }
// ############################################################################################################## // ######################### METHODS FOR STITCHING TNC - EDDIE GAME's DATA // ######################### CONCATENATE EVERYTHING /// <summary> /// RECENT METHOD TO CONCATENATE Spectral INDEX.CSV FILES - August 2015. Revised Septermber 2016 /// Was written to deal with EDDIE GAME PNG data where the files to be concatenated are all in one top level directory. /// This method merges all files of spectral indices in the passed directories. /// The total length of the concatenated files can exceed 24 hours - limited by memory! /// </summary> public static Dictionary <string, double[, ]> ConcatenateAllSpectralIndexFiles(DirectoryInfo[] directories, string[] keys, IndexGenerationData indexGenerationData) { string analysisType = "Towsey.Acoustic"; var dictionaryOfSpectralIndices = IndexMatrices.GetSpectralIndexFilesAndConcatenate(directories, analysisType, keys, indexGenerationData, true); if (dictionaryOfSpectralIndices.Count == 0) { LoggedConsole.WriteErrorLine("WARNING from method LDSpectrogramStitching.ConcatenateSpectralIndexFiles() !!!"); LoggedConsole.WriteErrorLine(" An empty dictionary of spectral indices was returned !!! "); return(null); } // now add in derived indices i.e. POW, NCDI etc // dictionaryOfSpectralIndices = IndexMatrices.AddDerivedIndices(dictionaryOfSpectralIndices); return(dictionaryOfSpectralIndices); }
private static (TilingProfile Profile, TimeSpan padding) GetTilingProfile( ZoomParameters common, SpectrogramZoomingConfig zoomConfig, IndexGenerationData indexGeneration, double maxScale) { TilingProfile namingPattern; TimeSpan padding; switch (zoomConfig.TilingProfile) { case nameof(PanoJsTilingProfile): namingPattern = new PanoJsTilingProfile(); padding = TimeSpan.Zero; if (zoomConfig.TileWidth != namingPattern.TileWidth) { throw new ConfigFileException( "TileWidth must match the default PanoJS TileWidth of " + namingPattern.TileWidth); } break; case nameof(AbsoluteDateTilingProfile): // Zooming spectrograms use multiple color profiles at different levels // therefore unable to set a useful tag (like ACI-ENT-EVN). if (indexGeneration.RecordingStartDate != null) { var recordingStartDate = (DateTimeOffset)indexGeneration.RecordingStartDate; var tilingStartDate = GetPreviousTileBoundary( zoomConfig.TileWidth, maxScale, recordingStartDate); padding = recordingStartDate - tilingStartDate; // if we're not writing tiles to disk, omit the basename because whatever the container format is // it will have the base name attached namingPattern = new AbsoluteDateTilingProfile( common.OmitBasename ? string.Empty : common.OriginalBasename, "BLENDED.Tile", tilingStartDate, indexGeneration.FrameLength / 2, zoomConfig.TileWidth); } else { throw new ArgumentNullException( nameof(zoomConfig.TilingProfile), "`RecordingStateDate` from the `IndexGenerationData.json` cannot be null when `AbsoluteDateTilingProfile` specified"); } break; default: throw new ConfigFileException( $"The {nameof(zoomConfig.TilingProfile)} configuration property was set to an unsupported value - no profile known by that name"); } Log.Info($"Tiling had a left padding duration of {padding} to align tiles"); Log.Info( $"Tiling using {namingPattern.GetType().Name}, Tile Width: {namingPattern.TileWidth}, Height: {namingPattern.TileHeight}"); return(namingPattern, padding); }
/// <summary> /// Derives false colour varying-resolution multi-index spectrograms from provided spectral indices. /// </summary> private static void GenerateIndexSpectrogramTiles(double[] indexScales, LdSpectrogramConfig ldsConfig, Dictionary <string, IndexProperties> filteredIndexProperties, SpectrogramZoomingConfig zoomConfig, Dictionary <string, double[, ]> spectra, IndexGenerationData indexGenerationData, string fileStem, TilingProfile tilingProfile, Tiler tiler, TimeSpan alignmentPadding) { // TOP MOST ZOOMED-OUT IMAGES Log.Info("Begin drawwing zooming indec spectrograms"); foreach (double scale in indexScales) { Log.Info("Starting scale: " + scale); if (indexGenerationData.RecordingDuration.TotalSeconds < scale) { Log.Warn($"Skipping scale step {scale} because recording duration ({indexGenerationData.RecordingDuration}) is less than 1px of scale"); continue; } // TODO: eventual optimisation, remove concept of super tiles // TODO: optimisation, cache aggregation layers (currently every layer is reaggregated from base level) TimeSpan imageScale = TimeSpan.FromSeconds(scale); var superTiles = DrawSuperTilesAtScaleFromIndexSpectrograms( ldsConfig, filteredIndexProperties, zoomConfig, imageScale, spectra, indexGenerationData, fileStem, tilingProfile.ChromeOption, alignmentPadding); // tile images as we go Log.Debug("Writing index tiles for " + scale); tiler.TileMany(superTiles); Log.Debug("Completed writing index tiles for " + scale); } }
private static void GenerateStandardSpectrogramTiles( Dictionary <string, double[, ]> spectra, IndexGenerationData indexGeneration, LdSpectrogramConfig ldsConfig, Dictionary <string, IndexProperties> filteredIndexProperties, SpectrogramZoomingConfig zoomConfig, double[] standardScales, string fileStem, TilingProfile namingPattern, Tiler tiler, TimeSpan alignmentPadding) { Log.Info("START DRAWING ZOOMED-IN FRAME SPECTROGRAMS"); TimeSpan dataDuration = TimeSpan.FromTicks(spectra["PMN"].GetLength(1) * indexGeneration.IndexCalculationDuration.Ticks); TimeSpan duration = indexGeneration.RecordingDuration; Contract.Requires( (dataDuration - duration).Absolute() < indexGeneration.IndexCalculationDuration, "The expected amount of data was not supplied"); var minuteCount = (int)Math.Ceiling(dataDuration.TotalMinutes); // window the standard spectrogram generation so that we can provide adjacent supertiles to the // tiler, so that bordering / overlapping tiles (for cases where tile size != multiple of supertile size) // don't render partial tiles (i.e. bad/partial rendering of image) // this is the function generator // use of Lazy means results will only be evaluated once // and only when needed. This is useful for sliding window. Lazy <TimeOffsetSingleLayerSuperTile[]> GenerateStandardSpectrogramGenerator(int minuteToLoad) { return(new Lazy <TimeOffsetSingleLayerSuperTile[]>( () => { Log.Info("Starting generation for minute: " + minuteToLoad); var superTilingResults = DrawSuperTilesFromSingleFrameSpectrogram( null /*inputDirectory*/, //TODO:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ldsConfig, filteredIndexProperties, zoomConfig, minuteToLoad, standardScales, fileStem, indexGeneration, namingPattern.ChromeOption, alignmentPadding); return superTilingResults; })); } Lazy <TimeOffsetSingleLayerSuperTile[]> previous = null; Lazy <TimeOffsetSingleLayerSuperTile[]> current = null; Lazy <TimeOffsetSingleLayerSuperTile[]> next = null; for (int minute = 0; minute < minuteCount; minute++) { Log.Trace("Starting loop for minute" + minute); // shift each value back previous = current; current = next ?? GenerateStandardSpectrogramGenerator(minute); next = minute + 1 < minuteCount?GenerateStandardSpectrogramGenerator(minute + 1) : null; // for each scale level of the results for (int i = 0; i < current.Value.Length; i++) { // finally tile the output Log.Debug("Begin tile production for minute: " + minute); tiler.Tile(previous?.Value[i], current.Value[i], next?.Value[i]); Log.Debug("Begin tile production for minute: " + minute); } } }
[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); }
public void TestAnalyzeSr22050Recording() { int sampleRate = 22050; double duration = 420; // signal duration in seconds = 7 minutes int[] harmonics = { 500, 1000, 2000, 4000, 8000 }; var recording = DspFilters.GenerateTestRecording(sampleRate, duration, harmonics, WaveType.Cosine); var recordingPath = this.outputDirectory.CombineFile("TemporaryRecording1.wav"); WavWriter.WriteWavFileViaFfmpeg(recordingPath, recording.WavReader); // draw the signal as spectrogram just for debugging purposes /* * var fst = FreqScaleType.Linear; * var freqScale = new FrequencyScale(fst); * var sonoConfig = new SonogramConfig * { * WindowSize = 512, * WindowOverlap = 0.0, * SourceFName = recording.BaseName, * NoiseReductionType = NoiseReductionType.Standard, * NoiseReductionParameter = 2.0, * }; * var sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); * var image = sonogram.GetImageFullyAnnotated(sonogram.GetImage(), "SPECTROGRAM", freqScale.GridLineLocations); * var outputImagePath = this.outputDirectory.CombineFile("Signal1_LinearFreqScale.png"); * image.Save(outputImagePath.FullName); */ var configPath = PathHelper.ResolveConfigFile("Towsey.Acoustic.yml"); var arguments = new AnalyseLongRecording.Arguments { Source = recordingPath, Config = configPath.FullName, Output = this.outputDirectory, MixDownToMono = true, Parallel = !Debugger.IsAttached, }; AnalyseLongRecording.Execute(arguments); var resultsDirectory = this.outputDirectory.Combine("Towsey.Acoustic"); var listOfFiles = resultsDirectory.EnumerateFiles().ToArray(); Assert.AreEqual(38, listOfFiles.Length); var csvCount = listOfFiles.Count(f => f.Name.EndsWith(".csv")); Assert.AreEqual(15, csvCount); var jsonCount = listOfFiles.Count(f => f.Name.EndsWith(".json")); Assert.AreEqual(2, jsonCount); var pngCount = listOfFiles.Count(f => f.Name.EndsWith(".png")); Assert.AreEqual(21, pngCount); var twoMapsImagePath = resultsDirectory.CombineFile("TemporaryRecording1__2Maps.png"); var twoMapsImage = Image.Load <Rgb24>(twoMapsImagePath.FullName); // image is 7 * 632 Assert.AreEqual(7, twoMapsImage.Width); Assert.AreEqual(632, twoMapsImage.Height); var bgnFile = resultsDirectory.CombineFile("TemporaryRecording1__Towsey.Acoustic.BGN.csv"); double[,] actualBgn = Csv.ReadMatrixFromCsv <double>(bgnFile, TwoDimensionalArray.None); var expectedSpectrumFile = PathHelper.ResolveAsset("LongDuration", "BgnMatrix.LinearScale.csv"); // uncomment the following line when first produce the array // bgnFile.CopyTo(expectedSpectrumFile.FullName); // compare actual BGN file with expected file. var expectedBgn = Csv.ReadMatrixFromCsv <double>(expectedSpectrumFile, TwoDimensionalArray.None); CollectionAssert.That.AreEqual(expectedBgn, actualBgn, 0.000_000_001); var array = MatrixTools.GetRow(actualBgn, 0); Assert.AreEqual(7, expectedBgn.RowLength()); Assert.AreEqual(256, array.Length); // draw array just to check peaks are in correct places - just for debugging purposes var ldsBgnSpectrumFile = this.outputDirectory.CombineFile("Spectrum1.png"); GraphsAndCharts.DrawGraph(array, "LD BGN SPECTRUM Linear", ldsBgnSpectrumFile); var generationData = Json.Deserialize <IndexGenerationData>(IndexGenerationData.FindFile(resultsDirectory)); Assert.AreEqual("TemporaryRecording1", generationData.RecordingBasename); }
/// <summary> /// This method can add in absolute time if you want. /// Currently commented out - see below. /// </summary> public static Image <Rgb24> DrawIndexSpectrogramAtScale( LdSpectrogramConfig config, IndexGenerationData indexGenerationData, Dictionary <string, IndexProperties> indexProperties, TimeSpan focalTime, TimeSpan dataScale, TimeSpan imageScale, int imageWidth, Dictionary <string, double[, ]> spectra, string basename) { if (spectra == null) { LoggedConsole.WriteLine("WARNING: NO SPECTRAL DATA SUPPLIED"); return(null); } // check that scalingFactor >= 1.0 double scalingFactor = Math.Round(imageScale.TotalMilliseconds / dataScale.TotalMilliseconds); if (scalingFactor < 1.0) { LoggedConsole.WriteLine("WARNING: Scaling Factor < 1.0"); return(null); } Dictionary <string, IndexProperties> dictIp = indexProperties; dictIp = InitialiseIndexProperties.FilterIndexPropertiesForSpectralOnly(dictIp); // calculate start time by combining DatetimeOffset with minute offset. TimeSpan sourceMinuteOffset = indexGenerationData.AnalysisStartOffset; if (indexGenerationData.RecordingStartDate.HasValue) { DateTimeOffset dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; sourceMinuteOffset = dto.TimeOfDay + sourceMinuteOffset; } // calculate data duration from column count of abitrary matrix var kvp = spectra.First(); var matrix = kvp.Value; //var matrix = spectra["ACI"]; // assume this key will always be present!! TimeSpan dataDuration = TimeSpan.FromSeconds(matrix.GetLength(1) * dataScale.TotalSeconds); TimeSpan recordingStartTime = TimeSpan.Zero; // default = zero minute of day i.e. midnight recordingStartTime = indexGenerationData.RecordingStartDate.Value.TimeOfDay.Add(indexGenerationData.AnalysisStartOffset); TimeSpan offsetTime = TimeSpan.Zero; TimeSpan imageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks); TimeSpan halfImageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks / 2); TimeSpan startTime = TimeSpan.Zero; if (focalTime != TimeSpan.Zero) { startTime = focalTime - halfImageDuration; } if (startTime < TimeSpan.Zero) { offsetTime = TimeSpan.Zero - startTime; startTime = TimeSpan.Zero; } TimeSpan endTime = imageDuration; if (focalTime != TimeSpan.Zero) { endTime = focalTime + halfImageDuration; } if (endTime > dataDuration) { endTime = dataDuration; } TimeSpan spectrogramDuration = endTime - startTime; int spectrogramWidth = (int)(spectrogramDuration.Ticks / imageScale.Ticks); // get the plain unchromed spectrogram var ldfcSpectrogram = ZoomCommon.DrawIndexSpectrogramCommon( config, indexGenerationData, indexProperties, startTime, endTime, dataScale, imageScale, imageWidth, spectra, basename); if (ldfcSpectrogram == null) { LoggedConsole.WriteLine("WARNING: NO SPECTROGRAM AT SCALE " + imageScale); return(null); } // now chrome spectrogram ldfcSpectrogram.Mutate(g2 => { // draw red line at focus time if (focalTime != TimeSpan.Zero) { Pen pen = new Pen(Color.Red, 1); TimeSpan focalOffset = focalTime - startTime; int x1 = (int)(focalOffset.Ticks / imageScale.Ticks); g2.DrawLine(pen, x1, 0, x1, ldfcSpectrogram.Height); } }); // draw the title bar int nyquist = 22050 / 2; // default if (indexGenerationData.SampleRateResampled > 0) { nyquist = indexGenerationData.SampleRateResampled / 2; } int herzInterval = 1000; if (config != null) { herzInterval = config.YAxisTicInterval; } string title = $"SCALE={imageScale.TotalSeconds}s/px. Duration={spectrogramDuration} "; //add chrome // NEXT LINE USED ONLY IF WANT ABSOLUTE TIME //startTime += recordingStartTime; var titleBar = DrawTitleBarOfZoomSpectrogram(title, ldfcSpectrogram.Width); ldfcSpectrogram = FrameZoomSpectrogram( ldfcSpectrogram, titleBar, startTime, imageScale, config.XAxisTicInterval, nyquist, herzInterval); // create the base canvas image on which to centre the focal image var image = Drawing.NewImage(imageWidth, ldfcSpectrogram.Height, Color.DarkGray); int xOffset = (int)(offsetTime.Ticks / imageScale.Ticks); image.Mutate(g1 => g1.DrawImage(ldfcSpectrogram, new Point(xOffset, 0), 1)); return(image); }
/// <summary> /// This method can add in the absolute recording start time. However currently disabled. /// </summary> /// <param name="config">v.</param> /// <param name="indexGenerationData">indexGenerationData.</param> /// <param name="startTimeOfData">startTimeOfData.</param> /// <param name="compressionFactor">compressionFactor.</param> /// <param name="frameData">frameData.</param> /// <param name="indexData">indexData.</param> /// <param name="focalTime">focalTime.</param> /// <param name="frameScale">frameScale.</param> /// <param name="imageWidth">imageWidth.</param> public static Image DrawFrameSpectrogramAtScale( LdSpectrogramConfig config, IndexGenerationData indexGenerationData, TimeSpan startTimeOfData, int compressionFactor, List <double[]> frameData, double[,] indexData, TimeSpan focalTime, TimeSpan frameScale, int imageWidth) { if (frameData == null || frameData.Count == 0) { LoggedConsole.WriteLine("WARNING: NO SPECTRAL SPECTROGRAM DATA SUPPLIED"); return(null); } // var recordingStartTime = TimeSpan.Zero; // default = zero minute of day i.e. midnight // var recordingStartTime = TimeTools.DateTimePlusTimeSpan(indexGenerationData.RecordingStartDate, indexGenerationData.AnalysisStartOffset); TimeSpan imageScale = TimeSpan.FromTicks(frameScale.Ticks * compressionFactor); TimeSpan imageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks); TimeSpan halfImageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks / 2); TimeSpan startTime = focalTime - halfImageDuration; if (startTime < TimeSpan.Zero) { startTime = TimeSpan.Zero; } int startIndex = (int)((startTime.Ticks - startTimeOfData.Ticks) / frameScale.Ticks); int requiredFrameCount = imageWidth * compressionFactor; List <double[]> frameSelection = frameData.GetRange(startIndex, requiredFrameCount); double[,] spectralSelection = MatrixTools.ConvertList2Matrix(frameSelection); // compress spectrograms to correct scale if (compressionFactor > 1) { spectralSelection = TemporalMatrix.CompressFrameSpectrograms(spectralSelection, compressionFactor); } var spectrogramImage = DrawStandardSpectrogramInFalseColour(spectralSelection); int x1 = (int)(halfImageDuration.Ticks / imageScale.Ticks); spectrogramImage.Mutate(g2 => { // draw focus time on image if (focalTime != TimeSpan.Zero) { Pen pen = new Pen(Color.Red, 1); g2.DrawLine(pen, x1, 0, x1, spectrogramImage.Height); } }); int nyquist = 22050 / 2; // default if (indexGenerationData.SampleRateResampled > 0) { nyquist = indexGenerationData.SampleRateResampled / 2; } int herzInterval = config.YAxisTicInterval; string title = $"ZOOM SCALE={imageScale.TotalMilliseconds}ms/pixel Image duration={imageDuration} "; var titleBar = DrawTitleBarOfZoomSpectrogram(title, spectrogramImage.Width); // add the recording start time ONLY IF WANT ABSOLUTE TIME SCALE - obtained from info in file name // startTime += recordingStartTime; spectrogramImage = FrameZoomSpectrogram(spectrogramImage, titleBar, startTime, imageScale, config.XAxisTicInterval, nyquist, herzInterval); // MAY WANT THESE CLIPPING TRACKS AT SOME POINT // read high amplitude and clipping info into an image //string indicesFile = Path.Combine(configuration.InputDirectoryInfo.FullName, fileStem + ".csv"); //string indicesFile = Path.Combine(config.InputDirectoryInfo.FullName, fileStem + ".Indices.csv"); //string indicesFile = Path.Combine(configuration.InputDirectoryInfo.FullName, fileStem + "_" + configuration.AnalysisType + ".csv"); //Image imageX = DrawSummaryIndices.DrawHighAmplitudeClippingTrack(indicesFile.ToFileInfo()); //if (null != imageX) imageX.Save(Path.Combine(outputDirectory.FullName, fileStem + ".ClipHiAmpl.png")); // create the base image Image image = new Image <Rgb24>(imageWidth, spectrogramImage.Height); image.Mutate(g1 => { g1.Clear(Color.DarkGray); //int xOffset = (int)(startTime.Ticks / imageScale.Ticks); int xOffset = (imageWidth / 2) - x1; g1.DrawImage(spectrogramImage, new Point(xOffset, 0), 1); }); return(image); }
/// <summary> /// Draws FC index spectrograms for an entire row. /// </summary> public static TimeOffsetSingleLayerSuperTile[] DrawSuperTilesAtScaleFromIndexSpectrograms( LdSpectrogramConfig analysisConfig, Dictionary <string, IndexProperties> indexProperties, SpectrogramZoomingConfig zoomingConfig, TimeSpan imageScale, Dictionary <string, double[, ]> spectra, IndexGenerationData indexGeneration, string basename, ImageChrome chromeOption, TimeSpan alignmentPadding) { Contract.Requires(!spectra.IsNullOrEmpty(), "ERROR: NO SPECTRAL DATA SUPPLIED"); // calculate source data duration from column count of arbitrary matrix TimeSpan dataScale = indexGeneration.IndexCalculationDuration; double[,] matrix = spectra.First().Value; TimeSpan sourceDataDuration = TimeSpan.FromSeconds(matrix.GetLength(1) * dataScale.TotalSeconds); int tileWidth = zoomingConfig.TileWidth; int superTileWidth = zoomingConfig.SuperTileWidthDefault(); var superTileCount = (int)Math.Ceiling(zoomingConfig.SuperTileCount(sourceDataDuration, imageScale.TotalSeconds)); TimeSpan superTileDuration = TimeSpan.FromTicks(superTileWidth * imageScale.Ticks); // initialize the image array to return var superTiles = new TimeOffsetSingleLayerSuperTile[superTileCount]; // sometimes the whole recording is not analyzed. In this case, jump the time index forward. TimeSpan startTime = indexGeneration.AnalysisStartOffset; // start the loop for (int t = 0; t < superTileCount; t++) { var image = DrawOneScaledIndexSpectrogramTile( analysisConfig, indexGeneration, indexProperties, startTime, dataScale, imageScale, superTileWidth, spectra, basename, chromeOption); superTiles[t] = new TimeOffsetSingleLayerSuperTile( durationToPreviousTileBoundaryAtUnitScale: alignmentPadding, spectrogramType: SpectrogramType.Index, scale: imageScale, image: image.CloneAs <Rgba32>(), timeOffset: startTime); startTime += superTileDuration; if (startTime > sourceDataDuration) { break; } } return(superTiles); }
public static Image <Rgb24> DrawOneScaledIndexSpectrogramTile( LdSpectrogramConfig config, IndexGenerationData indexGenerationData, Dictionary <string, IndexProperties> indexProperties, TimeSpan startTime, TimeSpan dataScale, TimeSpan imageScale, int superTileImageWidth, Dictionary <string, double[, ]> spectra, string basename, ImageChrome chromeOption) { Contract.Requires(!spectra.IsNullOrEmpty()); // calculate data duration from column count of abitrary matrix var matrix = spectra.First().Value; int columnCount = matrix.GetLength(1); TimeSpan dataDuration = TimeSpan.FromSeconds(columnCount * dataScale.TotalSeconds); var analysisStartTime = indexGenerationData.RecordingStartDate.Value.TimeOfDay.Add(indexGenerationData.AnalysisStartOffset); TimeSpan offsetTime = TimeSpan.Zero; TimeSpan imageDuration = TimeSpan.FromTicks(superTileImageWidth * imageScale.Ticks); TimeSpan halfImageDuration = TimeSpan.FromTicks(superTileImageWidth * imageScale.Ticks / 2); if (startTime < TimeSpan.Zero) { offsetTime = TimeSpan.Zero - startTime; startTime = TimeSpan.Zero; } TimeSpan endTime = startTime + imageDuration; if (endTime > dataDuration) { endTime = dataDuration; } // get the plain unchromed spectrogram var ldSpectrogram = ZoomCommon.DrawIndexSpectrogramCommon( config, indexGenerationData, indexProperties, startTime, endTime, dataScale, imageScale, superTileImageWidth, spectra, basename); if (chromeOption == ImageChrome.Without) { return(ldSpectrogram); } int nyquist = 22050 / 2; if (indexGenerationData.SampleRateResampled > 0) { nyquist = indexGenerationData.SampleRateResampled / 2; } int hertzInterval = 1000; if (config != null) { hertzInterval = config.YAxisTicInterval; } string title = $"ZOOM SCALE={imageScale.TotalSeconds}s/pixel"; var titleBar = ZoomFocusedSpectrograms.DrawTitleBarOfZoomSpectrogram(title, ldSpectrogram.Width); startTime += analysisStartTime; ldSpectrogram = ZoomFocusedSpectrograms.FrameZoomSpectrogram( ldSpectrogram, titleBar, startTime, imageScale, config.XAxisTicInterval, nyquist, hertzInterval); // create the base image var image = Drawing.NewImage(ldSpectrogram.Width, ldSpectrogram.Height, Color.DarkGray); var xOffset = (int)(offsetTime.Ticks / imageScale.Ticks); image.Mutate(g1 => { g1.DrawImage(ldSpectrogram, new Point(xOffset, 0), 1); }); return(image); }
/// <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); var spectrogramImage = DrawFrameSpectrogramAtScale( analysisConfig, zoomingConfig, startTimeOfData, imageScale, data, indexGeneration, chromeOption); str[scale] = new TimeOffsetSingleLayerSuperTile( alignmentPadding, SpectrogramType.Frame, imageScale, spectrogramImage.CloneAs <Rgba32>(), startTimeOfData); } return(str); }
private DirectoryInfo CreateTestData(DirectoryInfo output) { // create 11 "day"s of data, with three spectral ribbon variants var sourceDirectory = output.CreateSubdirectory("FakeIndices"); var firstDate = new DateTimeOffset(2019, 4, 18, 0, 0, 0, TimeSpan.FromHours(10)); CreateDay(Increment(0), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(1), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(1), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(1), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(1), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(1), "ACI-ENT-EVN", "BGN-PMN-OSC", Color.Red, Color.Blue); CreateDay(Increment(2), "ACI-ENT-EVN", "ENT-CVR-OSC", Color.Red, Color.Green); CreateDay(Increment(1), "ACI-ENT-EVN", "ENT-CVR-OSC", Color.Red, Color.Green); CreateDay(Increment(1), "ACI-ENT-EVN", "ENT-CVR-OSC", Color.Red, Color.Green); CreateDay(Increment(1), "ACI-ENT-EVN", "ENT-CVR-OSC", Color.Red, Color.Green); CreateDay(Increment(1), "ACI-ENT-EVN", "ENT-CVR-OSC", Color.Red, Color.Green); return(sourceDirectory); DateTimeOffset Increment(int days) { firstDate = firstDate.AddDays(days); return(firstDate); } void CreateDay(DateTimeOffset startDate, string colorMap1, string colorMap2, Rgb24 color1, Rgb24 color2) { var basename = startDate.ToIso8601SafeString(); var extension = Random.NextChoice(".wav", ".mp3", ".flac"); var data = new IndexGenerationData() { //Source = this.outputDirectory.CombineFile(name), AnalysisStartOffset = TimeSpan.Zero, BackgroundFilterCoeff = SpectrogramConstants.BACKGROUND_FILTER_COEFF, BgNoiseNeighbourhood = IndexCalculateConfig.DefaultBgNoiseNeighborhood.Seconds(), FrameLength = 512, FrameStep = 512, IndexCalculationDuration = 60.Seconds(), LongDurationSpectrogramConfig = new LdSpectrogramConfig() { ColorMap1 = colorMap1, ColorMap2 = colorMap2, }, MaximumSegmentDuration = 60.Seconds(), RecordingBasename = basename, RecordingDuration = TimeSpan.FromHours(24), RecordingExtension = extension, RecordingStartDate = startDate, SampleRateOriginal = 22050, SampleRateResampled = 22050, }; var icdPath = FilenameHelpers.AnalysisResultPath( sourceDirectory, basename, IndexGenerationData.FileNameFragment, "json"); Json.Serialise(icdPath.ToFileInfo(), data); var ribbon = new Image <Rgb24>(Configuration.Default, 1440, LdSpectrogramRibbons.RibbonPlotHeight, color1); ribbon.Save(FilenameHelpers.AnalysisResultPath(sourceDirectory, basename, colorMap1 + LdSpectrogramRibbons.SpectralRibbonTag, "png")); ribbon = new Image <Rgb24>(Configuration.Default, 1440, LdSpectrogramRibbons.RibbonPlotHeight, color2); ribbon.Save(FilenameHelpers.AnalysisResultPath(sourceDirectory, basename, colorMap2 + LdSpectrogramRibbons.SpectralRibbonTag, "png")); } }
[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 void SummariseResults(AnalysisSettings settings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results) { var acousticIndicesConfig = (AcousticIndicesConfig)settings.AnalysisAnalyzerSpecificConfiguration; var sourceAudio = inputFileSegment.Source; var resultsDirectory = AnalysisCoordinator.GetNamedDirectory(settings.AnalysisOutputDirectory, this); bool tileOutput = acousticIndicesConfig.TileOutput; var frameWidth = acousticIndicesConfig.FrameLength; int sampleRate = AppConfigHelper.DefaultTargetSampleRate; sampleRate = acousticIndicesConfig.ResampleRate ?? sampleRate; // Gather settings for rendering false color spectrograms var ldSpectrogramConfig = acousticIndicesConfig.LdSpectrogramConfig; string basename = Path.GetFileNameWithoutExtension(sourceAudio.Name); // output to disk (so other analyzers can use the data, // only data - configuration settings that generated these indices // this data can then be used by post-process analyses /* NOTE: The value for FrameStep is used only when calculating a standard spectrogram * FrameStep is NOT used when calculating Summary and Spectral indices. */ var indexConfigData = new IndexGenerationData() { RecordingExtension = inputFileSegment.Source.Extension, RecordingBasename = basename, RecordingStartDate = inputFileSegment.TargetFileStartDate, RecordingDuration = inputFileSegment.TargetFileDuration.Value, SampleRateOriginal = inputFileSegment.TargetFileSampleRate.Value, SampleRateResampled = sampleRate, FrameLength = frameWidth, FrameStep = settings.Configuration.GetIntOrNull(AnalysisKeys.FrameStep) ?? frameWidth, IndexCalculationDuration = acousticIndicesConfig.IndexCalculationDurationTimeSpan, BgNoiseNeighbourhood = acousticIndicesConfig.BgNoiseBuffer, AnalysisStartOffset = inputFileSegment.SegmentStartOffset ?? TimeSpan.Zero, MaximumSegmentDuration = settings.AnalysisMaxSegmentDuration, BackgroundFilterCoeff = SpectrogramConstants.BACKGROUND_FILTER_COEFF, LongDurationSpectrogramConfig = ldSpectrogramConfig, }; var icdPath = FilenameHelpers.AnalysisResultPath( resultsDirectory, basename, IndexGenerationData.FileNameFragment, "json"); Json.Serialise(icdPath.ToFileInfo(), indexConfigData); // gather spectra to form spectrograms. Assume same spectra in all analyzer results // this is the most efficient way to do this // gather up numbers and strings store in memory, write to disk one time // this method also AUTOMATICALLY SORTS because it uses array indexing var dictionaryOfSpectra = spectralIndices.ToTwoDimensionalArray(SpectralIndexValues.CachedSelectors, TwoDimensionalArray.Rotate90ClockWise); // Calculate the index distribution statistics and write to a json file. Also save as png image var indexDistributions = IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionaryOfSpectra, resultsDirectory, basename); // HACK: do not render false color spectrograms unless IndexCalculationDuration = 60.0 (the normal resolution) if (acousticIndicesConfig.IndexCalculationDurationTimeSpan != 60.0.Seconds()) { Log.Warn("False color spectrograms were not rendered"); } else { FileInfo indicesPropertiesConfig = acousticIndicesConfig.IndexPropertiesConfig.ToFileInfo(); // Actually draw false color / long duration spectrograms Tuple <Image <Rgb24>, string>[] images = LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( inputDirectory: resultsDirectory, outputDirectory: resultsDirectory, ldSpectrogramConfig: ldSpectrogramConfig, indexPropertiesConfigPath: indicesPropertiesConfig, indexGenerationData: indexConfigData, basename: basename, analysisType: this.Identifier, indexSpectrograms: dictionaryOfSpectra, indexStatistics: indexDistributions, imageChrome: (!tileOutput).ToImageChrome()); if (tileOutput) { Debug.Assert(images.Length == 2); Log.Info("Tiling output at scale: " + acousticIndicesConfig.IndexCalculationDuration); foreach (var image in images) { TileOutput(resultsDirectory, Path.GetFileNameWithoutExtension(sourceAudio.Name), image.Item2 + ".Tile", inputFileSegment, image.Item1); } } } }
public override void SummariseResults( AnalysisSettings analysisSettings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results) { // below is example of how to access values in ContentDescription config file. //sampleRate = analysisSettings.Configuration.GetIntOrNull(AnalysisKeys.ResampleRate) ?? sampleRate; var cdConfiguration = (CdConfig)analysisSettings.Configuration; var ldSpectrogramConfig = cdConfiguration.LdSpectrogramConfig; //var cdConfigFile = analysisSettings.ConfigFile; //var configDirectory = cdConfigFile.DirectoryName ?? throw new ArgumentNullException(nameof(cdConfigFile), "Null value"); var sourceAudio = inputFileSegment.Source; string basename = Path.GetFileNameWithoutExtension(sourceAudio.Name); var resultsDirectory = AnalysisCoordinator.GetNamedDirectory(analysisSettings.AnalysisOutputDirectory, this); // check for null values - this was recommended by ReSharper! if (inputFileSegment.TargetFileDuration == null || inputFileSegment.TargetFileSampleRate == null) { throw new NullReferenceException(); } // output config data to disk so other analyzers can use the data, // Should contain data only - i.e. the configuration settings that generated these indices // this data can then be used by later analysis processes. var indexConfigData = new IndexGenerationData() { RecordingExtension = inputFileSegment.Source.Extension, RecordingBasename = basename, RecordingStartDate = inputFileSegment.TargetFileStartDate, RecordingDuration = inputFileSegment.TargetFileDuration.Value, SampleRateOriginal = inputFileSegment.TargetFileSampleRate.Value, SampleRateResampled = ContentSignatures.SampleRate, FrameLength = ContentSignatures.FrameSize, FrameStep = ContentSignatures.FrameSize, IndexCalculationDuration = TimeSpan.FromSeconds(ContentSignatures.IndexCalculationDurationInSeconds), BgNoiseNeighbourhood = TimeSpan.FromSeconds(5), // default value for content description AnalysisStartOffset = inputFileSegment.SegmentStartOffset ?? TimeSpan.Zero, MaximumSegmentDuration = analysisSettings.AnalysisMaxSegmentDuration, BackgroundFilterCoeff = SpectrogramConstants.BACKGROUND_FILTER_COEFF, LongDurationSpectrogramConfig = ldSpectrogramConfig, }; var icdPath = FilenameHelpers.AnalysisResultPath( resultsDirectory, basename, IndexGenerationData.FileNameFragment, "json"); Json.Serialise(icdPath.ToFileInfo(), indexConfigData); // gather spectra to form spectrograms. Assume same spectra in all analyzer results var dictionaryOfSpectra = spectralIndices.ToTwoDimensionalArray(SpectralIndexValuesForContentDescription.CachedSelectors, TwoDimensionalArray.Rotate90ClockWise); // Calculate the index distribution statistics and write to a json file. Also save as png image // The following method returns var indexDistributions =, but we have no use for them. IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionaryOfSpectra, resultsDirectory, basename); // Draw ldfc spectrograms and return path to 2maps image. string ldfcSpectrogramPath = DrawSpectrogramsFromSpectralIndices( ldSpectrogramConfig, outputDirectory: resultsDirectory, indexGenerationData: indexConfigData, basename: basename, indexSpectrograms: dictionaryOfSpectra); // Gather the content description results into an array of DescriptionResult and then convert to dictionary var allContentDescriptionResults = results.Select(x => (DescriptionResult)x.MiscellaneousResults[nameof(DescriptionResult)]); var contentDictionary = DataProcessing.ConvertResultsToDictionaryOfArrays(allContentDescriptionResults.ToList()); // Write the results to a csv file var filePath = Path.Combine(resultsDirectory.FullName, "AcousticSignatures.csv"); // TODO: fix this so it writes header and a column of content description values. //Csv.WriteToCsv(new FileInfo(filePath), contentDictionary); FileTools.WriteDictionaryAsCsvFile(contentDictionary, filePath); // prepare graphical plots of the acoustic signatures. var contentPlots = GetPlots(contentDictionary); var images = GraphsAndCharts.DrawPlotDistributions(contentPlots); var plotsImage = ImageTools.CombineImagesVertically(images); plotsImage.Save(Path.Combine(resultsDirectory.FullName, "DistributionsOfContentScores.png")); // Attach content description plots to LDFC spectrogram and write to file var ldfcSpectrogram = Image.Load <Rgb24>(ldfcSpectrogramPath); var image = ContentVisualization.DrawLdfcSpectrogramWithContentScoreTracks(ldfcSpectrogram, contentPlots); var path3 = Path.Combine(resultsDirectory.FullName, basename + ".ContentDescription.png"); image.Save(path3); }