Different ways to do background correction#
Background correction is an optional step in the analysis pipeline, and is used to remove static elements in an image for improved analysis results.
In PyOpia there are several ways to use the background correction functionality, illustrated in this notebook.
The default behavior is to set up the background for the first N images of a pipeline run, and not perform any analysis. After the background is completely set up, analysis starts (from image N), with either a dynamic running average or static background, depending on the configuration choices. You can customize the behavior you want by changing the configuration and by adding custom code, illustrated at the end of this notebook.
from glob import glob
import numpy as np
import matplotlib.pyplot as plt
from skimage.exposure import rescale_intensity
import zipfile
import pyopia.exampledata
from pyopia.pipeline import Pipeline
# Download example image files
pyopia.exampledata.get_file_from_pysilcam_blob('oil.zip')
with zipfile.ZipFile('oil.zip', 'r') as zipit:
zipit.extractall('.')
# These imports are indirectly needed for the Pipeline
import pyopia.background
import pyopia.instrument.silcam
# Manually define PyOpia pipeline configuration
NUM_IMAGES_FOR_BACKGROUND = 5
pipeline_config = {
'general': {
'raw_files': f'oil/*.silc',
'pixel_size': 24 # pixel size in um
},
'steps': {
### start of steps applied to every image
# load the image using instrument-specific loading function
'load': {
'pipeline_class': 'pyopia.instrument.silcam.SilCamLoad'
},
# apply background correction - argument is which method to use:
# 'accurate' - recommended method for moving background
# 'fast' - faster method for realtime applications
# 'pass' - omit background correction
'correctbackground': {
'pipeline_class': 'pyopia.background.CorrectBackgroundAccurate',
'average_window': NUM_IMAGES_FOR_BACKGROUND,
'bgshift_function': 'accurate'
}
}
}
# now initialise the pipeline
processing_pipeline = Pipeline(pipeline_config)
Create a background from multiple images#
# The background stack (of raw images) and background image (mean of bgstack) is built during the first
# N run steps of the pipeline. During this process, further analysis steps are skipped.
# Get a sorted list of image files
image_files = sorted(glob(pipeline_config['general']['raw_files']))
print(f'Found {len(image_files)} image files')
# Process first N images to create the background.
for filename in image_files[:NUM_IMAGES_FOR_BACKGROUND]:
processing_pipeline.run(filename)
# Inspect the background image
fig, ax = plt.subplots()
ax.imshow(processing_pipeline.data['imbg'])
Run background correction on a single image#
# Process one image using the already prepared background of the first N images
# NB: Each time you call run(), the background stack and background image will be updated! (Unless 'pass' was set as bgshift_function - see below)
processing_pipeline.run(image_files[NUM_IMAGES_FOR_BACKGROUND])
# Plot raw and corrected image
fig, axes = plt.subplots(1, 2, figsize=(2*6, 4))
axes[0].imshow(processing_pipeline.data['imraw'])
axes[0].set_title(f'Raw image #{NUM_IMAGES_FOR_BACKGROUND}')
axes[1].imshow(processing_pipeline.data['im_corrected'])
axes[1].set_title('Background corrected image')
Static vs running average background#
The CorrectBackgroundAccurate class have two different modes for a dynamic (running) background correction (bgshift_function either ‘fast’ and ‘accurate’), and one mode for a static background that is created once and then not updated (bgshift_function=’pass’). The static background is set up in the same way as the dynamic one, by the N initial calls to the pipeline run. You can choose how many and which images to use for the background, illustrated below.
# Recreate pipeline and update background step config for static background correction
processing_pipeline = Pipeline(processing_pipeline.settings)
processing_pipeline.settings['steps'].update(
{
'correctbackground':
{
'pipeline_class': 'pyopia.background.CorrectBackgroundAccurate',
'average_window': NUM_IMAGES_FOR_BACKGROUND,
'bgshift_function': 'pass'
}
}
)
# Process first N images to create the static background.
for filename in image_files[:NUM_IMAGES_FOR_BACKGROUND]:
processing_pipeline.run(filename)
# With a static background, the processing order does not matter, so we can for instance process the last image in the list.
# Now processing an image will not cause the background to be updated
imbg_before = processing_pipeline.data['imbg'].copy()
processing_pipeline.run(image_files[-1])
# Check difference in imbg before and after analysis step, should be zero
np.abs(imbg_before - processing_pipeline.data['imbg']).sum()
Correct images by subtracting vs dividing average background#
The CorrectBackgroundAccurate class have two modes for a subtracting background correction (divide_bg=False), or dividing background correction (divide_bg=True) that provides the corrected image (‘im_corrected’) for further analysis. For dividing background mode, the zero-value pixels of the average background image are initially rescaled to 1/255 to prevent division by zero. For more information, refer to: https://doi.org/10.1016/j.marpolbul.2016.11.063). You can select the subtracting/dividing correction modes used in the pipeline to process raw images, as illustrated below.
# Corrected image uisng subtracting method
im_corrected_subtract = processing_pipeline.data['im_corrected'].copy()
# Update pipeline config and background step with dividing background correction (divide_bg=True)
pipeline_config['steps']['correctbackground']['divide_bg'] = True
# Run the first N images to creat the background
for filename in image_files[:NUM_IMAGES_FOR_BACKGROUND]:
processing_pipeline.run(filename)
# Now process one of the raw images
processing_pipeline.run(image_files[NUM_IMAGES_FOR_BACKGROUND])
# Corrected image uisng dividing mode
im_corrected_division = processing_pipeline.data['im_corrected'].copy()
# Plot raw, im_corrected by subtracing and dividing averaged background image
fig, axes = plt.subplots(1, 3, figsize=(3*6, 5))
axes[0].imshow(processing_pipeline.data['imraw'])
axes[0].set_title(f'Raw image #{NUM_IMAGES_FOR_BACKGROUND}')
axes[1].imshow(im_corrected_subtract)
axes[1].set_title('Corrected image by background subtraction')
axes[2].imshow(im_corrected_division)
axes[2].set_title('Corrected image by background division')
Custom background correction#
You can write your own custom background correction class, here is a simple example of how to do that.
class MyCustomBackgroundClass():
'''
Example custom background class: use a randomly generated image to "correct" the background
'''
def __call__(self, data):
# Create a random background image
data['imbg'] = np.random.random(data['imraw'].shape)
data['bgstack'] = [data['imbg']]
# Correct
data['im_corrected'] = np.maximum(data['imraw'] - data['imbg'], 0)
# Stretch contrast
data['im_corrected'] = rescale_intensity(data['im_corrected'], out_range=(0, 1))
return data
# Monkey patch the custom class into PyOpia
pyopia.background.MyCustomBackgroundClass = MyCustomBackgroundClass
# Recreate pipeline and update background step config with cutsom background correction
processing_pipeline = Pipeline(processing_pipeline.settings)
processing_pipeline.settings['steps'].update(
{
'correctbackground':
{
'pipeline_class': 'pyopia.background.MyCustomBackgroundClass',
}
}
)
processing_pipeline.run(image_files[0])
# Plot raw and corrected image
fig, axes = plt.subplots(1, 2, figsize=(2*6, 4))
axes[0].imshow(processing_pipeline.data['imraw'])
axes[0].set_title('Raw image')
axes[1].imshow(processing_pipeline.data['im_corrected'])
axes[1].set_title('Background corrected image')