Plant Emotions with AI
From the signal comes an image, and a pre-trained network reads it: can you tell from the plant signal whether the person beside it was happy? Pure inference — you train no network of your own.
In a nutshell
What: You take the raw voltage signal of a houseplant and, measured at the same time by a smartwatch, the mood of the person beside it. For each mood reading you cut out the plant signal of the last few minutes, make — as everywhere in this book — a spectrogram of it, and let a ready pre-trained network (ResNet) read the image. A simple nearest-neighbour method guesses the mood from that.
The core idea: pure inference. The deep network has long since learned from millions of images — you do not train it, you use it as a ready-made "eye". And this is the scepticism chapter: you check soberly how much — and how little — really sits in the signal.
You need: the dataset from figshare (31527778) with plant_raw.csv and
predictions.csv, plus Python with pandas, scipy,
torch/torchvision and scikit-learn.
What it's about
If a plant reacts to light — does it also react to us? In a self-study over eleven days a person sat at their desk, half a metre from a Tradescantia. A simple sensor picked up the plant's electrical signal; at the same time a Happimeter smartwatch on the wrist continuously estimated the person's mood — happy, stressed, low. So two traces lie side by side: here the plant signal, there the person's mood.
The question is delicate and therefore exciting: does the plant signal really hold something about the person? Instead of believing it, you turn it into an image and let an AI look — and then check honestly whether its guess beats pure chance.
Before you start — the data
The dataset lies on figshare under the DOI 10.6084/m9.figshare.31527778. Two files:
plant_raw.csv (columns timestamp, voltage — about 312,000 measurement points over
eleven days, roughly one per second) and predictions.csv (the Happimeter readings with
timestamp and happy, stress, depression, each on a scale
from 0 to 2). The two are joined via the timestamp.
A little background
Again an image from the signal. The procedure is familiar: a signal over time becomes a spectrogram — an image of its frequencies — and then the same image-recognising machinery reads it that read cat sound, voice and heartbeat. New is only this: we do not train the network ourselves but take a ready one that has already learned from millions of images, and use its "view" as features. That is the core of transfer and inference.
Why the plant is slow. The plant signal changes over seconds to minutes, not milliseconds. So per mood reading we look at a window of a few minutes before it — long enough for the slow rhythms of the plant to show in the spectrogram.
Cut windows and build spectrograms
For each Happimeter reading we take the plant signal of the last three minutes, put it on an even 1 Hz grid and make a spectrogram. The full code is on GitHub.
import numpy as np, pandas as pd
from scipy import signal, interpolate
raw = pd.read_csv("plant_raw.csv", parse_dates=["timestamp"]).sort_values("timestamp")
reads = pd.read_csv("predictions.csv", parse_dates=["timestamp"]) # Happimeter readings
EPOCH = pd.Timestamp("1970-01-01") # fixed zero point
t = ((raw["timestamp"] - EPOCH) / pd.Timedelta(seconds=1)).to_numpy() # seconds
volt = raw["voltage"].to_numpy()
WINDOW = 180 # seconds of plant signal before each reading
GRID = np.arange(0, WINDOW, 1.0) # unify to 1 Hz
def spectrogram(end_s):
m = (t >= end_s - WINDOW) & (t < end_s)
if m.sum() < 120: # too few points -> skip
return None
w = interpolate.interp1d(t[m] - (end_s - WINDOW), volt[m],
bounds_error=False, fill_value="extrapolate")(GRID)
w = (w - w.mean()) / (w.std() + 1e-9) # normalise
_, _, S = signal.spectrogram(w, fs=1.0, nperseg=32, noverlap=16)
return np.log1p(S) # image of the slow rhythms
specs, y = [], []
for _, r in reads.iterrows():
S = spectrogram((r["timestamp"] - EPOCH) / pd.Timedelta(seconds=1))
if S is not None:
specs.append(S)
y.append(int(r["happy"] >= 2)) # 1 = happy, 0 = less
y = np.array(y)
print(len(specs), "windows | happy balance:", np.bincount(y))
The pre-trained network reads — pure inference
Now the core: a ResNet, pre-trained on millions of images, turns each spectrogram into a feature vector — its "view" of the image. It learns nothing here; we only let a plain nearest-neighbour method guess the mood from these features.
import torch, torch.nn as nn
from torchvision.models import resnet18, ResNet18_Weights
from PIL import Image
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
weights = ResNet18_Weights.IMAGENET1K_V1 # loads the weights once (internet needed)
net = resnet18(weights=weights); net.fc = nn.Identity(); net.eval()
prepare = weights.transforms()
def features(S): # spectrogram -> image -> ResNet view
a = (S - S.min()) / (np.ptp(S) + 1e-9)
img = Image.fromarray((a * 255).astype("uint8")).convert("RGB").resize((64, 64))
with torch.no_grad():
return net(prepare(img).unsqueeze(0))[0].numpy()
X = np.array([features(S) for S in specs]) # pure inference: the net is not trained
chance = max(np.bincount(y)) / len(y)
score = cross_val_score(KNeighborsClassifier(5), X, y, cv=5).mean()
print("hits (5-fold):", round(score, 2), " | chance line:", round(chance, 2))
What you should see
For "happy" the method lands a little above the chance line — a weak but real trace. If you try
the same with stress instead of happy, you will usually find no signal
above chance. That is exactly the honest, instructive double answer: yes, a breath of the person's mood sits
in the plant signal — but only a breath, and not for every emotion. Whoever expected big percentages here
has not taken the scepticism chapter seriously.
Worksheet
Real or imagined?
- How high was your hit rate for "happy", how high the chance line? How large is the gap really?
- Switch the target to
stress(e.g.stress >= 1). Do you still find anything above chance? What follows from that for the claim "the plant reads feelings"? - The network is never trained — yet useful features arise. Explain in one sentence how a network trained on photos can help with a plant spectrogram.
- The whole study rests on one person and one plant over a few days. Name two reasons why the result is exciting but not generalisable.
- Even if "happy" lies above chance: why does it not follow that the plant "knows" or "feels" how you are?
Show solution
1. Individual — typically a few percentage points above the chance line (which, at roughly equal class split, lies near 0.5). What matters is naming the gap honestly: "above chance" is the proof, not the absolute number.
2. Usually "stress" does not lie above chance. Conclusion: not every emotion leaves a readable trace; "the plant reads feelings" is too coarse. Honest is: a weak trace for some states, none for others.
3. The network has learned to recognise general image patterns — edges, areas, textures. A spectrogram is an image of such patterns, so the early, general layers of the network deliver useful features here too, without it ever having seen a plant signal.
4. One person/plant/span can have quirks that do not generalise; without many people and plants and without strict control it stays a single finding. Only replication makes it a robust statement.
5. What is measured is a correlation between mood and signal shape — no mechanism and no consciousness. The trace probably runs via the heartbeat (see 17.1) and scents, not via "understanding". Correlation is not understanding.
When it sticks
| Problem | Likely cause & fix |
|---|---|
| ResNet loads forever / error on load | The weights are downloaded once (internet needed). After that they sit in the cache. Behind a firewall: load the model beforehand on a machine with internet. |
0 windows | Timestamps do not line up. Check that the time spans of plant_raw.csv and predictions.csv overlap; only overlapping readings yield a window. |
| Very unbalanced classes | Choose a different threshold (e.g. happy >= 1) or use stratify so both classes are similar in size. |
| Hits exactly on the chance line | An honest result, not an error — for this target (often "stress") there is simply nothing readable in the signal. |
| Very slow | Compute the features once and keep them in X; a smaller image size (e.g. 64×64) is enough. |
Food for thought
- Pure inference is a powerful lever: a network that has never seen a plant signal still delivers useful features — because a spectrogram is, after all, an image. The same procedure, from tomato to stock market.
- This is the chapter where the whole book is put to the test. A responsible person does not believe an astonishing claim just because it is exciting — not even when it comes from a professor writing a book about it. You measured for yourself: a weak trace for "happy", barely anything for "stress".
- And the boundary stays: even a real correlation is not yet understanding. The trace probably runs via the heartbeat and via scents — not via a consciousness of the plant. Wonder need not fear sobriety.
Extension
- Look at spectrograms. Draw one spectrogram each from a "happy" and a "not happy" window side by side. Do you see a difference with the naked eye — or only the network?
- Other emotions, other windows. Vary the target (
stress,depression) and the window length. Which combination shows a signal above chance at all — and which none? - The real plant network. Instead of a photo network you can use Peter's own pipeline trained on
plant signals (
github.com/pgloor/hiddenbiosignals, folderplant_spectrograms) and compare the results.