Skip to main content
Step 2 of the analysis pipeline computes dexterity metrics that quantify surgical skill based on fundamental physics principles. These metrics measure how smoothly, efficiently, and quickly the surgeon performs movements.

Function Signature

def _paso2_calcular_destreza(df: pd.DataFrame) -> Dict:
    """
    Calculates physics-based dexterity metrics from trajectory DataFrame.
    Returns dict with economia, velocities, acceleration, jerk, distance, and duration.
    """

Calculated Metrics

Velocity

Rate of position change (distance/time)

Acceleration

Rate of velocity change (Δv/Δt)

Jerk

Rate of acceleration change (Δa/Δt) - measures smoothness

Economy

Ratio of actual path length to direct distance

Complete Implementation

analysis_pipeline.py
def _paso2_calcular_destreza(df: pd.DataFrame) -> Dict:
    # Diferencias de posición
    dx = df["x"].diff().fillna(0)
    dy = df["y"].diff().fillna(0)
    dz = df["z"].diff().fillna(0)
    dist = np.sqrt(dx**2 + dy**2 + dz**2)
    
    # Velocidad (v = ds/dt)
    v = dist / df["dt"].replace(0, np.inf)
    v = v.replace(np.inf, 0)
    
    # Aceleración (a = dv/dt)
    a = v.diff() / df["dt"].replace(0, np.inf)
    a = a.replace(np.inf, 0)
    
    # Jerk (j = da/dt) - Suavidad
    j = a.diff() / df["dt"].replace(0, np.inf)
    j = j.replace(np.inf, 0)
    
    # Economía de movimiento
    total_dist = dist.sum()
    p1 = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
    p2 = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
    direct_dist = np.linalg.norm(p2 - p1)
    economia = total_dist / direct_dist if direct_dist > 0 else 1.0
    
    return {
        "economia": economia,
        "v_avg": v.mean(),
        "a_max": a.abs().max(),
        "j_avg": j.abs().mean(),
        "total_dist": total_dist,
        "duration": df["t"].iloc[-1]
    }

Detailed Breakdown

1. Distance Calculation

The Euclidean distance between consecutive points is computed using the 3D distance formula:
dx = df["x"].diff().fillna(0)
dy = df["y"].diff().fillna(0)
dz = df["z"].diff().fillna(0)
dist = np.sqrt(dx**2 + dy**2 + dz**2)
Formula: d=(Δx)2+(Δy)2+(Δz)2d = \sqrt{(\Delta x)^2 + (\Delta y)^2 + (\Delta z)^2}
The diff() operation computes the difference between consecutive rows, giving us Δx, Δy, Δz.

2. Velocity Calculation

Velocity is the rate of position change:
v = dist / df["dt"].replace(0, np.inf)
v = v.replace(np.inf, 0)
Formula: v=dsdtv = \frac{ds}{dt}
Division by zero is handled by replacing zero dt values with infinity, then replacing resulting infinities back to zero.

3. Acceleration Calculation

Acceleration measures how quickly velocity changes:
a = v.diff() / df["dt"].replace(0, np.inf)
a = a.replace(np.inf, 0)
Formula: a=dvdta = \frac{dv}{dt} High acceleration indicates rapid speed changes, which can suggest jerky or unstable movements.

4. Jerk Calculation

Jerk (the derivative of acceleration) indicates movement smoothness:
j = a.diff() / df["dt"].replace(0, np.inf)
j = j.replace(np.inf, 0)
Formula: j=dadtj = \frac{da}{dt}
Lower average jerk indicates smoother, more controlled movements - a key indicator of surgical skill.

5. Economy of Movement

Economy compares the actual path length to the shortest possible path:
total_dist = dist.sum()
p1 = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
p2 = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
direct_dist = np.linalg.norm(p2 - p1)
economia = total_dist / direct_dist if direct_dist > 0 else 1.0
Formula: E=total path lengthdirect distanceE = \frac{\text{total path length}}{\text{direct distance}}
Perfect economy - the surgeon moved in a perfectly straight line.
Good economy - path is only 20% longer than necessary.
Poor economy - path is twice as long as the direct route.

Output Structure

The function returns a dictionary with these keys:
KeyTypeDescriptionIdeal Value
economiafloatPath length ratio< 1.2
v_avgfloatAverage velocity (units/s)Moderate
a_maxfloatMaximum accelerationLow
j_avgfloatAverage jerk (smoothness)Low
total_distfloatTotal path lengthN/A
durationfloatTotal procedure time (s)N/A

Example Output

{
  "economia": 1.35,
  "v_avg": 2.73,
  "a_max": 8.42,
  "j_avg": 0.67,
  "total_dist": 123.45,
  "duration": 45.3
}

Interpretation Guide

  • Economy: 1.0 - 1.2x
  • Jerk: < 0.5 (smooth movements)
  • Max Acceleration: < 5.0
  • Duration: Appropriate for task complexity

Why These Metrics Matter

Velocity

Reveals hesitation (low v) or rushed movements (high v)

Acceleration

High peaks indicate sudden stops/starts, suggesting poor planning

Jerk

Direct measure of hand tremor and movement control

Economy

Indicates spatial planning and efficiency

Performance Optimization

All calculations use vectorized NumPy operations for maximum speed:
  • No loops: Entire columns processed at once
  • In-place operations: Minimal memory allocation
  • Efficient algorithms: O(n) complexity for all metrics
Typical performance: ~5ms for 5000 movements

Next Step

With dexterity metrics calculated, the pipeline moves to benchmarking:

Benchmarking

Compare performance against ideal patterns

Build docs developers (and LLMs) love