Skip to content

About

Hair morphology analysis tool

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

Hair Analysis Pipeline

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.


Installation

1. Core dependencies

pip install -r requirements.txt

Requires Python ≥ 3.10.

2. SAM2 (optional — GPU segmentation)

# 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.git

Without SAM2 the pipeline falls back to watershed segmentation automatically.

3. SAM2 model weights

mkdir -p checkpoints
wget -P checkpoints/ \
  https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt

The pipeline looks for weights at checkpoints/sam2.1_hiera_tiny.pt (relative to the package root). No other locations are searched.

4. RAW image support (optional)

pip install rawpy   # enables NEF, CR2, ARW, DNG input

Running the Streamlit app

On an HPC cluster (Great Lakes / SLURM)

# 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 8501

Accessing from your local machine via SSH port-forward

ssh -L 8501:localhost:8501 <user>@greatlakes.arc-ts.umich.edu
# then open http://localhost:8501 in your browser

App overview — four tabs

1. Quick Test

Upload 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

2. Batch (Cluster)

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

3. Submit & Monitor

Review and edit the generated script, then submit it.

  • Editable script text area before submission
  • One-click sbatch submission — job ID is captured automatically
  • Live status polling via squeue with optional 10 s auto-refresh
  • Live log tail from slurm_{job_id}.out
  • Download the .sbatch file for reuse

4. Results

Load completed results from a cluster directory and explore them interactively.

  • Reads hair_analysis_per_image.csv and hair_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

CLI usage

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/

All flags

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.


Image naming convention

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.


Output files

Both files are written to --output-dir.

hair_analysis_per_image.csv — one row per image

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.

hair_analysis_per_sample.csv — one row per sample_id × region × image_type

All numeric columns aggregated to {col}_mean and {col}_std across replicates, plus n_valid (replicate count) and shape_class_mode.


Shape classes

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.


Project structure

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

Adding new figures

All visualisation logic lives in visualizations.py. Each figure function takes a pd.DataFrame and returns a plt.Figure. To add a new figure:

  1. Write a function following the existing pattern (accept df, return fig, handle missing columns gracefully via _empty())
  2. Yield it from section_figures(), curvature_figures(), or sample_figures() with a group label and title
  3. The Results tab and Quick Test tab will pick it up automatically

Requirements

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).


Citation

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

About

Hair morphology analysis tool

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages