コード例 #1
0
        /// <summary>
        /// returns an oscillation array for a single frequency bin.
        /// </summary>
        /// <param name="xCorrByTimeMatrix">derived from single frequency bin.</param>
        /// <param name="sensitivity">a threshold used to ignore low ascillation intensities.</param>
        /// <returns>vector of oscillation values.</returns>
        public static double[] GetOscillationArrayUsingFft(double[,] xCorrByTimeMatrix, double sensitivity)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // set up vector to contain fft output
            var oscillationsVector = new double[xCorrLength / 2];

            // loop over all the Auto-correlation vectors and do FFT
            for (int e = 0; e < sampleCount; e++)
            {
                double[] autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);

                // zero mean the auto-correlation vector before doing FFT
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in the low coeff because these can dominate.
                // This is a hack!
                spectrum[0] *= 0.33;

                // convert to energy and calculate total power in spectrum
                spectrum = DataTools.SquareValues(spectrum);
                double sumOfSquares = spectrum.Sum();

                // get combined relative power in the three bins centred on max.
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];

                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[1] + spectrum[2];
                }
                else if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex + 1];
                }

                double relativePower = powerAtMax / sumOfSquares;

                // if the relative power of the max oscillation is large enough,
                // then accumulate its power into the oscillations Vector
                if (relativePower > sensitivity)
                {
                    oscillationsVector[maxIndex] += powerAtMax;
                }
            }

            return(LogTransformOscillationVector(oscillationsVector, sampleCount));
        }
コード例 #2
0
        /// <summary>
        /// returns the fft spectrum of a cross-correlation function.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static double[] CrossCorr(double[] v1, double[] v2)
        {
            int n = v1.Length; // assume both vectors of same length

            double[] r;
            alglib.corrr1d(v1, n, v2, n, out r);

            // alglib.complex[] f;
            // alglib.fftr1d(newOp, out f);
            // System.LoggedConsole.WriteLine("{0}", alglib.ap.format(f, 3));
            // for (int i = 0; i < op.Length; i++) LoggedConsole.WriteLine("{0}   {1:f2}", i, op[i]);

            // rearrange corr output and NormaliseMatrixValues
            int xcorrLength = 2 * n;

            double[] xCorr = new double[xcorrLength];

            // for (int i = 0; i < n - 1; i++) newOp[i] = r[i + n];   //rearrange corr output
            // for (int i = n - 1; i < opLength-1; i++) newOp[i] = r[i - n + 1];
            for (int i = 0; i < n - 1; i++)
            {
                xCorr[i] = r[i + n] / (i + 1);  // rearrange and NormaliseMatrixValues
            }

            for (int i = n - 1; i < xcorrLength - 1; i++)
            {
                xCorr[i] = r[i - n + 1] / (xcorrLength - i - 1);
            }

            // add extra value at end so have length = power of 2 for FFT
            // xCorr[xCorr.Length - 1] = xCorr[xCorr.Length - 2];
            // LoggedConsole.WriteLine("xCorr length = " + xCorr.Length);
            // for (int i = 0; i < xCorr.Length; i++) LoggedConsole.WriteLine("{0}   {1:f2}", i, xCorr[i]);
            // DataTools.writeBarGraph(xCorr);

            xCorr = DataTools.DiffFromMean(xCorr);
            FFT.WindowFunc wf  = FFT.Hamming;
            var            fft = new FFT(xCorr.Length, wf);

            var spectrum = fft.Invoke(xCorr);

            return(spectrum);
        }// CrossCorrelation()
コード例 #3
0
        public static Image AnalyseLocation(double[] signal, int sr, double startTimeInSeconds, int windowWidth)
        {
            int binCount = windowWidth / 2;

            int location = (int)Math.Round(startTimeInSeconds * sr); //assume location points to start of grunt

            if (location >= signal.Length)
            {
                LoggedConsole.WriteErrorLine("WARNING: Location is beyond end of signal.");
                return(null);
            }

            int nyquist = sr / 2;

            FFT.WindowFunc wf               = FFT.Hamming;
            var            fft              = new FFT(windowWidth, wf);
            int            maxHz            = 1000; // max frequency to display in fft image
            double         hzPerBin         = nyquist / (double)binCount;
            int            requiredBinCount = (int)Math.Round(maxHz / hzPerBin);

            double[] subsampleWav = DataTools.Subarray(signal, location, windowWidth);
            var      spectrum     = fft.Invoke(subsampleWav);

            // convert to power
            spectrum = DataTools.SquareValues(spectrum);
            spectrum = DataTools.filterMovingAverageOdd(spectrum, 3);
            spectrum = DataTools.normalise(spectrum);
            var subBandSpectrum = DataTools.Subarray(spectrum, 1, requiredBinCount); // ignore DC in bin zero.

            var startTime = TimeSpan.FromSeconds(startTimeInSeconds);

            double[] scoreArray = CalculateScores(subBandSpectrum, windowWidth);
            Image    image4     = GraphsAndCharts.DrawWaveAndFft(subsampleWav, sr, startTime, spectrum, maxHz * 2, scoreArray);

            return(image4);
        }
コード例 #4
0
        public static Image <Rgb24> DrawWaveAndFft(double[] signal, int sr, TimeSpan startTime, double[] fftSpectrum, int maxHz, double[] scores)
        {
            int    imageHeight = 300;
            double max         = -2.0;

            foreach (double value in signal)
            {
                double absValue = Math.Abs(value);
                if (absValue > max)
                {
                    max = absValue;
                }
            }

            double scalingFactor = 0.5 / max;

            // now process neighbourhood of each max
            int    nyquist     = sr / 2;
            int    windowWidth = signal.Length;
            int    binCount    = windowWidth / 2;
            double hzPerBin    = nyquist / (double)binCount;

            if (fftSpectrum == null)
            {
                FFT.WindowFunc wf  = FFT.Hamming;
                var            fft = new FFT(windowWidth, wf);
                fftSpectrum = fft.Invoke(signal);
            }

            int requiredBinCount = (int)(maxHz / hzPerBin);
            var subBandSpectrum  = DataTools.Subarray(fftSpectrum, 1, requiredBinCount); // ignore DC in bin zero.

            var endTime = startTime + TimeSpan.FromSeconds(windowWidth / (double)sr);

            string title1  = $"Bandpass filtered: tStart={startTime.ToString()},  tEnd={endTime.ToString()}";
            var    image4A = DrawWaveform(title1, signal, signal.Length, imageHeight, scalingFactor);

            string title2  = $"FFT 1->{maxHz}Hz.,    hz/bin={hzPerBin:f1},    score={scores[0]:f3}={scores[1]:f3}+{scores[2]:f3}";
            var    image4B = DrawGraph(title2, subBandSpectrum, signal.Length, imageHeight, 1);

            var imageList = new List <Image <Rgb24> > {
                image4A, image4B
            };

            Pen pen1       = new Pen(Color.Wheat, 1f);
            var stringFont = Drawing.Arial9;
            var bmp2       = new Image <Rgb24>(signal.Length, 25);

            bmp2.Mutate(g2 =>
            {
                g2.DrawLine(pen1, 0, 0, signal.Length, 0);
                g2.DrawLine(pen1, 0, bmp2.Height - 1, signal.Length, bmp2.Height - 1);
                int barWidth = signal.Length / subBandSpectrum.Length;
                for (int i = 1; i < subBandSpectrum.Length - 1; i++)
                {
                    if (subBandSpectrum[i] > subBandSpectrum[i - 1] && subBandSpectrum[i] > subBandSpectrum[i + 1])
                    {
                        string label = $"{i + 1},";
                        g2.DrawText(label, stringFont, Color.Wheat, new PointF((i * barWidth) - 3, 3));
                    }
                }
            });
            imageList.Add(bmp2);
            var image = ImageTools.CombineImagesVertically(imageList);

            return(image);
        }
コード例 #5
0
        /// <summary>
        /// Do your analysis. This method is called once per segment (typically one-minute segments).
        /// </summary>
        /// <param name="audioRecording"></param>
        /// <param name="configuration"></param>
        /// <param name="segmentStartOffset"></param>
        /// <param name="getSpectralIndexes"></param>
        /// <param name="outputDirectory"></param>
        /// <param name="imageWidth"></param>
        /// <returns></returns>
        public override RecognizerResults Recognize(AudioRecording audioRecording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth)
        {
            const double minAmplitudeThreshold = 0.1;
            const int    percentile            = 5;
            const double scoreThreshold        = 0.3;
            const bool   doFiltering           = true;
            const int    windowWidth           = 1024;
            const int    signalBuffer          = windowWidth * 2;

            //string path = @"C:\SensorNetworks\WavFiles\Freshwater\savedfortest.wav";
            //audioRecording.Save(path); // this does not work
            int sr      = audioRecording.SampleRate;
            int nyquist = audioRecording.Nyquist;

            // Get a value from the config file - with a backup default
            //int minHz = (int?)configuration[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 = (double?)configuration.someExampleSettingA ?? 0.0;

            // common properties
            //string speciesName = (string)configuration[AnalysisKeys.SpeciesName] ?? "<no species>";
            //string abbreviatedSpeciesName = (string)configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>";

            // min score for an acceptable event
            double eventThreshold = (double)configuration.GetDoubleOrNull(AnalysisKeys.EventThreshold);

            // get samples
            var samples = audioRecording.WavReader.Samples;

            double[] bandPassFilteredSignal = null;

            if (doFiltering)
            {
                // high pass filter
                int      windowLength = 71;
                double[] highPassFilteredSignal;
                DSP_IIRFilter.ApplyMovingAvHighPassFilter(samples, windowLength, out highPassFilteredSignal);

                //DSP_IIRFilter filter2 = new DSP_IIRFilter("Chebyshev_Highpass_400");
                //int order2 = filter2.order;
                //filter2.ApplyIIRFilter(samples, out highPassFilteredSignal);

                // Amplify 40dB and clip to +/-1.0;
                double factor = 100; // equiv to 20dB
                highPassFilteredSignal = DspFilters.AmplifyAndClip(highPassFilteredSignal, factor);

                //low pass filter
                string        filterName = "Chebyshev_Lowpass_5000, scale*5";
                DSP_IIRFilter filter     = new DSP_IIRFilter(filterName);
                int           order      = filter.order;

                //System.LoggedConsole.WriteLine("\nTest " + filterName + ", order=" + order);
                filter.ApplyIIRFilter(highPassFilteredSignal, out bandPassFilteredSignal);
            }
            else // do not filter because already filtered - using Chris's filtered recording
            {
                bandPassFilteredSignal = samples;
            }

            // calculate an amplitude threshold that is above Nth percentile of amplitudes in the subsample
            int[]  histogramOfAmplitudes;
            double minAmplitude;
            double maxAmplitude;
            double binWidth;
            int    window = 66;

            Histogram.GetHistogramOfWaveAmplitudes(bandPassFilteredSignal, window, out histogramOfAmplitudes, out minAmplitude, out maxAmplitude, out binWidth);
            int percentileBin = Histogram.GetPercentileBin(histogramOfAmplitudes, percentile);

            double amplitudeThreshold = (percentileBin + 1) * binWidth;

            if (amplitudeThreshold < minAmplitudeThreshold)
            {
                amplitudeThreshold = minAmplitudeThreshold;
            }

            bool doAnalysisOfKnownExamples = true;

            if (doAnalysisOfKnownExamples)
            {
                // go to fixed location to check
                //1:02.07, 1:07.67, 1:12.27, 1:12.42, 1:12.59, 1:12.8, 1.34.3, 1:35.3, 1:40.16, 1:50.0, 2:05.9, 2:06.62, 2:17.57, 2:21.0
                //2:26.33, 2:43.07, 2:43.15, 3:16.55, 3:35.09, 4:22.44, 4:29.9, 4:42.6, 4:51.48, 5:01.8, 5:21.15, 5:22.72, 5:32.37, 5.36.1,
                //5:42.82, 6:03.5, 6:19.93, 6:21.55, 6:42.0, 6:42.15, 6:46.44, 7:12.17, 7:42.65, 7:45.86, 7:46.18, 7:52.38, 7:59.11, 8:10.63,
                //8:14.4, 8:14.63, 8_15_240, 8_46_590, 8_56_590, 9_25_77, 9_28_94, 9_30_5, 9_43_9, 10_03_19, 10_24_26, 10_24_36, 10_38_8,
                //10_41_08, 10_50_9, 11_05_13, 11_08_63, 11_44_66, 11_50_36, 11_51_2, 12_04_93, 12_10_05, 12_20_78, 12_27_0, 12_38_5,
                //13_02_25, 13_08_18, 13_12_8, 13_25_24, 13_36_0, 13_50_4, 13_51_2, 13_57_87, 14_15_00, 15_09_74, 15_12_14, 15_25_79

                //double[] times = { 2.2, 26.589, 29.62 };
                //double[] times = { 2.2, 3.68, 10.83, 24.95, 26.589, 27.2, 29.62 };
                //double[] times = { 2.2, 3.68, 10.83, 24.95, 26.589, 27.2, 29.62, 31.39, 62.1, 67.67, 72.27, 72.42, 72.59, 72.8, 94.3, 95.3,
                //                   100.16, 110.0, 125.9, 126.62, 137.57, 141.0, 146.33, 163.07, 163.17, 196.55, 215.09, 262.44, 269.9, 282.6,
                //                   291.48, 301.85, 321.18, 322.72, 332.37, 336.1, 342.82, 363.5, 379.93, 381.55, 402.0, 402.15, 406.44, 432.17,
                //                   462.65, 465.86, 466.18, 472.38, 479.14, 490.63, 494.4, 494.63, 495.240, 526.590, 536.590, 565.82, 568.94,
                //                   570.5, 583.9, 603.19, 624.26, 624.36, 638.8, 641.08, 650.9, 65.13, 68.63, 704.66,
                //                   710.36, 711.2, 724.93, 730.05, 740.78, 747.05, 758.5, 782.25, 788.18, 792.8,
                //                   805.24, 816.03, 830.4, 831.2, 837.87, 855.02, 909.74, 912.14, 925.81  };

                var filePath = new FileInfo(@"C:\SensorNetworks\WavFiles\Freshwater\GruntSummaryRevisedAndEditedByMichael.csv");
                List <CatFishCallData> data = Csv.ReadFromCsv <CatFishCallData>(filePath, true).ToList();

                //var catFishCallDatas = data as IList<CatFishCallData> ?? data.ToList();
                int count = data.Count();

                var subSamplesDirectory = outputDirectory.CreateSubdirectory("testSubsamples_5000LPFilter");

                //for (int t = 0; t < times.Length; t++)
                foreach (var fishCall in data)
                {
                    //Image bmp1 = IctalurusFurcatus.AnalyseLocation(bandPassFilteredSignal, sr, times[t], windowWidth);

                    // use following line where using time in seconds
                    //int location = (int)Math.Round(times[t] * sr); //assume location points to start of grunt
                    //double[] subsample = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 2 * signalBuffer);

                    // use following line where using sample
                    int location1 = fishCall.Sample / 2;                        //assume Chris's sample location points to centre of grunt. Divide by 2 because original recording was 44100.
                    int location  = (int)Math.Round(fishCall.TimeSeconds * sr); //assume location points to centre of grunt

                    double[] subsample = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 2 * signalBuffer);

                    // calculate an amplitude threshold that is above 95th percentile of amplitudes in the subsample
                    //int[] histogramOfAmplitudes;
                    //double minAmplitude;
                    //double maxAmplitude;
                    //double binWidth;
                    //int window = 70;
                    //int percentile = 90;
                    //Histogram.GetHistogramOfWaveAmplitudes(subsample, window, out histogramOfAmplitudes, out minAmplitude, out maxAmplitude, out binWidth);
                    //int percentileBin = Histogram.GetPercentileBin(histogramOfAmplitudes, percentile);

                    //double amplitudeThreshold = (percentileBin + 1) * binWidth;
                    //if (amplitudeThreshold < minAmplitudeThreshold) amplitudeThreshold = minAmplitudeThreshold;

                    double[] scores1 = AnalyseWaveformAtLocation(subsample, amplitudeThreshold, scoreThreshold);
                    string   title1  = $"scores={fishCall.Timehms}";
                    Image    bmp1    = GraphsAndCharts.DrawGraph(title1, scores1, subsample.Length, 300, 1);

                    //bmp1.Save(path1.FullName);

                    string title2 = $"tStart={fishCall.Timehms}";
                    Image  bmp2   = GraphsAndCharts.DrawWaveform(title2, subsample, 1);
                    var    path1  = subSamplesDirectory.CombineFile($"scoresForTestSubsample_{fishCall.TimeSeconds}secs.png");

                    //var path2 = subSamplesDirectory.CombineFile($@"testSubsample_{times[t]}secs.wav.png");
                    Image[] imageList = { bmp2, bmp1 };
                    Image   bmp3      = ImageTools.CombineImagesVertically(imageList);
                    bmp3.Save(path1.FullName);

                    //write wave form to txt file for later work in XLS
                    //var path3 = subSamplesDirectory.CombineFile($@"testSubsample_{times[t]}secs.wav.csv");
                    //signalBuffer = 800;
                    //double[] subsample2 = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 3 * signalBuffer);
                    //FileTools.WriteArray2File(subsample2, path3.FullName);
                }
            }

            int signalLength = bandPassFilteredSignal.Length;

            // count number of 1000 sample segments
            int blockLength = 1000;
            int blockCount  = signalLength / blockLength;

            int[]    indexOfMax = new int[blockCount];
            double[] maxInBlock = new double[blockCount];

            for (int i = 0; i < blockCount; i++)
            {
                double max        = -2.0;
                int    blockStart = blockLength * i;
                for (int s = 0; s < blockLength; s++)
                {
                    double absValue = Math.Abs(bandPassFilteredSignal[blockStart + s]);
                    if (absValue > max)
                    {
                        max           = absValue;
                        maxInBlock[i] = max;
                        indexOfMax[i] = blockStart + s;
                    }
                }
            }

            // transfer max values to a list
            var indexList = new List <int>();

            for (int i = 1; i < blockCount - 1; i++)
            {
                // only find the blocks that contain a max value that is > neighbouring blocks
                if (maxInBlock[i] > maxInBlock[i - 1] && maxInBlock[i] > maxInBlock[i + 1])
                {
                    indexList.Add(indexOfMax[i]);
                }

                //ALTERNATIVELY
                // look at max in each block
                //indexList.Add(indexOfMax[i]);
            }

            // now process neighbourhood of each max
            int binCount = windowWidth / 2;

            FFT.WindowFunc wf               = FFT.Hamming;
            var            fft              = new FFT(windowWidth, wf);
            int            maxHz            = 1000;
            double         hzPerBin         = nyquist / (double)binCount;
            int            requiredBinCount = (int)Math.Round(maxHz / hzPerBin);

            // init list of events
            List <AcousticEvent> events = new List <AcousticEvent>();

            double[] scores = new double[signalLength]; // init of score array

            int id = 0;

            foreach (int location in indexList)
            {
                //System.LoggedConsole.WriteLine("Location " + location + ", id=" + id);

                int start = location - binCount;
                if (start < 0)
                {
                    continue;
                }

                int end = location + binCount;
                if (end >= signalLength)
                {
                    continue;
                }

                double[] subsampleWav = DataTools.Subarray(bandPassFilteredSignal, start, windowWidth);

                var spectrum = fft.Invoke(subsampleWav);

                // convert to power
                spectrum = DataTools.SquareValues(spectrum);
                spectrum = DataTools.filterMovingAverageOdd(spectrum, 3);
                spectrum = DataTools.normalise(spectrum);
                var subBandSpectrum = DataTools.Subarray(spectrum, 1, requiredBinCount); // ignore DC in bin zero.

                // now do some tests on spectrum to determine if it is a candidate grunt
                bool eventFound = false;

                double[] scoreArray = CalculateScores(subBandSpectrum, windowWidth);
                double   score      = scoreArray[0];

                if (score > scoreThreshold)
                {
                    eventFound = true;
                }

                if (eventFound)
                {
                    for (int i = location - binCount; i < location + binCount; i++)
                    {
                        scores[location] = score;
                    }

                    var    startTime  = TimeSpan.FromSeconds((location - binCount) / (double)sr);
                    string startLabel = startTime.Minutes + "." + startTime.Seconds + "." + startTime.Milliseconds;
                    Image  image4     = GraphsAndCharts.DrawWaveAndFft(subsampleWav, sr, startTime, spectrum, maxHz * 2, scoreArray);

                    var path4 = outputDirectory.CreateSubdirectory("subsamples").CombineFile($@"subsample_{location}_{startLabel}.png");
                    image4.Save(path4.FullName);

                    // have an event, store the data in the AcousticEvent class
                    double duration = 0.2;
                    int    minFreq  = 50;
                    int    maxFreq  = 1000;
                    var    anEvent  = new AcousticEvent(segmentStartOffset, startTime.TotalSeconds, duration, minFreq, maxFreq);
                    anEvent.Name = "grunt";

                    //anEvent.Name = DataTools.WriteArrayAsCsvLine(subBandSpectrum, "f4");
                    anEvent.Score = score;
                    events.Add(anEvent);
                }

                id++;
            }

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

            //// 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 plot = new Plot(this.DisplayName, scores, eventThreshold);

            return(new RecognizerResults()
            {
                Events = events,
                Hits = null,

                //ScoreTrack = null,
                Plots = plot.AsList(),
                Sonogram = sonogram,
            });
        }
コード例 #6
0
        /// <summary>
        /// Returns the following 18 values encapsulated in class EnvelopeAndFft
        /// 1) the minimum and maximum signal values
        /// 2) the average of absolute amplitudes for each frame
        /// 3) the minimum value in each frame
        /// 3) the maximum value in each frame.
        /// 3) the signal envelope as vector. i.e. the maximum of absolute amplitudes for each frame.
        /// 4) vector of frame energies
        /// 5) the high amplitude and clipping counts
        /// 6) the signal amplitude spectrogram
        /// 7) the power of the FFT Window, i.e. sum of squared window values.
        /// 8) the nyquist
        /// 9) the width of freq bin in Hz
        /// 10) the Nyquist bin ID
        /// AND OTHERS
        /// The returned info is used by Sonogram classes to draw sonograms and by Spectral Indices classes to calculate Spectral indices.
        /// Less than half the info is used to draw sonograms but it is difficult to disentangle calculation of all the info without
        /// reverting back to the old days when we used two classes and making sure they remain in synch.
        /// </summary>
        public static EnvelopeAndFft ExtractEnvelopeAndAmplSpectrogram(
            double[] signal,
            int sampleRate,
            double epsilon,
            int frameSize,
            int frameStep,
            string windowName = null)
        {
            // SIGNAL PRE-EMPHASIS helps with speech signals
            // Do not use this for environmental audio
            //if (config.DoPreemphasis)
            //{
            //    signal = DSP_Filters.PreEmphasis(signal, 0.96);
            //}

            int[,] frameIDs = FrameStartEnds(signal.Length, frameSize, frameStep);
            if (frameIDs == null)
            {
                throw new NullReferenceException("Thrown in EnvelopeAndFft.ExtractEnvelopeAndAmplSpectrogram(): int matrix, frameIDs, cannot be null.");
            }

            int frameCount = frameIDs.GetLength(0);

            // set up the FFT parameters
            if (windowName == null)
            {
                windowName = FFT.KeyHammingWindow;
            }

            FFT.WindowFunc w   = FFT.GetWindowFunction(windowName);
            var            fft = new FFT(frameSize, w);                     // init class which calculates the Matlab compatible .NET FFT

            double[,] spectrogram = new double[frameCount, fft.CoeffCount]; // init amplitude sonogram
            double minSignalValue = double.MaxValue;
            double maxSignalValue = double.MinValue;

            double[] average       = new double[frameCount];
            double[] minValues     = new double[frameCount];
            double[] maxValues     = new double[frameCount];
            double[] envelope      = new double[frameCount];
            double[] frameEnergy   = new double[frameCount];
            double[] frameDecibels = new double[frameCount];

            // for all frames
            for (int i = 0; i < frameCount; i++)
            {
                int start = i * frameStep;
                int end   = start + frameSize;

                // get average and envelope for current frame
                double frameMin    = signal[start];
                double frameMax    = signal[start];
                double frameSum    = signal[start];
                double total       = Math.Abs(signal[start]);
                double maxAbsValue = total;
                double energy      = 0;

                // for all values in frame
                for (int x = start + 1; x < end; x++)
                {
                    if (signal[x] > maxSignalValue)
                    {
                        maxSignalValue = signal[x];
                    }

                    if (signal[x] < minSignalValue)
                    {
                        minSignalValue = signal[x];
                    }

                    frameSum += signal[x];

                    // Get frame min and max
                    if (signal[x] < frameMin)
                    {
                        frameMin = signal[x];
                    }

                    if (signal[x] > frameMax)
                    {
                        frameMax = signal[x];
                    }

                    energy += signal[x] * signal[x];

                    // Get absolute signal average in current frame
                    double absValue = Math.Abs(signal[x]);
                    total += absValue;

                    // Get the maximum absolute signal value in current frame
                    if (absValue > maxAbsValue)
                    {
                        maxAbsValue = absValue;
                    }
                } // end of frame

                double frameDc = frameSum / frameSize;
                minValues[i]     = frameMin;
                maxValues[i]     = frameMax;
                average[i]       = total / frameSize;
                envelope[i]      = maxAbsValue;
                frameEnergy[i]   = energy / frameSize;
                frameDecibels[i] = 10 * Math.Log10(frameEnergy[i]);

                // remove DC value from signal values
                double[] signalMinusAv = new double[frameSize];
                for (int j = 0; j < frameSize; j++)
                {
                    signalMinusAv[j] = signal[start + j] - frameDc;
                }

                // generate the spectra of FFT AMPLITUDES - NOTE: f[0]=DC;  f[64]=Nyquist
                var f1 = fft.InvokeDotNetFFT(signalMinusAv);

                // Previous alternative call to do the FFT and return amplitude spectrum
                //f1 = fft.Invoke(window);

                // Smooth spectrum to reduce variance
                // In the early days (pre-2010), we used to smooth the spectra to reduce sonogram variance. This is statistically correct thing to do.
                // Later, we stopped this for standard sonograms but kept it for calculating acoustic indices.
                // As of 28 March 2017, we are merging the two codes and keeping spectrum smoothing.
                // Will need to check the effect on spectrograms.
                int smoothingWindow = 3;
                f1 = DataTools.filterMovingAverage(f1, smoothingWindow);

                // transfer amplitude spectrum to spectrogram matrix
                for (int j = 0; j < fft.CoeffCount; j++)
                {
                    spectrogram[i, j] = f1[j];
                }
            } // end frames

            // Remove the DC column ie column zero from amplitude spectrogram.
            double[,] amplitudeSpectrogram = MatrixTools.Submatrix(spectrogram, 0, 1, spectrogram.GetLength(0) - 1, spectrogram.GetLength(1) - 1);

            // check the envelope for clipping. Accept a clip if two consecutive frames have max value = 1,0
            Clipping.GetClippingCount(signal, envelope, frameStep, epsilon, out int highAmplitudeCount, out int clipCount);

            // get SNR data
            var snrData = new SNR(signal, frameIDs);

            return(new EnvelopeAndFft
            {
                // The following data is required when constructing sonograms
                Duration = TimeSpan.FromSeconds((double)signal.Length / sampleRate),
                Epsilon = epsilon,
                SampleRate = sampleRate,
                FrameCount = frameCount,
                FractionOfHighEnergyFrames = snrData.FractionOfHighEnergyFrames,
                WindowPower = fft.WindowPower,
                AmplitudeSpectrogram = amplitudeSpectrogram,

                // The below 11 variables are only used when calculating spectral and summary indices
                // energy level information
                ClipCount = clipCount,
                HighAmplitudeCount = highAmplitudeCount,
                MinSignalValue = minSignalValue,
                MaxSignalValue = maxSignalValue,

                // envelope info
                Average = average,
                MinFrameValues = minValues,
                MaxFrameValues = maxValues,
                Envelope = envelope,
                FrameEnergy = frameEnergy,
                FrameDecibels = frameDecibels,

                // freq scale info
                NyquistFreq = sampleRate / 2,
                NyquistBin = amplitudeSpectrogram.GetLength(1) - 1,
                FreqBinWidth = sampleRate / (double)amplitudeSpectrogram.GetLength(1) / 2,
            });
        }
コード例 #7
0
        public static double[] GetOscillationArrayUsingCwt(double[,] xCorrByTimeMatrix, double framesPerSecond, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);

            //int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // loop over the singular values and
            // transfer data from SVD.UMatrix to a single vector of oscilation values
            var oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < 10; e++)
            {
                var autocor = new double[xCorrLength];

                // the sign of the left singular vectors are usually negative.
                if (autocor[0] < 0)
                {
                    for (int i = 0; i < autocor.Length; i++)
                    {
                        autocor[i] *= -1.0;
                    }
                }

                // ##########################################################\
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.5;

                spectrum = DataTools.SquareValues(spectrum);
                double avPower    = spectrum.Sum() / spectrum.Length;
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];

                //double relativePower1 = powerAtMax / sumOfSquares;
                double relativePower2 = powerAtMax / avPower;

                //if (relativePower1 > 0.05)
                if (relativePower2 > 10.0)
                {
                    // check for boundary overrun
                    if (maxIndex < oscillationsVector.Length)
                    {
                        // add in a new oscillation
                        //oscillationsVector[maxIndex] += powerAtMax;
                        oscillationsVector[maxIndex] += relativePower2;
                    }
                }
            }

            for (int i = 0; i < oscillationsVector.Length; i++)
            {
                // NormaliseMatrixValues by sample count
                //oscillationsVector[i] /= sampleCount;
                // do log transform
                if (oscillationsVector[i] < 1.0)
                {
                    oscillationsVector[i] = 0.0;
                }
                else
                {
                    oscillationsVector[i] = Math.Log10(1 + oscillationsVector[i]);
                }
            }

            return(oscillationsVector);
        }
コード例 #8
0
        /// <summary>
        ///  reduces the sequence of Xcorrelation vectors to a single summary vector.
        ///  Does this by:
        ///  (1) do SVD on the collection of XCORRELATION vectors
        ///  (2) select the dominant ones based on the eigen values - 90% threshold
        ///      Typically there are 1 to 10 eigen values depending on how busy the bin is.
        ///  (3) Do an FFT on each of the returned SVD vectors to pick the dominant oscillation rate.
        ///  (4) Accumulate the oscillations in a freq by oscillation rate matrix.
        ///      The amplitude value for the oscillation is the eigenvalue.
        /// #
        ///  NOTE: There should only be one dominant oscillation in any one freq band at one time.
        ///        Birds with oscillating calls do call simultaneously, but this technique will only pick up the dominant call.
        /// #.
        /// </summary>
        /// <param name="xCorrByTimeMatrix">double[,] xCorrelationsByTime = new double[sampleLength, sampleCount]. </param>
        /// <param name="sensitivity">can't remember what this does.</param>
        /// <param name="binNumber">only used when debugging.</param>
        public static double[] GetOscillationArrayUsingSvdAndFft(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);

            // int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // do singular value decomp on the xcorrelation vectors.
            // we want to compute the U and V matrices of singular vectors.
            //var svd = DenseMatrix.OfArray(xCorrByTimeMatrix).Svd(true);
            var svd = DenseMatrix.OfArray(xCorrByTimeMatrix).Svd();

            // svd.S returns the singular values in a vector
            Vector <double> singularValues = svd.S;

            // get total energy in first singular values
            double energySum = 0.0;

            foreach (double v in singularValues)
            {
                energySum += v * v;
            }

            // get the 90% most significant ####### THis is a significant parameter but not critical. 90% is OK
            double significanceThreshold = 0.9;
            double energy = 0.0;
            int    countOfSignificantSingularValues = 0;

            for (int n = 0; n < singularValues.Count; n++)
            {
                energy += singularValues[n] * singularValues[n];
                double fraction = energy / energySum;
                if (fraction > significanceThreshold)
                {
                    countOfSignificantSingularValues = n + 1;
                    break;
                }
            }

            //foreach (double d in singularValues)
            //    Console.WriteLine("singular value = {0}", d);
            //Console.WriteLine("Freq bin:{0}  Count Of Significant SingularValues = {1}", binNumber, countOfSignificantSingularValues);

            // svd.U returns the LEFT singular vectors in matrix
            Matrix <double> uMatrix = svd.U;

            //Matrix<double> relevantU = UMatrix.SubMatrix(0, UMatrix.RowCount-1, 0, eigenVectorCount);

            //Console.WriteLine("\n\n");
            //MatrixTools.writeMatrix(UMatrix.ToArray());
            //string pathUmatrix1 = @"C:\SensorNetworks\Output\Sonograms\testMatrixSVD_U1.png";
            //ImageTools.DrawReversedMDNMatrix(UMatrix, pathUmatrix1);
            //string pathUmatrix2 = @"C:\SensorNetworks\Output\Sonograms\testMatrixSVD_U2.png";
            //ImageTools.DrawReversedMDNMatrix(relevantU, pathUmatrix2);

            // loop over the singular values and
            // transfer data from SVD.UMatrix to a single vector of oscilation values
            double[] oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < countOfSignificantSingularValues; e++)
            {
                double[] autocor = uMatrix.Column(e).ToArray();

                // the sign of the left singular vectors are usually negative.
                if (autocor[0] < 0)
                {
                    for (int i = 0; i < autocor.Length; i++)
                    {
                        autocor[i] *= -1.0;
                    }
                }

                // ##########################################################\
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.5;

                spectrum = DataTools.SquareValues(spectrum);

                // get relative power in the three bins around max.
                double sumOfSquares = spectrum.Sum();
                int    maxIndex     = DataTools.GetMaxIndex(spectrum);
                double powerAtMax   = spectrum[maxIndex];

                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[1] + spectrum[2];
                }
                else if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex + 1];
                }

                double relativePower = powerAtMax / sumOfSquares;

                // if the relative power of the max oscillation is large enough,
                // then accumulate its power into the oscillationsVector
                if (relativePower > sensitivity)
                {
                    oscillationsVector[maxIndex] += powerAtMax;
                }
            }

            return(LogTransformOscillationVector(oscillationsVector, countOfSignificantSingularValues));
        }
コード例 #9
0
        public static double[] GetOscillationArrayUsingFft(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // loop over the Auto-correlation vectors and do FFT
            double[] oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < sampleCount; e++)
            {
                double[] autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);

                //DataTools.writeBarGraph(autocor);

                // ##########################################################\
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.66;

                spectrum = DataTools.SquareValues(spectrum);

                // get relative power in the three bins around max.
                double sumOfSquares = spectrum.Sum();

                //double avPower = spectrum.Sum() / spectrum.Length;
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];
                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1];
                }

                if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex + 1];
                }

                double relativePower1 = powerAtMax / sumOfSquares;

                //double relativePower2 = powerAtMax / avPower;

                if (relativePower1 > sensitivity)

                //if (relativePower2 > 10.0)
                {
                    // check for boundary overrun
                    if (maxIndex < oscillationsVector.Length)
                    {
                        // add in a new oscillation
                        oscillationsVector[maxIndex] += powerAtMax;

                        //oscillationsVector[maxIndex] += relativePower;
                    }
                }
            }

            for (int i = 0; i < oscillationsVector.Length; i++)
            {
                // NormaliseMatrixValues by sample count
                oscillationsVector[i] /= sampleCount;

                // do log transform
                if (oscillationsVector[i] < 1.0)
                {
                    oscillationsVector[i] = 0.0;
                }
                else
                {
                    oscillationsVector[i] = Math.Log10(1 + oscillationsVector[i]);
                }
            }

            return(oscillationsVector);
        }