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