@@ -325,7 +325,7 @@ GraphSpectrumCalc._getFlightChunks = function() {
325
325
return allChunks ;
326
326
} ;
327
327
328
- GraphSpectrumCalc . _getFlightSamplesFreq = function ( ) {
328
+ GraphSpectrumCalc . _getFlightSamplesFreq = function ( scaled = true ) {
329
329
330
330
const allChunks = this . _getFlightChunks ( ) ;
331
331
@@ -335,7 +335,11 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() {
335
335
let samplesCount = 0 ;
336
336
for ( const chunk of allChunks ) {
337
337
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
+ }
339
343
samplesCount ++ ;
340
344
}
341
345
}
@@ -423,13 +427,14 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I
423
427
for ( const vsValueArray of vsValues ) {
424
428
slicedVsValues . push ( vsValueArray . slice ( 0 , samplesCount ) ) ;
425
429
}
430
+
426
431
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
+ } ;
433
438
} ;
434
439
435
440
GraphSpectrumCalc . _getFlightSamplesPidErrorVsSetpoint = function ( axisIndex ) {
@@ -458,8 +463,8 @@ GraphSpectrumCalc._getFlightSamplesPidErrorVsSetpoint = function(axisIndex) {
458
463
}
459
464
460
465
return {
461
- piderror,
462
- setpoint,
466
+ piderror : piderror . slice ( 0 , samplesCount ) ,
467
+ setpoint : setpoint . slice ( 0 , samplesCount ) ,
463
468
maxSetpoint,
464
469
count : samplesCount ,
465
470
} ;
@@ -526,3 +531,116 @@ GraphSpectrumCalc._normalizeFft = function(fftOutput) {
526
531
527
532
return fftData ;
528
533
} ;
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