mcpField
T
trackpy-particle-tracking

trackpy-particle-tracking

github.com/jaechang-hits数据与数据库38469 次安装6 天前更新
代码仓库

关于 trackpy-particle-tracking

trackpy 是用于视频显微镜单粒子追踪的 Python 库,可从图像序列检测粒子并连接为轨迹,再分析位移与运动。适用于显微成像研究,支持二维和三维数据及流式读取。

主要功能

  1. 01亚像素精度粒子检测
  2. 02跨帧连接粒子轨迹
  3. 03过滤过短轨迹
  4. 04计算单粒子及群体 MSD
  5. 05表征运动并校正漂移

适用场景

  1. 01分析荧光视频中的粒子轨迹
  2. 02估算粒子扩散系数
  3. 03追踪共聚焦图像中的三维运动
  4. 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")

关键参数

参数模块默认值范围 / 选项效果
diameterlocate、batch必填奇数 ≥ 3(或用于 3D 的元组)粒子的近似直径(像素);必须为奇数。过小:检测结果分裂。过大:检测结果合并
minmasslocate、batch1000 至 ∞最小积分亮度;用于过滤噪声的主要参数。从 0 开始,绘制质量直方图,并设置该值以区分噪声峰
search_rangelink必填1–50 像素帧间最大位移。设置为每帧最大预期运动距离的约 1.5 倍
memorylink00–10 帧轨迹中断前允许粒子缺失的帧数;适用于闪烁的荧光团
thresholdfilter_stubs1整数 ≥ 1轨迹的最小帧数;较短轨迹的 MSD 不可靠
max_lagtimeimsd、emsd100整数MSD 计算的最大滞后时间(帧);为提高可靠性,使用总帧数的约 10–20%
mppimsd、emsd1浮点数 > 0每像素的微米数;将像素单位转换为物理单位(µm)
fpsimsd、emsd1浮点数 > 0每秒帧数;将帧滞后转换为秒
separationlocate、batchdiameter+1整数特征之间的最小中心间距;防止对密集粒子重复计数
invertlocate、batchFalseTrue、False对亮背景上的暗粒子(透射光成像)设置为 True

最佳实践

  1. 始终先在单帧上调整 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
    
  2. 谨慎设置 search_range:搜索范围过大会导致密集样本中不相关的粒子被错误关联。关联前,根据 tp.locate() 输出的散布情况估算每帧的典型位移。

  3. 计算 MSD 前先扣除漂移:载物台漂移会增大 MSD,导致高估 D。调用 tp.emsd() 前,务必先调用 tp.compute_drift() 和 tp.subtract_drift()。

  4. 拟合扩散系数时只使用线性区间:滞后时间较长时,MSD 曲线会变得嘈杂(参与计算的轨迹对较少)。只拟合可用滞后时间的前 10–20%。拟合前使用双对数斜率检测非布朗行为。

  5. 不要在定位和 MSD 步骤中混用 mpp 单位:tp.locate() 返回以像素为单位的位置。mpp 仅应用于 tp.imsd()/tp.emsd()。关联前不要手动缩放位置,否则会破坏以像素为单位的 search_range。

  6. 使用各向异性体素进行 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;如果帧率很高,可考虑对帧进行抽样

参考资料

评论

支持 Markdown