12 KiB
12 KiB
title, task, lineage_type, upstream_source, upstream_sha, imported_at, prompt_class, upstream_changes, author, validated
| title | task | lineage_type | upstream_source | upstream_sha | imported_at | prompt_class | upstream_changes | author | validated |
|---|---|---|---|---|---|---|---|---|---|
| Image Processing Basics | import | https://github.com/mims-harvard/ToolUniverse/blob/e2520a96/skills/tooluniverse-image-analysis/references/image_processing.md | e2520a96 | 2026-06-26 | prompt | accepted | upstream | false |
Image Processing Basics
Complete guide for image loading, preprocessing, and format handling.
Table of Contents
Image Loading
Load TIFF Files
import tifffile
import numpy as np
# Load single-page TIFF
image = tifffile.imread("image.tif")
# Load multi-page TIFF (Z-stack, time series)
stack = tifffile.imread("stack.tif") # Returns (T/Z, H, W) or (T/Z, H, W, C)
# Load specific page from multi-page TIFF
with tifffile.TiffFile("stack.tif") as tif:
page_3 = tif.pages[3].asarray()
# Load with metadata
with tifffile.TiffFile("image.tif") as tif:
image = tif.asarray()
metadata = tif.pages[0].tags
print(metadata)
Load PNG/JPG
from PIL import Image
import numpy as np
# Using PIL
img = Image.open("image.png")
image_array = np.array(img)
# Using scikit-image
from skimage import io
image = io.imread("image.png")
Load from scikit-image
from skimage import io
# Many formats supported
image = io.imread("image.png") # or .jpg, .tif, .bmp, etc.
# Load image collection
from skimage.io import ImageCollection
ic = ImageCollection("folder/*.tif")
images = [img for img in ic]
Library Selection Guide
scikit-image vs OpenCV
Use scikit-image when:
✅ Scientific measurements needed
regionpropsfor area, perimeter, circularity- Shape metrics (eccentricity, solidity, moments)
- Publication-quality analysis
✅ Easier syntax for scientists
from skimage import filters, measure
thresh = filters.threshold_otsu(image)
binary = image > thresh
labels = measure.label(binary)
props = measure.regionprops(labels)
✅ Integration with scipy/numpy
- Natural workflow with scientific Python stack
- Good documentation with examples
Use OpenCV when:
✅ Speed is critical
- Real-time processing
- Large image batches
- Video analysis
✅ Advanced computer vision
- Feature detection (SIFT, SURF, ORB)
- Template matching
- Object tracking
✅ GPU acceleration available
- CUDA support for specific operations
Both work for:
- Thresholding
- Morphological operations
- Filtering (Gaussian, median, etc.)
- Edge detection
- Image transformations
Preprocessing
Convert to Grayscale
from skimage.color import rgb2gray
# scikit-image (returns float 0-1)
gray = rgb2gray(image_rgb)
# OpenCV
import cv2
gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
# Manual (weighted average)
gray = 0.299 * image[:, :, 0] + 0.587 * image[:, :, 1] + 0.114 * image[:, :, 2]
Enhance Contrast
from skimage import exposure
# Histogram equalization
enhanced = exposure.equalize_hist(image)
# Adaptive histogram equalization (CLAHE)
enhanced = exposure.equalize_adapthist(image, clip_limit=0.03)
# Rescale intensity
enhanced = exposure.rescale_intensity(image, in_range=(low, high))
# Gamma correction
enhanced = exposure.adjust_gamma(image, gamma=1.5)
Noise Reduction
from skimage import filters
from skimage.restoration import denoise_bilateral, denoise_tv_chambolle
# Gaussian blur
smoothed = filters.gaussian(image, sigma=2.0)
# Median filter (good for salt-and-pepper noise)
from scipy.ndimage import median_filter
smoothed = median_filter(image, size=3)
# Bilateral filter (edge-preserving)
smoothed = denoise_bilateral(image, sigma_color=0.05, sigma_spatial=15)
# Total variation denoising
smoothed = denoise_tv_chambolle(image, weight=0.1)
Sharpening
from scipy.ndimage import convolve
# Unsharp masking
from skimage.filters import unsharp_mask
sharpened = unsharp_mask(image, radius=2, amount=1.0)
# Laplacian sharpening
laplacian_kernel = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])
sharpened = convolve(image, laplacian_kernel)
Format Conversions
Data Type Conversions
from skimage import img_as_float, img_as_ubyte, img_as_uint
# Convert to float (0.0 - 1.0)
image_float = img_as_float(image_uint8)
# Convert to uint8 (0 - 255)
image_uint8 = img_as_ubyte(image_float)
# Convert to uint16 (0 - 65535)
image_uint16 = img_as_uint(image_float)
# Manual conversion with scaling
image_uint8 = ((image_float - image_float.min()) /
(image_float.max() - image_float.min()) * 255).astype(np.uint8)
Channel Manipulations
# Extract channel from multi-channel image
channel_0 = image[:, :, 0]
# Merge channels
merged = np.stack([ch1, ch2, ch3], axis=-1)
# Split RGB
r, g, b = image[:, :, 0], image[:, :, 1], image[:, :, 2]
# Convert RGB to BGR (OpenCV uses BGR)
bgr = image[:, :, [2, 1, 0]]
Resize and Rescale
from skimage.transform import resize, rescale
# Resize to specific dimensions
resized = resize(image, (512, 512), anti_aliasing=True)
# Rescale by factor
scaled = rescale(image, scale=0.5, anti_aliasing=True)
# OpenCV resize
import cv2
resized = cv2.resize(image, (width, height), interpolation=cv2.INTER_LINEAR)
Batch Processing
Process Multiple Images
import os
import glob
import tifffile
import pandas as pd
def batch_process_images(input_folder, output_csv, process_func):
"""Process all images in folder.
Args:
input_folder: Path to folder with images
output_csv: Path to save results
process_func: Function that takes image, returns dict of results
Returns: DataFrame with results
"""
# Find all images
image_paths = glob.glob(os.path.join(input_folder, "*.tif"))
image_paths += glob.glob(os.path.join(input_folder, "*.png"))
results = []
for img_path in image_paths:
print(f"Processing {os.path.basename(img_path)}...")
# Load image
image = tifffile.imread(img_path)
# Process
result = process_func(image)
result['filename'] = os.path.basename(img_path)
results.append(result)
# Combine and save
df = pd.DataFrame(results)
df.to_csv(output_csv, index=False)
return df
# Example process function
def count_cells_in_image(image):
from skimage import filters, measure, morphology
# Segment
thresh = filters.threshold_otsu(image)
binary = image > thresh
binary = morphology.remove_small_objects(binary, min_size=50)
labels = measure.label(binary)
return {
'cell_count': labels.max(),
'mean_cell_area': np.mean([r.area for r in measure.regionprops(labels)])
}
# Run batch processing
results = batch_process_images("images/", "results.csv", count_cells_in_image)
Memory Management
Handle Large Images
# Load image in chunks
import tifffile
def process_large_image_tiles(image_path, tile_size=512):
"""Process large image in tiles to save memory.
Args:
image_path: Path to image
tile_size: Size of tiles
Returns: Results from processing
"""
with tifffile.TiffFile(image_path) as tif:
image_shape = tif.pages[0].shape
results = []
for y in range(0, image_shape[0], tile_size):
for x in range(0, image_shape[1], tile_size):
# Define tile bounds
y_end = min(y + tile_size, image_shape[0])
x_end = min(x + tile_size, image_shape[1])
# Load tile
tile = tif.pages[0].asarray()[y:y_end, x:x_end]
# Process tile
result = process_tile(tile, x, y)
results.append(result)
return results
def process_tile(tile, offset_x, offset_y):
"""Process a single tile."""
# Your processing here
from skimage import filters, measure
thresh = filters.threshold_otsu(tile)
binary = tile > thresh
labels = measure.label(binary)
# Adjust coordinates by offset
props = []
for r in measure.regionprops(labels):
props.append({
'centroid_x': r.centroid[1] + offset_x,
'centroid_y': r.centroid[0] + offset_y,
'area': r.area
})
return props
Calibration
Convert Pixels to Physical Units
def pixels_to_microns(pixel_value, pixel_size_um):
"""Convert pixels to micrometers.
Args:
pixel_value: Value in pixels (area, length, etc.)
pixel_size_um: Size of one pixel in micrometers
Returns: Value in micrometers (or µm²)
"""
return pixel_value * pixel_size_um
def calibrate_measurements(results_df, pixel_size_um, area_cols=None, length_cols=None):
"""Add calibrated measurements to DataFrame.
Args:
results_df: DataFrame with pixel measurements
pixel_size_um: Pixel size in micrometers
area_cols: List of area column names
length_cols: List of length column names
Returns: DataFrame with calibrated columns added
"""
df = results_df.copy()
if area_cols:
for col in area_cols:
df[f'{col}_um2'] = df[col] * (pixel_size_um ** 2)
df[f'{col}_mm2'] = df[f'{col}_um2'] / 1e6
if length_cols:
for col in length_cols:
df[f'{col}_um'] = df[col] * pixel_size_um
return df
# Example
results = pd.DataFrame({
'area': [100, 200, 150],
'perimeter': [40, 60, 50]
})
calibrated = calibrate_measurements(
results,
pixel_size_um=0.65,
area_cols=['area'],
length_cols=['perimeter']
)
print(calibrated)
Quality Checks
Detect Focus Issues
from skimage.filters import laplace
def check_image_focus(image):
"""Check if image is in focus using Laplacian variance.
Args:
image: Grayscale image
Returns: Focus score (higher = better focus)
"""
laplacian = laplace(image)
variance = laplacian.var()
return variance
# Example: Check all images in folder
import glob
focus_scores = []
for img_path in glob.glob("images/*.tif"):
img = tifffile.imread(img_path)
score = check_image_focus(img)
focus_scores.append({
'filename': os.path.basename(img_path),
'focus_score': score,
'in_focus': score > 100 # Threshold depends on your images
})
focus_df = pd.DataFrame(focus_scores)
print(focus_df)
Detect Saturation
def check_saturation(image, threshold=0.01):
"""Check if image has saturated pixels.
Args:
image: Image array
threshold: Fraction of pixels allowed to be saturated
Returns: (is_saturated, fraction_saturated)
"""
if image.dtype == np.uint8:
max_val = 255
elif image.dtype == np.uint16:
max_val = 65535
else:
max_val = image.max()
saturated = image >= max_val
fraction = saturated.sum() / image.size
return fraction > threshold, fraction
# Check all channels
for i in range(image.shape[-1]):
is_sat, frac = check_saturation(image[..., i])
if is_sat:
print(f"Channel {i}: {frac*100:.2f}% pixels saturated!")
Troubleshooting Common Issues
Image orientation wrong
# Rotate
from skimage.transform import rotate
rotated = rotate(image, angle=90)
# Flip
flipped_ud = np.flipud(image) # Up-down
flipped_lr = np.fliplr(image) # Left-right
Image too dark/bright
# Auto-adjust contrast
from skimage import exposure
adjusted = exposure.rescale_intensity(image, in_range='image', out_range=(0, 255))
Uneven illumination
# Background subtraction
from skimage.morphology import white_tophat, disk
background = white_tophat(image, disk(50))
corrected = image - background
File won't load
# Try different libraries
try:
image = tifffile.imread(path)
except:
from PIL import Image
image = np.array(Image.open(path))