@@ -325,7 +325,7 @@ GraphSpectrumCalc._getFlightChunks = function() {
325325 return allChunks ;
326326} ;
327327
328- GraphSpectrumCalc . _getFlightSamplesFreq = function ( ) {
328+ GraphSpectrumCalc . _getFlightSamplesFreq = function ( scaled = true ) {
329329
330330 const allChunks = this . _getFlightChunks ( ) ;
331331
@@ -335,7 +335,11 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() {
335335 let samplesCount = 0 ;
336336 for ( const chunk of allChunks ) {
337337 for ( const frame of chunk . frames ) {
338- samples [ samplesCount ] = ( this . _dataBuffer . curve . lookupRaw ( frame [ this . _dataBuffer . fieldIndex ] ) ) ;
338+ if ( scaled ) {
339+ samples [ samplesCount ] = this . _dataBuffer . curve . lookupRaw ( frame [ this . _dataBuffer . fieldIndex ] ) ;
340+ } else {
341+ samples [ samplesCount ] = frame [ this . _dataBuffer . fieldIndex ] ;
342+ }
339343 samplesCount ++ ;
340344 }
341345 }
@@ -423,13 +427,14 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I
423427 for ( const vsValueArray of vsValues ) {
424428 slicedVsValues . push ( vsValueArray . slice ( 0 , samplesCount ) ) ;
425429 }
430+
426431 return {
427- samples : samples . slice ( 0 , samplesCount ) ,
428- vsValues : slicedVsValues ,
429- count : samplesCount ,
430- minValue : minValue ,
431- maxValue : maxValue ,
432- } ;
432+ samples : samples . slice ( 0 , samplesCount ) ,
433+ vsValues : slicedVsValues ,
434+ count : samplesCount ,
435+ minValue : minValue ,
436+ maxValue : maxValue ,
437+ } ;
433438} ;
434439
435440GraphSpectrumCalc . _getFlightSamplesPidErrorVsSetpoint = function ( axisIndex ) {
@@ -458,8 +463,8 @@ GraphSpectrumCalc._getFlightSamplesPidErrorVsSetpoint = function(axisIndex) {
458463 }
459464
460465 return {
461- piderror,
462- setpoint,
466+ piderror : piderror . slice ( 0 , samplesCount ) ,
467+ setpoint : setpoint . slice ( 0 , samplesCount ) ,
463468 maxSetpoint,
464469 count : samplesCount ,
465470 } ;
@@ -526,3 +531,116 @@ GraphSpectrumCalc._normalizeFft = function(fftOutput) {
526531
527532 return fftData ;
528533} ;
534+
535+ /**
536+ * Compute PSD for data samples by Welch method follow Python code
537+ */
538+ GraphSpectrumCalc . _psd = function ( samples , pointsPerSegment , overlapCount , scaling = 'density' ) {
539+ // Compute FFT for samples segments
540+ const fftOutput = this . _fft_segmented ( samples , pointsPerSegment , overlapCount ) ;
541+
542+ const dataCount = fftOutput [ 0 ] . length ;
543+ const segmentsCount = fftOutput . length ;
544+ const psdOutput = new Float64Array ( dataCount ) ;
545+
546+ // Compute power scale coef
547+ let scale = 1 ;
548+ if ( userSettings . analyserHanning ) {
549+ const window = Array ( pointsPerSegment ) . fill ( 1 ) ;
550+ this . _hanningWindow ( window , pointsPerSegment ) ;
551+ if ( scaling == 'density' ) {
552+ let skSum = 0 ;
553+ for ( const value of window ) {
554+ skSum += value ** 2 ;
555+ }
556+ scale = 1 / ( this . _blackBoxRate * skSum ) ;
557+ } else if ( scaling == 'spectrum' ) {
558+ let sum = 0 ;
559+ for ( const value of window ) {
560+ sum += value ;
561+ }
562+ scale = 1 / sum ** 2 ;
563+ }
564+ } else if ( scaling == 'density' ) {
565+ scale = 1 / pointsPerSegment ;
566+ } else if ( scaling == 'spectrum' ) {
567+ scale = 1 / pointsPerSegment ** 2 ;
568+ }
569+
570+ // Compute average for scaled power
571+ let min = 1e6 ,
572+ max = - 1e6 ;
573+ // Early exit if no segments were processed
574+ if ( segmentsCount === 0 ) {
575+ return {
576+ psdOutput : new Float64Array ( 0 ) ,
577+ min : 0 ,
578+ max : 0 ,
579+ maxNoiseIdx : 0 ,
580+ } ;
581+ }
582+ const maxFrequency = ( this . _blackBoxRate / 2.0 ) ;
583+ const noise50HzIdx = 50 / maxFrequency * dataCount ;
584+ const noise3HzIdx = 3 / maxFrequency * dataCount ;
585+ let maxNoiseIdx = 0 ;
586+ let maxNoise = - 100 ;
587+ for ( let i = 0 ; i < dataCount ; i ++ ) {
588+ psdOutput [ i ] = 0.0 ;
589+ for ( let j = 0 ; j < segmentsCount ; j ++ ) {
590+ let p = scale * fftOutput [ j ] [ i ] ** 2 ;
591+ if ( i != dataCount - 1 ) {
592+ p *= 2 ;
593+ }
594+ psdOutput [ i ] += p ;
595+ }
596+
597+ const min_avg = 1e-7 ; // limit min value for -70db
598+ let avg = psdOutput [ i ] / segmentsCount ;
599+ avg = Math . max ( avg , min_avg ) ;
600+ psdOutput [ i ] = 10 * Math . log10 ( avg ) ;
601+ if ( i > noise3HzIdx ) { // Miss big zero freq magnitude
602+ min = Math . min ( psdOutput [ i ] , min ) ;
603+ max = Math . max ( psdOutput [ i ] , max ) ;
604+ }
605+ if ( i > noise50HzIdx && psdOutput [ i ] > maxNoise ) {
606+ maxNoise = psdOutput [ i ] ;
607+ maxNoiseIdx = i ;
608+ }
609+ }
610+
611+ const maxNoiseFrequency = maxNoiseIdx / dataCount * maxFrequency ;
612+
613+ return {
614+ psdOutput : psdOutput ,
615+ min : min ,
616+ max : max ,
617+ maxNoiseIdx : maxNoiseFrequency ,
618+ } ;
619+ } ;
620+
621+
622+ /**
623+ * Compute FFT for samples segments by lenghts as pointsPerSegment with overlapCount overlap points count
624+ */
625+ GraphSpectrumCalc . _fft_segmented = function ( samples , pointsPerSegment , overlapCount ) {
626+ const samplesCount = samples . length ;
627+ let output = [ ] ;
628+ for ( let i = 0 ; i <= samplesCount - pointsPerSegment ; i += pointsPerSegment - overlapCount ) {
629+ const fftInput = samples . slice ( i , i + pointsPerSegment ) ;
630+
631+ if ( userSettings . analyserHanning ) {
632+ this . _hanningWindow ( fftInput , pointsPerSegment ) ;
633+ }
634+
635+ const fftComplex = this . _fft ( fftInput ) ;
636+ const magnitudes = new Float64Array ( pointsPerSegment / 2 ) ;
637+ for ( let i = 0 ; i < pointsPerSegment / 2 ; i ++ ) {
638+ const re = fftComplex [ 2 * i ] ;
639+ const im = fftComplex [ 2 * i + 1 ] ;
640+ magnitudes [ i ] = Math . hypot ( re , im ) ;
641+ }
642+ output . push ( magnitudes ) ;
643+ }
644+
645+ return output ;
646+ } ;
0 commit comments