Software Package for Analyzing Nanoparticle Shape Distribution
NanoPSD is a production-ready Python package designed to extract particle shape distributions (PSD) of Nanoparticles (NPs) from TEM/STEM images. It supports both single-image and batch image analysis, providing a modular and object-oriented pipeline for nanoparticle research and metrology.
- Automated scale bar & text exclusion from images
- Manual calibration mode for images without scale bars (direct nm/pixel input)
- Particle segmentation using classical methods (Otsu thresholding, preprocessing filters)
- Size extraction & visualization (histograms, plots, CSV export)
- Flexible particle filtering with
--min-sizeand--max-size(removes noise and false detections) - Pipeline visualization for papers and presentations (
--save-preprocessing-steps,--save-segmentation-steps) - Morphology-based nanoparticle classification (spherical, rod-like, aggregate)
- Customizable morphology thresholds via optional CLI flags
- Publication-quality plots with enhanced font sizes and statistics
- Comprehensive shape analysis (aspect ratio, circularity, solidity distributions)
- Classification of nanoparticles based on morphology
- Works with both single images and batch folders
- Modular, object-oriented codebase for easy extension
- Ready for future AI/ML-based segmentation integration
The processing workflow follows these main steps:
- Input Acquisition – TEM/STEM image(s) provided as single or batch mode.
- Preprocessing – Contrast enhancement (CLAHE, filters) to improve particle visibility.
- Segmentation – Classical thresholding (Otsu) to identify particle regions.
- Scale Bar & Text Exclusion – Automatic masking of scale bar and annotation text.
- Particle Size Measurement – Extract particle sizes and compute statistics.
- Morphology Classification - Classify particles based on their morphology and report morphology statistics.
- Visualization & Export – Histograms, CSV tables, and segmented overlay images.
opencv-python(≥4.5.0)numpy(≥1.21.0)matplotlib(≥3.4.0)scikit-image(≥0.18.0)scipy(≥1.7.0)pandas(≥1.3.0)Pillow(≥8.3.0)
EasyOCR (Supports both CPU and GPU)
pip install easyocr torch torchvision
# GPU (CUDA): Fast performance (milliseconds)
# CPU: Slower but functional (1-2 seconds per image)Skip OCR entirely (Recommended)
Provide --scale-bar-nm parameter manually. This is the fastest and most reliable method.
NanoPSD/
├── CITATION.cff # Guideline for citation
├── LICENSE # GPL-3.0 license
├── README.md # Project overview & usage
├── requirements.txt # Python dependencies
├── imglab_environment.yml # Conda environment
├── nanopsd.py # Entry point (calls CLI & pipeline)
├── sample images # Sample images for single image processing
│ ├── sample_image_1.tif
│ └── sample_image_2.png
├── batch_images/ # Sample folder for batch mode testing
│ ├── batch_sample_1.tif
│ ├── batch_sample_2.tif
│ └── batch_sample_3.tif
│
├── pipeline/ # Orchestrates the full workflow
│ ├── __init__.py
│ └── analyzer.py # NanoparticleAnalyzer class
│
├── scripts/ # Modular processing steps
│ ├── __init__.py
│ ├── cli.py # Command-line argument parser
│ │
│ ├── preprocessing/
│ │ ├── __init__.py
│ │ └── clahe_filter.py # Contrast enhancement (CLAHE)
│ │
│ ├── segmentation/
│ │ ├── __init__.py
│ │ ├── base.py # Segmentation base interface
│ │ ├── otsu_impl.py # Otsu thresholding implementation
│ │ └── otsu_segment.py # Segmentation workflow
│ │
│ ├── analysis/
│ │ └── size_measurement.py # Particle measurement & LaTeX export
│ │
│ └── visualization/
│ └── plotting.py # Histogram and plot outputs
│
├── utils/ # Helper utilities
│ ├── __init__.py
│ ├── ocr.py # OCR for scale bar text (EasyOCR)
│ └── scale_bar.py # Scale bar detection (hybrid)
│
├── docs/ # Documentation & assets
│ └── figures/ # Documentation images (README assets)
│ ├── scale_candidates.png
│ ├── sample_image_1_true_contours.jpg
│ ├── sample_image_1_morphology_overlay.jpg
│ └── sample_image_1_diameter_histogram.png
│
├── notebooks/
│ └── PSD_Interactive_Analysis.ipynb # Jupyter notebook demo
│
└── outputs/ # Generated results & reports
├── debug/ # Debug intermediate images
├── figures/ # Plots, overlays, batch comparisons
│ │
│ ├── # Single Image Outputs:
│ ├── {image}_diameter_histogram.png # Size distribution with statistics
│ ├── {image}_aspect_ratio_histogram.png # Aspect ratio distribution
│ ├── {image}_circularity_histogram.png # Circularity distribution
│ ├── {image}_solidity_histogram.png # Solidity distribution
│ ├── {image}_morphology_pie.png # Morphology distribution pie chart
│ ├── {image}_boxplot.png # Size distribution box plot
│ ├── {image}_morphology_histograms.png # 4-panel morphology breakdown
│ ├── {image}_morphology_overlay.{ext} # Color-coded morphology overlay
│ ├── {image}_true_contours.{ext} # True particle contours
│ ├── {image}_circular_equivalent.{ext} # Circular equivalent contours
│ ├── {image}_elliptical_equivalent.{ext} # Elliptical fit contours
│ ├── {image}_all_contour_types.{ext} # Combined contour comparison
│ │
│ └── # Batch Mode Outputs:
│ ├── batch_boxplot_comparison.png # Size distribution box plots
│ ├── batch_morphology_stacked_bars.png # Morphology counts by image
│ ├── batch_morphology_pie_chart.png # Overall morphology distribution
│ └── batch_summary_table.png # Statistics table visualization
│
├── preprocessed/ # Preprocessed images
├── preprocessing_steps/ # Step-by-step preprocessing (--save-preprocessing-steps)
│ ├── {image}_step1_original.png
│ ├── {image}_step2_normalized.png
│ ├── {image}_step3_clahe.png
│ ├── {image}_step4_gaussian_blur.png
│ ├── {image}_step5_otsu_threshold.png
│ └── {image}_step6_inverted.png
│
├── segmentation_steps/ # Step-by-step segmentation (--save-segmentation-steps)
│ ├── {image}_step1_input_binary.png
│ ├── {image}_step2_after_small_removal.png
│ ├── {image}_step3_after_large_removal.png
│ ├── {image}_step4_after_hole_filling.png
│ └── {image}_step5_labeled_components.png
│
├── results/ # CSV & LaTeX summaries
│ │
│ ├── # Single Image Outputs:
│ ├── nanoparticle_data.csv # Per-particle detailed data
│ ├── {image_name}_summary.csv # Summary statistics
│ ├── report.tex # LaTeX summary
│ │
│ └── # Batch Mode Outputs:
│ ├── batch_all_particles.csv # Combined data from all images
│ └── batch_summary.csv # Per-image summary statistics
│
└── scale_bar_debug/ # Scale bar detection visualization (optional)
├── scale_candidates.png
└── scale_bar_final.pngNanoPSD/
├── nanopsd.py # Main entry point (start here!)
├── analyzer.py # Core pipeline orchestrator
├── cli.py # Command-line argument parser
│
├── Preprocessing:
│ └── clahe_filter.py # Contrast enhancement + thresholding
│
├── Scale Bar Detection:
│ ├── scale_bar.py # Geometric detection
│ └── ocr.py # Text recognition (EasyOCR)
│
├── Segmentation:
│ ├── base.py # Abstract interface
│ ├── otsu_impl.py # Otsu implementation
│ └── otsu_segment.py # Classical segmentation
│
├── Measurement & Analysis:
│ ├── size_measurement.py # Particle measurement + morphology
│ └── plotting.py # Histograms + visualizations
│
└── Configuration:
├── requirements.txt
└── imglab_environment.ymlNanoPSD classifies nanoparticles into three morphology categories based on shape descriptors:
| Metric | Description | Range |
|---|---|---|
| Aspect Ratio (AR) | Ratio of major to minor axis of fitted ellipse | ≥ 1.0 |
| Circularity (C) | 4π × Area / Perimeter² (1.0 = perfect circle) |
0.0 - 1.0 |
| Solidity (S) | Area / Convex Hull Area (1.0 = no concavity) |
0.0 - 1.0 |
| Morphology | Conditions (Priority: Aggregate > Spherical > Rod-like) |
|---|---|
| Spherical | AR < 1.5 AND C > 0.75 AND S > 0.90 |
| Rod-like | AR ≥ 1.8 AND S > 0.80 |
| Aggregate | S < 0.85 OR C < 0.60 (fallback for unclassified) |
You can adjust classification thresholds using optional CLI flags:
# Customize aspect ratio thresholds (ascending order required)
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--aspect-ratio 1.4 2.0
# Customize circularity thresholds
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--circularity 0.55 0.80
# Customize solidity thresholds (3 values: rodlike_min, aggregate_max, spherical_min)
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--solidity 0.78 0.82 0.92
# Combine multiple threshold customizations
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--aspect-ratio 1.4 2.0 \
--circularity 0.55 0.80 \
--solidity 0.78 0.82 0.92- Values must be in strict ascending order (no equal values allowed)
- Circularity and solidity must be between 0 and 1
- Aspect ratio must be positive (typically 1.0 - 10.0)
- If not specified, default values are used
For each image, NanoPSD generates:
Histograms with statistics:
- Diameter distribution (with mean, median, std dev)
- Aspect ratio distribution
- Circularity distribution
- Solidity distribution
Morphology visualizations:
- Pie chart showing morphology percentages
- Color-coded overlay (green=spherical, blue=rod-like, red=aggregate)
- 4-panel morphology breakdown by particle type
git clone https://github.com/Huq2090/NanoPSD.git
cd NanoPSDpython3 nanopsd.py
# Displays comprehensive usage examples and common commandsconda create -n imglab python=3.10
conda activate imglabconda install -c conda-forge opencv numpy matplotlib scikit-image scipy pandas pillowOr recreate directly from the environment file:
conda env create -f imglab_environment.yml
conda activate imglabOnly if you want automatic scale detection:
Install EasyOCR:
pip install easyocr torch torchvisionNanoPSD provides two filtering parameters to control which particles are analyzed:
- Default: 3 pixels
- Purpose: Remove small noise objects
- When to adjust:
- Increase (5-10) for noisy images
- Decrease (1-2) to detect very small particles
Example:
python3 nanopsd.py --mode single --input image.tif --scale-bar-nm 200 --min-size 5- Default: None (no upper limit)
- Purpose: Remove large false detections (scale bars, artifacts, large aggregates)
- When to use:
- Images with large artifacts
- To exclude particles above a certain size
- To remove incorrectly detected regions
Example:
python3 nanopsd.py --mode single --input image.tif --scale-bar-nm 200 --max-size 100Example:
# Only analyze particles between 5 and 100 pixels in diameter
python3 nanopsd.py --mode single --input image.tif --scale-bar-nm 200 --min-size 5 --max-size 100Both parameters are in pixels, making them scale-independent:
- Works for nanometer, micrometer, or millimeter scale images
- Same pixel threshold works regardless of calibration
- No need to convert between units
NanoPSD can generate step-by-step visualization of the processing pipeline, ideal for:
- 📄 Scientific papers - Show methodology in Methods section
- 📊 Presentations - Explain algorithm to audiences
- 🐛 Debugging - Understand why segmentation succeeded/failed
- 🎓 Teaching - Educational demonstrations
Saves 6 intermediate images showing the preprocessing pipeline:
python3 nanopsd.py --mode single --input image.tif \
--scale-bar-nm 200 --min-size 3 \
--save-preprocessing-stepsOutput: outputs/preprocessing_steps/
*_step1_original.png- Original grayscale image*_step2_normalized.png- Intensity normalization (0-255)*_step3_clahe.png- CLAHE contrast enhancement*_step4_gaussian_blur.png- Gaussian noise reduction*_step5_otsu_threshold.png- Otsu global thresholding*_step6_inverted.png- Final binary mask
Saves 5 intermediate images showing the segmentation pipeline:
python3 nanopsd.py --mode single --input image.tif \
--scale-bar-nm 200 --min-size 5 --max-size 100 \
--save-segmentation-stepsOutput: outputs/segmentation_steps/
*_step1_input_binary.png- Input binary mask (from preprocessing)*_step2_after_small_removal.png- After min-size filtering*_step3_after_large_removal.png- After max-size filtering (if used)*_step4_after_hole_filling.png- After morphological hole filling*_step5_labeled_components.png- Color-coded particle labels
Generate both preprocessing and segmentation steps:
python3 nanopsd.py --mode single --input image.tif \
--scale-bar-nm 200 --min-size 5 \
--save-preprocessing-steps \
--save-segmentation-stepsUse Case Example:
Figure 2 in your paper: "Image preprocessing pipeline showing progressive
refinement from raw SEM image to binary mask. Generated using NanoPSD v1.0
with --save-preprocessing-steps flag."The scale bar size parameter is written on the image
- Open your image in ImageJ, Fiji, or any image viewer
- Read the scale bar annotation (e.g., "200 nm")
- Use this value with
--scale-bar-nmparameter
Example:
# Basic usage
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --scale-bar-nm 200
# With minimum and maximum size filter (remove small and large false detections)
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --max-size 100 --scale-bar-nm 200
# Generate step-by-step images for methodology visualization
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --max-size 100 --scale-bar-nm 200 --save-preprocessing-steps --save-segmentation-stepsUse --ocr-backend flag to enable automatic scale bar detection:
# CPU-friendly (easyocr-cpu)
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --ocr-backend easyocr-cpu
# GPU-accelerated (EasyOCR - requires CUDA (fallback to CPU, if GPU is not available))
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --ocr-backend easyocr-auto
# With minimum and maximum size filter
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 5 --max-size 150 --ocr-backend easyocr-auto
# With verification prompt
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --ocr-backend easyocr-auto --verify-scale-bar
# With step-by-step images for methodology visualization
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --max-size 100 --scale-bar-nm 200 --save-preprocessing-steps --save-segmentation-stepsImportant Notes on Automatic Detection:
- Works best with dark scale bars on light backgrounds
- White or light-colored scale bars often fail - use manual calculation instead
- EasyOCR on CPU is relatively slow
- Manual scale bar size parameter input is always faster and more reliable
If your images do not have scale bars but you know the calibration factor from microscope settings:
# Provide nm-per-pixel directly
python3 nanopsd.py --mode single --input no_scalebar.tif --algo classical --min-size 3 --nm-per-pixel 2.5
# Batch mode with manual calibration
python3 nanopsd.py --mode batch --input ./images --algo classical --min-size 3 --nm-per-pixel 1.8When to use this:
- Images without visible scale bars
- You have calibration data from microscope metadata
- Faster processing (skips scale bar detection entirely)
How to determine nm-per-pixel:
- Check microscope settings/metadata
- Calculate from known magnification:
nm_per_pixel = (pixel_size_µm × 1000) / magnification - Example: 5µm pixel at 10,000x magnification = (5 × 1000) / 10000 = 0.5 nm/pixel
- All images in the folder must have the same calibration (same magnification/nm-per-pixel)
- If images have different magnifications, process them in separate batches with different
--nm-per-pixelvalues - For mixed magnifications with scale bars, use
--ocr-backend(auto-detection) instead
Recommended (with manual scale):
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --scale-bar-nm 200For images without scale bars:
python3 nanopsd.py --mode single --input no_scalebar.tif --algo classical --min-size 3 --nm-per-pixel 2.5With automatic scale detection:
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --ocr-backend easyocr-autoProcess multiple images in a folder and generate both individual outputs for each image and aggregate comparisons across all images.
Basic batch processing:
# With manual scale (all images must have same scale)
python3 nanopsd.py --mode batch --input ./batch_images --algo classical --min-size 3 --scale-bar-nm 200
# With automatic scale detection (each image detected separately)
python3 nanopsd.py --mode batch --input ./batch_images --algo classical --min-size 3 --ocr-backend easyocr-auto
# For images without scale bars (known calibration)
python3 nanopsd.py --mode batch --input ./batch_images --algo classical --min-size 3 --nm-per-pixel 1.8Per-Image Outputs (same as single mode):
- Individual histograms (
{image}_histogram.png) - Individual box plots (
{image}_boxplot.png) - Individual contour overlays (
{image}_contours.png) - Individual morphology overlays (
{image}_morphology_overlay.png) - Individual morphology charts (
{image}_morphology_*.png)
Aggregate Batch Outputs:
batch_all_particles.csv- Combined dataset from all imagesbatch_summary.csv- Summary statistics per imagebatch_boxplot_comparison.png- Box plot comparison showing size distributionsbatch_morphology_stacked_bars.png- Morphology distribution by image (stacked bar chart)batch_morphology_pie_chart.png- Overall morphology distribution (pie chart)batch_summary_table.png- Statistical summary table
Example batch folder:
batch_images/
batch_sample_1.tif
batch_sample_2.tif
batch_sample_3.tif| Backend | Hardware | Speed | Recommended For |
|---|---|---|---|
easyocr-auto |
GPU (fallback CPU) | GPU: Fast, CPU: Moderate | Most users (default) |
easyocr-cpu |
CPU only | Moderate | Systems without GPU |
Performance Example:
- EasyOCR (GPU): ~8-12 seconds per image
- EasyOCR (CPU): ~15-25 seconds per image
Recommendations:
- Best practice: Input scale bar size manually, skip OCR entirely
- CPU-only systems: Use
--ocr-backend easyocr-cpu - GPU systems (CUDA): Use
--ocr-backend easyocr-auto
| Parameter | Description | Example | Required |
|---|---|---|---|
--mode |
Processing mode: single or batch |
--mode single |
Yes |
--input |
Input image path or folder | --input image.png |
Yes |
--algo |
Segmentation algorithm: classical |
--algo classical |
Yes |
--min-size |
Minimum particle size (nm) | --min-size 3 |
Yes |
--scale-bar-nm |
Scale bar size in nm. Use with images that have scale bars. | --scale-bar-nm 200 |
One of these* |
--nm-per-pixel |
Direct calibration (nm/pixel). Use for images WITHOUT scale bars. | --nm-per-pixel 2.5 |
One of these* |
--ocr-backend |
OCR engine: easyocr-auto, or easyocr-cpu |
--ocr-backend easyocr-auto |
No |
--verify-scale-bar |
Prompt user to verify detected scale | --verify-scale-bar |
No |
--aspect-ratio |
Aspect ratio thresholds (2 values, ascending) | --aspect-ratio 1.5 1.8 |
No |
--circularity |
Circularity thresholds (2 values, 0-1, ascending) | --circularity 0.60 0.75 |
No |
--solidity |
Solidity thresholds (3 values, 0-1, ascending) | --solidity 0.80 0.85 0.90 |
No |
--max-size |
Maximum particle size (pixels) | --max-size 200 |
No |
--save-preprocessing-steps |
Save step-by-step preprocessing images | --save-preprocessing-steps |
No |
--save-segmentation-steps |
Save step-by-step segmentation images | --save-segmentation-steps |
No |
* Must provide either --scale-bar-nm OR --nm-per-pixel (not both)
Analysis results are saved in the outputs/ folder:
- Particle size histogram (
histogram.png) - Tabulated results (
results.csvwith particle diameters & statistics) - Visualization plots (segmented overlays showing detected particles)
- LaTeX summary (
.texfiles for academic reports)
Particle_ID, Diameter_nm
1, 42.5
2, 37.8
3, 56.2
...- Mean diameter
- Standard deviation
- Median diameter
- Particle count
- Size distribution histogram
NanoPSD automatically classifies particles into three morphological categories based on shape analysis.
| Type | Description | Default Criteria |
|---|---|---|
| Spherical | Round, compact particles | Aspect ratio < 1.5 AND Circularity > 0.75 AND Solidity > 0.90 |
| Rod-like | Elongated particles | Aspect ratio ≥ 1.8 AND Solidity > 0.80 |
| Aggregate | Clustered or irregular particles | Solidity < 0.85 OR Circularity < 0.60 (fallback for unclassified) |
Classification Priority: Aggregate > Spherical > Rod-like
The classification uses the following geometric measurements:
| Metric | Formula | Range | Description |
|---|---|---|---|
| Aspect Ratio (AR) | Major axis / Minor axis | ≥ 1.0 | Elongation measure (1.0 = circular) |
| Circularity (C) | 4π × Area / Perimeter² | 0.0 - 1.0 | Shape roundness (1.0 = perfect circle) |
| Solidity (S) | Area / Convex Hull Area | 0.0 - 1.0 | Boundary smoothness (1.0 = no concavity) |
| Extent | Area / Bounding Box Area | 0.0 - 1.0 | Space filling (computed but not used in classification) |
Note: Extent is computed and saved in output CSV files for user analysis but is not used in the built-in classification algorithm.
You can override default thresholds using optional command-line flags:
Syntax:
--aspect-ratio [spherical_max] [rodlike_min]
--circularity [aggregate_max] [spherical_min]
--solidity [rodlike_min] [aggregate_max] [spherical_min]Important Rules:
- All values must be in strict ascending order (no equal values allowed)
- Circularity and solidity must be between 0.0 and 1.0
- Aspect ratio must be positive (typically 1.0 - 10.0)
- Values cannot be equal (transition gaps are required)
Examples:
# Stricter spherical classification
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--aspect-ratio 1.3 1.9 \
--circularity 0.65 0.82 \
--solidity 0.82 0.87 0.93
# More lenient rod-like detection
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200 \
--aspect-ratio 1.6 2.2
# Custom thresholds for gold nanospheres
python3 nanopsd.py --mode single --input gold_np.tif --scale-bar-nm 200 \
--circularity 0.70 0.85 \
--solidity 0.85 0.88 0.95
# Batch processing with custom thresholds
python3 nanopsd.py --mode batch --input ./images/ --scale-bar-nm 200 \
--aspect-ratio 1.4 2.0 \
--circularity 0.55 0.80 \
--solidity 0.78 0.82 0.92Particles are color-coded in the morphology overlay:
- Green: Spherical particles
- Blue: Rod-like particles
- Red: Aggregate particles
NanoPSD generates the following outputs for each image:
1. Detailed Particle Data (nanoparticle_data.csv):
Diameter (nm),Diameter (pixels),Centroid_X,Centroid_Y,Aspect_Ratio,Circularity,Solidity,Extent,Morphology
42.5,20.8,523.4,312.1,1.2,0.85,0.92,0.78,spherical
37.8,18.5,601.2,445.8,2.3,0.65,0.87,0.71,rod-like
56.2,27.5,234.7,189.3,1.8,0.54,0.71,0.65,aggregate
...2. Summary Statistics ({image_name}_summary.csv):
Image,Total_Particles,Mean_Diameter_nm,Std_Diameter_nm,Median_Diameter_nm,Min_Diameter_nm,Max_Diameter_nm,Spherical_Count,RodLike_Count,Aggregate_Count
sample_image.tif,147,42.35,12.78,39.21,18.45,89.32,89,32,263. Visualizations (in outputs/figures/):
Size Distribution:
{image}_diameter_histogram.png- Size distribution with statistics (mean, median, std dev, min, max){image}_boxplot.png- Box plot showing median, quartiles, and outliers
Shape Analysis:
{image}_aspect_ratio_histogram.png- Aspect ratio distribution with statistics{image}_circularity_histogram.png- Circularity distribution with statistics{image}_solidity_histogram.png- Solidity distribution with statistics
Morphology Classification:
{image}_morphology_pie.png- Morphology distribution pie chart (percentages and counts){image}_morphology_overlay.{ext}- Color-coded particle contours (Green=Spherical, Blue=Rod-like, Red=Aggregate){image}_morphology_histograms.png- 4-panel morphology breakdown by particle type
Contour Overlays:
{image}_true_contours.{ext}- True detected contours overlay{image}_circular_equivalent.{ext}- Circular equivalent diameter overlay{image}_elliptical_equivalent.{ext}- Elliptical fit overlay{image}_all_contour_types.{ext}- Combined contour visualization (all three types)
Note: All histograms include publication-quality statistics boxes with mean (red), median (blue), standard deviation, min, max, and bin width.
4. LaTeX Summary (report.tex):
Statistical summary table formatted for direct inclusion in scientific documents.
1. Combined Particle Data (batch_all_particles.csv):
All particles from all images with source tracking:
Image,Diameter (nm),Diameter (pixels),Centroid_X,Centroid_Y,Aspect_Ratio,Circularity,Solidity,Extent,Morphology
sample_1.tif,42.5,20.8,523.4,312.1,1.2,0.85,0.92,0.78,spherical
sample_1.tif,37.8,18.5,601.2,445.8,2.3,0.65,0.87,0.71,rod-like
sample_2.tif,56.2,27.5,234.7,189.3,1.8,0.54,0.71,0.65,aggregate
...2. Batch Summary (batch_summary.csv):
Per-image statistics for all processed images:
Image,Total_Particles,Mean_Diameter_nm,Std_Diameter_nm,Median_Diameter_nm,Min_Diameter_nm,Max_Diameter_nm,Spherical_Count,RodLike_Count,Aggregate_Count
sample_1.tif,147,42.35,12.78,39.21,18.45,89.32,89,32,26
sample_2.tif,203,38.92,10.45,36.78,20.12,75.43,142,45,16
sample_3.tif,189,45.67,15.23,41.55,22.34,95.67,98,54,37Key Features:
- Comprehensive statistics for each image in the batch
- Median diameter included for robust central tendency measure
- Morphology counts enable comparison of particle type distributions
- All values rounded to 2 decimal places for readability
3. Comparative Visualizations (in outputs/figures/):
batch_boxplot_comparison.png- Box plot comparison showing median, quartiles, and outliersbatch_morphology_stacked_bars.png- Morphology distribution by image (stacked bar chart with percentages)batch_morphology_pie_chart.png- Overall morphology distribution across all images (pie chart with counts)batch_summary_table.png- Summary statistics table visualization
All summary files (both single and batch mode) include:
- Total Particles: Count of detected particles after filtering
- Mean Diameter: Average particle size (nm)
- Standard Deviation: Spread of size distribution (nm)
- Median Diameter: 50th percentile particle size (nm) - robust to outliers
- Min/Max Diameter: Size range (nm)
- Morphology Counts: Number of spherical, rod-like, and aggregate particles
Input Command:
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --scale-bar-nm 200
Console Output:
2026-01-03 22:59:23,582 [INFO] Processing: sample_image_1.tif
2026-01-03 22:59:23,582 [INFO] 📏 Scale bar detection mode
Excluded text region: (820, 2017, 145, 21)
2026-01-03 22:59:24,102 [INFO] Calibration: 0.6289 nm/pixel (bar: 318 px, value: 200.0 nm)
2026-01-03 22:59:24,154 [INFO] Excluding scale bar region from particle detection...
2026-01-03 22:59:24,157 [INFO] Using geometric text exclusion around scale bar
2026-01-03 22:59:24,157 [INFO] Excluded area: (564,2017) to (1282,2115)
2026-01-03 22:59:24,695 [INFO] Segmented 824 regions after exclusion.
Saved all contour types:
- outputs/figures/sample_image_1_true_contours.tif
- outputs/figures/sample_image_1_circular_equivalent.tif
- outputs/figures/sample_image_1_elliptical_equivalent.tif
- outputs/figures/sample_image_1_all_contour_types.tif
Morphology distribution: {'aggregate': 327, 'spherical': 37, 'rod-like': 13}
- outputs/figures/sample_image_1_morphology_overlay.tif
2026-01-03 22:59:33,443 [INFO] Measured 377 particles (post-filter).
Saved: outputs/figures/sample_image_1_diameter_histogram.png
Saved summary statistics: outputs/results/sample_image_1_summary.csv
============================================================
MORPHOLOGY SUMMARY
============================================================
Spherical : 37 ( 9.8%) Avg: 7.88 nm
Rod-like : 13 ( 3.4%) Avg: 9.63 nm
Aggregate : 327 ( 86.7%) Avg: 13.61 nm
============================================================
2026-01-03 22:59:34,805 [INFO] Completed: sample_image_1.tif | Count=377Solutions:
-
Use manual scale calculation (most reliable):
# Open the image and read the scale bar size from the image, and fed to the CLI command: python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --scale-bar-nm 200 -
Verify detected scale bar:
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --ocr-backend easyocr-auto --verify-scale-bar
This will prompt you to confirm the detected scale bar.
Cause: You're using EasyOCR on a CPU-only system.
Solutions:
-
Use manual scale bar text input (no OCR needed):
python3 nanopsd.py --mode single --input sample_image_1.tif --algo classical --min-size 3 --scale-bar-nm 200
-
If you have a GPU, ensure PyTorch with CUDA is installed:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install easyocr
Solutions:
- Process in smaller batches
- Use manual scale bar text to skip OCR
- Consider downsampling very high-resolution images (if scientifically appropriate)
- Use GPU if available for EasyOCR
Potential issues:
- Low contrast images: Adjust preprocessing parameters
- Overlapping particles: May require manual separation or AI segmentation
- Non-uniform background: Try adjusting CLAHE parameters
- Wrong minimum size: Adjust
--min-sizeparameter - Large false detections: Use
--max-sizeto filter out artifacts
# Example: Exclude particles larger than 200 pixels
python3 nanopsd.py --mode single --input image.tif --scale-bar-nm 200 --min-size 3 --max-size 200Solution: Use manual calibration mode with --nm-per-pixel:
python3 nanopsd.py --mode single --input image.tif --algo classical --min-size 3 --nm-per-pixel 2.5How to find nm-per-pixel:
- Check microscope metadata/settings
- Calculate from magnification:
(pixel_size_µm × 1000) / magnification - Use ImageJ/Fiji: Set scale manually and read calibration
Example calculation:
- Pixel size: 5 µm
- Magnification: 10,000x
- nm/pixel = (5 × 1000) / 10000 = 0.5 nm/pixel
Diagnosis: Default thresholds may not suit your specific particle type.
Solutions:
-
Check current defaults:
- Aspect ratio: spherical < 1.5, rod-like ≥ 1.8
- Circularity: aggregate < 0.60, spherical > 0.75
- Solidity: rod-like > 0.80, aggregate < 0.85, spherical > 0.90
-
View distribution histograms to understand your particles:
# Run analysis first
python3 nanopsd.py --mode single --input sample.tif --scale-bar-nm 200
# Check these output files:
# - {image}_aspect_ratio_histogram.png
# - {image}_circularity_histogram.png
# - {image}_solidity_histogram.pngUse these histograms to determine appropriate thresholds for your particles.
- Adjust thresholds based on particle type:
# For gold nanospheres (stricter spherical)
python3 nanopsd.py --mode single --input gold_np.tif --scale-bar-nm 200 \
--circularity 0.70 0.85 \
--solidity 0.85 0.88 0.95
# For silver nanorods (more elongated)
python3 nanopsd.py --mode single --input silver_nr.tif --scale-bar-nm 200 \
--aspect-ratio 1.6 2.5
# For aggregated particles (lower solidity)
python3 nanopsd.py --mode single --input aggregates.tif --scale-bar-nm 200 \
--solidity 0.75 0.80 0.88Error message examples:
ERROR: --aspect-ratio values must be in STRICT ascending order
ERROR: --circularity values must be between 0 and 1
ERROR: --solidity values must be in STRICT ascending order
Common causes and fixes:
- Values not in ascending order:
# WRONG - descending order
--aspect-ratio 1.8 1.5
# CORRECT - ascending order
--aspect-ratio 1.5 1.8- Equal values (not allowed):
# WRONG - equal values
--aspect-ratio 1.5 1.5
--circularity 0.75 0.75
# CORRECT - must have gap between values
--aspect-ratio 1.5 1.8
--circularity 0.60 0.75Why equal values are rejected: Transition gaps between morphology types are scientifically standard practice. Equal values would create sharp boundaries with no transition zone.
- Out of range values:
# WRONG - circularity > 1.0
--circularity 0.60 1.2
# CORRECT - must be 0-1
--circularity 0.60 0.80- Solidity requires 3 values in ascending order:
# WRONG - only 2 values
--solidity 0.80 0.90
# WRONG - not ascending
--solidity 0.90 0.85 0.80
# CORRECT - 3 values, ascending
--solidity 0.80 0.85 0.90Remember: All three morphology flags are optional. If not provided, default values are used.
Particle Size (Diameter) Histogram

- Integrate AI-assisted segmentation (U-Net, Mask R-CNN)
- Extend support for TEM images with diffraction patterns
- Jupyter Notebook integration for reproducible workflows
- Batch processing with parallel execution
- GUI interface for non-programmers
NanoPSD is free software released under the GNU General Public License version 3 or later (GPL-3.0-or-later).
You are free to use, modify, and redistribute this software under the terms of the GPL, provided that any distributed modifications remain licensed under the same terms and that the corresponding source code is made available.
See the LICENSE file for full details.
If you use NanoPSD in academic work, please cite:
Huq, M.F. (2026). NanoPSD: Automated Nanoparticle Size and Morphology Distribution Analysis
from Electron Microscopy Images. GitHub repository.
https://github.com/Huq2090/NanoPSD
- GitHub Issues: https://github.com/Huq2090/NanoPSD/issues
- Author: M.F. Huq
- Institution: University of Illinois at Urbana-Champaign
For questions, bug reports, or feature requests, please open an issue on GitHub.
- Prof. Davide Curreli for project supervision
- [Other contributors or funding sources]
Last Updated: February 2026



