Tutorial: Compare RF And Acoustic Data At One Position¶

This notebook shows how to:

  1. load the RF and acoustic NetCDF datasets from results/
  2. resolve one physical rover position from the RF dataset
  3. recover the matching (experiment_id, cycle_id) pair
  4. load the acoustic waveform data for that same measurement
  5. plot RF CSI values and acoustic values for the selected point

The RF dataset is the source of truth for rover position. The acoustic dataset is then indexed with the matching experiment_id and cycle_id.

# Optional: uncomment when this Jupyter kernel misses the plotting dependencies.
import sys
!{sys.executable} -m pip install matplotlib numpy xarray pyyaml h5netcdf h5py
# Alternative NetCDF backend: !{sys.executable} -m pip install netCDF4
Requirement already satisfied: matplotlib in c:\users\calle\radioconda\lib\site-packages (3.9.2)
Requirement already satisfied: numpy in c:\users\calle\radioconda\lib\site-packages (2.1.2)
Requirement already satisfied: xarray in c:\users\calle\radioconda\lib\site-packages (2025.6.1)
Requirement already satisfied: pyyaml in c:\users\calle\radioconda\lib\site-packages (6.0.1)
Requirement already satisfied: h5netcdf in c:\users\calle\radioconda\lib\site-packages (1.8.1)
Requirement already satisfied: h5py in c:\users\calle\radioconda\lib\site-packages (3.12.1)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (1.3.0)
Requirement already satisfied: cycler>=0.10 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (4.54.1)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (1.4.7)
Requirement already satisfied: packaging>=20.0 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (24.1)
Requirement already satisfied: pillow>=8 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (10.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (3.2.0)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\calle\radioconda\lib\site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: pandas>=2.1 in c:\users\calle\radioconda\lib\site-packages (from xarray) (2.2.3)
Requirement already satisfied: pytz>=2020.1 in c:\users\calle\radioconda\lib\site-packages (from pandas>=2.1->xarray) (2024.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\calle\radioconda\lib\site-packages (from pandas>=2.1->xarray) (2024.2)
Requirement already satisfied: six>=1.5 in c:\users\calle\radioconda\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
[notice] A new release of pip is available: 24.3.1 -> 26.0.1
[notice] To update, run: python.exe -m pip install --upgrade pip
from pathlib import Path
import importlib.util
import sys

import matplotlib.pyplot as plt
import numpy as np
import xarray as xr

NOTEBOOK_DIR = Path.cwd().resolve()
for candidate_dir in (
    NOTEBOOK_DIR,
    NOTEBOOK_DIR / "tutorials",
    NOTEBOOK_DIR / "processing" / "tutorials",
):
    if (candidate_dir / "csi_plot_utils.py").exists():
        NOTEBOOK_DIR = candidate_dir.resolve()
        break
else:
    raise ImportError(f"Could not locate csi_plot_utils.py from {Path.cwd().resolve()}")

UTILS_PATH = NOTEBOOK_DIR / "csi_plot_utils.py"
PROCESSING_DIR = NOTEBOOK_DIR.parent
PROJECT_ROOT = PROCESSING_DIR.parent
spec = importlib.util.spec_from_file_location("csi_plot_utils", UTILS_PATH)
if spec is None or spec.loader is None:
    raise ImportError(f"Could not load utility module from {UTILS_PATH}")
csi = importlib.util.module_from_spec(spec)
sys.modules["csi_plot_utils"] = csi
spec.loader.exec_module(csi)

RESULTS_DIR = PROJECT_ROOT / "results"
ACOUSTIC_DOWNLOAD_SCRIPT = Path("processing") / "dataset-download" / "download_acoustic_datasets.py"
plt.rcParams["figure.figsize"] = (12, 4.5)
RF_DATASET_PATH = None
ACOUSTIC_DATASET_PATH = None

SEARCH_EXPERIMENT_IDS = None  # Example: ["EXP003", "EXP005"]. None means: search all RF experiments.
DEFAULT_EXPERIMENT_ID = "EXP003"  # Used when TARGET_POSITION is None.
SELECTED_CYCLE_ID = None  # Set an integer to override the automatic cycle selection.
TARGET_POSITION = None  # Example: {"x": 1.92, "y": 2.87, "z": None}

RF_VALUE_MODE = "power_db"  # One of: "power_db", "amplitude", "phase_deg"
ACOUSTIC_MICROPHONE_LABEL = None  # Example: "A06". None means: use the first microphone with data.
ACOUSTIC_MAX_HEATMAP_MICROPHONES = 24
def acoustic_download_instructions(experiment_id: str) -> str:
    script_path = ACOUSTIC_DOWNLOAD_SCRIPT.as_posix()
    return (
        f"Could not find an acoustic dataset for {experiment_id} in {RESULTS_DIR}. "
        "Expected a file such as acoustic_<EXP>.nc. "
        f"From the repo root, run `python {script_path} --experiment-id {experiment_id}` to download it into {RESULTS_DIR}, "
        f"or `python {script_path} --list` to inspect the server listing first."
    )


def resolve_acoustic_dataset_path(experiment_id: str, dataset_path: str | Path | None = None) -> Path:
    if dataset_path is not None:
        return Path(dataset_path).resolve()

    patterns = [
        f"acoustic_{experiment_id}.nc",
        f"acoustic_{experiment_id}_*.nc",
        f"acoustic_{experiment_id}*.nc",
    ]
    candidates: list[Path] = []
    for pattern in patterns:
        candidates.extend(sorted(RESULTS_DIR.glob(pattern)))
    candidates = list(dict.fromkeys(path.resolve() for path in candidates))
    if not candidates:
        raise FileNotFoundError(acoustic_download_instructions(experiment_id))
    return candidates[0]


def open_acoustic_dataset(experiment_id: str, dataset_path: str | Path | None = None) -> tuple[xr.Dataset, Path]:
    path = resolve_acoustic_dataset_path(experiment_id, dataset_path)
    ds = csi.open_netcdf_dataset(path, label=f"acoustic dataset for {experiment_id}")
    available = ds["experiment_id"].values.astype(str).tolist()
    if experiment_id not in available:
        raise ValueError(
            f"Acoustic dataset {path} does not contain experiment_id={experiment_id}. "
            f"Available experiment IDs: {available}"
        )
    return ds, path


def resolve_measurement(rf_ds: xr.Dataset) -> dict[str, object]:
    search_ids = SEARCH_EXPERIMENT_IDS
    if TARGET_POSITION is not None:
        nearest = csi.find_nearest_position_cycle(
            rf_ds,
            search_ids,
            x=TARGET_POSITION["x"],
            y=TARGET_POSITION["y"],
            z=TARGET_POSITION.get("z"),
        )
        nearest["selection_mode"] = "nearest_position"
        return nearest

    candidate_experiment_ids = (
        csi.normalize_experiment_ids(search_ids)
        if search_ids is not None
        else csi.available_experiment_ids(rf_ds)
    )
    if not candidate_experiment_ids:
        raise ValueError("No experiment_id values are available in the RF dataset.")

    selected_experiment_id = str(DEFAULT_EXPERIMENT_ID or candidate_experiment_ids[0])
    if SELECTED_CYCLE_ID is None:
        positions = csi.positions_for_experiments(rf_ds, [selected_experiment_id])
        if positions.sizes.get("measurement_index", 0) == 0:
            raise ValueError(f"No valid RF positions are available for experiment {selected_experiment_id}.")

        measurement_index = 0
        return {
            "experiment_id": str(positions["experiment_id"].values[measurement_index]),
            "cycle_id": int(positions["cycle_id"].values[measurement_index]),
            "position_available": True,
            "rover_x": float(positions["rover_x"].values[measurement_index]),
            "rover_y": float(positions["rover_y"].values[measurement_index]),
            "rover_z": float(positions["rover_z"].values[measurement_index]),
            "csi_host_count": int(positions["csi_host_count"].values[measurement_index]),
            "selection_mode": "first_valid_position",
        }

    measurement = csi.cycle_position(rf_ds, selected_experiment_id, int(SELECTED_CYCLE_ID))
    measurement["selection_mode"] = "cycle_id"
    return measurement


def extract_acoustic_cycle(acoustic_ds: xr.Dataset, experiment_id: str, cycle_id: int) -> tuple[np.ndarray, np.ndarray]:
    available_cycles = acoustic_ds["cycle_id"].values.astype(int)
    if int(cycle_id) not in available_cycles.tolist():
        raise ValueError(
            f"Acoustic dataset has no cycle_id={cycle_id} for {experiment_id}. "
            f"Available cycle count: {available_cycles.size}"
        )

    cycle = acoustic_ds.sel(experiment_id=experiment_id, cycle_id=int(cycle_id))
    microphone_labels = cycle["microphone_label"].values.astype(str)
    values = np.asarray(cycle["values"].values, dtype=float)
    if values.ndim == 1:
        values = values[np.newaxis, :]

    available_mask = np.isfinite(values).any(axis=1)
    return microphone_labels[available_mask], values[available_mask]


def select_acoustic_microphone(
    microphone_labels: np.ndarray,
    values: np.ndarray,
    microphone_label: str | None,
) -> tuple[str, np.ndarray]:
    if microphone_labels.size == 0:
        raise ValueError("No acoustic microphone waveforms are available for the selected cycle.")

    if microphone_label is None:
        index = 0
    else:
        matches = np.where(np.char.upper(microphone_labels) == str(microphone_label).upper())[0]
        if matches.size == 0:
            raise ValueError(
                f"Microphone {microphone_label!r} is not available. "
                f"Available microphones include: {microphone_labels[:10].tolist()}"
            )
        index = int(matches[0])

    return str(microphone_labels[index]), values[index]


def rf_value_series(snapshot: xr.Dataset, value_mode: str) -> tuple[str, np.ndarray]:
    mode = str(value_mode).lower()
    mapping = {
        "power_db": ("csi_power_db", snapshot["csi_power_db"].values.astype(float)),
        "amplitude": ("csi_amplitude", snapshot["csi_amplitude"].values.astype(float)),
        "phase_deg": ("csi_phase_deg", snapshot["csi_phase_deg"].values.astype(float)),
    }
    if mode not in mapping:
        raise ValueError(f"Unsupported RF_VALUE_MODE={value_mode!r}. Choose one of: {sorted(mapping)}")
    return mapping[mode]
rf_ds, rf_dataset_path = csi.open_dataset(experiment_id=SEARCH_EXPERIMENT_IDS, dataset_path=RF_DATASET_PATH)
measurement = resolve_measurement(rf_ds)

SELECTED_EXPERIMENT_ID = str(measurement["experiment_id"])
SELECTED_CYCLE_ID = int(measurement["cycle_id"])
acoustic_ds, acoustic_dataset_path = open_acoustic_dataset(SELECTED_EXPERIMENT_ID, ACOUSTIC_DATASET_PATH)

print(f"Loaded RF dataset: {rf_dataset_path}")
print(f"Loaded acoustic dataset: {acoustic_dataset_path}")
print(f"Selection mode: {measurement['selection_mode']}")
print(f"Resolved experiment_id: {SELECTED_EXPERIMENT_ID}")
print(f"Resolved cycle_id: {SELECTED_CYCLE_ID}")
measurement
Loaded RF dataset: C:\Users\Calle\OneDrive\Documenten\GitHub\ELLIIIT-dataset-26\results\csi_EXP003__EXP005__EXP006__EXP007__EXP008__EXP009__EXP010__EXP011__EXP012.nc
Loaded acoustic dataset: C:\Users\Calle\OneDrive\Documenten\GitHub\ELLIIIT-dataset-26\results\acoustic_EXP003.nc
Selection mode: first_valid_position
Resolved experiment_id: EXP003
Resolved cycle_id: 1
{'experiment_id': 'EXP003',
 'cycle_id': 1,
 'position_available': True,
 'rover_x': 1.9183037109375,
 'rover_y': 2.865016845703125,
 'rover_z': 0.7394267578125,
 'csi_host_count': 42,
 'selection_mode': 'first_valid_position'}

Inspect The Selected RF Position¶

The grey cloud below contains the valid RF rover positions used for the search. The red star is the measurement that will also be used to index the acoustic dataset.

positions = csi.positions_for_experiments(rf_ds, SEARCH_EXPERIMENT_IDS)

fig, ax = plt.subplots(figsize=(6.8, 6.2))
ax.scatter(
    positions["rover_x"].values.astype(float),
    positions["rover_y"].values.astype(float),
    s=18,
    alpha=0.35,
    color="0.6",
    label="All valid RF positions",
)
ax.scatter(
    [float(measurement["rover_x"])],
    [float(measurement["rover_y"])],
    s=120,
    color="crimson",
    marker="*",
    label=f"Selected: {SELECTED_EXPERIMENT_ID} / cycle {SELECTED_CYCLE_ID}",
)
ax.set_aspect("equal")
ax.set_xlabel("Rover x [m]")
ax.set_ylabel("Rover y [m]")
ax.set_title("RF Position Used To Resolve The Acoustic Measurement")
ax.legend(loc="best")
plt.show()
No description has been provided for this image

RF Values At The Selected Position¶

The RF snapshot is extracted from the CSI dataset for the resolved (experiment_id, cycle_id) pair.

antenna_positions = csi.load_antenna_positions()
rf_snapshot = csi.extract_csi_snapshot(
    rf_ds,
    SELECTED_EXPERIMENT_ID,
    SELECTED_CYCLE_ID,
    antenna_positions=antenna_positions,
)
rf_snapshot
<xarray.Dataset> Size: 3kB
Dimensions:        (hostname: 42)
Coordinates:
  * hostname       (hostname) <U3 504B 'G05' 'F05' 'E05' ... 'C10' 'B10' 'A10'
Data variables:
    csi_real       (hostname) float64 336B 0.002539 -0.002133 ... 0.003275
    csi_imag       (hostname) float64 336B 0.000818 -4.723e-05 ... 0.007729
    csi_amplitude  (hostname) float64 336B 0.002668 0.002133 ... 0.008394
    csi_power_db   (hostname) float64 336B -51.48 -53.42 ... -52.43 -41.52
    csi_phase_deg  (hostname) float64 336B 17.86 -178.7 -69.94 ... -105.5 67.04
    antenna_x      (hostname) float64 336B 0.35 1.55 2.75 ... 5.15 6.35 7.55
    antenna_y      (hostname) float64 336B 0.66 0.66 0.66 ... 3.66 3.66 3.66
    antenna_z      (hostname) float64 336B 2.4 2.4 2.4 2.4 ... 2.4 2.4 2.4 2.4
Attributes:
    experiment_id:       EXP003
    cycle_id:            1
    position_available:  True
    rover_x:             1.9183037109375
    rover_y:             2.865016845703125
    rover_z:             0.7394267578125
    csi_host_count:      42
xarray.Dataset
    • hostname: 42
    • hostname
      (hostname)
      <U3
      'G05' 'F05' 'E05' ... 'B10' 'A10'
      array(['G05', 'F05', 'E05', 'D05', 'C05', 'B05', 'A05', 'G06', 'F06', 'E06',
             'D06', 'C06', 'B06', 'A06', 'G07', 'F07', 'E07', 'D07', 'C07', 'B07',
             'A07', 'G08', 'F08', 'E08', 'D08', 'C08', 'B08', 'A08', 'G09', 'F09',
             'E09', 'D09', 'C09', 'B09', 'A09', 'G10', 'F10', 'E10', 'D10', 'C10',
             'B10', 'A10'], dtype='<U3')
    • csi_real
      (hostname)
      float64
      0.002539 -0.002133 ... 0.003275
      array([ 0.00253915, -0.0021329 ,  0.00775323, -0.00146286,  0.0102869 ,
              0.00334053,  0.00981416,  0.02233973, -0.01667921,  0.00628579,
              0.01076409, -0.0060573 ,  0.00093531,  0.00644217,  0.01634078,
             -0.00076861, -0.00257259, -0.00247897, -0.00391324, -0.00697323,
              0.00484674, -0.02469186,  0.00289381,  0.00144258,  0.00694542,
             -0.00625906, -0.00545229,  0.00036805, -0.00197713,  0.0277492 ,
             -0.01322389, -0.01313527, -0.00634714, -0.00956864, -0.00458623,
             -0.02389963, -0.03606365,  0.01385979,  0.0097141 , -0.00506383,
             -0.00064021,  0.00327471])
    • csi_imag
      (hostname)
      float64
      0.000818 -4.723e-05 ... 0.007729
      array([ 8.17997347e-04, -4.72327285e-05, -2.12289494e-02,  3.94306020e-03,
             -4.68772566e-03, -3.72670212e-03, -1.86194304e-03,  2.75024800e-02,
              6.10924738e-03, -1.65577200e-02,  6.08372759e-03,  1.26524333e-03,
              3.90656100e-03, -3.84528334e-03, -2.15343495e-03,  1.20392800e-03,
             -1.14439828e-03,  2.00674807e-03, -4.79137541e-05, -8.44146060e-03,
             -4.19017556e-03,  2.41104462e-02,  2.87520854e-02,  8.78084242e-03,
             -6.26920718e-03,  2.28160368e-04, -7.21652743e-05, -1.73134731e-03,
              8.40189055e-03, -1.10313788e-02, -1.92502483e-02,  5.12686735e-03,
             -2.70615179e-03, -6.51874087e-03, -3.57859306e-03,  8.68850846e-03,
              5.77083044e-03, -4.85708940e-03,  2.42566565e-03, -9.04766459e-03,
             -2.30234289e-03,  7.72882921e-03])
    • csi_amplitude
      (hostname)
      float64
      0.002668 0.002133 ... 0.008394
      array([0.00266766, 0.00213342, 0.02260046, 0.00420567, 0.01130465,
             0.00500474, 0.00998922, 0.03543233, 0.01776285, 0.01771071,
             0.01236435, 0.00618803, 0.00401697, 0.00750252, 0.01648206,
             0.00142836, 0.00281565, 0.00318941, 0.00391353, 0.01094916,
             0.00640691, 0.03451089, 0.02889735, 0.00889855, 0.00935638,
             0.00626322, 0.00545277, 0.00177004, 0.00863138, 0.0298615 ,
             0.02335473, 0.01410036, 0.00689996, 0.01157812, 0.0058172 ,
             0.02542995, 0.03652245, 0.01468622, 0.01001237, 0.01036835,
             0.0023897 , 0.00839396])
    • csi_power_db
      (hostname)
      float64
      -51.48 -53.42 ... -52.43 -41.52
      array([-51.47740597, -53.41846056, -32.9176534 , -47.52328899,
             -38.93485652, -46.0123658 , -40.00936545, -29.01200636,
             -35.00974656, -35.03528059, -38.15657074, -44.16895521,
             -47.92203265, -42.49586216, -35.65977076, -56.90326763,
             -51.00843357, -49.92579591, -48.14862116, -39.21238028,
             -43.86702719, -29.24087727, -30.78284112, -41.01361265,
             -40.57784601, -44.0640483 , -45.26766191, -55.04036113,
             -41.27839123, -30.49776639, -32.63250381, -37.01539556,
             -43.22307456, -38.72723815, -44.70571862, -31.89308889,
             -28.7488024 , -36.66179756, -39.98926012, -39.68580939,
             -52.43314362, -41.52066587])
    • csi_phase_deg
      (hostname)
      float64
      17.86 -178.7 ... -105.5 67.04
      array([  17.85657643, -178.73140154,  -69.9368049 ,  110.35468499,
              -24.49868062,  -48.1276879 ,  -10.74248492,   50.91371809,
              159.88326669,  -69.21178835,   29.47459568,  168.2017393 ,
               76.53561794,  -30.83266961,   -7.50734412,  122.55488113,
             -156.01846076,  141.00952306, -179.29850474, -129.55906443,
              -40.84453311,  135.68256603,   84.25270691,   80.67036426,
              -42.07065143,  177.91233237, -179.24169019,  -77.99867133,
              103.24187692,  -21.67973171, -124.48698014,  158.67865411,
             -156.90863687, -145.73492874, -142.03540725,  160.02169192,
              170.90872127,  -19.31273741,   14.02036877, -119.23503316,
             -105.53958896,   67.03761715])
    • antenna_x
      (hostname)
      float64
      0.35 1.55 2.75 ... 5.15 6.35 7.55
      array([0.35, 1.55, 2.75, 3.95, 5.15, 6.35, 7.55, 0.35, 1.55, 2.75, 3.95,
             5.15, 6.35, 7.55, 0.35, 1.55, 2.75, 3.95, 5.15, 6.35, 7.55, 0.35,
             1.55, 2.75, 3.95, 5.15, 6.35, 7.55, 0.35, 1.55, 2.75, 3.95, 5.15,
             6.35, 7.55, 0.35, 1.55, 2.75, 3.95, 5.15, 6.35, 7.55])
    • antenna_y
      (hostname)
      float64
      0.66 0.66 0.66 ... 3.66 3.66 3.66
      array([0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 1.26, 1.26, 1.26, 1.26,
             1.26, 1.26, 1.26, 1.86, 1.86, 1.86, 1.86, 1.86, 1.86, 1.86, 2.46,
             2.46, 2.46, 2.46, 2.46, 2.46, 2.46, 3.06, 3.06, 3.06, 3.06, 3.06,
             3.06, 3.06, 3.66, 3.66, 3.66, 3.66, 3.66, 3.66, 3.66])
    • antenna_z
      (hostname)
      float64
      2.4 2.4 2.4 2.4 ... 2.4 2.4 2.4 2.4
      array([2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4,
             2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4,
             2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4,
             2.4, 2.4, 2.4])
    • hostname
      PandasIndex
      PandasIndex(Index(['G05', 'F05', 'E05', 'D05', 'C05', 'B05', 'A05', 'G06', 'F06', 'E06',
             'D06', 'C06', 'B06', 'A06', 'G07', 'F07', 'E07', 'D07', 'C07', 'B07',
             'A07', 'G08', 'F08', 'E08', 'D08', 'C08', 'B08', 'A08', 'G09', 'F09',
             'E09', 'D09', 'C09', 'B09', 'A09', 'G10', 'F10', 'E10', 'D10', 'C10',
             'B10', 'A10'],
            dtype='object', name='hostname'))
  • experiment_id :
    EXP003
    cycle_id :
    1
    position_available :
    True
    rover_x :
    1.9183037109375
    rover_y :
    2.865016845703125
    rover_z :
    0.7394267578125
    csi_host_count :
    42
rf_variable_name, rf_values = rf_value_series(rf_snapshot, RF_VALUE_MODE)
hostnames = rf_snapshot["hostname"].values.astype(str)
ylabel_map = {
    "csi_power_db": "Power [dB]",
    "csi_amplitude": "Amplitude",
    "csi_phase_deg": "Phase [deg]",
}

fig, ax = plt.subplots(figsize=(12, 4.5))
ax.plot(hostnames, rf_values, marker="o", linewidth=1.2, markersize=4)
ax.set_xlabel("Hostname")
ax.set_ylabel(ylabel_map[rf_variable_name])
ax.set_title(
    f"RF {rf_variable_name} at {SELECTED_EXPERIMENT_ID} / cycle {SELECTED_CYCLE_ID}"
)
ax.grid(True, alpha=0.3)
ax.tick_params(axis="x", rotation=90)
plt.show()
No description has been provided for this image

Matching Acoustic Values¶

The acoustic dataset is opened for the resolved experiment and then indexed with the same cycle_id.

acoustic_microphone_labels, acoustic_values = extract_acoustic_cycle(
    acoustic_ds,
    SELECTED_EXPERIMENT_ID,
    SELECTED_CYCLE_ID,
)
selected_microphone_label, selected_waveform = select_acoustic_microphone(
    acoustic_microphone_labels,
    acoustic_values,
    ACOUSTIC_MICROPHONE_LABEL,
)

print(f"Acoustic microphones with data: {acoustic_microphone_labels.size}")
print(f"Selected microphone for the waveform plot: {selected_microphone_label}")
print(f"Samples per waveform: {acoustic_values.shape[1]}")
acoustic_microphone_labels[:min(10, acoustic_microphone_labels.size)]
Acoustic microphones with data: 91
Selected microphone for the waveform plot: A06
Samples per waveform: 14999
array(['A06', 'A08', 'A09', 'A10', 'A11', 'A12', 'A13', 'A14', 'B01',
       'B02'], dtype='<U3')
heatmap_count = min(ACOUSTIC_MAX_HEATMAP_MICROPHONES, acoustic_microphone_labels.size)
heatmap_labels = acoustic_microphone_labels[:heatmap_count]
heatmap_values = acoustic_values[:heatmap_count]

fig, axes = plt.subplots(2, 1, figsize=(12, 8), constrained_layout=True, height_ratios=[1.0, 1.6])

axes[0].plot(np.arange(selected_waveform.size), selected_waveform, linewidth=1.0)
axes[0].set_xlabel("Sample index")
axes[0].set_ylabel("Acoustic value")
axes[0].set_title(
    f"Acoustic waveform at {SELECTED_EXPERIMENT_ID} / cycle {SELECTED_CYCLE_ID} / mic {selected_microphone_label}"
)
axes[0].grid(True, alpha=0.25)

image = axes[1].imshow(heatmap_values, aspect="auto", interpolation="none", cmap="viridis")
axes[1].set_xlabel("Sample index")
axes[1].set_ylabel("Microphone label")
axes[1].set_title(f"Acoustic values across the first {heatmap_count} microphones with data")
axes[1].set_yticks(np.arange(heatmap_count))
axes[1].set_yticklabels(heatmap_labels.tolist())
fig.colorbar(image, ax=axes[1], label="Acoustic value")

plt.show()
No description has been provided for this image