Skip to content

Repository files navigation

SEM Grain Boundary Segmentation — Multi-Image Streamlit App

A browser-based, no-code interface for segmenting grain/splat boundaries in SEM (or optical) micrographs, replicating the Original | Otsu | U-Net | Ground truth figure layout from the reference paper — on any number of images at once.


Quick start

pip install -r requirements.txt
streamlit run app.py
# or simply:
bash launch.sh

Then open http://localhost:8501 in your browser.


How to use it

1 · Upload tab

Drag-and-drop one or more TIF/PNG/JPG micrographs. The SEM info bar (scale bar, kV, WD, timestamp, Zeiss logo …) is detected and stripped automatically from every image before any analysis runs.

2 · Sidebar — Pipeline mode (choose one)

Mode What happens
Classical only Otsu (global) + Otsu (adaptive) + Watershed. No GPU, no training, runs in seconds.
Bootstrap — per-image For each image: auto-generate watershed pseudo-labels, train a small U-Net from scratch on those pseudo-labels, run inference. Use when images differ significantly from each other.
Bootstrap — shared model Train the U-Net on the first image's pseudo-labels, then run inference on all subsequent images with that shared model. Faster when images are from the same material/magnification.
Load checkpoint Upload a .pt checkpoint (from a previous training run saved by train.py) and run inference only.

3 · Sidebar — Tuning knobs

Preprocessing

  • CLAHE contrast enhancement — local histogram equalisation, improves faint boundaries
  • Bilateral denoising — edge-preserving noise reduction

Watershed

  • Smooth σ (4–30): controls grain scale. Too low → over-segmented (too many tiny fragments). Too high → under-segmented (grains merge). Default 14 worked well on the provided sample.
  • Prominence threshold (5–40): only keep local minima that are this deep. Higher → fewer, larger grains detected.
  • Boundary thickness (1–7 px): how wide the boundary target is for U-Net training. Wider = more positive pixels = easier class balance.

U-Net (visible when a non-classical mode is selected)

  • Epoch count, patch size, batch size, learning rate, base channels — all configurable
  • Inference stride: smaller = more overlap = smoother stitching at cost of speed

Boundary line cleanup (yellow-line output)

  • Test-time augmentation — averages the prediction over 4 flips/rotations; small, free accuracy bump
  • Skeletonize to 1px line — thins the raw prediction down to a clean centerline instead of a thick blob, matching the look of a hand-drawn ground-truth overlay
  • Remove speckle blobs smaller than (px) — kills isolated noise before thinning
  • Final line thickness (px) — re-thickens the skeleton to the width you want
  • Accuracy tolerance band (px) — how many pixels off a prediction can be from the reference line and still count as a match, used for the boundary F1/precision/recall numbers

4 · Process tab

Click ▶️ Run pipeline. Live progress bars and per-image status panels update as each image finishes.

5 · Results tab

For each image: a 5-panel comparison figure (Original | Otsu global | Otsu adaptive | Watershed | U-Net), boundary overlays coloured on the original image (U-Net drawn as a thin yellow line, matching the reference figure's ground-truth panel), grain area histogram, U-Net loss curve (if trained), and accuracy metrics.

6 · Statistics tab

Cross-image comparison bar charts (grain count, mean area), a pooled grain area distribution histogram across all images, and a summary table downloadable as CSV.

7 · Download tab

One-click download of a zip containing all masks, figures, and the CSV for every processed image.


About "95% accuracy" — read this before you trust a number

There is no setting in this app that guarantees 95% accuracy — that number is a property of your training data and your model, not of the code. What the app now gives you:

  • A visual style fix: the U-Net output is post-processed (speckle removal → morphological closing → skeletonization) into a thin yellow line that looks like the hand-drawn ground-truth panel, instead of a thick, noisy blue blob.
  • A measurement tool, not a promise: postprocessing.compute_metrics() reports IoU, Dice, and a pixel-tolerant boundary precision/recall/F1 (the standard way to score edge/boundary predictions, since a line one pixel off from hand-drawn ground truth is still a correct detection). Use these numbers to actually track progress toward whatever accuracy target you need.
  • A caveat that matters: in Bootstrap modes, and in the app's Results tab by default, "accuracy" is measured against the watershed pseudo-label, not hand-verified ground truth. Watershed pseudo-labels are themselves approximate, so a high score there tells you the U-Net agrees with a classical heuristic — it does not certify real-world accuracy. To get a trustworthy number:
    1. Hand-correct a batch of watershed masks (see workflow below).
    2. Train on the corrected masks with train.py --images ... --masks ....
    3. Run predict.py --checkpoint ... --gt <hand-labelled mask> — only then does the printed IoU/F1 reflect real accuracy.
  • Test-time augmentation (--tta in predict.py, or the toggle in the app) gives a small, free accuracy improvement by averaging predictions over 4 orientations — it does not replace having good training data.

In short: the pipeline is built to let you measure and improve toward a target accuracy honestly; treat any single quoted percentage — including ones this app prints from pseudo-labels — with appropriate skepticism until it's checked against real ground truth.


Important: bootstrap vs. real ground truth

The bootstrap modes use watershed pseudo-labels as training targets for the U-Net. These are a strong classical approximation but are not hand-verified. The resulting U-Net will be qualitatively better than plain Otsu at cleaning up spurious noise but will have the same topological limitations as the watershed method itself.

For production-quality segmentation matching the paper's IoU numbers, you need hand-annotated masks:

  1. Run the app in Classical only mode to get watershed pseudo-labels
  2. Download the watershed masks (Download tab → individual image)
  3. Correct them manually in ImageJ / CVAT / Labelme (they're already close — you're editing, not drawing from scratch)
  4. Save corrected masks as PNG (white = boundary, black = background), matching the image filename
  5. Re-train with train.py --images <folder> --masks <corrected_folder> --epochs 150 --base_ch 32
  6. Load the resulting .pt checkpoint in the app's Load checkpoint mode

File structure

sem_app/
├── app.py              ← Streamlit app (this is the main file)
├── launch.sh           ← One-command startup
├── requirements.txt
│
├── preprocessing.py    ← Load + info-bar crop + CLAHE + denoise
├── otsu_baseline.py    ← Otsu global and adaptive thresholding
├── pseudo_labels.py    ← Watershed pseudo-label generator
├── postprocessing.py   ← Thin yellow-line cleanup + accuracy metrics (IoU/Dice/boundary F1)
├── unet_model.py       ← U-Net architecture (PyTorch)
├── dataset.py          ← Patch extraction, augmentation, sliding-window + TTA inference
└── train.py            ← Training loop, loss functions, CLI trainer

Hardware

Everything runs on CPU — no GPU required. On a modern laptop:

  • Classical only: ~2–5s per image
  • Bootstrap training (40 epochs, patch 128, base_ch 16): ~45–90s per image on CPU, ~5–10s on GPU
  • Inference only (after training): ~5–15s per image on CPU

For batch jobs of 10+ images with U-Net training, a CUDA GPU (even a modest RTX 3060) cuts the per-image time by ~10–20×.

About

This repository, sem_boundary_app_v9 , contains a Streamlit web application and a supporting CLI command pipeline for the segmentation of grain and splat boundaries in Scanning Electron Microscope (SEM) or optical micrographs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages