Modern lineshape fitting for pseudo-3D NMR spectra.
- Multiple lineshape models: Gaussian, Lorentzian, Pseudo-Voigt, and apodization-specific models (SP1, SP2, No-Apod)
- Automatic lineshape detection: Detects optimal lineshape from NMRPipe processing parameters
- Peak clustering: Automatic grouping of overlapping peaks for simultaneous fitting
- Modern CLI: Intuitive command-line interface with rich terminal output
- Configuration files: TOML-based configuration for reproducible analyses
- Type-safe: Full type hints and Pydantic models for validation
- Comprehensive testing: Extensive test suite with synthetic data validation
uv is a fast Python package and project manager. Install it first:
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"Then install PeakFit:
# Install PeakFit
uv pip install peakfit
# Or create a new project with PeakFit
uv init my-project
cd my-project
uv add peakfitpip install peakfitgit clone https://github.com/gbouvignies/PeakFit.git
cd PeakFit
uv sync --all-extras # Install all dependencies including dev tools- Python >= 3.13
- NMRPipe format spectrum files (.ft2, .ft3)
# Fit peaks in a pseudo-3D spectrum
peakfit fit spectrum.ft2 peaks.list
# Specify output directory and refinement iterations
peakfit fit spectrum.ft2 peaks.list --output Results --refine 2
# Use specific lineshape model
peakfit fit spectrum.ft2 peaks.list --lineshape pvoigt
# Fix peak positions during fitting
peakfit fit spectrum.ft2 peaks.list --fixed# Generate a default configuration file
peakfit init config.toml
# Edit the configuration file, then run:
peakfit fit spectrum.ft2 peaks.list --config config.tomlExample configuration (config.toml):
[fitting]
lineshape = "auto"
refine_iterations = 2
fix_positions = false
max_iterations = 1000
tolerance = 1e-8
[clustering]
contour_factor = 5.0
[output]
directory = "Fits"
formats = ["txt"]
save_simulated = true
save_html_report = true
exclude_planes = []# Validate input files before fitting
peakfit validate spectrum.ft2 peaks.list# Generate intensity plots
peakfit plot intensity Results/ --show
# Launch interactive spectra viewer
peakfit plot spectra Results/ --spectrum spectrum.ft2Fit lineshapes to peaks in pseudo-3D NMR spectrum.
peakfit fit SPECTRUM PEAKLIST [OPTIONS]
Arguments:
SPECTRUM Path to NMRPipe spectrum file (.ft2, .ft3)
PEAKLIST Path to peak list file (.list, .csv, .json, .xlsx)
Options:
-z, --z-values PATH Path to Z-dimension values file
-o, --output PATH Output directory [default: Fits]
-c, --config PATH Path to TOML configuration file
-l, --lineshape TEXT Lineshape: auto, gaussian, lorentzian, pvoigt, sp1, sp2
-r, --refine INTEGER Number of refinement iterations [default: 1]
-t, --contour FLOAT Contour level for segmentation
-n, --noise FLOAT Manual noise level
--fixed/--no-fixed Fix peak positions
--jx/--no-jx Fit J-coupling constant
--phx/--no-phx Fit phase correction in X
--phy/--no-phy Fit phase correction in Y
-e, --exclude INTEGER Plane indices to exclude
--help Show this message and exit
Note: The CLI option `--workers` has been removed. PeakFit now runs sequentially by default.Validate input files before fitting.
peakfit validate SPECTRUM PEAKLISTGenerate a default configuration file.
peakfit init [PATH] [OPTIONS]
Arguments:
PATH Path for new configuration file [default: peakfit.toml]
Options:
-f, --force Overwrite existing fileGenerate plots from fitting results.
peakfit plot RESULTS [OPTIONS]
Arguments:
RESULTS Path to results directory or file
Options:
-s, --spectrum PATH Path to original spectrum for overlay
-o, --output PATH Output file for plots (PDF)
--show/--no-show Display plots interactively
-t, --type TEXT Plot type: intensity, cest, cpmg, spectra# Sparky peak list
Assignment w1 w2
Peak1 8.50 120.5
Peak2 7.80 115.3
Peak3 8.52 120.8
Assign F1,Assign F2,Pos F1,Pos F2
Peak1,Peak1,8.50,120.5
Peak2,Peak2,7.80,115.3
[
{"name": "Peak1", "x": 8.50, "y": 120.5},
{"name": "Peak2", "x": 7.80, "y": 115.3}
]After fitting, PeakFit generates the following files in the output directory:
{peak_name}.out- Per-peak fitting results with intensity profilesshifts.list- Fitted chemical shift positionssimulated.ft2/ft3- Reconstructed spectrum from fitted parameterslogs.html- HTML report with detailed fitting information
- Gaussian:
exp(-(dx²) * 4*ln(2) / FWHM²) - Lorentzian:
(0.5*FWHM)² / (dx² + (0.5*FWHM)²) - Pseudo-Voigt:
(1-η)*Gaussian + η*Lorentzian
- NO_APOD: No apodization window applied
- SP1: Sine-bell apodization (power 1)
- SP2: Sine-bell apodization (power 2)
PeakFit performs sequential cluster fitting using scipy.optimize least squares for predictable execution and minimal memory usage.
Notes:
- Multi-process/parallel cluster fitting was removed to simplify the execution model.
- For datasets with many clusters, performance can be improved by optimizing lineshape calculations or using the benchmark tools to tune your environment.
# Exclude specific planes from fitting
peakfit fit spectrum.ft2 peaks.list --exclude 0 --exclude 5 --exclude 10# Set manual noise level instead of auto-detection
peakfit fit spectrum.ft2 peaks.list --noise 100.0# Clone the repository
git clone https://github.com/gbouvignies/PeakFit.git
cd PeakFit
# Install with all dependencies (recommended)
uv sync --all-extras
# Or install development dependencies only
uv sync --extra dev# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=peakfit --cov-report=html
# Run specific test file
uv run pytest tests/unit/test_lineshapes.py# Linting with Ruff
uv run ruff check peakfit/
# Type checking
uv run mypy peakfit/
# Format code
uv run ruff format peakfit/
# Run pre-commit hooks
uv run pre-commit run --all-files# Build wheel and source distribution
uv build
# Build artifacts are in dist/peakfit/
├── lineshapes/ # Lineshape functions and models
│ ├── gaussian.py # Gaussian lineshape
│ ├── lorentzian.py # Lorentzian lineshape
│ ├── pvoigt.py # Pseudo-Voigt lineshape
│ └── ...
├── fitting/ # Fitting algorithms and parameters
│ ├── parameters.py # Parameter system
│ ├── fit.py # Fitting engine
│ └── ...
├── data/ # Data structures
│ ├── spectrum.py # Spectrum data
│ ├── cluster.py # Peak clusters
│ └── ...
├── models/ # Configuration models
│ └── config.py # Pydantic models
├── analysis/ # Analysis tools
│ ├── benchmarks.py # Performance benchmarking
│ ├── profiling.py # Profiling utilities
│ └── ...
├── io/ # Input/output operations
│ └── readers.py # File readers
├── cli/ # Modern CLI with Typer
│ ├── app.py # Main Typer application
│ └── ...
└── plotting/ # Visualization
└── plots/ # Individual plot generators
PeakFit provides comprehensive plotting capabilities through the peakfit plot command with dedicated subcommands for each plot type:
# Generate intensity plots
peakfit plot intensity Fits/ --output plots.pdf
# Interactive display (limited to first 10 plots for large datasets)
peakfit plot intensity Fits/ --show# Auto-detect reference points (|offset| >= 10 kHz)
peakfit plot cest Fits/ --output cest.pdf
# Manually specify reference point indices
peakfit plot cest Fits/ --ref 0 1 2
# Interactive display (limited to first 10 plots)
peakfit plot cest Fits/ --show# Generate CPMG plots (--time-t2 is required)
peakfit plot cpmg Fits/ --time-t2 0.04 --output cpmg.pdf
# With interactive display
peakfit plot cpmg Fits/ --time-t2 0.04 --show# Launch PyQt5 viewer with spectrum overlay
peakfit plot spectra Fits/ --spectrum data.ft2The new CLI provides a more intuitive interface while maintaining all functionality:
| Old Command | New Command |
|---|---|
peakfit -s spec.ft2 -l peaks.list |
peakfit fit spec.ft2 peaks.list |
peakfit -s spec.ft2 -l peaks.list -o Out -r 3 |
peakfit fit spec.ft2 peaks.list --output Out --refine 3 |
peakfit -s spec.ft2 -l peaks.list --pvoigt |
peakfit fit spec.ft2 peaks.list --lineshape pvoigt |
If you use PeakFit in your research, please cite:
[Citation information to be added]
GPL-3.0-or-later
See CONTRIBUTING.md for guidelines.
PeakFit uses a custom parameter system optimized for NMR fitting with domain-specific bounds and metadata:
from peakfit.fitting import Parameters, Parameter, ParameterType
# Create parameters with NMR-specific types
params = Parameters()
# Position parameter (ppm)
params.add(
"peak1_x0",
value=8.50,
min=8.40,
max=8.60,
param_type=ParameterType.POSITION,
unit="ppm"
)
# Linewidth parameter with automatic bounds
# FWHM type defaults to bounds (0.1, 200.0) Hz
params.add(
"peak1_fwhm",
value=25.0,
param_type=ParameterType.FWHM,
unit="Hz"
)
# Phase correction with automatic bounds
# PHASE type defaults to bounds (-180.0, 180.0) degrees
params.add(
"peak1_phase",
value=0.0,
param_type=ParameterType.PHASE,
unit="deg"
)
# J-coupling constant with automatic bounds
# JCOUPLING type defaults to bounds (0.0, 20.0) Hz
params.add(
"peak1_j",
value=7.0,
param_type=ParameterType.JCOUPLING,
unit="Hz"
)
# Fraction (mixing) parameter
# FRACTION type defaults to bounds (0.0, 1.0)
params.add(
"peak1_eta",
value=0.5,
param_type=ParameterType.FRACTION
)
# Parameter operations
params.freeze(["peak1_x0"]) # Fix parameters
params.unfreeze(["peak1_x0"]) # Release parameters
boundary_params = params.get_boundary_params() # Check for boundary issues
print(params.summary()) # Formatted parameter table- POSITION: Peak center position (units: ppm or points)
- FWHM: Full width at half maximum (units: Hz, bounds: 0.1-200.0)
- FRACTION: Mixing parameters like eta (bounds: 0.0-1.0)
- PHASE: Phase correction (units: deg, bounds: -180.0 to 180.0)
- JCOUPLING: J-coupling constants (units: Hz, bounds: 0.0-20.0)
- AMPLITUDE: Peak amplitudes (bounds: 0.0 to inf)
- GENERIC: Other parameters (no default bounds)
The fitting engine uses scipy.optimize.least_squares directly for optimal performance:
from peakfit.fitting import fit_cluster, FitResult
# Fit a single cluster
result: FitResult = fit_cluster(
params,
cluster,
noise,
max_nfev=1000,
ftol=1e-8,
xtol=1e-8,
gtol=1e-8
)
# Access fit statistics
print(f"Chi-squared: {result.chisqr}")
print(f"Reduced chi-squared: {result.redchi}")
print(f"Function evaluations: {result.nfev}")
print(f"Success: {result.success}")PeakFit includes global optimization methods for difficult fitting problems.
These are available as development utilities in tools/analysis/:
# From tools/analysis (development utilities, not part of installed package)
from analysis.benchmarks import (
benchmark_lineshape_backends,
profile_fit_cluster,
)
from analysis.profiling import (
Profiler,
estimate_optimal_workers,
)
# Profile fitting stages
profile = profile_fit_cluster(params, cluster, noise)
print(f"Shape calculation: {profile['shape_calculation']*1000:.3f} ms")
print(f"Residual calculation: {profile['residual_calculation']*1000:.3f} ms")
print(f"Full fit: {profile['full_fit']*1000:.3f} ms")
# Benchmark different lineshape backends
results = benchmark_lineshape_backends(n_points=1000, n_iterations=100)
print(results)