A Python workflow for automatic registration and quantitative comparison of paired RGB images.
The script is designed to process multiple regions of interest (ROIs), align each RIE image to its corresponding Original image, and generate a series of registration quality-control images, pixel-wise difference maps, color-distance measurements, structural similarity analyses, and joint intensity distributions.
The workflow provides:
- Automatic pairing of
OriginalandRIEimages by ROI number - 2D rigid image registration
- Optional Otsu foreground masking
- Optional moment-based initialization for large initial displacements
- Optional CLAHE contrast enhancement for difficult registration cases
- Coarse rotational search before optimization
- Registration quality-control visualization
- Grayscale and RGB difference analysis
- WCAG-style luminance contrast analysis
- Pixel-wise intensity ratio analysis
- LAB color-space distance analysis
- SSIM structural difference analysis
- 2D joint intensity histograms
- K-Means clustering of paired pixel intensities
- Automatic export of quantitative results to CSV
- Separate preservation of raw/analysis images and display-enhanced images
The script requires Python and the following packages:
pip install numpy matplotlib SimpleITK scikit-image scikit-learnMain dependencies:
- NumPy
- Matplotlib
- SimpleITK
- scikit-image
- scikit-learn
Input images must follow the default naming convention:
ROI <number> Original.tiff
ROI <number> RIE.tiff
For example:
ROI 1 Original.tiff
ROI 1 RIE.tiff
ROI 2 Original.tiff
ROI 2 RIE.tiff
ROI 3 Original.tiff
ROI 3 RIE.tiff
Images with the same ROI number are automatically paired.
The current filename-matching rule is:
pattern = re.compile(r"ROI (\d+) (Original|RIE)\.tiff?")If your dataset uses a different naming convention, modify this regular expression accordingly.
Run the script from the command line:
python image_alignment.py -d "D:/My New Data/Folder"The -d / --data_dir argument specifies the directory containing the input images.
For example:
python image_alignment.py -d "E:/linshi/20260903 Alignment"Different datasets may require different combinations of registration options.
Recommended when the specimen can be clearly separated from the background:
python image_alignment.py -d "D:/My New Data/Folder" --use_mask--use_mask applies Otsu thresholding followed by morphological processing to generate a foreground mask. The registration metric is then evaluated primarily within the segmented region.
For image pairs with relatively large translational offsets, use moment-based initialization:
python image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_moments--use_moments initializes the rigid transformation according to the intensity moments of the two images instead of assuming that their physical centers are initially aligned.
This is generally more robust when the corresponding structures begin at noticeably different positions.
python image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_moments --use_view_for_analysisThe registration procedure itself remains unchanged.
However, after registration, quantitative outputs 00–08 are calculated using the display-normalized View images:
12_Original_RGB_View.tiff
14_Aligned_RIE_RGB_View.tiff
For the unaligned comparisons (07_raw and 08_raw), the following pair is used:
12_Original_RGB_View.tiff
13_Raw_RIE_RGB_View.tiff
This mode can be useful when subsequent analysis is intended to operate on images whose intensity ranges have first been normalized for visualization.
For images with weak contrast or poorly defined registration features:
python image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_moments --enhance_contrast--enhance_contrast applies adaptive histogram equalization (CLAHE) to the grayscale images used for registration preprocessing.
The enhancement affects the registration process only; it does not directly replace the raw RGB images used by the default quantitative-analysis workflow.
Specifies the directory containing the paired ROI images.
Example:
python image_alignment.py -d "D:/Dataset"Enables Otsu-based foreground masking.
Recommended when:
- the object and background are clearly distinguishable;
- the background occupies a large fraction of the image;
- background pixels interfere with registration.
Uses image moments to initialize the rigid transformation.
Recommended when:
- the initial displacement between images is relatively large;
- center-based initialization does not provide a sufficiently good starting position.
Applies adaptive histogram equalization before registration.
Recommended when:
- the images have low contrast;
- useful structural features are difficult to distinguish;
- registration is unstable because the grayscale intensity distribution is relatively uniform.
Alias:
--analysis_from_view
Changes the image source used for the downstream 00–08 analyses.
Without this option:
Original: 10_Original_RGB.tiff
Aligned RIE: 09_Aligned_RIE_RGB.tiff
are used as the analysis sources.
With this option:
Original: 12_Original_RGB_View.tiff
Aligned RIE: 14_Aligned_RIE_RGB_View.tiff
are used instead.
Importantly, this option changes only the post-registration analysis data source. It does not change how registration itself is performed.
The program deliberately distinguishes between raw/analysis data and display-enhanced data.
If --use_view_for_analysis is not specified, downstream analysis uses:
10_Original_RGB.tiff
09_Aligned_RIE_RGB.tiff
This preserves the original RGB intensity relationships as far as possible and is the default behavior.
Example:
python image_alignment.py -d "D:/Dataset" --use_mask --use_momentsIf:
--use_view_for_analysis
is enabled, downstream analysis instead uses:
12_Original_RGB_View.tiff
14_Aligned_RIE_RGB_View.tiff
Example:
python image_alignment.py -d "D:/Dataset" --use_mask --use_moments --use_view_for_analysisThe View images are converted to 8-bit using a single shared scalar gain for all RGB channels within each image. Independent per-channel stretching is not performed, helping preserve the relative RGB channel ratios.
Results are automatically written to:
<data_dir>/Results_Analysis/
Each ROI receives an individual subdirectory:
Results_Analysis/
├── ROI_1/
├── ROI_2/
├── ROI_3/
└── ...
A typical ROI directory contains files such as:
ROI_1_00_Registration_QC.tiff
ROI_1_00_Registration_QC.csv
ROI_1_01_Diff_Gray.tiff
ROI_1_01_Diff_Gray.csv
ROI_1_02_Diff_RGB_Contrast.tiff
ROI_1_02_Diff_RGB_Contrast.csv
ROI_1_03_WCAG_Contrast.tiff
ROI_1_03_WCAG_Contrast.csv
ROI_1_04_Ratio_Image.tiff
ROI_1_04_Ratio_Image.csv
ROI_1_05_LAB_Euclidean_Distance.tiff
ROI_1_05_LAB_Euclidean_Distance.csv
ROI_1_06_SSIM_Structural_Difference.tiff
ROI_1_06_SSIM_Structural_Difference.csv
ROI_1_07_2D_Histogram.png
ROI_1_07_2D_Histogram.csv
ROI_1_08_Scatter_Clustering.png
ROI_1_08_Scatter_Clustering.csv
ROI_1_09_Aligned_RIE_RGB.tiff
ROI_1_10_Original_RGB.tiff
ROI_1_11_Raw_RIE_RGB.tiff
ROI_1_12_Original_RGB_View.tiff
ROI_1_13_Raw_RIE_RGB_View.tiff
ROI_1_14_Aligned_RIE_RGB_View.tiff
Additional _raw versions of the joint-distribution and clustering results are generated for comparison with the unaligned RIE image.
Registration quality-control overlay.
- Red channel: Original grayscale image
- Green channel: aligned RIE grayscale image
Good alignment tends to produce overlapping yellow structures, whereas red or green regions indicate local spatial mismatch.
This image is intended primarily for visual quality control.
Pixel-wise absolute grayscale difference:
|Original_gray - Aligned_RIE_gray|
The CSV contains the quantitative floating-point difference values.
Pixel-wise RGB-channel absolute differences:
Diff_R = |R1 - R2|
Diff_G = |G1 - G2|
Diff_B = |B1 - B2|
The CSV records the individual R, G, and B difference values.
Pixel-wise luminance contrast calculated using a WCAG-style luminance formulation.
The quantitative contrast values are stored in CSV format, while the TIFF version is mapped for convenient visualization.
Pixel-wise grayscale intensity ratio:
Original_gray / Aligned_RIE_gray
A small numerical offset is used internally to avoid division by zero.
Color difference calculated in LAB color space:
sqrt(
(L1 - L2)^2 +
(a1 - a2)^2 +
(b1 - b2)^2
)
Before LAB conversion, the two RGB images are normalized using a shared scalar scale rather than independent RGB-channel stretching.
Structural difference based on the Structural Similarity Index Measure (SSIM):
1 - SSIM_map
Higher values therefore indicate greater local structural difference.
Joint pixel-intensity distribution between the Original and comparison image.
Each pixel contributes a paired intensity coordinate:
(Original_gray, RIE_gray)
The plot provides a cytofluorogram-style representation of the relationship between corresponding pixels.
Scatter plot of corresponding grayscale intensities with MiniBatch K-Means clustering.
Three clusters are calculated by default.
The CSV contains:
Original intensity
RIE intensity
Cluster ID
Spatially registered RIE RGB image.
The image is generated by applying the calculated rigid transformation to the original RIE image.
No display-specific RGB stretching is applied.
Exact copy of the Original input image.
This image serves as the fixed/reference image.
Exact copy of the RIE input image before registration.
This image serves as the moving image.
Display-normalized version of the Original image.
Display-normalized version of the unaligned RIE image.
Display-normalized version of the aligned RIE image.
The same RIE display scaling reference is used for both 13 and 14, enabling more direct visual comparison before and after registration.
The main registration workflow is approximately:
Input RGB images
│
▼
RGB → luminance grayscale
│
▼
Robust intensity normalization
│
├── optional CLAHE
│
├── optional Otsu mask
│
▼
Initial rigid transformation
│
├── center initialization
│
└── moment initialization (--use_moments)
│
▼
Coarse rotational search
│
▼
Multi-resolution rigid registration
│
▼
Resample RIE image
│
▼
Aligned RGB image
│
▼
Difference / color / structural analyses
The registration uses a 2D Euler rigid transformation and Mattes mutual information as the registration metric.
python image_alignment.py -d "D:/My New Data/Folder" --use_maskpython image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_momentspython image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_moments --enhance_contrastpython image_alignment.py -d "D:/My New Data/Folder" --use_mask --use_moments --use_view_for_analysisExample with an actual Windows directory:
python image_alignment.py -d "E:/linshi/20260903 Alignment" --use_mask --use_moments --use_view_for_analysisUsing forward slashes in Windows paths is recommended in command-line examples because it avoids ambiguity associated with backslash escape sequences.
A practical starting strategy is:
Normal displacement, clear foreground
→ --use_mask
Large displacement, clear foreground
→ --use_mask --use_moments
Large displacement + low contrast
→ --use_mask --use_moments --enhance_contrast
Need downstream calculations based on View images
→ add --use_view_for_analysis
If registration quality is uncertain, inspect:
00_Registration_QC.tiff
09_Aligned_RIE_RGB.tiff
14_Aligned_RIE_RGB_View.tiff
before interpreting downstream quantitative results.
-
Registration and downstream image analysis are intentionally separated.
-
--use_view_for_analysisdoes not modify the registration procedure. It only switches the images used after registration for outputs00–08. -
Display TIFF files may contain visualization-specific intensity mapping. Whenever available, use the corresponding CSV files for quantitative analysis.
-
The input filename regular expression can be modified if a different dataset naming convention is required.
-
Each ROI must contain both an
Originaland anRIEimage to be processed.
pattern = re.compile(r"ROI (\d+) (Original|RIE)\.tiff?")Modify this expression in image_alignment.py if your file naming convention changes.