关于 trackpy-particle-tracking
trackpy 是用于视频显微镜单粒子追踪的 Python 库,可从图像序列检测粒子并连接为轨迹,再分析位移与运动。适用于显微成像研究,支持二维和三维数据及流式读取。
主要功能
- 01亚像素精度粒子检测
- 02跨帧连接粒子轨迹
- 03过滤过短轨迹
- 04计算单粒子及群体 MSD
- 05表征运动并校正漂移
适用场景
- 01分析荧光视频中的粒子轨迹
- 02估算粒子扩散系数
- 03追踪共聚焦图像中的三维运动
- 04校正台面漂移后分析 MSD
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill trackpy-particle-tracking安装后在 Claude Code 中输入/trackpy-particle-tracking确认已加载。
详细介绍
trackpy
概述
trackpy 是一个用于视频显微成像中单粒子跟踪(SPT)的 Python 库。它实现了 Crocker-Grier 算法,以亚像素精度定位每一帧中的亮点,然后将这些位置跨帧连接成连续轨迹。trackpy 根据轨迹计算均方位移(MSD)、扩散系数和运动分类(受限、正常、定向)。它支持 2D 荧光视频、3D 共聚焦 z-stack,并通过 pims 图像读取库进行内存高效的流式处理,以处理大型图像序列。
适用场景
- 你有一段标记粒子(量子点、荧光微珠、囊泡、受体)的荧光显微视频,需要提取各粒子的轨迹和扩散系数。
- 你想测量粒子的迁移率:计算 MSD 曲线,并根据单粒子轨迹区分布朗扩散、定向运动或受限运动。
- 你正在分析胶体动力学、脂质膜扩散、细胞内货物运输或病毒与细胞的相互作用,需要获取单粒子轨迹数据。
- 你需要从共聚焦 z-stack 时间序列中进行 3D 跟踪,以捕捉粒子或细胞器的面外运动。
- 你想在计算粒子的内禀运动统计量之前进行漂移校正,以消除载物台漂移。
- 你需要对数百条轨迹计算集合平均 MSD,以通过统计分析提取群体层面的扩散行为。
- 如果你需要图形界面、手动整理轨迹,或与生物对象分割器(Cellpose、StarDist)集成,请改用
TrackMate(Fiji/ImageJ 插件)。 - 如果你想在图像数据旁交互式地可视化并手动编辑轨迹,请改用
napari和napari-trackpy。
先决条件
- Python 包:
trackpy、pims、pandas、numpy、matplotlib、scipy - 数据要求:灰度或单通道图像序列(TIF 堆栈、AVI 文件,或包含 PNG/TIF 帧的目录);粒子应呈现为暗背景上的亮高斯斑点(对于亮背景上的暗斑点,可使用
invert=True) - 环境:可在 Jupyter Notebook 和脚本中运行;
pims可处理大多数显微成像格式;对于 ND2 或 CZI 文件,请安装pims-nd2或aicsimageio
pip install trackpy pims pandas numpy matplotlib scipy
# For reading multi-channel or proprietary formats:
pip install pims[bioformats] # Bioformats via JPype
pip install aicsimageio # ND2, CZI, LIF via AICSImageIO
快速入门
import trackpy as tp
import pims
# Load a TIF image stack (T frames × Y × X)
frames = pims.open("particles.tif") # shape: (T, Y, X)
# Locate particles in all frames
f = tp.batch(frames, diameter=11, minmass=500)
print(f"Found {len(f)} particle detections across {f['frame'].nunique()} frames")
# Link into trajectories
t = tp.link(f, search_range=5, memory=3)
# Remove short-lived tracks (fewer than 10 frames)
t = tp.filter_stubs(t, threshold=10)
print(f"Retained {t['particle'].nunique()} trajectories")
# Compute ensemble MSD
imsd = tp.imsd(t, mpp=0.16, fps=10) # mpp: microns per pixel, fps: frames per second
print(imsd.head())
核心 API
模块 1:tp.locate() — 单帧粒子检测
tp.locate() 使用带通滤波器和局部极大值检测,在单张图像帧中查找明亮的圆形特征。它会返回一个 DataFrame,其中包含每个检测到的粒子的亚像素 x/y 位置、积分质量、信号和偏心率。
import trackpy as tp
import pims
import matplotlib.pyplot as plt
frames = pims.open("particles.tif")
frame0 = frames[0] # single 2D array
# Locate particles: diameter must be odd integer, roughly matching spot size in pixels
f0 = tp.locate(frame0, diameter=11, minmass=300, maxsize=None, separation=None)
print(f"Detected {len(f0)} particles in frame 0")
print(f0[['x', 'y', 'mass', 'size', 'ecc']].head())
# x, y: subpixel centroid; mass: integrated brightness; size: Gaussian width; ecc: eccentricity (0=circular)
# Diagnostic plot: annotate detected particles on the raw frame
fig, ax = plt.subplots(figsize=(8, 8))
tp.annotate(f0, frame0, ax=ax, imshow_style={"cmap": "gray"})
ax.set_title(f"Frame 0: {len(f0)} particles detected")
plt.tight_layout()
plt.savefig("locate_diagnostic.png", dpi=150)
print("Saved locate_diagnostic.png")
模块 2:tp.batch() — 多帧检测
tp.batch() 对图像序列中的每一帧应用 tp.locate(),并将结果合并为一个带有 frame 列的 DataFrame。它接受任何兼容 pims 的图像读取器或 2D 数组列表。
import trackpy as tp
import pims
frames = pims.open("particles.tif")
# Locate particles across all frames (same parameters as tp.locate)
f = tp.batch(frames, diameter=11, minmass=300, processes=1)
# processes=1 uses serial processing; set processes="auto" for multicore (requires joblib)
print(f"Total detections: {len(f)}")
print(f"Frames with data: {f['frame'].nunique()} / {len(frames)}")
print(f"Mean particles per frame: {len(f)/f['frame'].nunique():.1f}")
print(f.groupby('frame').size().describe())
# Mass histogram: use to choose minmass cutoff
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4))
f['mass'].hist(bins=40, ax=ax)
ax.axvline(300, color='red', linestyle='--', label='minmass=300')
ax.set_xlabel("Integrated mass")
ax.set_ylabel("Count")
ax.set_title("Mass distribution of detections")
ax.legend()
plt.tight_layout()
plt.savefig("mass_histogram.png", dpi=150)
print("Saved mass_histogram.png — use to refine minmass cutoff")
模块 3:tp.link() — 轨迹连接
tp.link() 通过求解二分图匹配问题(匈牙利算法),将跨帧的粒子检测结果连接成轨迹。它会向位置 DataFrame 添加 particle 列(整数轨迹 ID)。search_range(像素)是帧间最大位移;memory 允许粒子消失最多 N 帧,之后才将其丢弃。
import trackpy as tp
import pims
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=300)
# Link: search_range in pixels; memory handles brief disappearances (blinking, out-of-focus)
t = tp.link(f, search_range=5, memory=3)
print(f"Number of unique trajectories: {t['particle'].nunique()}")
print(f"Trajectory length distribution:")
print(t.groupby('particle').size().describe())
# Visualize all trajectories overlaid on the first frame
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 8))
tp.plot_traj(t, superimpose=frames[0], ax=ax)
ax.set_title(f"{t['particle'].nunique()} trajectories")
plt.tight_layout()
plt.savefig("trajectories.png", dpi=150)
print("Saved trajectories.png")
模块 4:tp.filter_stubs() — 移除短轨迹
tp.filter_stubs() 会移除短于指定帧数的轨迹。短轨迹可能由噪声检测、粒子进入或离开视野,或连接错误导致。移除这些轨迹可以提高 MSD 的可靠性,因为短轨迹会在较长滞后时间下产生高方差的 MSD 估计值。
import trackpy as tp
import pims
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=300)
t = tp.link(f, search_range=5, memory=3)
before = t['particle'].nunique()
t_filt = tp.filter_stubs(t, threshold=10) # keep only tracks with ≥10 frames
after = t_filt['particle'].nunique()
print(f"Tracks before filtering: {before}")
print(f"Tracks after filtering (≥10 frames): {after}")
print(f"Removed {before - after} short tracks ({100*(before-after)/before:.1f}%)")
模块 5:MSD 分析 — tp.imsd() 和 tp.emsd()
tp.imsd() 计算每个粒子随滞后时间变化的均方位移,返回一个 DataFrame(滞后时间为索引,粒子 ID 为列)。tp.emsd() 计算所有粒子的系综平均 MSD。两者都需要物理尺度(mpp,每像素的微米数)和帧率(fps)。
import trackpy as tp
import pims
import matplotlib.pyplot as plt
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=300)
t = tp.link(f, search_range=5, memory=3)
t = tp.filter_stubs(t, threshold=10)
mpp = 0.16 # microns per pixel (from microscope calibration)
fps = 10.0 # frames per second
# Individual MSD curves (one column per particle)
imsd = tp.imsd(t, mpp=mpp, fps=fps, max_lagtime=100)
print(f"IMSD shape: {imsd.shape}") # (lag times) × (particles)
# Ensemble MSD
emsd = tp.emsd(t, mpp=mpp, fps=fps, max_lagtime=100)
print(f"EMSD at lag 1 s: {emsd.iloc[0]:.4f} µm²")
# Plot ensemble MSD and fit diffusion coefficient
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import linregress
mpp = 0.16
fps = 10.0
# Fit MSD = 4*D*t (2D Brownian) over first 10 lag times
lag_s = emsd.index.values[:10] # lag times in seconds
msd_vals = emsd.values[:10]
slope, intercept, r, p, se = linregress(lag_s, msd_vals)
D = slope / 4 # diffusion coefficient in µm²/s
print(f"Diffusion coefficient D = {D:.4f} µm²/s (R²={r**2:.3f})")
fig, ax = plt.subplots(figsize=(6, 5))
ax.plot(emsd.index, emsd.values, 'o-', label='Ensemble MSD')
ax.plot(lag_s, slope * lag_s + intercept, 'r--', label=f'Fit: D={D:.4f} µm²/s')
ax.set_xlabel("Lag time (s)")
ax.set_ylabel("MSD (µm²)")
ax.set_title("Ensemble Mean Squared Displacement")
ax.legend()
plt.tight_layout()
plt.savefig("emsd.png", dpi=150)
print("Saved emsd.png")
模块 6:运动分析 — 特征分析和漂移校正
tp.motion.characterize() 计算每条轨迹的统计数据(平均速度、净位移、直线度)。tp.subtract_drift() 在 MSD 分析前从轨迹中去除整体载物台漂移。
import trackpy as tp
import pims
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=300)
t = tp.link(f, search_range=5, memory=3)
t = tp.filter_stubs(t, threshold=10)
# Estimate and subtract drift (bulk movement of the sample/stage)
drift = tp.compute_drift(t)
print("Drift (first 5 frames):")
print(drift.head())
t_corrected = tp.subtract_drift(t.copy(), drift)
print(f"Drift subtracted from {t_corrected['particle'].nunique()} trajectories")
import trackpy as tp
# Characterize individual trajectories (requires tp.motion module)
from trackpy import motion
# Per-particle summary statistics
char = motion.characterize(t, mpp=0.16, fps=10.0)
print(char.columns.tolist())
# Columns: 'alpha' (anomalous exponent), 'D_app' (apparent diffusion), 'r^2' (fit quality)
print(char[['alpha', 'D_app']].describe())
# alpha ~ 1.0: Brownian; alpha < 1: confined/subdiffusion; alpha > 1: directed/superdiffusion
常见工作流
工作流 1:包含 MSD 和扩散系数的完整 2D 追踪流程
目标:加载荧光视频,定位粒子并在所有帧间进行链接,筛除较短的轨迹,计算 MSD,并提取扩散系数。
import trackpy as tp
import pims
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import linregress
# ── 1. Load image sequence ──────────────────────────────────────────────────
frames = pims.open("fluorescence_video.tif") # (T, Y, X) grayscale TIF stack
print(f"Loaded {len(frames)} frames, frame shape: {frames.frame_shape}")
# ── 2. Tune detection on a single frame ─────────────────────────────────────
f0 = tp.locate(frames[0], diameter=11, minmass=400)
print(f"Frame 0: {len(f0)} particles detected")
# Adjust diameter (odd integer ≥ spot size) and minmass until count looks right
# ── 3. Batch detect across all frames ───────────────────────────────────────
f = tp.batch(frames, diameter=11, minmass=400, processes=1)
print(f"Total detections: {len(f)} across {f['frame'].nunique()} frames")
# ── 4. Link into trajectories ────────────────────────────────────────────────
t = tp.link(f, search_range=6, memory=3)
print(f"Unique trajectories before filtering: {t['particle'].nunique()}")
# ── 5. Remove short trajectories ─────────────────────────────────────────────
t = tp.filter_stubs(t, threshold=15)
print(f"Trajectories after filtering (≥15 frames): {t['particle'].nunique()}")
# ── 6. Subtract stage drift ───────────────────────────────────────────────────
drift = tp.compute_drift(t)
t = tp.subtract_drift(t.copy(), drift)
# ── 7. Compute MSD ────────────────────────────────────────────────────────────
mpp = 0.16 # µm/pixel — from microscope calibration
fps = 10.0 # frames per second
emsd = tp.emsd(t, mpp=mpp, fps=fps, max_lagtime=50)
imsd = tp.imsd(t, mpp=mpp, fps=fps, max_lagtime=50)
# ── 8. Fit diffusion coefficient from linear regime (first 10 points) ─────────
n_fit = 10
lag_s = emsd.index.values[:n_fit]
msd_v = emsd.values[:n_fit]
slope, intercept, r, _, _ = linregress(lag_s, msd_v)
D = slope / 4 # MSD = 4Dt for 2D Brownian
print(f"Diffusion coefficient D = {D:.4f} µm²/s (R²={r**2:.3f})")
# ── 9. Plot ────────────────────────────────────────────────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Individual + ensemble MSD
axes[0].plot(imsd.index, imsd.values, alpha=0.2, color='steelblue', linewidth=0.8)
axes[0].plot(emsd.index, emsd.values, 'k-', linewidth=2, label='Ensemble MSD')
axes[0].plot(lag_s, slope * lag_s + intercept, 'r--', label=f'D={D:.4f} µm²/s')
axes[0].set_xlabel("Lag time (s)")
axes[0].set_ylabel("MSD (µm²)")
axes[0].set_title("MSD: individual (blue) + ensemble (black)")
axes[0].legend()
# Trajectory overlay
tp.plot_traj(t, superimpose=frames[0], ax=axes[1])
axes[1].set_title(f"{t['particle'].nunique()} trajectories")
plt.tight_layout()
plt.savefig("tracking_results.png", dpi=150, bbox_inches="tight")
print("Saved tracking_results.png")
# ── 10. Export trajectories ───────────────────────────────────────────────────
t.to_csv("trajectories.csv", index=False)
emsd.to_csv("ensemble_msd.csv")
print("Exported trajectories.csv and ensemble_msd.csv")
工作流 2:从共聚焦 Z 堆栈进行 3D 粒子追踪
目标:从时间序列的共聚焦 Z 堆栈(T × Z × Y × X)中以 3D 方式追踪粒子,在 3D 中进行链接,并计算 3D MSD。
import trackpy as tp
import pims
import numpy as np
import matplotlib.pyplot as plt
# ── 1. Load 4D image stack (T × Z × Y × X) ─────────────────────────────────
# pims opens multi-page TIF; reshape into (T, Z, Y, X) as needed
raw = pims.open("confocal_3d_timeseries.tif")
# Assume each "frame" in pims is one Z-slice; reshape to (T, Z, Y, X)
T, n_z = 50, 20 # adjust to match acquisition
frames_4d = np.array(raw).reshape(T, n_z, raw.frame_shape[0], raw.frame_shape[1])
print(f"4D stack shape: {frames_4d.shape}") # (T, Z, Y, X)
# ── 2. Detect in 3D (tp.locate works on 3D arrays) ─────────────────────────
# For 3D, pass a single 3D volume; diameter can be (z_diam, y_diam, x_diam)
f0_3d = tp.locate(frames_4d[0], diameter=(7, 11, 11), minmass=2000)
print(f"3D detections in t=0: {len(f0_3d)}")
print(f0_3d[['x', 'y', 'z', 'mass']].head())
# ── 3. Batch detect across all time points ──────────────────────────────────
detections = []
for t_idx in range(T):
frame_3d = frames_4d[t_idx] # Z × Y × X
detected = tp.locate(frame_3d, diameter=(7, 11, 11), minmass=2000)
detected['frame'] = t_idx
detections.append(detected)
import pandas as pd
f3d = pd.concat(detections, ignore_index=True)
print(f"Total 3D detections: {len(f3d)}")
# ── 4. Link in 3D ────────────────────────────────────────────────────────────
# search_range in pixels; use a 3-tuple (z, y, x) for anisotropic voxels
t3d = tp.link(f3d, search_range=(3, 6, 6), memory=2)
t3d = tp.filter_stubs(t3d, threshold=10)
print(f"3D trajectories: {t3d['particle'].nunique()}")
# ── 5. Compute 3D MSD ────────────────────────────────────────────────────────
mpp_xy = 0.16 # µm/pixel in x, y
mpp_z = 0.30 # µm/pixel in z (z-step size)
fps = 1.0 # z-stack volume rate
# Scale z coordinates to µm
t3d_um = t3d.copy()
t3d_um['x'] *= mpp_xy
t3d_um['y'] *= mpp_xy
t3d_um['z'] *= mpp_z
emsd_3d = tp.emsd(t3d_um, mpp=1.0, fps=fps, max_lagtime=20) # mpp=1 since already in µm
print(f"3D ensemble MSD (lag=1 s): {emsd_3d.iloc[0]:.4f} µm²")
# ── 6. Fit 3D diffusion coefficient (MSD = 6Dt for 3D) ─────────────────────
from scipy.stats import linregress
lag_s = emsd_3d.index.values[:8]
slope, _, r, _, _ = linregress(lag_s, emsd_3d.values[:8])
D_3d = slope / 6
print(f"3D Diffusion coefficient D = {D_3d:.4f} µm²/s (R²={r**2:.3f})")
t3d.to_csv("trajectories_3d.csv", index=False)
print("Saved trajectories_3d.csv")
关键参数
| 参数 | 模块 | 默认值 | 范围 / 选项 | 效果 |
|---|---|---|---|---|
diameter | locate、batch | 必填 | 奇数 ≥ 3(或用于 3D 的元组) | 粒子的近似直径(像素);必须为奇数。过小:检测结果分裂。过大:检测结果合并 |
minmass | locate、batch | 100 | 0 至 ∞ | 最小积分亮度;用于过滤噪声的主要参数。从 0 开始,绘制质量直方图,并设置该值以区分噪声峰 |
search_range | link | 必填 | 1–50 像素 | 帧间最大位移。设置为每帧最大预期运动距离的约 1.5 倍 |
memory | link | 0 | 0–10 帧 | 轨迹中断前允许粒子缺失的帧数;适用于闪烁的荧光团 |
threshold | filter_stubs | 1 | 整数 ≥ 1 | 轨迹的最小帧数;较短轨迹的 MSD 不可靠 |
max_lagtime | imsd、emsd | 100 | 整数 | MSD 计算的最大滞后时间(帧);为提高可靠性,使用总帧数的约 10–20% |
mpp | imsd、emsd | 1 | 浮点数 > 0 | 每像素的微米数;将像素单位转换为物理单位(µm) |
fps | imsd、emsd | 1 | 浮点数 > 0 | 每秒帧数;将帧滞后转换为秒 |
separation | locate、batch | diameter+1 | 整数 | 特征之间的最小中心间距;防止对密集粒子重复计数 |
invert | locate、batch | False | True、False | 对亮背景上的暗粒子(透射光成像)设置为 True |
最佳实践
-
始终先在单帧上调整
diameter和minmass:对一个有代表性的帧运行tp.locate(),并使用tp.annotate()直观检查检测结果,然后再运行tp.batch()。过度检测会浪费时间;检测不足则会漏掉粒子。f0 = tp.locate(frames[0], diameter=11, minmass=200) tp.annotate(f0, frames[0]) # visual check in Jupyter -
谨慎设置
search_range:搜索范围过大会导致密集样本中不相关的粒子被错误关联。关联前,根据tp.locate()输出的散布情况估算每帧的典型位移。 -
计算 MSD 前先扣除漂移:载物台漂移会增大 MSD,导致高估 D。调用
tp.emsd()前,务必先调用tp.compute_drift()和tp.subtract_drift()。 -
拟合扩散系数时只使用线性区间:滞后时间较长时,MSD 曲线会变得嘈杂(参与计算的轨迹对较少)。只拟合可用滞后时间的前 10–20%。拟合前使用双对数斜率检测非布朗行为。
-
不要在定位和 MSD 步骤中混用
mpp单位:tp.locate()返回以像素为单位的位置。mpp仅应用于tp.imsd()/tp.emsd()。关联前不要手动缩放位置,否则会破坏以像素为单位的 search_range。 -
使用各向异性体素进行 3D 跟踪时:将
diameter和search_range设为与(z, y, x)轴顺序对应的元组。z 方向步长通常比 xy 像素尺寸粗 2–5 倍;相应设置diameter和search_range的 z 分量。
常用方案
方案:漂移校正及校正前后 MSD 对比
适用场景:比较原始 MSD 与漂移校正后的 MSD,以评估载物台漂移的影响。
import trackpy as tp
import pims
import matplotlib.pyplot as plt
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=400, processes=1)
t = tp.link(f, search_range=6, memory=3)
t = tp.filter_stubs(t, threshold=15)
mpp, fps = 0.16, 10.0
# MSD without drift correction
emsd_raw = tp.emsd(t, mpp=mpp, fps=fps, max_lagtime=50)
# Subtract drift
drift = tp.compute_drift(t)
t_corr = tp.subtract_drift(t.copy(), drift)
emsd_corr = tp.emsd(t_corr, mpp=mpp, fps=fps, max_lagtime=50)
fig, ax = plt.subplots(figsize=(6, 5))
ax.loglog(emsd_raw.index, emsd_raw.values, 'r--', label='Raw MSD')
ax.loglog(emsd_corr.index, emsd_corr.values, 'b-', label='Drift-corrected MSD')
ax.set_xlabel("Lag time (s)")
ax.set_ylabel("MSD (µm²)")
ax.set_title("Effect of drift correction on MSD")
ax.legend()
plt.tight_layout()
plt.savefig("drift_correction_comparison.png", dpi=150)
print("Saved drift_correction_comparison.png")
方案:按扩散状态分类粒子
适用场景:根据双对数 MSD 斜率(反常指数 alpha),将粒子群体分为受限运动、正常(布朗)运动和定向运动。
import trackpy as tp
import pims
import numpy as np
import pandas as pd
from scipy.stats import linregress
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=400, processes=1)
t = tp.link(f, search_range=6, memory=3)
t = tp.filter_stubs(t, threshold=20)
drift = tp.compute_drift(t)
t = tp.subtract_drift(t.copy(), drift)
mpp, fps = 0.16, 10.0
imsd = tp.imsd(t, mpp=mpp, fps=fps, max_lagtime=30)
# Fit log-log slope (anomalous exponent alpha) for each particle
results = []
for pid in imsd.columns:
curve = imsd[pid].dropna()
if len(curve) < 5:
continue
log_lag = np.log(curve.index.values)
log_msd = np.log(curve.values)
slope, intercept, r, _, _ = linregress(log_lag[:10], log_msd[:10])
D_app = np.exp(intercept) / 4 # apparent D from intercept
results.append({'particle': pid, 'alpha': slope, 'D_app': D_app, 'r2': r**2})
df_char = pd.DataFrame(results)
# Classify by alpha
df_char['regime'] = pd.cut(
df_char['alpha'],
bins=[-np.inf, 0.7, 1.3, np.inf],
labels=['confined', 'brownian', 'directed']
)
print(df_char['regime'].value_counts())
print(f"\nMean D by regime:\n{df_char.groupby('regime')['D_app'].mean()}")
df_char.to_csv("particle_classification.csv", index=False)
print("Saved particle_classification.csv")
方案:按偏心率筛选以去除聚集体
适用场景:排除通过质量阈值但形状细长的非圆形检测结果(双联体、聚集体、碎屑)。
import trackpy as tp
import pims
frames = pims.open("particles.tif")
f = tp.batch(frames, diameter=11, minmass=400, processes=1)
# Eccentricity: 0 = perfect circle, 1 = line
# Remove elongated features (likely aggregates or debris)
f_round = f[f['ecc'] < 0.3]
print(f"Before ecc filter: {len(f)} detections")
print(f"After ecc filter (ecc<0.3): {len(f_round)} detections")
print(f"Removed: {len(f)-len(f_round)} elongated features")
t = tp.link(f_round, search_range=6, memory=3)
t = tp.filter_stubs(t, threshold=10)
print(f"Trajectories after eccentricity filtering: {t['particle'].nunique()}")
故障排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 虚假检测过多 | minmass 过低,或 diameter 与斑点尺寸不匹配 | 绘制质量直方图;将 minmass 提高至噪声峰与信号峰之间的谷值。确认 diameter 与实际斑点宽度(像素)相符 |
| 检测结果很少或为零 | minmass 过高,或粒子较暗/失焦 | 降低 minmass;检查图像对比度;定位前执行背景扣除 |
| 轨迹非常短(所有短轨迹均被滤除) | 对于粒子速度而言,search_range 过小;或粒子闪烁时 memory=0 | 将 search_range 增至每帧最大位移的 1.5–2 倍;对于闪烁染料,将 memory 设为 2 或 3 |
| 滞后时间较长时,MSD 曲线嘈杂或不单调 | 轨迹太少,或拟合的滞后点过多 | 拟合时仅使用滞后时间的前 10–20%;确保集合 MSD 至少有 50 条轨迹 |
| 漂移校正使 MSD 变差 | 静止参考粒子太少;漂移是根据运动粒子估算的 | 加入基准微珠或静止粒子;仅对已知静止的粒子使用 tp.compute_drift() |
tp.batch() 期间出现 MemoryError | 所有帧一次性加载到 RAM 中 | 使用 pims 延迟读取器(默认);设置 processes=1;通过循环调用 tp.locate() 分块处理帧 |
3D locate 仅返回 2D 位置 | 传入的是 2D 帧,而非 3D 体数据 | 确认输入数组有 3 个维度 (Z, Y, X);检查 frames_4d[t_idx].ndim == 3 |
| 关联后的轨迹被拆分为许多短片段 | 粒子在帧间移动速度超过 search_range | 增大 search_range;增大 memory;如果帧率很高,可考虑对帧进行抽样 |
参考资料
- trackpy 文档 — 官方 API 参考、教程和笔记本
- trackpy GitHub 仓库 — 源代码、问题跟踪器和示例笔记本
- Crocker, J.C. & Grier, D.G. (1996). Methods of Digital Video Microscopy for Colloidal Studies. J. Colloid Interface Sci. 179, 298–310 — 原始算法论文
- pims 文档 — 与 trackpy 集成的图像序列读取库
- trackpy 分步讲解笔记本 — 2D 跟踪的分步教程