A flexible and modular Python toolkit for multi-peak curve fitting, parameter management, visualization, and area analysis using scipy.optimize.curve_fit.
This package is designed to handle arbitrary numbers of peaks with different models (Gaussian, Voigt, Asymmetric, Skewed), including support for fixed parameters (e.g., fixed peak centers μ).
🔹 Automatic peak detection — Intelligently finds peaks and estimates initial parameters
🔹 Multi-peak fitting with arbitrary peak count
🔹 Multiple peak models:
- Gaussian
- Voigt
- Asymmetric
- Skewed
🔹 Support for fixed parameters (e.g., μ fixed per peak)
🔹 Automatic parameter flattening/unflattening
🔹 Intelligent initial guess generation from data
🔹 Flexible bounds handling
🔹 Full peak decomposition after fitting
🔹 Area integration for peaks and total signal
🔹 Clean visualization with residuals and RMSE
Install from source:
git clone https://github.com/your-username/ReadyToFit.git
cd ReadyToFit
pip install .
Or install directly from GitHub:
pip install git+https://github.com/your-username/ReadyToFit.git
Dependencies: numpy, scipy, matplotlib (automatically installed).
Each peak is defined as a dictionary:
peaks = [
{"model": "gauss"},
{"model": "voigt", "mu": 50}, # fixed center at x=50
{"model": "asym"}
]The system automatically:
- Detects peaks in data using
scipy.signal.find_peaks - Estimates initial parameters (amplitude, position, width) from data
- Builds the composite model (sum of all peaks)
- Flattens parameters for optimization
- Handles fixed parameters (removed from optimization, reinstated after fit)
- Reconstructs fitted peaks
The Problem: Multi-peak fitting requires good initial guesses (p0). Bad guesses lead to poor convergence or fitting the wrong peaks.
The Solution: ReadyToFit includes intelligent automatic peak detection:
-
Peak Detection (
detect_peaks()):- Finds local maxima using local gradient analysis
- Filters by prominence (relative height above baseline)
- Estimates peak positions, amplitudes, and widths
-
Parameter Estimation (
estimate_initial_parameters()):- Converts detected peaks into initial parameter guesses
- Estimates widths from Full-Width-Half-Maximum (FWHM)
- Respects user-defined fixed parameters (not overridden)
-
Automatic p0:
- When
fit_model()is called withoutp0, it auto-generates it - Much more robust than naive guesses (e.g., same μ for all peaks)
- Can be overridden by providing manual
p0if needed
- When
Example:
from readytofit import detect_peaks
# Detect peaks manually (optional — fit_model() does this automatically)
detected = detect_peaks(x, y, n_peaks=3)
# Returns: [{"mu": 25.5, "A": 4.8, "sigma": 5.1}, ...]The simplest approach: ReadyToFit automatically detects peaks and estimates initial parameters.
from readytofit import fit_model
import numpy as np
import matplotlib.pyplot as plt
# Your noisy multi-peak data
x = np.array([...])
y = np.array([...])
# Define peak structure (models only — parameters auto-detected)
peaks = [
{"model": "gauss"},
{"model": "gauss"},
{"model": "voigt"}
]
# Fit — p0 and initial parameters are auto-generated!
result = fit_model(x, y, peaks)
print(f"RMSE: {result['rmse']:.4f}")
print("Fitted parameters per peak:")
for i, peak_params in enumerate(result["params"]):
print(f" Peak {i+1}: {peak_params}")How auto-detection works:
- Peak finding: Uses
scipy.signal.find_peaksto locate local maxima - Parameter estimation: Estimates amplitude (A), center (μ), and width (σ) from data
- Smart initial guess: Provides excellent starting point for optimization
- Respects fixed parameters: If you specify
"mu": 50, it's locked and not overridden
Lock specific parameter values (e.g., peak center):
peaks = [
{"model": "gauss"}, # All parameters free
{"model": "gauss", "mu": 50}, # Center fixed at x=50
{"model": "voigt", "mu": 80} # Center fixed at x=80
]
result = fit_model(x, y, peaks)
# View results (fixed parameters are preserved in result["params"])
assert result["params"][1]["mu"] == 50 # mu is locked
assert result["params"][1]["A"] is not None # A and sigma are fittedOverride automatic detection with manual p0:
# Manual initial guesses (if you know better than auto-detection!)
p0 = [
{"A": 100, "mu": 25, "sigma": 5},
{"A": 80, "mu": 75, "sigma": 6}
]
result = fit_model(x, y, peaks, p0=p0)from readytofit import plot_fit_result
fig, ax = plt.subplots(figsize=(10, 6))
plot_fit_result(x, y, result, show_residual=True, show_rmse=True, fig=fig, ax=ax)
fig.suptitle("Multi-Peak Fitting Results")
plt.savefig("fit_result.png")
plt.close()Example output:
from readytofit import evaluate_peak_areas
areas = evaluate_peak_areas(x, result)
print(f"Total signal area: {areas['total']:.2f}")
print(f"Individual peak areas: {areas['peaks']}")Detect peaks without fitting:
from readytofit import detect_peaks
detected = detect_peaks(x, y, n_peaks=3)
for i, peak in enumerate(detected):
print(f"Peak {i+1}:")
print(f" Center (μ): {peak['mu']:.2f}")
print(f" Amplitude (A): {peak['A']:.3f}")
print(f" Width (σ): {peak['sigma']:.3f}")The fit_model() function returns a comprehensive results dictionary:
result = {
# Parameters
"popt": [...], # Optimized parameters (flat, free only)
"params": [ # Structured parameters per peak
{"A": 5.0, "mu": 30.0, "sigma": 5.1},
{"A": 3.0, "mu": 70.0, "sigma": 3.0}
],
# Fitted curves
"total_fit": [...], # Full reconstructed signal
"peak_fits": [...], # Individual peaks
# Diagnostics
"residual": [...], # y - total_fit
"rmse": 0.1006, # Root mean square error
# Metadata
"param_names": [...], # Names of free parameters
"param_slices": [...], # Index mapping per peak
"model_function": callable, # Composite model function
"p0": [...], # Initial guess used
"bounds": (lower, upper) # Bounds used in optimization
}Access results:
# Check fit quality
print(f"RMSE: {result['rmse']:.4f}")
# Get all parameters (including fixed ones)
for i, peak in enumerate(result["params"]):
print(f"Peak {i}: {peak}")
# Get fitted curve and residuals
y_fitted = result["total_fit"]
residuals = result["residual"]
# Get individual peak contributions
for i, peak_curve in enumerate(result["peak_fits"]):
plt.plot(x, peak_curve, label=f"Peak {i}")| Model | Parameters | Use Case |
|---|---|---|
| gauss | A, μ, σ | Symmetric peaks (most common) |
| voigt | A, μ, σ, γ | Symmetric peaks with natural broadening |
| asym | A, μ, σL, σR, γ | Asymmetric peaks (different left/right widths) |
| skew | A, μ, σ, γ, α | Peaks with asymmetric tails |
Legend:
- A: Amplitude (peak height above baseline)
- μ: Center position (can be fixed:
{"model": "gauss", "mu": 50}) - σ: Standard deviation (width parameter)
- σL, σR: Left/right widths for asymmetric model
- γ: Lorentz width (natural line broadening)
- α: Skew parameter (asymmetry control)
Fixed parameters are removed from optimization. Example:
{"model": "gauss", "mu": 50}
# → Only A and sigma are fitted; mu is locked at 50🔹 Automatic parameter handling
No manual indexing required — parameters are flattened internally and fixed parameters are properly managed.
🔹 Robust fitting pipeline
Handles missing p0, partial bounds, and invalid input gracefully with intelligent fallbacks.
🔹 Full decomposition
Inspect total fit, individual peaks, residuals, and peak areas independently.
🔹 Peak detection
detect_peaks() and estimate_initial_parameters() enable automatic initial guess generation.
fit_model(x, y, peaks, p0=None, bounds=None, debug=False)
Main fitting function. Automatically detects peaks and estimates p0 if not provided.
plot_fit_result(x, y, result, show_residual=True, show_rmse=True, fig=None, ax=None)
Plot raw data, total fit, individual peaks, residuals, and RMSE.
-
detect_peaks(x, y, n_peaks=None, height_threshold=0.1, prominence_threshold=None, distance=None)
Automatically detect peaks. Returns list of peak dictionaries with estimated parameters. -
estimate_initial_parameters(x, y, peaks)
Estimate initial parameters from data. Respects fixed parameters in peak definitions.
-
evaluate_peak_areas(x, result)
Compute areas of total fit and individual peaks using trapezoidal rule. -
area_integration(y, x=None)
Low-level numerical integration utility.
numpy
scipy
matplotlib
This project is designed as a modular research-grade fitting system, not a black-box tool. Each component can be reused independently in scientific workflows.
