Tempeh Tech

Image Whiteness


By Noël Jung


Content

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

Intro

During fermentation, the fungus grows on the chickpeas as distinctly white mycelium. In a usual grayscale picture, each pixel is a single number between 0 and 255, sometimes scaled to 0-1. The higher the value, the brighter (more white) the pixel. The average whiteness value of the entire picture increases when more and denser mycelium emerges. This principle is sometimes used in scientific studies. In the following, we will examine how the gray value changes in one of our fermentations over time.

Imports

In [1]:
from functools import partial
from matplotlib import ticker
import matplotlib as mpl
import matplotlib.pyplot as plt
import graph_style
from project_utils import (
    standard_load_transform,
    load_image_from_path,
    zoom_in,
    scale_to_percent,
)

We load the data and add a few new columns.

In [2]:
IMAGE_DIR = r"../runV7"
CSV_FILE = r"../CSV/runV7.csv"
EX_ID = "V7"
display_only = True

ferm_data = standard_load_transform(CSV_FILE, new_columns=["h_passed", "min_passed"])

We adjust the image loader for the images and task at hand, and overwrite matplotlib's default styles.

In [3]:
# Define a default image loader that zooms-in on the image.
default_im_loader = partial(load_image_from_path,
    dir_path=IMAGE_DIR, as_gray=True,
    img_transformers=[(zoom_in, (1050, 3300, 1230, 2000))])


# Overwrite matplotlib default rcParams with custom styles.
for param, value in mpl.rcParamsDefault.items():
    mpl.rcParams[param] = graph_style.style.get(param, value)

Code

Let's start by looking at a few images in grayscale.
In [4]:
# Some example data points.
sample_rows = ferm_data.iloc[[50,100,150,200]]

# Create a figure with subplots to display images.
fig, axs = plt.subplots(2, int(len(sample_rows)/2), figsize=(20, 5),)
fig.subplots_adjust(left=0, right=1, wspace=-0.60, hspace=0.6)
fig.suptitle("Images taken at different times in grayscale", **graph_style.super_title_style, y=1.1)

# Create subplots.
for ax, (index, row) in zip(axs.flatten(), sample_rows.iterrows()):
    im = default_im_loader(row["im"])
    ax.set_title(label=f"{row['h_passed']} h fermentation", **graph_style.title_style)
    ax.set(xticks=[], yticks=[])
    ax.imshow(im, cmap='gray')

Okay. The pictures look different in terms of how much mycelium can be seen. To be honest, I personally find it hard to say whether the later images are more white (brighter) than the early ones. Maybe because I am not used to looking at the images in grayscale. Grayscale histograms indicate how many pixels correspond to each gray value.

In [5]:
# This function is used to scale y-axis values to a more readable format.
def y_to_k(y_val, _):
    """Convert y-axis: 1000 -> k"""
    return f"{int(y_val / 1000)}k"

# Produce a figure with four subplots.
fig, axs = plt.subplots(2, int(len(sample_rows)/2), figsize=(10, 10))
fig.subplots_adjust(left=0, right=1, wspace=0.4, hspace=0.6)
fig.suptitle("Gray value distribution of images taken at different times",
    **graph_style.super_title_style, y=1.0)

# Populate figure with histograms subplots.
for ax, (index, row) in zip(axs.flatten(), sample_rows.iterrows()):
    # Load image and plot histogram.
    im = default_im_loader(row["im"])
    ax.hist(im.flatten(), bins=40, **graph_style.histogram_kwargs)
    ax.set_title(label=f"{row['h_passed']} h fermentation", **graph_style.title_style)
        
    # Style the histogram.
    ax.set_xlim(0, 1)
    ax.set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1.0])
    ax.set_xlabel(xlabel="Gray value", **graph_style.axes_style)
    ax.set_ylim(0,20000)
    ax.set_yticks([1000 * y for y in [0, 50, 100, 150, 200]])
    ax.yaxis.set_major_formatter(ticker.FuncFormatter(y_to_k))
    ax.set_ylabel(ylabel="Counts", **graph_style.axes_style)
    ax.tick_params(**graph_style.tick_style)
    
    # Draw a vertical line at the mean value.
    ax.vlines(x=im.flatten().mean(), ymin=0, ymax=200*1000,
        colors=graph_style.colors_pomegranate[0], ls='-', lw=2)

Displayed like this, the change over time becomes even clearer. We see that the number of values between 0.0 and 0.2 does not change much. These pixels are the black background. On the 8h image, we have a lot of values spread out between 0.5 and 0.9. Over time these values wander to the right. Consequently, we see the mean value (red line) increase from below 0.6 to ca. 0.7.

We can plot this mean gray value over time as proxy for the fungal growth.

In [6]:
# Collect the mean gray values for all images.
mean_gray_values = ferm_data["im"].apply(lambda x: default_im_loader(x).flatten().mean())
ferm_data['mean_gray_value'] = mean_gray_values
ferm_data.to_csv(f"../OutputCSVs/{EX_ID}-gray_values.csv")

# Plot the mean gray values over fermentation time.
fig, ax = plt.subplots()
ax.plot(ferm_data['min_passed']/60, mean_gray_values,
    **graph_style.lineplot_kwargs, color=graph_style.colors_pomegranate[1])

# Apply graph styles.
ax.set_title(label="Development of mean gray value", **graph_style.title_style)
ax.set_xlabel(xlabel="Time in hours", **graph_style.axes_style)
ax.set_ylabel(ylabel="Gray value", **graph_style.axes_style)
ax.tick_params(**graph_style.tick_style)
ax.set(xlim=(0, ferm_data["min_passed"].iloc[-1]/60), ylim=(0.55, 0.7));

Indeed, we see something resembling a growth curve. Let's scale to percentage as the raw gray value is arbitrarily dependent on the camera settings and the color and size of the background.

In [7]:
# Plot the scaled mean gray values over fermentation time.
fig, ax = plt.subplots()
ax.plot(ferm_data['min_passed']/60, scale_to_percent(mean_gray_values),
    **graph_style.lineplot_kwargs, color=graph_style.colors_pomegranate[1])

# Apply graph styles.
ax.set_title(label="Scaled development of mean gray value", **graph_style.title_style)
ax.set_xlabel(xlabel="Time in hours", **graph_style.axes_style)
ax.set_ylabel(ylabel=r"% of max. gray value", **graph_style.axes_style)
ax.set(xlim=(0, ferm_data["min_passed"].iloc[-1]/60), ylim=(0, 101))
ax.tick_params(**graph_style.tick_style)

Perfect. One could consider smoothing the line to make it look nicer, but that is not our concern now.

Conclusion

We have created a growth curve based on gray value. It is really convenient that fungal mycelium is often white, which is a color that is easily measurable in grayscale images. In the next notebook, we will see how growth curves can be derived from colored images.