How to combine multiple spectrogram subplots to produce single plot?

1.4k views Asked by At

I am visualizing four classes of the spectrogram. For a single class, My spectrogram code looks like this

enter image description here

Considering this as one image.

And the code to produce this, is

def spec(filename):
    time_period = 0.5 # FFT time period (in seconds). Can comfortably process time frames from 0.05 seconds - 10 seconds

    # ==============================================

    fs_rate, signal_original = wavfile.read(filename)
    total_time = int(np.floor(len(signal_original)/fs_rate))
    sample_range = np.arange(0,total_time,time_period)
    total_samples = len(sample_range)

    print ("Frequency sampling", fs_rate)
    print ("total time: ", total_time)
    print ("sample time period: ", time_period)
    print ("total samples: ", total_samples)

    output_array = []
    for i in sample_range:

#         print ("Processing: %d / %d (%d%%)" % (i/time_period + 1, total_samples, (i/time_period + 1)*100/total_samples))

        sample_start = int(i*fs_rate)
        sample_end = int((i+time_period)*fs_rate)
        signal = signal_original[sample_start:sample_end]

        l_audio = len(signal.shape)
        #print ("Channels", l_audio)

        if l_audio == 2:
            signal = signal.sum(axis=1) / 2
        N = signal.shape[0]
        #print ("Complete Samplings N", N)

        secs = N / float(fs_rate)
        # print ("secs", secs)
        Ts = 1.0/fs_rate # sampling interval in time
        #print ("Timestep between samples Ts", Ts)

        t = scipy.arange(0, secs, Ts) # time vector as scipy arange field / numpy.ndarray

        FFT = abs(scipy.fft(signal))
        FFT_side = FFT[range(int(N/2))] # one side FFT range
        freqs = scipy.fftpack.fftfreq(signal.size, t[1]-t[0])
        fft_freqs = np.array(freqs)
        freqs_side = freqs[range(int(N/2))] # one side frequency range
        fft_freqs_side = np.array(freqs_side)

        # Reduce to 0-5000 Hz
        bucket_size = 5
        buckets = 16

        FFT_side = FFT_side[0:bucket_size*buckets]
        fft_freqs_side = fft_freqs_side[0:bucket_size*buckets]

        # Combine frequencies into buckets
        FFT_side = np.array([int(sum(FFT_side[current: current+bucket_size])) for current in range(0, len(FFT_side), bucket_size)])
        fft_freqs_side = np.array([int(sum(fft_freqs_side[current: current+bucket_size])) for current in range(0, len(fft_freqs_side), bucket_size)])

        # FFT_side: Normalize (0-1)
        max_value = max(FFT_side)
        if (max_value != 0):
            FFT_side_norm = FFT_side / max_value

        # Append to output array
        output_array.append(FFT_side_norm)

    # ============================================

    # Plotting

    plt.figure(figsize=(4,7))

    plt.subplot(411)
    plt.subplots_adjust(hspace = 0.5)
    plt.plot(t, signal, "g") # plotting the signal
    plt.xlabel('Time')
    plt.ylabel('Amplitude')

    plt.subplot(412)
    diff = np.diff(fft_freqs_side)
    widths = np.hstack([diff, diff[-1]])
    plt.bar(fft_freqs_side, abs(FFT_side_norm), width=widths) # plotting the positive fft spectrum
    plt.xticks(fft_freqs_side, fft_freqs_side, rotation='vertical')
    plt.xlabel('Frequency (Hz)')
    plt.ylabel('Count single-sided')

    FFT_side_norm_line = FFT_side_norm.copy()
    FFT_side_norm_line.resize( (1,buckets) )

    plt.subplot(413)
    plt.imshow(FFT_side_norm_line)
    plt.xlabel('Image Representation 1D')
    plt.show()
    print("\n\n\n\n\n\n")

How can I combine four images plot like this, and make a single output image. Something like this

enter image description here

2

There are 2 answers

0
Camilo Gomez On

You can do it this way:

fig, axs = plt.subplots(2, 2)
axs[0, 0].plot(x, y)
axs[0, 0].set_title('Axis [0, 0]')
axs[0, 1].plot(x, y, 'tab:orange')
axs[0, 1].set_title('Axis [0, 1]')
axs[1, 0].plot(x, -y, 'tab:green')
axs[1, 0].set_title('Axis [1, 0]')
axs[1, 1].plot(x, -y, 'tab:red')
axs[1, 1].set_title('Axis [1, 1]')

for ax in axs.flat:
    ax.set(xlabel='x-label', ylabel='y-label')

# Hide x labels and tick labels for top plots and y ticks for right plots.
for ax in axs.flat:
    ax.label_outer()

The result will be like this:

enter image description here

Taken from https://matplotlib.org/stable/gallery/subplots_axes_and_figures/subplots_demo.html

0
gboffi On

enter image description here

I'd suggest using fig.subfigures and plt.subplot_mosaic.

The plot above was obtained using this simple script:

import matplotlib.pyplot as plt

fig = plt.figure(figsize = (8, 10), layout='constrained')

# next two lines make the trick
sfigs = fig.subfigures(2,2)
mosaics = [f.subplot_mosaic('t;t;t;f;f;f;i;.') for f in sfigs.flat]

# next, "how to" reference the subplots in subfigures

mosaics[0]['t'].plot(range(5), color='b')
mosaics[1]['t'].plot(range(5), color='k')
mosaics[2]['t'].plot(range(5), color='r')
mosaics[3]['t'].plot(range(5), color='g')

mosaics[0]['f'].plot(range(3), color='b')
mosaics[1]['f'].plot(range(3), color='k')
mosaics[2]['f'].plot(range(3), color='r')
mosaics[3]['f'].plot(range(3), color='g')

mosaics[0]['i'].imshow([range(10)]*2)

plt.show()