Tempeh Tech

Artifacts and FSR


By Noël Jung


Content

  1. Intro
  2. Imports
  3. Code
  4. Conclusion

Intro

We want to examine whether we can measure the mycelium strength occurring over the fermentation with a force sensitive resistor (FSR). Furthermore, we identify the cause of the flickering spots that we observed on the video recordings.

Imports

In [1]:
import os
import cv2
import seaborn as sns
import pandas as pd
from IPython.display import display, HTML
from matplotlib import animation
import matplotlib.pyplot as plt
import graph_style # Contains styling parameters for the plots.
from project_utils import time_parser, add_time_columns

As before, we load the data and add some additional columns stating how much fermentation time has passed.

In [2]:
IMAGE_DIR = r"../runV2"
CSV_FILE = r"../CSV/runV2.csv"
EX_ID = "V2"
display_only = True
ferm_data = pd.read_csv(CSV_FILE, parse_dates=["Time"], date_parser=time_parser)
# Transform time column into a readable format
ferm_data["Time"] = ferm_data["Time"].dt.strftime("%d%m%Y-%H:%M:%S")
ferm_data = add_time_columns(ferm_data, new_columns=["h_passed", "min_passed"])
print(f'Values sensor 1: {ferm_data["name_dev_1"].unique()}. Values sensor 2: {ferm_data["name_dev_2"].unique()}.')
Values sensor 1: ['28-3c01f09505ec']. Values sensor 2: ['28-3c01f0953253'].

Let's take a look at the data.

In [3]:
ferm_data.head()
Out[3]:
Time name_dev_1 T_dev_1 name_dev_2 T_dev_2 T_CPU im led FSR_value FSR_voltage min_passed h_passed
0 16072023-09:33:48 28-3c01f09505ec 25.750 28-3c01f0953253 25.437 51.6 16072023-093348.jpeg ok 0 0.0 0 0
1 16072023-09:44:22 28-3c01f09505ec 41.812 28-3c01f0953253 25.937 48.2 16072023-094422.jpeg ok 0 0.0 10 0
2 16072023-09:54:55 28-3c01f09505ec 34.750 28-3c01f0953253 25.937 52.1 16072023-095455.jpeg ok 0 0.0 21 0
3 16072023-10:05:29 28-3c01f09505ec 30.750 28-3c01f0953253 28.250 49.6 16072023-100529.jpeg ok 0 0.0 31 1
4 16072023-10:16:03 28-3c01f09505ec 29.062 28-3c01f0953253 28.312 51.1 16072023-101603.jpeg ok 0 0.0 42 1

Code

As before, we first produce the video. This time, I took an image every 10 minutes.

In [4]:
if display_only: 
    video_filename = f"./assets/{EX_ID}-zoom-medium.mp4"
else:
    video_filename = f"../OutputVideos/{EX_ID}-zoom-medium.mp4"

# Functions for collecting and zooming in on each image.
def get_image_from_path(basename, dir_path=IMAGE_DIR, rotate=True, img_transformers=[]):
    """Load a single image, potentially turn, and return it."""
    path = os.path.join(dir_path, basename)
    im = cv2.imread(path)

    if rotate:
        im = cv2.rotate(im, cv2.ROTATE_180)

    for transformer, args in img_transformers:
        im = transformer(im, *args)

    return im

def zoom_in(im, x_1, x_2, y_1, y_2):
    """Zooms in on a picture."""
    return im[y_1:y_2, x_1:x_2]

# We assemble the video, while we zoom in a little bit.
if not display_only:
    im_files_iter = (get_image_from_path(row, img_transformers=[(zoom_in, (300, 1100, 30, 350))]) for row in ferm_data["im"])
    # Exactly this encoding needs to be used.
    video_writer = cv2.VideoWriter(video_filename, 0x00000020 , 15, (800, 320))
    for im_file_name in im_files_iter:
        video_writer.write(im_file_name)

# Display the video.
video_html = HTML(f'<video controls src="{video_filename}" width="640" height="360"></video>')
display(video_html)

Ok, looks good. The video is much less shaky, so one can really see what's going on. We can clearly see the fluctuations we observed before. These are most noticeable in areas where the mycelium has not yet grown a lot, so especially in the beginning. Our hypothesis was, that these somehow have to do with the temperature fluctuations in the incubator. We can check this by viewing the video and the incubator temperature side by side.

In [5]:
if display_only: 
    ani_filename = f"./assets/{EX_ID}-cond-vs-temp.mp4"
else:
    ani_filename = f"../OutputVideos/{EX_ID}-cond-vs-temp.mp4"


# Set theme for graphs.
axes_style = sns.axes_style(style=graph_style.style)
sns.set(rc={"figure.figsize":(10, 4), 'animation.embed_limit': 2**128})
sns.set_theme(style=axes_style, palette=graph_style.colors_pomegranate ,font=graph_style.FONT)

# Collect the images.
im_files_iter = (get_image_from_path(row, img_transformers=[(zoom_in, (300, 1100, 30, 350))]) for row in ferm_data["im"])
im_list = list(im_files_iter)

# Produce an "empty" figure with two subplots.
fig, ax = plt.subplots(2,1, figsize=(10, 10),  gridspec_kw={'height_ratios': [1, 1]})
fig.tight_layout(pad=5)
fig.suptitle("Incubator temperature and condensation", **graph_style.super_title_style)

# Update empty figure frame by frame.
def animate(i):
    """Find ith temperature value and corresponding image."""
    # Put image in lower subplot.
    ax[1] = plt.imshow(cv2.cvtColor(im_list[i], cv2.COLOR_RGB2BGR))
    plt.axis('off')

    # Set plot axis parameters.
    ax[0].set_ylim(25,42)
    ax[0].set_xlim(0,1000)

    # Plot and style temperature curve.
    df_index = i + 1
    x_range = ferm_data["min_passed"].iloc[:df_index]
    y_range = ferm_data["T_dev_1"].iloc[:df_index]
    ani_T_plot = sns.lineplot(ax=ax[0], x=x_range, y=y_range,  color=graph_style.colors_pomegranate[1])
    ani_T_plot.set_xlabel(xlabel="Time in minutes", **graph_style.axes_style)
    ani_T_plot.set_ylabel(ylabel="Temperature in °C", **graph_style.axes_style)
    ani_T_plot.tick_params( **graph_style.tick_style)

# We assemble the animation.
if not display_only:
    ani = animation.FuncAnimation(fig, animate, frames=94,  interval=300) # frames=94
    ani_save = animation.FFMpegWriter(fps=5)
    ani.save(ani_filename, writer=ani_save)

# Display the animation video.
video_html = HTML(f'<video controls src="{ani_filename}" width="1280" height="720"></video>')
display(video_html)

Success! There is indeed a correlation between the temperature spikes and the fluctuating spots on the tempeh. As the incubator temperature decreases, bright looking condensation drops appear on the inside of the plastic bag. When the temperature suddenly rises, these drops disappear at once. This cycle repeats until the mycelium is too dense to allow for any drops to appear.

Take another look at the video. You see two sensors stuck into the tempeh. The one coming from the right is the thermometer. The one on the left is new: It is the FSR. These are essentially buttons that can sense how hard they are being pressed. The theory was that as growth advances, the tempeh becomes harder, and the mycelium increases its pressure on the FSR. The signal would hence be a proxy measure for the mycelium strength. Let's see whether that worked!

In [6]:
# Initialize graph.
FSR_plot = sns.lineplot(data=ferm_data, x="min_passed", y="FSR_value", **graph_style.lineplot_kwargs)

# Set labels and apply styles.
FSR_plot.set_title(label="FSR curve", **graph_style.title_style)
FSR_plot.set_xlabel(xlabel="Time in minutes", **graph_style.axes_style)
FSR_plot.set_ylabel(ylabel="Signal", **graph_style.axes_style)
FSR_plot.tick_params( **graph_style.tick_style)
FSR_plot.set(xlim=(0,4150), ylim=(0, 4));
My FSR setup detects forces in the range of 0 to 500 gram in 256 discrete steps. This relationship between steps and force is not linear over the entire range. There are individual signal peaks of 1-3 (of 255), but those are most likely noise. The signal is 0 otherwise. If the pressure on the FSR would really increase, we would see a steady signal increase over time.

It is relatively hard to take the tempeh out of its plastic bag. It seemed to me like it had increased in size and would therefore not slide out easily. I thought this increase in size is facilitated by the mycelium pushing the chickpeas away from one another, which I would be able to monitor with the FSR: The peas would push against the "button".

The moment I held the tempeh in my hand, I realized my previous theory was wrong. It was easy to remove the sensor, it was not grown in or otherwise stuck. Hence there was no pressure against the FSR. I now believe that the mycelium does the opposite: Instead of pushing the chickpeas apart, it pulls them together. But since it can't grow through, this does not work between chickpeas of opposite sides of the sensors. The fact that it is hard to remove it from the plastic bag has most likely to do with the tempeh block becoming more rigid over the fermentation time.

Conclusion

We have improved the quality of our video. We have found an explanation for the strange visual artifacts. However, we could not measure the mycelium strength with the FSR. Now, I will upgrade my fermentation setup to get higher quality data and tempeh. And I will find new questions to investigate. I hope to soon report again!