public static void DrawEventLabel(this SpectralEvent @event, IImageProcessingContext graphics, EventRenderingOptions options) { if (!options.DrawLabel) { return; } var text = @event.Name; if (string.IsNullOrWhiteSpace(text)) { return; } var bounds = TextMeasurer.MeasureBounds(text, new RendererOptions(Roboto6)); var topLeft = options.Converters.GetPoint(@event); topLeft.Offset(0, -bounds.Height); graphics.DrawTextSafe( @event.Name, Roboto6, options.Label, topLeft); }
public void DrawTest() { // arrange string specification = @" ⬇10 E10R80E10 78×E10RE78RE10 E10R80E10 ⬇10 "; this.ExpectedImage = TestImage.Create(100, 100, Color.Black, specification); var @event = new SpectralEvent() { EventEndSeconds = 9, EventStartSeconds = 1, HighFrequencyHertz = 900, LowFrequencyHertz = 100, }; var options = new EventRenderingOptions(new UnitConverters(0, 10, 1000, 100, 100)); // act this.ActualImage.Mutate(x => @event.Draw(x, options)); // assert this.AssertImagesEqual(); }
/// <summary> /// Determines if two events overlap in time. /// </summary> /// <param name="event1">event one.</param> /// <param name="event2">event two.</param> /// <returns>true if events overlap.</returns> public static bool EventsOverlapInTime(SpectralEvent event1, SpectralEvent event2) { //check if event 1 starts within event 2 if (event1.EventStartSeconds >= event2.EventStartSeconds && event1.EventStartSeconds <= event2.EventEndSeconds) { return(true); } // check if event 1 ends within event 2 if (event1.EventEndSeconds >= event2.EventStartSeconds && event1.EventEndSeconds <= event2.EventEndSeconds) { return(true); } // now check possibility that event2 is inside event1. //check if event 2 starts within event 1 if (event2.EventStartSeconds >= event1.EventStartSeconds && event2.EventStartSeconds <= event1.EventEndSeconds) { return(true); } // check if event 2 ends within event 1 if (event2.EventEndSeconds >= event1.EventStartSeconds && event2.EventEndSeconds <= event1.EventEndSeconds) { return(true); } return(false); }
/// <summary> /// Draws a "score" indicator on the left edge of an event. /// </summary> /// <param name="event">The event for which to draw the score indicator.</param> /// <param name="graphics">The image context to draw to.</param> /// <param name="options">The event rendering options to use.</param> public static void DrawScoreIndicator(this SpectralEvent @event, IImageProcessingContext graphics, EventRenderingOptions options) { if (!options.DrawScore) { return; } var normalizedScore = @event.ScoreNormalized.Clamp(0, 1); if (normalizedScore == 0 || double.IsNaN(normalizedScore)) { return; } var rect = options.Converters.GetPixelRectangle(@event); // truncate score bar to neatest whole pixel after scaling by height var scaledHeight = (int)((float)normalizedScore * rect.Height); var top = new PointF(rect.Left, rect.Bottom - scaledHeight); var bottom = new PointF(rect.Left, rect.Bottom); // the order of the supplied points is important! // DO NOT CHANGE graphics.NoAA().DrawLines(options.Score, top, bottom); }
//################################################################################################################# //FOLLOWING METHODS DEAL WITH THE OVERLAP OF EVENTS /// <summary> /// Determines if two events overlap in frequency. /// </summary> /// <param name="event1">event one.</param> /// <param name="event2">event two.</param> /// <returns>true if events overlap.</returns> public static bool EventsOverlapInFrequency(SpectralEvent event1, SpectralEvent event2) { //check if event 1 freq band overlaps event 2 freq band if (event1.HighFrequencyHertz >= event2.LowFrequencyHertz && event1.HighFrequencyHertz <= event2.HighFrequencyHertz) { return(true); } // check if event 1 freq band overlaps event 2 freq band if (event1.LowFrequencyHertz >= event2.LowFrequencyHertz && event1.LowFrequencyHertz <= event2.HighFrequencyHertz) { return(true); } //check if event 2 freq band overlaps event 1 freq band if (event2.HighFrequencyHertz >= event1.LowFrequencyHertz && event2.HighFrequencyHertz <= event1.HighFrequencyHertz) { return(true); } // check if event 2 freq band overlaps event 1 freq band if (event2.LowFrequencyHertz >= event1.LowFrequencyHertz && event2.LowFrequencyHertz <= event1.HighFrequencyHertz) { return(true); } return(false); }
public void TestEventMerging() { // make a list of three events var events = new List <SpectralEvent>(); var segmentStartTime = TimeSpan.FromSeconds(10); var event1 = new SpectralEvent(segmentStartOffset: segmentStartTime, eventStartRecordingRelative: 11.0, eventEndRecordingRelative: 16.0, minFreq: 1000, maxFreq: 6000) { Name = "Event1", Score = 1.0, }; events.Add(event1); var event2 = new SpectralEvent(segmentStartOffset: segmentStartTime, eventStartRecordingRelative: 12.0, eventEndRecordingRelative: 15.0, minFreq: 1500, maxFreq: 8000) { Name = "Event2", Score = 5.0, }; events.Add(event2); var event3 = new SpectralEvent(segmentStartOffset: segmentStartTime, eventStartRecordingRelative: 17.0, eventEndRecordingRelative: 19.0, minFreq: 1000, maxFreq: 8000) { Name = "Event3", Score = 9.0, }; events.Add(event3); // combine Overlapping acoustic events var newEvents = CompositeEvent.CombineOverlappingEvents(events: events.Cast <EventCommon>().ToList()); events = newEvents.Cast <SpectralEvent>().ToList(); //require two events, the first being a composite of two events. Assert.AreEqual(2, events.Count); Assert.AreEqual(typeof(CompositeEvent), events[0].GetType()); Assert.AreEqual(2, ((CompositeEvent)events[0]).ComponentEvents.Count); Assert.AreEqual(11.0, events[0].EventStartSeconds); Assert.AreEqual(16.0, events[0].EventEndSeconds); Assert.AreEqual(1000, events[0].LowFrequencyHertz); Assert.AreEqual(8000, events[0].HighFrequencyHertz); Assert.AreEqual(5.0, events[0].Score); Assert.AreEqual(typeof(SpectralEvent), events[1].GetType()); Assert.AreEqual(17.0, events[1].EventStartSeconds); Assert.AreEqual(19.0, events[1].EventEndSeconds); Assert.AreEqual(1000, events[1].LowFrequencyHertz); Assert.AreEqual(8000, events[1].HighFrequencyHertz); Assert.AreEqual(9.0, events[1].Score); }
/// <summary> /// Calculates the average amplitude in the frequency just above the whistle. /// If it contains above threshold acoustic content, this is unlikely to be a whistle. /// </summary> /// <param name="ev">The event.</param> /// <param name="sonogramData">The spectrogram data as matrix with origin top/left.</param> /// <param name="bufferBins">THe badnwidth of the buffer zone in bins.</param> /// <param name="converter">A converter to convert seconds/Hertz to frames/bins.</param> /// <returns>Average of the spectrogram amplitude in buffer band above whistler.</returns> public static double GetAverageAmplitudeInNeighbourhood(SpectralEvent ev, double[,] sonogramData, int bufferBins, UnitConverters converter) { var bottomBufferBin = converter.GetFreqBinFromHertz(ev.HighFrequencyHertz) + 5; var topBufferBin = bottomBufferBin + bufferBins; var frameStart = converter.FrameFromStartTime(ev.EventStartSeconds); var frameEnd = converter.FrameFromStartTime(ev.EventEndSeconds); var subMatrix = MatrixTools.Submatrix <double>(sonogramData, frameStart, bottomBufferBin, frameEnd, topBufferBin); var averageRowDecibels = MatrixTools.GetRowAverages(subMatrix); var av = averageRowDecibels.Average(); return(av); }
public static SpectralEvent OverlapsEventInList(SpectralEvent anEvent, List <SpectralEvent> events) { foreach (var se in events) { if (EventsOverlapInTime(anEvent, se)) { return(se); } } return(null); }
public void DerivedPropertiesTest() { var @event = new SpectralEvent() { EventEndSeconds = 9, EventStartSeconds = 1, HighFrequencyHertz = 900, LowFrequencyHertz = 100, }; Assert.AreEqual(8, @event.EventDurationSeconds); Assert.AreEqual(800, @event.BandWidthHertz); }
public static AcousticEvent ConvertSpectralEventToAcousticEvent(this SpectralEvent se) { var segmentStartOffset = TimeSpan.FromSeconds(se.SegmentStartSeconds); double startTime = se.EventStartSeconds; double duration = se.EventDurationSeconds; double minHz = se.HighFrequencyHertz; double maxHz = se.HighFrequencyHertz; var ae = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz) { Name = se.Name, SegmentDurationSeconds = se.SegmentDurationSeconds, }; return(ae); }
/// <summary> /// Merges two spectral events into one event. /// </summary> /// <param name="e1">first event.</param> /// <param name="e2">second event.</param> /// <returns>a composite event.</returns> public static CompositeEvent CombineTwoEvents(SpectralEvent e1, SpectralEvent e2) { // Assume that we only merge events that are in the same recording segment. // Therefore the value of segmentStartOffset has already been set and is the same for both events. var e1Type = e1.GetType(); var e2Type = e2.GetType(); // There are three possibilities if (e1Type == typeof(CompositeEvent) && e2Type == typeof(CompositeEvent)) { var e2Events = ((CompositeEvent)e2).ComponentEvents; ((CompositeEvent)e1).ComponentEvents.AddRange(e2Events); return((CompositeEvent)e1); } else if (e1Type == typeof(CompositeEvent)) { ((CompositeEvent)e1).ComponentEvents.Add(e2); return((CompositeEvent)e1); } else if (e2Type == typeof(CompositeEvent)) { ((CompositeEvent)e2).ComponentEvents.Add(e1); return((CompositeEvent)e2); } var twoEvents = new List <SpectralEvent> { e1, e2, }; var compositeEvent = new CompositeEvent(twoEvents) { Name = e1.Name, }; return(compositeEvent); }
public void TestGetPixelRectangle() { // arrange var @event = new SpectralEvent() { EventEndSeconds = 9, EventStartSeconds = 1, HighFrequencyHertz = 900, LowFrequencyHertz = 100, }; var converters = new UnitConverters(0, 10, 1000, 100, 100); // act var rect = converters.GetPixelRectangle(@event); // assert Assert.AreEqual(10, rect.Left); Assert.AreEqual(10, rect.Top); Assert.AreEqual(80, rect.Width); Assert.AreEqual(80, rect.Height); }
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> public override RecognizerResults Recognize(AudioRecording audioRecording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { // Get a value from the config file - with a backup default int minHz = configuration.GetIntOrNull(AnalysisKeys.MinHz) ?? 600; // Get a value from the config file - with no default, throw an exception if value is not present //int maxHz = ((int?)configuration[AnalysisKeys.MaxHz]).Value; // Get a value from the config file - without a string accessor, as a double double someExampleSettingA = configuration.GetDoubleOrNull("SomeExampleSettingA") ?? 0.0; // common properties string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; /* * Examples of using profiles */ // Examples of the APIs available. You don't need all of these commands! Pick and choose. bool hasProfiles = ConfigFile.HasProfiles(configuration); //Config profile = ConfigFile.GetProfile<Config, Aed.AedConfiguration>(configuration, "Groote"); //Config profile2; //bool success = ConfigFile.TryGetProfile(configuration, "FemaleRelease", out profile2); //string[] profileNames = ConfigFile.GetProfileNames<Config>(configuration); // IEnumerable<(string Name, object Profile)> allProfiles = ConfigFile.GetAllProfiles<IProfile<object>>(configuration); // foreach (var profile in allProfiles) // { // object currentProfile = profile.Profile; // Log.Info(profile.Name + ": " + ((int)currentProfile.MinHz).ToString()); // } // Profile example: running the same algorithm on every profile with different settings (regional variation) /* * List<AcousticEvent> allAcousticEvents = new List<AcousticEvent>(); * Dictionary<string, Config> allProfiles = ConfigFile.GetAllProfiles(configuration); * foreach (var kvp in allProfiles) * { * string profileName = kvp.Key; * Log.Info($"Analyzing profile: {profileName}"); * Config currentProfile = kvp.Value; * * // extract parameters * int minHz = (int)configuration[AnalysisKeys.MinHz]; * * // ... * * // run the algorithm * List<AcousticEvent> acousticEvents; * Oscillations2012.Execute( All the correct parameters, minHz); * * // augment the returned events * acousticEvents.ForEach(ae => * { * ae.SpeciesName = speciesName; * ae.Profile = profileName; * ae.AnalysisIdealSegmentDuration = recordingDuration; * ae.Name = abbreviatedSpeciesName; * }); * * // add events found in this profile to the total list * allAcousticEvents.AddRange(acousticEvents); * } */ // Profile example: running a different algorithm on different profiles /* * bool hasProfiles = ConfigFile.HasProfiles(configuration); * if (hasProfiles) * { * // add resulting events from each algorithm into the combined event list * allAcousticEvents.AddRange(RunFemaleProfile(...all the arguments)); * allAcousticEvents.AddRange(RunMaleProfile(...all the arguments)); * } * * // example method * private static List<AcousticEvent> RunFemaleProfile(configuration, rest of arguments) * { * const string femaleProfile = "Female"; * Config currentProfile = ConfigFile.GetProfile(configuration, femaleProfile); * Log.Info($"Analyzing profile: {femaleProfile}"); * * // extract parameters * int minHz = (int)configuration[AnalysisKeys.MinHz]; * * // ... * * // run the algorithm * List<AcousticEvent> acousticEvents; * Oscillations2012.Execute(All the correct parameters, minHz); * * // augment the returned events * acousticEvents.ForEach(ae => * { * ae.SpeciesName = speciesName; * ae.Profile = femaleProfile; * ae.AnalysisIdealSegmentDuration = recordingDuration; * ae.Name = abbreviatedSpeciesName; * }); * * return acousticEvents; * } */ // get samples var samples = audioRecording.WavReader.Samples; // make a spectrogram var config = new SonogramConfig { NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0, }; var sonogram = (BaseSonogram) new SpectrogramStandard(config, audioRecording.WavReader); // get high resolution indices // when the value is accessed, the indices are calculated var indices = getSpectralIndexes.Value; // check if the indices have been calculated - you shouldn't actually need this if (getSpectralIndexes.IsValueCreated) { // then indices have been calculated before } var foundEvents = new List <EventCommon>(); // some kind of loop where you scan through the audio // 'find' an event - if you find an event, store the data in the AcousticEvent class var anEvent = new SpectralEvent { Name = "FAKE!", }; foundEvents.Add(anEvent); return(new RecognizerResults() { //Plots = null, NewEvents = foundEvents, Hits = null, ScoreTrack = null, Sonogram = sonogram, }); }