中文

Python实战AFM力曲线分析:从原始数据到力学性能

为什么写这个教程

做AFM力谱实验的朋友都知道:仪器导出一堆txt文件,在商业软件里手动处理几个小时。这个教程给你一套完整的Python流程,从读数据到出杨氏模量,不依赖任何商业软件。只用numpy、scipy、matplotlib。

1. AFM力曲线数据结构

一次典型的AFM力曲线记录的是微悬臂偏转随压电陶瓷移动趋近和远离样品:

阶段物理含义提取参数
------------------------
逼近针尖向样品靠近接触点、样品刚度
接触针尖压入样品弹性模量 (通过拟合)
回退针尖拉离样品粘附力、解结合事件

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal, optimize

2. 加载典型数据

绝大多数AFM仪器导出的是表格数据,常见格式:

  • Bruker/Veeco: .txt,头部以 \ 开头的注释行
  • JPK: .txt.jpk-force,带元数据头
  • Asylum: .txt.ibw(可用 igorpro 包读取)
  • 通用: .csv,列为 Z_piezo、Deflection

def load_afm_curve(filepath, skip_rows=0):
    """加载通用AFM力曲线文件"""
    data = np.loadtxt(filepath, skiprows=skip_rows)
    return data[:, 0], data[:, 1]

# 示例:Bruker力曲线
z_piezo, deflection = load_afm_curve("force_curve_001.txt", skip_rows=320)

处理Bruker头部

Bruker NanoScope文件头部长度不固定,下面是自动检测的鲁棒方法:


def load_bruker_curve(filepath):
    """自动检测Bruker头部长度并加载数据"""
    with open(filepath, "r") as f:
        lines = f.readlines()
    skip = 0
    for i, line in enumerate(lines):
        if line.strip().startswith("\\*") or "Ciao" in line:
            skip = i + 1
    data = np.loadtxt(filepath, skiprows=skip)
    return data[:, 0], data[:, 1]

3. 偏转-力转换

微悬臂偏转(单位:伏特)需转换为力(单位:纳牛):


def deflection_to_force(deflection_V, spring_constant, sensitivity_nm_per_V):
    """将偏转从伏特转换为力"""
    deflection_nm = deflection_V * sensitivity_nm_per_V
    force_N = spring_constant * deflection_nm * 1e-9
    return force_N * 1e9  # 转换为纳牛

典型值:软悬臂(如Bruker MLCT)


k = 0.06        # N/m,悬臂弹性常数
sensitivity = 45.0  # nm/V,偏转灵敏度
force = deflection_to_force(deflection, k, sensitivity)

4. 基线校正

非接触区的力曲线应该是平坦的(零力),但实际中存在热漂移和激光干涉造成的微小斜率:


def baseline_correct(z_piezo, force, contact_idx):
    """使用接触前区域进行线性基线校正"""
    baseline_region = slice(0, contact_idx)
    coeffs = np.polyfit(
        z_piezo[baseline_region], force[baseline_region], 1
    )
    baseline = np.polyval(coeffs, z_piezo)
    return force - baseline

5. 接触点检测

这是最关键的步骤。稳健方法:找力的导数显著偏离基线噪声的地方。


def find_contact_point(z_piezo, force, window=50, threshold_factor=5):
    """用导数阈值法定位针尖-样品接触点"""
    force_smooth = signal.savgol_filter(
        force, window_length=21, polyorder=3
    )
    dz = np.gradient(z_piezo)
    df = np.gradient(force_smooth)
    derivative = df / dz
    
    noise_region = slice(0, len(force) // 5)
    noise_std = np.std(derivative[noise_region])
    threshold = threshold_factor * noise_std
    
    above_threshold = np.abs(derivative) > threshold
    for i in range(len(above_threshold) - window):
        if np.all(above_threshold[i:i + window]):
            return i + window // 2

contact_idx = find_contact_point(z_piezo, force)
contact_z = z_piezo[contact_idx]
print(f"接触点: z = {contact_z:.1f} nm")

6. 粘附力提取

回退曲线上的最小力就是粘附力(拔拉事件):


def extract_adhesion(z_piezo, force, contact_idx):
    """从回退段提取粘附力"""
    retract_region = force[contact_idx:]
    if len(retract_region) == 0:
        return 0.0
    adhesion_idx = np.argmin(retract_region)
    adhesion_force = retract_region[adhesion_idx]
    return adhesion_force if adhesion_force < 0 else 0.0

7. Hertz模型拟合杨氏模量

球形压头在弹性半空间上的Hertz模型:

F = (4/3) E* sqrt(R) delta^(3/2)

其中 E* = E / (1 - nu^2)。


def hertz_sphere(indentation, E_star, R):
    """Hertz球压头模型"""
    return (4/3) * E_star * np.sqrt(R) * np.power(
        np.maximum(indentation, 0), 1.5
    )

def fit_youngs_modulus(z_piezo, force, contact_idx,
                        tip_radius_nm=20.0, poisson=0.5,
                        fit_range_nm=50.0):
    """拟合Hertz模型提取杨氏模量"""
    indent = z_piezo[contact_idx] - z_piezo
    contact_force = force - force[contact_idx]
    
    fit_mask = (
        (indent > 0) & (indent < fit_range_nm) & (contact_force > 0)
    )
    indent_fit = indent[fit_mask]
    force_fit = contact_force[fit_mask]
    
    if len(indent_fit) < 50:
        print("警告: 拟合区数据点太少")
        return np.nan, np.nan
    
    def model(delta, E_star):
        return hertz_sphere(delta, E_star, tip_radius_nm)
    
    popt, pcov = optimize.curve_fit(
        model, indent_fit, force_fit,
        p0=[1000], bounds=(1, 1e9)
    )
    E_star = popt[0]
    E = E_star * (1 - poisson**2)
    E_std = np.sqrt(pcov[0, 0]) * (1 - poisson**2)
    
    return E, E_std

8. 批量处理

一次典型实验通常有几十条力曲线:


import glob
from pathlib import Path

def batch_process_curves(data_dir, k=0.06, sensitivity=45.0):
    """批量处理AFM力曲线,提取力学性能"""
    results = []
    for filepath in sorted(glob.glob(f"{data_dir}/*.txt")):
        try:
            z, deflection = load_afm_curve(filepath, skip_rows=320)
            force = deflection_to_force(deflection, k, sensitivity)
            cidx = find_contact_point(z, force)
            force_corr = baseline_correct(z, force, cidx)
            adhesion = extract_adhesion(z, force_corr, cidx)
            E, E_std = fit_youngs_modulus(
                z, force_corr, cidx,
                tip_radius_nm=20.0, poisson=0.5
            )
            results.append({
                "file": Path(filepath).name,
                "contact_z_nm": z[cidx],
                "adhesion_nN": adhesion,
                "youngs_modulus_Pa": E,
            })
        except Exception as e:
            print(f"失败: {Path(filepath).name}: {e}")
    return results

# 使用示例
# results = batch_process_curves("./afm_data/")
# import pandas as pd
# df = pd.DataFrame(results)
# df.to_csv("mechanical_properties.csv", index=False)

9. 常见坑

问题可能原因解决方法
------------------------
接触点太早基线噪声大增大 threshold_factor
负杨氏模量接触点方向反了检查z_piezo符号约定
基底效应压入深度过大fit_range_nm < 样品厚度10%
零粘附回退段未记录AFM方法中启用回退
结果离散样品不均或针尖污染逐条检查再批量拟合

10. 替代模型

Hertz假设线弹性。对粘弹性生物材料,考虑:

  • JKR模型(Johnson-Kendall-Roberts):包含接触力学中的粘附
  • Ting模型:适用于粘弹性松弛
  • Sneddon模型:锥形压头(半角alpha)

def sneddon_cone(indentation, E_star, alpha_deg=18):
    """Sneddon锥形压头"""
    alpha = np.radians(alpha_deg)
    return (2 / np.pi) * E_star * np.tan(alpha) * np.power(indentation, 2)

参考资料

  • Cappella, B. & Dietler, G. (1999). Force-distance curves by atomic force microscopy. Surface Science Reports, 34(1-3), 1-104.
  • Hertz, H. (1882). On the contact of elastic solids. J. Reine Angew. Math., 92, 156-171.
  • Sneddon, I.N. (1965). The relation between load and penetration. Int. J. Eng. Sci., 3(1), 47-57.
  • JPK Instruments Application Note: A Practical Guide to AFM Force Spectroscopy and Data Analysis.

💬 Questions or Feedback?

This blog is actively maintained by a PhD researcher. Reach out on GitHub for collaborations or corrections.

View on GitHub