Hidden Signals · Companion site ← All activities DE·EN
Chapter 17 · A Shared Timeline · Activity 17.1

Three Kingdoms on One Timeline

A single plant signal — and within it who was nearby: nobody, a human, a dog, a sheep. The three kingdoms meet in one bioelectric trace.

Duration double lesson Difficulty high Group in pairs or project Fully digital S

In a nutshell

What: You take the bioelectric signal of one plant and let a model guess which creature was near it — nobody, a human, a dog or a sheep. It is the book's grand finale: plant, animal and human lie side by side on a shared timeline, read with the same procedure as everything before.

The core idea: Two convergences become visible. First that of the method — again signal → features → model, exactly as with voice, cat sound and heartbeat. Second that of the result: the plant, the silent kingdom, carries in its voltage curve traces of the other two kingdoms.

You need: the 3-second recordings from the COIN study (see below), Python with librosa, scikit-learn and xgboost. Without the study data it works too — with your Biolingo sensor from 11.1.

What it's about

For four parts you have learned to read single beings — humans, animals, plants, whole groups. This chapter finally lays them side by side and shows the astonishing thing: it was always the same procedure, and always the same signals. Here you make this convergence tangible yourself — in a single case where all three kingdoms come together.

In a study from the COINs seminar, students clipped a home-built sensor to a basil plant and set different guests before its nose one after another: sometimes nobody (baseline), sometimes a human, sometimes a dog (a carnivore), sometimes a sheep (a herbivore). Then they asked: can you guess from the plant signal alone who was there? The answer was a surprisingly clear yes — and exactly this you rebuild.

Before you start — the data

The original data come from the COIN study by Rokicki, Stricker, Schäfer and Shi (University of Cologne, WS 2025/26): 39 recordings, cut into 3-second windows, four classes. Ask the seminar for the WAV files and put them in four folders: data/baseline/, data/human/, data/dog/, data/sheep/. Without the study data: record your own with the Biolingo sensor (11.1) — a few minutes each of "nobody there" versus "a human sits beside it", and, if you like, a pet. Two classes already suffice to show the principle.

A little background

What the plant gives away. The study found clear, statistically significant differences (Welch test, p < 0.05). Human and dog made the signal more restless — more variance, more noise. The sheep, by contrast, made the signal energy drop, a kind of falling silent — perhaps, the authors suspect, an old reaction to a herbivore. An XGBoost model reached about 83 per cent hits across all four classes, with F1 values above 90 per cent for human and dog; the sheep stayed hardest.

Again an image from the signal. The procedure is familiar to you: the sensor (an OPA2134 amplifier on an ESP32, 380 Hz) cuts the signal into 3-second pieces. From each piece we pull the same sound features as with the cat in 12.1 — MFCCs, spectral centroid, zero-crossing rate, energy and their rates of change. In short: we listen to the plant as if its signal were a sound.

Extract features and train the model

First the features per 3-second window, then XGBoost — the study's winning model. The full code is on GitHub.

import librosa, numpy as np, glob, os
from xgboost import XGBClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

def features(file):
    y, sr = librosa.load(file)                      # load 3-second window
    mfcc  = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
    delta = librosa.feature.delta(mfcc)             # rate of change
    centroid = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
    zcr      = librosa.feature.zero_crossing_rate(y)[0]
    energy   = librosa.feature.rms(y=y)[0]
    return np.concatenate([
        mfcc.mean(axis=1), mfcc.std(axis=1),        # shape + volatility
        delta.mean(axis=1),                         # how fast the signal changes
        [centroid.mean(), centroid.std(),           # frequency centre
         zcr.mean(),      zcr.std(),                # roughness
         energy.mean(),   energy.std()],            # intensity
    ])

CLASSES = ["baseline", "human", "dog", "sheep"]
X, y = [], []
for c in CLASSES:
    for file in glob.glob(f"data/{c}/*.wav"):
        X.append(features(file)); y.append(c)
X = np.array(X)
print("read:", len(X), "windows")

enc = LabelEncoder().fit(y)
Xtr, Xte, ytr, yte = train_test_split(X, enc.transform(y), test_size=0.2,
                                      random_state=0, stratify=y)
model = XGBClassifier(objective="multi:softprob", num_class=len(enc.classes_),
                      eval_metric="mlogloss")
model.fit(Xtr, ytr)

print("overall hits:", round(model.score(Xte, yte), 2))
print(classification_report(yte, model.predict(Xte), target_names=enc.classes_))

What you should see

Human and dog the model recognises almost surely (high F1 values) — their restlessness in the signal is unmistakable. The sheep is the hard case and is confused most readily with the baseline, because its "falling silent" resembles the calm nothing. Across all four classes you land near the study's 83 per cent. With your own Biolingo recordings (only two classes) it gets easier — but then it is your plant telling human from emptiness.

The shared timeline

Now the image that gives the chapter its name. Draw the signal energy window by window over time and colour it by who was there. You see the kingdoms side by side — the calm baseline, the restless ups and downs with human and dog, the drop with the sheep.

import matplotlib.pyplot as plt

colour = {"baseline":"#9db3a3", "human":"#6B7A8F", "dog":"#E0A82E", "sheep":"#B5524C"}
plt.figure(figsize=(10, 3.5))
i = 0
for c in CLASSES:
    for file in sorted(glob.glob(f"data/{c}/*.wav")):
        y, sr = librosa.load(file)
        plt.scatter(i, librosa.feature.rms(y=y)[0].mean(), color=colour[c], s=12)
        i += 1
plt.xlabel("window (in order)"); plt.ylabel("signal energy")
plt.title("Three kingdoms, one plant, one timeline"); plt.tight_layout(); plt.show()

Worksheet

What connects the kingdoms

  1. Which two classes does the model separate most surely, which does it confuse most readily? Why exactly these?
  2. Human and dog raise the variance, the sheep lowers the energy. Formulate a guess why a plant might react differently to a herbivore than to a human.
  3. List the steps you went through here (signal → … → prediction). Where have you done exactly the same steps before — with the cat, the heartbeat, the voice?
  4. It is only one basil plant. Name two reasons why the result is exciting but not yet conclusive.
  5. "The plant reads human and animal." What about this statement is covered by the experiment — and where does over-interpretation begin?
Show solution

1. Most surely it separates human and dog (strong restlessness in the signal); it confuses sheep and baseline most readily, because the sheep's "falling silent" resembles the calm nothing — both are low in energy.

2. Individual. One plausible idea: a herbivore is the real danger to a plant; a "shutting down" might save resources or prepare a chemical defence, while human and dog are more of a general disturbance (more restlessness). What matters is phrasing it as a guess, not a fact.

3. Record signal → cut into windows → build features/spectrogram → let the model decide. Exactly this chain ran with the cat (12.1), the heartbeat (5.1) and the voice (10.1) — a single procedure across all kingdoms. That is the convergence of method.

4. Only one plant (no statement about other specimens/species); no clean control setup, and the measurements partly happened in different places (possible confounders). A pattern in one plant is a start, not a proof.

5. Covered is: from the plant signal one can statistically guess which category of creature was near — better than chance. Over-interpretation begins at "the plant knows/feels/recognises" — what is measured is a correlation between proximity and signal shape, not consciousness.

When it sticks

ProblemLikely cause & fix
xgboost will not importInstall first: pip install xgboost. Alternatively RandomForestClassifier from sklearn also works — the study used it for comparison.
read: 0 windowsFolder structure is wrong. Put the WAVs in data/<class>/ and adjust CLASSES to the folders you actually have.
Everything is recognised as "baseline"Classes strongly imbalanced. Take roughly equal numbers of windows per class or use class_weight/sampling.
Very low hits on your own recordingsToo little data or too much interference. Record longer, fix the cables, keep a calm setting — and honestly stay with two clearly separated classes.
Very slowCompute the features once and keep them in X; do not re-read from the WAVs each run.

Food for thought

Extension

← 16.2 My Digital Twin 19.1 AI Literacy →