Unified cross-section shape analysis and fiber curvature analysis for anthropological hair research.
Cross-section pipeline — SAM2 (GPU) or watershed (CPU) segmentation → EFD, Hu moments, radial profile, shape classification into 7 classes.
Curvature pipeline — CLAHE preprocessing, medial-axis skeleton, Taubin circle-fit per sliding window → curvature statistics, curl index, wave count, fiber diameter.
Streamlit app — browser-based interface with file upload for quick tests, SLURM batch submission for large datasets, and interactive visualisation of results.
pip install -r requirements.txtRequires Python ≥ 3.10.
# First install PyTorch with the correct CUDA build for your system:
# https://pytorch.org/get-started/locally/
pip install git+https://github.com/facebookresearch/segment-anything-2.gitWithout SAM2 the pipeline falls back to watershed segmentation automatically.
mkdir -p checkpoints
wget -P checkpoints/ \
https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.ptThe pipeline looks for weights at checkpoints/sam2.1_hiera_tiny.pt (relative to the package root). No other locations are searched.
pip install rawpy # enables NEF, CR2, ARW, DNG input# Keep the process alive across SSH disconnection
tmux new -s hairapp
/home/<user>/.conda/envs/sam2_env/bin/streamlit run \
/path/to/hair_analysis/app.py \
--server.port 8501ssh -L 8501:localhost:8501 <user>@greatlakes.arc-ts.umich.edu
# then open http://localhost:8501 in your browserUpload TIFF images directly from your browser (no cluster paths needed). Analysis runs immediately on the server — no SLURM job required. Useful for testing a handful of images and reviewing results before running a full batch.
- Upload cross-section and/or curvature TIFFs
- Configure resolution, diameter limits, SAM2 toggle
- Results and visualisations appear instantly in the same page
Point to directories already on the cluster. Configures and generates an SBATCH script from your settings.
- Enter cluster paths for section and/or curvature image directories
- Set resolution, segmentation method, SAM2 checkpoint path, SLURM resources
- Click Generate SBATCH Script — the script appears in the next tab for review
Review and edit the generated script, then submit it.
- Editable script text area before submission
- One-click
sbatchsubmission — job ID is captured automatically - Live status polling via
squeuewith optional 10 s auto-refresh - Live log tail from
slurm_{job_id}.out - Download the
.sbatchfile for reuse
Load completed results from a cluster directory and explore them interactively.
- Reads
hair_analysis_per_image.csvandhair_analysis_per_sample.csv - Summary metrics bar (total images, unique samples, most common shape)
- Cross-section figures (11): shape distribution, shape space scatter, geometric boxplots, size distributions, EFD power spectrum, radial/boundary features, correlation heatmap, region comparison, segmentation breakdown, feature fingerprint heatmap, within-individual variability
- Curvature figures (6): curvature distributions, curl index & wave count, fiber diameter, curvature vs curl scatter, region feature heatmap, within-individual variability
- Sample-level figure: per-sample Z-scored feature heatmap (top 30 individuals)
- Raw data tables (collapsed by default)
- CSV download buttons
The pipeline can also be run directly without the Streamlit app — useful for scripted workflows and SBATCH submission.
# Cross-section only (CPU, no SAM2):
python run.py \
--section-dir /path/to/section_tiffs/ \
--output-dir ./results/ \
--no-sam2
# Curvature only:
python run.py \
--curv-dir /path/to/curvature_tiffs/ \
--output-dir ./results/ \
--resolution-mm 132.0
# Both pipelines with SAM2:
python run.py \
--section-dir /path/to/section/ \
--curv-dir /path/to/curvature/ \
--output-dir ./results/| Flag | Default | Description |
|---|---|---|
--section-dir |
— | Directory of cross-section TIFF/RAW images |
--curv-dir |
— | Directory of curvature TIFF/RAW images |
--output-dir |
required | Output directory for CSVs |
--resolution-mu |
4.25 | Cross-section resolution (µm/pixel) |
--resolution-mm |
132.0 | Curvature resolution (pixels/mm) |
--min-diam |
30 | Minimum hair diameter to keep (µm, section only) |
--max-diam |
150 | Maximum hair diameter to keep (µm, section only) |
--window-size |
50 | Taubin sliding window size (pixels, curvature only) |
--no-sam2 |
— | Force watershed segmentation (CPU-only nodes) |
--sam2-checkpoint |
checkpoints/sam2.1_hiera_tiny.pt |
Override SAM2 weights path |
--sam2-cfg |
(SAM2 package default) | Override SAM2 config yaml |
The cross-section and curvature pipelines run independently — either can be omitted by not providing its directory flag.
Images must be named {SAMPLEID}_{REGION}_{REPLICATE}.tiff for sample-level aggregation:
140025_A_3.tiff → sample_id=140025, region=A, replicate=3
Files that do not match this pattern are still processed; metadata fields are left empty.
Both files are written to --output-dir.
| Column group | Columns |
|---|---|
| Metadata | sample_id, region, replicate, image_type, source_file, segmentation_method |
| Shape | area_mu2, perimeter_mu, circularity, solidity, convexity, aspect_ratio, eccentricity, orientation_deg |
| Radial profile | radial_mean_mu, radial_std_mu, radial_min_mu, radial_max_mu, radial_cv, n_radial_peaks, asymmetry_index |
| Hu moments | hu1–hu7 (log-transformed, scale/rotation invariant) |
| EFD | efd_a1–efd_d10 (size-normalised, 10 harmonics), efd_power_h1–h10, efd_deviation |
| Classification | shape_class, confidence |
| Curvature | curv_mean, curv_median, curv_std, curv_max, curv_cv, curv_iqr |
| Curl / wave | curl_index, curl_index_std, wave_count, wave_count_per_mm |
| Length | length_mean, length_median, length_total, length_std, hair_count |
| Diameter | diameter_mean_mu, diameter_cv |
Section and curvature rows appear in the same file, distinguished by image_type.
All numeric columns aggregated to {col}_mean and {col}_std across replicates, plus n_valid (replicate count) and shape_class_mode.
| Class | Aspect Ratio | Circularity | Notes |
|---|---|---|---|
| Circular | < 1.3 | > 0.85 | Tightly coiled cross-section |
| Elliptical | 1.3–2.5 | ≥ 0.72 | Most common class |
| Flattened | ≥ 2.5 | — | Ribbon-like; Franbourg et al. threshold |
| Tear-drop | 1.1–2.0 | 0.55–0.80 | Pyriform; asymmetry index > 0.15 |
| Triangular | — | < 0.78 | Exactly 3 radial lobes |
| Multi-polar | — | — | > 3 radial lobes, solidity < 0.82 |
| Irregular | — | < 0.60 | Highly non-convex or damaged |
Classification uses ordered priority rules — the first matching rule wins. See core/shape_analysis.py for full thresholds.
hair_analysis/
├── core/
│ ├── shape_analysis.py # EFD, radial profile, Hu moments, classify_shape()
│ └── curvature_core.py # taubin_curv(), subset_gen(), pixel_length_correction()
├── pipeline/
│ ├── io.py # Image loading (TIFF/RAW/LA), metadata parsing
│ ├── section.py # SAM2 → watershed → extract_features_from_array()
│ ├── curvature.py # CLAHE → medial axis → Taubin → 17 metrics
│ └── batch.py # Batch loop, per-sample aggregation, CSV output
├── visualizations.py # All figure functions (returns plt.Figure, no side effects)
├── app.py # Streamlit interface (4 tabs)
├── run.py # CLI entry point
├── requirements.txt
└── pyproject.toml
All visualisation logic lives in visualizations.py. Each figure function takes a pd.DataFrame and returns a plt.Figure. To add a new figure:
- Write a function following the existing pattern (accept
df, returnfig, handle missing columns gracefully via_empty()) - Yield it from
section_figures(),curvature_figures(), orsample_figures()with a group label and title - The Results tab and Quick Test tab will pick it up automatically
numpy>=1.24
pandas>=2.0
opencv-python>=4.8
scikit-image>=0.21
scipy>=1.11
Pillow>=10.0
tqdm>=4.65
matplotlib>=3.7
seaborn>=0.12
streamlit>=1.35
Optional: rawpy>=0.18 (RAW images), torch>=2.1 + sam2 (GPU segmentation).
If you use this pipeline in your research please cite:
- SAM2: Ravi et al. (2024). SAM 2: Segment Anything in Images and Videos. arXiv:2408.00714
- Taubin circle fit: Taubin, G. (1991). Estimation of Planar Curves, Surfaces and Nonplanar Space Curves Defined by Implicit Equations. IEEE PAMI 13(11):1115–1138
- Loussouarn curl index: Loussouarn et al. (2007). Worldwide diversity of hair morphology at the follicular level. British Journal of Dermatology 156(4):632–642
- Franbourg shape classification: Franbourg et al. (2003). Current research on ethnic hair. Journal of the American Academy of Dermatology 48(6):S115–S119