mcsm-benchs: Benchmarking methods for component retrieval

[1]:
import numpy as np
from numpy import pi as pi
import scipy.signal as sg
import pandas as pd
from matplotlib import pyplot as plt
from mcsm_benchs.Benchmark import Benchmark
from mcsm_benchs.ResultsInterpreter import ResultsInterpreter
from mcsm_benchs.SignalBank import SignalBank
from utils import get_stft, invert_stft

Creating a dictionary of signals

We can use the SignalBank class to generate a dictionary of signals to study. We are going to use a signal with two components: 1) a linear chirp, 2) a cosenoidal chirp. Below we can see how to generate the signal as well as its spectrogram.

We use the option return_signal=True, so that the signals generated by the SignalBank are objects of the Signal class, which behave like a regular numpy array, but include additional information of the generated signals, such as the instantaneous frequency of each signal component.

[2]:
# Create a dictionary of signals:
N = 2048
sb = SignalBank(N=N, return_signal=True)

signal_1 = sb.signal_mc_damped_cos()

# Create a dictionary of signals for later.
signals = {'linear_chirp':signal_1,}

# Display the spectrograms of the signals.
stft1 = get_stft(signal_1)

fig, axs = plt.subplots(1,1, sharey=True)
axs.imshow(np.abs(stft1)**2, origin='lower')
axs.set_xlabel('time'); axs.set_ylabel('frequency')
[2]:
Text(0, 0.5, 'frequency')
../_images/notebooks_demo_component_estim_4_1.png

Creating a dictionary of methods

We define a simple method that tracks two ridges in the spectrogram as a chain of maxima for each column. After detecting a ridge, the method sets to \(0\) a number \(L\) of pixes above and below the ridge, allowing to search for another ridge using the residual STFT. This is known as a peeling scheme. Finding a particular value of \(L\) for a signal depends on the SNR of the component. \(2L\) is also the width of the strip of the STFT used to recover the individual components.

We will study the results for this only method but using different values of the parameter \(L\), and evaluate the reconstruction of each component.

The method should return a list of components, in any order. The benchmarking procedure takes care of matching the output of the method with the most similar component from the signal in order to compute the quality reconstruction factor (QRF) for each component.

[3]:
def get_ridge(stft, L=100):
    """
    Get a ridge from the stft of a signal
    """
    spectrogram = np.abs(stft)**2
    stft2 = stft.copy()
    mask = np.zeros_like(spectrogram)
    ridge = np.zeros((spectrogram.shape[1]))
    for i in range(spectrogram.shape[1]):
        ridge[i] = np.argmax(spectrogram[:,i])
        stft2[int(np.max([0,ridge[i]-L])):int(np.min([ridge[i]+L,stft.shape[0]])),i] = 0 # Erase current ridge from spectrogram.
        mask[int(np.max([0,ridge[i]-L])):int(np.min([ridge[i]+L,stft.shape[0]])),i] = 1
    return ridge, stft2, mask

def method_1(signal, L=100):
    # 1. Compute STFT
    stft = get_stft(signal)
    # 2. Get two ridges from spectrogram
    resid_stft = stft
    components = []
    for i in range(2):
        ridge, resid_stft, mask = get_ridge(resid_stft, L=L)
        component = invert_stft(stft,mask=mask)
        components.append(component)
    return components # List of estimated components, even if its only one.

# Check results using one of the signals defined before.
components = method_1(signal_1, L=100) # Output of the method for L=100
fig, axs = plt.subplots(1,3, sharey=True)
axs[0].imshow(np.abs(get_stft(signal_1)), origin='lower')
axs[0].set_title('Original signal')
axs[1].imshow(np.abs(get_stft(components[0])), origin='lower')
axs[1].set_title('Estimated Comp.')
axs[2].imshow(np.abs(get_stft(components[1])), origin='lower')
axs[2].set_title('Estimated Comp.')
[3]:
Text(0.5, 1.0, 'Estimated Comp.')
../_images/notebooks_demo_component_estim_6_1.png

Finally, we create a dictionary of methods to compare (with only one method here), and a dictionary of parameters to use with each method.

[4]:
methods = {'method_1':method_1,}
parameters = {'method_1':[
                        {'L':25},
                        {'L':50},
                        {'L':100},
                        ]
            }

Instantiating a benchmark object

We instantiate a benchmark object and run the tests.

[5]:
benchmark = Benchmark(task='component_denoising',
                      methods=methods,
                      parameters=parameters,
                      signal_ids=signals,
                      SNRin=[0,10,20],
                      repetitions=5,
                      N = N,
                      verbosity=5)
benchmark.run()
Running benchmark...
- Signal linear_chirp
-- SNR: 0 dB
--- Method: method_1
---- Parameters Combination: 0
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.598179817199707
---- Parameters Combination: 1
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5431218147277832
---- Parameters Combination: 2
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5588950157165526
-- SNR: 10 dB
--- Method: method_1
---- Parameters Combination: 0
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5767138004302979
---- Parameters Combination: 1
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5996645450592042
---- Parameters Combination: 2
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5932747364044189
-- SNR: 20 dB
--- Method: method_1
---- Parameters Combination: 0
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5435516834259033
---- Parameters Combination: 1
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5798948764801026
---- Parameters Combination: 2
------ Inner loop. method_1: 0
------ Inner loop. method_1: 1
------ Inner loop. method_1: 2
------ Inner loop. method_1: 3
------ Inner loop. method_1: 4
Elapsed:1.5917760848999023
The test has finished.
[5]:
{'perf_metric': {'linear_chirp': {0: {'method_1': {"{'L': 25}": {'Comp.0': [np.float64(1.9345058847705467),
       np.float64(1.1282311561428875),
       np.float64(0.9410921708666034),
       np.float64(1.1546533420250624),
       np.float64(1.4987758767200394)],
      'Comp.1': [np.float64(5.175580885279313),
       np.float64(8.19876681693958),
       np.float64(5.286697485064469),
       np.float64(8.669357043621943),
       np.float64(7.650636930463604)]},
     "{'L': 50}": {'Comp.0': [np.float64(3.8208649938350403),
       np.float64(5.455134515150642),
       np.float64(3.607283581262556),
       np.float64(4.877066684972691),
       np.float64(4.666206191674506)],
      'Comp.1': [np.float64(4.319067525652214),
       np.float64(7.062947898884301),
       np.float64(4.230686571236454),
       np.float64(7.25084716698017),
       np.float64(6.686451720468276)]},
     "{'L': 100}": {'Comp.0': [np.float64(4.371009198299424),
       np.float64(6.050561048363743),
       np.float64(4.196274908796927),
       np.float64(5.666757338103646),
       np.float64(5.5084936329620735)],
      'Comp.1': [np.float64(3.2591057879819734),
       np.float64(5.345952599845003),
       np.float64(3.1022914571998377),
       np.float64(5.678273229334579),
       np.float64(5.222835953629762)]}}},
   10: {'method_1': {"{'L': 25}": {'Comp.0': [np.float64(3.1794853875344935),
       np.float64(2.4683056726803674),
       np.float64(2.282625013370526),
       np.float64(2.3343049140289445),
       np.float64(2.248433656167679)],
      'Comp.1': [np.float64(12.254898141264105),
       np.float64(14.77843208626979),
       np.float64(11.114214314875817),
       np.float64(14.938372153944979),
       np.float64(12.979656547432782)]},
     "{'L': 50}": {'Comp.0': [np.float64(9.041697553874679),
       np.float64(9.498485790327019),
       np.float64(8.185426251990938),
       np.float64(9.414451523075533),
       np.float64(8.903311988710318)],
      'Comp.1': [np.float64(15.100606969203373),
       np.float64(21.178905645081784),
       np.float64(12.579658650599498),
       np.float64(19.702890059788228),
       np.float64(15.907363628079459)]},
     "{'L': 100}": {'Comp.0': [np.float64(13.434171592022269),
       np.float64(15.220317449623126),
       np.float64(11.54238727037582),
       np.float64(15.254464616987079),
       np.float64(13.654549142188904)],
      'Comp.1': [np.float64(13.991942242839325),
       np.float64(17.74604283758289),
       np.float64(11.78177462908232),
       np.float64(17.230117982698566),
       np.float64(14.503241668406856)]}}},
   20: {'method_1': {"{'L': 25}": {'Comp.0': [np.float64(2.7916975519316036),
       np.float64(2.6709074018981456),
       np.float64(2.662176305585389),
       np.float64(2.5725156138052876),
       np.float64(2.542967232627133)],
      'Comp.1': [np.float64(14.87383862686157),
       np.float64(15.038587595103653),
       np.float64(14.904783776444251),
       np.float64(15.259010046466255),
       np.float64(15.074281252614457)]},
     "{'L': 50}": {'Comp.0': [np.float64(10.16005721906393),
       np.float64(10.017593755138872),
       np.float64(10.105618437252435),
       np.float64(10.013835257391541),
       np.float64(9.998069963273092)],
      'Comp.1': [np.float64(29.46580652383519),
       np.float64(30.68537702951093),
       np.float64(30.37218617952931),
       np.float64(30.902228088645174),
       np.float64(30.82719628692924)]},
     "{'L': 100}": {'Comp.0': [np.float64(18.88766168596554),
       np.float64(18.451550246682388),
       np.float64(18.70504935446653),
       np.float64(18.643398021731063),
       np.float64(18.526666568942503)],
      'Comp.1': [np.float64(27.156250258386887),
       np.float64(27.559718333920642),
       np.float64(27.21948425588033),
       np.float64(27.873369246766018),
       np.float64(27.636428429525353)]}}}}}}

Displaying results.

[6]:
results = benchmark.results # Get dictionary with the results.
df = benchmark.dic2df(results) # Transform dictionary to DataFrame
df = df.reset_index()
df
[6]:
level_0 level_1 level_2 level_3 level_4 level_5 Comp.0 Comp.1
0 perf_metric linear_chirp 0 method_1 {'L': 25} 0 1.934506 5.175581
1 perf_metric linear_chirp 0 method_1 {'L': 25} 1 1.128231 8.198767
2 perf_metric linear_chirp 0 method_1 {'L': 25} 2 0.941092 5.286697
3 perf_metric linear_chirp 0 method_1 {'L': 25} 3 1.154653 8.669357
4 perf_metric linear_chirp 0 method_1 {'L': 25} 4 1.498776 7.650637
5 perf_metric linear_chirp 0 method_1 {'L': 50} 0 3.820865 4.319068
6 perf_metric linear_chirp 0 method_1 {'L': 50} 1 5.455135 7.062948
7 perf_metric linear_chirp 0 method_1 {'L': 50} 2 3.607284 4.230687
8 perf_metric linear_chirp 0 method_1 {'L': 50} 3 4.877067 7.250847
9 perf_metric linear_chirp 0 method_1 {'L': 50} 4 4.666206 6.686452
10 perf_metric linear_chirp 0 method_1 {'L': 100} 0 4.371009 3.259106
11 perf_metric linear_chirp 0 method_1 {'L': 100} 1 6.050561 5.345953
12 perf_metric linear_chirp 0 method_1 {'L': 100} 2 4.196275 3.102291
13 perf_metric linear_chirp 0 method_1 {'L': 100} 3 5.666757 5.678273
14 perf_metric linear_chirp 0 method_1 {'L': 100} 4 5.508494 5.222836
15 perf_metric linear_chirp 10 method_1 {'L': 25} 0 3.179485 12.254898
16 perf_metric linear_chirp 10 method_1 {'L': 25} 1 2.468306 14.778432
17 perf_metric linear_chirp 10 method_1 {'L': 25} 2 2.282625 11.114214
18 perf_metric linear_chirp 10 method_1 {'L': 25} 3 2.334305 14.938372
19 perf_metric linear_chirp 10 method_1 {'L': 25} 4 2.248434 12.979657
20 perf_metric linear_chirp 10 method_1 {'L': 50} 0 9.041698 15.100607
21 perf_metric linear_chirp 10 method_1 {'L': 50} 1 9.498486 21.178906
22 perf_metric linear_chirp 10 method_1 {'L': 50} 2 8.185426 12.579659
23 perf_metric linear_chirp 10 method_1 {'L': 50} 3 9.414452 19.702890
24 perf_metric linear_chirp 10 method_1 {'L': 50} 4 8.903312 15.907364
25 perf_metric linear_chirp 10 method_1 {'L': 100} 0 13.434172 13.991942
26 perf_metric linear_chirp 10 method_1 {'L': 100} 1 15.220317 17.746043
27 perf_metric linear_chirp 10 method_1 {'L': 100} 2 11.542387 11.781775
28 perf_metric linear_chirp 10 method_1 {'L': 100} 3 15.254465 17.230118
29 perf_metric linear_chirp 10 method_1 {'L': 100} 4 13.654549 14.503242
30 perf_metric linear_chirp 20 method_1 {'L': 25} 0 2.791698 14.873839
31 perf_metric linear_chirp 20 method_1 {'L': 25} 1 2.670907 15.038588
32 perf_metric linear_chirp 20 method_1 {'L': 25} 2 2.662176 14.904784
33 perf_metric linear_chirp 20 method_1 {'L': 25} 3 2.572516 15.259010
34 perf_metric linear_chirp 20 method_1 {'L': 25} 4 2.542967 15.074281
35 perf_metric linear_chirp 20 method_1 {'L': 50} 0 10.160057 29.465807
36 perf_metric linear_chirp 20 method_1 {'L': 50} 1 10.017594 30.685377
37 perf_metric linear_chirp 20 method_1 {'L': 50} 2 10.105618 30.372186
38 perf_metric linear_chirp 20 method_1 {'L': 50} 3 10.013835 30.902228
39 perf_metric linear_chirp 20 method_1 {'L': 50} 4 9.998070 30.827196
40 perf_metric linear_chirp 20 method_1 {'L': 100} 0 18.887662 27.156250
41 perf_metric linear_chirp 20 method_1 {'L': 100} 1 18.451550 27.559718
42 perf_metric linear_chirp 20 method_1 {'L': 100} 2 18.705049 27.219484
43 perf_metric linear_chirp 20 method_1 {'L': 100} 3 18.643398 27.873369
44 perf_metric linear_chirp 20 method_1 {'L': 100} 4 18.526667 27.636428

Before displaying the results, we need to format the DataFrame in the correct way:

[7]:
# For the first Component:
df = df.iloc[:,[3,4,1,5,2,6]]
col_names = list(df.columns)
col_names[0:6] = ['Method','Parameter', 'Signal_id','Repetition','SNR','QRF']
df.columns = col_names
df = df.pivot_table(index= ['Method','Parameter', 'Signal_id','Repetition'], columns='SNR', values='QRF')
df = df.reset_index()
df
[7]:
SNR Method Parameter Signal_id Repetition 0 10 20
0 method_1 {'L': 100} linear_chirp 0 4.371009 13.434172 18.887662
1 method_1 {'L': 100} linear_chirp 1 6.050561 15.220317 18.451550
2 method_1 {'L': 100} linear_chirp 2 4.196275 11.542387 18.705049
3 method_1 {'L': 100} linear_chirp 3 5.666757 15.254465 18.643398
4 method_1 {'L': 100} linear_chirp 4 5.508494 13.654549 18.526667
5 method_1 {'L': 25} linear_chirp 0 1.934506 3.179485 2.791698
6 method_1 {'L': 25} linear_chirp 1 1.128231 2.468306 2.670907
7 method_1 {'L': 25} linear_chirp 2 0.941092 2.282625 2.662176
8 method_1 {'L': 25} linear_chirp 3 1.154653 2.334305 2.572516
9 method_1 {'L': 25} linear_chirp 4 1.498776 2.248434 2.542967
10 method_1 {'L': 50} linear_chirp 0 3.820865 9.041698 10.160057
11 method_1 {'L': 50} linear_chirp 1 5.455135 9.498486 10.017594
12 method_1 {'L': 50} linear_chirp 2 3.607284 8.185426 10.105618
13 method_1 {'L': 50} linear_chirp 3 4.877067 9.414452 10.013835
14 method_1 {'L': 50} linear_chirp 4 4.666206 8.903312 9.998070

Finally, we can use the functionality from the ResultsInterpreter class to display the result on interactive plots (using plotly).

[8]:
# Summary interactive plots with Plotly and a report.
from plotly.offline import  iplot
import plotly.io as pio
pio.renderers.default = "plotly_mimetype+notebook"
interpreter = ResultsInterpreter(benchmark)
figs = interpreter.get_summary_plotlys(df, bars=True,)
for fig in figs:
    iplot(fig)

[9]:
results = benchmark.results # Get dictionary with the results.
df = benchmark.dic2df(results) # Transform dictionary to DataFrame
df = df.reset_index()
[10]:
# For the second component:
df = df.iloc[:,[3,4,1,5,2,7]]
col_names = list(df.columns)
col_names[0:6] = ['Method','Parameter', 'Signal_id','Repetition','SNR','QRF']
df.columns = col_names
df = df.pivot_table(index= ['Method','Parameter', 'Signal_id','Repetition'], columns='SNR', values='QRF')
df = df.reset_index()
df
[10]:
SNR Method Parameter Signal_id Repetition 0 10 20
0 method_1 {'L': 100} linear_chirp 0 3.259106 13.991942 27.156250
1 method_1 {'L': 100} linear_chirp 1 5.345953 17.746043 27.559718
2 method_1 {'L': 100} linear_chirp 2 3.102291 11.781775 27.219484
3 method_1 {'L': 100} linear_chirp 3 5.678273 17.230118 27.873369
4 method_1 {'L': 100} linear_chirp 4 5.222836 14.503242 27.636428
5 method_1 {'L': 25} linear_chirp 0 5.175581 12.254898 14.873839
6 method_1 {'L': 25} linear_chirp 1 8.198767 14.778432 15.038588
7 method_1 {'L': 25} linear_chirp 2 5.286697 11.114214 14.904784
8 method_1 {'L': 25} linear_chirp 3 8.669357 14.938372 15.259010
9 method_1 {'L': 25} linear_chirp 4 7.650637 12.979657 15.074281
10 method_1 {'L': 50} linear_chirp 0 4.319068 15.100607 29.465807
11 method_1 {'L': 50} linear_chirp 1 7.062948 21.178906 30.685377
12 method_1 {'L': 50} linear_chirp 2 4.230687 12.579659 30.372186
13 method_1 {'L': 50} linear_chirp 3 7.250847 19.702890 30.902228
14 method_1 {'L': 50} linear_chirp 4 6.686452 15.907364 30.827196
[11]:
# Summary interactive plots with Plotly and a report.
from plotly.offline import  iplot
import plotly.io as pio
pio.renderers.default = "plotly_mimetype+notebook"
interpreter = ResultsInterpreter(benchmark)
figs = interpreter.get_summary_plotlys(df, bars=True,)
for fig in figs:
    iplot(fig)