Note
Go to the end to download the full example code.
Interactive server applications with SpectAcoular.
Standalone HTML documents are often not sufficient for full interactivity, especially when Python-side computations are needed to update displayed results. Bokeh can host applications in a web browser and provide real-time updates.
This example creates an interactive application to visualize the beamforming result for Acoular’s three sources example. See the Acoular example documentation. As the HTML document below shows, changing the frequency or grid parameters will not update the plot because the document is static and cannot interact with the Python code. To get updates, the Python code must run in a Bokeh server application to which the client can listen.
To start the Bokeh server, run the following command in the terminal:
$ bokeh serve --show examples/interactive_apps.py
Create three sources example#
Let’s begin with the necessary imports and the simulation pipeline for the three sources example.
from pathlib import Path
import acoular as ac
import spectacoular as sp
from bokeh.io import curdoc
from bokeh.layouts import column, gridplot, row
from bokeh.models import ColumnDataSource, LinearColorMapper
from bokeh.models.widgets import Slider
from bokeh.palettes import viridis
from bokeh.plotting import figure
mg = ac.MicGeom(file=Path(ac.__file__).parent / 'xml' / 'array_64.xml')
# create time data source
three_sources = ac.demo.create_three_sources(mg)
Next, we define a rectangular grid
(RectGrid) that discretizes the source area.
To obtain a source map at 4 kHz (one-third octave) associated with the
defined grid, we use conventional beamforming
(BeamformerBase).
# set up the rectangular grid
rg = ac.RectGrid(x_min=-0.2, x_max=0.2, y_min=-0.2, y_max=0.2, z=-0.3, increment=0.01)
# set up the beamformer
bb = ac.BeamformerBase(
freq_data=ac.PowerSpectra(source=three_sources, block_size=128, window='Hanning'),
steer=ac.SteeringVector(grid=rg, mics=mg),
)
# calculate the beamforming result as sound pressure level (Lp/dB)
res = ac.L_p(bb.synthetic(f=4000, num=3)).T
Create widgets#
To enable interactive changes to the beamforming result, we will create some widgets based on the existing processing chain, arranged in a grid layout.
grid_grid = gridplot(list(sp.get_widgets(rg).values()), ncols=2, width=150)
Plot the beamforming result#
To visualize the beamforming result, we create a figure and use
Bokeh’s Image glyph, which displays 2D source
mappings. The Image glyph requires the x and
y coordinates of the bottom-left grid corner, the width and height of
the grid, and the sound pressure to be displayed.
source_plot = figure(title='Acoular Three Sources', tools='hover,reset,pan,wheel_zoom')
cds = ColumnDataSource(
data={
'bfdata': [res],
'x': [rg.x_min],
'y': [rg.y_min],
'dw': [rg.x_max - rg.x_min],
'dh': [rg.y_max - rg.y_min],
}
)
color_mapper = LinearColorMapper(palette=viridis(100), low=res.max() - 20, high=res.max())
source_plot.image(
color_mapper=color_mapper,
image='bfdata',
x='x',
y='y',
dw='dw',
dh='dh',
alpha=0.9,
anchor='bottom_left',
origin='bottom_left',
source=cds,
)
The Presenter class#
SpectAcoular provides Presenter classes that automatically handle
updates of ColumnDataSource data whenever the
desired evaluation parameters change. Here, we use the
BeamformerPresenter class with
auto_update=True, which means that the data in the
ColumnDataSource is updated automatically
whenever the internal state of the beamformer changes.
bf_presenter = sp.BeamformerPresenter(
source=bb,
cdsource=cds,
freq=4000,
num=3,
auto_update=True,
)
The BeamformerPresenter class provides two traits, freq and num that
can be used to control the frequency and the frequency band width of the beamforming result. As
with all BaseSpectacoular classes, we could use the
get_widgets() function to create Bokeh widgets for these traits.
However, this time, we will take a different approach and assign an existing widget to the freq
trait via the set_widgets() function.
freqs = bb.freq_data.fftfreq()
df = int(bb.freq_data.sample_freq / bb.freq_data.block_size)
freq_slider = Slider(title='f/Hz', value=bf_presenter.freq, start=freqs[1], end=freqs[-1], step=df)
bf_presenter.set_widgets(freq=freq_slider)
To handle interaction, we will use the Bokeh curdoc() function to add the layout to the current document, which allows for interaction with the widgets and the figure in a web browser.
widget_layout = column(freq_slider, grid_grid)
layout = row(widget_layout, source_plot)
curdoc().add_root(layout)