diff --git a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.NinoxStrenua.yml b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.NinoxStrenua.yml
new file mode 100644
index 000000000..6165a1fc7
--- /dev/null
+++ b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.NinoxStrenua.yml
@@ -0,0 +1,60 @@
+---
+
+# Powerful Owl = Towsey.NinoxStrenua
+# Resample rate must be 2 X the desired Nyquist
+ResampleRate: 16000
+# SegmentDuration: units=seconds;
+SegmentDuration: 60
+# SegmentOverlap: units=seconds;
+SegmentOverlap: 0
+
+# Each of these profiles will be analyzed
+# This profile is required for the species-specific recogniser and must have the current name.
+Profiles:
+ StrenuaSyllable: !ForwardTrackParameters
+ ComponentName: RidgeTrack
+ SpeciesName: NinoxStrenua
+ FrameSize: 1024
+ FrameStep: 256
+ WindowFunction: HANNING
+ # min and max of the freq band to search
+ MinHertz: 300
+ MaxHertz: 600
+ MinDuration: 0.3
+ MaxDuration: 1.5
+ DecibelThreshold: 12.0
+
+#################### POST-PROCESSING of EVENTS ###################
+
+# A: First post-processing steps are to combine overlapping/proximal/sequential events
+# 1: Combine overlapping events
+CombineOverlappingEvents: true
+
+# 2: Combine each pair of Boobook syllables as one event
+# Can also use this to "mop up" events in neighbourhood - these can be removed later.
+CombinePossibleSyllableSequence: false
+SyllableStartDifference: 1.5
+SyllableHertzGap: 300
+
+# B: Filter the events for excess activity in their upper and lower buffer zones
+NeighbourhoodLowerHertzBuffer: 100
+NeighbourhoodUpperHertzBuffer: 300
+NeighbourhoodDbThreshold: 12.0
+
+# C: Options to save results files
+# 4: Available options for saving spectrograms (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
+# "True" is useful when debugging but "WhenEventsDetected" is required for operational use.
+#SaveSonogramImages: True
+SaveSonogramImages: WhenEventsDetected
+
+# 5: Available options for saving data files (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
+SaveIntermediateWavFiles: Never
+SaveIntermediateCsvFiles: false
+
+# 6: DisplayCsvImage is obsolete - ensure it remains set to: false
+DisplayCsvImage: false
+## End section for AnalyzeLongRecording
+
+# Other config files to reference
+HighResolutionIndicesConfig: "../Towsey.Acoustic.HiResIndicesForRecognisers.yml"
+...
\ No newline at end of file
diff --git a/src/AnalysisPrograms/Recognizers/Birds/NinoxStrenua.cs b/src/AnalysisPrograms/Recognizers/Birds/NinoxStrenua.cs
new file mode 100644
index 000000000..3fc0c6e3e
--- /dev/null
+++ b/src/AnalysisPrograms/Recognizers/Birds/NinoxStrenua.cs
@@ -0,0 +1,270 @@
+//
+// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
+//
+
+namespace AnalysisPrograms.Recognizers
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+ using System.Runtime.CompilerServices;
+ using Acoustics.Shared.ConfigFile;
+ using AnalysisBase;
+ using AnalysisPrograms.Recognizers.Base;
+ using AudioAnalysisTools;
+ using AudioAnalysisTools.Events;
+ using AudioAnalysisTools.Indices;
+ using AudioAnalysisTools.WavTools;
+ using log4net;
+ using TowseyLibrary;
+ using static AnalysisPrograms.Recognizers.GenericRecognizer;
+ using Path = System.IO.Path;
+
+ ///
+ /// A recognizer for the Australian Powerful Owl, https://en.wikipedia.org/wiki/Powerful_owl.
+ /// The owl is so named because it is the largest of the Australian owls and it preys on large marsupials such as possums.
+ /// Its range is confined to the East and SE coast of Australia.
+ /// Its conservation status is "threatened".
+ /// This recognizer has been trained on good quality calls provided by NSW DPI by Brad Law and Kristen Thompson.
+ ///
+ internal class NinoxStrenua : RecognizerBase
+ {
+ private static readonly ILog PowerfulOwlLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ public override string Author => "Towsey";
+
+ public override string SpeciesName => "NinoxStrenua";
+
+ public override string Description => "[ALPHA] Detects acoustic events for the Australian Powerful Owl.";
+
+ public override AnalyzerConfig ParseConfig(FileInfo file)
+ {
+ RuntimeHelpers.RunClassConstructor(typeof(NinoxStrenuaConfig).TypeHandle);
+ var config = ConfigFile.Deserialize(file);
+
+ // validation of configs can be done here
+ GenericRecognizer.ValidateProfileTagsMatchAlgorithms(config.Profiles, file);
+
+ // This call sets a restriction so that only one generic algorithm is used.
+ // CHANGE this to accept multiple generic algorithms as required.
+ //if (result.Profiles.SingleOrDefault() is ForwardTrackParameters)
+ if (config.Profiles?.Count == 1 && config.Profiles.First().Value is ForwardTrackParameters)
+ {
+ return config;
+ }
+
+ throw new ConfigFileException("NinoxStrenua expects one and only one ForwardTrack algorithm.", file);
+ }
+
+ ///
+ /// This method is called once per segment (typically one-minute segments).
+ ///
+ /// one minute of audio recording.
+ /// config file that contains parameters used by all profiles.
+ /// when recording starts.
+ /// not sure what this is.
+ /// where the recognizer results can be found.
+ /// assuming ????.
+ /// recognizer results.
+ public override RecognizerResults Recognize(
+ AudioRecording audioRecording,
+ Config config,
+ TimeSpan segmentStartOffset,
+ Lazy getSpectralIndexes,
+ DirectoryInfo outputDirectory,
+ int? imageWidth)
+ {
+ //class NinoxStrenuaConfig is defined at bottom of this file.
+ var genericConfig = (NinoxStrenuaConfig)config;
+ var recognizer = new GenericRecognizer();
+
+ RecognizerResults combinedResults = recognizer.Recognize(
+ audioRecording,
+ genericConfig,
+ segmentStartOffset,
+ getSpectralIndexes,
+ outputDirectory,
+ imageWidth);
+
+ // ################### POST-PROCESSING of EVENTS ###################
+ // Following two commented lines are different ways of casting lists.
+ //var newEvents = spectralEvents.Cast().ToList();
+ //var spectralEvents = events.Select(x => (SpectralEvent)x).ToList();
+
+ if (combinedResults.NewEvents.Count == 0)
+ {
+ PowerfulOwlLog.Debug($"Return zero events.");
+ return combinedResults;
+ }
+
+ // 1: Filter the events for duration in seconds
+ // Get the PowerfulOwl Syllable config.
+ const string profileName = "StrenuaSyllable";
+ var configuration = (NinoxStrenuaConfig)genericConfig;
+ var chirpConfig = (ForwardTrackParameters)configuration.Profiles[profileName];
+ var minimumEventDuration = chirpConfig.MinDuration;
+ var maximumEventDuration = chirpConfig.MaxDuration;
+ if (genericConfig.CombinePossibleSyllableSequence)
+ {
+ minimumEventDuration *= 2.0;
+ maximumEventDuration *= 1.5;
+ }
+
+ combinedResults.NewEvents = EventExtentions.FilterOnDuration(combinedResults.NewEvents, minimumEventDuration.Value, maximumEventDuration.Value);
+ PowerfulOwlLog.Debug($"Event count after filtering on duration = {combinedResults.NewEvents.Count}");
+
+ // 2: Filter the events for bandwidth in Hertz
+ double average = 400;
+ double sd = 50;
+ double sigmaThreshold = 3.0;
+ //combinedResults.NewEvents = EventExtentions.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold);
+ PowerfulOwlLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}");
+
+ // 3: Filter on COMPONENT COUNT in Composite events.
+ int maxComponentCount = 5;
+ combinedResults.NewEvents = EventExtentions.FilterEventsOnCompositeContent(combinedResults.NewEvents, maxComponentCount);
+ PowerfulOwlLog.Debug($"Event count after filtering on component count = {combinedResults.NewEvents.Count}");
+
+ // 4: Pull out the chirp events and calculate their frequency profiles.
+ var (chirpEvents, others) = combinedResults.NewEvents.FilterForEventType();
+
+ // Uncomment the next line when want to obtain the event frequency profiles.
+ // WriteFrequencyProfiles(chirpEvents);
+ foreach (var ev in chirpEvents)
+ {
+ // Calculate frequency profile score for event
+ SetFrequencyProfileScore((ChirpEvent)ev);
+ }
+
+ if (combinedResults.NewEvents.Count == 0)
+ {
+ PowerfulOwlLog.Debug($"Return zero events.");
+ return combinedResults;
+ }
+
+ //UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots.
+ // NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in UserName.SpeciesName.yml config file.
+ //GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName);
+ return combinedResults;
+ }
+
+ ///
+ /// The Powerful Owl call syllable is shaped like an inverted "U". Its total duration is close to 0.15 seconds.
+ /// The rising portion lasts for 0.06s, followed by a turning portion, 0.03s, followed by the decending portion of 0.06s.
+ /// The constants for this method were obtained from the calls in a Gympie recording obtained by Yvonne Phillips.
+ ///
+ /// An event containing at least one forward track i.e. a chirp.
+ public static void SetFrequencyProfileScore(ChirpEvent ev)
+ {
+ const double risingDuration = 0.06;
+ const double gapDuration = 0.03;
+ const double fallingDuration = 0.06;
+
+ var track = ev.Tracks.First();
+ var profile = track.GetTrackFrequencyProfile().ToArray();
+
+ // get the first point
+ var firstPoint = track.Points.First();
+ var frameDuration = firstPoint.Seconds.Maximum - firstPoint.Seconds.Minimum;
+ var risingFrameCount = (int)Math.Floor(risingDuration / frameDuration);
+ var gapFrameCount = (int)Math.Floor(gapDuration / frameDuration);
+ var fallingFrameCount = (int)Math.Floor(fallingDuration / frameDuration);
+
+ var startSum = 0.0;
+ if (profile.Length >= risingFrameCount)
+ {
+ for (var i = 0; i <= risingFrameCount; i++)
+ {
+ startSum += profile[i];
+ }
+ }
+
+ int startFrame = risingFrameCount + gapFrameCount;
+ int endFrame = startFrame + fallingFrameCount;
+ var endSum = 0.0;
+ if (profile.Length >= endFrame)
+ {
+ for (var i = startFrame; i <= endFrame; i++)
+ {
+ endSum += profile[i];
+ }
+ }
+
+ // set score to 1.0 if the profile has inverted U shape.
+ double score = 0.0;
+ if (startSum > 0.0 && endSum < 0.0)
+ {
+ score = 1.0;
+ }
+
+ ev.FrequencyProfileScore = score;
+ }
+
+ ///
+ /// WARNING - this method assumes that the rising and falling parts of a Powerful Owl call syllable last for 5 frames.
+ ///
+ /// List of spectral events.
+ public static void WriteFrequencyProfiles(List events)
+ {
+ /* Here are the frequency profiles of some events.
+ * Note that the first five frames (0.057 seconds) have positive slope and subsequent frames have negative slope.
+ * The final frames are likely to be echo and to be avoided.
+ * Therefore take the first 0.6s to calculate the positive slope, leave a gap of 0.025 seconds and then get negative slope from the next 0.6 seconds.
+42,21,21,42,21, 00, 21,-21,-21,-21, 00,-21,-42
+42,42,21,21,42,-21, 21, 00,-21,-21,-21,-21, 00,-21,21,-21
+42,42,21,21,42, 00, 00, 00,-21,-21,-21,-21,-21
+21,21,00,00,21, 21,-21, 00, 00,-21, 00,-21,-21,21,-21,42
+42,42,21,00,42, 00, 00,-21,-21,-21,-21, 00,-21,
+21,42,21,21,21, 00,-21,-21,-21, 00,-21,-21
+42,21,21,42,21, 21, 00,-21,-21,-21,-21
+42,42,21,42,00, 00,-21, 00,-21,-21, 00,-21,-21
+*/
+
+ var spectralEvents = events.Select(x => (ChirpEvent)x).ToList();
+ foreach (var ev in spectralEvents)
+ {
+ foreach (var track in ev.Tracks)
+ {
+ var profile = track.GetTrackFrequencyProfile().ToArray();
+ var startSum = 0.0;
+ if (profile.Length >= 5)
+ {
+ startSum = profile[0] + profile[1] + profile[2] + profile[3] + profile[4];
+ }
+
+ var endSum = 0.0;
+ if (profile.Length >= 11)
+ {
+ endSum = profile[6] + profile[7] + profile[8] + profile[9] + profile[10];
+ }
+
+ LoggedConsole.WriteLine($"{startSum} {endSum}");
+ LoggedConsole.WriteLine(DataTools.WriteArrayAsCsvLine(profile, "F0"));
+ }
+ }
+ }
+
+/*
+///
+/// Summarize your results. This method is invoked exactly once per original file.
+///
+public override void SummariseResults(
+ AnalysisSettings settings,
+ FileSegment inputFileSegment,
+ EventBase[] events,
+ SummaryIndexBase[] indices,
+ SpectralIndexBase[] spectralIndices,
+ AnalysisResult2[] results)
+{
+ // No operation - do nothing. Feel free to add your own logic.
+ base.SummariseResults(settings, inputFileSegment, events, indices, spectralIndices, results);
+}
+*/
+
+ public class NinoxStrenuaConfig : GenericRecognizerConfig, INamedProfiles